This repository was archived by the owner on Aug 31, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathtmpl.js
298 lines (239 loc) · 9.43 KB
/
tmpl.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/*
* tmpljs
* A DOM element based templating engine with
* a logic-less Zen Coding-like markup, object caching, partials and variables
*
* https://github.com/mastermatt/tmpljs
*
* Requires jQuery 1.4+
*/
(function($) {
"use strict";
var version = "0.12.1";
// set the `value` property instead of `innerHTML` for these tags
var setValuesFor = ["input", "textarea"];
// Regex to break the main string into parts
// example " myFunc(p, 6).world[placeholder=some text] {myVar} yeah"
// matches
// 1: leading spaces " "
// 2: tag name or partial signature "myFunc(p, 6)"
// 3: partial name "myFunc"
// 4: partial args "p, 6"
// 5: everything else ".world[placeholder=some text] {myVar} yeah"
var regLine = /^(\s*)(([\w.-]*)\((.*)\)|[\w-]*)(.*)$/;
// Regex for explicitly stated attributes ( the stuff in square brackets )
// matches
// 1: attribute name "placeholder"
// 2: value "some text"
var regAttributes = /\[([\w-]+)=?([^\]]*)\]/g;
// Regex for the modifiers ( class, id, cache )
// matches
// 1: type flag ".", "#", "$"
// 2: value from the example above "world"
var regModifiers = /([.#$])([\w-]+)/g;
// Regex for the handlebar type variable syntax in text matches
// 1: a bang for escaping literal brackets
// 2: variable key
var regVariables = /\{([!&]?)\s*([^\s\}]*)\s*\}/g;
// escape HTML entities
// https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet (rule #1)
// loving borrowed from handlebars.js and mustache.js
var regEscapeCharacters = /[&<>"'`\/]/g;
var escapeCharacterLookup = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`", // IE can quote attributes with ticks too
"/": '/'
};
var escapeCharacters = function(character) {
return escapeCharacterLookup[character];
};
// Turn dot notation in a string into object reference
// example dotToRef("a.b.c", {a:{b:{c:42}}}) will return 42
var dotToRef = function(notation, object) {
var segments = notation.split(".");
var i = 0;
var len = segments.length;
for (; i < len;) {
object = object[segments[i++]];
}
return object;
};
// scratch vars
var lastEl;
var parentEl;
var indexOfSpace;
var textVal;
var modVal;
// The actual plugin function
var tmpl = function(template, data, partials) {
if (!$.isArray(template)) {
throw "template must be array";
}
data = data || {};
partials = partials || data;
var ret = $();
var templateIndex = 0;
var templateLength = template.length;
var lastDepth = 0;
var objCache = {};
// Replace variables in strings
var varReplacer = function(match, flag, key) {
// when a bang is supplied after the opening bracket, the brackets are treated as
// literal and the string is returned like it was (minus the bang).
if ("!" === flag) {
return "{" + match.slice(2)
}
var val = dotToRef(key, data);
if ($.isFunction(val)) {
val = val.call(data);
}
if (!val && 0 !== val) {
val = "";
}
// ignore the default behavior of escaping HTML entities when an
// ampersand flag is provided.
if ("&" === flag) {
return val;
}
return val.replace(regEscapeCharacters, escapeCharacters);
};
while (templateIndex < templateLength) {
var matches = regLine.exec(template[templateIndex++]);
var tagName = matches[2];
var partialName = matches[3];
var postTag = matches[5];
var el = false;
var $el = false;
var classes = [];
// The amount of white space that starts the string
// defines its depth in the DOM tree
// Four spaces to a level, add one to compensate for
// the quote character then floor the value
// examples
// "tag" : 0 spaces = 0
// " tag" : 3 spaces = 1
// " tag" : 7 spaces = 2
var depth = ((matches[1].length + 1) / 4) | 0;
// Make sure there is at least a tag, partial or postTag declared
// basically, skip empty lines
if (!tagName && !postTag) {
continue;
}
if (partialName) {
// call the partial function with clean arguments
el = dotToRef(partialName, partials)
.apply(partials, $.map(matches[4].split(","), $.trim));
// allow partials to return falsy values and have this line in the template skipped
// useful for partials that implement conditional logic
if (!el) {
continue;
}
// If a jQuery object is returned with multiple items,
// the whole object can be cached, but only the first
// item is used in the object that is returned from this plugin
if (el instanceof $) {
$el = el;
el = el[0];
}
}
// Ensure we have a proper ELEMENT_NODE in our el variable
if (!el || el.nodeType !== 1) {
// Create the element, default to div if not declared
el = document.createElement(tagName || "div");
}
if (depth && parentEl) {
if (depth > lastDepth) { // nest in last element
parentEl = lastEl;
}
while (depth < lastDepth--) { // traverse up
parentEl = parentEl.parentNode;
}
parentEl.appendChild(el);
} else {
ret.push(parentEl = el);
}
lastDepth = depth;
lastEl = el;
// Don't bother with the rest if there's no mods or text
if (!postTag) {
continue;
}
// Search for attributes
// Attach them to the element and remove the characters
// from the postTag string, this allows us to have spaces in the attr values
//
// input[placeholder=Hello World] - <input placeholder="Hello World" />
// input[disabled] - <input disabled />
postTag = postTag.replace(regAttributes, function(match, attr, val) {
el.setAttribute(attr, val || "");
return "";
});
// Look for text content after the mods via a space character
indexOfSpace = postTag.indexOf(" ");
if (indexOfSpace !== -1) {
// Strip everything after the first space to use it as the text
// value and run it through the replace func to replace variables
textVal = postTag.substr(indexOfSpace + 1)
.replace(regVariables, varReplacer);
// Remove the text from the postTag so that only mods remain
postTag = postTag.substr(0, indexOfSpace);
// Set the value for the tags we want to,
// otherwise set innerHTML
if ($.inArray(el.tagName.toLowerCase(), setValuesFor) < 0) {
el.innerHTML = textVal;
} else {
el.value = textVal;
}
}
// Loop the mods
while ((matches = regModifiers.exec(postTag))) {
modVal = matches[2];
switch (matches[1]) {
case ".": // Add class
classes.push(modVal);
break;
case "#": // Set id
el.id = modVal;
break;
case "$": // cache jQueryized element for later
objCache[modVal] = $el || $(el);
}
}
// Add any classes a partial may have added to the classes list
if (el.className) {
classes.push(el.className);
}
// Attach all the classes at once
if (classes.length) {
el.className = classes.join(" ");
}
}
// Alias the object cache as "cache" and "c"
ret.c = ret.cache = objCache;
return ret;
};
tmpl.version = version;
// Add as a jQuery plugin
$.tmpl = tmpl;
$.fn.tmpl = function(template, data, partials) {
var self = this;
var cache = self.c = self.cache = self.c || {};
var compiled;
var key;
return self.each(function() {
compiled = tmpl(template, data, partials);
$(this).append(compiled);
for (key in compiled.c) {
if (cache[key]) {
cache[key].push(compiled.c[key][0]);
} else {
cache[key] = compiled.c[key];
}
}
});
};
})(jQuery);