简体   繁体   中英

How can I perform dynamic operations on dynamically added elements?

My objective:

  • Filling in the 'performer-payments' table dynamically with JS/Jquery
  • For each (dynamically added) row in the table, one of the data cells contains a dropdown box.
  • This dropdown box should, when a certain option is selected, make visible another dropdown box (in the same cell). Otherwise, this second dropdown should be invisible.
  • Elsewhere I am accomplishing the hide/show dynamics by means of a toggleVisible function, which simply adds custom classes which is marked by css to hide or show the element.

The relevant code:

The table I want to populate:

<table id='performer-payments' class='performer-profile payments-table'>              
    <tr>
        <th> Period </th>                                                             
        <th> Amount </th>                                                             
        <th> Paid? </th>                                                              
    </tr>
</table>

The code that populates the table:

for (period in data['Performers'][performer]['Payments']) {
    var amount = utils.calcPerformerCut(data, performer, period);                 
    var row = "<tr>";
    row += "<td> " + period + " </td>";
    row += "<td> " + amount + " $ </td>";
    row += "<td>";
    row += "<div class='performer-profile payment-status'>";
    row += data['Performers'][performer]['Payments'][period];
    row += "</div>";
    row += "<select id='payment-status-" + performer + "-" + period + "' class='perfomer-profile hidden-onload displayNone payment-status-menu'>";
    row += "<option value='paid'>Paid</option>";
    row += "<option value='unpaid'>Unpaid</option>";
    row += "<option value='transfer'>Transfer to...</option>";
    row += "</select>";
    row += "<select id='payment-transfer-period-" + performer + "-" + period + "' class='performer-profile hidden-onload displayNone payment-period-menu'>";
    for (var i=0; i < data['Periods'].length; i++) {                              
        row += "<option value='" + period + "'>" + period + '</option>';          
    }
    row += "</select>";
    row += "</td>";
    row += "</tr>";

    $('#performer-payments').append(row);
    $('#performer-payments').on('change', {perf: performer, per: period}, function (even) {
        if (even.target.value == 'transfer') {
            utils.toggleVisible($('#payment-transfer-period-' + even.data.perf + '-' + even.data.per), true);
        } else {
            utils.toggleVisible($('#payment-transfer-period-' + even.data.perf + '-' + even.data.per), false);
        }
    });
}

For reference, the code that toggles visibility:

exports.toggleVisible = function (selector, visible) {
    if (visible) {
        selector.removeClass('displayNone').addClass('displayBlock');
    } else {
        selector.removeClass('displayBlock').addClass('displayNone');
    }
}

There are (at least) two issues with this:

  • The #payment-transfer-period-... select box is never displayed, even when the 'transfer' option is chosen in the first select box. From debugging efforts it seems to me that it could be that the #payment-transfer-period-.. for some reason is not a valid object yet, or something like that.
  • (Obviously, really), the on-change event is triggered N times (N=number of periods) because I am just telling the program to trigger whenever something in the table changes. I would like it to trigger only for the relevant dropdown, but when I tried adding the #payment-status-... as a selector to the .on() function, it made it never trigger.

Note: I welcome feedback on this in general - I am an experienced programmer but have very little experience with HTML/JS/Jquery. Further, I have decided to not use templates for this project since I am trying to learn the basics, so if you get pancreatitis from seeing the way I am 'dynamically' adding the rows to the table, I apologize but it is partly intentional.

Other than that, please ask for clarifications if something is not clear here.

Edit : Here is the relevant part of the data structure:

data = {
    'Performers': {
        'Dira Klaggen': {
            'Payments': {
                'Q1': 'Paid',
                'Q2': 'Paid',
                'Q3': 'Unpaid'
            },
        },
        'Holden Bolden': {
            'Payments': {
                'Q2': 'Transferred to Q3',
                'Q3': 'Unpaid'
            }
        },
        'Manny Joe': {
            'Payments': {
                'Q1': 'Paid',
                'Q2': 'Unpaid',
                'Q3': 'Unpaid',
            }
        }
    },
    'Periods': [
        'Q1',
        'Q2',
        'Q3'
    ]
}  

You do not attach the change handler to the right element. I should be the first select in the row... Instead of the whole table .

Try this change handler:

$('#performer-payments').find('#payment-status-' + performer + '-' + period).on('change', function(){
  if ($(this).val() == 'transfer') {
    $(this).next('select').show();
  } else {
    $(this).next('select').hide();
  }
});


Second approach:
You could simplify that by using a class instead of a "complicated" unique id for the first select .

Say you use the class "payment-status":

The handler would be:

 $('#performer-payments').on('change', '.payment-status', function(){ if ($(this).val() == 'transfer') { $(this).next('select').show(); } else { $(this).next('select').hide(); } }); 

And this handler can be out of the row appending loop because it uses delegation .

Let's clean up your code by doing the following things:

  1. Use classes instead of ugly IDs.

  2. Use data-attributes or hidden input fields to hold extra information.

  3. Use event delegation to bind dynamically-created elements. Inside the event handler, use tree traversal methods to limit the scope of the search based on the current element this .

Let's apply these things.

Build each row like this. {PLACEHOLDER} is where you put your variable stuff like you have in your code.

<tr>
    <td>{PERIOD}</td>
    <td>{AMOUNT} $ </td>
    <td>
        <div class='performer-profile payment-status'>
            {SOMETHING-RELATING-TO-PERFORMER-PAYMENT-PERIOD}
        </div>
        <!-- remove ID -->
        <!-- store performer and period in data-attributes -->
        <select class='perfomer-profile hidden-onload displayNone payment-status-menu' data-performer='{PERFORMER}' data-period='{PERIOD}'>
            <option value='paid'>Paid</option>
            <option value='unpaid'>Unpaid</option>
            <option value='transfer'>Transfer to...</option>
        </select>
        <!-- remove ID -->
        <select class='performer-profile hidden-onload displayNone payment-period-menu'>                             
            <option value='{PERIOD}'>{PERIOD}</option>
            <option value='{PERIOD}'>{PERIOD}</option>  
            <option value='{PERIOD}'>{PERIOD}</option>
            <!-- etc -->
        </select>
    </td>
</tr>

In your JavaScript, create a delegated event handler . Note the syntax.

$(function () {

    // ...

    for (period in data['Performers'][performer]['Payments']) {
        // build and append row
    }

    // create delegated event handler once and outside FOR loop
    $(document).on('change', '.payment-status-menu', function () {
        // get the current status menu
        var statusMenu = $(this);
        // find its related period menu
        var periodMenu = statusMenu.closest('tr').find('.payment-period-menu');
        // toggle its visibility
        periodMenu.toggle(this.value == 'Transfer');

        // of course, this could be a one-liner
        //$(this).closest('tr').find('.payment-period-menu').toggle(this.value == 'Transfer');
    });

});

It doesn't seem like you need (2.) but if you do, within the event handler, use statusMenu.data('performer') or statusMenu.data('period') to get its performer and period values. You could also do this.dataset.performer or this.dataset.period .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM