Search code examples
javascriptjqueryhtmlattrprop

jQuery .prop() returns undefined, while .attr() works as expected for data-*


I am simply trying to get a couple of properties from two elements. Getting the property value from the input element works as expected. The problem is with getting the property data-detail property from the button element. It returns undefined when using .prop(), but works as expected when using .attr().

Can anyone explain this odd behaviour I am witnessing?

HTML

<div class="formRow">
    <label for="firstName">First name</label>
    <div class="detailsControlBtns">
        <button id="editFirstName" class="btn ctaBtn greenBtn editBtn">Edit</button>
        <button class="btn ctaBtn greenBtn saveBtn" data-detail="firstName">Save</button>
        <button id="closeFirstName" class="btn ctaBtn greyBtn closeBtn">Close</button>
    </div>
    <input type="text" id="firstName" name="firstName" value="[+firstName+]" readonly>
</div>

JS

$(".saveBtn").on("click", function() {
    var saveBtn = $(this);
    // The following statement yields undefined. When using .attr() it works as expected.
    var detail = saveBtn.prop("data-detail");
    var relevantInput = saveBtn.parent().next();
    // The following statement works as expected.
    var value = relevantInput.prop("value");
    // ...
});

Solution

  • That's because there's no data-detail property on HTML element.

    Here is a quick explanation for .data(), .prop() and .attr() :

    DOM element is an object which has methods, and properties (from the DOM) and attributes(from the rendered HTML). Some of those properties get their initial value by the attributes
    id->id, class->className, title->title, style->style etc.

    Consider this element: <input type="checkbox" checked data-detail="somedata" >

    The result of the following would be:

    $('input').prop('id'); // => " "-empty string, property id exist on the element (defined by DOM) , but is not set.
    $('input').attr('id');// => undefined - doesn't exist.
    


    If you do the following:

    $('input').attr('id',"someID");
    $('input').prop('id'); // =>  "someID"
    $('input').attr('id'); // =>  "someID"
    

    And also:

    $('input').prop('id',"someOtherID");
    $('input').prop('id');// =>  "someOtherID"
    $('input').attr('id');// =>  "someOtherID"
    

    So, some attributes and properties have 1:1 mapping. (change of the attr result change of the prop and vice versa).


    Consider the following: <input type="text" data-detail="somedata" value="someValue">

    $('input').prop('value'); // =>  "someValue"
    $('input').val();         // =>  "someValue"
    $('input').attr('value'); // =>  "someValue"
    

    And if you do:

    $('input').prop('value','newVal');
    
    // or
    
    $('input').val('newVal');
    
    $('input').prop('value'); // => "newVal"    -value of the property
    $('input').val();         // => "newVal"    -value of the property
    $('input').attr('value'); // => "someValue" -value of the attr didn't change, since in this case it is not 1:1 mapping (change of the prop value doesn't reflect to the attribute value).
    

    Case with the .data()

    1) How to get:

    - Have in mind that attribute name is data-* and property name is dataset, so:

    <input type="checkbox" data-detail="somedata" > 
    

     

     $('input')[0].dataset; //=> [object DOMStringMap] { detail: "somedata"}
     $('input')[0].dataset.detail; // => "somedata"
     $('input').prop('dataset'); //=>[object DOMStringMap] { detail: "somedata"}
     $('input').prop('dataset').detail; // => "somedata"
     $('input').data('detail'); // => "somedata"
     $('input').attr('data-detail');  //  => "somedata"
    

    2) How to set:

    I) $('input').prop('dataset').detail='newData';

     $('input').prop('dataset');  //=> [object DOMStringMap] { detail: "newData"}
     $('input').prop('dataset').detail; // => "newData"
     $('input').attr('data-detail'); // => "newData"
     $('input').data('detail'); // => "newData"
    

    II) $('input').attr('data-detail','newData');

     $('input').prop('dataset');  //=> [object DOMStringMap] { detail: "newData"}
     $('input').prop('dataset').detail; // => "newData"
     $('input').attr('data-detail'); // => "newData"
     $('input').data('detail'); // => "newData"
    

    So you can see that here is 1:1 mapping, attr change reflects prop and vice versa.

    But check the third way:

    III) $('input').data('detail','newData');

     $('input').prop('dataset'); // =>  [object DOMStringMap] { detail: "somedata"}
     $('input').prop('dataset').detail; // => "somedata"
     $('input').attr('data-detail'); // => "somedata"
     $('input').data('detail');  // => "newData"    <-----******
    

    So, what is happening up here?

    $(elem).data(key, value) does not change the HTML5 data-* attributes of the element. It stores its values in $.cache internally.

    So for getting data-* you would never go wrong with .data() :

    $(".saveBtn").on("click", function() {
        var saveBtn = $(this);
        var detail = saveBtn.data("detail");
        var relevantInput = saveBtn.parent().next();
        var value = relevantInput.prop("value");
    
    });