Add a new function L.error() which creates and throws a custom error object with stack information and given type. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
873 lines
22 KiB
JavaScript
873 lines
22 KiB
JavaScript
(function(window, document, undefined) {
|
|
/* Object.assign polyfill for IE */
|
|
if (typeof Object.assign !== 'function') {
|
|
Object.defineProperty(Object, 'assign', {
|
|
value: function assign(target, varArgs) {
|
|
if (target == null)
|
|
throw new TypeError('Cannot convert undefined or null to object');
|
|
|
|
var to = Object(target);
|
|
|
|
for (var index = 1; index < arguments.length; index++)
|
|
if (arguments[index] != null)
|
|
for (var nextKey in arguments[index])
|
|
if (Object.prototype.hasOwnProperty.call(arguments[index], nextKey))
|
|
to[nextKey] = arguments[index][nextKey];
|
|
|
|
return to;
|
|
},
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
/*
|
|
* Class declaration and inheritance helper
|
|
*/
|
|
|
|
var toCamelCase = function(s) {
|
|
return s.replace(/(?:^|[\. -])(.)/g, function(m0, m1) { return m1.toUpperCase() });
|
|
};
|
|
|
|
var superContext = null, Class = Object.assign(function() {}, {
|
|
extend: function(properties) {
|
|
var props = {
|
|
__base__: { value: this.prototype },
|
|
__name__: { value: properties.__name__ || 'anonymous' }
|
|
};
|
|
|
|
var ClassConstructor = function() {
|
|
if (!(this instanceof ClassConstructor))
|
|
throw new TypeError('Constructor must not be called without "new"');
|
|
|
|
if (Object.getPrototypeOf(this).hasOwnProperty('__init__')) {
|
|
if (typeof(this.__init__) != 'function')
|
|
throw new TypeError('Class __init__ member is not a function');
|
|
|
|
this.__init__.apply(this, arguments)
|
|
}
|
|
else {
|
|
this.super('__init__', arguments);
|
|
}
|
|
};
|
|
|
|
for (var key in properties)
|
|
if (!props[key] && properties.hasOwnProperty(key))
|
|
props[key] = { value: properties[key], writable: true };
|
|
|
|
ClassConstructor.prototype = Object.create(this.prototype, props);
|
|
ClassConstructor.prototype.constructor = ClassConstructor;
|
|
Object.assign(ClassConstructor, this);
|
|
ClassConstructor.displayName = toCamelCase(props.__name__.value + 'Class');
|
|
|
|
return ClassConstructor;
|
|
},
|
|
|
|
singleton: function(properties /*, ... */) {
|
|
return Class.extend(properties)
|
|
.instantiate(Class.prototype.varargs(arguments, 1));
|
|
},
|
|
|
|
instantiate: function(args) {
|
|
return new (Function.prototype.bind.apply(this,
|
|
Class.prototype.varargs(args, 0, null)))();
|
|
},
|
|
|
|
call: function(self, method) {
|
|
if (typeof(this.prototype[method]) != 'function')
|
|
throw new ReferenceError(method + ' is not defined in class');
|
|
|
|
return this.prototype[method].apply(self, self.varargs(arguments, 1));
|
|
},
|
|
|
|
isSubclass: function(_class) {
|
|
return (_class != null &&
|
|
typeof(_class) == 'function' &&
|
|
_class.prototype instanceof this);
|
|
},
|
|
|
|
prototype: {
|
|
varargs: function(args, offset /*, ... */) {
|
|
return Array.prototype.slice.call(arguments, 2)
|
|
.concat(Array.prototype.slice.call(args, offset));
|
|
},
|
|
|
|
super: function(key, callArgs) {
|
|
for (superContext = Object.getPrototypeOf(superContext ||
|
|
Object.getPrototypeOf(this));
|
|
superContext && !superContext.hasOwnProperty(key);
|
|
superContext = Object.getPrototypeOf(superContext)) { }
|
|
|
|
if (!superContext)
|
|
return null;
|
|
|
|
var res = superContext[key];
|
|
|
|
if (arguments.length > 1) {
|
|
if (typeof(res) != 'function')
|
|
throw new ReferenceError(key + ' is not a function in base class');
|
|
|
|
if (typeof(callArgs) != 'object')
|
|
callArgs = this.varargs(arguments, 1);
|
|
|
|
res = res.apply(this, callArgs);
|
|
}
|
|
|
|
superContext = null;
|
|
|
|
return res;
|
|
},
|
|
|
|
toString: function() {
|
|
var s = '[' + this.constructor.displayName + ']', f = true;
|
|
for (var k in this) {
|
|
if (this.hasOwnProperty(k)) {
|
|
s += (f ? ' {\n' : '') + ' ' + k + ': ' + typeof(this[k]) + '\n';
|
|
f = false;
|
|
}
|
|
}
|
|
return s + (f ? '' : '}');
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
/*
|
|
* HTTP Request helper
|
|
*/
|
|
|
|
Headers = Class.extend({
|
|
__name__: 'LuCI.XHR.Headers',
|
|
__init__: function(xhr) {
|
|
var hdrs = this.headers = {};
|
|
xhr.getAllResponseHeaders().split(/\r\n/).forEach(function(line) {
|
|
var m = /^([^:]+):(.*)$/.exec(line);
|
|
if (m != null)
|
|
hdrs[m[1].trim().toLowerCase()] = m[2].trim();
|
|
});
|
|
},
|
|
|
|
has: function(name) {
|
|
return this.headers.hasOwnProperty(String(name).toLowerCase());
|
|
},
|
|
|
|
get: function(name) {
|
|
var key = String(name).toLowerCase();
|
|
return this.headers.hasOwnProperty(key) ? this.headers[key] : null;
|
|
}
|
|
});
|
|
|
|
Response = Class.extend({
|
|
__name__: 'LuCI.XHR.Response',
|
|
__init__: function(xhr, url, duration) {
|
|
this.ok = (xhr.status >= 200 && xhr.status <= 299);
|
|
this.status = xhr.status;
|
|
this.statusText = xhr.statusText;
|
|
this.responseText = xhr.responseText;
|
|
this.headers = new Headers(xhr);
|
|
this.duration = duration;
|
|
this.url = url;
|
|
this.xhr = xhr;
|
|
},
|
|
|
|
json: function() {
|
|
return JSON.parse(this.responseText);
|
|
},
|
|
|
|
text: function() {
|
|
return this.responseText;
|
|
}
|
|
});
|
|
|
|
Request = Class.singleton({
|
|
__name__: 'LuCI.Request',
|
|
|
|
interceptors: [],
|
|
|
|
request: function(target, options) {
|
|
var state = { xhr: new XMLHttpRequest(), url: target, start: Date.now() },
|
|
opt = Object.assign({}, options, state),
|
|
content = null,
|
|
contenttype = null,
|
|
callback = this.handleReadyStateChange;
|
|
|
|
return new Promise(function(resolveFn, rejectFn) {
|
|
opt.xhr.onreadystatechange = callback.bind(opt, resolveFn, rejectFn);
|
|
opt.method = String(opt.method || 'GET').toUpperCase();
|
|
|
|
if ('query' in opt) {
|
|
var q = (opt.query != null) ? Object.keys(opt.query).map(function(k) {
|
|
if (opt.query[k] != null) {
|
|
var v = (typeof(opt.query[k]) == 'object')
|
|
? JSON.stringify(opt.query[k])
|
|
: String(opt.query[k]);
|
|
|
|
return '%s=%s'.format(encodeURIComponent(k), encodeURIComponent(v));
|
|
}
|
|
else {
|
|
return encodeURIComponent(k);
|
|
}
|
|
}).join('&') : '';
|
|
|
|
if (q !== '') {
|
|
switch (opt.method) {
|
|
case 'GET':
|
|
case 'HEAD':
|
|
case 'OPTIONS':
|
|
opt.url += ((/\?/).test(opt.url) ? '&' : '?') + q;
|
|
break;
|
|
|
|
default:
|
|
if (content == null) {
|
|
content = q;
|
|
contenttype = 'application/x-www-form-urlencoded';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!opt.cache)
|
|
opt.url += ((/\?/).test(opt.url) ? '&' : '?') + (new Date()).getTime();
|
|
|
|
if (!/^(?:[^/]+:)?\/\//.test(opt.url))
|
|
opt.url = location.protocol + '//' + location.host + opt.url;
|
|
|
|
if ('username' in opt && 'password' in opt)
|
|
opt.xhr.open(opt.method, opt.url, true, opt.username, opt.password);
|
|
else
|
|
opt.xhr.open(opt.method, opt.url, true);
|
|
|
|
opt.xhr.responseType = 'text';
|
|
opt.xhr.overrideMimeType('application/octet-stream');
|
|
|
|
if ('timeout' in opt)
|
|
opt.xhr.timeout = +opt.timeout;
|
|
|
|
if ('credentials' in opt)
|
|
opt.xhr.withCredentials = !!opt.credentials;
|
|
|
|
if (opt.content != null) {
|
|
switch (typeof(opt.content)) {
|
|
case 'function':
|
|
content = opt.content(xhr);
|
|
break;
|
|
|
|
case 'object':
|
|
content = JSON.stringify(opt.content);
|
|
contenttype = 'application/json';
|
|
break;
|
|
|
|
default:
|
|
content = String(opt.content);
|
|
}
|
|
}
|
|
|
|
if ('headers' in opt)
|
|
for (var header in opt.headers)
|
|
if (opt.headers.hasOwnProperty(header)) {
|
|
if (header.toLowerCase() != 'content-type')
|
|
opt.xhr.setRequestHeader(header, opt.headers[header]);
|
|
else
|
|
contenttype = opt.headers[header];
|
|
}
|
|
|
|
if (contenttype != null)
|
|
opt.xhr.setRequestHeader('Content-Type', contenttype);
|
|
|
|
try {
|
|
opt.xhr.send(content);
|
|
}
|
|
catch (e) {
|
|
rejectFn.call(opt, e);
|
|
}
|
|
});
|
|
},
|
|
|
|
handleReadyStateChange: function(resolveFn, rejectFn, ev) {
|
|
var xhr = this.xhr;
|
|
|
|
if (xhr.readyState !== 4)
|
|
return;
|
|
|
|
if (xhr.status === 0 && xhr.statusText === '') {
|
|
rejectFn.call(this, new Error('XHR request aborted by browser'));
|
|
}
|
|
else {
|
|
var response = new Response(
|
|
xhr, xhr.responseURL || this.url, Date.now() - this.start);
|
|
|
|
Promise.all(Request.interceptors.map(function(fn) { return fn(response) }))
|
|
.then(resolveFn.bind(this, response))
|
|
.catch(rejectFn.bind(this));
|
|
}
|
|
|
|
try {
|
|
xhr.abort();
|
|
}
|
|
catch(e) {}
|
|
},
|
|
|
|
get: function(url, options) {
|
|
return this.request(url, Object.assign({ method: 'GET' }, options));
|
|
},
|
|
|
|
post: function(url, data, options) {
|
|
return this.request(url, Object.assign({ method: 'POST', content: data }, options));
|
|
},
|
|
|
|
addInterceptor: function(interceptorFn) {
|
|
if (typeof(interceptorFn) == 'function')
|
|
this.interceptors.push(interceptorFn);
|
|
return interceptorFn;
|
|
},
|
|
|
|
removeInterceptor: function(interceptorFn) {
|
|
var oldlen = this.interceptors.length, i = oldlen;
|
|
while (i--)
|
|
if (this.interceptors[i] === interceptorFn)
|
|
this.interceptors.splice(i, 1);
|
|
return (this.interceptors.length < oldlen);
|
|
},
|
|
|
|
poll: Class.singleton({
|
|
__name__: 'LuCI.Request.Poll',
|
|
|
|
queue: [],
|
|
|
|
add: function(interval, url, options, callback) {
|
|
if (isNaN(interval) || interval <= 0)
|
|
throw new TypeError('Invalid poll interval');
|
|
|
|
var e = {
|
|
interval: interval,
|
|
url: url,
|
|
options: options,
|
|
callback: callback
|
|
};
|
|
|
|
this.queue.push(e);
|
|
return e;
|
|
},
|
|
|
|
remove: function(entry) {
|
|
var oldlen = this.queue.length, i = oldlen;
|
|
|
|
while (i--)
|
|
if (this.queue[i] === entry) {
|
|
delete this.queue[i].running;
|
|
this.queue.splice(i, 1);
|
|
}
|
|
|
|
if (!this.queue.length)
|
|
this.stop();
|
|
|
|
return (this.queue.length < oldlen);
|
|
},
|
|
|
|
start: function() {
|
|
if (!this.queue.length || this.active())
|
|
return false;
|
|
|
|
this.tick = 0;
|
|
this.timer = window.setInterval(this.step, 1000);
|
|
this.step();
|
|
document.dispatchEvent(new CustomEvent('poll-start'));
|
|
return true;
|
|
},
|
|
|
|
stop: function() {
|
|
if (!this.active())
|
|
return false;
|
|
|
|
document.dispatchEvent(new CustomEvent('poll-stop'));
|
|
window.clearInterval(this.timer);
|
|
delete this.timer;
|
|
delete this.tick;
|
|
return true;
|
|
},
|
|
|
|
step: function() {
|
|
Request.poll.queue.forEach(function(e) {
|
|
if ((Request.poll.tick % e.interval) != 0)
|
|
return;
|
|
|
|
if (e.running)
|
|
return;
|
|
|
|
var opts = Object.assign({}, e.options,
|
|
{ timeout: e.interval * 1000 - 5 });
|
|
|
|
e.running = true;
|
|
Request.request(e.url, opts)
|
|
.then(function(res) {
|
|
if (!e.running || !Request.poll.active())
|
|
return;
|
|
|
|
try {
|
|
e.callback(res, res.json(), res.duration);
|
|
}
|
|
catch (err) {
|
|
e.callback(res, null, res.duration);
|
|
}
|
|
})
|
|
.finally(function() { delete e.running });
|
|
});
|
|
|
|
Request.poll.tick = (Request.poll.tick + 1) % Math.pow(2, 32);
|
|
},
|
|
|
|
active: function() {
|
|
return (this.timer != null);
|
|
}
|
|
})
|
|
});
|
|
|
|
|
|
var dummyElem = null,
|
|
domParser = null,
|
|
originalCBIInit = null,
|
|
classes = {};
|
|
|
|
LuCI = Class.extend({
|
|
__name__: 'LuCI',
|
|
__init__: function(env) {
|
|
Object.assign(this.env, env);
|
|
|
|
document.addEventListener('DOMContentLoaded', this.setupDOM.bind(this));
|
|
|
|
document.addEventListener('poll-start', function(ev) {
|
|
document.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e) {
|
|
e.style.display = (e.id == 'xhr_poll_status_off') ? 'none' : '';
|
|
});
|
|
});
|
|
|
|
document.addEventListener('poll-stop', function(ev) {
|
|
document.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e) {
|
|
e.style.display = (e.id == 'xhr_poll_status_on') ? 'none' : '';
|
|
});
|
|
});
|
|
|
|
originalCBIInit = window.cbi_init;
|
|
window.cbi_init = function() {};
|
|
},
|
|
|
|
error: function(type, fmt /*, ...*/) {
|
|
var e = null,
|
|
msg = fmt ? String.prototype.format.apply(fmt, this.varargs(arguments, 2)) : null,
|
|
stack = null;
|
|
|
|
if (type instanceof Error) {
|
|
e = type;
|
|
stack = (e.stack || '').split(/\n/);
|
|
|
|
if (msg)
|
|
e.message = msg + ': ' + e.message;
|
|
}
|
|
else {
|
|
e = new (window[type || 'Error'] || Error)(msg || 'Unspecified error');
|
|
e.name = type || 'Error';
|
|
|
|
try { throw new Error('stacktrace') }
|
|
catch (e2) { stack = (e2.stack || '').split(/\n/) }
|
|
|
|
/* IE puts the exception message into the first line */
|
|
if (stack[0] == 'Error: stacktrace')
|
|
stack.shift();
|
|
|
|
/* Pop L.error() invocation from stack */
|
|
stack.shift();
|
|
}
|
|
|
|
/* Append shortened & beautified stacktrace to message */
|
|
e.message += '\n' + stack.join('\n')
|
|
.replace(/(.*?)@(.+):(\d+):(\d+)/g, ' at $1 ($2:$3:$4)');
|
|
|
|
if (window.console && console.debug)
|
|
console.debug(e);
|
|
|
|
throw e;
|
|
},
|
|
|
|
/* Class require */
|
|
require: function(name, from) {
|
|
var L = this, url = null, from = from || [];
|
|
|
|
/* Class already loaded */
|
|
if (classes[name] != null) {
|
|
/* Circular dependency */
|
|
if (from.indexOf(name) != -1)
|
|
L.error('DependencyError',
|
|
'Circular dependency: class "%s" depends on "%s"',
|
|
name, from.join('" which depends on "'));
|
|
|
|
return classes[name];
|
|
}
|
|
|
|
document.querySelectorAll('script[src$="/luci.js"]').forEach(function(s) {
|
|
url = '%s/%s.js'.format(
|
|
s.getAttribute('src').replace(/\/luci\.js$/, ''),
|
|
name.replace(/\./g, '/'));
|
|
});
|
|
|
|
if (url == null)
|
|
L.error('InternalError', 'Cannot find url of luci.js');
|
|
|
|
from = [ name ].concat(from);
|
|
|
|
var compileClass = function(res) {
|
|
if (!res.ok)
|
|
L.error('NetworkError',
|
|
'HTTP error %d while loading class file "%s"', res.status, url);
|
|
|
|
var source = res.text(),
|
|
reqmatch = /(?:^|\n)[ \t]*(?:["']require[ \t]+(\S+)(?:[ \t]+as[ \t]+([a-zA-Z_]\S*))?["']);/g,
|
|
depends = [],
|
|
args = '';
|
|
|
|
/* find require statements in source */
|
|
for (var m = reqmatch.exec(source); m; m = reqmatch.exec(source)) {
|
|
var dep = m[1], as = m[2] || dep.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
depends.push(L.require(dep, from));
|
|
args += ', ' + as;
|
|
}
|
|
|
|
/* load dependencies and instantiate class */
|
|
return Promise.all(depends).then(function(instances) {
|
|
try {
|
|
_factory = eval(
|
|
'(function(window, document, L%s) { %s })\n\n//# sourceURL=%s\n'
|
|
.format(args, source, res.url));
|
|
}
|
|
catch (error) {
|
|
L.error('SyntaxError', '%s\n in %s:%s',
|
|
error.message, res.url, error.lineNumber || '?');
|
|
}
|
|
|
|
_factory.displayName = toCamelCase(name + 'ClassFactory');
|
|
_class = _factory.apply(_factory, [window, document, L].concat(instances));
|
|
|
|
if (!Class.isSubclass(_class))
|
|
L.error('TypeError', '"%s" factory yields invalid constructor', name);
|
|
|
|
if (_class.displayName == 'AnonymousClass')
|
|
_class.displayName = toCamelCase(name + 'Class');
|
|
|
|
var ptr = Object.getPrototypeOf(L),
|
|
parts = name.split(/\./),
|
|
instance = new _class();
|
|
|
|
for (var i = 0; ptr && i < parts.length - 1; i++)
|
|
ptr = ptr[parts[i]];
|
|
|
|
if (!ptr)
|
|
L.error('DependencyError',
|
|
'Parent "%s" for class "%s" is missing',
|
|
parts.slice(0, i).join('.'), name);
|
|
|
|
classes[name] = ptr[parts[i]] = instance;
|
|
|
|
return instance;
|
|
});
|
|
};
|
|
|
|
/* Request class file */
|
|
classes[name] = Request.get(url, { cache: true }).then(compileClass);
|
|
|
|
return classes[name];
|
|
},
|
|
|
|
/* DOM setup */
|
|
setupDOM: function(ev) {
|
|
Promise.all([
|
|
L.require('ui')
|
|
]).then(function() {
|
|
Request.addInterceptor(function(res) {
|
|
if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
|
|
return;
|
|
|
|
Request.poll.stop();
|
|
|
|
L.ui.showModal(_('Session expired'), [
|
|
E('div', { class: 'alert-message warning' },
|
|
_('A new login is required since the authentication session expired.')),
|
|
E('div', { class: 'right' },
|
|
E('div', {
|
|
class: 'btn primary',
|
|
click: function() {
|
|
var loc = window.location;
|
|
window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
|
|
}
|
|
}, _('To login…')))
|
|
]);
|
|
|
|
L.error('AuthenticationError', 'Session expired');
|
|
});
|
|
|
|
originalCBIInit();
|
|
Request.poll.start();
|
|
|
|
document.dispatchEvent(new CustomEvent('luci-loaded'));
|
|
}).catch(function(error) {
|
|
alert('LuCI class loading error:\n' + error);
|
|
});
|
|
},
|
|
|
|
env: {},
|
|
|
|
/* URL construction helpers */
|
|
path: function(prefix, parts) {
|
|
var url = [ prefix || '' ];
|
|
|
|
for (var i = 0; i < parts.length; i++)
|
|
if (/^(?:[a-zA-Z0-9_.%,;-]+\/)*[a-zA-Z0-9_.%,;-]+$/.test(parts[i]))
|
|
url.push('/', parts[i]);
|
|
|
|
if (url.length === 1)
|
|
url.push('/');
|
|
|
|
return url.join('');
|
|
},
|
|
|
|
url: function() {
|
|
return this.path(this.env.scriptname, arguments);
|
|
},
|
|
|
|
resource: function() {
|
|
return this.path(this.env.resource, arguments);
|
|
},
|
|
|
|
location: function() {
|
|
return this.path(this.env.scriptname, this.env.requestpath);
|
|
},
|
|
|
|
|
|
/* HTTP resource fetching */
|
|
get: function(url, args, cb) {
|
|
return this.poll(null, url, args, cb, false);
|
|
},
|
|
|
|
post: function(url, args, cb) {
|
|
return this.poll(null, url, args, cb, true);
|
|
},
|
|
|
|
poll: function(interval, url, args, cb, post) {
|
|
if (interval !== null && interval <= 0)
|
|
interval = this.env.pollinterval;
|
|
|
|
var data = post ? { token: this.env.token } : null,
|
|
method = post ? 'POST' : 'GET';
|
|
|
|
if (!/^(?:\/|\S+:\/\/)/.test(url))
|
|
url = this.url(url);
|
|
|
|
if (args != null)
|
|
data = Object.assign(data || {}, args);
|
|
|
|
if (interval !== null)
|
|
return Request.poll.add(interval, url, { method: method, query: data }, cb);
|
|
else
|
|
return Request.request(url, { method: method, query: data })
|
|
.then(function(res) {
|
|
var json = null;
|
|
if (/^application\/json\b/.test(res.headers.get('Content-Type')))
|
|
try { json = res.json() } catch(e) {}
|
|
cb(res.xhr, json, res.duration);
|
|
});
|
|
},
|
|
|
|
stop: function(entry) { return Request.poll.remove(entry) },
|
|
halt: function() { return Request.poll.stop() },
|
|
run: function() { return Request.poll.start() },
|
|
|
|
/* DOM manipulation */
|
|
dom: Class.singleton({
|
|
__name__: 'LuCI.DOM',
|
|
|
|
elem: function(e) {
|
|
return (e != null && typeof(e) == 'object' && 'nodeType' in e);
|
|
},
|
|
|
|
parse: function(s) {
|
|
var elem;
|
|
|
|
try {
|
|
domParser = domParser || new DOMParser();
|
|
elem = domParser.parseFromString(s, 'text/html').body.firstChild;
|
|
}
|
|
catch(e) {}
|
|
|
|
if (!elem) {
|
|
try {
|
|
dummyElem = dummyElem || document.createElement('div');
|
|
dummyElem.innerHTML = s;
|
|
elem = dummyElem.firstChild;
|
|
}
|
|
catch (e) {}
|
|
}
|
|
|
|
return elem || null;
|
|
},
|
|
|
|
matches: function(node, selector) {
|
|
var m = this.elem(node) ? node.matches || node.msMatchesSelector : null;
|
|
return m ? m.call(node, selector) : false;
|
|
},
|
|
|
|
parent: function(node, selector) {
|
|
if (this.elem(node) && node.closest)
|
|
return node.closest(selector);
|
|
|
|
while (this.elem(node))
|
|
if (this.matches(node, selector))
|
|
return node;
|
|
else
|
|
node = node.parentNode;
|
|
|
|
return null;
|
|
},
|
|
|
|
append: function(node, children) {
|
|
if (!this.elem(node))
|
|
return null;
|
|
|
|
if (Array.isArray(children)) {
|
|
for (var i = 0; i < children.length; i++)
|
|
if (this.elem(children[i]))
|
|
node.appendChild(children[i]);
|
|
else if (children !== null && children !== undefined)
|
|
node.appendChild(document.createTextNode('' + children[i]));
|
|
|
|
return node.lastChild;
|
|
}
|
|
else if (typeof(children) === 'function') {
|
|
return this.append(node, children(node));
|
|
}
|
|
else if (this.elem(children)) {
|
|
return node.appendChild(children);
|
|
}
|
|
else if (children !== null && children !== undefined) {
|
|
node.innerHTML = '' + children;
|
|
return node.lastChild;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
content: function(node, children) {
|
|
if (!this.elem(node))
|
|
return null;
|
|
|
|
while (node.firstChild)
|
|
node.removeChild(node.firstChild);
|
|
|
|
return this.append(node, children);
|
|
},
|
|
|
|
attr: function(node, key, val) {
|
|
if (!this.elem(node))
|
|
return null;
|
|
|
|
var attr = null;
|
|
|
|
if (typeof(key) === 'object' && key !== null)
|
|
attr = key;
|
|
else if (typeof(key) === 'string')
|
|
attr = {}, attr[key] = val;
|
|
|
|
for (key in attr) {
|
|
if (!attr.hasOwnProperty(key) || attr[key] == null)
|
|
continue;
|
|
|
|
switch (typeof(attr[key])) {
|
|
case 'function':
|
|
node.addEventListener(key, attr[key]);
|
|
break;
|
|
|
|
case 'object':
|
|
node.setAttribute(key, JSON.stringify(attr[key]));
|
|
break;
|
|
|
|
default:
|
|
node.setAttribute(key, attr[key]);
|
|
}
|
|
}
|
|
},
|
|
|
|
create: function() {
|
|
var html = arguments[0],
|
|
attr = arguments[1],
|
|
data = arguments[2],
|
|
elem;
|
|
|
|
if (!(attr instanceof Object) || Array.isArray(attr))
|
|
data = attr, attr = null;
|
|
|
|
if (Array.isArray(html)) {
|
|
elem = document.createDocumentFragment();
|
|
for (var i = 0; i < html.length; i++)
|
|
elem.appendChild(this.create(html[i]));
|
|
}
|
|
else if (this.elem(html)) {
|
|
elem = html;
|
|
}
|
|
else if (html.charCodeAt(0) === 60) {
|
|
elem = this.parse(html);
|
|
}
|
|
else {
|
|
elem = document.createElement(html);
|
|
}
|
|
|
|
if (!elem)
|
|
return null;
|
|
|
|
this.attr(elem, attr);
|
|
this.append(elem, data);
|
|
|
|
return elem;
|
|
}
|
|
}),
|
|
|
|
Class: Class,
|
|
Request: Request
|
|
});
|
|
|
|
XHR = Class.extend({
|
|
__name__: 'LuCI.XHR',
|
|
__init__: function() {
|
|
if (window.console && console.debug)
|
|
console.debug('Direct use XHR() is deprecated, please use L.Request instead');
|
|
},
|
|
|
|
_response: function(cb, res, json, duration) {
|
|
if (this.active)
|
|
cb(res, json, duration);
|
|
delete this.active;
|
|
},
|
|
|
|
get: function(url, data, callback, timeout) {
|
|
this.active = true;
|
|
L.get(url, data, this._response.bind(this, callback), timeout);
|
|
},
|
|
|
|
post: function(url, data, callback, timeout) {
|
|
this.active = true;
|
|
L.post(url, data, this._response.bind(this, callback), timeout);
|
|
},
|
|
|
|
cancel: function() { delete this.active },
|
|
busy: function() { return (this.active === true) },
|
|
abort: function() {},
|
|
send_form: function() { L.error('InternalError', 'Not implemented') },
|
|
});
|
|
|
|
XHR.get = function() { return window.L.get.apply(window.L, arguments) };
|
|
XHR.post = function() { return window.L.post.apply(window.L, arguments) };
|
|
XHR.poll = function() { return window.L.poll.apply(window.L, arguments) };
|
|
XHR.stop = Request.poll.remove.bind(Request.poll);
|
|
XHR.halt = Request.poll.stop.bind(Request.poll);
|
|
XHR.run = Request.poll.start.bind(Request.poll);
|
|
XHR.running = Request.poll.active.bind(Request.poll);
|
|
|
|
window.XHR = XHR;
|
|
window.LuCI = LuCI;
|
|
})(window, document);
|