diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-01-07 14:48:19 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-07-07 15:25:49 +0200 |
commit | ec6d4094b988818faf6d5d06f6b26d3e1bcbcd6f (patch) | |
tree | c0aa947cddafe54c31a85bc0b2eaae9bda3f51a0 | |
parent | 992638947d0489ea886d2b603855dff1e4310141 (diff) |
luci-base: luci.js: add dynamic class loader
Introduce L.require() to fetch additional JavaScript classes.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/luci.js | 90 |
1 files changed, 89 insertions, 1 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js index 7cddc4e10..610cbcb62 100644 --- a/modules/luci-base/htdocs/luci-static/resources/luci.js +++ b/modules/luci-base/htdocs/luci-static/resources/luci.js @@ -428,7 +428,8 @@ tooltipTimeout = null, dummyElem = null, domParser = null, - originalCBIInit = null; + originalCBIInit = null, + classes = {}; LuCI = Class.extend({ __name__: 'LuCI', @@ -464,6 +465,93 @@ window.cbi_init = function() {}; }, + /* 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) + throw new Error('Circular dependency: class "%s" depends on "%s"' + .format(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) + throw new Error('Cannot find url of luci.js'); + + from = [ name ].concat(from); + + var compileClass = function(res) { + if (!res.ok) + throw new Error('HTTP error %d while loading class file "%s"' + .format(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) { + throw new SyntaxError('%s\n in %s:%s' + .format(error.message, res.url, error.lineNumber || '?')); + } + + _factory.displayName = toCamelCase(name + 'ClassFactory'); + _class = _factory.apply(_factory, [window, document, L].concat(instances)); + + if (!Class.isSubclass(_class)) + throw new TypeError('"%s" factory yields invalid constructor' + .format(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) + throw new Error('Parent "%s" for class "%s" is missing' + .format(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) { this.tabs.init(); |