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

[Bug] toFront() and toBack() don't work

1431877565

Edited 1432394299
I'm writing a pretty complex script (that I'm going to share with the community) that simply won't reliably draw without toFront() and toBack() working properly. I've tried the proposed workaround in <a href="https://app.roll20.net/forum/post/1489084/i-need-c" rel="nofollow">https://app.roll20.net/forum/post/1489084/i-need-c</a>... as well as a few of my own, but nothing has ever worked. I have a mix of graphics and paths where I need to be able to control the Z-order, and I find toFront() and toBack() to have no effect. Is there any chance of getting this fixed? Pretty please? Thanks to The Aaron for pointing out a workaround: run toBack() against objects in series with a delay of 50ms between each operation. (The same process for toFront() failed even with delays up to 1000ms). Successes: Natural insertion of paths (if no graphics are involved). toBack() of mixed graphics and paths if delayed in series. Failures: Natural insertion of graphics. Natural insertion of mixed graphics and paths. toFront(). toBack() without delays. Bug report (if it matters): bug report Here's a script that illustrates the problem (the pic above was generated by this script): var APIToFrontTester = APIToFrontTester || (function() { var graphicUrl1 = '<a href="https://s3.amazonaws.com/files.d20.io/images/48971/thumb.jpg?1340229647" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/48971/thumb.jpg?1340229647</a>', graphicUrl2 = '<a href="https://s3.amazonaws.com/files.d20.io/images/3243309/fbP3iGSXAjodjtPzJfNy-Q/thumb.jpg?1393492761" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/3243309/fbP3iGSXAjodjtPzJfNy-Q/thumb.jpg?1393492761</a>', graphicUrl3 = '<a href="https://s3.amazonaws.com/files.d20.io/images/224431/2KRtd2Vic84zocexdHKSDg/thumb.jpg?1348140031" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/224431/2KRtd2Vic84zocexdHKSDg/thumb.jpg?1348140031</a>', pathColor1 = '#ff0000', pathColor2 = '#00ff00', pathColor3 = '#0000ff', displayUi = function() { sendChat('Area API', '[run](!api-toFrontTester run)' + ' [clear](!api-toFrontTester clear)' ); }, getRectanglePath = function(height, width) { return "[[\"M\",0,0],[\"L\",0," + height + "],[\"L\"," + width + "," + height + "],[\"L\"," + width + ",0],[\"L\",0,0]]"; }, createPathObject = function(layer, strokeColor, fillColor, size, top, left) { var obj = createObj('path', { layer: layer, pageid: Campaign().get('playerpageid'), top: top + ( size / 2), left: left + (size / 2), width: size, height: size, stroke: strokeColor, stroke_width: 1, fill: fillColor, _path: getRectanglePath(size, size), rotation: 0 }); if(obj) { var objectInfo = ['path', obj.id]; state.APIToFrontTester.objects.push(objectInfo); return objectInfo; } return null; }, createGraphicObject = function(imgsrc, layer, size, top, left) { var obj = createObj('graphic', { imgsrc: imgsrc, layer: layer, pageid: Campaign().get('playerpageid'), top: top + (size / 2), left: left + (size / 2), height: size, width: size, rotation: 0 }); if(obj) { var objectInfo = ['graphic', obj.id]; state.APIToFrontTester.objects.push(objectInfo); return objectInfo; } return null; }, createTextObject = function(text, top, left) { var obj = createObj('text', { layer: 'objects', pageid: Campaign().get('playerpageid'), top: top, left: left, text: text, width: 300, font_size: 16 }); if(obj) { var objectInfo = ['text', obj.id]; state.APIToFrontTester.objects.push(objectInfo); return objectInfo; } return null; }, toFrontObject = function(type, id) { var obj = getObj(type, id); if(obj) { toFront(obj); } }, toBackObject = function(type, id) { var obj = getObj(type, id); if(obj) { toBack(obj); } }, toFrontList = function (l) { var o; if(l.length) { o = l.shift(); toFrontObject(o[0], o[1]); if(l.length) { setTimeout(_.partial(toFrontList, l), 50); } } }, toBackList = function (l) { var o; if(l.length) { o = l.shift(); toBackObject(o[0], o[1]); if(l.length) { setTimeout(_.partial(toBackList, l), 50); } } }, run = function() { var graphics = []; graphics.push(graphicUrl1); graphics.push(graphicUrl2); graphics.push(graphicUrl3); var colors = []; colors.push(pathColor1); colors.push(pathColor2); colors.push(pathColor3); var itemOffset = 30; var graphicIndex = 0, pathIndex = 0; var testTop = 50, testLeft = 150; createTextObject('graphics - natural insertion', testTop, testLeft + 50); for(var i = 1; i &lt;= 10; i++) { createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset)); } testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('paths - natural insertion', testTop, testLeft + 50); for(var i = 1; i &lt;= 10; i++) { createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, testTop + (i * itemOffset), testLeft + (i * itemOffset)); } testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('mixed - natural insertion', testTop, testLeft + 50); for(var i = 1; i &lt;= 10; i++) { if(i % 2) { createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset)); } else { createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, testTop + (i * itemOffset), testLeft + (i * itemOffset)); } } testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('graphic - toFront from bottom', testTop, testLeft + 50); var objects = []; for(var i = 10; i &gt; 0; i--) { objects.unshift(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } //toFront in reverse order: objects.forEach(function(o) { toFrontObject(o[0], o[1]); }, this); testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('graphic - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 10; i &gt; 0; i--) { objects.push(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } //toBack in regular order: objects.forEach(function(o) { toBackObject(o[0], o[1]); }, this); testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('path - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 10; i &gt; 0; i--) { objects.push(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } //toBack in regular order: objects.forEach(function(o) { toBackObject(o[0], o[1]); }, this); testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('mixed - toFront from bottom', testTop, testLeft + 50); var objects = []; for(var i = 10; i &gt; 0; i--) { if(i % 2) { objects.unshift(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } else { objects.unshift(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } } //toFront in reverse order: objects.forEach(function(o) { toFrontObject(o[0], o[1]); }, this); testLeft += 200; graphicIndex = 0; pathIndex = 0; createTextObject('mixed - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 10; i &gt; 0; i--) { if(i % 2) { objects.push(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } else { objects.push(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } } //toBack in regular order: objects.forEach(function(o) { toBackObject(o[0], o[1]); }, this); testLeft += 250; graphicIndex = 0; pathIndex = 0; createTextObject('mixed, delayed - toFront from bottom', testTop, testLeft + 50); var objects = []; for(var i = 10; i &gt; 0; i--) { if(i % 2) { objects.unshift(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } else { objects.unshift(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, testTop + (i * itemOffset), testLeft + (i * itemOffset))); } } //toFront in reverse order: toFrontList(objects); }, deleteObject = function(type, id) { var obj = getObj(type, id); if(obj) { obj.remove(); } }, clearObjects = function() { if(state.APIToFrontTester && state.APIToFrontTester.objects) { state.APIToFrontTester.objects.forEach(function(obj) { deleteObject(obj[0], obj[1]); }, this); } state.APIToFrontTester.objects = []; }, handleUserInput = function(msg) { if(msg.type == 'api' && msg.content.match(/^!api-toFrontTester/)) { var chatCommand = msg.content.split(' '); if(chatCommand.length === 1) { displayUi(); } else { switch(chatCommand[1]) { case 'run': run(); break; case 'clear': clearObjects(); break; default: displayUi(); break; } } } }, registerEventHandlers = function() { if(!_.has(state, 'APIToFrontTester')) { state.APIToFrontTester = { objects: [] } } on('chat:message', handleUserInput); }; //expose public functions: return { registerEventHandlers: registerEventHandlers, displayUi: displayUi }; })(); on('ready', function() { 'use strict'; APIToFrontTester.registerEventHandlers(); APIToFrontTester.displayUi(); });
1431888628
Lithl
Pro
Sheet Author
API Scripter
I remember recently having trouble getting some newly-created graphics on the map layer to appear above the large background image on the map. IIRC at first I was using toBack on the map image, but is wasn't working. Currently, I'm using toFront on the newly-created image from within an asynchronous callback function (character.get('defaulttoken', function(blob) { ... });) and it's working fine. A potential point of importance, the map image that was failing to work isn't in my image library, but the defaulttoken image is.
That brings up a good point - every graphic and path that I'm working with is newly created and handled synchronously (I haven't found asynchronous tricks to help). Depending on the number of objects being drawn, the Z-order will be different, but it's certainly not following the order of calls to toFront().
It's probably also safe to say that natural insertion order of newly created objects is broken too. In my particular use cases, I'm only using toFront() to try to get the Z-order to match the order in which things are created, but obviously I wouldn't want to be limited to that.
If I make a script illustrating the problem that could also be used for testing, would it get dev attention? I can do that if it will help.
1432221005
Lithl
Pro
Sheet Author
API Scripter
It certainly can't hurt.
Okay - I'll get something thrown together.... I imagine this would be an easy thing to fix.
Can't you create the graphics ahead of time somehow and store them out of sight and then move them in as needed later?
1432306318

