FLOPPY: Plugin for localStorage, sessionStorage, IndexedDB storage, List Creation/Manipulation, Iteration, and More! Now with even more video docs!

Whenever you see that sort of behavior (this works in step mode, but not in real-time), it’s because the list you’re trying to operate on is not actually populated yet, @david_t.

Are you creating the date range list via process list action or something? If so, you can’t just fire process list and then, in the next workflow step, expect List Shifter’s Processed List to be populated. However, list shifter will tell you that Process List is done via an Event. (I’m AFK at the moment, but it’s called something like Process List Complete or similar.) What you need to do is create a new workflow that triggers off of that event and do your next steps.

(Even if I’m guessing wrong about what you’re doing, the explanation is the same. You’re experiencing asynchronous behavior, which is how web applications generally operate, which is why my plugins generally throw lots of events so that you can know when something is finished and ready for further processing!)

Feel free to provide more details about what you’re doing, with screenshots.

I should add: For a general exploration of issues around synchronous/asynchronous behavior of plugins wrt native Bubble actions, see the video posted here (higher up the thread):

Thanks for the response. I should have thought of the async thing; the linked discussion was useful.

I put a 1 second pause in the workflow between the API call and the list iteration start and that’s worked, but seems a bit unreliable for production. I’ll have a look through the video.

Here’s the scenario. It’s just a test page but the eventual implementation will be for an appointments system (probably obvious).

I have an API call that gets a JSON object from Google (a Google user’s calendar freebusy data for a time period). The JSON looks like:

