I created a custom component with personalized HTML and Javascript. The component is a widget and when the user interact it an event is generated and managed with javascript. The a python function in Anvil Form is called. The widget has an element with an ID that I use as reference in my JS code.
When I convert the Form in component and use it multiple times in another Form (creating a copy), only the fist is shown, this happen because the ID of my HTML elements maintain the same name.
Is there a way to duplicate the component? So I create a template (HTML+JS) and then I can insert it multiple times in my final App? Like a random number concatenated to to my IDs and generated by Anvil when creating the component instance?
Hereās one way. This is a custom component which can be added to a project as a dependency. Each instance requires a unique ID, so I just replace the ID in the DOM :
<div id="unique_table_div"></div>
<script>
/*
Changes the DIV to a random one to avoid clashes when multiple
instances are created.
Uses the same random id in the global list of tabulator tables.
*/
$(document).ready(function(){
let unique_id = "uid_"+getRandomIntInclusive(1,9999999).toString();
let div_id = 'unique_table_div';
if ( document.getElementById(div_id) ) {
document.getElementById(div_id).setAttribute("id",unique_id);
}
window['tableArray'][unique_id] = new MyTable("#"+unique_id,$("#"+unique_id));
/*
Call home to register the unique ID
*/
anvil.call($("#"+unique_id), "table_created",unique_id);
});
</script>
Have you tried adding a property to the custom component, adding a textbox, making it invisible, assigning the property value to the textbox text and using jquery to find the textbox using its inner html?
If this works (Iām on my cell now, I havenāt tried) you will be able to set the property for each instance of the component in the ide.
Please let me know if you want a more detailed explanation (and a verification that my idea will actually work) and I will try tomorrow.
I used the approach proposed by David, itās working great! But, I implemented the code using Brython instead of Javascript. This is an example of a DevExtreme widget integration (a simple button, but applicable to other JS/HTML/CSS widgets)
<center style="font-style:italic; color:#888;">
<div id="buttonContainer" class="widget1"></div>
</center>
<script type="text/python">
import random
from browser import document, alert, window
jq = window.jQuery
def generate_rand_ID():
return "".join([random.choice("abcdefghijklmnopqrstuvwxyz") for i in range(8)])
unique_IDs = ['buttonContainer']
random_ID = generate_rand_ID()
postfix = "-inst" + random_ID
for id in unique_IDs:
el = document.getElementById(id)
if el:
el.attrs['id'] = el.attrs['id'] + postfix
window.anvil.call(el, "save_ref_ID", random_ID)
def buttonContainer_onClick_result(res):
alert(res)
def ready(ev):
jq('#buttonContainer'+postfix).dxButton({
'text': "Click me!",
'onClick': lambda ev: window.anvil.call(ev.element, "my_method", "World").then(buttonContainer_onClick_result)
})
jq(ready)
</script>
In the form Class I have then
def form_show(self, **event_args):
"""This method is called when the HTML panel is shown on the screen"""
self.call_js("brython", 1)
def my_method(self, name):
alert("Hello, %s!" % name)
return "Returned Message"
def save_ref_ID(self, random_id):
self.ref_ID = random_id
And in Native Libraries I imported the Brython interpreter and the Widget library (+CSS). Note that the DevExtreme library is free only for non commercial uses.
Always had trouble getting my head around this, but ā¦
I have a custom component form that initialises an external JS widget on a unique div ID (as the widgetās docs say I must). If I want to have multiple instances of this custom component on the same Anvil form, then each ID has to be globally unique, yes?
Thatās been my reasoning behind my random ID functions I referred to in this thread.
Your example @shaun is using dom classes instead of IDs. I assume I cannot āburyā duplicate IDs inside a class instance? I know the answer to that, Iām sure.
Is there a better way than mine when you require IDs for the widgets?
Also interested in this, as Iāve been using the unique IDs approach (since I need to address the components from Python). If thereās a simpler way, thatād be great.
Your example @shaun is using dom classes instead of IDs. I assume I cannot āburyā duplicate IDs inside a class instance?
Can you describe more specifically how this would work? Iām not quite picturing it.
Is there a better way than mine when you require IDs for the widgets?
What are you using the IDs for? (Assuming youāre able to divulge!)
The $(this).find('.bob')[0] approach might be the way to go - if you need to select the element from outside, you might be able to write an accessor function in the Custom HTML Form (@jshaffstall - that may work for you too?) .
No. The widget Iām using (http://tabulator.info) needs to be attached to a div id rather than a class. At least I think it does, as you pass the id to the constructor rather than the dom object itself. This is from one of their examples :
<div id="example-table"></div>
...
var table = new Tabulator("#example-table", {.. etc .
Ignore that bit - makes no sense
So if I am trying to have several of these on a page, I seem to have to ensure there is an empty div with a globally unique id.
Looking at the Tabulator docs, it seems that you can initialise one from a JQuery selector:
// From the Tabulator documentation
$("#example-table").tabulator();
So you could do this in your Custom HTML Form:
<div class="tabulate"></div>
<script>
function initTabulator() {
$(this).find('.tabulate')[0].tabulator();
}
(Or even use JQueryās each function to iterate over all the elements found by find, that way you can use multiple <div class="tabulate"> on one Form.
Iāve looked at your example from above. I think I understand how itās working, with $(this) restricting the search to the current component, right? So when it finds items of class ābobā, itās only returning the ones in the current component.
And on the Anvil side, when it calls self.call_js(āgetBobā) Anvil makes sure $(this) is set to the right component.
If Iām understanding that correctly, I should be able to simplify my code and reduce the Javascript clutter quite a bit. Thanks for the example!
How would you recommend I reference the newly created component?
I was sending the unique id back to Anvil for that purpose. A single form might have 4 of these components, so I maintained my own ānamespaceā array of live components. When I wanted to call the api of s specific one I would use this id.
@david.wylie: $(this).find('.tabulate') returns a JavaScript array, so you can get the nth one on the page (starting at the top) using $(this).find('.tabulate')[n].
It required a bit of reworking (that example you posted @shaun was from an older version), but it now works without renaming the DIV, and I think is a much better control for it.
Iāll post it for general consumption once Iāve tidied it all up and got rid of my debug.