Button event can only fire once


#1

So I’m trying to use a concept from the simple todos, having a button set a collection object’s private variable to false or true. However the button will only change the private variable once each time the template is loaded instead of being able to toggle the private setting multiple times within one template instance. What am I doing wrong/overlooking?

<template name="mapInfo">   
    {{# if ownMap}}
    <form>
        <div class="form-group">
            <label class="control-label" for="title">Map Title</label>
            <div class="controls">
                <input name="title" id="title" type="text" value="{{map.title}}" placeholder="Name Your Map" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label class="control-label" for="mapInfo">About this Map</label>
            <div class="controls">
                <input name="mapInfo" id="mapInfo" type="text" value="{{map.mapInfo}}" placeholder="What's in this map?" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label class="control-label" for="privacy-settings">Privacy Settings</label>
            <p>A private map can only be viewed if a person has a copy of the URL. It will not show up on the Public Maps page.</p>
                <button class="toggle-private" data-action="setPrivate">
                {{#if map.private}}
                    Make Public
                {{else}}
                    Make Private
                {{/if}}
            </button>
        </div>
        <hr/>
        <input type="submit" value="Submit" class="btn btn-primary submit"/>
        <a class="btn btn-danger delete" href="#">Delete Map</a>
    </form>
    
    {{else}}
    <h3>
    {{map.title}}
    </h3>
    <p><strong>created by: {{map.author}}</strong></p>
    <p>{{map.mapInfo}}</p>
    {{/if}}

</template>

And the event:

"click [data-action='setPrivate']": function(e) {
        e.preventDefault();
        
        Meteor.call('maps.setPrivate', currentMap._id, !currentMap.private);
        console.log('private toggled');
    }

#2

It’s hard to tell without knowing the javascript related to the template.


#3

@AnnotatedJS This is the entirety of the javascript related to the template.

Template.mapInfo.helpers({
    map: function() {
        return Maps.findOne({_id: currentMap._id});
    },
    ownMap: function() {
        return currentMap.userId === Meteor.userId();
    }
});

Template.mapInfo.events({
    'submit form': function(e) {
        e.preventDefault();
        
        var currentMapId = currentMap._id;
        
        var mapProperties = {
            title: $(e.target).find('[name=title]').val(),
            mapInfo: $(e.target).find('[name=mapInfo]').val()
        }
        
        Maps.update(currentMapId, { $set: mapProperties }, function(error) {
            if (error) {
                // display the error to the user
                alert(error.reason);
            } 
        });
    },
    
    'click .delete': function(e) {
        e.preventDefault();
        
        if (confirm("Delete this map?")) {
            var currentMapId = currentMap._id;
            Maps.remove(currentMapId);
            Router.go('mapList');
        }
    },
    
    "click [data-action='setPrivate']": function(e) {
        e.preventDefault();
        
        Meteor.call('maps.setPrivate', currentMap._id, !currentMap.private);
        console.log('private toggled');
    }
});

As well as the meteor method:

'maps.setPrivate'(mapId, setToPrivate) {
        Maps.update(mapId, { $set: { private: setToPrivate } });
        console.log('setPrivate');
    }

#4

The issue lies with your map helper in that findOne is not a reactive data source. Change it to find and you should be good to go.


#5

@robfallows I tried switching it to return Maps.find({_id: currentMap._id}).fetch() but the problem still remains.

return Maps.find({_id: currentMap._id});

This also didn’t work.


#6

Seems like currentMap may not be reactive. Where is that declared?


#7

Typically the map item would be passed in from a reactive source in the parent template, and as a template argument (i.e. this.map) it stays updated.

In this case you have the map helper which retrieves the reactive map item, but the event handler refers to a different variable currentMap, whose private property may not be updated, so that you’re continually passing the same value to the method for the setToPrivate argument. As a quick hack, you could replace your method call with

Meteor.call('maps.setPrivate', currentMap._id, !Maps.findOne({_id: currentMap._id}).private);

Longer term you’d probably want to store the reactive value on the instance so that you can reference it from the event handler rather than re-running the collection query.

Btw, findOne is reactive by default, so I don’t think there’s actually a problem with the helper.


#8

Yes - my bad. Don’t know where that came from! :confused:


#9

@robfallows

You were right findOne was not reactive, but it seems they changed this behavior, it’s now equivallent to find().fetch()[0]. Weird, I could have swore findOne is not reactive.


#10

It’s always been equivalent to find().fetch()[0], but I honestly cannot recall whether the behaviour’s changed. It would be odd if it had though - MDG are thorough about maintaining backwards compatibility. I suspect I’d just remembered it wrong (another sign I’m getting too old).


#11

The older the wiser. + We remember the same thing.

I remember for a fact that findOne() caused issues with reactivity changing to find().fetch()[0] solved the issue. I had this problem in 1.2 others had it as well and I know I helped them with the exact same thing. Either that or I’ve been transported into an alternate universe where that has not been the case :smiley:


#12

Thanks @carina! I tried that and it works as a quick hack. I got around the problem by using two buttons but it’s helpful to know that this could also work! I’m having trouble getting the currentMap object to be reactive as I’m using it within multiple templates that are being displayed all at the same time but so far it’s all holding together.


#13

That’s great that you got it working!

I recommend taking a look at the Blaze guide since it sketches out a lot of current recommendations about how keep things manageable. Good luck!

@robfallows @diaconutheodor I’m now vaguely remembering the issue with findOne also :slight_smile: