Meteor - Setting the document title - meteor

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'
}
})
}
})();

Related

Why isn't this template reactive?

Why isn't this reactive? And more importantly how can it be made reactive?
I'd like the data to be saved in Mongo and used in the template. I could use a ReactiveVar or ReactiveDict. Do I need two copies of the data?
Doesn't Suspects.findOne('bruce') return a reactive object already? I tried putting the human answer directly on Bruce, but it didn't trigger an update.
The events fire, log(this) shows bruce's answer was changed, but the template doesn't re-render. What's the good way to do this?
http://meteorpad.com/pad/KoH5Qu7Fg3osMQ79e/Classification
It's Meteor 1.2 with iron:router added:
<head>
<title>test</title>
</head>
<template name="question">
{{#unless isAnswered 'human'}} <!-- :-< I'm not reacting here -->
<div>Sir, are you classified as human?</div>
<button id="no">No, I am a meat popsicle</button>
<button id="smokeYou">Smoke you</button>
{{else}}
<div> Classified as human? <b>{{answers.human}}</b></div>
{{/unless}}
</template>
And the JavaScript:
// Why isn't this reactive?
if (Meteor.isClient) {
Template.question.helpers({
isAnswered: function (question) { // :-< I'm not reactive
var suspect = Template.instance().data;
return (typeof suspect.answers[question] !== 'undefined');
}
});
Template.question.events({
'click #no': function () {
this.answers.human = "No"; // :-< I'm not reactive
console.log(this);
},
'click #smokeYou': function() {
this.answers.human = "Ouch"; // :-< I'm not reactive
console.log(this);
}
});
}
// Collection
Suspects = new Meteor.Collection('suspects');
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Suspects.upsert('bruce', { quest: 'for some elements', answers: {}});
});
Meteor.publish('suspects', function() {
return Suspects.find({});
});
}
// Iron Router
Router.route('/', {
template: 'question',
waitOn: function() {
return Meteor.subscribe('suspects');
},
data: function() {
return Suspects.findOne('bruce');
}
});
Thanks :-)
The events are not actually updating the reactive data source (the db record). Instead of doing:
Template.question.events({
'click #no': function () {
this.answers.human = "No";
}
});
The event needs to perform a database action, either through a direct update or through a Meteor.call() to a Meteor.method. For example:
'click #no': function(){
Suspects.update('bruce', {'answers': {'human': 'no'}});
}
If you use this pattern, you will also need to set the correct allow and deny rules to permit the update from client code. http://docs.meteor.com/#/full/allow. Methods generally end up being a better pattern for bigger projects.
Also, I'm not sure off the top of my head that Template.instance().data in your helper is going to be reactive. I would use Template.currentData() instead just to be sure. http://docs.meteor.com/#/full/template_currentdata
Very close you just need to use ReactiveVar by the sound of it it pretty much explains what it's :) http://docs.meteor.com/#/full/reactivevar
and here's how to use it
if (Meteor.isClient) {
Template.question.onCreated(function () {
this.human = new ReactiveVar();
});
Template.question.helpers({
isAnswered: function (question) {
return Template.instance().human.get();
}
});
Template.question.events({
'click #no': function (e, t) {
t.human.set('No');
console.log(t.human.get());
},
'click #smokeYou': function(e, t) {
t.human.set('Ouch');
console.log(t.human.get());
}
});
}
UPDATE: if you're using a cursor I usually like to keep it on the template level not on iron router:
if (Meteor.isClient) {
Template.question.helpers({
isAnswered: function (question) {
return Suspects.findOne('bruce');
}
});
Template.question.events({
'click #no': function (e, t) {
Suspects.update({_id: ''}, {$set: {human: 'No'}});
},
'click #smokeYou': function(e, t) {
Suspects.update({_id: ''}, {$set: {human: 'Ouch'}});
}
});
}

UI helper function does not have the subscription data in Meteor

