1

I'm having trouble with data attributes in some basic html and javascript. I have several links throughout a page that dynamically insert a map and a 'close' link to get rid of the map.

The links are all similar to:

<a class="maplocation" href="#" data-coords="4645/234234" data-zoom="9">Link text<span class="mapslideicon"></span></a>

And the javascript on clicking these links is:

$("a.maplocation").click(function() {
    if ($(this).data("mapopen") == true) {
        // Map already clicked and open, do nothing
    } else {
        var timeStamp = $.now();
        var mapID = "m_"+timeStamp;
        var mapCloseID = "c_"+timeStamp;
        var anchorID = "a_"+timeStamp;
        var mapClass = 'widemap';
        var mapDiv = $("<div class='"+mapClass+"' id='"+mapID+"'>&nbsp;</div><a href='#' id='"+mapCloseID+"' class='maplocationclose'>close</a>");
            mapDiv.insertAfter($(this).parent());
        // Set a data attribute on the link to let it know a map is open
        $(this).data( "mapopen", true );
        // Set a data attribute on the link so that we can reference it from the close button
        $(this).data( "anchorid", anchorID );
    }
    return false;
});

This creates a div for a map, places a data attribute on the original anchor to say that the map is open and also creates an anchor for closing the map.

When the close map anchor is clicked it executes the following:

$('body').on('click', 'a.maplocationclose', function(){ // delegated.
        var id = $(this).attr('id');
        idNo = id.split("_");
        var assocMapID = "m_"+idNo[1];
        var assocAnchorID = "a_"+idNo[1];
        $("#"+id).remove();
        $("#"+assocMapID).slideUp( 300, function() {
            $("#"+assocMapID).remove();
        });
    // Set a data elemt on the original map link that let's us know the map is closed again
    $('.maplocation[anchorid="'+assocAnchorID+'"]').data( "mapopen", false );
    return false;
});

This all works but I'm having difficulty in accessing the data-attribute from the close anchor. It references fine from the original link, as I intended, and sets the mapped attribute as true and reads it as true. However, when I set it to false in the close anchor it cannot find it and it's never set.

I've run a test (from inside the maplocationclose function) to see if I can find any data attributes from the anchor, such as:

console.log("Zoom Attr is: " + $('a.maplocation[anchorid="'+assocAnchorID+'"]').data('zoom'));

And they're returning 'undefined'.

biscuitstack
  • 11,591
  • 1
  • 26
  • 41

2 Answers2

5

Attaching data using .data() does not add/alter any data-* attributes, hence your attribute selector won't match anything.

You can use filter instead though:

