summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2018-11-21 18:44:59 +0100
committerJo-Philipp Wich <jo@mein.io>2018-11-22 12:49:14 +0100
commit706c6836e40046ca82efc3355fc018fd37654d88 (patch)
tree95fcf2aeb5ffd0cd9773e03e7007f1960fa82199
parent17690f2d73b640cf4bbf082fe96f5b3075a040fe (diff)
luci-base: introduce common JavaScript api
Introduce a new script file luci.js which is included by default and intended to be the common location of functions currently scattered in cbi.js and xhr.js. The luci.js file provides a LuCI() class which - among other things - implements helpers to construct URL paths and making HTTP requests. A singleton instance of the class is instantiated as window.L upon load and preset with the necessary environment information. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/luci.js165
-rw-r--r--modules/luci-base/luasrc/view/header.htm11
2 files changed, 176 insertions, 0 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js
new file mode 100644
index 000000000..cbf22460e
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/luci.js
@@ -0,0 +1,165 @@
+(function(window, document) {
+ var modalDiv = null,
+ tooltipDiv = null,
+ tooltipTimeout = null;
+
+ LuCI.prototype = {
+ /* 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(0, url, args, cb, false);
+ },
+
+ post: function(url, args, cb) {
+ return this.poll(0, url, args, cb, true);
+ },
+
+ poll: function(interval, url, args, cb, post) {
+ var data = post ? { token: this.env.token } : null;
+
+ if (!/^(?:\/|\S+:\/\/)/.test(url))
+ url = this.url(url);
+
+ if (typeof(args) === 'object' && args !== null) {
+ data = data || {};
+
+ for (var key in args)
+ if (args.hasOwnProperty(key))
+ switch (typeof(args[key])) {
+ case 'string':
+ case 'number':
+ case 'boolean':
+ data[key] = args[key];
+ break;
+
+ case 'object':
+ data[key] = JSON.stringify(args[key]);
+ break;
+ }
+ }
+
+ if (interval > 0)
+ return XHR.poll(interval, url, data, cb, post);
+ else if (post)
+ return XHR.post(url, data, cb);
+ else
+ return XHR.get(url, data, cb);
+ },
+
+
+ /* Modal dialog */
+ showModal: function(title, children) {
+ var dlg = modalDiv.firstElementChild;
+
+ while (dlg.firstChild)
+ dlg.removeChild(dlg.firstChild);
+
+ dlg.setAttribute('class', 'modal');
+ dlg.appendChild(E('h4', {}, title));
+
+ if (!Array.isArray(children))
+ children = [ children ];
+
+ for (var i = 0; i < children.length; i++)
+ if (isElem(children[i]))
+ dlg.appendChild(children[i]);
+ else
+ dlg.appendChild(document.createTextNode('' + children[i]));
+
+ document.body.classList.add('modal-overlay-active');
+
+ return dlg;
+ },
+
+ hideModal: function() {
+ document.body.classList.remove('modal-overlay-active');
+ },
+
+
+ /* Tooltip */
+ showTooltip: function(ev) {
+ var target = findParent(ev.target, '[data-tooltip]');
+
+ if (!target)
+ return;
+
+ if (tooltipTimeout !== null) {
+ window.clearTimeout(tooltipTimeout);
+ tooltipTimeout = null;
+ }
+
+ var rect = target.getBoundingClientRect(),
+ x = rect.left + window.pageXOffset,
+ y = rect.top + rect.height + window.pageYOffset;
+
+ tooltipDiv.className = 'cbi-tooltip';
+ tooltipDiv.innerHTML = '▲ ';
+ tooltipDiv.firstChild.data += target.getAttribute('data-tooltip');
+
+ if (target.hasAttribute('data-tooltip-style'))
+ tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));
+
+ if ((y + tooltipDiv.offsetHeight) > (window.innerHeight + window.pageYOffset)) {
+ y -= (tooltipDiv.offsetHeight + target.offsetHeight);
+ tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(2);
+ }
+
+ tooltipDiv.style.top = y + 'px';
+ tooltipDiv.style.left = x + 'px';
+ tooltipDiv.style.opacity = 1;
+ },
+
+ hideTooltip: function(ev) {
+ if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv)
+ return;
+
+ if (tooltipTimeout !== null) {
+ window.clearTimeout(tooltipTimeout);
+ tooltipTimeout = null;
+ }
+
+ tooltipDiv.style.opacity = 0;
+ tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250);
+ }
+ };
+
+ function LuCI(env) {
+ this.env = env;
+
+ modalDiv = document.body.appendChild(E('div', { id: 'modal_overlay' }, E('div', { class: 'modal' })));
+ tooltipDiv = document.body.appendChild(E('div', { 'class': 'cbi-tooltip' }));
+
+ document.addEventListener('mouseover', this.showTooltip.bind(this), true);
+ document.addEventListener('mouseout', this.hideTooltip.bind(this), true);
+ document.addEventListener('focus', this.showTooltip.bind(this), true);
+ document.addEventListener('blur', this.hideTooltip.bind(this), true);
+ }
+
+ window.LuCI = LuCI;
+})(window, document);
diff --git a/modules/luci-base/luasrc/view/header.htm b/modules/luci-base/luasrc/view/header.htm
index f6e20c9a4..2813c4d94 100644
--- a/modules/luci-base/luasrc/view/header.htm
+++ b/modules/luci-base/luasrc/view/header.htm
@@ -10,3 +10,14 @@
luci.dispatcher.context.template_header_sent = true
end
%>
+
+<script type="text/javascript" src="<%=resource%>/luci.js"></script>
+<script type="text/javascript">
+ L = new LuCI(<%= luci.http.write_json({
+ token = token,
+ resource = resource,
+ scriptname = luci.http.getenv("SCRIPT_NAME"),
+ pathinfo = luci.http.getenv("PATH_INFO"),
+ requestpath = luci.dispatcher.context.requestpath
+ }) %>);
+</script>