class Extra
nodash/extra
. In browser context they
are part of the main _
/NoDash
object… Defined in: extra.js, line 45
Performs a remote request using XMLHttpRequest
, offering a subset of
jQuery’s ajax()
API.
Types | Notes |
---|---|
XMLHttpRequest | The xhr |
Name | Types | Notes |
---|---|---|
options | object |
Possible options
keys:
Name | Types | Notes | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
url | str | |||||||||||||
type | str | GET by default. | ||||||||||||
data | str | Request data for POST , etc.; useful object types
are mdn:API/FormData, mdn:API/Blob and
mdn:API/URLSearchParams (not in IE). | ||||||||||||
object | ||||||||||||||
dataType | str | Type of xhr.response , from standard
mdn:API/XMLHttpRequestResponseType; text by default; other useful
types are document (for HTML and XML), json , arraybuffer and
blob | ||||||||||||
context | object | Calling context for below callbacks. | ||||||||||||
beforeSend | function | Called before xhr.open() ; receives xhr and
options (mutable, affects internal copy, not given options ); if
returns === false then the request is not performed and error is
called without giving e (imitates abort() ). | ||||||||||||
success | function | Called when response has arrived; receives xhr and
e Warning: if | ||||||||||||
error | function | Called on a request or response error, and also on
beforeSend and xhr.abort() ; receives xhr (always) and e (only
if not on beforeSend ). | ||||||||||||
complete | function | Called after completion, successful or not;
receives xhr and e | ||||||||||||
progress | function | Called during response transmission; receives xhr
and e where useful e properties are:
| ||||||||||||
timeout | int milliseconds | If exceeded, request error -s with the
status of 0. | ||||||||||||
headers | object | Members can be strings or arrays; if missing, assumed
X-Requested-With: XMLHttpRequest (for compatibility with jQuery’s
ajax() ).
For CORS, custom headers like | ||||||||||||
username | str | For HTTP Basic Authentication. | ||||||||||||
password | str | For HTTP Basic Authentication. |
It is guaranteed that, per given ajax() call:
success or error
and one complete
is calledsuccess
is called on a 200-299 status
and responseType
matching
dataType
(the latter is browser-dependent and not very reliable)complete
is called after success or error
, even if the latter
has thrown an exception (it’s re-thrown after complete
provided it
didn’t throw another one)progress
is never called after calling success or error
ECMAScript equivalents: mdn:API/XMLHttpRequest, mdn:API/Fetch_API.
Defined in: extra.js, lines 144-227 (84 lines) • Show code
ajax: function (options) {
var o = NoDash.assign({}, {
url: location.href,
type: 'GET',
data: undefined,
dataType: 'text',
context: undefined,
beforeSend: new Function,
success: new Function,
error: new Function,
complete: new Function,
progress: new Function,
timeout: 0,
headers: {'X-Requested-With': 'XMLHttpRequest'},
username: undefined,
password: undefined,
}, options)
if (!o.headers['Content-Type'] && o.type != 'GET' && typeof o.data != 'object') {
o.headers['Content-Type'] = 'application/x-www-form-urlencoded'
}
var xhr = new XMLHttpRequest
var queue = []
function finish() {
// Delayed processing to let all other events (errors) pass through.
queue.length || NoDash.defer(function () {
// No pop() - queue.length must be non-0 to prevent late XHR events
// from re-triggering this.
var args = [xhr].concat(Array.prototype.slice.call(queue[queue.length - 1]))
var ok = xhr.status >= 200 && xhr.status < 300 &&
// This check isn't very reliable as at least Firefox leaves
// 'json' as is even if response is 'text/html'.
xhr.responseType == o.dataType
try {
NoDash.bind(ok ? o.success : o.error).apply(o.context, args)
} catch (e) {
var ex = e
}
NoDash.bind(o.complete).apply(o.context, args)
if (ex) { throw ex }
})
queue.push(arguments)
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
finish.apply(undefined, arguments)
}
}
// ontimeout is fired after onreadystatechange.
xhr.ontimeout = finish
xhr.upload.onprogress = function () {
if (!queue.length) {
var args = [xhr].concat(Array.prototype.slice.call(arguments))
NoDash.bind(o.progress).apply(o.context, args)
}
}
if (NoDash.bind(o.beforeSend).call(o.context, xhr, o) === false) {
NoDash.bind(o.error).call(o.context, xhr)
} else {
xhr.open(o.type, o.url, true, o.username, o.password)
xhr.timeout = o.timeout
xhr.responseType = o.dataType
NoDash.forEach(o.headers, function (value, name) {
NoDash.toArray(value).forEach(function (item) {
xhr.setRequestHeader(name, item)
})
})
xhr.send(o.data)
}
return xhr
},
Interpolates arg
’uments into str
’ing according to format
specifiers inspired by printf()
of C, or returns a function doing so.
Types | Notes |
---|---|
string formatted str | Result depends on options.return |
string function’s source code | |
function accepting arg ’uments |
Name | Types | Notes |
---|---|---|
options | object | |
omitted |
See also template().
Specifiers in str
start with %
. Special %%
is output as is while
others have this format (unsupported specifier triggers an error):
% [[+|-][n]$] [+[ ]] [[-|=][0|`c]d] [.[-][p]] (s|c|d|f|x|X|b|o|H|I|S|Y|M|D|K|T)
n - takes 1-based n'th arg; causes next $-less % to take n+1, etc.
+-n - ...relative: %+1$ = %+$ skip over next, %-2$ go back by two
+ - prefix positive number with '+' or ' '
d - number of symbols to pad to, using spaces on the left unless...
- - ...pads on the right
= - ...pads on both sides (centers)
0 - ...pads with zeros
c - ...pads with that symbol
p - %f: rounds to this number of decimal places (without .p = 6),
padding with zeros on the right if no '-'; %s %c: cuts if
longer, from the right if no '-'; '.' alone = defaultPrecision
%s - treats arg as a string
%c - treats arg as one or many (if array) String.fromCharCode()-s
%d - treats arg as an integer, discarding fractional part
%f - treats arg as a fractional number; mantissa is exempted from d
%x - treats arg as an integer, output in hexadecimal lower case
%X - as %x but in upper case
%b - as %x but in binary
%o - as %x but in octal
%H - treats arg as a Date instance, outputs local hours (1-24)
%I - as %H but outputs minutes (0-59)
%S - as %H but outputs seconds (0-59)
%Y - as %H but outputs full year (like 2022)
%M - as %H but outputs month number (1-12)
%D - as %H but outputs day number (1-31)
%K - as %H but outputs week day number (1-7, 1 for Monday)
Possible options
keys:
Name | Types | Notes | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
return | int | If 2 , format() returns the formatted
string; if 1 , returns a function that can be used to format the same
str using different set of arg ’uments; if 0 , returns its source
code. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
omitted = 2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
silent | bool | If set, doesn’t throw on insufficient number of
arg -s and other warnings. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
omitted | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ellipsis | str | Terminator used when
.p ’recision outputs a longer %s /%c string; use '' to cut without
one. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
null/omitted = '...' | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defaultPrecision | object | p value when no number follows
the dot; unset %s /%c default to 100. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
omitted | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
specifiers | object | Non-standard or overridden specifiers
(%... ); key is a single \w character, value is a function
receiving params, c :
Function must return a JavaScript code string (will be wrapped in
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
omitted |
These variables exist within the returned function:
Name | Types | Notes |
---|---|---|
_ | Reference to NoDash regardless of the global _ | |
c | Current formatter’s state (see above), shallow-copied for every new
call of the returned function (still sharing options and others). | |
a | Temporary variable for use by the specifier formatter while it
transforms the current arg |
For pretty formatting visible to user consider using standard Intl
classes, such as:
Defined in: extra.js, lines 893-1108 (216 lines) • Show code
format: function (options, str) {
function noPrecision(c, params) {
if (params[8]) {
c.e('%' + params[11] + ' is incompatible with precision (' + params[8] + ')')
}
}
if (!(options instanceof Object)) {
str = options
options = {}
}
var args = NoDash.rest(arguments, 1 + (options == arguments[0]))
options.defaultPrecision = NoDash.assign({
s: 100,
c: 100,
}, options.defaultPrecision)
options.specifiers = NoDash.assign({
s: function (params, c, res) {
res = res || 'c.next(' + params[2] + ')'
if (params[8]) {
res = 'c.ellipsize(' + res + ',' + (params[10] || params[7] || options.defaultPrecision[params[11]] || 0) + ',' + !!params[9] + ')'
}
return res
},
c: function (params, c) {
var res = 'a=c.next(' + params[2] + '),String.fromCharCode.apply(undefined,_.isArrayLike(a)?a:[a])'
return options.specifiers.s(params, c, res)
},
d: function (params, c) {
noPrecision(c, params)
return 'Math.trunc(c.next(' + params[2] + '))'
},
f: function (params) {
var p = params[8] ? +params[10] || options.defaultPrecision[params[11]] || 0 : 6
var m = NoDash.repeat('0', p)
var variableWidth = params[9] /*'-' p*/ || !p /*no '.' p or '.0'*/
// If number is below 0, its string form will be shorter than p:
// %.2f: 0.012 * 100 = '1', not '01' and we won't know the length of
// mantissa.
var res = 'Math.round((c.next(' + params[2] + ')' + (variableWidth ? '' : '+1') + ')' + (m && '*1' + m) + ')'
if (variableWidth) {
return 'a=' + res + (m && '/1' + m) + '+"",al-=a.length-Math.max(0,a.indexOf(".")),a'
} else {
return 'al-=' + (p + 1 /*dot*/) + ',a=' + res + '+"",a.substr(0,a.length-' + p + ')-1+"."+a.substr(-' + p + ')'
}
},
x: function (params, c, base) {
noPrecision(c, params)
return '(+c.next(' + params[2] + ')).toString(' + (base || 16) + ')'
},
X: function (params, c) {
return options.specifiers.x(params, c) + '.toUpperCase()'
},
b: function (params, c) {
return options.specifiers.x(params, c, 2)
},
o: function (params, c) {
return options.specifiers.x(params, c, 8)
},
H: function (params, c, func) {
noPrecision(c, params)
return 'c.next(' + params[2] + ').' + (func || 'getHours') + '()'
},
I: function (params, c) {
return options.specifiers.H(params, c, 'getMinutes')
},
S: function (params, c) {
return options.specifiers.H(params, c, 'getSeconds')
},
Y: function (params, c) {
return options.specifiers.H(params, c, 'getFullYear')
},
M: function (params, c) {
return options.specifiers.H(params, c, 'getMonth') + '+1'
},
D: function (params, c) {
return options.specifiers.H(params, c, 'getDate')
},
K: function (params, c) {
return options.specifiers.H(params, c, 'getDay')
},
}, options.specifiers)
var c = {
//! +ig
//args: null, // format-time
//arg: 0, // format-time
head: 'var a,al,c=_.assign({arg:0,args:_.rest(arguments,2)},_c);',
options: options,
e: function (msg) {
if (!this.options.silent) {
throw new Error('format: ' + msg + '.')
}
return true
},
//! +ig
next: function (param) { // [+-][n]
if (param) {
isNaN(param[0]) ? this.arg += +param : this.arg = param - 1
}
if (++this.arg > this.args.length) {
this.e('too few arguments')
return ''
} else {
return this.args[this.arg - 1]
}
},
ellipsize: function (s, max, left) {
s += ''
if (s.length > max) {
var ell = options.ellipsis == null ? '...' : options.ellipsis
s = max <= ell.length
? left ? s.substr(-max) : s.substr(0, max)
: left
? ell + s.substr(-(max - ell.length))
: s.substr(0, max - ell.length) + ell
}
return s
},
}
// Since specifier format looks the same when stringify()'ed, can call the
// latter once on the entire string rather than on every parts.shift().
var parts = JSON.stringify(str)
// Note: update doc comment above and splice() below if changing ( )s.
.split(/%(%|(([+-]\d*|\d+)\$)?(\+ ?)?(([-=])?(0|`.)?(\d+))?(\.(-)?(\d*))?(\w))/)
// 0 12 3 45 6 7 8 9 10 11
var code = ''
while (true) {
code += parts.shift() // A%sB%dC = [A] [%s] [B] [%d] [C]
if (!parts.length) { break } // ^shift ^shift ^shift
// 0 part after initial '%': '%' or full specifier
// 1 n + '$'
// 2 [+|-] [n]
// 3 '+' or '+ '
// 4 [[-|=][0|`c]d]
// 5 '-' or '='
// 6 '0' or '`' + c
// 7 d
// 8 [.[-][p]]
// 9 '-'
// 10 p
// 11 specifier symbol
var params = parts.splice(0, 12) // 12 = number of ( )s above
if (params[0] == '%') {
code += '%'
continue
}
var func = options.specifiers[params[11]]
if (!func) {
c.e('unsupported specifier %' + params[11] + ': %' + params[0])
continue
}
if (params[2] == '+' || params[2] == '-') { params[2] += '1' }
params[2] = params[2] ? '"' + params[2] + '"' : ''
code += '"+(al=0,a=(' + func(params, c) + '),'
if (params[3]) {
code += '(a>0?"' + params[3].substr(-1) + '":"")+'
}
if (!params[4]) {
code += 'a'
} else {
var n = params[7]
var p = JSON.stringify(NoDash.repeat((params[6] || ' ').substr(-1), +n))
if (params[5] == '=') {
code += '(a+="",a=' + p + '.substr(0,(' + n + '-a.length+al)/2)+a)+' + p + '.substr(a.length)'
} else {
p += '.substr(a.length+al)'
code += '(a+="",' + (params[5] ? 'a+' + p : p + '+a') + ')'
}
}
code += ')+"'
}
code = c.head + 'a=' + code + ';c.arg==c.args.length||c.e("too many arguments");return a'
if (options.return != 0) {
code = (new Function('_c,_', code)).bind(undefined, c, NoDash)
if (options.return != 1) {
return code.apply(undefined, args)
}
}
if (args.length) {
c.e("have both format arg-s and options.return < 2")
}
return code
},
Converts a template str
to a function that accepts variables and
returns a formatted string.
Types | Notes |
---|---|
function accepting vars | |
string if options.source |
Name | Types | Notes |
---|---|---|
str | string | The template. |
function take the contents of the first /**/ comment (make sure it’s not minified away) | ||
Node take textContent | ||
options | object | Compilation options. |
omitted |
template() can be used outside of web browser environment.
See also format().
Possible options
keys:
Name | Types | Notes | |||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prepare | omitted | Function receives caller-given vars
({} if not given) and options under o (may be undefined ) and
returns an object with complete variables and options (may be mutated). | |||||||||||||||||||||||||||||||||||||||||||||||||||
object defaults for formatting variables and/or
options (under o ) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
function | |||||||||||||||||||||||||||||||||||||||||||||||||||||
source | bool | If set, returns a JavaScript string – code of the function to be compiled. | |||||||||||||||||||||||||||||||||||||||||||||||||||
omitted | |||||||||||||||||||||||||||||||||||||||||||||||||||||
with | bool | If set, members of vars can be
referenced directly, not through v ; such templates are slower due to
using JavaScript’s with { } | |||||||||||||||||||||||||||||||||||||||||||||||||||
omitted = true | |||||||||||||||||||||||||||||||||||||||||||||||||||||
laxRef | bool | If set, non-code ref are resolved
with at(), meaning they return the non-object value immediately, even
if there are more path components (that triggers an error without
laxRef , but is faster). | |||||||||||||||||||||||||||||||||||||||||||||||||||
omitted = true | |||||||||||||||||||||||||||||||||||||||||||||||||||||
code | bool | If unset, fails to compile if there are
constructs allowing arbitrary code execution; in such case it should be
safe to pass str from untrusted input since it can only read values
given to the compiled function and from global window (unless there
are custom blocks ). | |||||||||||||||||||||||||||||||||||||||||||||||||||
omitted = true | |||||||||||||||||||||||||||||||||||||||||||||||||||||
backslash | bool | If set, preprocesses str by
removing backslashes placed before a line break together with all the
following whitespace and line breaks; useful for splitting long lines
that can’t normally be split, wrapping {{ }} or getting rid of
unwanted spaces inside <u> | |||||||||||||||||||||||||||||||||||||||||||||||||||
omitted = false | |||||||||||||||||||||||||||||||||||||||||||||||||||||
blocks | object | New custom or
overridden standard if /for /etc.; key is the block’s name
(alphanumeric), value is a function receiving param, value, c :
Function must return an object with keys:
Code snippets can use context variables like | |||||||||||||||||||||||||||||||||||||||||||||||||||
omitted = default if /for /etc. |
Template syntax loosely resembles that of Mustache.js/Handlebars.js – all
substitutions are performed within {{...}}
:
escaped = \[\\...]{{
echo = {{ [=] ref }}
conditional = {{ [else]if[:not] ref }}
| {{ else[:] }}
loop = {{ for[:[*][pf]] ref }}
| {{ elseif[:pf|-[:not]] ref }}
| {{ else[:pf|-] }}
block_end = {{ / [if|for] }}
custom = {{ key[:[param]][ value] }}
| {{ / [key] }}
ref = var[.prop...] | code
escaped
: a pair of {
prefixed with odd number of \
becomes raw
{{
prefixed with half that number of \
; even number of \
doesn’t
escape {{
but is still emitted “halved”: \{{ = {{ \\\{{ = \{ \\\\\{{ = \\{{
\\{{...}} = \ + result \\\\{{...}} = \\ + result
echo
: emits value of a variable/property (not of global like window
)
or result of executing arbitrary code (if ref
is not alphanumeric with
periods). Without =
the result is post-processed by v.o.escaper
(e.g. HTML-escaped). If ref
is null
or undefined
, emits nothing.conditional
: emits the enclosed block only if ref
is truthy (or
falsy with :not
).loop
: emits the enclosed block once for every iteration. ref
is
given to forEach(), with forceObject if asterisk (*
) is present.
Optional pf
specifies prefix for variables inside the block that are
by default m
(“m”ember’s value), k
(“k”ey), i
(“i”ndex, same as
k
if ref
isArrayLike), a
(“a”ll, the ref
). In object mode,
iterations are seamlessly ordered by comparing keys as strings
(for:array
iterates in different order than for:*array
:
2 < 10
but '2' > '10'
).Only i
exists outside of the block and is undefined
prior to the
first loop
with that pf
; after a loop
it holds index of the last
iterated member, or -1
if ref
was empty (or
undefined
/null
/false
) – this is what loop
’s enclosed
elseif
/else
test (they also change for
’s block_end
from /for
to /if
). Without :
or with :-
, last for
(its pf
) is checked:
{{elseif}} {{elseif:-}} {{elseif:-:not}}
– but here last for
with
empty pf
is checked: {{elseif:}} {{elseif::not}}
.
block_end
: give the expected block’s type (not simply {{/}}
) for
extra syntax safety: {{if ...}} {{for ...}} {{/}} {{/}} - works
{{if ...}} {{for ...}} {{/for}} {{/if}} - works
{{if ...}} {{for ...}} {{/if}} {{/for}} - fails to compile
{{
}}
are not supported but you can use string
backslash
or escapes: {{'}}\n'}}
fails but {{'\\x7d\\n}'}}
works.The returned compiled template function accepts these arguments:
Name | Types | Notes | |||||||
---|---|---|---|---|---|---|---|---|---|
v | object | Variables for access by ref ; the o key
contains formatting options; standard o subkeys:
| |||||||
falsy = {} |
These variables exist within the returned function:
Name | Types | Notes |
---|---|---|
_ | Reference to NoDash regardless of the global _ | |
v | Variables given by the caller (v.o = options, or {} ). | |
_x | The c.extra object (see the blocks option). | |
* | Members of v , if options.with is set. |
Other examples where backslash
is useful:
{{if foo... && \ becomes {{if foo... && bar... }}
bar... \ without spaces before \ would be:
}} {{if foo...&& bar...}}
<u ...>\ becomes <u ...>text</u> rather than
text\ <u ...> text </u>, removing underline of the
</u> whitespace after "text" when viewed in browser
Defined in: extra.js, lines 580-762 (183 lines) • Show code
template: function (str, options) {
options = NoDash.assign({with: true, laxRef: true, code: true}, options)
options.blocks = NoDash.assign({
if: function (param, value, c, ref) {
if (param && param != 'not') {
throw new Error('template: bad "if:' + param + '".')
}
return {start: '(' + (param ? '!' : '') + (ref || c.ref)(value) + '?""',
end: '"":"")'}
},
elseif: function (param, value, c, ref) {
var prev = c.stack[0] && !c.stack[0]._else && c.stack[0].type
if (prev == 'for') {
param = (param == null ? '-' : param).match(/^(\w*|-)(:(.*))?$/)
if (param[1] == '-') { param[1] = c._lastFor }
var res = options.blocks.if(param[3], value, c, function (s) {
return '(' + param[1] + 'i==-1&&' + (ref || c.ref)(s) + ')'
})
return NoDash.assign(res, {start: c.stack.shift().end + '+' + res.start,
type: 'if', _for: param[1]})
} else if (prev != 'if') {
throw new Error('template: elseif: no preceding if or for.')
} else {
if (c.stack[0]._for != null) {
arguments[3] = function (s) {
return '(' + c.stack[0]._for + 'i==-1&&' + (ref || c.ref)(s) + ')'
}
arguments.length++
}
var res = options.blocks.if.apply(this, arguments)
c.stack[0].end += ')'
return {start: '"":' + res.start}
}
},
else: function (param, value, c) {
if ((param && ((c.stack[0] || {}).type != 'for')) || value) {
throw new Error('template: else takes no arguments.')
} else {
var res = options.blocks.elseif(param, '', c, function () { return 1 })
c.stack[0]._else = true // may have been shift()'ed
return res
}
},
for: function (pf, value, c) {
var match = (pf || '').match(/^(\*)?(\w*)()$/)
if (!match) {
throw new Error('template: bad "for:' + pf + '".')
}
pf = c._lastFor = match[2]
return {
head: 'var ' + pf + 'i;',
start: '(' + pf + 'i=-1,' +
'_x.for(' + c.ref(value) + ',' + !!match[1] + ',' +
'function(' + pf + 'm,' + pf + 'k,' + pf + 'a){' +
pf + 'i++;return""',
end: '""}))',
}
},
}, options.blocks)
var c = {
options: options,
stack: [],
_lastFor: null,
extra: {
for: function (value, forceObject, func) {
if (value == null || value === false) {
return ''
} else if (forceObject || !NoDash.isArrayLike(value)) {
return NoDash.entries(value)
.sort(function (a, b) {
// Keys may be numbers if forceObject && isArrayLike.
return (a[0] += '') > (b[0] += '') ? +1 : -1
})
.map(function (entry) { return func(entry[1], entry[0], value) })
.join('')
} else {
return NoDash.map(value, func).join('')
}
},
},
ref: function (s) {
if (!(s = s.trim())) { throw new Error('template: blank ref.') }
var m
if (m = s.match(/^(\w+)((\.\w+)*)$/)) {
s = '["' + m[2].substr(1).replace(/\./g,
(options.laxRef ? '","' : '"]["')) + '"]'
if (options.laxRef) {
s = '(typeof ' + m[1] + '=="undefined"?undefined:' +
(m[2] ? '_.at(' + m[1] + ',' + s + ')' : m[1]) + ')'
} else {
s = m[1] + (m[2] ? s : '')
}
} else if (!options.code) {
throw new Error('template: code refs prohibited.')
} else {
s = '(' + s + ')'
}
return s
},
}
var head = ''
var blocks = NoDash.keys(options.blocks).join('|')
var blockStart = new RegExp('^(' + blocks + ')(:\\S*)?(\\s+(.*))?$')
var blockEnd = new RegExp('^\\/\\s*(' + blocks + ')?\\s*$')
if (str instanceof Function) {
// RegExp /s flag (dotAll) is not supported in IE and older FF.
str = str.toString().match(/\/\*([\s\S]*)\*\//)[1].trim()
} else if (NoDash.isElement(str)) {
str = str.textContent
}
if (options.backslash) {
str = str.replace(/\\[\r\n]\s*/g, '')
}
str = str.replace(/(\\(\\\\)*)\{\{|((?:\\\\)*)\{\{\s*(.*?)\}\}|(["\\\0-\x1F])/g, function () {
var m = arguments
if (m[1]) {
var res = m[0].substr(1)
} else if (m[5]) { // \ or " or non-printable
var code = m[0].charCodeAt(0)
var res = '\\x' + (code < 16 ? '0' : '') + code.toString(16)
} else {
var res = m[3] + '"+'
var inside = m[4]
if (m = inside.match(blockStart)) {
var block = options.blocks[m[1]](m[2] ? m[2].substr(1) : null,
m[3] ? m[4] : null, c)
head += block.head || ''
res += block.start || ''
block.type = block.type || m[1]
block.end && c.stack.unshift(block)
} else if (m = inside.match(blockEnd)) {
if (!c.stack.length || (m[1] && c.stack[0].type != m[1])) {
throw new Error('template: /' + c.stack[0].type + ' expected, {{' + m[0] + '}} found.')
}
res += c.stack.shift().end
} else {
res += '((T=' + c.ref(inside.substr(inside[0] == '=')) + ')==null?"":' +
(inside[0] == '=' ? 'T' : 'E(T)') + ')'
}
res += '+"'
}
return res
})
if (c.stack.length) {
str = NoDash.pluck(c.stack, 'type').join('}} <- {{')
throw new Error('template: unclosed {{' + str + '}}.')
}
str = head + 'return"' + str + '"'
// str (head) may contain "var".
if (options.with) { str = 'with(v){' + str + '}' }
if (!options.source) {
var def = options.prepare
if (def && (typeof def != 'function')) {
options.prepare = function (v) {
return NoDash.assign({}, def, v, {o: NoDash.assign({}, def.o, v.o)})
}
}
// It appears that strict mode isn't applied to such functions even
// though it applies to eval, neither when they're created nor called.
// But unlike eval they have access to the global scope only.
str = 'v=v||{};v=_p?_p(v):v;v.o=v.o||{};' +
'var T,E=v.o.escaper||function(s){return s};' + str
str = (new Function('_p,_,_x,v', str))
.bind(undefined, options.prepare, NoDash, c.extra)
}
return str
},