Select specific component with javascript

Hi folks,

This question is due to my lack of JS knowledge.

If I have two components on a custom HTML form named text_area_1 and text_area_2, how would I reference only text_area_1 in a JS function (via call_js)?

If I do this:

<script>
function setColor(color) {
  var all_textareas = this.find("textarea");
  all_textareas.css("background-color", color);
}
</script>

of course all textareas get a red background. I would like to access a single Anvil component by name or some other unique identifier.

I have read nearly every JS-related post on the forum, and explored the JQuery docs but I still have not been able to piece together the correct syntax.

Any slight nudge in the right direction would be much appreciated.

Here is my working clone:
https://FCPWWQJO7VL2H4ZU.anvil.app/YM5N3MPYZINMJHH6GUN5EMTI

2 Likes

I have never found an easy way. See here: Add the component name to the HTML tag

One workaround that I sometime use is to put text_area_1 inside a container together with a label, make the label invisible and set its text to "text_area_1" (or any other unique string). Then javascript will look for a label with the text "text_area_1", get its parent, then its sibling and start from there.
It ain’t pretty and it’s brittle: I don’t know if the HTML structure of the components will change, but that’s the best solution that I have found so far.

2 Likes

Oh I see!

I was wondering why I couldn’t find an example of this in my reading (at least one that made sense to me).

I am going to try your clever solution here.

I have already upvoted your FR. Others please do the same!

I just remembered of another limitation of my approach: when you use it in a repeating panel the text in the label cannot be just a name like "text_area_1" because you would end up with many items with the same id.
In this case I add something unique like a random number to the name, which is the easy part. Then I need to pass the whole id to javascript, which depending on the context can be tricky.

1 Like

So I spent quite a lot of time working on how to do this with the popover and hover/focus behaviour dependencies…

This was my methodology and how I came about it:

  • call the js function from the init method of a Form or from a button click or wherever makes sense…

  • use the component as an argument

  • if we then take a look at the component with console.log(component) we can start to dive into its attributes and methods using chrome dev tools.

  • the path of the html element is: component.v._anvil.element (note this is a jQuery object)

  • (depending on the component you will get different html tags - so a link component gives the <a> tag, text_box gives you the input element, a button component gives the surrounding div)

Optional:

  • if you wanted to send the same element back to python use component.v e.g. anvil.call(html_element, 'my_function', component.v)

Example below:

https://anvil.works/build#clone:SIXH2YXISGCZN3VS=KWOC3UBMEFM243P27RS7UEZU

The code in the example is:

js.call_js('make_component_red', self.label_2)
js.call_js('make_component_red', self.button_1)
js.call_js('make_component_red', self.link_1)

in Native Libraries
<script>

function make_component_red(component){
    var html_element = component.v._anvil.element // jQuery Object
    html_element.css('background','red')

    // optional - if you want to then send this element back to a python function
    anvil.call(html_element, 'i_am_red', component.v)
};
  
</script>

A method inside the HTML Form that the component belongs to…(i.e. not a Blank Panel Form)

  def i_am_red(self, component):
    print(f"component '{component.__name__}' is now red")



side note: since the html element of a button component: button_component.v._anvil.element is the div rather than the button element, I got into the habit of using:

if (component.v._anvil.element[0].classList.contains("anvil-button")) {
        var html_component = $(component.v._anvil.element[0].firstElementChild) 
        // jQuery Button object rather than the surrounding div
    }
else { var html_component = component.v._anvil.element}; // jQuery object

But I don’t think it’s really necessary for most use cases…

You could also do:

if (component.v._anvil.componentSpec.type == "Button") {
        var html_component = $(component.v._anvil.element[0].firstElementChild)
        // jQuery Button object rather than the surrounding div
    }
else { var html_component = component.v._anvil.element}; // jQuery object

which is prettier… But only anvil elements that are created in the designer have a js attribute componentSpec.
Elements that are created in code don’t seem to have a componentSpec attribute so the former code worked more generally… I found this out the hard way…




side side note - I’m sure this is a case of use with caution - it’s not documented and just the result of me hacking around - anything not documented might be liable to change - but then again - the popover dependency made it into the component library

4 Likes

@stefano.menci and @stucork thank you very much for this insight and for the concrete examples.

This is on the edge of my understanding and so these suggestions give me a lot to learn from. I’m going to dig into this.

I really appreciate it.

1 Like

Good luck @alcampopiano

You may have worked it out but just to add - in the example you wrote - the code to change text_area_1 might look something like:

def button_1_click(self, **event_args):
  js.call_js('setColor', 'red', self.text_area_1)

<script>
function setColor(color, component) {
  var html_element = component.v._anvil.element // jQuery Object
  html_element.css("background-color", color);
}
</script>




A quick note
component.v._anvil.element is a jQuery object (just realised) so I didn’t need to use $(html_element) earlier. :upside_down_face: - i’ll edit.

So anything jQuery like you can use component.v._anvil.element

To get to the Dom Element from the jQuery object (in order to do Vanilla Javascript) you’d want to use component.v._anvil.element[0].

var html_element = component.v._anvil.element // jQuery Object
console.log(html_element instanceof jQuery) // true
var dom_element =  component.v._anvil.element[0] // Dom Element
console.log(dom_element instanceof Element) // true




Also the console is definitely your friend here - this is what you get in chrome devtools when you you do console.log(component). And basically how I went about finding out all of this stuff. Plus a lot of stackoverflow!

1 Like

Hey Stu,

Yes, thank you. I was able to work out this part out.

The rest I’m understanding slowly. Your posts here are so thorough that they will continue to be a helpful resource for me and others. Pure gold. Thank you very much.