I have a template:
<template name="example">
<div class="target"></div>
</template>
I want to select the .target element (in coffeescript):
Template.example.rendered = ->
$target = $(".target")
I could do this. But, it will look in the whole html document. I want to search only in the template. What should I do?
In coffee:
Template.example.rendered = ->
$target = #$('.target')
In javascript:
Template.example.rendered = function() {
var $target = this.$('.target');
};
In Meteor 1.0.4.2 and later, template.rendered is deprecated. Use onRendered instead.
In coffee:
Template.example.onRendered ->
$target = #$('.target')
In javascript:
Template.example.onRendered(function() {
var $target = this.$('.target');
});
Related
I've just started using iron router in meteor. How do I hide or delete a template, or replace it with another?
I have two client-side routes. One shows a list of chatrooms a user can join:
Router.route('/', function () {
this.layout('bodyLayout');
this.render('roomList', {
to: 'roomList'
});
});
The other is for the chatrooms:
Router.route('/room/:_id', function () {
this.layout('bodyLayout');
var roomId = this.params._id;
this.render('room', {
to: 'room',
data: () => {
return { roomId }
}
});
});
Both of these have the same layout where they are yielded close together:
<template name="bodyLayout">
<!-- layout stuff -->
{{> yield 'roomList'}}
{{> yield 'room'}}
<!-- layout stuff -->
</template>
Now, if I go to / and click a room, the room renders under it. But what I really want is for this to show either room OR roomList. How do I delete roomList when creating room, or can I replace it with room somehow?
So ultimately I've found several solutions.
Replacing a template
This is useful in a case like mine, if a certain location needs only one of several templates. It turns out that several routes can render to the same yield. we can remove one of the yields:
<template name="bodyLayout">
<!-- layout stuff -->
{{> yield }}
<!-- layout stuff -->
</template>
And remove the to attribute from the object we pass to this.render:
Router.route('/', function () {
this.layout('bodyLayout');
this.render('roomList');
});
Router.route('/room/:_id', function () {
this.layout('bodyLayout');
var roomId = this.params._id;
this.render('room', {
// We don't need this anymore:
// to: 'room',
data: () => {
return { roomId }
}
});
});
Alternatively, do give the yield a name:
<template name="bodyLayout">
<!-- layout stuff -->
{{> yield 'content' }}
<!-- layout stuff -->
</template>
And give both routers the 'to' attribute that refers to the same yield:
Router.route('/', function () {
this.layout('bodyLayout');
this.render('roomList', { to: 'content'});
});
Router.route('/room/:_id', function () {
this.layout('bodyLayout');
var roomId = this.params._id;
this.render('room', {
to: 'content',
data: () => {
return { roomId }
}
});
});
Removing a template from a router
If you do need to remove a template from a router, you can do this by giving the this.render function an empty string instead of a template name, thus telling it to render no template to this yield:
Router.route('/', function () {
this.layout('bodyLayout');
this.render('roomList', {
to: 'roomList'
});
// Remove the room that was shown
this.render('', { to: 'room'});
});
My brain is hurting because of all the inconsistency. Please have a look at the code below and correct/complete it:
Template.Example.events({
'click #example': function(event, template) {
instance = template; // or = Template.instance();
instance_reactive_data_context = template.currentData(); // or = Template.currentData();
instance_nonreactive_data_context = ???
event_data_context = event.currentTarget;
});
Template.Example.helpers({
example: function() {
instance = Template.instance();
instance_reactive_data_context = this; // or = Template.currentData();
instance_nonreactive_data_context = ???
}
});
Template.Example.onCreated(function () {
instance = this;
instance_reactive_data_context = this.currentData();
instance_nonreactive_data_context = this.data;
});
Here's the answer, which even shows a bit more. It includes creating and accessing a reactive-var or reactive-dictionaries attached to the template. All this is extremely important to understand for Meteor developers:
Template.Example.onCreated(function () {
instance = this; // or = Template.instance();
// instance_reactive_data_context = no point in having a reactive data context since this function is only executed once
instance_nonreactive_data_context = this.data;
// now in order to attach a reactive variable to the template:
let varInitialValue = ...
instance.reactive_variable = new ReactiveVar(varInitialValue);
// and now let's attach two reactive dictionaries to the template:
let dictInitialValue_1 = { ... }
let dictInitialValue_2 = [ ... ]
instance.reactive_dictionaries = new ReactiveDict();
instance.reactive_dictionaries.set('attachedDict_1', dictInitialValue_1);
instance.reactive_dictionaries.set('attachedDict_2', dictInitialValue_2);
});
Template.Example.events({
'click #example': function(event, template) {
instance = template; // or = Template.instance();
instance_reactive_data_context = Template.currentData();
instance_nonreactive_data_context = template.data;
event_data_context = event.currentTarget;
// to access or modify the reactive-var attached to the template:
console.log(template.reactive_variable.get());
template.reactive_variable.set('new value');
// to access or modify one of the reactive-dictionaries attached to the template:
console.log(template.reactive_dictionaries.get('attachedDict_2'));
template.reactive_dictionaries.set('attachedDict_2', { newkey: 'new value', somearray: ['a', 'b'] });
});
Template.Example.helpers({
example: function() {
instance = Template.instance();
instance_reactive_data_context = this; // or = Template.currentData();
// instance_nonreactive_data_context = it can't be accessed as a non-reactive source. When you'll need something like this, most likely because the helper is running too many times, look into the [meteor-computed-field][1] package
// to access or modify the reactive-var attached to the template:
console.log(Template.instance().reactive_variable.get());
Template.instance().reactive_variable.set('new value');
// to access or modify one of the reactive-dictionaries attached to the template:
console.log(Template.instance().reactive_dictionaries.get('attachedDict_2'));
Template.instance().reactive_dictionaries.set('attachedDict_2', 'new value here');
// obviously since you declared instance on the first line, you'd actually use everywhere "instance." instead of "Template.instance()."
}
});
How can I make Iron:router re-render a template?
I have this html:
<head>
</head>
<body>
</body>
<template name="mainLayout">
list
find
{{> yield}}
</template>
<template name="listTemplate">
<p>list</p>
</template>
and this js:
Router.configure({
layoutTemplate: 'mainLayout'
});
Router.route('/list', {
name: 'list',
template: 'listTemplate'
});
Router.route('/find', {
name: 'find',
template: 'listTemplate',
data: function () {
return this.params.query;
}
});
if (Meteor.isClient) {
Template.listTemplate.rendered = function () {
if (this.data)
console.log('find ' + this.data.q);
else
console.log('list all');
};
}
When I click on the links to switch views (simulated here with console.log), the route does change, but the template is not re-rendered.
Is there a way to force iron:router to re-render?
Setting the router controller state did not work for me. The answer Antônio Augusto Morais gave in this related github issue worked. Using the Session to store the reactive var to trigger the autorun reactiveness. It's a hack, but it works.
## router.coffee
Router.route '/profile/:_id',
name: 'profile'
action: ->
Session.set 'profileId', #params._id
#render 'profile'
## profile.coffee
Template.profile.onCreated ->
#user = new ReactiveVar
template = #
#autorun ->
template.subscription = template.subscribe 'profile', Session.get 'profileId'
if template.subscription.ready()
template.user.set Meteor.users.findOne _id: Session.get 'profileId'
else
console.log 'Profile subscription is not ready'
Template.profile.helpers
user: -> Template.instance().user.get()
## profile.html
<template name="profile">
{{#if user}}
{{#with user.profile}}
<span class="first-name">{{firstName}}</span>
<span class="last-name">{{lastName}}</span>
{{/with}}
{{else}}
<span class="warning">User not found.</span>
{{/if}}
</template>
You can try something like this:
Router.configure({
layoutTemplate: 'mainLayout'
});
Router.route('/list', {
name: 'list',
template: 'listTemplate',
action: function() {
this.state.set('query', this.params.query);
}
});
Router.route('/find', {
name: 'find',
template: 'listTemplate',
data: function() {
return this.params.query;
},
action: function() {
this.state.set('query', this.params.query);
}
});
if (Meteor.isClient) {
Template.listTemplate.rendered = function() {
this.autorun(
function() {
if (this.state.get('query'))
console.log('find ' + this.data.q);
else
console.log('list all');
}
);
};
}
The rendered method isn't reactive, that's why you need an autorun.
The template "this.data" isn't reactive so you're gonna need a reactive var to do that, either a Session variable, a controller state, or some kind of reactive var.
You may need to add the reactive-var package depending on what approach you take.
I need to invoke a JS function inside a foreach loop in knockout data-bind.
I need to do something like:
<div data-bind="foreach:items()">
.....
<script>
jQuery(function () { jQuery('#myid').rateit({ value:$data.value }); })
</script>
....
</div>
Obviously it doesn't work, i've not found a way to apply data binding inside a script tag ... is there a way to do it?
No, you can't.
If you want to execute something for every item in a collection (or on any observable) you can use a computed observable.
This would work in your scenario:
ko.computed(function () {
var items = this.items();
for (var i = 0; i < items.length; i++) {
jQuery(function () { jQuery('#myid').rateit({ value: item[i].value }); })
}
}, viewModel);
Is there a way to change the <title> element in a Meteor app? Seems templates are only processed in the <body>.
Pretty much anywhere in your client-side JavaScript code:
document.title = "My new title";
You can extend David Wihl's solution:
Deps.autorun(function(){
document.title = Session.get("DocumentTitle");
});
Then You can in any time call:
Session.set("DocumentTitle","New Document Title");
If you use iron:router you can add the package manuelschoebel:ms-seo to handle the title along with meta tags. This is useful for static and dynamic SEO data:
Router.map(function() {
return this.route('blogPost', {
path: '/blog/:slug',
onAfterAction: function() {
var post = this.data().post;
SEO.set({
title: post.title,
meta: {
'description': post.description
},
og: {
'title': post.title,
'description': post.description
}
});
}
});
});
You can create a helper for setting the title (CoffeeScript):
UI.registerHelper "setTitle", ->
title = ""
for i in [0..arguments.length-2]
title += arguments[i]
document.title = title
undefined
or the same in Js:
UI.registerHelper("setTitle", function() {
var title = "";
for (var i = 0; i < arguments.length - 1; ++i) {
title += arguments[i];
}
document.title = title;
});
Then you can use it in complex ways, and it will be reactive:
{{#if book}}
{{setTitle book.title}}
{{else}}
{{setTitle "My books"}}
{{/if}}
I find it more convenient to handle that kind of thing directly in the router with a onBeforeAction:
Router.map(function() {
return this.route('StudioRoot', {
path: '/',
onBeforeAction: function() {
return document.title = "My Awesome Meteor Application";
}
});
});
you can also include in <head> </head> tags which does not reside in a template. try this:
contents of sample.html:
<head>
<title>my title</title>
</head>
<body>
...
</body>
<template name="mytemplate">
...
</template>
What I ended up doing:
in the Meteor.isClient:
Meteor.startup(function() {
Deps.autorun(function() {
document.title = Session.get('documentTitle');
});
});
now that the var is set reactively, go in the router file (if not already done: meteor add iron:router. My router file is both client and server)
Router.route('/', {
name: 'nameOfYourTemplate',
onBeforeAction: function () {
Session.set('documentTitle', 'whateverTitleIWant');
this.next(); //Otherwise I would get no template displayed
}
});
It doesn't matter if you already set a title in the head tag. It will be replaced by this one according to your route.
I had to look for an answer that would work for ui-router. I know that this might not be the answer you were looking for. Since this question was posted about 2 years ago, I figured if someone else was to come here looking for a solution with ui-router, this answer could help them:
myApp.run.js
(function() {
'use strict';
angular
.module('myApp')
.run(run);
run.$inject = ['$rootScope', '$state'];
function run($rootScope, $state) {
$rootScope.$on("$stateChangeSuccess", function(previousRoute, currentRoute){
document.title = 'myApp - ' + currentRoute.data.pageTitle;
});
};
})();
routes.js
(function() {
'use strict';
angular
.module('myApp')
.config(config);
config.$inject =
['$urlRouterProvider', '$stateProvider', '$locationProvider'];
function config($urlRouterProvider, $stateProvider) {
// ...
$stateProvider
.state('home', {
url: '/',
templateUrl: 'client/home/views/home.ng.html',
controller: 'HomeController',
data: {
pageTitle: 'My Dynamic title'
}
})
}
})();