Search code examples
jqueryextendcustom-data-attribute

Extending object stored in HTML data-xxx element


Say I have the following simplified HTML form:

<form class = 'crud'>
  <div class = 'record' id='record_0'>
    <input id='testa_0' name='testa' class = 'crud-control'>
    <input id='testb_0' name='testb' class = 'crud-control'>
  </div>
  <div class = 'record' id='record_1'>
    <input id='testa_1' name='testa' class = 'crud-control'>
    <input id='testb_1' name='testb' class = 'crud-control'>
  </div>
  <div class = 'record' id='record_2'>
    <input id='testa_2' name='testa' class = 'crud-control'>
    <input id='testb_2' name='testb' class = 'crud-control'>
  </div>
</form>

Within the data-update attribute of the div.record elements, I'm storing the values modified/entered by the user in the input fields in an object {testa: newvalue, testb: othervalue} (until they are submitted to a database -- I'm skipping that submission step here for less code/complexity). I'm updating the data-update attribute using a jquery event handler:

  $( 'form.crud, table.crud' ).each(function(i,crud) {

    $(crud).find('.crud-control').change(function() {
      let $record = $($(this).closest( '.record' ));
      $record.data('update',{...$record.data('update'),...{[$(this).attr( 'name' )]:$(this).val()}});
      
    });

  });

This code works, but the $record.data('update', ... looks a little too complicated to be the best solution. Is there a better method to extend a data-xxx stored object with a key/value pair?

I tried:

$.extend( $record.data('update'), {[$(this).attr( 'name' )]:$(this).val()})

But this - for some reason, which I fail to understand - updates the data-update attribute for all div.records and not only the one closest() to the input.crud-control updated.

Any ideas?


Solution

  • You were very close. If you want to create a new object, it's basically a combination of the two:

    $record.data(
        'update',
        // Create a new object
        {
            // Spread out the previous object's properties into it (if any)
            ...$record.data('update'),
            // Add the new property using computed property name syntax
            [$(this).attr( 'name' )]: $(this).val()
        }
    );
    

    But note that there's no reason for $(this).attr('name'), just use this.name, it's entirely standard. Similarly, $(this).val() is unnecessary with input elements, just this.value is sufficient.

    With those applied:

    $record.data(
        'update',
        // Create a new object
        {
            // Spread out the previous object's properties into it (if any)
            ...$record.data('update'),
            // Add the new property using computed property name syntax
            [this.name]: this.value
        }
    );
    

    Alternatively, you can update the existing object if present (since you're using jQuery's data method, it remains an object, it's not making a round-trip through text):

    const object = $record.data("update") || {};
    object[this.name] = this.value;
    $record.data("update", object);
    

    If you data-update="{}" to the HTML markup for the elements so you know the object will be there, then you can just do:

    $record.data("update")[this.name] = this.value;
    

    Again, since this is using jQuery's data (only getting initial values from the data-update attribute), the object is stored as an object, not text.