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. Good one.