Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Sorting an array that comes from findObjs is not doing any sorting.

Hi, First up I'm not a programmer by background I'm just figuring things out as I go along so it's quite possible I'm doing something really dumb. I'm trying to make a quick script to sort all the cards drawn to make them easier to see.  But I want to organise them by the deck they were drawn from and by name.  To simplify things I've taken out the deck sorting and only sorting by name but still it's not doing what it should and the list is not sorted. I've tried different methods of sorting, originally using the  .sort ( a, b ) method and most recently using underscore.js  but it doesn't seem to make any difference no sorting actually happens.  When I manually created an array of names and numbers and tried to sort that it worked as expected.  So I'm not sure if my knowledge of JS is letting me down and I'm sorting an array that can't be sorted for some reason I don't know about. This is a snippet of my code.  I'd appreciate if someone could tell me where I'm going wrong. if (msg.type === "api" && msg.content.indexOf("!sortCards") !== -1) { //store the page ID const currentPage = Campaign().get('playerpageid'); //find all the cards on the page. let cards = findObjs({ _type: 'graphic', _subtype: 'card', _pageid: currentPage, layer: 'objects' }); //Assign to each card graphic on the page the card name and deck name stored in name and bar1_max. Also resizes the card to make it smaller than default size. for (i = 0; i< cards.length; i++) { let cardGraphic = cards[i]; let card = getObj('card', cardGraphic.get('_cardid')); let deck = getObj('deck', card.get('_deckid')); cardGraphic.set({ name: card.get('name'), bar1_max: deck.get('name'), height: 288, width: 180 }) } //Sort the cards by name and deck. NB deck sorting currently removed, only sorting by name. Trying to get something to work. let sortedCards = _.sortBy(cards, 'name'); log(cards); log(sortedCards);
1730731119

