Strange behavior adding custom html to a column panel component

In my application I am displaying photos and videos with a repeating panel. The item template for the repeating panel checks if it is a photo or video and then either displays the standard image component or a custom html video component I built.

if 'video' in self.item['content_file'].content_type:
      self.content_panel.add_component(video_player(url=self.item['content_file'].url, video_type = self.item['content_file'].content_type))
      self.content.visible=False

The video player form raises the following event

def form_show(self, **event_args):
    self.custom_1.raise_event('x-click', video_url = self.video_url, video_type=self.video_type

This passes the video url and type to the event handler in the custom html form, via.

self.set_event_handler('x-click', lambda video_url, video_type, **e: self.call_js('videoPlayer',video_url, video_type ))

My html and javascript function are as follows:

    <center>
    <div id="main">

    </div>
    </center>
    <script>
    function videoPlayer(video_url, video_type) {
      var video_element = document.createElement("VIDEO");
      video_element.id = "vid"
      video_element.width = 300;
      video_element.height = 240;
      video_element.controls = 'True';

      var video_source = document.createElement("SOURCE");
      video_source.src = video_url;
      
      
      video_element.appendChild(video_source); 
      var main_element = document.getElementById('main')
      main_element.appendChild(video_element)
      
      return String(video_source.src)
    }
    </script>

the strange thing is that when the ‘x-click’ event is raised on form load with the show event, it puts all the videos that are included in the repeating panel in the first content card.

However, when I raise the same ‘x-click’ event on a button press on this form, the behavior is correct.


Could someone explain to me the nuts-and-bolts of the show event so I can hopefully solve this problem? thank you!

Update

The problem was because I was appending the video element to the div with id “main”. HTML must append it to the first element with that ID because when I re-named the div with main_element.id = 'something_else after appending the video, the next video element would get displayed on the next panel in line. This caused problems if there is a photo in the feed, because the next time a video comes up, it gets displayed in the photo’s item template because the div in there had the same id that the javascript function was looking for.

My dirty solution which now works is to raise an event called ‘rename_element_id’ on show of photo which renamed the HTML element id so video elements don’t get appended to the photo template adjacent to it in the feed. It’s almost like I am incrementing a panel counter by renaming the id of the element that gets created on each panel and looking for the first occurance of the default name. This is obviously not a pythonic way to do html, so any advice on dynamically adding html elements and manipulating attributes would be greatly appreciated.

Hi @joinlook.

It’s nice to see you’ve found a solution but there might be a neater way. Since your problem is that you have two elements with the same ID, try selecting them in a different way.

JQuery

Have you heard of JQuery? It’s a very widely-used JavaScript library that makes up for a lot of JavaScript’s … idiosyncrasies. It allows you to select HTML elements using CSS-style selectors.

It’s so popular that it’s usually set to a variable named $, as an extreme shorthand.

Let’s say your HTML is

<div class="foo">
  <p>Hello, World!</p>
</div>
<div class="bar">
</div>

Then to select the <p>, you could do

$('div.foo p');

Or alternatively, you could do

$('div').find('p');

W3Schools has a great set of documentation about JQuery. (W3Schools is a brilliant reference in general.)

Selecting children of a Custom HTML Form

In an Anvil Custom HTML Form, the current Form is available as

$(this)

So you could select your main elements using

$(this).find('#main');

Generally speaking, you’re not supposed to have multiple elements with the same ID, so instead you could assign them a joinlook-custom-video class and select them like

$(this).find('.joinlook-custom-video')

An example

Here’s an app that selects a <div> using JQuery and appends a random number to it. I’ve put the Custom HTML Form into a RepeatingPanel to show that it works with RepeatingPanels and doesn’t put everything in the first slot.

https://anvil.works/build#clone:X4YWYJAWY6GWATF7=SYFE6IGPHO44YRRGNTMDBWOR

Does that help at all?

3 Likes

thank you for explaining $(this) to me, it is exactly what I needed and in a few tries I was able to solve the issue. I was literally just telling my friend that you can ask a question in the anvil forum and within hours you’ll get an elegant solution requiring only a few lines of code. you guys are world class.

UPDATED SOLUTION

<center >
  <div class="video_player">
  </div>
</center>
<script>
function videoPlayer(video_url, video_type) {
  var video_element = document.createElement("VIDEO");
  video_element.id = 'vp';
  video_element.width=400;
  video_element.height = 300;
  video_element.controls = 'True';
  
  var video_source = document.createElement("SOURCE");
  video_source.src = video_url;
  
  $(this).find(".video_player").append(video_element);
  $(this).find("#vp").append(video_source);
  
};
</script>
3 Likes