1 (function(window
, document
, undefined) {
2 /* Object.assign polyfill for IE */
3 if (typeof Object
.assign
!== 'function') {
4 Object
.defineProperty(Object
, 'assign', {
5 value
: function assign(target
, varArgs
) {
7 throw new TypeError('Cannot convert undefined or null to object');
9 var to
= Object(target
);
11 for (var index
= 1; index
< arguments
.length
; index
++)
12 if (arguments
[index
] != null)
13 for (var nextKey
in arguments
[index
])
14 if (Object
.prototype.hasOwnProperty
.call(arguments
[index
], nextKey
))
15 to
[nextKey
] = arguments
[index
][nextKey
];
25 * Class declaration and inheritance helper
28 var toCamelCase = function(s
) {
29 return s
.replace(/(?:^|[\. -])(.)/g, function(m0
, m1
) { return m1
.toUpperCase() });
32 var superContext
= null, Class
= Object
.assign(function() {}, {
33 extend: function(properties
) {
35 __base__
: { value
: this.prototype },
36 __name__
: { value
: properties
.__name__
|| 'anonymous' }
39 var ClassConstructor = function() {
40 if (!(this instanceof ClassConstructor
))
41 throw new TypeError('Constructor must not be called without "new"');
43 if (Object
.getPrototypeOf(this).hasOwnProperty('__init__')) {
44 if (typeof(this.__init__
) != 'function')
45 throw new TypeError('Class __init__ member is not a function');
47 this.__init__
.apply(this, arguments
)
50 this.super('__init__', arguments
);
54 for (var key
in properties
)
55 if (!props
[key
] && properties
.hasOwnProperty(key
))
56 props
[key
] = { value
: properties
[key
], writable
: true };
58 ClassConstructor
.prototype = Object
.create(this.prototype, props
);
59 ClassConstructor
.prototype.constructor = ClassConstructor
;
60 Object
.assign(ClassConstructor
, this);
61 ClassConstructor
.displayName
= toCamelCase(props
.__name__
.value
+ 'Class');
63 return ClassConstructor
;
66 singleton: function(properties
/*, ... */) {
67 return Class
.extend(properties
)
68 .instantiate(Class
.prototype.varargs(arguments
, 1));
71 instantiate: function(args
) {
72 return new (Function
.prototype.bind
.apply(this,
73 Class
.prototype.varargs(args
, 0, null)))();
76 call: function(self
, method
) {
77 if (typeof(this.prototype[method
]) != 'function')
78 throw new ReferenceError(method
+ ' is not defined in class');
80 return this.prototype[method
].apply(self
, self
.varargs(arguments
, 1));
83 isSubclass: function(_class
) {
84 return (_class
!= null &&
85 typeof(_class
) == 'function' &&
86 _class
.prototype instanceof this);
90 varargs: function(args
, offset
/*, ... */) {
91 return Array
.prototype.slice
.call(arguments
, 2)
92 .concat(Array
.prototype.slice
.call(args
, offset
));
95 super: function(key
, callArgs
) {
96 for (superContext
= Object
.getPrototypeOf(superContext
||
97 Object
.getPrototypeOf(this));
98 superContext
&& !superContext
.hasOwnProperty(key
);
99 superContext
= Object
.getPrototypeOf(superContext
)) { }
104 var res
= superContext
[key
];
106 if (arguments
.length
> 1) {
107 if (typeof(res
) != 'function')
108 throw new ReferenceError(key
+ ' is not a function in base class');
110 if (typeof(callArgs
) != 'object')
111 callArgs
= this.varargs(arguments
, 1);
113 res
= res
.apply(this, callArgs
);
121 toString: function() {
122 var s
= '[' + this.constructor.displayName
+ ']', f
= true;
123 for (var k
in this) {
124 if (this.hasOwnProperty(k
)) {
125 s
+= (f
? ' {\n' : '') + ' ' + k
+ ': ' + typeof(this[k
]) + '\n';
129 return s
+ (f
? '' : '}');
136 * HTTP Request helper
139 Headers
= Class
.extend({
140 __name__
: 'LuCI.XHR.Headers',
141 __init__: function(xhr
) {
142 var hdrs
= this.headers
= {};
143 xhr
.getAllResponseHeaders().split(/\r\n/).forEach(function(line
) {
144 var m
= /^([^:]+):(.*)$/.exec(line
);
146 hdrs
[m
[1].trim().toLowerCase()] = m
[2].trim();
150 has: function(name
) {
151 return this.headers
.hasOwnProperty(String(name
).toLowerCase());
154 get: function(name
) {
155 var key
= String(name
).toLowerCase();
156 return this.headers
.hasOwnProperty(key
) ? this.headers
[key
] : null;
160 Response
= Class
.extend({
161 __name__
: 'LuCI.XHR.Response',
162 __init__: function(xhr
, url
, duration
) {
163 this.ok
= (xhr
.status
>= 200 && xhr
.status
<= 299);
164 this.status
= xhr
.status
;
165 this.statusText
= xhr
.statusText
;
166 this.responseText
= xhr
.responseText
;
167 this.headers
= new Headers(xhr
);
168 this.duration
= duration
;
174 return JSON
.parse(this.responseText
);
178 return this.responseText
;
182 Request
= Class
.singleton({
183 __name__
: 'LuCI.Request',
187 request: function(target
, options
) {
188 var state
= { xhr
: new XMLHttpRequest(), url
: target
, start
: Date
.now() },
189 opt
= Object
.assign({}, options
, state
),
192 callback
= this.handleReadyStateChange
;
194 return new Promise(function(resolveFn
, rejectFn
) {
195 opt
.xhr
.onreadystatechange
= callback
.bind(opt
, resolveFn
, rejectFn
);
196 opt
.method
= String(opt
.method
|| 'GET').toUpperCase();
198 if ('query' in opt
) {
199 var q
= (opt
.query
!= null) ? Object
.keys(opt
.query
).map(function(k
) {
200 if (opt
.query
[k
] != null) {
201 var v
= (typeof(opt
.query
[k
]) == 'object')
202 ? JSON
.stringify(opt
.query
[k
])
203 : String(opt
.query
[k
]);
205 return '%s=%s'.format(encodeURIComponent(k
), encodeURIComponent(v
));
208 return encodeURIComponent(k
);
213 switch (opt
.method
) {
217 opt
.url
+= ((/\?/).test(opt
.url
) ? '&' : '?') + q
;
221 if (content
== null) {
223 contenttype
= 'application/x-www-form-urlencoded';
230 opt
.url
+= ((/\?/).test(opt
.url
) ? '&' : '?') + (new Date()).getTime();
232 if (!/^(?:[^/]+:)?\/\//.test(opt
.url
))
233 opt
.url
= location
.protocol
+ '//' + location
.host
+ opt
.url
;
235 if ('username' in opt
&& 'password' in opt
)
236 opt
.xhr
.open(opt
.method
, opt
.url
, true, opt
.username
, opt
.password
);
238 opt
.xhr
.open(opt
.method
, opt
.url
, true);
240 opt
.xhr
.responseType
= 'text';
241 opt
.xhr
.overrideMimeType('application/octet-stream');
243 if ('timeout' in opt
)
244 opt
.xhr
.timeout
= +opt
.timeout
;
246 if ('credentials' in opt
)
247 opt
.xhr
.withCredentials
= !!opt
.credentials
;
249 if (opt
.content
!= null) {
250 switch (typeof(opt
.content
)) {
252 content
= opt
.content(xhr
);
256 content
= JSON
.stringify(opt
.content
);
257 contenttype
= 'application/json';
261 content
= String(opt
.content
);
265 if ('headers' in opt
)
266 for (var header
in opt
.headers
)
267 if (opt
.headers
.hasOwnProperty(header
)) {
268 if (header
.toLowerCase() != 'content-type')
269 opt
.xhr
.setRequestHeader(header
, opt
.headers
[header
]);
271 contenttype
= opt
.headers
[header
];
274 if (contenttype
!= null)
275 opt
.xhr
.setRequestHeader('Content-Type', contenttype
);
278 opt
.xhr
.send(content
);
281 rejectFn
.call(opt
, e
);
286 handleReadyStateChange: function(resolveFn
, rejectFn
, ev
) {
289 if (xhr
.readyState
!== 4)
292 if (xhr
.status
=== 0 && xhr
.statusText
=== '') {
293 rejectFn
.call(this, new Error('XHR request aborted by browser'));
296 var response
= new Response(
297 xhr
, xhr
.responseURL
|| this.url
, Date
.now() - this.start
);
299 Promise
.all(Request
.interceptors
.map(function(fn
) { return fn(response
) }))
300 .then(resolveFn
.bind(this, response
))
301 .catch(rejectFn
.bind(this));
310 get: function(url
, options
) {
311 return this.request(url
, Object
.assign({ method
: 'GET' }, options
));
314 post: function(url
, data
, options
) {
315 return this.request(url
, Object
.assign({ method
: 'POST', content
: data
}, options
));
318 addInterceptor: function(interceptorFn
) {
319 if (typeof(interceptorFn
) == 'function')
320 this.interceptors
.push(interceptorFn
);
321 return interceptorFn
;
324 removeInterceptor: function(interceptorFn
) {
325 var oldlen
= this.interceptors
.length
, i
= oldlen
;
327 if (this.interceptors
[i
] === interceptorFn
)
328 this.interceptors
.splice(i
, 1);
329 return (this.interceptors
.length
< oldlen
);
332 poll
: Class
.singleton({
333 __name__
: 'LuCI.Request.Poll',
337 add: function(interval
, url
, options
, callback
) {
338 if (isNaN(interval
) || interval
<= 0)
339 throw new TypeError('Invalid poll interval');
352 remove: function(entry
) {
353 var oldlen
= this.queue
.length
, i
= oldlen
;
356 if (this.queue
[i
] === entry
) {
357 delete this.queue
[i
].running
;
358 this.queue
.splice(i
, 1);
361 if (!this.queue
.length
)
364 return (this.queue
.length
< oldlen
);
368 if (!this.queue
.length
|| this.active())
372 this.timer
= window
.setInterval(this.step
, 1000);
374 document
.dispatchEvent(new CustomEvent('poll-start'));
382 document
.dispatchEvent(new CustomEvent('poll-stop'));
383 window
.clearInterval(this.timer
);
390 Request
.poll
.queue
.forEach(function(e
) {
391 if ((Request
.poll
.tick
% e
.interval
) != 0)
397 var opts
= Object
.assign({}, e
.options
,
398 { timeout
: e
.interval
* 1000 - 5 });
401 Request
.request(e
.url
, opts
)
402 .then(function(res
) {
403 if (!e
.running
|| !Request
.poll
.active())
407 e
.callback(res
, res
.json(), res
.duration
);
410 e
.callback(res
, null, res
.duration
);
413 .finally(function() { delete e
.running
});
416 Request
.poll
.tick
= (Request
.poll
.tick
+ 1) % Math
.pow(2, 32);
420 return (this.timer
!= null);
428 tooltipTimeout
= null,
431 originalCBIInit
= null;
433 LuCI
= Class
.extend({
435 __init__: function(env
) {
436 Object
.assign(this.env
, env
);
438 modalDiv
= document
.body
.appendChild(
439 this.dom
.create('div', { id
: 'modal_overlay' },
440 this.dom
.create('div', { class: 'modal', role
: 'dialog', 'aria-modal': true })));
442 tooltipDiv
= document
.body
.appendChild(this.dom
.create('div', { class: 'cbi-tooltip' }));
444 document
.addEventListener('mouseover', this.showTooltip
.bind(this), true);
445 document
.addEventListener('mouseout', this.hideTooltip
.bind(this), true);
446 document
.addEventListener('focus', this.showTooltip
.bind(this), true);
447 document
.addEventListener('blur', this.hideTooltip
.bind(this), true);
449 document
.addEventListener('DOMContentLoaded', this.setupDOM
.bind(this));
451 document
.addEventListener('poll-start', function(ev
) {
452 document
.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e
) {
453 e
.style
.display
= (e
.id
== 'xhr_poll_status_off') ? 'none' : '';
457 document
.addEventListener('poll-stop', function(ev
) {
458 document
.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e
) {
459 e
.style
.display
= (e
.id
== 'xhr_poll_status_on') ? 'none' : '';
463 originalCBIInit
= window
.cbi_init
;
464 window
.cbi_init = function() {};
468 setupDOM: function(ev
) {
471 Request
.addInterceptor(function(res
) {
472 if (res
.status
!= 403 || res
.headers
.get('X-LuCI-Login-Required') != 'yes')
477 L
.showModal(_('Session expired'), [
478 E('div', { class: 'alert-message warning' },
479 _('A new login is required since the authentication session expired.')),
480 E('div', { class: 'right' },
482 class: 'btn primary',
484 var loc
= window
.location
;
485 window
.location
= loc
.protocol
+ '//' + loc
.host
+ loc
.pathname
+ loc
.search
;
487 }, _('To login…')))
490 return Promise
.reject(new Error('Session expired'));
494 Request
.poll
.start();
499 /* URL construction helpers */
500 path: function(prefix
, parts
) {
501 var url
= [ prefix
|| '' ];
503 for (var i
= 0; i
< parts
.length
; i
++)
504 if (/^(?:[a-zA-Z0-9_.%,;-]+\/)*[a-zA-Z0-9_.%,;-]+$/.test(parts
[i
]))
505 url
.push('/', parts
[i
]);
507 if (url
.length
=== 1)
514 return this.path(this.env
.scriptname
, arguments
);
517 resource: function() {
518 return this.path(this.env
.resource
, arguments
);
521 location: function() {
522 return this.path(this.env
.scriptname
, this.env
.requestpath
);
526 /* HTTP resource fetching */
527 get: function(url
, args
, cb
) {
528 return this.poll(null, url
, args
, cb
, false);
531 post: function(url
, args
, cb
) {
532 return this.poll(null, url
, args
, cb
, true);
535 poll: function(interval
, url
, args
, cb
, post
) {
536 if (interval
!== null && interval
<= 0)
537 interval
= this.env
.pollinterval
;
539 var data
= post
? { token
: this.env
.token
} : null,
540 method
= post
? 'POST' : 'GET';
542 if (!/^(?:\/|\S+:\/\/)/.test(url
))
546 data
= Object
.assign(data
|| {}, args
);
548 if (interval
!== null)
549 return Request
.poll
.add(interval
, url
, { method
: method
, query
: data
}, cb
);
551 return Request
.request(url
, { method
: method
, query
: data
})
552 .then(function(res
) {
554 if (/^application\/json\b/.test(res
.headers
.get('Content-Type')))
555 try { json
= res
.json() } catch(e
) {}
556 cb(res
.xhr
, json
, res
.duration
);
560 stop: function(entry
) { return Request
.poll
.remove(entry
) },
561 halt: function() { return Request
.poll
.stop() },
562 run: function() { return Request
.poll
.start() },
566 showModal: function(title
, children
) {
567 var dlg
= modalDiv
.firstElementChild
;
569 dlg
.setAttribute('class', 'modal');
571 this.dom
.content(dlg
, this.dom
.create('h4', {}, title
));
572 this.dom
.append(dlg
, children
);
574 document
.body
.classList
.add('modal-overlay-active');
579 hideModal: function() {
580 document
.body
.classList
.remove('modal-overlay-active');
585 showTooltip: function(ev
) {
586 var target
= findParent(ev
.target
, '[data-tooltip]');
591 if (tooltipTimeout
!== null) {
592 window
.clearTimeout(tooltipTimeout
);
593 tooltipTimeout
= null;
596 var rect
= target
.getBoundingClientRect(),
597 x
= rect
.left
+ window
.pageXOffset
,
598 y
= rect
.top
+ rect
.height
+ window
.pageYOffset
;
600 tooltipDiv
.className
= 'cbi-tooltip';
601 tooltipDiv
.innerHTML
= 'â–² ';
602 tooltipDiv
.firstChild
.data
+= target
.getAttribute('data-tooltip');
604 if (target
.hasAttribute('data-tooltip-style'))
605 tooltipDiv
.classList
.add(target
.getAttribute('data-tooltip-style'));
607 if ((y
+ tooltipDiv
.offsetHeight
) > (window
.innerHeight
+ window
.pageYOffset
)) {
608 y
-= (tooltipDiv
.offsetHeight
+ target
.offsetHeight
);
609 tooltipDiv
.firstChild
.data
= 'â–¼ ' + tooltipDiv
.firstChild
.data
.substr(2);
612 tooltipDiv
.style
.top
= y
+ 'px';
613 tooltipDiv
.style
.left
= x
+ 'px';
614 tooltipDiv
.style
.opacity
= 1;
616 tooltipDiv
.dispatchEvent(new CustomEvent('tooltip-open', {
618 detail
: { target
: target
}
622 hideTooltip: function(ev
) {
623 if (ev
.target
=== tooltipDiv
|| ev
.relatedTarget
=== tooltipDiv
||
624 tooltipDiv
.contains(ev
.target
) || tooltipDiv
.contains(ev
.relatedTarget
))
627 if (tooltipTimeout
!== null) {
628 window
.clearTimeout(tooltipTimeout
);
629 tooltipTimeout
= null;
632 tooltipDiv
.style
.opacity
= 0;
633 tooltipTimeout
= window
.setTimeout(function() { tooltipDiv
.removeAttribute('style'); }, 250);
635 tooltipDiv
.dispatchEvent(new CustomEvent('tooltip-close', { bubbles
: true }));
640 itemlist: function(node
, items
, separators
) {
643 if (!Array
.isArray(separators
))
644 separators
= [ separators
|| E('br') ];
646 for (var i
= 0; i
< items
.length
; i
+= 2) {
647 if (items
[i
+1] !== null && items
[i
+1] !== undefined) {
648 var sep
= separators
[(i
/2) % separators
.length
],
651 children
.push(E('span', { class: 'nowrap' }, [
652 items
[i
] ? E('strong', items
[i
] + ': ') : '',
656 if ((i
+2) < items
.length
)
657 children
.push(this.dom
.elem(sep
) ? sep
.cloneNode(true) : sep
);
661 this.dom
.content(node
, children
);
671 LuCI
.prototype.tabs
= {
673 var groups
= [], prevGroup
= null, currGroup
= null;
675 document
.querySelectorAll('[data-tab]').forEach(function(tab
) {
676 var parent
= tab
.parentNode
;
678 if (!parent
.hasAttribute('data-tab-group'))
679 parent
.setAttribute('data-tab-group', groups
.length
);
681 currGroup
= +parent
.getAttribute('data-tab-group');
683 if (currGroup
!== prevGroup
) {
684 prevGroup
= currGroup
;
686 if (!groups
[currGroup
])
687 groups
[currGroup
] = [];
690 groups
[currGroup
].push(tab
);
693 for (var i
= 0; i
< groups
.length
; i
++)
694 this.initTabGroup(groups
[i
]);
696 document
.addEventListener('dependency-update', this.updateTabs
.bind(this));
701 this.setActiveTabId(-1, -1);
704 initTabGroup: function(panes
) {
705 if (!Array
.isArray(panes
) || panes
.length
=== 0)
708 var menu
= E('ul', { 'class': 'cbi-tabmenu' }),
709 group
= panes
[0].parentNode
,
710 groupId
= +group
.getAttribute('data-tab-group'),
713 for (var i
= 0, pane
; pane
= panes
[i
]; i
++) {
714 var name
= pane
.getAttribute('data-tab'),
715 title
= pane
.getAttribute('data-tab-title'),
716 active
= pane
.getAttribute('data-tab-active') === 'true';
718 menu
.appendChild(E('li', {
719 'class': active
? 'cbi-tab' : 'cbi-tab-disabled',
723 'click': this.switchTab
.bind(this)
730 group
.parentNode
.insertBefore(menu
, group
);
732 if (selected
=== null) {
733 selected
= this.getActiveTabId(groupId
);
735 if (selected
< 0 || selected
>= panes
.length
)
738 menu
.childNodes
[selected
].classList
.add('cbi-tab');
739 menu
.childNodes
[selected
].classList
.remove('cbi-tab-disabled');
740 panes
[selected
].setAttribute('data-tab-active', 'true');
742 this.setActiveTabId(groupId
, selected
);
746 getActiveTabState: function() {
747 var page
= document
.body
.getAttribute('data-page');
750 var val
= JSON
.parse(window
.sessionStorage
.getItem('tab'));
751 if (val
.page
=== page
&& Array
.isArray(val
.groups
))
756 window
.sessionStorage
.removeItem('tab');
757 return { page
: page
, groups
: [] };
760 getActiveTabId: function(groupId
) {
761 return +this.getActiveTabState().groups
[groupId
] || 0;
764 setActiveTabId: function(groupId
, tabIndex
) {
766 var state
= this.getActiveTabState();
767 state
.groups
[groupId
] = tabIndex
;
769 window
.sessionStorage
.setItem('tab', JSON
.stringify(state
));
771 catch (e
) { return false; }
776 updateTabs: function(ev
) {
777 document
.querySelectorAll('[data-tab-title]').forEach(function(pane
) {
778 var menu
= pane
.parentNode
.previousElementSibling
,
779 tab
= menu
.querySelector('[data-tab="%s"]'.format(pane
.getAttribute('data-tab'))),
780 n_errors
= pane
.querySelectorAll('.cbi-input-invalid').length
;
782 if (!pane
.firstElementChild
) {
783 tab
.style
.display
= 'none';
784 tab
.classList
.remove('flash');
786 else if (tab
.style
.display
=== 'none') {
787 tab
.style
.display
= '';
788 requestAnimationFrame(function() { tab
.classList
.add('flash') });
792 tab
.setAttribute('data-errors', n_errors
);
793 tab
.setAttribute('data-tooltip', _('%d invalid field(s)').format(n_errors
));
794 tab
.setAttribute('data-tooltip-style', 'error');
797 tab
.removeAttribute('data-errors');
798 tab
.removeAttribute('data-tooltip');
803 switchTab: function(ev
) {
804 var tab
= ev
.target
.parentNode
,
805 name
= tab
.getAttribute('data-tab'),
806 menu
= tab
.parentNode
,
807 group
= menu
.nextElementSibling
,
808 groupId
= +group
.getAttribute('data-tab-group'),
813 if (!tab
.classList
.contains('cbi-tab-disabled'))
816 menu
.querySelectorAll('[data-tab]').forEach(function(tab
) {
817 tab
.classList
.remove('cbi-tab');
818 tab
.classList
.remove('cbi-tab-disabled');
820 tab
.getAttribute('data-tab') === name
? 'cbi-tab' : 'cbi-tab-disabled');
823 group
.childNodes
.forEach(function(pane
) {
824 if (L
.dom
.matches(pane
, '[data-tab]')) {
825 if (pane
.getAttribute('data-tab') === name
) {
826 pane
.setAttribute('data-tab-active', 'true');
827 L
.tabs
.setActiveTabId(groupId
, index
);
830 pane
.setAttribute('data-tab-active', 'false');
839 /* DOM manipulation */
840 LuCI
.prototype.dom
= {
842 return (typeof(e
) === 'object' && e
!== null && 'nodeType' in e
);
849 domParser
= domParser
|| new DOMParser();
850 elem
= domParser
.parseFromString(s
, 'text/html').body
.firstChild
;
856 dummyElem
= dummyElem
|| document
.createElement('div');
857 dummyElem
.innerHTML
= s
;
858 elem
= dummyElem
.firstChild
;
866 matches: function(node
, selector
) {
867 var m
= this.elem(node
) ? node
.matches
|| node
.msMatchesSelector
: null;
868 return m
? m
.call(node
, selector
) : false;
871 parent: function(node
, selector
) {
872 if (this.elem(node
) && node
.closest
)
873 return node
.closest(selector
);
875 while (this.elem(node
))
876 if (this.matches(node
, selector
))
879 node
= node
.parentNode
;
884 append: function(node
, children
) {
885 if (!this.elem(node
))
888 if (Array
.isArray(children
)) {
889 for (var i
= 0; i
< children
.length
; i
++)
890 if (this.elem(children
[i
]))
891 node
.appendChild(children
[i
]);
892 else if (children
!== null && children
!== undefined)
893 node
.appendChild(document
.createTextNode('' + children
[i
]));
895 return node
.lastChild
;
897 else if (typeof(children
) === 'function') {
898 return this.append(node
, children(node
));
900 else if (this.elem(children
)) {
901 return node
.appendChild(children
);
903 else if (children
!== null && children
!== undefined) {
904 node
.innerHTML
= '' + children
;
905 return node
.lastChild
;
911 content: function(node
, children
) {
912 if (!this.elem(node
))
915 while (node
.firstChild
)
916 node
.removeChild(node
.firstChild
);
918 return this.append(node
, children
);
921 attr: function(node
, key
, val
) {
922 if (!this.elem(node
))
927 if (typeof(key
) === 'object' && key
!== null)
929 else if (typeof(key
) === 'string')
930 attr
= {}, attr
[key
] = val
;
933 if (!attr
.hasOwnProperty(key
) || attr
[key
] === null || attr
[key
] === undefined)
936 switch (typeof(attr
[key
])) {
938 node
.addEventListener(key
, attr
[key
]);
942 node
.setAttribute(key
, JSON
.stringify(attr
[key
]));
946 node
.setAttribute(key
, attr
[key
]);
952 var html
= arguments
[0],
953 attr
= (arguments
[1] instanceof Object
&& !Array
.isArray(arguments
[1])) ? arguments
[1] : null,
954 data
= attr
? arguments
[2] : arguments
[1],
959 else if (html
.charCodeAt(0) === 60)
960 elem
= this.parse(html
);
962 elem
= document
.createElement(html
);
967 this.attr(elem
, attr
);
968 this.append(elem
, data
);
975 __name__
: 'LuCI.XHR',
976 __init__: function() {
977 if (window
.console
&& console
.debug
)
978 console
.debug('Direct use XHR() is deprecated, please use L.Request instead');
981 _response: function(cb
, res
, json
, duration
) {
983 cb(res
, json
, duration
);
987 get: function(url
, data
, callback
, timeout
) {
989 L
.get(url
, data
, this._response
.bind(this, callback
), timeout
);
992 post: function(url
, data
, callback
, timeout
) {
994 L
.post(url
, data
, this._response
.bind(this, callback
), timeout
);
997 cancel: function() { delete this.active
},
998 busy: function() { return (this.active
=== true) },
999 abort: function() {},
1000 send_form: function() { throw 'Not implemented' },
1003 XHR
.get = function() { return window
.L
.get.apply(window
.L
, arguments
) };
1004 XHR
.post = function() { return window
.L
.post
.apply(window
.L
, arguments
) };
1005 XHR
.poll = function() { return window
.L
.poll
.apply(window
.L
, arguments
) };
1006 XHR
.stop
= Request
.poll
.remove
.bind(Request
.poll
);
1007 XHR
.halt
= Request
.poll
.stop
.bind(Request
.poll
);
1008 XHR
.run
= Request
.poll
.start
.bind(Request
.poll
);
1009 XHR
.running
= Request
.poll
.active
.bind(Request
.poll
);
1013 })(window
, document
);