Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@iStefo
Created April 29, 2013 13:14
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save iStefo/5481507 to your computer and use it in GitHub Desktop.
Save iStefo/5481507 to your computer and use it in GitHub Desktop.
Ember-Meta module. See first comment for usage etc.!
// include this code in your application, you don't need to call any initialization manually!
Ember.Application.initializer({
name: "meta",
initialize: function(container, application) {
// helper function to get tag from dom
var _getTag = function(tagname, property, value) {
var tags = document.head.getElementsByTagName(tagname),
tag,
i;
for (i = 0; i < tags.length; i++) {
tag = tags[i];
if (tag[property] && tag[property] === value) {
return tag;
}
}
};
// hold logic and information
var Meta = Ember.Object.extend(Ember.Evented, {
application: null,
// string values
title: null,
description: null,
// dom elements
_ogTitle: null,
_description: null,
_ogDescription: null,
// defaults
defaults: {
title: null,
description: null
},
summary: Ember.computed(function() {
return '<title>' + this.get('title') + '</title>\n' +
this.get('_ogTitle').outerHTML + '\n' +
this.get('_description').outerHTML + '\n' +
this.get('_ogDescription').outerHTML;
}).property('_ogTitle', '_ogDescription'),
// propagate changes to dom elements
titleChanged: function() {
document.title = this.get('title');
this.get('_ogTitle').setAttribute('content', this.get('title'));
this.notifyPropertyChange('_ogTitle');
}.observes('title'),
descriptionChanged: Ember.observer(function() {
this.get('_description').setAttribute('content', this.get('description'));
this.get('_ogDescription').setAttribute('content', this.get('description'));
this.notifyPropertyChange('_ogDescription');
}, 'description'),
init: function() {
this._super();
this.on('reloadDataFromRoutes', this.reloadDataFromRoutes);
},
reloadDataFromRoutes: function() {
var handlers = this.get('application').Router.router.currentHandlerInfos,
newTitle,
newDescription,
i = handlers.length;
// walk through handlers until we have title and description
// take the first ones that are not empty
while (i--) {
var handler = handlers[i].handler;
if (!newTitle) {
newTitle = Ember.get(handler, 'metaTitle');
}
if (!newDescription) {
newDescription = Ember.get(handler, 'metaDescription');
}
}
// save changes or snap back to defaults
if (newTitle) {
this.set('title', newTitle);
} else if (this.get('defaults.title')) {
this.set('title', this.get('defaults.title'));
}
if (newDescription) {
this.set('description', newDescription);
} else if (this.get('defaults.description')) {
this.set('description', this.get('defaults.description'));
}
this.trigger('didReloadDataFromRoutes');
}
});
var meta = Meta.create({application: application});
meta.set('defaults.title', document.title);
// setup meta object
// are there any tags present yet? if not, create them
// ogTitle
var _ogTitle = _getTag('meta', 'property', 'og:title');
if (!_ogTitle) {
_ogTitle = document.createElement('meta');
_ogTitle.setAttribute('property', 'og:title');
document.head.appendChild(_ogTitle);
}
meta.set('_ogTitle', _ogTitle);
// description
var _description = _getTag('meta', 'name', 'description');
if (!_description) {
_description = document.createElement('meta');
_description.setAttribute('name', 'description');
document.head.appendChild(_description);
} else {
meta.set('defaults.description', _description.content);
}
meta.set('_description', _description);
// ogDescription
var _ogDescription = _getTag('meta', 'property', 'og:description');
if (!_ogDescription) {
_ogDescription = document.createElement('meta');
_ogDescription.setAttribute('property', 'og:description');
document.head.appendChild(_ogDescription);
} else {
meta.set('defaults.description', _ogDescription.content);
}
meta.set('_ogDescription', _ogDescription);
// save object to app
application.set('meta', meta);
}
});
// to update the information, a little push is required.
// you usually would want to do it like this:
App.Router.reopen({
didTransition: function(infos) {
this._super(infos);
Ember.run.next(function() {
// the meta module will now go trough the routes and look for data
App.meta.trigger('reloadDataFromRoutes');
});
}
});
// as you can see, there is a App.meta object that handles all the action
// you can even set title and description directly if you want to!
App.meta.set('title', 'New Site Title');
App.meta.set('description', 'Somethign changed, so I update my meta data.');
// you can as well ask for all the tags that were set by calling
App.meta.get('summary');
// you can define static metadata in your root url
App.BlogRoute = Ember.Route.extend({
metaTitle: "My awesome blog",
metaDescription: "Here you can read stuff from brushing my teeths to drying my hair."
});
// or dynamic metadata for dynamic routes
App.PostRoute = Ember.Route.extend({
model: function(params) {
// ... etc
},
metaTitle: function() {
return this.get('context.title');
}.property('context.title'),
metaDescription: function() {
return this.get('context.abstract');
}.property('context.abstract')
});
// unfortunately, it's not possible to do metaTitleBinding: 'context.title' :(
@iStefo
Copy link
Author

iStefo commented Apr 29, 2013

Ember-Meta

This module takes care of keeping the html meta tags updated when the user navigates through the application.

Specifically, it updates <title>...</title>, <meta name="description" content="...">, <meta property="og:title" content="..."> and <meta property="og:description" content="...">.

When received the 'reloadDataFromRoutes' event, it looks up the currently active route handlers and searches them from leave to root until it finds a set metaTitle and metaDescription.
It then updates the page's title, description, opengraph title and opengraph description tags in the site's head. To do so, at initialization, it detects if there are any tags present yet and re-uses them or creates new tags if needed.

This is usefull if you are keeping static (plain html) versions of your site for crawlers etc. This way, the correct metadata is right at your hand!

See examples on how to use the module below and please feel free to tell me your improvements and things I missed when coding this module!

@thingista
Copy link

@iStefo Very cool. Thanks for sharing this! I tested it on our app, but I ran into an issue. In my case, the _getTag function does not work properly. It seems the tag objects returned by getElementsByTagName are different from what you expect. There is an "attributes" indirection (e.g. tag.attributes[property]). I had to replace:

if (tag[property] && tag[property] === value) { ...

with

if (tag.attributes[property] && tag.attributes[property].value.toLowerCase() === value.toLowerCase()){ ...

I haven't tested thoroughly though, but I wanted to mention this to you in case you know what's going on.

But thanks again, great stuff!
PJ

@marlonmantilla
Copy link

Hi guys forget the ignorance here but I'm new to Ember, trying to make this work but getting App is undefined when calling App.meta.trigger('reloadDataFromRoutes'); inside my router. Thanks in advance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment