How to get the particular item's cell index (item#) in RG or WF step

A tip to get the particular item’s cell index (item#) in RG or when using the list in WF (“make changes in the list” step) to address the previous or next item in the list:

  1. Take the list of types
  2. :format as text of id’s separated by comma
  3. extract with Regex all the commas before the target item’s id. Regex: ,(?=.*?item_id)
  4. count commas + 1
  5. you got the target item’s cell index (item# in the list)

P.S. I’m not sure how that performs on a very long list, but on a list of 100 items worked as a charm.

7 Likes

What is your use case you needed the cell number of an item while running a workflow?

Backend API WF → I have a list of blog posts sorted by ‘likes’ number. I needed to compare a particular blog post’s ‘views’ number with the previous blog post’s ‘views’ number in that list and run the next steps based on the comparison result.

1 Like

Wouldn’t a :count of the list + 1 work the same? :thinking:

How? Tell me the WF sentence. How do you know the particular item’s place somewhere inside the list, to address the prior item? Actually, the flow, I made, was for complex financial calculations which involve many years of data. So, I needed to compare some financial data between the years, and those years were sorted by some dynamic field, not the date. The example with blog posts was to simplify understanding of the issue.

As I mentions at the beginning, it isn’t only about the cell’s number but rather item# on the list.

From what I understand of your original post, that’s pretty much what your doing? Counting the list?

I’ve been meaning to respond to this: tl;dr this is pretty freaking brilliant.

For those asking: What the OP (@gs1) explains is a very clever (in my estimation) workaround for the fact that Bubble has no native “indexOf()” function.

It takes advantage of the fact that regular expressions have an implicit “up to” function.

What @gs1’s format-then-regex does is execute what’s essentially a “first index of” function. It does this by (1) changing the list to a comma-separated list of text blobs (that we don’t care about, but it’s important that they are unique) and then (2) spitting out the separators (commas) between them. This results in a list of commas (could be any other separator, of course, but comma is the default) that represents the “spaces” between what were previously the unique items (converted to strings)… BUT ONLY UP TO the item in question (item_id). Upon finding that item, under the hood, we either stop, or continue thru the entire array, depending upon the implementation of regex.

This has the net effect of giving us the zero-based index of the item in question (by simply examining how many commas we are left with).

Consider: If the original list is just [item], the text-formatted list will have no commas in it. So, there must have only been one item. Note that, if the original list was empty, we would also get the same result, but should we need to, we could work around that with a condition. That is, we have a list of items, we can tell if it has any items or not. If it has no items, its length [its :count] will be zero. The list has no items [duh] and so we’ve already answered the question of where the item is in the list… which is to say, it cannot be present, as no items are present in the list. So we could just skip the regex step in this case.

However, if we know that our list always has items (or we determine that the original list has more than zero items based on the check described above and proceed), now the count of the commas is in fact the first zero-based index of the item in question in the list.

So, this is NOT the same as the count of the whole (prior-to-being-transformed) list. It’s a representation of where item_id first appears in the list. If item_id appears in the first position (index 0 as most languages would have it, or index 1 as Bubble would have it), there will be no commas. If item_id appears second in the list, there will be one comma (index 1 as most languages would have it, or 2 as Bubble would have it)… Etc.

To @gs1’s implied question about “is this performant?”, the answer is that it’s certainly NOT as performant as the browser’s native indexOf() function. However, it’s not much worse either. Consider that in this version, we do two things:

(1) a map function to iterate over the entire original array (list) to turn the list of Thing objects into their unique ID (in JS, psuedo-code: Array.map(item => function(get unique ID of item)). The get function may involve a database operation if Bubble doesn’t already know the unique ID of the Thing, but in most contexts it already does. So, just looping over the whole array once. (We do this all the time in JS, especially if we are lazy.)

(2) a second map function to traverse the list again, comparing the unique ID strings via regex to see if they match the condition. Depending upon the implementation of regex, we might traverse the entire list once more to arrive at the result. But a good implementation might only need to traverse the list up to the point where it identifies item_id and then automatically stops.

(3) the end result is an array, which (like Bubble’s “lists”, with their :count property) has a length property, which we can now inspect to understand the zero-based position of our target item in the original list (simply add 1 to it to get the 1-based position).

Regardless of how (2) works under-the-hood, the worst-case scenario would seem to be that we have to traverse the entire array twice to come to our understanding of where the target item is in the list.

In a native implementation of indexOf(), if we didn’t have to do the first map operation, we would only traverse the array at worst once. (If the target item is at the very END of the array – or if the item isn’t in the array – we will have to traverse the entire array once to determine this. If the target item is at the beginning of the array, we’d be done immediately.)

But in the @gs1 case, at worst it seems we’d have to do this (traverse the entire array) 2 or 3 times (depending on how regex works in a given browser’s implementation). That is, this “hack” at worst only takes 2-3x as long as a native indexOf() call, as far as I can fathom (speaking theoretically).

So, this is not terrible at all and, for any reasonable in-page operation, is VERY unlikely to be perceptibly slower than the native call. (If the original array is 100Ks of items, well 2-3x difference starts to become noticeable over the 1x case, but for numbers of items below this might well not even be detectable given that the resolution of the JavaScript date object is 1ms.)

Note that the speed of modern devices and modern, array-oriented JavaScript syntax conspire against us (in the interest of code-readability and general laziness) to encourage sequential array-based operations. For example, we might often write array.map(something).filter(something).replace_operation(something) (which will iterate over our array at least two full times) even though we know we could write the “map each item, filter each item” function in a more efficient way, because, practically speaking, it doesn’t effing matter.

So, my hat’s off to you, @gs1. :tophat: Good one.

2 Likes

Wow, @keith, thank you! It’s an honor for me to get “hat’s off” from a pro like you :slightly_smiling_face:
Also, thanks for the brilliant explanation of how it works!

1 Like

Great. I need indices. And I’ve been banging my head against (soft) things now for a day and half a night. The “content creators” flood the zone with how to use :get first and :get last (um, duh?) but I can find nothing that leans into what seems to be a years-old omission, and not a trivial one at that. I’m new to Bubble—surely this topic has come up? What sayeth the company on this?

Frankly its infuriating that I can’t run something like Item n's cumulative_expenses=Item n's current_expense + Item n-1 's cumulative_expenses.on a list of Expenses after a new one is inserted or an old one is changed. Its straightforward and concise in Java or Python. (Well, more concise in Python.). And I cannot fathom why I can :count and select :item # yet can’t do current Item ElementIndex. (Nor can I understand why I must compose complex expressions within a field less than 2" wide, in what must be the furthest-removed thing from a “visual programming environment”, but I won’t start on that or my dog will get upset).

Suffice it to say that I’m beginning to regret the two solid weeks of grinding thru content I paid to look at in order to pay a monthly fee to use software that ain’t so hot.

But on a positive note, perhaps I’m not just a dim brutish oaf who just doesn’t get it yet. Clearly minds more capable than mine have grappled with this same problem. And there are solutions. I found a plug in. And finally I found this superbly-crafted treatise (wow, Keith, just…wow) on Gs1’s regex trickery. And since there’s little I love more than very clever shite, I’ll stop my sleep-deprived ranting into space and tackle it…

works like a charm, thanks!

1 Like

Can you elaborate a bit on how step 4 works exactly? I have a repeating group of items, where i want to “deny access” to the current cell’s content if previous cell hasn’t been solved. In this instance, I want to check if “Journal_ID” is equal to previous cell’s unique ID (if solved, the unique ID would be stored in the user’s “Journal_ID” database).

image

Is this applicable here?

In step 4, I count how many items are on the list before the ‘item-in-question’. Then, that number + 1 will be the the ‘item-in-question’ item# on the list. By adding or deducting numbers, you can find previous or next items

@gs1 This is genius. Thanks so much for sharing it.

1 Like

Sir I can’t thank you enough for this trick! I am finally able to “bulk edit things” using the Make changes to a list of things in combination with this.

The process of updating thousands of records takes a few seconds instead of multiple minutes, this is a huge improvement for my app.

Thanks again!!

1 Like

Can you share a bit more details on the way you’ve used the trick?

I am using it to match a list of twitter accounts (fetched via api) to my database user. By using your trick I’m able to get the index of each account item from the api response in a “modify a list of thing” action. Then all matching accounts will get their social metrics updated in one go!

Hope it makes sense, cheers!

1 Like

Used this method when generating an SRT file, and I’ll be damned, I lost 1h trying to find a good method before falling on yours - THANK YOU