You are viewing limited content. For full access, please sign in.

Discussion

Discussion

Table and Collection JavaScript Event Handlers

posted on January 10 Show version history

Greetings,

I've seen this issue come up over and over, and it can be a real pain.

You create a Table (or Collection) with a dynamic number of rows and you add event handlers for the fields only to find that they only work on the first row.

 

The (Usual) Problem:

The event handlers are assigned when the form loads, but those additional fields don't exist yet. Basically, when you add the row(s) they lack the same custom events because they are new elements created without your customizations.

 

The Solution:

Your form needs a way to build events that can apply to all rows in the table, not just the ones that exist on form load. Originally, I would add an event to the "add row" button that went back and assigned the event handlers all over again.

This works, but it has drawbacks.

  1. You run the risk of attaching multiple handlers if it is touching the fields that already have the handler(s)
  2. You have to add a completely separate event to the add row button and essentially rebuild your functionality each time a row is added to the table.
  3. It does not account for rows added with lookups because the user isn't clicking the "add row" button.

For item 3, you could add yet another handler for the lookup complete event or something along those lines, but that's even more code you have to manage on your form.

 

The (Better?) Solution:

Forget dealing with constant changes to event handlers and just take advantage of event bubbling and delegated handlers in JQuery. 

Instead of assigning your handlers to the fields in a row, and needing to change it each time a row is added, assign the handler to the table and check which field changed when the event is triggered.

First, assign a custom class to your table (or find its ID) and assign your event handler on document ready

$(document).ready(function(){
  $('.MyTable').on('change',function(e){
    console.log('My Table Changed!');
  });
});

Now, whenever you change something in that table it triggers the event and you get a nice little message in the console.

But, we don't want every single change firing our event; we want to know which field changed, so we just need to add some selectors and JQuery will "filter" the bubbled events to give us the elements we want to monitor.

Let's say in this case I only want column 1, so I add a Column1 css class to that field in my table.

$(document).ready(function(){
  $('li.MyTable').on('change','.Column1',function(e){
    console.log(e.target.id + ' in My Table Changed!');
  });
});

Now when column 1 is changed the event fires, but when column 2 changes it does not. 

I could specify the field type using '.Column1 input' in the selector, but leaving it generic means it would work for drop down other types by default (we could use the id to accomplish the same thing, but a class could apply to multiple types and multiple fields).

So we have our event, and we identified the event source, but what if we want to do something with column 2?

 

Hang In There

There's probably 100+ ways to do it, but we already have the id, and Forms adds a row number to the id automatically, so for this example I will just use a quick way to extract that info.

$(document).ready(function(){
  $('li.MyTable').on('change','.Column1',function(e){
    // extract field and row number
    var field = e.target.id.match(/\d+/g)[0];
    var row = e.target.id.match(/\d+/g)[1];
    
    console.log('Field' + field + ' from row ' + row + ' in My Table Changed!');
  });
});

So, now we have our row, but we still need to update the column 2 value; there's multiple ways, but I'm going to show two that use the row number.(you can skip the row number and just navigate the DOM tree instead, but I'm going for short/simple code here angel)

First, using a Column2 custom class

$(document).ready(function(){
  $('li.MyTable').on('change','.Column1',function(e){
    // extract row number from field id
    var field = e.target.id.match(/\d+/g)[0];
    var row = e.target.id.match(/\d+/g)[1];
    
    var neighbor = $('.Column2 input:eq('+ (row -1 ) + ')').val(e.target.id + ' updated me!').change();
    
    console.log('Field' + field + ' from row ' + row + ' in My Table Changed!');
  });
});

Next, using the id for the field, in this case "Field188" (plus the row)

$(document).ready(function(){
  $('li.MyTable').on('change','.Column1',function(e){
    // extract row number from field id
    var field = e.target.id.match(/\d+/g)[0];
    var row = e.target.id.match(/\d+/g)[1];
    
    var neighbor = $('#Field118\\('+ row + '\\)').val(e.target.id + ' updated me!').change();
    
    console.log('Field' + field + ' from row ' + row + ' in My Table Changed!');
  });
});

(note the \\ to escape the parentheses in the field id, without them the selector won't work)

 

You Can Stop Scrolling Now

So, there it is. Add as many rows as you want, and it will always work without having to worry about reassigning event handlers.

What's nice is that this same approach also works for collections so it is very reusable; all you have to do is assign the same classes because the field id naming is the same. (you could even use the :eq selector when you assign the event handler if, for some reason, you only wanted to monitor a specific row)

It is worth noting that fields added after the form loads will fire the events twice. However, I see the same thing happen even when I add handlers directly to a field using the console, so it seems to be a quirk of Forms, not something related to this specific approach.

 

My obligatory disclaimer is that I'm not a JavaScript expert, so there may be better ways to do a lot of these individual steps; the goal was just to demonstrate the concept.

I know even I sometimes use a different approach from one form to the next, so if you have anything to add or ways to may this more efficient, by all means share!

5 0
replied on January 11

I wouldn't worry too much about the JavaScript being better. The key in this scenario is that it's clear, and understandable. Sometimes it's better to have each step in an explicit line than it is to try to achieve a clever one-liner.

There's always going to be some weirdness since we're glommed on to somebody else's structure. Explicit comments help in this regard.

I really like this writeup!

1 0
replied on January 11

Couldn't agree more! One-liners save space, but they can make it really hard to remember/identify what's going on in each step.

0 0
You are not allowed to follow up in this post.

Sign in to reply to this post.