I have a model:
App.Checkin = DS.Model.extend({
latitude: DS.attr('string'),
longitude: DS.attr('string'),
time: DS.attr('number')
});
And a route that loads the collection of checkin models (making a request with ember-data) by
model: function() {
return this.store.find('checkin');
}
And then in the template for the route I have
{{view App.MapView}}
And I need to access the model programmatically so that I can iterate over each item in the model to add a pin to the map.
Inside my view I have
didInsertElement: function() {
var data = this.get("context.content");
}
and data is
Class {type: function, store: Class, isLoaded: true, isUpdating: true, toString: function…}
In the network window, the request to the server hasn't completed by that point, so it obviously wouldn't have data to provide. (Even if it did, I don't know how to query an object like that, none of the expected methods worked (get/forEach))
I believe that I need to observe the model being changed, and have tried
updatePins: function() {
debugger;
}.observes('context.content')
inside of the view. I have tried binding to all sorts of things, but reRender has never been called. I've tried the recommendations on Emberjs view binding for Google maps markers
by trying to bind to controller.content, context, App.Checkin, etc.
How do I go about getting the data from the model once it has loaded...inside of the view?
Until the model does not resolve what you get is a promise for that model, obviously the promise does not contain the data just yet but when the request comes back from the server. You could check in your template if the model has data, by observing the model.length property for example using an if helper, the if helper block will re-evaluate when the length property changes this beeing when it has received data.
For example, you could try something like this:
...
{{#if model.length}}
{{view App.MapView}}
{{/if}}
...
This will ensure that your App.MapView is then rendered when your model has data and therefore you will have also access to the data in the view's didInsertElement hook as you'd expect.
Update
Your reRender hook is named slightly wrong, it should be rerender
rerender: function() {
debugger;
}.observes('context.content')
Hope it helps.
Related
I have a template named profile which contains three other templates. One of these templates is {{> postlist}}
and the helper function for this template is
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : postsArr}});
}
});
The problem is on going to the route, postlist template is empty, since postsArr is calculated later after the dom has loaded on the basis of other two templates. But, if I click on other route and come back to this route, the template renders properly.
What should I do that template renders properly initially itself?
The easiest way would be to us Session, though it's probably the worst option:
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : Session.get('postsArr') }});
}
});
If you now call Session.set('postArr', ...) anywhere in your code the posts helper will update automatically. The second option is to use a shared reactive variable:
var postsArr = new ReactiveVar();
and then inside your helper:
return Posts.find({rph: {$in : posts.Arr.get() }});
Now you can do postsArr.set(...) and everything should work fine. Just remember to meteor add reactive-var do your project.
One last doubt is: where to put that reactive variable declaration? In most cases you can do away with putting in a single "controller" file. It will work as long as:
- you only have one instance of your template a time
- the code which sets ad gets the value of you reactive variable may be put in the same file
If one of the above conditions does not hold, then the only option to go, which is BTW the best possible, is to put your state variable in your template's scope. This is how you do it:
Template.postsList.created = function () {
this.postsArr = new ReactiveVar();
};
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : Template.instance().postsArr.get() }});
}
});
From helpers you can always access postsArr using the Template.instance() routine which always return the current template instance, for which the helper was called. From event handlers, note that the second argument of your handler is always the template instance, which you're interested in.
If you need to access it from another templates, then you should probably put your state variable on the corresponding route controller. Assuming you're using iron-router, that would be:
Iron.controller().state.get('postsArr');
The Iron.controller routine grants you access to the current route controller. Read this for more details.
What's the best way to completely teardown and reinstantiate a component in the old one's place, preferably from a template?
Our use case is we have a bunch of Backbone models/collections that are used in our views. In init we might listen to some of those model or collection events (that are sometimes deep), or we may do some sort of setup work relative to that model. It seems we have two options: listen for if the entire model property changes on the view and then unbind any events and bind them to the new model and redo any setup work, or force the view to teardown and put a new one in its place with the new model, since the template may change significantly or even completely. We chose the latter route due to the significance of the change and to ensure we start with a clean slate in the view.
Up to this point we've been wrapping the component in a conditional and changing a boolean to force the old component to teardown and a new one to rerender:
HTML
<p>Some stuff that isn't bound to the model: {{prop1}}, {{prop2}}</p>
{{#if isRenderable}}
<myComponent model="{{model}}" />
{{/if}}
JS
component.set('isRenderable', false); // force `myComponent` to teardown
component.set('model', aDifferentModel); // this often happens in/via template
component.set('isRenderable', true); // force a new `myComponent` to render
Is this a decent approach or are we looking at this all wrong? It seems like there has to be a better option, especially since is necessary in a few places in our app.
One way to do this would be to use the reset() method of the component to change the data, and include a dynamic template function to choose the appropriate template. One of the advantages is that it will not need to re-render the template unless data.type changes. (btw - the design behind the default for components to not re-render is that if the data is updating is more efficient to update the DOM values than to re-render everything. The falsey-block trick works to force a refresh - but that may not always be needed).
There are a lot of details that are specific to your implementation, but this example will give you some ideas:
var Page = Ractive.extend({
template: function(data, t){
return data.type ? t.fromId(data.type) : 'loading...'
}
})
var r = new Ractive({
el: '#container',
template: '#template',
data: { model: datas.person1 },
components: {
page: Page
},
oninit: function(){
var page = this.findComponent('page')
this.observe('model', function(n){
page.reset(n)
})
},
load: function(load){
this.set('model', datas[load])
}
})
This works if there is shared-logic, or no logic, in the component that is rendering the various models.
Often though, you want to use a different component for each model type because there are observers and event handlers specific to that view for that particular model. In that case, this example up-levels the dynamism to the parent and uses an option function for the component:
var r = new Ractive({
el: '#container',
template: '#template',
data: datas.person1,
components: {
page: function(data){
return components[data.type]
}
},
load: function(load){
this.reset( datas[load] )
}
})
In an application currently I am loading my views using routers like below
router('menu/:item', function (item) {
app.uiHandler.toggleMenuSelected('menu', item);
// The below method updates the view with selected menu item's model.
app.channel.publish('menu', item);
});
Currently each menu item shares same data object. But the master view is replaced with new html based on each menu selection.
I am thinking to have instance like below for each menu item
var ractive = new Ractive({
el: 'container', // el is same for all instances.
template: '<p> I am {{selection}}, after {{prevSelection}}!</p>',
data: { selection: 'Home', prevSelection: 'Profile' }
});
But here my doubt is as I will be invoking each instance to render the view into 'container' whenever hash is changed how to clear all the two way data bindings created when master view is replaced with new html. Please help me on this.
If I am handling in wrong way, what would be the best way to handle the same.
Note : My question might sound like stupid, but I am looking for clarification on this :)
how to clear all the two way data bindings created when master view is replaced with new html
You can use teardown() to destroy the ractive instance, but if you don't ractive will do that automatically as soon as you try to render a new instance into the same container.
That said, it's probably better to have one instance and only update the data. I.e. when the section changes call ractive.set({ selection: 'Profile', prevSelection: 'Home' }).
In my meteor app I need to load an array of items corresponding to the item clicked.As I'm new to meteor, I'm held up here.Here is my code.
Template.templatename.events({
'click .showdiv' : function()
{
Template.templatename.vname = function () {
return Db.find();
}
}
Can I set the variable vname dynamically by this code ? This is not working for me.
I think you're misunderstanding the notion of reactivity. A reactive data source will cause any functions which depend on it (including helpers) to rerun when its value is changed, which seems to be the behavior you're looking for here. Instead, you're rewriting the helper function itself every time an item is clicked, which kind of defeats the object of Meteor's reactive data model. Session variables could help:
Template.templatename.events({
'click .showdiv' : function() {
Session.set('vname', Db.find());
}
});
Template.templatename.vname = function () {
return Session.get('vname');
}
If you use an {{#each vname}} block in the templatename template, it will automatically update with the results of the Db.find() query when a .showdiv is clicked. If all you want to do is show the result of that query regardless of whether a click has been registered it would be as simple as:
Template.templatename.vname = function () {
return Db.find();
}
Note that it's still not clear exactly what data you're trying to populate here since the query will return a cursor (which is fine, but you need to loop through it using {{#each ...}} - use findOne if you only want one item), and its contents aren't going to depend on anything intrinsic to the click event (like which .showdiv you clicked). In the former example it will however fail to show anything until the first click (after which you would have to reset with Session.set('vname', null) to stop it showing anything again).
I have a Meteor template that includes the following:
{{#with selected_recipe}}
{{>recipe}}
{{/with}}
In my code (Coffeescript), I want to call a function by name from my event map (Backbone-style):
Template.recipe.events = {
'click #btn-edit-recipe': 'editRecipe'
}
editRecipe = (event) ->
console.log # #should log the selected_recipe object
#edit recipe
However, this fails. When I click on my button in the recipe template, I get Uncaught TypeError: Object editRecipe has no method 'call' (liveui.js:651) I learned event maps from Backbone, and maybe Meteor is different. I can get it to work with:
Template.recipe.events = {
'click #btn-edit-recipe': -> editRecipe.call(#, event)
}
Is this the right way to do this? Or am I making some simple error? I've always liked using event maps this way because it summarizes the behaviors of the rendered template in just a few lines. Anonymous functions can spread the list out, making it harder to read, and of course they are not reusable.
What you are doing (later one, where event definition points to a function) is right.
Event map with value as function name (string) is pattern specific to backbone. Meteor doesn't support it.
I've always liked using event maps this way because it summarizes the
behaviors of the rendered template in just a few lines.
But you can acheive similar functionality by doing something like this:
Template.recipe.doLogin = function(){};
Template.recipe.requestData = function(){};
// OR Another way
_.extend(Template.recipe, {
"openFile":function(){},
"editRecipe":function(){}
});
// now Events
Template.recipe.events {
'click #btn-edit-recipe': Template.recipe['editRecipe'],
'click #btn-create-recipe': Template.recipe['createRecipe']
}
Personally, I don't like event-maps. cause its a mapping, which developer has to maintain manually.
Edit: Working code # https://gist.github.com/3010818