1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /** @typedef {Document|DocumentFragment|Element} */
9 * @fileoverview This is a simple template engine inspired by JsTemplates
12 * It currently supports three handlers:
14 * * i18n-content which sets the textContent of the element.
16 * <span i18n-content="myContent"></span>
18 * * i18n-options which generates <option> elements for a <select>.
20 * <select i18n-options="myOptionList"></select>
22 * * i18n-values is a list of attribute-value or property-value pairs.
23 * Properties are prefixed with a '.' and can contain nested properties.
25 * <span i18n-values="title:myTitle;.style.fontSize:fontSize"></span>
27 * This file is a copy of i18n_template.js, with minor tweaks to support using
28 * load_time_data.js. It should replace i18n_template.js eventually.
31 var i18nTemplate
= (function() {
33 * This provides the handlers for the templating engine. The key is used as
34 * the attribute name and the value is the function that gets called for every
35 * single node that has this attribute.
40 * This handler sets the textContent of the element.
41 * @param {!HTMLElement} element The node to modify.
42 * @param {string} key The name of the value in |data|.
43 * @param {!LoadTimeData} data The data source to draw from.
44 * @param {!Array<ProcessingRoot>} visited
46 'i18n-content': function(element
, key
, data
, visited
) {
47 element
.textContent
= data
.getString(key
);
51 * This handler adds options to a <select> element.
52 * @param {!HTMLElement} select The node to modify.
53 * @param {string} key The name of the value in |data|. It should
54 * identify an array of values to initialize an <option>. Each value,
55 * if a pair, represents [content, value]. Otherwise, it should be a
56 * content string with no value.
57 * @param {!LoadTimeData} data The data source to draw from.
58 * @param {!Array<ProcessingRoot>} visited
60 'i18n-options': function(select
, key
, data
, visited
) {
61 var options
= data
.getValue(key
);
62 options
.forEach(function(optionData
) {
63 var option
= typeof optionData
== 'string' ?
64 new Option(optionData
) :
65 new Option(optionData
[1], optionData
[0]);
66 select
.appendChild(option
);
71 * This is used to set HTML attributes and DOM properties. The syntax is:
74 * .nested.dom.property:key
75 * @param {!HTMLElement} element The node to modify.
76 * @param {string} attributeAndKeys The path of the attribute to modify
77 * followed by a colon, and the name of the value in |data|.
78 * Multiple attribute/key pairs may be separated by semicolons.
79 * @param {!LoadTimeData} data The data source to draw from.
80 * @param {!Array<ProcessingRoot>} visited
82 'i18n-values': function(element
, attributeAndKeys
, data
, visited
) {
83 var parts
= attributeAndKeys
.replace(/\s/g, '').split(/;/);
84 parts
.forEach(function(part
) {
88 var attributeAndKeyPair
= part
.match(/^([^:]+):(.+)$/);
89 if (!attributeAndKeyPair
)
90 throw new Error('malformed i18n-values: ' + attributeAndKeys
);
92 var propName
= attributeAndKeyPair
[1];
93 var propExpr
= attributeAndKeyPair
[2];
95 var value
= data
.getValue(propExpr
);
97 // Allow a property of the form '.foo.bar' to assign a value into
99 if (propName
[0] == '.') {
100 var path
= propName
.slice(1).split('.');
101 var targetObject
= element
;
102 while (targetObject
&& path
.length
> 1) {
103 targetObject
= targetObject
[path
.shift()];
106 targetObject
[path
] = value
;
107 // In case we set innerHTML (ignoring others) we need to recursively
108 // check the content.
109 if (path
== 'innerHTML') {
110 for (var i
= 0; i
< element
.children
.length
; ++i
) {
111 processWithoutCycles(element
.children
[i
], data
, visited
, false);
116 element
.setAttribute(propName
, /** @type {string} */(value
));
124 // Only look through shadow DOM when it's supported. As of April 2015, iOS
125 // Chrome doesn't support shadow DOM.
126 if (Element
.prototype.createShadowRoot
)
127 prefixes
.push('* /deep/ ');
129 var attributeNames
= Object
.keys(handlers
);
130 var selector
= prefixes
.map(function(prefix
) {
131 return prefix
+ '[' + attributeNames
.join('], ' + prefix
+ '[') + ']';
135 * Processes a DOM tree using a |data| source to populate template values.
136 * @param {!ProcessingRoot} root The root of the DOM tree to process.
137 * @param {!LoadTimeData} data The data to draw from.
139 function process(root
, data
) {
140 processWithoutCycles(root
, data
, [], true);
144 * Internal process() method that stops cycles while processing.
145 * @param {!ProcessingRoot} root
146 * @param {!LoadTimeData} data
147 * @param {!Array<ProcessingRoot>} visited Already visited roots.
148 * @param {boolean} mark Whether nodes should be marked processed.
150 function processWithoutCycles(root
, data
, visited
, mark
) {
151 if (visited
.indexOf(root
) >= 0) {
152 // Found a cycle. Stop it.
156 // Mark the node as visited before recursing.
159 var importLinks
= root
.querySelectorAll('link[rel=import]');
160 for (var i
= 0; i
< importLinks
.length
; ++i
) {
161 var importLink
= /** @type {!HTMLLinkElement} */(importLinks
[i
]);
162 if (!importLink
.import) {
163 // Happens when a <link rel=import> is inside a <template>.
164 // TODO(dbeam): should we log an error if we detect that here?
167 processWithoutCycles(importLink
.import, data
, visited
, mark
);
170 var templates
= root
.querySelectorAll('template');
171 for (var i
= 0; i
< templates
.length
; ++i
) {
172 var template
= /** @type {HTMLTemplateElement} */(templates
[i
]);
173 if (!template
.content
)
175 processWithoutCycles(template
.content
, data
, visited
, mark
);
178 var isElement
= root
instanceof Element
;
179 if (isElement
&& root
.webkitMatchesSelector(selector
))
180 processElement(/** @type {!Element} */(root
), data
, visited
);
182 var elements
= root
.querySelectorAll(selector
);
183 for (var i
= 0; i
< elements
.length
; ++i
) {
184 processElement(elements
[i
], data
, visited
);
188 var processed
= isElement
? [root
] : root
.children
;
190 for (var i
= 0; i
< processed
.length
; ++i
) {
191 processed
[i
].setAttribute('i18n-processed', '');
198 * Run through various [i18n-*] attributes and populate.
199 * @param {!Element} element
200 * @param {!LoadTimeData} data
201 * @param {!Array<ProcessingRoot>} visited
203 function processElement(element
, data
, visited
) {
204 for (var i
= 0; i
< attributeNames
.length
; i
++) {
205 var name
= attributeNames
[i
];
206 var attribute
= element
.getAttribute(name
);
207 if (attribute
!= null)
208 handlers
[name
](element
, attribute
, data
, visited
);