How Do I Properly Make a Meteor Template Reactive? - javascript

My app displays a collection of items and I'd like to add an item drilldown view.
I've only been able to get half of the solution working; I find the appropriate document and use that document to render the template:
var item = Items.findOne('my_id');
$('#main').html(
Meteor.render(function () {
return Templates.item(item)
}));
This renders the individual item successfully and the appropriate events are bound.
Here's the rub, the template isn't reactive! If I change its data using the associated event handlers or from the console, the template isn't updated. However, a page refresh will reveal the updated data.
I'm a Meteor noobie, so it's probably something very simple. Any help would be greatly appreciated.

It seems to me that you aren't using the templates in they way they were really intended to be.
A meteor app starts with the main html markup which can only exist once in your app..
<head>
<title>My New Fancy App</title>
</head>
<body>
{{>templateName}}
</body>
Then you add a template..
<template name="templateName">
{{#each items}}
template or relevant html goes here..
{{/each}}
</template>
Now you need a template helper to give you data for your {{#each items}} block helper..
Template.templateName.helpers({
items: function(){ return Items.find({}) }
});
All this gets defined on the client side..
Then you'll need a collection and the collection should be defined on both the client and server.
Items = new Meteor.Collection('items');
This should now work as long as you have records in your collection.
Since you wish to only wish to render a single document you can change the helper and template just slightly..
first the helper becomes:
Template.templateName.helpers({
item: function(){ return Items.findOne() }
});
Then the template can reference the values of the returned document through document, so we change our template to:
<template name="templateName">
{{item.propertyName}}
</template>

Related

Ember generated action or jquery onclick

I have an ember application which works fine. But user's interaction does some DOM insertion like below...
$(.test).append(<a {{action "getData"}}>Get Data</a>);
The problem is that Ember seems do not recognize that an action "getData" has been added to the DOM. Nothing is happening when I click the element. Any thoughts on this?
Another way I am trying to do is:
//create the element
$(.test).append(<a id="idnum">Get Data</a>);
//make click listener
$('#idnum').click(function(){
console.log("get data");
}
my question is where should i place the code inside the component so the it can listen on the click event. Thanks.
You should do it in Ember way. Try handlebars {{#if}} helper to render an element dynamically.
{{#if canGetData}}
<a {{action "getData"}}>Get Data</a>
{{/if}}
Here you can set the value of the canGetData to true in the controller based on the users action.
The first example can't work because ember does not analythe the Handlebars elements in the DOM, but rather parses your Handlebars template with HTMLBars, which is a full HTML parser, and then renders it manually by inserting elements, not text into the DOM.
However the second example is the way to go if you have to rely on external code that does manual DOM manipulation. And it does work. Checkout this twiddle.
This does work:
this.$('.target').append('<a id="idnum">Get Data</a>');
this.$('#idnum').on('click', () => {
alert('clicked');
});
Just make sure that the DOM is ready. So do it in the didInsertElement hook or after the user clicked a button or so.
Like Lux suggested avoid DOM manipulation. I prefer the following approach,
if it is dynamic then you can consider wrapping DOM element as a new component and use component helper.
find sample twiddle
In application.js
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
linksArray:[ Ember.Object.create({value:'Text to display',routename:'home'}),
Ember.Object.create({value:'Text to display2',routename:'home'})],
actions:{
addItem(){
this.get('linksArray').pushObject(Ember.Object.create({value:'AddedDynamically',routename:'home'}));
}
}
});
in Application.hbs
<h1>Welcome to {{appName}}</h1>
<br>
{{#each linksArray as |item|}}
{{component 'link-to' item.value item.route }}
{{/each}}
<button {{action 'addItem'}}>Add Item</button>
<br>
{{outlet}}
<br>
<br>

New to Meteor and helper function is not working

I am learning meteor and I cannot get my helper function to return some static text.
<head>
<title>LeaderBoard</title>
</head>
<body>
<h1>Leaderboard</h1>
<p>{{player}}</p>
</body>
in JS
if(Meteor.isClient){
Template.leaderboard.helpers({
player: function(){
return "text";
}
});
}
This only returns the Leaderboard header
UPDATE:
changed to:
LeaderBoard
<body>
<h1>Leaderboard</h1>
<p>{{player}}</p>
</body>
<template name="leaderboard">
{{player}}
</template>
and JS is still the same and it still does not work
So, there are few mistakes you made. Let's deconstruct it.
What is a template?
A template is a piece of code that renders into DOM and can be manipulated using helpers, events and such. For you to use any template, there has to exist one. They can either be put into your app from packages or made by yourself. In this particular case, you're looking for the latter.
To define a template, pick any HTML file or create a new one and define it in HTML way:
<template name="theTemplate">
Hello, I am the template.
</template>
So now you can inject this template wherever in the DOM you want, using this syntax:
<body>
<h1>My super app</h1>
<div>{{> theTemplate}}</div>
</body>
It will render into
<body>
<h1>My super app</h1>
<div>
Hello, I am the template.
</div>
</body>
or, in fact, something a bit uglier since Meteor preserves all the indentation and stuff.
How can I put changeable text into the template?
You're already right that you need helpers for that. A helper is a function that returns an Object (be it String, Number, etc.) which is being injected as is, as if it was document.writed.
Helpers for any template are defined in this way:
Template.theTemplate.helpers({
coolestString: function () {
return 'I am the coolest string put by a helper.';
}
});
Note that Template object contains theTemplate property. It happened exactly after Meteor picked up your template definition and then stored it into an object with helpers method (and a bunch of other useful methods, too).
If you remove theTemplate template definition (aka HTML), the Template object will not have its theTemplate property, and the whole thing will throw a TypeError since you try to access a property of undefined.
How do I put values returned by helpers into the template?
Simply use {{ ... }} syntax. Say, you have a helper coolestString and you need to fetch value from it, whatever it is, and put into h1 tag:
<template name="theTemplate">
<h1>{{ coolestString }}</h1>
</template>
Note the difference between {{> ...}} and {{ ...}}. The former inject a template, the latter inject a value from current context; template's helpers stay within its root context (or just forget it if you don't understand contexts yet).
So, what should I do to use template in my app?
To make a conclusion,
Define a template.
Optionally, define its helpers. Each helper should return a string, a number, an array or an object.
Access helpers' values within the template using {{ ... }} syntax.
Inject the template into your document using {{> ...}} syntax.
That's it.
Okay, show me the whole code!
In myCoolestApp.html,
<body>
{{> theTemplate}}
</body>
<template name="theTemplate">
{{ coolestAppName }}
</template>
And in myCoolestApp.js,
if (Meteor.isClient()) {
Template.theTemplate.helpers({
coolestAppName: function () {
return 'My super cool app!';
}
});
}
Done!
But what if I want to omit template?
In general, a helper by definition belongs to some template, so the hierarchy of injection is the body, then the template, then the helper. But it is possible to inject a helper right into document body and omit intermediary template. You do so with Template.registerHelper method:
Template.registerHelper('theHelper', function () {
return 'I am helper'; // add some logic here and see how it works; hint: reactively.
});
What you do then is just put it into your document:
<body>
{{ theHelper }}
</body>
which gets rendered to
<body>
I am helper
</body>
The principle behind Template.registerHelper is DRY, don't repeat yourself. Sometimes you need to provide exactly same data to more than one template, so at first you would think you have to copy helpers code. But this method helps avoid unnecessary repetition.
You can use more complex objects, covered with more complex logic, this way, or you can even put Mongo collections into the document directly.
Option 1
In case you dont have multiple pages/screens for your app. Edit your template html like this.
<head>
<title>LeaderBoard</title>
</head>
<body>
<h1>Leaderboard</h1>
{{> leaderboard}}
</body>
<template name="leaderboard">
{{player}}
</template>
PS:- {{player}} refers to the template helper "player" and {{> leaderboard}} refers to a template ( This is handlebar syntax ).
Option2 : Your template should look like this.(Assuming you have multiple pages/screens for you app - it would be better if you use some kind of router)
A main layout page - call it master.html
<head>
<title>LeaderBoard</title>
</head>
<body>
</body>
A template named leaderboard. call it leaderboard.html
<template name="leaderboard">
{{player}}
</template>
Then your helper with the same code that you provided in the question.
This should work.

Updating Ember.js data from external event without global controller

I have started learning Ember.js and have been trying to make a page with some data that updates when I get an event (pushed by web sockets).
To make it simple I made an example where I have a list of nodes and when I get a new node I want to call addNode on the controller to add the node. The UI should then update.
The problem I have is that the only way I managed to do this is by having a Global Controller, and then using that in my template instead of my model for the template.
I would like to link up the controller to the route, and have a method on the controller add data when the event arrives - not by having some global list or something.
is this possible? And if so how?
I have included my sample so you can change it and show me how its done.
js:
x = {title : 'test'};
App = Ember.Application.create();
App.Router.map(function() {
this.resource("system");
});
App.NodesController = Ember.ArrayController.create({
content: [],
addNode: function(nodeInfo){
this.pushObject(nodeInfo);
}
});
html:
<script type="text/x-handlebars" id="system">
{{#each App.NodesController}}
{{title}}
{{/each}}
</script>
Thanks, Jason
This is a little shady, but I'm not sure if the ember guys have provided another method of getting the controller externally yet.
jsbin
You've to put needs in your system controller.
App.SystemController - Ember.Controller.extend({
needs:['nodes']
});
and in your system template,
<script type="text/x-handlebars" id="system">
{{#each controllers.nodes}}
{{title}}
{{/each}}
</script>

Meteor: Re-use template with different data

I've got a few templates which list items (log lines, users) and style them accordingly in tables. Now I've added a "search" page which searches for a string in different item types and displays them on one page. In terms of layout, I want to reuse the original templates, but obviously with the data returned by the search.
How can I do that without duplicating the template itself in the HTML file?
Put another way: How can I use the actual data from the top-level template in the sub-templates.
Small example:
<template name="user_list">
{{#each user_list}}
</template>
<template name="users">
{{> user_list}}
</template>
<template name="search">
{{> user_list}}
{{> group_list}}
</template>
In .coffee:
Template.users.user_list = ->
[a,b,c,d]
Template.search.user_list = ->
[b,c]
Maybe this is an easy one, which would show how little I really understood about Meteor.
Just put the template you want to use inside the {{#each}} statement. Then each item in the array will be the context for that inner template.
HTML file
<template name="users">
<ul>
{{#each UserArr}}
{{> userItem}}
{{/each}}
</ul>
</template>
<template name="userItem">
<li>{{username}} {{profile.name}}</li>
</template>
JS file
Template.users.UserArr = function()
{
return Meteor.users.find({});
};
another solution is to put your request in a Session attribute and check for its existence when querying for the users. By default Session.get("userQuery") would return undefined but once you enter something in the search field you change that with Session.set("userQuery", {type: "something"})
Now in the coffeescript you could say:
Template.users.user_list = ->
if(Session.get("userQuery"))
[a,b,c,d]
else
[b,c]
or alternatively use a MiniMongo query because it is much nicer :-)
the nice thing is that your HTML will rerender because it is reactive to the Session's content.

Meteor Startup JQuery DOM Elements Not Ready (JQueryUI Draggable)

In the Meteor docs it says that Meteor.startup will be called after the DOM and all templates have been processed. However, my code within Meteor.startup is acting as if the DOM elements aren't there.
In .js:
Meteor.startup(function () {
console.log($('.draggable').length);
});
In .html:
<template name="item">
<div class="draggable ui-widget-content">
</div>
</template>
In the console I see:
0
But on the page I can see my items. And indeed if I include my JQuery in Template.item.rendered or in a mouseover event, I get the correct length of the array. So why would the startup function not have my DOM elements ready to use?
I'm guessing your code looks something like this, but let me know if I'm wrong:
<template name="list">
{{#each items}}
{{> item}}
{{/each}}
</template>
The {{#each ...}} helper works with a cursor object to respond to data changes on the cursor. So, in your case, if that data is coming from the server (e.g. a subscription), at the time of Meteor.startup, the data might not have been loaded yet. So initially your list will be empty. Then, as data comes off the wire a new item template will be rendered for each data item. If you want to make a specific item draggable, you could put that jQuery code in the Template.item.rendered callback.
Does this help?

Categories