├── LICENSE ├── README.md ├── angularjs-viewhead.js ├── bower.json ├── example ├── example.js └── index.html └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Martin Atkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angularjs-viewhead 2 | ================== 3 | 4 | An AngularJS module to allow views to set the page title and insert extra elements into the head. 5 | 6 | Motivation 7 | ---------- 8 | 9 | In normal use, AngularJS's router allows the selected view to affect a particular portion of the page, 10 | marked by the ``ng-view`` directive. However, AngularJS provides no solution for having the view affect 11 | data outside of the view element, with the most pertinent example being the global page title. 12 | 13 | Many applications resort to tricks like having the controller for each view write extra data into the 14 | root scope, which does indeed make that data available on a per-view basis, but it also violates the 15 | separation of concerns between the controller and the template: the page title (and other ``head`` 16 | elements) are a presentational concern, so they rightfully belong in the template. 17 | 18 | This module provides simple directives to allow this information to be provided via the view template 19 | but to still appear in the ``head`` element in the final document. 20 | 21 | Loading the Module 22 | ------------------ 23 | 24 | This module declares itself as ``viewhead``, so it can be declared as a dependency of your application as 25 | normal: 26 | 27 | ```js 28 | var app = angular.module('myApp', ['ng', 'viewhead']); 29 | ``` 30 | 31 | This makes available the directives described in the following sections. 32 | 33 | Setting a Per-view Page Title 34 | ----------------------------- 35 | 36 | A common pattern in web application is to have the main page title (as displayed in the browser title bar 37 | or tab caption) be a combination of the name of the site or application and the name of the current view. 38 | For example, the about page of a site called FooBaz might have the title "About - FooBaz". 39 | 40 | This sort of setup can be achieved in an AngularJS application using the ``view-title`` directive. First, 41 | set up your title element to bind to the special scope variable ``viewTitle``, which will be set when 42 | a titled view is instantiated: 43 | 44 | ```html 45 | FooBaz 46 | ``` 47 | 48 | With this in place, add to each view's template a single ``view-title`` element setting the view's title: 49 | 50 | ```html 51 | About 52 | ``` 53 | 54 | The content of the element may contain interpolated expressions just like any other element in an AngularJS 55 | template, but the content (after any HTML elements have been stripped) will appear in the main page title 56 | rather than in the document. 57 | 58 | It's common for there to already be an element in the template containing the view title. If that is the 59 | case for your application, you can avoid redundancy by instantiating the directive as an attribute: 60 | 61 | ```html 62 |

About

63 | ``` 64 | 65 | In this case, the text content of the element (after interpolation) will be mirrored into the view title, 66 | and any later changes to the content will cause the title to be updated. 67 | 68 | The ``view-title`` directive should only be used in a view template, and only one instance of it should 69 | appear per view. It should also not be nested inside any element which creates a local scope, such as 70 | ``ng-repeat``. If any of these rules are violated the behavior will be unpredictable. 71 | 72 | Since the ``viewTitle`` variable is just a normal variable in the global scope, you can of course use 73 | it anywhere in your root template, so the view title could be displayed in other spots as well. 74 | 75 | Adding Per-view HTML metadata elements 76 | -------------------------------------- 77 | 78 | There are sometimes other elements in the HTML head that vary between views. Examples include RSS feed links 79 | and Facebook open graph metadata. This markup is parsed primarily by robots like search engine crawlers, 80 | so having your AngularJS app generate them dynamically via JavaScript will not be useful alone, but if you 81 | create static snapshots of your application to serve to robots, for example using a service like 82 | [BromBone](http://www.brombone.com/), this module can be an effective way to include machine-readable 83 | metadata in those snapshots. 84 | 85 | To add per-view metadata elements to the HTML head, place them near the top of your view template (it doesn't 86 | really matter where you put them as long as the are directly in the view scope, not nested inside something 87 | like `ng-repeat`) and add the ``view-head`` directive attribute. For example: 88 | 89 | ```html 90 | 91 | 92 | ``` 93 | 94 | Elements with the ``view-head`` directive will be compiled and evaluated in the scope in which they are 95 | declared (so ``rssUrl`` can be defined by the view-specific controller) but they will actually appear in 96 | the HTML ``head`` element. They will remain there until the view changes, at which point they will be 97 | automatically removed. 98 | 99 | The ``view-head`` directive can be used on any number of elements, but it only makes sense to use it on 100 | elements that are valid in the ``head`` element. No attempt is made to automatically override similar 101 | elements that were originally defined in the head, so e.g. if your main HTML file already includes an 102 | RSS link the view-specific one will appear *in addition to* the main one. Using this directive in spots 103 | other than directly inside the view scope will result in undefined behavior. 104 | 105 | Copyright & License 106 | ------------------- 107 | 108 | Copyright 2013 Martin Atkins. All Rights Reserved. 109 | 110 | This may be redistributed under the MIT licence. For the full license terms, see the LICENSE file which 111 | should be alongside this readme. 112 | 113 | -------------------------------------------------------------------------------- /angularjs-viewhead.js: -------------------------------------------------------------------------------- 1 | 2 | (function (angular) { 3 | 4 | var mod = angular.module('viewhead', []); 5 | 6 | var title; 7 | 8 | mod.directive( 9 | 'viewTitle', 10 | ['$rootScope', '$timeout', function ($rootScope, $timeout) { 11 | return { 12 | restrict: 'EA', 13 | link: function (scope, iElement, iAttrs, controller, transcludeFn) { 14 | // If we've been inserted as an element then we detach from the DOM because the caller 15 | // doesn't want us to have any visual impact in the document. 16 | // Otherwise, we're piggy-backing on an existing element so we'll just leave it alone. 17 | var tagName = iElement[0].tagName.toLowerCase(); 18 | if (tagName === 'view-title' || tagName === 'viewtitle') { 19 | iElement.remove(); 20 | } 21 | 22 | scope.$watch( 23 | function () { 24 | return iElement.text(); 25 | }, 26 | function (newTitle) { 27 | $rootScope.viewTitle = title = newTitle; 28 | } 29 | ); 30 | scope.$on( 31 | '$destroy', 32 | function () { 33 | title = undefined; 34 | // Wait until next digest cycle do delete viewTitle 35 | $timeout(function() { 36 | if(!title) { 37 | // No other view-title has reassigned title. 38 | delete $rootScope.viewTitle; 39 | } 40 | }); 41 | } 42 | ); 43 | } 44 | }; 45 | }] 46 | ); 47 | 48 | mod.directive( 49 | 'viewHead', 50 | ['$document', function ($document) { 51 | var head = angular.element($document[0].head); 52 | return { 53 | restrict: 'A', 54 | link: function (scope, iElement, iAttrs, controller, transcludeFn) { 55 | // Move the element into the head of the document. 56 | // Although the physical location of the document changes, the element remains 57 | // bound to the scope in which it was declared, so it can refer to variables from 58 | // the view scope if necessary. 59 | head.append(iElement); 60 | 61 | // When the scope is destroyed, remove the element. 62 | // This is on the assumption that we're being used in some sort of view scope. 63 | // It doesn't make sense to use this directive outside of the view, and nor does it 64 | // make sense to use it inside other scope-creating directives like ng-repeat. 65 | scope.$on( 66 | '$destroy', 67 | function () { 68 | iElement.remove(); 69 | } 70 | ); 71 | } 72 | }; 73 | }] 74 | ); 75 | 76 | })(angular); 77 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-viewhead", 3 | "main": "angularjs-viewhead.js", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/apparentlymart/angularjs-viewhead", 6 | "authors": [ 7 | "Martin Atkins " 8 | ], 9 | "description": "Change the HTML title and head elements on a per-view basis", 10 | "license": "MIT", 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | 2 | var app = angular.module('viewheadExample', ['ng', 'ngRoute', 'viewhead']); 3 | 4 | app.config( 5 | function ($routeProvider) { 6 | $routeProvider.when( 7 | '/', 8 | { 9 | templateUrl: 'partials/home.html' 10 | } 11 | ); 12 | $routeProvider.when( 13 | '/dynamic-title', 14 | { 15 | templateUrl: 'partials/dynamic-title.html', 16 | controller: function ($scope) { 17 | $scope.chosenTitle = 'Dynamic Title'; 18 | } 19 | } 20 | ); 21 | $routeProvider.when( 22 | '/rss-link', 23 | { 24 | templateUrl: 'partials/rss-link.html', 25 | controller: function ($scope) { 26 | $scope.rssUrl = 'example.rss'; 27 | } 28 | } 29 | ); 30 | } 31 | ); 32 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | viewhead example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 25 | 26 |
27 | 28 |
29 | 30 | 31 | 47 | 48 | 62 | 63 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-viewhead", 3 | "version": "0.0.2", 4 | "description": "Change the HTML title and head elements on a per-view basis", 5 | "authors": [ 6 | "Martin Atkins " 7 | ], 8 | "analyze": true, 9 | "contributors": [ 10 | { 11 | "name": "Alejandro Moreno", 12 | "email": "alejonext@gmail.com" 13 | } 14 | ], 15 | "main": "angularjs-viewhead.js", 16 | "repository": { 17 | "type": "git", 18 | "url": "git@github.com:apparentlymart/angularjs-viewhead.git" 19 | }, 20 | "engines": { 21 | "node": ">=0.10", 22 | "npm": ">=1.1.59" 23 | }, 24 | "license": "MIT" 25 | } 26 | --------------------------------------------------------------------------------