The layers variable in your Ruby code has nothing to do with the (currently undeclared) variable layers in JavaScript.
You need to “inform” JavaScript e.g. with the #execute_script method about this variable within the action callback.
One of the possibility could be to declared variable outside a function, in your JavaScript and wait for the Ruby side to be completed, then you can build the list.
( The #add_action_callback method is asynchronous. JavaScript call might return before Ruby callback even called. Use onCompleted callback to get notified for completion.)
Without I checked my code below I assume you need to do something like this:
Rather than work with html in js, maybe use the DOM? I know it’s another API to learn, but it’s pretty well optimized…
EDIT:
I haven’t worked with plugins for a while, but I found the function used for loading select elements. One function call could load multiple select elements, using nested arrays passed to js from Ruby.
const setOptions = (a) => {
a.forEach( ai => {
let el = $(ai[0]),
opts = ai[1];
el.options.length = opts.length;
opts.forEach( (txt,i) => el.options[i].text = txt );
});
};
Given the error message, it seems that the below code is setting el to null. But, MDN’s docs of the ‘Options’ collection indicate that in some browsers ‘el.options.length’ may be readonly.
If that’s now the case with SU (I believe it worked with some versions), then change:
The onCompleted callback on JS should be able to receive the return value from Ruby, so if you pull data from JS you don’t need to use execute_script to push the result back from Ruby:
Something like this (untested):
Ruby:
dialog.add_action_callback("layers_to_form") {|action_context|
# This returns a JSON string from this callback.
Sketchup.active_model.layers.map(&:name).to_json
}
JavaScript :
function listOfLayers () {
sketchup.layers_to_form({
onCompleted: function(json) {
var layersReceived = JSON.parse(json); # Parse the JSON here
var arrayLength = layersReceived.length;
var layerList = "<option value='0'>select layer</option>";
for (var i = 0; i<arrayLength; i++) {
layerList += "<option value='"+i+"'>"+layersReceived[i]+"</option>";
}
document.getElementById('id10').innerHTML = layerList;
}
});
}
If you return only simple values, (not arrays or hashes etc) you shouldn’t even need to return a JSON string, SU should take care of string and numeric conversions for you. (Might not even be necessary to do to_json on that array in this example)
I also wasn’t aware of that. You’re a little unclear as to what type of Ruby objects can be passed to the onCompleted function. Would certainly be helpful if ‘base’ Ruby objects could be parsed by the ‘Ruby to js’ transition. Might need some restrictions, ie, how symbols are handled, nesting level, etc…
But, as I recall with older versions of SU, Ruby’s inspect/to_s methods will generate a string that can be easily used in an execute_script string as a parameter, and hence, no need for json.
It worked too. I had only to remove the comment ruby #Parse the JSON here by js //Parse the JSON here but I think I should have notice before trying. I’m not an expert in JS.
Thanks to all of you.
I threw some code together, and with the select element’s simple child elements (<option>), the technique used to load the select element doesn’t really matter. I tried four cases:
Even with 300 options elements, timing from the call from the dialog to load/reload the select, thru Ruby, to the completion of the html document changes, they typically took between 12 and 15 mS to load the select element, and there weren’t consistent times.
Timing just the changes to the document in the dialog code (js only), the times were between 1.5 and 2.6 mS to add the <option> elements to the select element.
I’ll post a zip sometime soon.
Some notes:
If one is using innerHTML, one should consider HTML encoding the inner text. This can be done in Ruby with CGI.escapeHTML, but there isn’t a similar function in js. I believe when using DOM functions. all text is encoded as it’s assigned. Not.sure.
OnCompleted js block/function - with SU 2019, I tried passing an array, and it didn’t work. Didn’t try a hash. So, it may need to be a json string.
A while ago I had non-modal dialogs that mimicked SU’s native layer and material lists, as meta data could be attached to both. Don’t recall if they were ul/li lists or tables (tables I think). Hence, the child elements were much more complex. Given that, using a documentFragment was the best choice for fast rendering.