diff options
author | Jo-Philipp Wich <jo@mein.io> | 2018-11-21 18:44:59 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2018-11-22 12:49:14 +0100 |
commit | 706c6836e40046ca82efc3355fc018fd37654d88 (patch) | |
tree | 95fcf2aeb5ffd0cd9773e03e7007f1960fa82199 | |
parent | 17690f2d73b640cf4bbf082fe96f5b3075a040fe (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.js | 165 | ||||
-rw-r--r-- | modules/luci-base/luasrc/view/header.htm | 11 |
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> |