3

I have a need to generate random id's for some buttons created on the page. I create the buttons using this jquery:

var btnId = 'delete-' + row.id;
$("<button/>", {
    'id': btnId,
    'html': $("<i></i>").addClass("ion-icon ion-btn ion-trash-a"),
    'class': 'btn btn-primary btn-md'
}).append(' Delete')

The template for the button id's:

delete-$UUID, where $UUID is retrieved from a database.

This worked well, and after creating, I was able to even attach a click listener to each button using jquery:

$('button#' + btnId).click(function(e) { alert("Button clicked!"); });

The plot thickens

Now, I decided not to use the database row id, but instead generate a random string to use as the button id's, so I created this function:

function _randomString (min, max) {
    var text = "";
    var length = Math.floor(Math.random() * (max - min) + min);
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-abcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < length; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
}

, Then replaced the button id like this

var btnId = 'delete-' + _randomString(20, 30);

As you can see, the only thing different is that the $UUID is replaced by the value of this function I created.

The climax

Now when I use jquery to add a click listener (the same jquery as above), the bind stopped working with jquery. No matter what happens, jquery does not seem to want to find and bind the click listener to the buttons when created with such an id.

At first, I thought this may be due to the function not generating random strings, but I went to the console and I took the generated id of one of the buttons, and when I did $('copied-button-id').length, I got 1. So obviously, jquery is able to find the button now, but not when it was created as was the case when I used the database row id to create the button id.

Even stranger is that when I remove that randomly generated string and replace it with a plain string like sdfsdfsd, i.e. now all the buttons have the id delete-sdfsdfsd, jquery has no problem finding and binding to the buttons.

Before anyone asks, I have also perused the acceptable ids for html elements, so this is not an issue. What are valid values for the id attribute in HTML?

Resolution

Here is where I need your help. I guess I'm either really tired or something, but can you look at my function and tell me what may be causing this issue. I've eliminated length issue, as I've been able to use ids of length in excess of 60 characters and they all worked. Is JQuery exhibiting undefined behavior by being able to find (or not) any of the buttons created in this way? Am I exhibiting undefined behaviour? What am I missing?

Any help is welcome!


JQuery version: 2.1.4

MCVE:

$(function() {
  var numRows = 10;
  var $table = $('#db-table');
  for (var i = 0; i < numRows; i++) {
    $table.find('tbody').append("<tr/>");
    var name = _randomString(5, 10);
    var age = Math.floor(Math.random() * 25 + 5);
    var $row = $table.find('tbody tr:eq(' + i + ')');
    $row.append($("<td/>", {
      html: "<span>" + name + "</span>"
    })).append($("<td/>", {
      html: "<span>" + age + "</span>"
    })).append(_getButton({
      id: _randomString(20, 30)
    }));
  }
});

