# Get info from ruby action callback to javascript

**URL:** <https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017>\
**Category:** Ruby API\
**Tags:** ruby\
**Created:** [May 9, 2022, 10:51am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017 "2022-05-09T10:51:40Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 9, 2022, 10:51am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/1 "2022-05-09T10:51:40Z")

</div>

I’m trying to get info from ruby script callback to a javascript function but i’m not able to receive the data.

I have this in my html file (I want a drop-down list):

```nohighlight
<select class="despl-01" name="capa" id="id10"></select>

```

I have this in ruby(I nned the layer list):

```ruby
dialog.add_action_callback("layers_to_form") {|action_context|
	json_text = Sketchup.active_model.layers.map(&:name).to_json
	layers = "#{json_text}"
}

```

Then inside javascript I have this function in order to send layer list to drop-down list in html file:

```nohighlight
function listOfLayers () {
    sketchup.layers_to_form()
    var layersReceived = layers
    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
}

```

What am I doing wrong? I receive nothing in my drop-down list id10

---

<div class="post-metadata">

**Author:** ![TIG](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/tig/32/198_2.png) [@TIG](https://forums.sketchup.com/u/TIG)\
**Post date:** [May 9, 2022, 11:24am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/2 "2022-05-09T11:24:49Z")

</div>

How do you run the html-side’s function

```auto
listOfLayers 

```

---

<div class="post-metadata">

**Author:** ![dezmo](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/dezmo/32/51058_2.png) [@dezmo](https://forums.sketchup.com/u/dezmo)\
**Post date:** [May 9, 2022, 11:53am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/3 "2022-05-09T11:53:15Z")

</div>

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](https://ruby.sketchup.com/UI/HtmlDialog.html#execute_script-instance_method) 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](https://ruby.sketchup.com/UI/HtmlDialog.html#add_action_callback-instance_method) 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:

Ruby:

```ruby
dialog.add_action_callback("layers_to_form") {|action_context|
  json_text = Sketchup.active_model.layers.map(&:name).to_json
  dialog.execute_script("layers = JSON.parse(#{json_text});")
}

```

JavaScript :

```auto
var layers = [];
function listOfLayers () {
  sketchup.layers_to_form({
    onCompleted: function() {
      var layersReceived = layers;
      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;
    }
  });
}

```

---

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 9, 2022, 1:14pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/4 "2022-05-09T13:14:51Z")

</div>

> [@dezmo](#):
>
> `document.getElementById('id10').innerHTML = layerList;`

I run the html function with:

```js
document.getElementById('id10').innerHTML = layerList;

```

---

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 9, 2022, 1:18pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/5 "2022-05-09T13:18:53Z")

</div>

It doesn’t works. The drop-down list only receive “select layer”  
This part doesn’t attach nothing to layerList

```nohighlight
for (var i = 0; i<arrayLength; i++) {
     layerList += "<option value='"+i+"'>"+layersReceived[i]+"</option>";
}

```

 ![Captura de Pantalla 2022-05-09 a las 15.18.00](https://global.discourse-cdn.com/sketchup/original/3X/7/5/75c393fec51f44ea6c0b9b84f6225bb4109c7dbf.jpeg)  
 ![Captura de Pantalla 2022-05-09 a las 15.29.04](https://global.discourse-cdn.com/sketchup/original/3X/b/b/bbd9c76e4bea3f3c4a6090264f0188c8c0f0b703.jpeg)

---

<div class="post-metadata">

**Author:** ![MSP\_Greg](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/msp_greg/32/206066_2.png) [@MSP\_Greg](https://forums.sketchup.com/u/MSP_Greg)\
**Post date:** [May 9, 2022, 1:52pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/6 "2022-05-09T13:52:11Z")

</div>

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…

> **[The HTML DOM API - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API)**
>
> The HTML DOM API is made up of the interfaces that define the functionality of each of the elements in HTML, as well as any supporting types and interfaces they rely upon.

> **[HTMLSelectElement - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement)**
>
> The HTMLSelectElement interface represents a HTML Element. These elements also share all of the properties and methods of other HTML elements via the HTMLElement interface.

**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.

```nohighlight
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 );
  });
};

```

---

<div class="post-metadata">

**Author:** ![DanRathbun](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/danrathbun/32/500_2.png) [@DanRathbun](https://forums.sketchup.com/u/DanRathbun)\
**Post date:** [May 9, 2022, 3:54pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/7 "2022-05-09T15:54:16Z")

</div>

> [@dezmo](#):
>
> ```auto
> var layerList = "<option value='0'>select layer</option>";
> for (var i = 0; i<arrayLength; i++) {
> layerList += "<option value='"+i+"'>"+layersReceived[i]+"</option>";
> }
> 
> ```

This would make two options with an index of `0`.

---

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 9, 2022, 5:44pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/8 "2022-05-09T17:44:23Z")

</div>

Yes it’s true but I changed var = 0 by var = 1 and the problem remains…

---

<div class="post-metadata">

**Author:** ![MSP\_Greg](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/msp_greg/32/206066_2.png) [@MSP\_Greg](https://forums.sketchup.com/u/MSP_Greg)\
**Post date:** [May 9, 2022, 8:44pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/9 "2022-05-09T20:44:46Z")

</div>

Try the following:

Ruby

```ruby
dialog.add_action_callback("layers_to_form") {|action_context, select_id|
  lyr_ary = Sketchup.active_model.layers.map(&:name).prepend 'select layer'
  es_para = [[select_id, lyr_ary]]
  es = "setOptions(#{es_para.inspect});"
  dialog.execute_script es
}

```

js

```nohighlight
function listOfLayers () {
    sketchup.layers_to_form('id_10')
};

const setOptions = (a) => {
  a.forEach( ai => {
    let el = document.getElementById(ai[0]),
        opts = ai[1];
    el.options.length = opts.length;
    opts.forEach( (txt,i) => el.options[i].text = txt );
  });
};

```

Remove `document.getElementById('id10').innerHTML = layerList;`

---

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 10, 2022, 7:29am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/10 "2022-05-10T07:29:11Z")

</div>

It seems to work but it fails reading “ai”

In my code:

```nohighlight
32 const setOptions = (a) => {
33 //console.log(a);
34 a.forEach( ai => {
35 console.log(ai);
36 let el = document.getElementById(ai[0]),
37 opts = ai[1];
38 //console.log(el);
39 //console.log(opts);
40 el.options.length = opts.length;
41 opts.forEach( (txt,i) => el.options[i].text = txt );
42 });
43};

```

It returns main.js:40 Uncaught TypeError: Cannot read property ‘options’ of nul

I attach a screen capture if it helps

 ![Captura de Pantalla 2022-05-10 a las 9.28.40](https://global.discourse-cdn.com/sketchup/original/3X/4/7/47d6732ab77ae4bf34c78ea7d6e24d0f61414803.jpeg)

---

<div class="post-metadata">

**Author:** ![MSP\_Greg](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/msp_greg/32/206066_2.png) [@MSP\_Greg](https://forums.sketchup.com/u/MSP_Greg)\
**Post date:** [May 10, 2022, 5:08pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/11 "2022-05-10T17:08:09Z")

</div>

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.

> [@rtches](#):
>
> `let el = document.getElementById(ai[0])`

If that’s now the case with SU (I believe it worked with some versions), then change:

```js
el.options.length = opts.length

```

to

```js
optsLen = opts.length;
while (el.options.length > opts.length) { el.options.remove(el.options.length - 1) }

```

I.Think. I’m kind of busy with some Ruby stuff. You might also check that ‘el’ is an element…

---

<div class="post-metadata">

**Author:** ![MSP\_Greg](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/msp_greg/32/206066_2.png) [@MSP\_Greg](https://forums.sketchup.com/u/MSP_Greg)\
**Post date:** [May 10, 2022, 10:27pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/12 "2022-05-10T22:27:35Z")

</div>

I made a mistake. Before trying the above, I posted the following line of code:

> [@MSP\_Greg](#):
>
> `sketchup.layers_to_form('id_10')`

it should be:

```js
sketchup.layers_to_form('id10')

```

IOW, you listed the select element’s id as `id10`, but my ‘suggested’ code used `id_10`. Sorry.

---

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 11, 2022, 6:07am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/13 "2022-05-11T06:07:57Z")

</div>

Thank you very much @MSP_Greg it works perfectly. That was the reason why the drop-down menu didn´t receive the data.  
I owe you one!

 ![Captura de Pantalla 2022-05-11 a las 7.58.35](https://global.discourse-cdn.com/sketchup/original/3X/a/b/ab9b0461f873b90a290232683647feb5031b48a9.jpeg)

---

<div class="post-metadata">

**Author:** ![tt\_su](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/tt_su/32/17_2.png) [@tt\_su](https://forums.sketchup.com/u/tt_su)\
**Post date:** [May 11, 2022, 1:28pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/14 "2022-05-11T13:28:31Z")

</div>

> [@dezmo](#):
>
> Without I checked my code below I assume you need to do something like this:

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:

```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 :

```auto
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)

---

<div class="post-metadata">

**Author:** ![dezmo](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/dezmo/32/51058_2.png) [@dezmo](https://forums.sketchup.com/u/dezmo)\
**Post date:** [May 11, 2022, 1:40pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/15 "2022-05-11T13:40:28Z")

</div>

> [@tt\_su](#):
>
> The `onCompleted` callback on JS should be able to receive the return value from Ruby

👍 Good advice. I would never have found it myself. I’ll write it in my “lesson book”! Thanks!

---

<div class="post-metadata">

**Author:** ![MSP\_Greg](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/msp_greg/32/206066_2.png) [@MSP\_Greg](https://forums.sketchup.com/u/MSP_Greg)\
**Post date:** [May 11, 2022, 1:57pm UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/16 "2022-05-11T13:57:16Z")

</div>

> [@tt\_su](#):
>
> 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.

---

<div class="post-metadata">

**Author:** ![rtches](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/rtches/32/131827_2.png) [@rtches](https://forums.sketchup.com/u/rtches)\
**Post date:** [May 12, 2022, 5:47am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/17 "2022-05-12T05:47:34Z")

</div>

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.

 ![Captura de Pantalla 2022-05-12 a las 7.39.20](https://global.discourse-cdn.com/sketchup/original/3X/3/b/3bbfe362eb9f5bf120bab65cd1d7fedd96726f91.jpeg)

---

<div class="post-metadata">

**Author:** ![tt\_su](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/tt_su/32/17_2.png) [@tt\_su](https://forums.sketchup.com/u/tt_su)\
**Post date:** [May 12, 2022, 8:19am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/18 "2022-05-12T08:19:37Z")

</div>

> [@MSP\_Greg](#):
>
> 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…

To be honest, I just realized this myself a few days ago. The docs could do with some improvement in regard to explaining the JS side callbacks.

---

<div class="post-metadata">

**Author:** ![MSP\_Greg](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/msp_greg/32/206066_2.png) [@MSP\_Greg](https://forums.sketchup.com/u/MSP_Greg)\
**Post date:** [May 13, 2022, 4:03am UTC](https://forums.sketchup.com/t/get-info-from-ruby-action-callback-to-javascript/198017/19 "2022-05-13T04:03:46Z")

</div>

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:

OnCompletedInnerHTML  
OnCompletedDOM  
ExecuteScriptDOM  
ExecuteScriptDocumentFragment

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:

1. 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.

2. 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.

3. 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.
