Questions Meteor about changing view and putting array in collection - javascript

I got two question about the Meteor framework
First of all, how can I put an array inside a Meteor collection? And how can I push values into it?
Second of all, when I have a button and I click on it, how can I change the current view? Is this by hiding and showing templates?
Thanks!

Use $addToSet to push values into an array:
var coll = new Meteor.Collection;
coll.insert({myArray: []});
coll.update({}, {$addToSet: {myArray: "myNewValue"}});
There are many ways to change views, but an easy one is to use Session and check whether it has a value in your template:
<template name="mytemplate">
<button>Say hello</button>
{{#if sayHello}}<p>Hello</p>{{/if}}
</template>
Template.mytemplate.events({
"click button": function() {
Session.set("sayHello", true);
}
});
Template.mytemplate.sayHello = function() {
return Session.equals("sayHello", true);
}

Related

How to use Session.setDefault() with external data inside helper

I use Meteor 1.2.1. I am currently developing a Course Catalog Web App.
I'm trying to create a filtering feature just like Angular Filters. It should let the user specify his needs and then the course list refreshes automatically as he changes the filters.
This is my code:
app.html
<!-- ********** -->
<!-- courseList -->
<!-- ********** -->
<template name="courseList">
<div class="list-header">
<div>Course Name</div>
<div>Description</div>
<div>Category</div>
<div>Modality</div>
<div>Unit</div>
</div>
<div class="list-body">
{{ #each course }}
{{ > courseRow}}
<hr />
{{ /each }}
</div>
</template>
app.js
Template.courseList.helpers({
'course': function(){
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
}
});
So when I run this, I get an empty set for the course helper above, even though there's some dummy data present on the database.
It's seems to me that the issue is with Session.setDefault(). When I console.log the Session.course variable right after the find, I get an empty array [], maybe because there was no time to get the data from the server (or maybe not, because I'm developing with autopublish for now, I don't really know).
After I apply some of the filters (code not shown here), everything goes back to normal. This initialization only is the problem.
I've tried to call Session.set() inside Template.courses.rendered(), Template.courses.created(), Template.courses.onRendered(), Template.courses.onCreated() (yes, I was kinda desperate) but none of them worked.
Can someone please advise on that issue? Maybe I'm not trying the correct Meteor aproach, as I am a Meteor beginner.
Thanks!
I think you try to use template subsciption. so you can send some terms for publication. example;
Template.foo.onCreated(function() {
this.name = new reactiveVar("");
this.autorun( () => {
const terms = {
name: this.name.get()
};
this.subscribe("Posts_list", terms);
});
});
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
There are two things here. First thing is that your helper will run whenever either the result of Courses.find().fetch() or Session.get('course') changes, since both of them are reactive data sources. To avoid this, you can do
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
If you want to set the session variable so that you can use it somewhere else, then you can use Session.set instead of Session.setDefault, because Session.setDefault will only assign value only is the session variable is not already available. But I don't know why you want to do that, since you can use Posts.find().fetch() where ever required instead of session variable. Both are reactive.
Template.foo.helpers({
lists: function() {
Session.set('course', Courses.find().fetch());
return Session.get('course');
}
});
If you want to assign the course session variable only for the first time when you get non-empty data then you might want to do something like this.
Template.foo.helpers({
lists: function() {
if (Courses.find().count() > 0) {
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
} else {
return [];
}
});
If you can tell why you need the session variable, we might think of a different approach to solve the problem.
Hope it helps.

Adding text fields dynamically in Meteor without using jquery

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.

EmberJS filter array of items as the user types

I am very new to Ember, and trying to write a simple search/filter for array of items in EmberJS, but it seems to harder than it looks. I found a similar question here, but I can't seem to bind the search input to the function. My example is the defauly Todo App that is on EmberJS website.
<script type="text/x-handlebars" data-template-name="todos">
{{input type="text" id="search-todo" placeholder="Search Todo List..." value=searchTerm}}
.....
</script>
In my App.TodosController I have
searchResult: function(){
var searchTerm = this.get('searchTerm');
if(!searchTerm.trim()){return;}
var regExp = new RegExp(searchTerm);
return this.get('content').filter(function(item){
return regExp.test(item.get('title'));
});
}.property('searchTerm','#each.title')
but when I type, nothing happens. I tried to add the searchResult to the actions hash of the controlller, and I see the function get called when I add action= searchResult to the script tag, but I nothing happens. Basically, I just want a simple filtering of the list like AngularJS, and I want it to be not only over title but all of the content, and I don't need it to be a separate route, if it has to be in a separate route, I still don't know how to accomplish this.
http://jsbin.com/AViZATE/20/edit
Thanks for your help.
I was able to resolve it by adding the observes keyword.
Here is a link to the solution http://jsbin.com/AViZATE/37
searchResult: function(){
var searchTerm = this.get('searchTerm');
var regExp = new RegExp(searchTerm,'i');
this.get('model').set('content',this.store.filter('todo',function(item){
return regExp.test(item.get('title'));
}));
}.observes('searchTerm')
And in Html I have
{{input type="text" id="search-todo" placeholder="Search Todo List..." value=searchTerm}}
The "Ember way" to do this is to create a custom view that listens to the keyUp event (or any other event) and sends the search term to the controller's function:
Todos.SearchTodoView = Ember.TextField.extend({
keyUp: function(event){
this.get('controller').send('searchResult', this.value);
}
});
For some reason I can't get this to work on your udpated fiddle, when I try to acces the controller from the view it returns the view itself. But I know it works since I'm using it in my own app.

In Ember how do I update a view after the ArrayController content is modified?

I'm just starting with ember so forgive me if this question seems daft. I have an ArrayController that holds a list of people with the attribute name. I'm using the following handlebars call to create textfields for each name in the content array:
{{#each App.Controller}}
{{view Em.TextField placeholder="Name" valueBinding="name" }}
{{/each}}
this is working as expected. I first initialize my content array with several names and a textfield appears for each entry. However when I use pushObject to add a person to the content array a new textfield is not added! Using the console I can see that the people are being added to content, the view is simply not changing. Stranger still, if I add people in the following manner everything works as expected:
this.pushObject(newPerson);
var copy = this.get('content');
this.set('content',[]);
this.set('content',copy);
When I add people this way I get another textfield for the newly added person. The ember documentation says pushObject() is KVO-compliant, doesn't that mean it should update the dependencies and the view should update? Do I need to call rerender() or some other function to make pushObject() update the view or do I have to copy the array each time I want to update the view? Thanks!
So I figured out the issue. In my init function for the Controller I was simply adding elements to the content array like this
App.Controller = Em.ArrayController.create({
content: [],
init: function(){
this.insertNew();
},
insertNew: function(){
var t = App.Person.create({
name:"test"
});
this.pushObject(t);
},
});
This code did not change the view when insertNew was called (though the initialization worked). By adding this.set("content",[]) to my init function insertNew behaved as expected. Here's the working code:
App.Controller = Em.ArrayController.create({
content: [],
init: function(){
this.set("content",[]);
this.insertNew();
},
insertNew: function(){
var t = App.Person.create({
name:"test"
});
this.pushObject(t);
},
});
I had thought the first content:[] line meant I did not need to set content again later, but it seems that when initializing the array you have to set it again.
I encounter similar problems, when I use this.pushObject to add a new item, the view only updates if the item is the first object.
Later I tried with this.insertAt, it works well without any problem.

Select view template by model type/object value using Ember.js

I would like to store different objects in the same controller content array and render each one using an appropriate view template, but ideally the same view.
I am outputting list objects using the code below. They are currently identical, but I would like to be able to use different ones.
<script type="text/x-handlebars">
{{#each App.simpleRowController}}
{{view App.SimpleRowView class="simple-row" contentBinding="this"}}
{{/each}}
</script>
A cut-down version of the view is below. The other functions I haven't included could be used an any of the objects, regardless of model. So I would ideally have one view (although I have read some articles about mixins that could help if not).
<script>
App.SimpleRowView = Em.View.extend({
templateName: 'simple-row-preview',
});
</script>
My first few tests into allowing different object types ended up with loads of conditions within 'simple-row-preview' - it looked terrible!
Is there any way of dynamically controlling the templateName or view used while iterating over my content array?
UPDATE
Thanks very much to the two respondents. The final code in use on the view is below. Some of my models are similar, and I liked the idea of being able to switch between template (or a sort of 'state') in my application.
<script>
App.SimpleRowView = Em.View.extend({
templateName: function() {
return Em.getPath(this, 'content.template');
}.property('content.template').cacheable(),
_templateChanged: function() {
this.rerender();
}.observes('templateName'),
// etc.
});
</script>
You can make templateName a property and then work out what template to use based on the content.
For example, this uses instanceof to set a template based on the type of object:
App.ItemView = Ember.View.extend({
templateName: function() {
if (this.get("content") instanceof App.Foo) {
return "foo-item";
} else {
return "bar-item";
}
}.property().cacheable()
});
Here's a fiddle with a working example of the above: http://jsfiddle.net/rlivsey/QWR6V/
Based on the solution of #rlivsey, I've added the functionality to change the template when a property changes, see http://jsfiddle.net/pangratz666/ux7Qa/
App.ItemView = Ember.View.extend({
templateName: function() {
var name = Ember.getPath(this, 'content.name');
return (name.indexOf('foo') !== -1) ? 'foo-item' : 'bar-item';
}.property('content.name').cacheable(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
});

Categories