I have a simple application form. On click of one button I just need to add text fields and on click of another button, just remove text field dynamically.
How can this be done in meteor without using jQuery as I have seen many blogs that says it is not a good practice to use jQuery with meteor. Can any tell me how can this be achieved without using jQuery.
You can use a reactive variable and a helper that returns an array based on that reactive variable to construct template-level {{#each}} statements. A good choice for a reactive variable is the Session variable, since it's built into Meteor (you won't need the ReactiveVar package or to set up your own dependencies).
Then, you can use event handlers to update the reactive variable as appropriate. For example...
//client only code
Template.test.onCreated(function() {
Session.set('inputs', []); // on page load, set this to have no inputs
});
Template.test.helpers({
inputs: function () {
return Session.get('inputs'); // reactively watches the Session variable, so when it changes, this result will change and our template will change
}
});
// Now we'll set up a click handler to add inputs to our array when we click the "add" button
Template.test.events({
'click #add-input': function () {
var inputs = Session.get('inputs');
var uniqid = Math.floor(Math.random() * 100000); // Give a unique ID so you can pull _this_ input when you click remove
inputs.push({uniqid: uniqid, value: ""});
Session.set('inputs', inputs);
}
});
// We also need handlers for when the inputs themselves are changed / removed
Template.input.events({
'click .remove-input': function(event) {
var uniqid = $(event.currentTarget).attr('uniqid');
inputs = Session.get('inputs');
inputs = _.filter(inputs, function(x) { return x.uniqid != uniqid; });
Session.set('inputs', inputs);
},
'change input': function(event) {
var $input = $(event.currentTarget);
var uniqid = $input.attr('uniqid');
inputs = Session.get('inputs');
index = inputs.findIndex(function(x) { return x.uniqid == uniqid; });
inputs[index].value = $input.val();
Session.set('inputs', inputs);
}
});
Your templates would look something like...
<template name="test">
<button id='add-input'>
Add Input
</button>
{{#each inputs}}
{{> input}}
{{/each}}
</template>
<template name='input'>
<input name='testinput' class='test-input' type='text' uniqid="{{uniqid}}" value="{{value}}">
<button class='remove-input' uniqid="{{uniqid}}">Remove</button>
</template>
As per Ibrahim's comment below, if you want to delete the text fields, you'll need to keep track of the values in the text fields and repopulate them every time you delete an element. You can see the full work-up in action here. Note that in order to do this, I cheated and actually did use jQuery, because it was way easier to do it that way (at least for me).
A jQuery-less alternative might involve rigging up the onCreated function to store a reference to each input template instance, from which you might be able to pull the necessary information, but per this question there is no way to get all instances of a particular template through the Meteor API, which would be the easiest way to do it without jQuery.
Edit:
MeteorPad no longer exists -- The code above includes handling adding and removing a specific input using the reactive Session variable. I am now maintaining the current value of the input in the Session variable, and I use this new value property to populate the value every time the inputs are re-populated (when the Session variable updates).
You can see that constantly reading stuff off the screen and updating the array of inputs in the Session variable is quite manual and tedious -- which makes me think this is probably not the best way to be doing this.
One possible solution would be to use session variables. When the button is clicked, set the value of the session variable to what you want. In your template you can show the value of the session variable wherever you need.
Also, jquery is automatically included in meteor. There are definitely places to use jquery in meteor apps. May even be cleaner than using session variables in places. Depends on the situation.
Related
I'm new to meteor.js. Still getting used to it.
I get how templates update reactively according to the cursor updates on the server, like this:
{{#if waitingforsomething.length}} Something Happened! {{/if}}
This is good to display elements on the page, updating lists and content. Now, my question is: what if I want to call some javascript or fire some event when something gets updated reactively? What would be the right way to do it with meteor.js?
Anything inside Tracker.autorun or template instance this.autorun runs with changes in reactive data sources inside these autoruns.
Reactive data sources are ReactiveVar instances, db queries, Session variables, etc.
Template.myTemplate.onCreated(function() {
// Let's define some reactive data source
this.reactive = new ReactiveVar(0);
// And put it inside this.autorun
this.autorun(() => console.log(this.reactive.get()));
});
Template.myTemplate.events({
// Now whenever you click we assign new value
// to our reactive var and this fires
// our console.log
'click'(event, template) {
let inc = template.reactive.get() + 1;
template.reactive.set(inc);
}
});
It is a little bit outdated, but Sacha Greif's Reactivity Basics is a very quick and concise introduction to meteor's reactivity model.
Basically, you have what's called reactive computations, code that observes special data objects (sessions, subscriptions, cursors, etc.) and gets executed whenever any of these reactive sources changes.
This is exposed via the Tracker API
Computation works pretty well for me:
Template.myTemplate.onRendered(function() {
this.computation = Deps.autorun(function () {
if (something) {
$(".reactive").html("Something Happened!");
}
});
});
Template.myTemplate.destroyed = function(){
if (this.computation){
this.computation.stop()
}
};
I Hope this helps.
What is the correct approach when working with an "new object" that is to be saved in a collection. Say I have a collection Cars. I have a /cars/new-car
url and then a form with:
name: __
parts: list of parts here
If I want to make this form "reactive" in the sense that if I add a new part in the parts array it shows a rerender is the best approach to make the whole "Car" a reactive object. Or should one just add a new row in the dom?
I dont want to automatically insert the whole thing into the "Cars" collection until It has a name and a list of parts.
Most examples shows very simple of adding to collection -> rerender of DOM which is very straightforward.
Edit: The same concept may apply to when editing a car. Fetching the car from a collection, setting up so the returned object is reactive(so I can add/remove parts) when done get all values and store the edited car information.
Start out by initializing an "empty" car as a reactive variable.
Template.cars.onCreated(function () {
this.car = new ReactiveVar({}); // empty car
});
Say your dom has some sort of attribute on each field describing which key it is:
<input data-key="name" placeholder="Car name"/>
Then you can bind an event that will use the data from this to update the reactive variable.
Template.cars.events({
'change input': function (e, template) {
template.car.set(_.extend(template.car.get(), {
[$(e.target).data('key')]: $(e.target).val()
}));
}
});
This will construct the object as you fill in your inputs.
Consider using Session for your /cars/new-car page
When the page first loads
Session.set('parts', []});
Session.set('name', '');
When the user saves a part
var addedPart = getPart();
var update = Session.get('parts').push(addedPart);
Session.set('parts', update);
Then your template helper functions can get everything it needs to render the view by calling Session.get().
Template.view.helpers({
currentParts: function() {
return Session.get('parts');
}
});
What do you think? I'm fairly new to Meteor myself, so there maybe even more clever ways to do batch updates on the session. But this is general gist.
I have an object in an array
var
sidelist = [
{
name:"MURICA",
types:[...]
}
];
I have a box that displays the object's name. Then I have a text field and a button. On button press the object's name gets set to text field value. But I don't know how to make the name in the box change accordingly.
As I understand putting the object in a session variable is not an option since I will not be able to modify properties of objects inside of it without resetting the whole session var. I tried it and failed.
html
<template name="asdf">
{{#with object}}
<div>{{name}}</div>
{{/with}}
</template>
js
Template.asdf.object = function() {
return Objects.findOne(...);
};
EDIT
I think I've got your question wrong, sorry. If you have a value in memory that you'd like to change and have the DOM updated, use dependencies:
html
<template name="asdf">
{{property}}
</template>
js
var property;
// Create new dependency object that will manage refreshing property value:
var _dep = new Deps.Dependency;
updateProperty = function(value) {
property = value;
// Whenever you change value of the property, call changed() function:
_dep.changed();
};
Template.asdf.value = function() {
// Within reactive function, call depend() to rerun the function
// each time the value is changed:
_dep.depend();
return value;
};
How about a different and in my opinion simpler solution - using a local collection for your data.
I am not sure exactly why do you keep that sort of data into an array, but if it is because you only need it on the client then you can instead create a local collection and have all the reactivity benefits without writing all that code for making the array reactive. The data stored in a local collection is never sent to the server, so no communication or storage overhead.
You'd do it like that:
Sidelist = new Meteor.Collection(null);
[EDIT] Put the above line in your client-side-only part of the code.
Notice the null parameter. This will give you a collection that is only stored on the client and is a regular Meteor reactive source. Then you go about using it in your code and html just as you would a normal collection.
Hope that helps.
I'm going through the leaderboard example right now, and I've finished it, but I'm not fully satisfied with my implementation of the add functionality.
To start with, we have
Template.player.events({
'click': function () {
Session.set("selected_player", this._id);
}
});
I find it a little bit confusing how this is associated with the player collection, but I imagine this has to do with the <template part. I am also able to do
Template.leaderboard.events({
'click input.delete': function () {
Players.remove(this._id);
}
...which does remove the player with the associated button entry.
Now for the actual question part: I have added this to the bottom of the leaderboard template:
<div>
Add player: (Name <input required name="name" id="name">)
(Score <input required name="score" id="score">)
<input class="add" type="button" value="Add">
</div>
This works fine, and I have Template.leaderboard.events['click input.delete'] working fine, but in order to get the values I use:
'click input.add': function () {
var name = document.getElementById('name').value,
score = document.getElementById('score').value;
It would make a lot of sense to me if I were able to use this in some way, or use the event to somehow get the values that correspond the inputs. This not only makes sense to me from a design standpoint, but it would also cover the case of having more than one of these kinds of forms displaying simultaneously.
So in short is there any way to get elements that are near the target element in the context of an event?
Every event handler is given two arguments: event and template. You can read more about these event handler arguments here: http://docs.meteor.com/#eventmaps
event.target is a reference to the DOM element that originated the event. You can then use something like jQuery's traversing functions to get an element nearby.
You could also set the input values as properties of the template instance. E.g. in the template's created handler, you create name and score properties:
Template.player.created = function() {
this.name = '';
this.score = '';
};
And then you update those values in the keyup events of your input textboxes:
'keyup #name': function(event, template) {
template.name = event.target.value;
},
'keyup #score': function(event, template) {
template.score = event.target.value;
}
This is the way the same way that widgets made for Ember update their values, as explained here: http://www.emberist.com/2012/04/12/two-way-binding-to-the-dom.html
Nice to see someone with so much street cred using Meteor! The best way to get the value is with event.currentTarget and to get stuff from the data contexts there is also another way which needs no DOM knowledge
Template.player.events({
'keypress #name':function(event,context) {
//Get the event sending this' value
console.log(event.currentTarget.value)
//Traverse the DOM on the template 'player' in this case
console.log(context.find('#score').value)
}
});
Basically the best way to get the value of the sender is to use event.currentTarget to access the DOM for that object sending the event.
The reason it's implemented this way is probably because any dom object can send an event and it won't necessarily always have a value field so a slight bit of knowledge of the DOM is required when handling the event maps but using event.currentTarget.value works for most form fields
Data contexts
Regarding the data contexts you should be able to use the data available in the templates from the helpers, e.g if theres a {{score}} & a {{name}} value in the template or a helper, which is passed in this case via the {{#each}} for each individual player.
this.name,
this.score;
Which is also the same as (I usually use context in my helper but template is another way of callng it i guess like in travellingprog's answer)
context.data.name,
context.data.score;
The this helps get data from the template's data context into event's so that one doesn't have to use hidden HTML attributes containing data, e.g with how the player is removed its a bit cleaner than storing the _id in the dom somewhere. That being said event.currentTarget or context.find(..) are the best way to get the data from a textfield.
I have the following html that is bound to an object containing id and status. I want to translate status values into a specific color (hence the converter function convertStatus). I can see the converter work on the first binding, but if I change status in the binding list I do not see any UI update nor do I see convertStatus being subsequently called. My other issue is trying to bind the id property of the first span does not seem to work as expected (perhaps it is not possible to set this value via binding...)
HTML:
<span data-win-bind="id: id">person</span>
<span data-win-bind="textContent: status converter.convertStatus"></span>
Javascript (I have tried using to modify the status value):
// persons === WinJS.Binding.List
// updateStatus is a function that is called as a result of status changing in the system
function updateStatus(data) {
persons.forEach(function(value, index, array) {
if(value.id === data.id) {
value.status = data.status;
persons.notifyMutated(index);
}
}, this);
}
I have seen notifyMutated(index) work for values that are not using a converter.
Updating with github project
Public repo for sample (not-working) - this is a really basic app that has a listview with a set of default data and a function that is executed when the item is clicked. The function attempts to randomize one of the bound fields of the item and call notifyMutated(...) on the list to trigger a visual updated. Even with defining the WinJS.Binding.List({ binding: true }); I do not see updates unless I force it via notifyReload(), which produces a reload-flicker on the listview element.
To answer your two questions:
1) Why can't I set id through binding?
This is deliberately prevented. The WinJS binding system uses the ID to track the element that it's binding to (to avoid leaking DOM elements through dangling bindings). As such, it has to be able to control the id for bound templates.
2) Why isn't the converter firing more than once?
The Binding.List will tell the listview about changes in the contents of the list (items added, removed, or moved around) but it's the responsibility of the individual items to notify the listview about changes in their contents.
You need to have a data object that's bindable. There are a couple of options:
Call WinJS.Binding.as on the elements as you add them to the collection
Turn on binding mode on the Binding.List
The latter is probably easier. Basically, when you create your Binding.List, do this:
var list = new WinJS.Binding.List({binding: true});
That way the List will call binding.as on everything in the list, and things should start updating.
I've found that if I doing the following, I will see updates to the UI post-binding:
var list = new WinJS.Binding.List({binding: true});
var item = WinJS.Binding.as({
firstName: "Billy",
lastName: "Bob"
});
list.push(item);
Later in the application, you can change some values like so:
item.firstName = "Bobby";
item.lastName = "Joe";
...and you will see the changes in the UI
Here's a link on MSDN for more information:
MSDN - WinJS.Binding.as
Regarding setting the value of id.
I found that I was able to set the value of the name attribute, for a <button>.
I had been trying to set id, but that wouldn't work.
HTH
optimizeBindingReferences property
Determines whether or not binding should automatically set the ID of an element. This property should be set to true in apps that use Windows Library for JavaScript (WinJS) binding.
WinJS.Binding.optimizeBindingReferences = true;
source: http://msdn.microsoft.com/en-us/library/windows/apps/jj215606.aspx