Edited 1432306905
HoneyBadger said: Can't you create the graphics ahead of time somehow and store them out of sight and then move them in as needed later? In my particular case, no. I have a potentially large number of images that are going to be created dynamically based on user input, some of which could be from user-provided URLs. Also, I have complex polygon paths that are being shaped dynamically based on user input, which need to be on top of some images and beneath others. I have a video here of what I'm working on (I snipped out the parts where I had to manually push things to the back / front because of the bug): AreaMapper Even if I could go with a model that basically prepares a pool of usable graphics, it's a slippery slope. You have to worry about the size of the pool - you have to forecast growth, and you have to trim the pool size down if it's over allocated. This opens up edge cases too - what if your forecasting was off and you simply don't have enough resources in your pool? To me, this seems like a major work-around that doesn't even guarantee the desired behavior. I do something like that as a workaround here: VisualAlert . The first time a graphic is needed, I build it and store it in a long-lived sliding cache. Successive calls to the graphic pull the same one, but a graphic can only be used by one caller at a time. This is for a simple animation, so it's less painful to do a workaround with. The first time an image is used (it's newly created), the Z-order will be wrong because of the bug, but it's cleaner when reused. With the ability to create and delete objects dynamically, things get way simpler and code gets cut down. If you need something, you create it - if you're done, destroy it. The problem is right now that the object you create is bugged out. I love the create / delete functionality, and I think fixing this bug would bring it all the way to being where it needs to be.
Okay - a script is up that displays the bug (in my original post at the top)!
Rand said: The problem is right now that the object you create is bugged out. I love the create / delete functionality, and I think fixing this bug would bring it all the way to being where it needs to be. I can't even reliably use the delete() function to be honest. Figuring out if the API's methods are doing their intended function is always an interesting experiment. I'd post your working example in the bugs section, it'll get more dev visibility.
Ken L. said: Rand said: The problem is right now that the object you create is bugged out. I love the create / delete functionality, and I think fixing this bug would bring it all the way to being where it needs to be. I can't even reliably use the delete() function to be honest. Figuring out if the API's methods are doing their intended function is always an interesting experiment. I'd post your working example in the bugs section, it'll get more dev visibility. Yeah - I just posted it in bugs (and cross-linked the posts) a few minutes ago... I'm praying for a fix! :)
1432341587
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
I never have an issue with .remove(); Or back() / front() (so long as I use a delay.... it might miss a few, but I am moving 100 tiles at a time.)
Stephen S. said: I never have an issue with .remove(); Or back() / front() (so long as I use a delay.... it might miss a few, but I am moving 100 tiles at a time.) I've never had issues with .remove() either. When you use toBack() or toFront(), are you using new objects? As you can see from the screen shot above, graphics and paths don't place nicely together, and they produce deterministic, yet incorrect, results.
1432345736
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
changeTarget = createObj('graphic', { _type: 'graphic', subtype: 'token', pageid: thisPageId, layer: 'map', width: 70, height: 70, left: eachTileFlatten.updateLeft, top: eachTileFlatten.updateTop, rotation: checkTiles[i].degree, imgsrc: checkTiles[i].url, name: checkTiles[i].key, controlledby: 'DungeonDrawGraphic' }); setTimeout(function() {toBack(changeTarget); }, 500);
1432348782