{
“kind”: “calendar#freeBusy”,
“timeMin”: “2023-06-20T00:00:01.000Z”,
“timeMax”: “2023-06-30T23:59:59.000Z”,
“calendars”: {
“primary”: {
“busy”: [
{
“start”: “2023-06-28T11:00:00+10:00”,
“end”: “2023-06-28T18:00:00+10:00”
},
{
“start”: “2023-06-30T09:30:00+10:00”,
“end”: “2023-07-01T09:59:59+10:00”
(and a lot of close braces)

The API call spits out a thing it’s calling a ‘calendars primary busy’ list object, each of which has a start and end key:value pair. I put the object into a couple of custom states for testing, see below:

‘FB sample’ is a single value, just the start value from the first item in the list returned by Get Busy. This item has always been filled successfully when I’ve run the workflow. ‘FB List’ is a list of ‘Get Busy calendars primary busy’ things. (I’ve made more progress keeping it in this format until later steps.)

Listshifter A’s list on the page is the list custom state, FB List.
listshift object 1

I’ve tried the list iteration workflow as one and two step processes. Here’s the separated version:


The first step makes a date range out of the current iteration step’s start and end values (successfully, when given time) and the second step adds it to an ‘FB Ranges’ custom state.

On the page I’m visualising it for checking purposes in a few ways. The ‘failed’ version is as follows:
listshift outcome 1

The first timestamp is the FB Sample custom state’s value, a single start time for the first item in the JSON list.

The set of three timestamps is the FB List custom state, each item’s End value – and this always appears successfully, so it seems like the API does return data in time for that to occur.

The 0 is where a count of objects in the FB Ranges should appear, and below that the date ranges themselves. You can see what it looks like after the pause is added or the workflow is run in steps in the debugger:

I’m not quite sure why the FB List can show its values on the page directly with no problem, but the ListShifter seems to want to take action before it reads the same list…?

Because that’s not how you do that.

1 Like

FYI, you can use triple-back-ticks* to start a code block. (* you can also use four spaces)

```
insert your code here
```

You can even tell it what type of code you’re typing and it will highlight it for you.

```javascript
insert your code here
```

So, your code ‘snippet’ could look like:

{
“kind”: “calendar#freeBusy”,
“timeMin”: “2023-06-20T00:00:01.000Z”,
“timeMax”: “2023-06-30T23:59:59.000Z”,
“calendars”: {
“primary”: {
“busy”: [
{
“start”: “2023-06-28T11:00:00+10:00”,
“end”: “2023-06-28T18:00:00+10:00”
},
{
“start”: “2023-06-30T09:30:00+10:00”,
“end”: “2023-07-01T09:59:59+10:00”
// ...
1 Like

One thing that may be tripping you up here @david_t, is it looks like you may be changing the list referred to by the expression Group Main's FB List in your Iterate workflow. Is that correct? You said:

Note that (by design), whenever List Shifter’s “List to Shift” changes, that causes List Shifter to reset. Now, when Process List or Iterate is going, List Shifter generally suspends updates, but it’s possible that this might not always work as expected.

Again, I can’t quite tell if that’s what you’re doing, but make sure you aren’t doing that. (Basically, rather than modifying the source list, modify something else – e.g., some custom state that is a copy of the “list to shift” – so that there’s not this sort of circular reference.)

Of course, the other issue with Bubble is that it’s nearly impossible to troubleshoot even mildly complex stuff via screenshots (as one needs to really be able to look inside of everything). So, if you’re interested in me looking further into where your setup is going wrong, I’d have to have access to at least view your editor for this particular page (and preferably edit access). Feel free to PM me with details if you’d like me to take a look.

Hi again, and thanks for the reply. I agree it’s pretty difficult to see what’s going on with Bubble screenshots. I’m not changing the input list - I’m putting the output item produced by each iteration of ListShifter in a separate single-item custom state (that holds a date range) and then adding the contents of that state to a separate, third custom state that holds a list of date ranges. (I’m sure that could be done in one step, but I wanted to see each item being produced as I stepped through.) That list of date ranges is then displayed (for test purposes) and tested for overlap with a proposed appointment time (the actual behaviour I want).

I set the ListShifter to only load (assuming ‘become visible’ is equivalent to a component loading) once the custom state it accesses for its input list is non-empty, in an attempt to cope with the async nature of the problem. I don’t know enough about how ‘make it visible’ works in terms of load timing to know if that actually addresses the underlying problem, but it does produce the desired result in my testing; the busy time ranges are all coming in and the overlap test works.

Hello @keith - what a plugin this is. Thank you.

I am trying to figure out how to run a workflow AFTER the user has come back to the page after Authenticating by Google via OAuth.

Right now the page refreshes and that’s it but there should be other actions run when the user is logged in after the OAuth process, without having to use a page-wide event detecting if the user is logged in or not, which will run the workflow every time, not just after the initial OAuth Google Sign In process is complete.

Any guidance is appreciated.

@david_t, what you’ve done is perfectly valid:

Your assumption is correct: When an element plugin first becomes visible on the page, it will run its initialization routine and then run its update function for the first time (the update function might run again any time the values in its main interface change, BTW).

So, yes, if the element (List Shifter in this case) is hidden on page load and then you later make it visible, it will at that time become initialized and functional (note that this takes time and, should you need it, you will get the List Shifter Initialized/Updated event when the plugin is ready to accept actions).

@JustinC, thanks for the kind comment on Floppy. As for your question, I think I understand correctly that you’re looking to handle the following scenario:

  • You have some page(s) where the user might visit them in the logged out state.
  • They might then login via OAuth (or even any method) and become logged in (and the page will reload, of course, in the case of OAuth)
  • There is some workflow that you want to run when this happens, but only upon first logging in, not upon visiting the page in the already-logged-in state.

So, I can’t test this exactly very easily (my test app doesn’t have Oauth login setup, only email/password and I don’t feel like adding that feature at the moment), but the following technique should work.

Here’s the best and most reliable way to do this, after a bit of testing:

What we’re going to do is use a Floppy element configured for session storage to write a boolean value (a yes/no) that indicates if the page has been visited while logged out. So, add a new Floppy element to your page and set it up like this:

Storage type: sessionStorage
Auto Read Storage: yes
Type of Values: yes/no
Scalar Key Name: visitedLoggedOut (you could pick your own name, but this is how I would do it)
List Key Name: clear this field out, we won’t be using a list key

You’ll note that I used the inspector to give this element the name “Floppy Login Detector”, because that’s what it’s going to do for us.

All other fields should be default.

Now, what we are going to do with this Floppy is as follows:

  • If we ever see that the user is logged out and the key does not exist, we will write the key with the value “yes” (this indicates that the user visited the page in the logged out state).
  • If we ever see that this key is “yes”, but the user is logged in, this indicates that the user moved from the logged out to logged in state. In that case, we will remove the key and then do whatever actions we want to do “when the user first logs in”. But those actions will not be executed if the user came to the page already in the logged in state.

Cool. The best, fastest, most reliable way to do this – which should work properly whether the user logs in/out via OAuth or via email/password – is to create two “Do when a condition is true” events, as follows:

First, create a workflow that writes the key when the user is logged out:

The condition is Floppy Login Detector's Scalar Value (Storage) is "no" and Current User is logged out. Note that we also set this to run “Every Time”.

You can do other things in this workflow (like, I used debug buddy just to log a console message), but the important part is to set the key (which is called visitedLoggedOut) to yes:

That is, we want to set the default scalar key to yes, so we say “Store Scalar: yes”, we can leave the key name blank (use the default), and “Scalar Value: yes”. We do not need to store the list key so I set it to “no” (but since there is not default list key, you don’t actually have to do that, but it’s cleaner this way).

So, whenever we see that the user is logged out, but does not have this key written, we write it. So, this will handle the case where the user visits the page in logged out mode, but also the case where they visited logged in and then went logged out. (So, if they logged in again, we would see once again that went from the logged out to logged in state.)

The second workflow handles the case where we see that the user was logged out, but now has become logged in. Again this is a “Do when a condition is true” workflow that triggers “Every Time”:

The condition is: Floppy Login Detector's Scalar Value (Storage) is "yes" and Current User is logged in. Again, run this is set to “Every time”.

And what we do when we see this condition (which means that the user has moved from the logged out to logged in state), is we remove the “visitedLoggedOut” key using the Remove Keys action:

And then, in further workflow steps, we can do whatever it is we want to do when the user has logged in, having been previously logged out.

And these will only run when this change of state has occurred, not when the user visits the page and was already in the logged in state.

The reasons that we use session storage for this (rather than local or IndexedDB) is that:

  1. This key will only survive for the current session and will clean itself up when the session ends, but
  2. Session storage keys do survive a page refresh, as when happens with an Oauth workflow (the session stays alive during that hokey-pokey).

While I haven’t tested this with an OAuth login, it should work for you just fine. It also, as I noted before, works with email/password login/logouts. So it covers all the bases.

The reason that I’m using “Do when condition is true, Every time” type of triggers is that we don’t have to worry about whether the “User Logged In” or “User Logged Out” event happens before Floppy Login Detector is initialized (and vice-versa). These conditions seem to evaluate reliably and let us do the subsequent actions just fine.

Works great!!!

But I do get an error in the debugger for the plugin. I will be sending you a PM with a screenshot.

@JustinC, in answer to your question about putting Floppy in a pop-up: In general, we do not put element plugins in pop-ups or other groups that might become visible and invisible. For an element plugin to operate, it must be visible on the page. When the element first becomes visible, its initialization function runs, followed by its update function.

If an element plugin is not visible, or has not completed initialization, you cannot fire actions at it or you will get errors like the one you saw. (You were firing the Store Keys action at a Floppy that does not exist, and so there is no code for action to trigger, hence the error “… StoreKeys is not a function”.)

Though sometimes triggering visibility of an element plugin is useful, as described in my recent conversation with @david_t, above.

Hello @keith , did you happen to find any time to create a tutorial on the Code:Array method?

Hey @Ashish_KDM, no I haven’t really done a deep exploration on the “Code: Array Method” action, its features, and possible applications. I touch on a very simple application in my video about Floppy’s Step Mode, starting about here:

In that, I faff around a bit, forgetting that the code really needs an explicit return statement.

A little more detailed explanation/documentation of Code: Array Method follows.

Basically, how Code: Array Method works is that it implements the various JavaScript array methods that have a structure that accepts up to three arguments (those being the current item, index, and complete array being processed). This is why, for example, you find .findIndex() supported, but not .indexOf().

Of course, the most useful of these methods is really .map(), so we’ll use that to show the general structure of how this works. You can think of these methods as being like this:

code_method_output = list.map(fn)

Where list is the input list to the action. By default this is Floppy’s RAM List, but it could be a different list if you supply that in the Custom List field of the action.

The callback function fn is sort of defined as follows. Let’s say again that we are doing .map(), if we were doing this in our own code it would look like:

code_method_output = list.map((item, index, array) => {
    // the contents of your Function Code input here
    // If you've provided optional arguments, those
    // are also avaialable to you by name here.
    // This code block should explicitly return a value, e.g.,
    return item * 2
})

The optional arguments are made available to your code block via some magic with function.bind(). There are two scalar and two list type optional arguments that you can name and send to the code block. The inputs here are of “any” type, so they can be any data type, as long as they are single values (for the scalars) and list values (for the lists).

The source list (which by default is the current contents of Floppy’s RAM List, but can be customized) is read by my standard “getList” function, which will resolve Things into their uniqueIDs and not Bubble Thing objects. (Optionally preserving Things as their internal Bubble Thing objects, which would be more useful in building code actions that are more like plugins, is something I’ve intended to add, but it doesn’t do that yet. So ATM, this feature is intended for operating on non-Thing data types really.)

Note that in the case of the scalar optional argument inputs, if you send a Thing, that Thing will not be resolved to its unique ID, but will be a Bubble Thing object (an object with the listProperties() and get() methods on it as in the Bubble plugin builder API).

Of course, beyond .map(), there are some array methods that expect different types of values. For example, the implementation of .filter() expects that your code block will return true or false (indicating that the item in question passed the filter or not).

Let me know what it is you might be interested in (or trying to do) with Code: Array Method and I could make that part of a future tutorial on this powerful, but not exactly obvious, feature. (Note that this feature is intended for advanced users who do know JavaScript, but don’t feel like building an entire plugin just to do some simple-ish array operation).

2 Likes

Hi!
Context: I have a repeating group of products, and each cell has a reusable element which allows the user to select multiple variants. This is common enough in applications where repeating groups have focus group dropdowns (So you have to put them in a reusable) that I am looking for a stable and consistent solution.

  1. I want to store the selected variants from the reusable to a floppy with list key ‘selected-products’ (Accomplished… phew).
  2. I want to use a floppy reader to read the values of ‘selected-products’ (Values are not being read. Note that the reusable is in the repeating group.

Let me know if that makes sense.

I appreciate the help!

Ben

Maybe adding a new ‘Floppy Share’ element like the plugin below?

Hey @23cubed - Why can’t you use Floppy Reader (or just a Floppy’s list storage value)? Just drop it on your page and configure it to read the selected-values key.

Now, browser storage does not send an event when a key is updated (except in the weird case of localStorage and the key being updated in another window) so you’d have to trigger that Retrieve Key action for the page-level Floppy whenever your reusable(s) change the key.

(And this isn’t really what Floppy is for. If you’re assembling some list, you should assemble that in memory in the page. If you want the assembled list to survive sessions, you would write the list to browser storage. Then upon the same browser being used to visit the page in a future session, you could retrieve the stored list. All of this can be accomplished with Floppy and its RAM List feature. Recall that any modification to the RAM List can be written to storage by selecting the appropriate option in any of the RAM List actions [store key option].)

As in all things Bubble, it’s quite hard to tell you what to do in the abstract. The specifics of how your page and reusables are set up is crucial.

BTW, @23cubed, it sounds like you’re basically just building a shopping cart? See the following video. I’m talking about Floppy’s List Math action, but what I’m really doing is building a shopping cart, essentially:

1 Like

Hi @keith,

Bought your plugin and playing with it. Lots of things it can do but I am unable to figure out if it can do what I want. Hope you can give me some pointers.

I want to use local storage to save some values for onboarding purposes. Of course, our app can be used in that same browser by multiple users. So I need a way to split the onboarding values per user.

I tried it with indexeddb, making sure that on each login a user specific ID is logged as “key” . So far so good. This means I have something like this:

  • Database (global indexeddb database used for everything in our app)
    • Datastore (subject of data, in this case I used onboarding_todo)
    • Key (user specific ID)
    • Values (onboarding values)

Now I want to check if there is in the onboarding_todo (datastore) a user specific ID (key) that has a specific onboarding value (values) and if true, run a workflow.

Is this possible or else do you see another way to accomplisch what I am trying to do here? I can of course overwrite the value but this means that if a browser is used by multiple people, storing local for them will be pointless.

Seems that I have solved it with floppy_indexeddb’s List Values(storage):filtered:count>0 and using the constraints on DB Datastore en Keys Found in Storage

1 Like