$('.maplocation').filter(function(){
    return $(this).data('anchorid') == assocAnchorID;
}).data( "mapopen", false );
George
  • 36,413
  • 9
  • 66
  • 103
  • 1
    And even if it _did_ work (as it does for actual attributes), it would be `$('.maplocation[data-anchorid="'+assocAnchorID+'"]')...` – Mackan Jul 02 '15 at 13:24
  • @Mackan That's worth noting i guess, yes. – George Jul 02 '15 at 13:26
  • I'm getting my head around this, so excuse any mistakes, but perhaps I moved focus from the issue on hand with my `console.log` example. If that part is a mistake, using `data-*` attributes, I will correct it, though probably should have left it out of this example. My issue is with setting the `mapopen` attribute from the `maplocationclose` function and it doing nothing. I believed that I was exclusively using data caching for my `mapopen` attribute. Does your reason still apply? Thanks for the suggestion. – biscuitstack Jul 02 '15 at 14:02
  • @biscuitstack Sorry I'm not entirely sure what you are asking? – George Jul 02 '15 at 14:22
  • (Nevertheless I should mention that your suggestion does work, thank you. I'm just trying to better understand the problem and realise my issue is still there if I omit the `data-* attribute`s from my example along with the `console.log` test that refers to them. I'm wondering if they are a separate mistake, that you mention above, or if your explanation also applies to my main issue?) – biscuitstack Jul 02 '15 at 14:25
  • 1
    It works as so: `data-*` attributes are parsed by jQuery script on page load, and attached using `.data()`. It's a one way system though, as attaching data using `.data()` does not populate a `data-*` attribute. – George Jul 02 '15 at 14:27
  • That was my understanding. So my confusion is in that I attached the `mapopen` attribute using .data() and never had it as a `data-*` attribute. I was never trying to alter any of the initial data attributes, just adding a new one using `.data()`. I understand your explanation in reference to my `console.log` call which reference a `data-*` attribute named 'zoom' but not to my selectors which reference `anchorid` and `mapopen` that were added only with `.data()` with no attempt to reference or change any of the `data-*' attributes. – biscuitstack Jul 02 '15 at 14:35
  • Perhaps it would help if I defined that my understanding of `data-*` attributes in my example are all the ones I wrote in the html and never referenced again but for my call to `console.log`. Maybe I'm wrong on that one. – biscuitstack Jul 02 '15 at 14:37
  • 1
    There is nothing wrong with your use of the `data` method @biscuitstack. It's that you can't use the selector like you did, since no such attribute (`anchorid`) exist on the element. There is an `data-anchorid` (or similar) in the DOM, but it's never added to the element like using `.attr('data-anchorid', 'myval')` would behave. – Mackan Jul 02 '15 at 15:22
  • Which begs my question of how it is not attached to the element if I can set it and `mapopen` in my `a.maplocation` click function and correctly return a boolean for the status of `mapopen` in the same function when checked later? Is it that `$(this)` is not referring to the anchor element in the way I imagine? – biscuitstack Jul 02 '15 at 15:33
  • 1
    @biscuitstack It is attached, but not directly on the element. jQuerys `data()` manipulates and reads from the DOM/node tree, not the html elements. http://stackoverflow.com/questions/7261619/jquery-data-vs-attr – Mackan Jul 02 '15 at 16:48
  • And this is where I'm out of my depth and need to go and read up on this some more. Thanks for your patience with explanations Mackan and George. – biscuitstack Jul 02 '15 at 16:55
1

Just to expand on the comments following @Georges answer.

Your code that is causing issues:

$('.maplocation[anchorid="'+assocAnchorID+'"]').data( "mapopen", false );

Now, first of all the syntax for this selector is wrong. Even if you have a data attribute named anchorid on an element, that is not how you would retrieve it. You should include the data- part; data-anchorid.

Check the below example, and the console output, to understand why it is failing:

<div class="maplocation" data-anchorid="25" data-test="works">...</div>  

var x = $('.maplocation[anchorid="25"]');
var y = $('.maplocation[data-anchorid="25"]');
console.log('Length of x: ' + x.length) //This is 0, because element x does not exist
console.log('Length of y: ' + y.length) //1
console.log('Value of x: ' + x.data('test')) //This is undefined because element x does not exist
console.log('Value of y: ' + y.data('test')) //works

But, that doesn't solve the issue, since you have no attribute on the actual element you can't use that selector either. data() is not writing to the element, like attr() does, but instead it only adds the keys/values to the jQuery internal collection/cache.

jQuery docs on data():

Store arbitrary data associated with the matched elements or return the value at the named data store for the first element in the set of matched elements.

jQuery docs on attr():

Get the value of an attribute for the first element in the set of matched elements or set one or more attributes for every matched element.

See this for an illustration:

$('.maplocation').data('mydata','myvalue'); //Only added to jQuery collection/internal cache, not DOM/element
console.log($('.maplocation[data-mydata="myvalue"]').data('test')) //undefined
$('.maplocation').attr('data-mydata','myvalue'); //Added to DOM/element
console.log($('.maplocation[data-mydata="myvalue"]').attr('data-test')) //works

Solutions
So, the solution provided by George will work since it will get a collection of all elements with .maplocation and then filter that to only return the one that has a .data('anchorid') == assocAnchorID.

Other options:

  • You can use attr() instead of data() for setting/getting. Everything will be treated as strings, so you'll have to modify the code accordingly, and remember to include 'data-' when using it (example: data('test') should be attr('data-test')). Example:

    $('.maplocation[data-anchorid="'+assocAnchorID+'"]').attr( "data-mapopen", "false" );
    
  • You can use another selector to get the element, then use data() as normal. This is just an example as I don't know if closest will work with your structure:

    $(this).closest('.maplocation').data( "mapopen", false );
    
Mackan
  • 6,200
  • 2
  • 25
  • 45
  • 1
    Much appreciated that you took the time to add this extra specific followup Mackan. What you've written pretty much makes sense to me now other than contradicting bits and pieces I'd read before resorting to asking for help here. Either there's a lot of misinformation on `.data()` out there or I'm worse at selective reading than I thought ;) Either way, yours and @George examples solved the issue and gave nice clarity in the sea of information on the subject (there's plenty of similar posts to this, as I'm sure you're aware). Thanks again. – biscuitstack Jul 02 '15 at 20:15
  • 1
    @biscuitstack I posted it to help, obviously, but I also have a secondary goal: To test my own knowledge and see if I can explain it as well as I hope, and if my understanding and examples hold up :) As you say there are plenty of similar info about, so I will probably not leave this up. I gained some knowledge while writing this mess, so it's a win-win ;) Think of `data()` as `prop()`, both compared to `attr()`. – Mackan Jul 02 '15 at 20:42
  • 1
    I would leave it up. Honestly, I do my homework before asking a question here. I had read through and trialled many similar questions and solutions on this that left me stumped. I don't come from a programming background and none of the explanations I found quite hit the nail on the head for those coming from a different angle, as accurate or not as they were. This thorough explanation compliments my (persistent!) questions and is useful to anyone Googling for more detail on the matter. – biscuitstack Jul 03 '15 at 12:43