luci/modules/luci-base/htdocs/luci-static/resources/luci.js
Jo-Philipp Wich 8a6b89c671 luci-base: luci.js: rework L.error()
Factor out an L.raise() function out of L.error() which constructs and
throws an exception object.

Rework the remaining L.error() function to internally use L.raise() to
construct exceptionts to render.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
2019-07-07 15:36:25 +02:00

1222 lines
30 KiB
JavaScript

(function(window, document, undefined) {
'use strict';
/* 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
*/
var 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;
}
});
var Response = Class.extend({
__name__: 'LuCI.XHR.Response',
__init__: function(xhr, url, duration, headers, content) {
this.ok = (xhr.status >= 200 && xhr.status <= 299);
this.status = xhr.status;
this.statusText = xhr.statusText;
this.headers = (headers != null) ? headers : new Headers(xhr);
this.duration = duration;
this.url = url;
this.xhr = xhr;
if (content != null && typeof(content) == 'object') {
this.responseJSON = content;
this.responseText = null;
}
else if (content != null) {
this.responseJSON = null;
this.responseText = String(content);
}
else {
this.responseJSON = null;
this.responseText = xhr.responseText;
}
},
clone: function(content) {
var copy = new Response(this.xhr, this.url, this.duration, this.headers, content);
copy.ok = this.ok;
copy.status = this.status;
copy.statusText = this.statusText;
return copy;
},
json: function() {
if (this.responseJSON == null)
this.responseJSON = JSON.parse(this.responseText);
return this.responseJSON;
},
text: function() {
if (this.responseText == null && this.responseJSON != null)
this.responseText = JSON.stringify(this.responseJSON);
return this.responseText;
}
});
var requestQueue = [],
rpcBaseURL = null;
function isQueueableRequest(opt) {
if (!classes.rpc)
return false;
if (opt.method != 'POST' || typeof(opt.content) != 'object')
return false;
if (opt.nobatch === true)
return false;
if (rpcBaseURL == null)
rpcBaseURL = Request.expandURL(classes.rpc.getBaseURL());
return (rpcBaseURL != null && opt.url.indexOf(rpcBaseURL) == 0);
}
function flushRequestQueue() {
if (!requestQueue.length)
return;
var reqopt = Object.assign({}, requestQueue[0][0], { content: [], nobatch: true }),
batch = [];
for (var i = 0; i < requestQueue.length; i++) {
batch[i] = requestQueue[i];
reqopt.content[i] = batch[i][0].content;
}
requestQueue.length = 0;
Request.request(rpcBaseURL, reqopt).then(function(reply) {
var json = null, req = null;
try { json = reply.json() }
catch(e) { }
while ((req = batch.shift()) != null)
if (Array.isArray(json) && json.length)
req[2].call(reqopt, reply.clone(json.shift()));
else
req[1].call(reqopt, new Error('No related RPC reply'));
}).catch(function(error) {
var req = null;
while ((req = batch.shift()) != null)
req[1].call(reqopt, error);
});
}
var Request = Class.singleton({
__name__: 'LuCI.Request',
interceptors: [],
expandURL: function(url) {
if (!/^(?:[^/]+:)?\/\//.test(url))
url = location.protocol + '//' + location.host + url;
return url;
},
request: function(target, options) {
var state = { xhr: new XMLHttpRequest(), url: this.expandURL(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 (isQueueableRequest(opt)) {
requestQueue.push([opt, rejectFn, resolveFn]);
requestAnimationFrame(flushRequestQueue);
return;
}
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: {
add: function(interval, url, options, callback) {
if (isNaN(interval) || interval <= 0)
throw new TypeError('Invalid poll interval');
var ival = interval >>> 0,
opts = Object.assign({}, options, { timeout: ival * 1000 - 5 });
return Poll.add(function() {
return Request.request(url, options).then(function(res) {
if (!Poll.active())
return;
try {
callback(res, res.json(), res.duration);
}
catch (err) {
callback(res, null, res.duration);
}
});
}, ival);
},
remove: function(entry) { return Poll.remove(entry) },
start: function() { return Poll.start() },
stop: function() { return Poll.stop() },
active: function() { return Poll.active() }
}
});
var Poll = Class.singleton({
__name__: 'LuCI.Poll',
queue: [],
add: function(fn, interval) {
if (interval == null || interval <= 0)
interval = window.L ? window.L.env.pollinterval : null;
if (isNaN(interval) || typeof(fn) != 'function')
throw new TypeError('Invalid argument to LuCI.Poll.add()');
for (var i = 0; i < this.queue.length; i++)
if (this.queue[i].fn === fn)
return false;
var e = {
r: true,
i: interval >>> 0,
fn: fn
};
this.queue.push(e);
if (this.tick != null && !this.active())
this.start();
return true;
},
remove: function(entry) {
if (typeof(fn) != 'function')
throw new TypeError('Invalid argument to LuCI.Poll.remove()');
var len = this.queue.length;
for (var i = len; i > 0; i--)
if (this.queue[i-1].fn === fn)
this.queue.splice(i-1, 1);
if (!this.queue.length && this.stop())
this.tick = 0;
return (this.queue.length != len);
},
start: function() {
if (this.active())
return false;
this.tick = 0;
if (this.queue.length) {
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() {
for (var i = 0, e = null; (e = Poll.queue[i]) != null; i++) {
if ((Poll.tick % e.i) != 0)
continue;
if (!e.r)
continue;
e.r = false;
Promise.resolve(e.fn()).finally((function() { this.r = true }).bind(e));
}
Poll.tick = (Poll.tick + 1) % Math.pow(2, 32);
},
active: function() {
return (this.timer != null);
}
});
var dummyElem = null,
domParser = null,
originalCBIInit = null,
classes = {};
var LuCI = Class.extend({
__name__: 'LuCI',
__init__: function(env) {
document.querySelectorAll('script[src*="/luci.js"]').forEach(function(s) {
if (env.base_url == null || env.base_url == '')
env.base_url = s.getAttribute('src').replace(/\/luci\.js(?:\?v=[^?]+)?$/, '');
});
if (env.base_url == null)
this.error('InternalError', 'Cannot find url of luci.js');
Object.assign(this.env, env);
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' : '';
});
});
var domReady = new Promise(function(resolveFn, rejectFn) {
document.addEventListener('DOMContentLoaded', resolveFn);
});
Promise.all([
domReady,
this.require('ui'),
this.require('form')
]).then(this.setupDOM.bind(this)).catch(this.error);
originalCBIInit = window.cbi_init;
window.cbi_init = function() {};
},
raise: 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';
}
if (window.console && console.debug)
console.debug(e);
throw e;
},
error: function(type, fmt /*, ...*/) {
try {
L.raise.apply(L, Array.prototype.slice.call(arguments));
}
catch (e) {
var stack = (e.stack || '').split(/\n/).map(function(frame) {
frame = frame.replace(/(.*?)@(.+):(\d+):(\d+)/g, 'at $1 ($2:$3:$4)').trim();
return frame ? ' ' + frame : '';
});
if (!/^ at /.test(stack[0]))
stack.shift();
if (/\braise /.test(stack[0]))
stack.shift();
if (/\berror /.test(stack[0]))
stack.shift();
stack = stack.length ? '\n' + stack.join('\n') : '';
if (L.ui)
L.ui.showModal(e.name || _('Runtime error'),
E('pre', { 'class': 'alert-message error' }, e.message + stack));
else
L.dom.content(document.querySelector('#maincontent'),
E('pre', { 'class': 'alert-message error' }, e + stack));
throw e;
}
},
bind: function(fn, self /*, ... */) {
return Function.prototype.bind.apply(fn, this.varargs(arguments, 2, self));
},
/* 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];
}
url = '%s/%s.js'.format(L.env.base_url, name.replace(/\./g, '/'));
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(),
requirematch = /^require[ \t]+(\S+)(?:[ \t]+as[ \t]+([a-zA-Z_]\S*))?$/,
strictmatch = /^use[ \t]+strict$/,
depends = [],
args = '';
/* find require statements in source */
for (var i = 0, off = -1, quote = -1, esc = false; i < source.length; i++) {
var chr = source.charCodeAt(i);
if (esc) {
esc = false;
}
else if (chr == 92) {
esc = true;
}
else if (chr == quote) {
var s = source.substring(off, i),
m = requirematch.exec(s);
if (m) {
var dep = m[1], as = m[2] || dep.replace(/[^a-zA-Z0-9_]/g, '_');
depends.push(L.require(dep, from));
args += ', ' + as;
}
else if (!strictmatch.exec(s)) {
break;
}
off = -1;
quote = -1;
}
else if (quote == -1 && (chr == 34 || chr == 39)) {
off = i + 1;
quote = chr;
}
}
/* load dependencies and instantiate class */
return Promise.all(depends).then(function(instances) {
var _factory, _class;
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)
ptr[parts[i]] = instance;
classes[name] = instance;
return instance;
});
};
/* Request class file */
classes[name] = Request.get(url, { cache: true })
.then(compileClass)
.catch(L.error);
return classes[name];
},
/* DOM setup */
setupDOM: function(ev) {
Request.addInterceptor(function(res) {
if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
return;
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…')))
]);
throw 'Session expired';
});
originalCBIInit();
Poll.start();
document.dispatchEvent(new CustomEvent('luci-loaded'));
},
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 Poll.remove(entry) },
halt: function() { return Poll.stop() },
run: function() { return 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;
var dataNodes = node.querySelectorAll('[data-idref]');
for (var i = 0; i < dataNodes.length; i++)
delete this.registry[dataNodes[i].getAttribute('data-idref')];
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;
},
registry: {},
data: function(node, key, val) {
var id = node.getAttribute('data-idref');
/* clear all data */
if (arguments.length > 1 && key == null) {
if (id != null) {
node.removeAttribute('data-idref');
val = this.registry[id]
delete this.registry[id];
return val;
}
return null;
}
/* clear a key */
else if (arguments.length > 2 && key != null && val == null) {
if (id != null) {
val = this.registry[id][key];
delete this.registry[id][key];
return val;
}
return null;
}
/* set a key */
else if (arguments.length > 2 && key != null && val != null) {
if (id == null) {
do { id = Math.floor(Math.random() * 0xffffffff).toString(16) }
while (this.registry.hasOwnProperty(id));
node.setAttribute('data-idref', id);
this.registry[id] = {};
}
return (this.registry[id][key] = val);
}
/* get all data */
else if (arguments.length == 1) {
if (id != null)
return this.registry[id];
return null;
}
/* get a key */
else if (arguments.length == 2) {
if (id != null)
return this.registry[id][key];
}
return null;
},
bindClassInstance: function(node, inst) {
if (!(inst instanceof Class))
L.error('TypeError', 'Argument must be a class instance');
return this.data(node, '_class', inst);
},
findClassInstance: function(node) {
var inst = null;
do {
inst = this.data(node, '_class');
node = node.parentNode;
}
while (!(inst instanceof Class) && node != null);
return inst;
},
callClassMethod: function(node, method /*, ... */) {
var inst = this.findClassInstance(node);
if (inst == null || typeof(inst[method]) != 'function')
return null;
return inst[method].apply(inst, inst.varargs(arguments, 2));
}
}),
Poll: Poll,
Class: Class,
Request: Request,
view: Class.extend({
__name__: 'LuCI.View',
__init__: function() {
var vp = document.getElementById('view');
L.dom.content(vp, E('div', { 'class': 'spinning' }, _('Loading view…')));
return Promise.resolve(this.load())
.then(L.bind(this.render, this))
.then(L.bind(function(nodes) {
var vp = document.getElementById('view');
L.dom.content(vp, nodes);
L.dom.append(vp, this.addFooter());
}, this)).catch(L.error);
},
load: function() {},
render: function() {},
handleSave: function(ev) {
var tasks = [];
document.getElementById('maincontent')
.querySelectorAll('.cbi-map').forEach(function(map) {
tasks.push(L.dom.callClassMethod(map, 'save'));
});
return Promise.all(tasks);
},
handleSaveApply: function(ev) {
return this.handleSave(ev).then(function() {
L.ui.changes.apply(true);
});
},
handleReset: function(ev) {
var tasks = [];
document.getElementById('maincontent')
.querySelectorAll('.cbi-map').forEach(function(map) {
tasks.push(L.dom.callClassMethod(map, 'reset'));
});
return Promise.all(tasks);
},
addFooter: function() {
var footer = E([]),
mc = document.getElementById('maincontent');
if (mc.querySelector('.cbi-map')) {
footer.appendChild(E('div', { 'class': 'cbi-page-actions' }, [
E('input', {
'class': 'cbi-button cbi-button-apply',
'type': 'button',
'value': _('Save & Apply'),
'click': L.bind(this.handleSaveApply, this)
}), ' ',
E('input', {
'class': 'cbi-button cbi-button-save',
'type': 'submit',
'value': _('Save'),
'click': L.bind(this.handleSave, this)
}), ' ',
E('input', {
'class': 'cbi-button cbi-button-reset',
'type': 'button',
'value': _('Reset'),
'click': L.bind(this.handleReset, this)
})
]));
}
return footer;
}
})
});
var 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);