├── 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 |
13 |