function _randomString(min, max) {
  var text = "";
  var length = Math.floor(Math.random() * (max - min) + min);
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-abcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

function _getButton(row) {
  var btnId = 'delete-' + row.id;
  var $btn = $("<button/>", {
    'id': btnId,
    'html': $("<i></i>").addClass("ion-icon ion-btn ion-trash-a"),
    'class': 'btn btn-primary btn-md'
  }).append(' Delete');

  $('button#' + btnId).click(function(e) {
    alert("You clicked a button!!");
  });

  return $btn.prop('outerHTML');
}
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<div class="container">
  <div class="row">
    <table id="db-table" class="table-responsive">
      <thead>
        <tr>
          <th>Name</th>
          <th>Age</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>
  </div>
</div>
Barmar
  • 741,623
  • 53
  • 500
  • 612
smac89
  • 39,374
  • 15
  • 132
  • 179
  • 2
    Why do you need an ID at all? You can just create the element and bind the event handler to the element directly. – Barmar Aug 25 '17 at 18:40
  • @Barmar, oh yes I know this, but even that did not work haha ffs! – smac89 Aug 25 '17 at 18:41
  • 2
    I can't see any reason why it wouldn't work. You need to post an MCVE that demonstrates the problem, so we can see what you're doing wrong. – Barmar Aug 25 '17 at 18:41
  • @Barmar, I added my version of JQuery. This is a requirement as I do not touch the scripts at all – smac89 Aug 25 '17 at 18:42
  • why you are creating multiple class in a same element addClass("ion-icon ion-btn ion-trash-a") and 'class': 'btn btn-primary btn-md'?? – jvk Aug 25 '17 at 18:43
  • @whoami, Look closely, those classes are for two different elements – smac89 Aug 25 '17 at 18:43
  • @whoami - the addClass is adding classes to the `i` element, which is the html content of his `button`, the button has the btn etc classes.... – Stuart Aug 25 '17 at 18:44
  • @smac89 I doubt the jQuery version matters. – Barmar Aug 25 '17 at 18:44
  • 1
    It works here: https://jsfiddle.net/barmar/h8hetw2t/1/ While I was copying your code, I found that you're missing a `)` at the end of the `$("button#" + btnId).click(` line. – Barmar Aug 25 '17 at 18:49
  • No need for any of those ids. There are much simpler ways to implement this. – James Aug 25 '17 at 18:53
  • @Barmar, thanks for your help, I guess something is really amiss in my code then, I will keep searching – smac89 Aug 25 '17 at 18:58
  • 1
    @James, such as...? – smac89 Aug 25 '17 at 18:59
  • @smac89 If you post your code, we can help you figure out what's amiss. That's what we're here for. – Barmar Aug 25 '17 at 19:00
  • @Barmar, oh perfect, I was just compiling a MVCE, I will update soon – smac89 Aug 25 '17 at 19:02
  • Well, it's a delete button so I assume you want to delete some parent container that the button lives in, say a `tr`. `$(this).closest('tr').remove();` would be the content of the *single* event handler applied to *all* buttons, in order to do that, for example – James Aug 25 '17 at 19:05
  • @Barmar, sorry it took long, but here is the link: https://jsfiddle.net/yw7j068o/. Thanks for your help – smac89 Aug 25 '17 at 19:19
  • @Barmar, also note that if you go to that code and change the id used to something like `delete-"sdfsdfds"`, you will see that the click handler works as expected. – smac89 Aug 25 '17 at 19:30
  • 1
    `$('button#' + btnId).on('click', function(e) { alert("Button clicked!"); });` for dynamically added elements to the DOM, you must delegate the event by using the `.on()` method. – zer00ne Aug 25 '17 at 19:31
  • [no ids](https://jsfiddle.net/yw7j068o/4/) – James Aug 25 '17 at 21:45
  • @zer00ne, delegating the events was indeed the key to solving this. I was too focused on attaching a listener to each individual element, but this resulted in false-positive results due to the nature of jquery event binding – smac89 Aug 27 '17 at 06:32
  • @smac89 Not sure what "false-positive" jQuery event binding nature entails , but I see that your problem is resolved which is great, happy coding, sir. – zer00ne Aug 28 '17 at 12:54

2 Answers2

1

It seems really unnecessary to use an ID at all for this. Just use a CSS class, bind your click event to all buttons with that class, and if you need to do something specific to a row or data associated with that button, use html5 data tags on the element, or just use $(this).find(...) or $(this).closest(...) to get whatever information you need when the button is clicked.

Again, not 100% clear on the use case here, but just for the sake of example, let's say your button class is awesome-button:

// Using $(document).on('click') instead of just
// $('.awesome-button').click() since you seem to be dynamically creating buttons.

$(document).on('click', '.awesome-button', function() {
  var row = $(this).closest('tr');

  // Now do whatever you want with the row...
  row.find('.something-obscure');

  // Or maybe your rows have data tags on them that need to go somewhere?
  someCrazyFunction(row.data('example'));

  // etc...
});
Ben Fried
  • 2,168
  • 1
  • 14
  • 13
  • The buttons carry out a specific action corresponding the row for which that buttons was created. Using one css class will mean that only one row will be affected when the button is pressed. My example of using `alert` was simply for the purpose of making a point, the usecase is actually a lot more complicated than that – smac89 Aug 25 '17 at 19:01
  • @smac89 That's the point of using something like `$(this).closest("tr")` to find the row containing the button, then you can do something specific to that row. – Barmar Aug 25 '17 at 19:02
  • Just updated my answer to maybe make it more clear...I can't imagine what is so complicated that you can't traverse the DOM tree from where the button is located to get everything you could possibly need. – Ben Fried Aug 25 '17 at 19:15
  • @BenFried, maybe the confusion is that you don't realize that I have many buttons (see my updated question with the MVCE). Using a simple class will trigger the event on all buttons in the page, so an Id is needed – smac89 Aug 25 '17 at 19:32
  • 1
    A class will not trigger an event on all buttons on the page, it will trigger the event for the ONE button that is clicked, and based on the one that is clicked, you can find any data you need relative to its position. – Ben Fried Aug 25 '17 at 19:54
  • I guess you're right. I wasn't thinking when I wrote that last statement. Yea that makes sense. Thanks I will try it – smac89 Aug 25 '17 at 20:00
  • However, this does not seem quite right. I think I prefer using an id for each button rather than using a class. Classes should be used more for styling than for this. – smac89 Aug 25 '17 at 20:03
  • Classes are used for this purpose all the time. It makes way more sense than using ID's. But what do I know...I've only been doing this professionally for 20 years. – Ben Fried Aug 25 '17 at 23:34
1

You're using the selector $("button#' + btnId) in the _getButton() function. But the button isn't appended to the DOM until the function returns and the button is passed to $row.append().

Instead, just bind to the element itself, rather than using a selector. And _getButton() needs to return the element itself, not its outerHTML, because that will lose the event binding.

$(function() {
  var numRows = 10;
  var $table = $('#db-table');
  for (var i = 0; i < numRows; i++) {
    $table.find('tbody').append("<tr/>");
    var name = _randomString(5, 10);
    var age = Math.floor(Math.random() * 25 + 5);
    var $row = $table.find('tbody tr:eq(' + i + ')');
    $row.append($("<td/>", {
      html: "<span>" + name + "</span>"
    })).append($("<td/>", {
      html: "<span>" + age + "</span>"
    })).append(_getButton({
      id: _randomString(20, 30)
    }));
  }
});

function _randomString(min, max) {
  var text = "";
  var length = Math.floor(Math.random() * (max - min) + min);
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-abcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

function _getButton(row) {
  var btnId = 'delete-' + row.id;
  var $btn = $("<button/>", {
    'id': btnId,
    'html': $("<i></i>").addClass("ion-icon ion-btn ion-trash-a"),
    'class': 'btn btn-primary btn-md'
  }).append(' Delete');

  $btn.click(function(e) {
    alert("You clicked a button!!");
  });

  return $btn;
}
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<div class="container">
  <div class="row">
    <table id="db-table" class="table-responsive">
      <thead>
        <tr>
          <th>Name</th>
          <th>Age</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>
  </div>
</div>
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Thanks for your help so far. I have discovered that the reason using the row id's worked was because the table is loaded twice (once to retrieve data and load the HTML, and second time to refresh the table incase some data is missing). The problem is that the first time the rows are generated, the jquery binding, tries to bind to an element which does yet exist on the page and the second time around, the id is re-generated and again there is no such element on the page to attach listener to. Using row id works because second time around, the element is there and jquery can attach the listener. – smac89 Aug 25 '17 at 19:56