Edited 1432385385
Okay - I added to the script and updated the attached image. It looks like the following are the results (although I'm not testing with varying amounts of objects): Graphics only, natural insertion: slightly bugged Paths only, natural insertion: works Graphics only, toFront() & toBack(): bugged Paths only, toFront() & toBack(): bugged Mixed graphics and paths, natural insertion: bugged Mixed graphics and paths, toFront() & toBack(): bugged Mixed graphics and paths, toFront() & toBack() with delays: bugged
1432356820

Edited 1432356924
The Aaron
Pro
API Scripter
Your delayed test was only delaying the single operation of iterating across the list of objects and calling toBack() on them. I replaced that with a delay between individual operations, as well as adding a toBack() version. In my testing, if I set the delay between toBack() operations to 50ms, I always get proper ordering. I tried delays as high as 1000ms with toFront() and did not get a proper ordering. I also added width and height into your path objects -- it didn't change the behavior, but it makes selecting them more pleasant (and you don't get that weird popping out of existance when you scroll to the right...). I also tried writing a version that watched change:page:_zorder, but the event is not sent reliably enough to base an implementation on it. (My additions and changes bolded ): var APIToFrontTester = APIToFrontTester || (function() { var graphicUrl1 = '<a href="https://s3.amazonaws.com/files.d20.io/images/48971/thumb.jpg?1340229647" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/48971/thumb.jpg?1340229647</a>', graphicUrl2 = '<a href="https://s3.amazonaws.com/files.d20.io/images/3243309/fbP3iGSXAjodjtPzJfNy-Q/thumb.jpg?1393492761" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/3243309/fbP3iGSXAjodjtPzJfNy-Q/thumb.jpg?1393492761</a>', graphicUrl3 = '<a href="https://s3.amazonaws.com/files.d20.io/images/224431/2KRtd2Vic84zocexdHKSDg/thumb.jpg?1348140031" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/224431/2KRtd2Vic84zocexdHKSDg/thumb.jpg?1348140031</a>', pathColor1 = '#ff0000', pathColor2 = '#00ff00', pathColor3 = '#0000ff', displayUi = function() { sendChat('Area API', '[run](!api-toFrontTester run)' + ' [clear](!api-toFrontTester clear)' ); }, getRectanglePath = function(height, width) { return "[[\"M\",0,0],[\"L\",0," + height + "],[\"L\"," + width + "," + height + "],[\"L\"," + width + ",0],[\"L\",0,0]]"; }, createPathObject = function(layer, strokeColor, fillColor, size, top, left) { var obj = createObj('path', { layer: layer, pageid: Campaign().get('playerpageid'), top: top+(size/2), left: left+(size/2), width: size, height: size, stroke: strokeColor, stroke_width: 1, fill: fillColor, _path: getRectanglePath(size, size), rotation: 0 }); if(obj) { var objectInfo = ['path', obj.id]; state.APIToFrontTester.objects.push(objectInfo); return objectInfo; } return null; }, createGraphicObject = function(imgsrc, layer, size, top, left) { var obj = createObj('graphic', { imgsrc: imgsrc, layer: layer, pageid: Campaign().get('playerpageid'), top: top + (size / 2), left: left + (size / 2), height: size, width: size, rotation: 0 }); if(obj) { var objectInfo = ['graphic', obj.id]; state.APIToFrontTester.objects.push(objectInfo); return objectInfo; } return null; }, createTextObject = function(text, top, left) { var obj = createObj('text', { layer: 'objects', pageid: Campaign().get('playerpageid'), top: top, left: left, text: text, width: 300, font_size: 16 }); if(obj) { var objectInfo = ['text', obj.id]; state.APIToFrontTester.objects.push(objectInfo); return objectInfo; } return null; }, toFrontObject = function(type, id) { var obj = getObj(type, id); if(obj) { toFront(obj); } }, toBackObject = function(type, id) { var obj = getObj(type, id); if(obj) { toBack(obj); } }, toFrontList = function (l) { var o; if( l.length ) { o=l.pop(); toFrontObject(o[0],o[1]); if( l.length &gt; 1) { setTimeout(_.partial(toFrontList,l),50); } } }, toBackList = function (l) { var o; if( l.length ) { o=l.pop(); toBackObject(o[0],o[1]); if( l.length &gt; 1) { setTimeout(_.partial(toBackList,l),50); } } }, run = function() { var graphics = []; graphics.push(graphicUrl1); graphics.push(graphicUrl2); graphics.push(graphicUrl3); var colors = []; colors.push(pathColor1); colors.push(pathColor2); colors.push(pathColor3); var graphicIndex = 0, pathIndex = 0; var testTop = 50, testLeft = 150; var itemTop = testTop, itemLeft = testLeft; createTextObject('graphics - natural insertion', testTop, testLeft + 50); for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft); } testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('paths - natural insertion', testTop, testLeft + 50); for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft); } testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('mixed - natural insertion', testTop, testLeft + 50); for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; if(i % 2) { createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft); } else { createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft); } } testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('graphic - toFront from bottom', testTop, testLeft + 50); var objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; objects.unshift(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft)); } //toFront in reverse order: objects.forEach(function(o) { toFrontObject(o[0], o[1]); }, this); testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('graphic - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; objects.push(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft)); } //toBack in regular order: objects.forEach(function(o) { toBackObject(o[0], o[1]); }, this); testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('path - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; objects.push(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft)); } //toBack in regular order: objects.forEach(function(o) { toBackObject(o[0], o[1]); }, this); testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('mixed - toFront from bottom', testTop, testLeft + 50); var objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; if(i % 2) { objects.unshift(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft)); } else { objects.unshift(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft)); } } //toFront in reverse order: objects.forEach(function(o) { toFrontObject(o[0], o[1]); }, this); testLeft += 200; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('mixed - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; if(i % 2) { objects.push(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft)); } else { objects.push(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft)); } } //toBack in regular order: objects.forEach(function(o) { toBackObject(o[0], o[1]); }, this); testLeft += 250; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('mixed, delayed - toFront from bottom', testTop, testLeft + 50); var objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; if(i % 2) { objects.unshift(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft)); } else { objects.unshift(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft)); } } toFrontList(objects); testLeft += 350; itemTop = testTop; itemLeft = testLeft; graphicIndex = 0; pathIndex = 0; createTextObject('mixed, delayed - toBack from top', testTop, testLeft + 50); objects = []; for(var i = 0; i &lt; 10; i++) { itemTop += 30; itemLeft += 30; if(i % 2) { objects.push(createGraphicObject(graphics[graphicIndex++ % graphics.length], 'objects', 100, itemTop, itemLeft)); } else { objects.push(createPathObject('objects', colors[pathIndex++ % colors.length], colors[pathIndex % colors.length], 100, itemTop, itemLeft)); } } toBackList(objects); }, deleteObject = function(type, id) { var obj = getObj(type, id); if(obj) { obj.remove(); } }, clearObjects = function() { if(state.APIToFrontTester && state.APIToFrontTester.objects) { state.APIToFrontTester.objects.forEach(function(obj) { deleteObject(obj[0], obj[1]); }, this); } state.APIToFrontTester.objects = []; }, handleUserInput = function(msg) { if(msg.type == 'api' && msg.content.match(/^!api-toFrontTester/)) { var chatCommand = msg.content.split(' '); if(chatCommand.length === 1) { displayUi(); } else { switch(chatCommand[1]) { case 'run': run(); break; case 'clear': clearObjects(); break; default: displayUi(); break; } } } }, registerEventHandlers = function() { if(!_.has(state, 'APIToFrontTester')) { state.APIToFrontTester = { objects: [] } } on('chat:message', handleUserInput); }; //expose public functions: return { registerEventHandlers: registerEventHandlers, displayUi: displayUi }; })(); on('ready', function() { 'use strict'; APIToFrontTester.registerEventHandlers(); APIToFrontTester.displayUi(); });
The Aaron said: Your delayed test was only delaying the single operation of iterating across the list of objects and calling toBack() on them. I replaced that with a delay between individual operations, as well as adding a toBack() version. In my testing, if I set the delay between toBack() operations to 50ms, I always get proper ordering. I tried delays as high as 1000ms with toFront() and did not get a proper ordering. I also added width and height into your path objects -- it didn't change the behavior, but it makes selecting them more pleasant (and you don't get that weird popping out of existance when you scroll to the right...). Great! The script and bug report are updated.
1432394005
The Aaron
Roll20 Production Team
API Scripter
Your update says: " a workaround: run toFront() against objects in series with a delay of 50ms on each (delays of 1000ms failed). " But should probably say: " a workaround: run toBack() against objects in series with a delay of 50ms between each operation. (The same process for toFront() failed even with delays up to 1000ms) " Also, under Successes: " toFront() / toBack() of mixed graphics and paths if delayed in series. " Should probably say: " toBack() of mixed graphics and paths if delayed in series. " I highlighted your bug report to the dev team.
The Aaron said: Your update says: " a workaround: run toFront() against objects in series with a delay of 50ms on each (delays of 1000ms failed). " But should probably say: " a workaround: run toBack() against objects in series with a delay of 50ms between each operation. (The same process for toFront() failed even with delays up to 1000ms) " Also, under Successes: " toFront() / toBack() of mixed graphics and paths if delayed in series. " Should probably say: " toBack() of mixed graphics and paths if delayed in series. " I highlighted your bug report to the dev team. I see - I misunderstood what you were saying before. Updated. Thanks for escalating. :)