So, just to be clear: You really do have Song created as its own type, yes?
(At it’s simplest level, a Song would be a thing with just 1 field “Title” (which would be of type text). But of course a Song could have many more fields on it. But do not make the mistake of thinking that a Song is just a text string. This type of error is made all the time by those new to Bubble / new to apps and so it must be said.)
Now, in your app, there is some relationship between Users and Songs. I don’t exactly know what it is in your app, but let us assume for a moment that a User has Favorite Songs (or it could anything – Songs to Play at the Wedding, Songs for My Next Bangin’ Set List for example). Whatever it represents, this is a List of Songs. I’m just going to continue to refer to this as Favorite Songs.
We have many options for how to store this and for how to implicitly or explicitly retrieve Favorite Songs that are associated with a given User.
Option 1: The most obvious one is to create a field on the User object called “Favorite Songs” that is of type Song and it is a list. This might be what you do today – you push a new song onto this list.
Option 2: If Songs do not exist in the abstract – that is, if the only Songs associated with a User are Songs that User has created (e.g., The User enters the title of a song and this causes a Song to be created… they do not select a Song from some list we have provided or that is fetched from an API or whatnot) – we don’t need to create a list at all. The list of Songs created by the User is just: Do a Search for… Songs (constraint: Created by that User)
This search returns the list of those Songs.
Option 3: We might create a data type for holding generic lists of Songs. We could call that data type Song List and it would have at least 1 field: Field Songs of type Song and it is a list.
In this way, a Song List might represent many different kinds of lists of Songs: Favorite songs, songs in a bangin’ setlist for my big show saturday night, songs to play at my wedding, songs that I despise, songs I never hope to hear again, songs that have been stuck in my head.
So now if you wish to have a field “Favorite Songs” on the User object, this field would be of type Song List… and it would NOT be a list! (But of course, you could have a field that is a list of Song Lists. In fact, just like in Option 2, you could always retrieve the Song Lists created by any given user, right? (Do a Search for… Song Lists… created by this User.)
A Song List would have at least 1 field on it – again, Songs, a list of type Song. But it might also have a Title, right? (We might think of this object as being a “Playlist”.)
Now, with this sort of structure, you create Song Lists and then you might attach them to something like the User. Or they might be attached to any other sort of thing. Or you might just get them again simply by searching by their creator.
Option 4: Well, I could go on and on and on, but the main decision is around if a list of songs is just a one-time thing or if there might be many different types of lists of songs in your app.
NOTE: In NONE of these scenarios do we HAVE to add an order field. I didn’t forget to mention order. Lists that are stored in the database as lists HAVE AN ORDER and KEEP IT.
(Note: I’m not 100% sure that dynamically retrieved lists – such as Search for SomeThing created by SomeUser – are always returned in the same order… but I suspect, if no sorting - is defined, the list comes to us sorted by Creation Date or some such. But this is also no matter as we cannot take such a list and change its ordering – we must save to the database to do that – and so it would be a different list.)
Anyway, lists stored in the database have an order… and we can reorder them. Look here:
(Editor here: Lets-do-stuff-with-lists-and-js | Bubble Editor everyone can view. BTW, any errors you see thrown in this sample app are coming from the REVERSE plugin for which this app is the test app – just ignore them – that plugin element is not doing anything yet.)
There are a couple of things demonstrated by this example:
-
The list of texts retrieved here (it was easiest to build this example with texts, but the list could be anything) maintains its order on repeated page loads. There is no sorting applied to the list. It is simply displayed in the RG as it currently sits in the database. (This list of texts I called a “Bar” and it resides on an object called a Foo. So the RG displays a certain Foo’s Bar.)
-
We CAN do fairly complex array manipulations using the built in list operators, but often these expressions are not just hard to get your head around, but also physically difficult to build.
-
After we perform those gymnastics, we can push the reordered list back into the database in its new order. We do not need to add an “order” field.
What I’ve implemented here is swapping of two list elements’ positions in the list. We cannot, in vanilla Bubble, directly address the individual elements of the list. We can GET them by index number, but we cannot SET them.
What we must do instead is construct a list-wise expression that gives us the intended result (this may not always be possible in a one-liner, nor would we want to construct such one liners on a regular basis, if we value our wrists) and then take that result and write it back to the database, replacing the “original” list.
Now, I did not “know” how to do this particular operation list-wise (swap two elements’ positions), but I did have an idea about how it might be done. The algorithm works like this:
a. We have a list of things. We desire to swap the places of item x and item y (x and y represent the indexes of those items in the original list). For example, swap item 2 with item 4. In JS, we would do this directly – we would store item 2 temporarily in a variable, set the value of item 2 to item 4 and then set item 4 to the stored value of item 2. We don’t have this luxury in Bubble.
b. We can however get a subsets of the original list. Of x and y, one index will be smaller than the other. Let’s say that’s x (and y is the larger value – it is later/lower/further into the list than item x). We can isolate x and y by subsetting the original list – we can get the list through item x (“List 1”) and then get the list through item y (“List 2”).
Now x and y are at the endpoints of two lists and these are the items we need to swap. So we can take List 1 and pop item x off of the list (using :minus item). Then we can pop item y on to that list (using :plus item, putting it into the last position in the list – the space previously occupied by item x).
We have swapped the first element. Now, the second list we pop item y off of (again, using :minus item) we then push item x on to that list (again, using :plus item).
Now List 2 has junk at the front of it (junk we must replace with the contents of List 1), followed by some number of items and ending in the repositioned item x. We get rid of the junk by doing a :filter on List 2, using the advanced criteria – which can be used to iterate over a list. The criteria we use is List 1 does not contain “this item” (this loops through list one and, if an item in List 1 matches an item in List 2, that item is removed).
Now List 1 and List 2 represent the start of the list: Put List 1 in front of List 2 and however many items are in there – that is the correct re-arranged list up to item y. We can put them in this order by doing List 1:merged with List 2. (This preserves their order!)
After this there may still be items in the list that were after item y. But these items do not need rearranging – they are just “everything else that is not in List 1 or List 2”. Handily, Bubble does not retain duplicate items. So we can get our final list by merging “List1:merged with List 2” :merged with the original list.
Of course, this sounds and looks absolutely ludicrous. But that’s how you do it. Our expressions for List 1 and List 2 wind up looking like this in my example:
And, of course, List 2 has that gnarly filter condition on it – the long-ass expression there is just “List 1” written again… adding “doesn’t contain this text”.
Now, this is simply not how your mind thinks about these things – but this algorithm is exactly right. And though it is mildly inefficient, it’s just fine in an environment like Bubble where we cannot really iterate anyway.
The text element in the page I point to helps illustrate how this works.
As you can see from the screencap, to write the :merged expressions would become absolutely insane (and, in fact, due to order of evaluation, you cannot actually write this as a one-liner, even if your crazy enough to try it). So we do the merges in a workflow in steps.
We eventually wind up with the final result, which we save in a custom state. (As I’m always saying, custom states are just local variables where we can hold things.)
We can now, whenever we like, write that value back into the database, overwriting the original list using “set list”.
BUT, as you can see, this is a beautifully brutal proof that you do not need to store an “order” field with a list. You can reorder the list and you can replace it – even using vanilla Bubble.
NOW, it would be easier if you use JavaScript to manipulate the list and then rewrite the list to the database (more performant in most cases, as well).
So, all you need is a way to reorder your list (whatever method you use) and then write it back to the db.
A few other notes:
Now, because you can’t write the above as a one-liner, it is in fact impossible to do in a server-side workflow in vanilla Bubble. The proof of this is left as an exercise for the reader. 
We do wonder: Why is Bubble “missing” so many useful array manipulation operators that would make this easier? (ESPECIALLY since, while we have a plugin interface for adding in-page elements (which can be used for visual elements or for computation), an interface for adding in-page actions (which are really only intended for manipulating the DOM, as far as I can tell), and an interface for adding server-side computations, we DO NOT have an interface for adding new operators.
(Like, we would like to add :reverse now, wouldn’t we? Or :indexOf. Or any number of other things. It’s funky, right?)