Edited 1730735037
timmaugh
Pro
API Scripter
Sorting objects by their properties with vanilla javascript is just a matter of referencing the property in comparison to the next sibling: let sortedCards = cards.sort((a,b) => a.name < b.name ? -1 : 1); Think of the "-1" and "1" as positioning for the first element ("a", in this case). A -1 puts it before "b", a 1 puts it after. However, the real problem that is stopping you (I think) is that 'name' and 'bar1_max' are both Roll20-exposed properties, meaning you have to use the   get()  function with them. let sortedCards = cards.sort((a,b) => a.get('name') < b.get('name') ? -1 : 1); Because of that, I don't think the underscore version is going to work at all. Also, while you *can* chain sorts, one to the next, you'll basically be undoing your first sort with your next. That's not what you want to do; you want to sort by one property, and then sort again WITHIN the groups of that property. There are ways to do that by breaking the groups into individual arrays, sorting each, and then reconstructing the overall array... but I just use this function:     const fieldSorter = (...fields) => (a, b) => fields.map(o => {       let dir = 1;       if (o[0] === '-') { dir = -1; o = o.substring(1); }       return a[o] > b[o]         ? dir         : a[o] < b[o]           ? -(dir)           : 0         ;     }).reduce((p, n) => p ? p : n, 0); IF YOU WERE DEALING WITH VANILLA JS PROPERTIES  (you aren't, so this won't work -- yet), you'd use that by supplying  fieldSorter(fields)  to the  sort()  function as a callback, supplying the fields you want to sort on as string arguments: let sortedCards = cards.sort(fieldSorter('bar1_max', 'name')); If you wanted to sort by a field in a descending fashion, you'd prepend that field name with a hyphen: let sortedCards = cards.sort(fieldSorter('-bar1_max', 'name')); You, however, are dealing with Roll20 properties (exposed with the get() function). That means you have to either add the properties as standard properties to the object (you can assign any property to the js version of the token in your code), or you have to convert the token to a simple object via a JSON conversion (but at that point you would lose the connection of the object in your script to the token on the board). So, let's add the properties to the object as js-accessible properties then sort on them (if you wanted to, you could remove the extra properties at the end, but they don't really cause any harm, so that's up to you). That would look something like (untested, air-coded): if (msg.type === "api" && msg.content.indexOf("!sortCards") !== -1) {   const fieldSorter = (...fields) => (a, b) => fields.map(o => {     let dir = 1;     if (o[0] === '-') { dir = -1; o = o.substring(1); }     return a[o] > b[o]       ? dir       : a[o] < b[o]         ? -(dir)         : 0       ;   }).reduce((p, n) => p ? p : n, 0);     //store the page ID   const currentPage = Campaign().get('playerpageid');   //find all the cards on the page.   let cards = findObjs({      _type: 'graphic',     _subtype: 'card',      _pageid: currentPage,     layer: 'objects'   }).map(c => {     let card = getObj('card', c.get('_cardid'));     let deck = getObj('deck', card.get('_deckid'));     c.set({         name: card.get('name'),         bar1_max: deck.get('name'),         height: 288,         width: 180     });     c.sortName = card.get('name');     c.sortMax1 = deck.get('name');     return c;   });   //Sort the cards by name and deck   let sortedCards = cards.sort(fieldSorter('sortMax1','sortName'));   //add code here to remove extra properties, if desired   log(cards);   log(sortedCards); } Once you prove this out that this works, you might not need to keep the cards array at all. If you want, you could chain everything as an assignment to the sortedCards array, and skip the cards array altogether. That would let you chain another map() operation on the end to remove the extra properties... up to you.
1730733040
GiGs
Pro
Sheet Author
API Scripter
Read Tim's post for solution, but for information, findObjs does not create an array - it creates an object. In Javascript, Arrays and Objects are each their own thing and what works for an array often will not work for an object.
Super helpful thanks.&nbsp; The API reference here&nbsp; <a href="https://wiki.roll20.net/Mod:Objects#findObjs.28attrs.29" rel="nofollow">https://wiki.roll20.net/Mod:Objects#findObjs.28attrs.29</a> &nbsp;states it returns it as an array so I was not even looking for anything else.&nbsp; I shall digest your responses and give them a try.
1730734982
timmaugh
Pro
API Scripter
findObjs() does return an array. Maybe you're thinking of getObj(), GiGs? I get them confused almost every time I return to Roll20 coding after I've stepped away for a bit. =D
Unfortunately I don't understand what's happening at the front of the code enough to debug the errors (p not defined but I don't know what it's supposed to be doing so I can't define it.) but I got to an acceptable answer which was my array wasn't sorting because it wasn't an array. Thanks so much for your help.
1730738196
timmaugh
Pro
API Scripter
Hmm. You do have an array at the end of findObjs()... you just have an array of objects. You can prove that by putting this in immediately after the findObjs() call assigning the results to your cards object: log(Array.isArray(cards)); You'll get 'true'. Now, to sort an array of objects, you have to reference a property of each object to compare. Since Roll20 doesn't expose properties off the Roll20 object (only a couple are exposed that way), you have to use the get() function. As for the fieldSorter function, I can walk through that, if you want (p is declared in the reduce() function, only available in that scope, and refers to a memo object returned by each iteration through the code)... but I don't get that error (p not defined) when I put this in my sandbox. Everything works. So before we get to that point, maybe&nbsp; post your full code. We might be able to spot what's going on and get you working again.
In case anyone in the future stumbles upon this and wants a solution.&nbsp; This works for me.&nbsp; I tried doing the sorting without underscore but all the different methods I tried didn't do the double sorting correctly where as underscore did.&nbsp; Also it specifically only worked as a 2 step processes.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let sortedCards = _.sortBy(cards, 'cardName'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sortedCards = _.sortBy(sortedCards, 'deckName'); \\worked for double field sorting Listing the fields to sort by almost worked but went a bit wonky. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let sortedCards = _.sortBy(cards,&nbsp; 'deckName', 'cardName'); \\did not work for double field sorting. This is a snippet of the sorting part I ended up with and it's working fine for my purposes. if (msg.type === "api" &amp;&amp; msg.content.indexOf("!sortCards") !== -1) { &nbsp; &nbsp; //store the page ID &nbsp; &nbsp; const currentPage = Campaign().get('playerpageid'); &nbsp; &nbsp; //find all the cards on the page. &nbsp; &nbsp; let cards = findObjs({&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _type: 'graphic', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _subtype: 'card',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _pageid: currentPage, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; layer: 'objects' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }).map(cardGraphic =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let card = getObj('card', cardGraphic.get('_cardid')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let deck = getObj('deck', card.get('_deckid')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cardGraphic.set({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; height: 288, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; width: 180 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cardGraphic.cardName = card.get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cardGraphic.deckName = deck.get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return cardGraphic; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //Sort the cards in priority of deck then name. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let sortedCards = _.sortBy(cards, 'cardName'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sortedCards = _.sortBy(sortedCards, 'deckName'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //log(cards); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //log(sortedCards);
1730765942
GiGs
Pro
Sheet Author
API Scripter
timmaugh said: findObjs() does return an array. Maybe you're thinking of getObj(), GiGs? I get them confused almost every time I return to Roll20 coding after I've stepped away for a bit. =D That sounds correct. It's been a while since I've done any scripting, I should leave it till I know better!