How do I get a template helper to become non reactive? - javascript

I'm very new to Meteor (started yesterday) so bear with me.
I've got the following to try and get some messages from the server:
var messages = new Mongo.Collection('messages');
if (Meteor.isServer) {
//Initial first few messages
Meteor.publish("messages", function () {
return messages.find({}, {sort: {createdAt : -1}, limit: 2, reactive : false});
});
}
And then a helper for the template:
Template.card.helpers({
messages: function () {
return messages.find({});
}
});
Template:
<div class="messages-slider">
{{#each messages}}
{{> message}}
{{/each}}
</div>
I'm trying to get the first couple of messages and keep them static in the DOM, basically - however, with the above code the template updates with the collection regardless. How do I fix it?

The reactive option of find is client-only. Use it in your helper:
Session.set('ready', false);
Meteor.subscribe('messages', { onReady: function() {
Session.set('ready', true);
}});
Template.card.helpers({
messages: function () {
if (Session.get('ready')) {
return messages.find({}, {reactive: false});
}
}
});

With reference to Christian's answer:
I think you could probably solve the rendering-before-the-subscription-is-ready issue by wrapping the template in {{#if Template.subscriptionsReady}] so it won't render until the subscription is loaded. No need for Session variables in that case. For example:
Template.card.onCreated(function(){
var instance = this;
instance.autorun(function() {
instance.subscribe('messages');
});
});
Template.card.helpers({
messages: function() {
messages.find({},{reactive:false});
}
});
Template:
<template name="card">
{{#if Template.subscriptionsReady}}
<div class="messages-slider">
{{#each messages}}
{{> message}}
{{/each}}
</div>
{{/if}}
</template>

Related

Meteor: Router with URL of a userName?

I have problems with creating routes with user's usernames. So idea is something like this: Click on path and go to that users profile. His link should be something like : http://www.something.com/usersUsername
I was reading and trying everything I found on internet about this but lot of stuff changed so I couldn't manage this.
Only thing I found usefull is that when page loads client ,,watch" paths first and then subscribes to a collection so I got ,,null" for path. Any help? My idea is to create something to waitOn for subscribe...
Packages: iron:router , accounts-ui , accounts-password
Here is code:
Start page, template:
<template name="početna">
<h1>Dobrodošli!</h1>
<h3>Registrujte se:</h3>
{{> register}}
<h3>Prijavite se:</h3>
{{> login}}
{{#if currentUser}}
<h2>Logovan si!</h2>
{{> logout}}
Profil
{{/if}}
Router JS file:
Router.configure({
layoutTemplate: 'okvir'
});
// * * * * * * //
Router.route('/', {
name: 'početna',
template: 'početna',
});
Router.route('/:username', {
waitOn: function(){
return Meteor.subscribe('userData'), Meteor.user().username
},
name: 'profil',
template: 'profil',
});
Simple HTML template file only to se if it works:
<template name="profil">
<h1>RADI</h1>
</template>
Thanks!
Here you go:
Router.route('/:username',{
name: 'profil',
waitOn: function () {
return Meteor.subscribe('userData', this.params.username)
},
action: function () {
this.render('profil', {
data: function () {
return Meteor.users.findOne({username: this.params.username});
}
});
}
});
EDIT:
With this.params.username will let anybody visit that profile, user or not. If you want to prevent that, you can use onBeforeAction()
onBeforeAction: function() {
if(Meteor.user() && this.params.username == Meteor.user().username){
this.next();
} else {
Router.go('/not-authorized') //or any other route
}
},
Luna, thanks for help! Luna's answer helped but I also needed:
1.) Helper to set value of username=username
Template["početna"].helpers({ username: function() { return Meteor.user().username } })
2.) Publish
Meteor.publish("userData", (username) => {
return Meteor.users.find({
username: username
})
});

Can autorun's reactive computation be dependent on part of a collection?

I'd like to create a template in Meteor that has a Tracker.autorun which exclusively runs when part of a document changes --- but not when other parts of the document change.
So here is sample code using a minimongo collection and template.autorun
parent.html
{{#each items}}
{{> child}}
{{/each}}
child.html
<div>{{title}}</div>
<p>{{description}}</p>
Minimongo Collection
LocalProject.findOne() output:
"items": [
{
"title": "hi",
"description": "test"
},
{
"title": "hi 2",
"description": "test 2"
},
],
"otherstuff:{//etc}
child.js
Template.child.onRendered(function(){
this.autorun(function() {
var data = Template.currentData();
doSomething(data.title,data.description)
});
});
addnewitem.js
LocalProject.update(currentEditingProjectID,{ $push: { 'items': newItem }},function(error,result){
if(error){console.log(error)}
});
The problem is, whenever I run addnewitem.js, all of my Template.child autoruns execute even though their reactive data source (Template.currentData()) has not changed unless it was the specific item I updated. Similarly if I want to update an existing item, not just add a new one to the array, all of the autoruns for each item get executed.
So is there a way, using this model, to create a dependency for autorun that is reactively granular to specific portions of a document?
I don't think the way to go is by using an autorun. I would either set up individual reactive dependencies on each item, or use observe/observeChange.
First idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
Template.parent.helpers({
// Returns only item ids
items: function() {
return Items.find({}, { fields: { _id: 1 } });
}
});
child.html:
{{#each items}}
{{#with item=getItem}}
<div>{{item.title}}</div>
<p>{{item.description}}</p>
{{/with}}
{{/each}}
child.js:
Template.child.helpers({
getItem: function() {
// Get the item and set up a reactive dependency on this particular item
var item = Items.find(this._id);
// After item has been displayed, do something with the dom
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
return item;
}
});
Second idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
function do(item) {
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
}
Template.parent.onCreated({
this.items = Items.find({});
this.handle = this.items.observe({
added: function(item) { do(item); },
changed: function(oldItem, newItem) { do(newItem); },
});
});
Template.parent.onDestroyed({
this.handle.stop();
});
Template.parent.helpers({
items: function() {
return Template.instance().items;
}
});
child.html:
{{#each items}}
<div>{{title}}</div>
<p>{{description}}</p>
{{/each}}
There's a tool just for this - 3stack:embox-value provides reactive isolation, and value caching.
Using your example, you could isolate changes to title/description like so:
first up, add the packages
meteor add 3stack:embox-value
meteor add ejson
Then, update your code:
Template.child.onRendered(function(){
// creates a reactive data source, that only triggers "changes"
// when the output of the function changes.
var isolatedData = this.emboxValue(function(){
var data = Template.currentData();
return {title: data.title, description: data.description}
}, {equals: EJSON.equals})
this.autorun(function() {
// This autorun will only execute when title or description changes.
var data = isolatedData()
doSomething(data.title,data.description)
});
});

How do I change between templates to display different media in Meteor/Jscript

I am currently trying to have 3 separate templates that the user can switch between by clicking on one of 3 buttons. By using a session variable ('currentContent'), 3 buttons and 3 templates I cannot see what's going wrong with my current code.
In my javascript:
Template.priority.helpers({
expensesbtn:function(){
return Session.get('currentContent') ==='expenses'?true:false;
},
custombtn:function(){
return Session.get('currentContent') ==='cexpenses'?true:false;
},
incomebtn:function(){
return Session.get('currentContent') ==='earning'?true:false;
},
});
Template.priority.events({
"click #expensesbtn":function(event, template){
Session.set('currentContent', 'expenses')
},
"click #custombtn":function(event, template){
Session.set('currentContent', 'cexpenses')
},
"click #incomebtn":function(event, template){
Session.set('currentContent', 'earning')
}
});
and then in my html:
{{>priority}}
{{#if cexpenses}}
{{> cexpenses}}
{{/if}}
{{#if expenses}}
{{> expenses}}
{{/if}}
{{#if earning}}
{{> earning}}
{{/if}}
Any help with this would be greatly appreciated. Thanks!
#Sindis is right, your helper names must match what you are calling in your templates. But another problem is that while the conditionals aren't inside a template named priority, your helpers are attached to the priority template, so they wouldn't be recognized even if the names match. You can fix this by putting everything inside the priority template and separating the buttons out into a separate sub-template if that is your intention.
You can also make your code much more elegant and DRY by using only one session variable chosenTemplate and only one helper and avoiding repeating all those conditionals. Then use Meteor's Template.dynamic feature to display the correct template. Here is an example solution below. Make a new template called templateControl and place the buttons inside it. Then place everything inside the priority template.
<template name="priority">
{{> templateControl }}
{{> Template.dynamic template=chosenTemplate }}
</template>
<template name="templateControl">
{{#each buttons}}
<button id="{{ id }}" class="chose-template">{{ label }}</button>
{{/each}}
</template>
Template.templateControl.helpers
buttons: [
{ id: 'incomebtn', label: 'Income' },
{ id: 'expensesbtn', label: 'Expenses' },
{ id: 'custombtn', label: 'Custom' }
]
Template.templateControl.events
'click .chose-template': function(e, t) {
Session.set('chosenTemplate', this.id);
}
Template.priority.helpers
chosenTemplate: function(e, t) {
return Session.get('chosenTemplate');
}
Then be sure to give the three templates you switch between names corresponding to the template ids set in the chosenTemplate Session variable!
Hope this helps.
Your helpers have to be the same when you use them in HTML, so your js should look like:
Template.priority.helpers({
expenses: function(){
return Session.get('currentContent') === 'expenses';
},
cexpenses: function(){
return Session.get('currentContent') === 'cexpenses';
},
earning: function(){
return Session.get('currentContent') === 'earning';
}
});

Ember.js use itemController if not following naming Conventions

According to official documentation, way to create itemcontroller is:
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
But I'm not setting my ArrayController to App. It is set to a local variable behind a function scope. And the itemController property can only be string (according to documentation). So how do I set the itemController property?
My code looks like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
)
});
App.ChannelController=Ember.Controller.extend({
channels:Channels,
}));
<script type="text/x-handlebars" data-template-name='channel'>
<div>
{{#each channel in channels.list}}
{{channel.label}}
{{/each}}
</div>
</script>
I don't want to pollute App namespace with itemControllers that is to be used locally.
Update
Suppose my channels is like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
),
selected:"display"
});
and I want to something like this in template:
<script type="text/x-handlebars" data-template-name='channel'>
<h1>{{channels.selected}}</h1>
<div>
{{#each channel in channels.list}}
<div {{bind-attr class="channel.isselected:active:inactive"}}>{{channel.label}}</div>
{{/each}}
</div>
</script>
so that it outputs:
<h1>display</h1>
<div>
<div class="active">Display</div>
<div class="inactive">Social</div>
<div class="inactive">Email</div>
</div>
How do I do it with components?
You'll likely want to read the guide of components to get the full picture, but the gist of it is that you want to replace all item controllers with components. However, components will also replace the template inside of the each block as well. I don't entirely understand what's going on in your code, but here's an example roughly based on your code.
// Component
App.ChannelDisplayComponent = Ember.Component.extend({
channel: null,
isSelected: function() {
// Compute this however you want
// Maybe you need to pass in another property
}.property('channel')
});
{{! Component Template }}
<div {{bind-attr class="channel.isSelected:active:inactive"}}>
{{channel.label}}
</div>
{{!Channels Template}}
{{#each channel in channels.list}}
{{channel-component channel=channel}}
{{/each}}
The component is essentially your item controller, only it gets its own template as well.
You really shouldn't be worried about polluting the app namespace (unless you're having naming collisions, but that's a different issue). And as Kitler said, you should move to components instead of item controllers. But if you want to do this, the best way I can think of is overridding the (private) controllerAt hook.
var ItemController = Ember.Controller.extend({});
App.PostsController = Ember.ArrayController.extend({
controllerAt: function(idx, object, controllerClass) {
var subControllers = this._subControllers;
if (subControllers.length > idx) {
if (subControllers[idx]) {
return subControllers[idx];
}
}
var parentController = (this._isVirtual ? this.get('parentController') : this);
var controller = ItemController.create({
target: parentController,
parentController: parentController,
model: object
});
subControllers[idx] = controller;
return controller;
}
})

ArrayController sorting - each statement - model

I've been trying making a new library app based on the code-school ember tutorial (books instead products... not too complicated).
I'm using the latest ember.js stable release, 1.1.10.
using the {{#each}} in my templates,
js:
App.BooksRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('book');
}
});
html:
{{#each}}
<h1>{{title}}</h1>
{{/each}}
the following warning is shown in the console
DEPRECATION: Using the context switching form of {{each}} is deprecated. Please use the keyword form (`{{#each foo in bar}}`)
So I've been trying to use the recommended syntax and I've found this, which is working
html:
{{#each book in model}}
<h1>{{book.title}}</h1>
{{/each}}
But when it comes the time to try the sortProperties in my arrayController, with my {{#each book in model}}
js:
App.BooksRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('book');
}
});
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['title']
});
html:
{{#each book in model}}
<h1>{{book.title}}</h1>
{{/each}}
my books are not sorted...
I've found another workaround, building a property inside my ArrayController:
js:
App.BooksRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('book');
}
});
App.BooksController = Ember.ArrayController.extend({
books: function(){
return this;
}.property(),
sortProperties: ['title']
});
html:
{{#each book in books}}
<h1>{{book.title}}</h1>
{{/each}}
It's sorted!
, but I'm not satisfied...
Is there another cleanest/simplest way to use the each statement as defined in ember 1.1.10 and sort my array ?
Instead of {{#each book in model}}
use {{#each book in arrangedContent}} or {{#each book in this}}
Try using 'arrangedContent' to get your array sorted in ArrayController.
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['title'],
content: function() {
return this.get('arrangedContent'); // this gives sorted array
},
});
Hope this helps

Categories