I am fairly new to Meteor.
I have a template helper function which requires to work with data I am publishing from the server side. When the page loads, the value for profile below is undefined. But after that when I execute the same code snippet from browser's console, it works just fine. From what I understand, the template helper is being executed before the data is published. How can I wait until the data is published to run the UI helper?
Here is relevant code.
Helper Function
Template.header.helpers({
me: function() {
var user = Meteor.users.findOne(Meteor.userId());
if (user) {
return Profiles.findOne({
spid: user.profile.spid
});
}
return null;
}
});
HTML Template
<template name="header">
<header class="ui fixed inverted menu">
{{> thumb user=me}}
</header>
</template>
Thumbnail Template
<template name="thumb">
{{#with user}}
<div class="thumb">
<div class="text" style="background-color:{{color}}">
{{initials name}}
</div>
<div class="pic" style="background-image:url('{{pic}}')"></div>
</div>
{{/with}}
</template>
Publications
Meteor.publish('profiles', function() {
return Profiles.all();
});
Meteor.publish('departments', function() {
return Departments.all();
});
Meteor.publish('requestServiceIds', function() {
return Requests.getServiceIds();
});
Meteor.publish('relevantServices', function() {
return Services.getRelevant(this.userId, 5);
});
Router
Router.configure({
layoutTemplate: 'main',
waitOn: function() {
Deps.autorun(function() {
Meteor.subscribe('profiles', Partitioner.group());
Meteor.subscribe('departments', Partitioner.group());
});
}
});
Router.onBeforeAction(function() {
if (this.route.getName() === 'not-authorized') return this.next();
if (!Meteor.userId() || !Cookie.get('TKN')) {
this.render('login');
} else {
this.next();
}
});
Router.route('/', {
name: 'home',
waitOn: function() {
Deps.autorun(function() {
Meteor.subscribe('requestServiceIds', Partitioner.group());
Meteor.subscribe('relevantServices', Partitioner.group());
});
}
});
---
UPDATE 1
I updated the the router a bit. But it did not seem to have had made any difference.
Router.configure({
layoutTemplate: 'main',
waitOn: function() {
// Deps.autorun(function() {
// Meteor.subscribe('profiles', Partitioner.group());
// Meteor.subscribe('departments', Partitioner.group());
// });
return [
Meteor.subscribe('profiles', Partitioner.group()),
Meteor.subscribe('departments', Partitioner.group())
];
}
});
Router.route('/', {
name: 'home',
waitOn: function() {
// Deps.autorun(function() {
// Meteor.subscribe('requestServiceIds', Partitioner.group());
// Meteor.subscribe('relevantServices', Partitioner.group());
// });
return [
Meteor.subscribe('requestServiceIds', Partitioner.group()),
Meteor.subscribe('relevantServices', Partitioner.group())
];
}
});
Create a dumb loading template such as this one (using font awesome):
<template name="loading">
<div class="loading">
<i class="fa fa-circle-o-notch fa-4x fa-spin"></i>
</div>
</template>
And try replacing your Router.configure() part with something like this:
Router.configure({
layoutTemplate: 'main',
action: function() {
if(this.isReady()) { this.render(); } else {this.render("loading");}
},
isReady: function() {
var subs = [
Meteor.subscribe('profiles', Partitioner.group());
Meteor.subscribe('departments', Partitioner.group());
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {},
profiles: Profiles.find(),
departments: Departments.find()
};
}
}
});
I guess that could be achieved using waitOn, but since your way does not work, I give you a working recipe I use everyday. Code inspired from meteor Kitchen generated code.
To answer your comments:
Regarding the Action not being triggered, it might be because we tried to put in inside the Router.configure. I don't do it that way, here are some details on how I implement it.
First, I set a controller for each route inside a router.js file (where I have my Router.configure() function too. It looks like that:
Router.map(function () {
this.route("login", {path: "/login", controller: "LoginController"});
// other routes
}
Next, I create a controller that I store in the same folder than my client view template. It looks like that:
this.LoginController = RouteController.extend({
template: "Login",
yieldTemplates: {
/*YIELD_TEMPLATES*/
},
onBeforeAction: function() {
/*BEFORE_FUNCTION*/
this.next();
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("loading"); }
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {}
};
/*DATA_FUNCTION*/
},
onAfterAction: function() {
}
});
So this action function is working when I extend a route using a controller. Maybe it will solve your issue.
For completeness sake, here is how my Router.configure() looks like:
Router.configure({
templateNameConverter: "upperCamelCase",
routeControllerNameConverter: "upperCamelCase",
layoutTemplate: "layout",
notFoundTemplate: "notFound",
loadingTemplate: "loading",
//I removed the rest because it is useless.
});

One template for 2 thing MeteorJS

I have db with posts and all of them have bollean flagged
I have one template and navigation like (Read, Dont Read).
Problem is that I see all posts (when I must see posts with flagged false or flagged true), and I dont understand why, I think problem in publish/subscribe
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
});
AllPostsController = RouteController.extend({
template: 'timeTable',
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
Router.map(function() {
this.route('timeTable', {path: '/',
controller: AllPostsController
});
this.route('readPosts', {path: '/read',
controller: readPostsController
});
this.route('dontreaderPosts', {
path: '/dontreader',
controller: dontreaderPostController
});
});
Meteor.publish('allPosts', function(){
return Posts.find({},{ sort: { createdAt: -1 }});
});
Meteor.publish('readPosts', function(){
return Posts.find({read:true},{ sort: { createdAt: -1 }});
});
Meteor.publish('dontreadPosts', function(){
return Posts.find({read:false},{ sort: { createdAt: -1 }});
});
If someone need more code, Just ask me
Anybody help
EDIT : David solved problem for regular tasks. Main problem that I have specific return Posts.find(...) in my tamplate helper.
<template name="timeTable">
{{#if posts_exist_week}}
{{> table posts=week}}
{{/if}}
{{#if posts_exist_month}}
{{> table posts=month}}
{{/if}}
</template>
<template name="table">
<table class="main-table table">
{{#each posts}}
{{> post}}
{{/each}}
</table>
</template>
You solved my problem if I did not have template timeTable (that show posts for last week and month) Because here it Template helper
Template.timeTable.helpers({
week: function() {
//...
return Posts.find({createdAt: {$gte: weekstart, $lt: yesterday}},{ sort: { createdAt: -1 }}); //return posts that was created in this week
},
month: function() {
//...
return Posts.find({createdAt: {$gte: monthstart, $lte: weekstart}},{ sort: { createdAt: -1 }});
}
});
And now you see that if I choose your decision (David) I will have 2 !!
return
first - in router
second - in template helper
I recreated this locally and found that extend causes the parent controller's waitOn to run. So whenever you go to the /read route it will actually activate both subscriptions and you'll end up with all of the documents on your client. A simple fix is to refactor your controllers like so:
PostController = RouteController.extend({
template: 'timeTable'
});
AllPostsController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
That being said, you don't want to build your app in a way that it breaks when extra subscriptions happen to be running. I would rewrite the controllers to select only the documents that pertain to them. For example:
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
},
data: {selector: {read: false}}
});
And now your helpers can use the selector like this:
Template.timeTable.helpers({
week: function() {
var selector = _.clone(this.selector || {});
selector.createdAt = {$gte: weekstart, $lt: yesterday};
return Posts.find(selector, {sort: {createdAt: -1}});
}
});
Also note that sorting in the publish functions may not be useful - see common mistakes.

MeteorJs: Return data Iron:router

Iron router return data is in template but I can't use it.
For example I have db with jobs, where every job has a position (e.g. jobs.position):
ExistJobPostController = RouteController.extend({
layoutTemplate: 'existJob',
data:function() {return Posts.findOne(this.params._id); }
})
Router.map(function() {
this.route('existJob', {
path: '/jobs/:_id',
controller: ExistJobPostController,
});
});
<template name="existJob">
{{position}}
</template>
And nothing happens, I think that it's my fault, but I really can't understand how to fix this.
Can anybody help?
You should first check that the correct data is even being set on your template data context. Here's a quick general summary of how to set the data context and how to access it from various locations:
Router.map(function() {
this.route('index', {
path: '/index',
data: function(){
var obj = {
fname: "Tom",
lname: "Smith"
};
return obj;
}
});
});
Template.index.onRendered(function(){
console.log(this.data.fname);
});
Template.index.events({
'click body': function(e, tmpl){
console.log(tmpl.data.fname);
}
});
Template.index.helpers({
lastName: function(){
return this.lname;
}
});
<template name="index">
You have to use `this` when directly accessing template data from spacebars.
{{this.firstName}}
The following comes from Template.index.helpers:
{{lastName}}
</template>

Meteor: wainOn (Iron Route) not working

I have this issue that the rendered function is called while the elements are not yet in the DOM. Let me describe my situation. The templates looks like this:
<template name="barfoo">
<ol>
{{#each bars}}
<li item>{{title}}</li>
{{/each}}
</ol>
</template>
And the following javascript code
Template.barfoo.bars = function () {
return Bars.find({});
};
Tempalate.barfoo.rendered = function () {
var bars = Bars.find({}).fetch();
var list = $('[items]');
};
When the rendered function is called bars == list == [].
So to fix this I implemented Iron routes like this
Bars = new Meteor.Collection('bars');
// Routes
Router.configure({
layout: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound'
});
Router.map(function () {
this.route('/', {
controller: 'BarsController',
});
});
if (Meteor.isClient) {
App = {
subs: {
bars: Meteor.subscribe('bars'),
...
}
};
MainController = RouteController.extend({
template: 'barfoo',
before: function () { ... },
waitOn: function () {
return [App.subs.bars];
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
Meteor.publish('bars', function () {
return Bars.find({});
});
}
All basics, but when I go now to localhost:3000 I still get in the rendered function empty lists . Any suggestions what do wrong here ?
I assume your are working with the latest version of iron-router. If that's the case you will also need to add loading hook to your router, so:
Router.onBeforeAction('loading');
Also note that before hook is marked as deprecated.

Resources