Wrong data context in modal - meteor

I can't figure out why this Bootstrap modal receives the wrong data context.
Let's begin with my templates (excluding the modal for now). The first iterates through a list of items fetched by itemsList:
<template name="CategoryItems">
<ul>
{{#each itemsList}}
{{> Item}}
{{/each}}
</ul>
</template>
itemsList looks like this, by the way:
itemsList: function() {
return Items.find()
}
The inside template, Item, details just how these items should appear:
<template name="Item">
<li>
<span class="item-name">{{name}} </span>
<a href="#" class="anchor-item-edit" data-toggle="modal" data-target="#edit-item-modal">
<span class="fa fa-2x fa-pencil-square-o"></span> //Font Awesome icon
</a>
{{> EditItemModal}}
</li>
</template>
So basically it displays the name of the item fetched from the database and then provides an edit button that opens the edit-item-modal. The modal itself is placed here (it's hidden by default) so that it gets the correct data context, however that doesn't seem to work.
When the edit link is clicked, the modal opens. Excluding a lot of markup, it looks like this:
<template name="EditItemModal">
<div class="modal fade" id="edit-item-modal" tabindex="-1" role="dialog" aria-hidden="true">
<h4>{{name}}</h4>
</div>
</template>
The name, however, always displays the name of my first item in the list, ignoring what I actually clicked on.
A very strange thing, though, is that if I include a helper check inside the modal like so,
<template name="EditItemModal">
{{checkDataContext}}
//the other stuff
</template>
and makes the helper look like this,
Template.EditItemModal.helpers({
checkDataContext: function() {
console.dir(this)
}
})
then all the correct items are spit out in the console as soon as I load the page.
What's going on here?

Your modal markup only defines one shared ID between all of your modals, which is not valid HTML and ends up being the root of your problem.
When you click on any button triggering the modal, it's going to show up the first modal it finds in your markup, which always happens to be the first one.
You need to decorate your modals IDs with your items IDs (since they come from a Mongo.Collection), your markup will no longer contain duplicated modal IDs and your code will run as expected.
<template name="EditItemModal">
<div class="modal fade" id="edit-item-modal-{{_id}}" tabindex="-1" role="dialog" aria-hidden="true">
<h4>{{name}}</h4>
</div>
</template>
<template name="Item">
<li>
<span class="item-name">{{name}} </span>
<a href="#" class="anchor-item-edit" data-toggle="modal" data-target="#edit-item-modal-{{_id}}">
<span class="fa fa-2x fa-pencil-square-o"></span> //Font Awesome icon
</a>
{{> EditItemModal}}
</li>
</template>

Related

Add class to ian:accounts-ui-bootstrap-3 SELECT tag when rendered [Meteor JS + Blaze]

Meteor 1.6.1.1 is the latest version of my project. I am using package ian:accounts-ui-bootstrap-3 instead of accounts-ui. I have added a custom field as <select> in the code.
What I get on Screen is as below.
When I saw the HTML code, it is not adding class="form-control" to select tag.
below is the code when I inspect element on UI using Chrome.
<li id="login-dropdown-list" class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Sign in / Join <b class="caret"></b></a>
<div class="dropdown-menu">
<div class="select-dropdown">
<label>State</label><br>
<select id="login-state">
</select>
</div>
<button class="btn btn-primary col-xs-12 col-sm-12" id="login-buttons-password" type="button">
Create
</button>
<button id="back-to-login-link" class="btn btn-default col-xs-12 col-sm-12">Cancel</button>
</div>
</li>
All I want is to add class="form-control" to the select tag dynamically when component loads on screen and it must look like below;
I have tried below code, but it is not working;
Template.HomePage.onRendered(function () {
document.getElementById('login-state').classList.add('form-control');
});
Well, I tried one way to achieve this is by using Template event as below,
Template.HomePage.events({
'click #login-dropdown-list': function(event){
//document.getElementById('login-state').classList.add('form-control');
$('#login-state').addClass('form-control');
}
});
As you can see, the component gets loaded inside the li tag with id="login-dropdown-list". I simply loaded the class on click event. Even the commented code works fine.
Is this a good solution? please let me know if there much better answer to this? If it works I will definitely accept and upvote the answer.

Meteor delete list item with selected class

I am trying to add a delete function that if i click on a list item(template) it adds a selected class to the li. I then want be able to click a button that finds the li with a class of selected and passes the data to a meteor method that removes the data from a collection. How do i access this data.
I have tried a few ways but this is what i have so far.
sidebar.js
Template.Sidebar.events({
'click .button-collapse': function() {
console.log("here")
$(".button-collapse").sideNav();
},
'click #delete i': function() {
Meteor.call('deleteListItem', $( "li.selected" )._id);
}
})
sidebar.html
<template name="Sidebar">
<ul id="slide-out" class="side-nav fixed grey darken-3">
<li class="action-bar">
<span id="add-new" data-target="modal-add" class="modal-trigger"><i class="small material-icons">add</i></span>
<span id="save"><i class="small material-icons">note_add</i></span>
<span id="rename"><i class="small material-icons">mode_edit</i></span>
<span id="delete"><i class="small material-icons">delete</i></span>
<span data-activates="slide-out" id="close" class="button-collapse close "><i class="small material-icons right">reorder</i></span>
</li>
<!-- Load save items-->
{{#if Template.subscriptionsReady}}
{{#each userSaves}}
{{>ListItem}}
{{/each}}
{{else}}
<p>Loading</p>
{{/if}}
</ul>
<i class="material-icons">menu</i>
<!-- Modal form to add new simulator file -->
<!-- Modal Structure -->
<div id="modal-add" class="modal">
<div class="modal-content">
<h4>New Simulator</h4>
{{> quickForm collection=saves id="newSimulator" type="insert" buttonClasses="modal-action modal-close btn waves-effect waves-light" buttonContent="Add"}}
</div>
</div>
</template>
list class
Meteor.methods({
deleteListItem: function(id) {
Saves.remove(id);
}
});
You can not retrieve the ID of an object like that. Either your button has to be inside the document you're gonna remove or you have to include the _id in your HTML attributes and get that value.
So simply add data-value="{{_id}}" to your <li> in your loop and get the selected like this:
//this will retrieve the id of one selected element only
//you need to use each/forEach if you want to remove multiple
$('li.selected').attr('data-value')
EDIT:
Another solution would be using Session. Since you click on an item to select it, you can set a Session in that event
//in the click event where you select a list item
Session.set('itemId', this._id);
//in Sidebar events
'click #delete i': function() {
const itemId = Session.get('itemId');
Meteor.call('deleteListItem', itemId);
}
EDIT 2:
If you use Session solution, don't forget to remove the Session when the template is destroyed. Otherwise (for your use case) when you go to another route and come back without deleting a selected item and click the delete button, it will delete the document even though it doesn't appear to be selected.
Template.templateName.onDestroyed(function(){
Session.set('sessionName', undefined);
});

Polymer - How to data-bind paper-dropdown menu selections to fill another's

I have a dynamic form that's using <core-ajax> to bind data into multiple <paper-dropdown-menu>'s. My question: What is the preferred way to change the data in each dropdown based upon the previous one's selection. Right now, there is no javascript for it, only Polymer data-binding. Here is the code:
<polymer-element name="example-element" attributes="">
<template>
<link rel="stylesheet" href="example-element.css">
<core-ajax auto
url="http://example.json"
response="{{regionData}}"
handleAs="json">
</core-ajax>
<!-- global user object -->
<pvc-globals id="globals" values="{{globals}}"></pvc-globals>
<!-- page container -->
<div class="background" vertical layout>
<!-- toolbar -->
<template is="auto-binding">
<!-- Add teams dialog -->
<paper-action-dialog heading="Add A Example" backdrop autoCloseDisabled
id="addTeamDialog">
<p>Once this form is complete, you will have a new example on your account.</p>
<br>
<!-- Region Name -->
<paper-dropdown-menu label="Choose Your Region" style="width: 100%;">
<paper-dropdown class="dropdown">
<core-menu class="menu" selected="{{selection}}">
<template repeat="{{region in regionData}}">
<paper-item name="{{region.name}}">{{region.name}}</paper-item>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
<br><br>
<!-- State Name depending on what region you choose -->
<paper-dropdown-menu label="Choose Your State" style="width: 100%;">
<paper-dropdown class="dropdown">
<core-menu class="menu">
<template ref="{{region.name}}" repeat="{{region, regionIndex in regionData}}">
<paper-item>{{region.states[regionIndex]}}</paper-item>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
<br><br>
<!-- Club Name -->
<paper-dropdown-menu label="Choose Your Club depending on what region you choose" style="width: 100%;">
<paper-dropdown class="dropdown">
<core-menu class="menu">
<template repeat="{{region, regionIndex in regionData}}">
<template repeat="{{clubs in region.clubs}}">
<paper-item>{{clubs.name}}</paper-item>
</template>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
<br><br>
<!-- Team Name -->
<paper-dropdown-menu label="Choose Your Team" style="width: 100%;">
<paper-dropdown class="dropdown">
<core-menu class="menu">
<template repeat="{{region, regionIndex in regionData}}">
<template repeat="{{clubs in region.clubs}}">
<paper-item>{{clubs.teams[regionIndex]}}</paper-item>
</template>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
<!-- <paper-input-decorator label="Your Team Name" floatingLabel
error="Team Name is required!" autovalidate>
<input is="core-input" type="text" value="{{teamName}}" required>
</paper-input-decorator> -->
<paper-button dismissive on-tap="{{openInfo}}">More Info...</paper-button>
<paper-button affirmative>Cancel</paper-button>
<paper-button affirmative>Add Team</paper-button>
</paper-action-dialog>
<!-- more info dialog (At time, adding `backdrop` attr to this caused error on close) -->
<paper-dialog heading="More Info For Adding Teams" transition="core-transition-top"
id="infoDialog">
<p>If you're region or team is missing, please email us at
info#mintonette.io so we can contact the
neccessary region/authorities to request your addition to join our community!</p>
</paper-dialog>
<!-- toast -->
<paper-toast id="toast1" text="{{message}}" onclick="discardDraft(el)"></paper-toast>
</div>
</template>
<script src="example-element.js"></script>
</polymer-element>
Here is my example of dependent(cascading) drop-down's: JSBin
It is commented, but shortly this is what it does:
it assumes that there is saved state(in the DB) and at load it
initiate the drop-downs according to that state.
it catches selection changes(for storing them after).
Found what I feel to be the most 'Polymer-way' possible to this question (until a Polymer team member answers it). Once you have access to your JSON data via <core-ajax> element, then bind and loop through it in your <paper-dropdown-menu> like so:
<core-ajax auto
url="http://jsonexample.com/example.json"
response="{{yourData}}"
handleAs="json">
</core-ajax>
<!-- Region Name -->
<paper-dropdown-menu label="Your Label" style="width: 100%;">
<paper-dropdown class="dropdown">
<core-menu class="menu" selected="{{selection}}" on-core-select="{{DDSelected}}">
<template repeat="{{something in yourData}}">
<paper-item name="{{something.name}}">{{something.name}}</paper-item>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
Notice that on the <core-menu> element there is the on-core-selected attribute that binds to a function in our script, which looks like this:
regionSelected: function(e) {
var convert = [];
// save selected item
this.item = this.DDSelection;
// Loop through ajax data to find obj selected that matches selection
for (var i = 0; i <= this.yourData.length - 1; i++) {
if (this.yourData[i].name === this.item) {
this.yourObj = this.yourData[i];
// convert obj to array b/c Polymer doesn't loop -> obj
convert.push(this.yourObj);
this.convert = convert;
}
}
}
What this is basically doing is taking the selection that the user chose and looping through your ajax data to find a matching property name. Once it has, then convert that object into an array (if necessary). This is crucial because as of now, Polymer only loops through arrays - not objects. Then, store it as a variable called convert
Once you do this, in your next dropdown, loop through convert and you are golden:
...
<paper-dropdown-menu label="Choose Your Second" style="width: 100%;"
disabled?="{{!selection}}">
<paper-dropdown class="dropdown">
<core-menu class="menu">
<template repeat="{{key, index in convert}}">
<template repeat="{{prop in key.props}}">
<paper-item>{{prop}}</paper-item>
</template>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
A good UX move is to also disable the dropdown until the first has been selected. Notice the disabled?="{{selection}}" attribute on the <paper-dropdown-menu> element which is doing that...
That's it!

Ractive condition doesn't work for Hangout Button

I have a template that works with HangoutButton:
<div id="templateContainer" class="row">
<script id='template' type='text/ractive'>
{{#if HangoutUrl === null}}
<g:hangout render="createhangout">
</g:hangout>
{{else}}
<a id="hangoutUrl" href="{{HangoutUrl}}" class="btn btn-primary">Go to Hangout</a>
{{/if}}
</script>
</div>
If HangoutUrl is not specified I display a Hangout button, in other case I display a usual anchor. If I load the page when HangoutUrl is not specified yet and then I set the HangoutUrl value (without page reloading) here is what I see:
So instead of displaying just the anchor I get both of them. I guess it is caused by the ugly html that Hangout button generates.
What can I do to make Hangout button removed in case HangoutUrl is specified?
Fixed it by wrapping Hangout Button with div:
{{#if HangoutUrl === null}}
<div>
<g:hangout render="createhangout" lang="en-US" initial_apps="[{ app_id : '115733298764', start_data : '#Model.Id', 'app_type' : 'LOCAL_APP' }]">
</g:hangout>
</div>
{{else}}
I'm still not sure why it works this way. May be it is a bug in Ractive?

in Meteor.template.rendered how to make a call after everything is rendered (after the {{#each}} loop finishes)

Hi I have this template
<template name="users">
{{#each user}}
<li id="{{_id}}">
<input type="checkbox" checked />
<span><select name="colorpicker">
{{#each color}}
<option value="{{mColorCode}}" {{selected ../mColor mColorCode}}>{{mColorName}}</option>
{{/each}}
</select>
</span>
<img width="40" src="data:image/png;base64,{{mImage}}" />
<span class="name">{{mUsername}}</span>
<p><span class="description">{{mDescription}}</span></p>
</li>
{{/each}}
</template>
What i want to do is after the template is rendered, i want to convert the dropdown to a colorpicker. I'm using a jquery plugin
Template.users.rendered = function(){
$('select[name="colorpicker"]').simplecolorpicker({picker: true});
}
The problem is sometimes its not working, (Sometimes the call is being made before the dom being ready.)
I want to call this plugin after everything is rendered. and not for each user added, how can i do this ?
Thanks
I've found Meteor to be a little funny with how often it renders templates, and jQuery functions can end up building up.
I've taken to adding console.log("users rendered"); to understand how many times and when the render callback is triggered.
One thing I've had some success with is wrapping that template inside another, and then tying the callback to the outside template. Something like this:
<template name="container">
{{> users}}
</template>
<template name="users">
{{#each user}}
<li id="{{_id}}">
<input type="checkbox" checked />
<span><select name="colorpicker">
{{#each color}}
<option value="{{mColorCode}}" {{selected ../mColor mColorCode}}>{{mColorName}}</option>
{{/each}}
</select>
</span>
<img width="40" src="data:image/png;base64,{{mImage}}" />
<span class="name">{{mUsername}}</span>
<p><span class="description">{{mDescription}}</span></p>
</li>
{{/each}}
</template>
And this just add the callback to the container
Template.container.rendered = function(){
$('select[name="colorpicker"]').simplecolorpicker({picker: true});
console.log("rendered");
}
Not totally sure why it works, but it has for me, hopefully someone can illuminate us both.
This is the solution that I used, it's also a hack if someone has a better solution please post it.
What i did was i put the rendered template in the callback of the subscribe of my collection.
So my code was something like this :
Meteor.subscribe('trackedUser',function(){
Template.users.rendered = function(){
......
}
}
I see this is a pretty old post, but since I had a problem and I think I've found a better way, here it is. The containing template will technically be rendered even while the contents are rendering. In my case I was trying to initialize a materialize modal. To make it work It had to do the following.
template.html
<div class="row" id="eventResults">
{{#each eventResults}}
{{> eventResult}}
{{/each}}
</div>
<template name="eventResult">
<div class="modal event" id="{{id}}">
<div class="modal-content">
...
and template.js
Template.eventResult.onRendered(function(){
this.$('.modal-trigger').leanModal();
By calling the code on the onRendered of the child element and using this.$('...'), the code doesn't get called multiple times for each element, just once each.

Resources