5 <title>Source: ui.js
</title>
8 <script src=
"scripts/prettify/prettify.js"></script>
9 <script src=
"scripts/prettify/lang-css.js"></script>
10 <script src=
"scripts/jquery.min.js"></script>
12 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
14 <link type=
"text/css" rel=
"stylesheet" href=
"styles/prettify-tomorrow.css">
15 <link type=
"text/css" rel=
"stylesheet" href=
"styles/bootstrap.min.css">
16 <link type=
"text/css" rel=
"stylesheet" href=
"styles/jaguar.css">
20 var config = {
"monospaceLinks":true,
"cleverLinks":true,
"default":{
"outputSourceFiles":true}};
27 <div id=
"wrap" class=
"clearfix">
29 <div class=
"navigation">
30 <h3 class=
"applicationName"><a href=
"index.html"></a></h3>
33 <input id=
"search" type=
"text" class=
"form-control input-sm" placeholder=
"Search Documentations">
37 <li class=
"item" data-name=
"LuCI">
39 <a href=
"LuCI.html">LuCI
</a>
42 <ul class=
"members itemMembers">
44 <span class=
"subtitle">Members
</span>
46 <li data-name=
"LuCI#Class"><a href=
"LuCI.html#Class">Class
</a></li>
48 <li data-name=
"LuCI#dom"><a href=
"LuCI.html#dom">dom
</a></li>
50 <li data-name=
"LuCI#env"><a href=
"LuCI.html#env">env
</a></li>
52 <li data-name=
"LuCI#Poll"><a href=
"LuCI.html#Poll">Poll
</a></li>
54 <li data-name=
"LuCI#Request"><a href=
"LuCI.html#Request">Request
</a></li>
56 <li data-name=
"LuCI#view"><a href=
"LuCI.html#view">view
</a></li>
59 <ul class=
"typedefs itemMembers">
61 <span class=
"subtitle">Typedefs
</span>
63 <li data-name=
"LuCI.requestCallbackFn"><a href=
"LuCI.html#.requestCallbackFn">requestCallbackFn
</a></li>
66 <ul class=
"typedefs itemMembers">
69 <ul class=
"methods itemMembers">
71 <span class=
"subtitle">Methods
</span>
73 <li data-name=
"LuCI#bind"><a href=
"LuCI.html#bind">bind
</a></li>
75 <li data-name=
"LuCI#error"><a href=
"LuCI.html#error">error
</a></li>
77 <li data-name=
"LuCI#get"><a href=
"LuCI.html#get">get
</a></li>
79 <li data-name=
"LuCI#halt"><a href=
"LuCI.html#halt">halt
</a></li>
81 <li data-name=
"LuCI#hasSystemFeature"><a href=
"LuCI.html#hasSystemFeature">hasSystemFeature
</a></li>
83 <li data-name=
"LuCI#isObject"><a href=
"LuCI.html#isObject">isObject
</a></li>
85 <li data-name=
"LuCI#location"><a href=
"LuCI.html#location">location
</a></li>
87 <li data-name=
"LuCI#media"><a href=
"LuCI.html#media">media
</a></li>
89 <li data-name=
"LuCI#path"><a href=
"LuCI.html#path">path
</a></li>
91 <li data-name=
"LuCI#poll"><a href=
"LuCI.html#poll">poll
</a></li>
93 <li data-name=
"LuCI#post"><a href=
"LuCI.html#post">post
</a></li>
95 <li data-name=
"LuCI#raise"><a href=
"LuCI.html#raise">raise
</a></li>
97 <li data-name=
"LuCI#require"><a href=
"LuCI.html#require">require
</a></li>
99 <li data-name=
"LuCI#resolveDefault"><a href=
"LuCI.html#resolveDefault">resolveDefault
</a></li>
101 <li data-name=
"LuCI#resource"><a href=
"LuCI.html#resource">resource
</a></li>
103 <li data-name=
"LuCI#run"><a href=
"LuCI.html#run">run
</a></li>
105 <li data-name=
"LuCI#sortedKeys"><a href=
"LuCI.html#sortedKeys">sortedKeys
</a></li>
107 <li data-name=
"LuCI#stop"><a href=
"LuCI.html#stop">stop
</a></li>
109 <li data-name=
"LuCI#toArray"><a href=
"LuCI.html#toArray">toArray
</a></li>
111 <li data-name=
"LuCI#url"><a href=
"LuCI.html#url">url
</a></li>
114 <ul class=
"events itemMembers">
119 <li class=
"item" data-name=
"LuCI.baseclass">
121 <a href=
"LuCI.baseclass.html">LuCI.baseclass
</a>
124 <ul class=
"members itemMembers">
127 <ul class=
"typedefs itemMembers">
130 <ul class=
"typedefs itemMembers">
133 <ul class=
"methods itemMembers">
135 <span class=
"subtitle">Methods
</span>
137 <li data-name=
"LuCI.baseclass.extend"><a href=
"LuCI.baseclass.html#.extend">extend
</a></li>
139 <li data-name=
"LuCI.baseclass.instantiate"><a href=
"LuCI.baseclass.html#.instantiate">instantiate
</a></li>
141 <li data-name=
"LuCI.baseclass.isSubclass"><a href=
"LuCI.baseclass.html#.isSubclass">isSubclass
</a></li>
143 <li data-name=
"LuCI.baseclass.singleton"><a href=
"LuCI.baseclass.html#.singleton">singleton
</a></li>
145 <li data-name=
"LuCI.baseclass#super"><a href=
"LuCI.baseclass.html#super">super
</a></li>
147 <li data-name=
"LuCI.baseclass#varargs"><a href=
"LuCI.baseclass.html#varargs">varargs
</a></li>
150 <ul class=
"events itemMembers">
155 <li class=
"item" data-name=
"LuCI.dom">
157 <a href=
"LuCI.dom.html">LuCI.dom
</a>
160 <ul class=
"members itemMembers">
163 <ul class=
"typedefs itemMembers">
165 <span class=
"subtitle">Typedefs
</span>
167 <li data-name=
"LuCI.dom~ignoreCallbackFn"><a href=
"LuCI.dom.html#~ignoreCallbackFn">ignoreCallbackFn
</a></li>
170 <ul class=
"typedefs itemMembers">
173 <ul class=
"methods itemMembers">
175 <span class=
"subtitle">Methods
</span>
177 <li data-name=
"LuCI.dom#append"><a href=
"LuCI.dom.html#append">append
</a></li>
179 <li data-name=
"LuCI.dom#attr"><a href=
"LuCI.dom.html#attr">attr
</a></li>
181 <li data-name=
"LuCI.dom#bindClassInstance"><a href=
"LuCI.dom.html#bindClassInstance">bindClassInstance
</a></li>
183 <li data-name=
"LuCI.dom#callClassMethod"><a href=
"LuCI.dom.html#callClassMethod">callClassMethod
</a></li>
185 <li data-name=
"LuCI.dom#content"><a href=
"LuCI.dom.html#content">content
</a></li>
187 <li data-name=
"LuCI.dom#create"><a href=
"LuCI.dom.html#create">create
</a></li>
189 <li data-name=
"LuCI.dom#data"><a href=
"LuCI.dom.html#data">data
</a></li>
191 <li data-name=
"LuCI.dom#elem"><a href=
"LuCI.dom.html#elem">elem
</a></li>
193 <li data-name=
"LuCI.dom#findClassInstance"><a href=
"LuCI.dom.html#findClassInstance">findClassInstance
</a></li>
195 <li data-name=
"LuCI.dom#isEmpty"><a href=
"LuCI.dom.html#isEmpty">isEmpty
</a></li>
197 <li data-name=
"LuCI.dom#matches"><a href=
"LuCI.dom.html#matches">matches
</a></li>
199 <li data-name=
"LuCI.dom#parent"><a href=
"LuCI.dom.html#parent">parent
</a></li>
201 <li data-name=
"LuCI.dom#parse"><a href=
"LuCI.dom.html#parse">parse
</a></li>
204 <ul class=
"events itemMembers">
209 <li class=
"item" data-name=
"LuCI.fs">
211 <a href=
"LuCI.fs.html">LuCI.fs
</a>
214 <ul class=
"members itemMembers">
217 <ul class=
"typedefs itemMembers">
219 <span class=
"subtitle">Typedefs
</span>
221 <li data-name=
"LuCI.fs.FileExecResult"><a href=
"LuCI.fs.html#.FileExecResult">FileExecResult
</a></li>
223 <li data-name=
"LuCI.fs.FileStatEntry"><a href=
"LuCI.fs.html#.FileStatEntry">FileStatEntry
</a></li>
226 <ul class=
"typedefs itemMembers">
229 <ul class=
"methods itemMembers">
231 <span class=
"subtitle">Methods
</span>
233 <li data-name=
"LuCI.fs#exec"><a href=
"LuCI.fs.html#exec">exec
</a></li>
235 <li data-name=
"LuCI.fs#exec_direct"><a href=
"LuCI.fs.html#exec_direct">exec_direct
</a></li>
237 <li data-name=
"LuCI.fs#lines"><a href=
"LuCI.fs.html#lines">lines
</a></li>
239 <li data-name=
"LuCI.fs#list"><a href=
"LuCI.fs.html#list">list
</a></li>
241 <li data-name=
"LuCI.fs#read"><a href=
"LuCI.fs.html#read">read
</a></li>
243 <li data-name=
"LuCI.fs#read_direct"><a href=
"LuCI.fs.html#read_direct">read_direct
</a></li>
245 <li data-name=
"LuCI.fs#remove"><a href=
"LuCI.fs.html#remove">remove
</a></li>
247 <li data-name=
"LuCI.fs#stat"><a href=
"LuCI.fs.html#stat">stat
</a></li>
249 <li data-name=
"LuCI.fs#trimmed"><a href=
"LuCI.fs.html#trimmed">trimmed
</a></li>
251 <li data-name=
"LuCI.fs#write"><a href=
"LuCI.fs.html#write">write
</a></li>
254 <ul class=
"events itemMembers">
259 <li class=
"item" data-name=
"LuCI.headers">
261 <a href=
"LuCI.headers.html">LuCI.headers
</a>
264 <ul class=
"members itemMembers">
267 <ul class=
"typedefs itemMembers">
270 <ul class=
"typedefs itemMembers">
273 <ul class=
"methods itemMembers">
275 <span class=
"subtitle">Methods
</span>
277 <li data-name=
"LuCI.headers#get"><a href=
"LuCI.headers.html#get">get
</a></li>
279 <li data-name=
"LuCI.headers#has"><a href=
"LuCI.headers.html#has">has
</a></li>
282 <ul class=
"events itemMembers">
287 <li class=
"item" data-name=
"LuCI.network">
289 <a href=
"LuCI.network.html">LuCI.network
</a>
292 <ul class=
"members itemMembers">
295 <ul class=
"typedefs itemMembers">
297 <span class=
"subtitle">Typedefs
</span>
299 <li data-name=
"LuCI.network.SwitchTopology"><a href=
"LuCI.network.html#.SwitchTopology">SwitchTopology
</a></li>
301 <li data-name=
"LuCI.network.WifiEncryption"><a href=
"LuCI.network.html#.WifiEncryption">WifiEncryption
</a></li>
303 <li data-name=
"LuCI.network.WifiPeerEntry"><a href=
"LuCI.network.html#.WifiPeerEntry">WifiPeerEntry
</a></li>
305 <li data-name=
"LuCI.network.WifiRateEntry"><a href=
"LuCI.network.html#.WifiRateEntry">WifiRateEntry
</a></li>
307 <li data-name=
"LuCI.network.WifiScanResult"><a href=
"LuCI.network.html#.WifiScanResult">WifiScanResult
</a></li>
310 <ul class=
"typedefs itemMembers">
313 <ul class=
"methods itemMembers">
315 <span class=
"subtitle">Methods
</span>
317 <li data-name=
"LuCI.network#addNetwork"><a href=
"LuCI.network.html#addNetwork">addNetwork
</a></li>
319 <li data-name=
"LuCI.network#addWifiNetwork"><a href=
"LuCI.network.html#addWifiNetwork">addWifiNetwork
</a></li>
321 <li data-name=
"LuCI.network#deleteNetwork"><a href=
"LuCI.network.html#deleteNetwork">deleteNetwork
</a></li>
323 <li data-name=
"LuCI.network#deleteWifiNetwork"><a href=
"LuCI.network.html#deleteWifiNetwork">deleteWifiNetwork
</a></li>
325 <li data-name=
"LuCI.network#flushCache"><a href=
"LuCI.network.html#flushCache">flushCache
</a></li>
327 <li data-name=
"LuCI.network#formatWifiEncryption"><a href=
"LuCI.network.html#formatWifiEncryption">formatWifiEncryption
</a></li>
329 <li data-name=
"LuCI.network#getDevice"><a href=
"LuCI.network.html#getDevice">getDevice
</a></li>
331 <li data-name=
"LuCI.network#getDevices"><a href=
"LuCI.network.html#getDevices">getDevices
</a></li>
333 <li data-name=
"LuCI.network#getDSLModemType"><a href=
"LuCI.network.html#getDSLModemType">getDSLModemType
</a></li>
335 <li data-name=
"LuCI.network#getHostHints"><a href=
"LuCI.network.html#getHostHints">getHostHints
</a></li>
337 <li data-name=
"LuCI.network#getIfnameOf"><a href=
"LuCI.network.html#getIfnameOf">getIfnameOf
</a></li>
339 <li data-name=
"LuCI.network#getNetwork"><a href=
"LuCI.network.html#getNetwork">getNetwork
</a></li>
341 <li data-name=
"LuCI.network#getNetworks"><a href=
"LuCI.network.html#getNetworks">getNetworks
</a></li>
343 <li data-name=
"LuCI.network#getProtocol"><a href=
"LuCI.network.html#getProtocol">getProtocol
</a></li>
345 <li data-name=
"LuCI.network#getProtocols"><a href=
"LuCI.network.html#getProtocols">getProtocols
</a></li>
347 <li data-name=
"LuCI.network#getSwitchTopologies"><a href=
"LuCI.network.html#getSwitchTopologies">getSwitchTopologies
</a></li>
349 <li data-name=
"LuCI.network#getWAN6Networks"><a href=
"LuCI.network.html#getWAN6Networks">getWAN6Networks
</a></li>
351 <li data-name=
"LuCI.network#getWANNetworks"><a href=
"LuCI.network.html#getWANNetworks">getWANNetworks
</a></li>
353 <li data-name=
"LuCI.network#getWifiDevice"><a href=
"LuCI.network.html#getWifiDevice">getWifiDevice
</a></li>
355 <li data-name=
"LuCI.network#getWifiDevices"><a href=
"LuCI.network.html#getWifiDevices">getWifiDevices
</a></li>
357 <li data-name=
"LuCI.network#getWifiNetwork"><a href=
"LuCI.network.html#getWifiNetwork">getWifiNetwork
</a></li>
359 <li data-name=
"LuCI.network#getWifiNetworks"><a href=
"LuCI.network.html#getWifiNetworks">getWifiNetworks
</a></li>
361 <li data-name=
"LuCI.network#isIgnoredDevice"><a href=
"LuCI.network.html#isIgnoredDevice">isIgnoredDevice
</a></li>
363 <li data-name=
"LuCI.network#maskToPrefix"><a href=
"LuCI.network.html#maskToPrefix">maskToPrefix
</a></li>
365 <li data-name=
"LuCI.network#prefixToMask"><a href=
"LuCI.network.html#prefixToMask">prefixToMask
</a></li>
367 <li data-name=
"LuCI.network#registerErrorCode"><a href=
"LuCI.network.html#registerErrorCode">registerErrorCode
</a></li>
369 <li data-name=
"LuCI.network#registerPatternVirtual"><a href=
"LuCI.network.html#registerPatternVirtual">registerPatternVirtual
</a></li>
371 <li data-name=
"LuCI.network#registerProtocol"><a href=
"LuCI.network.html#registerProtocol">registerProtocol
</a></li>
373 <li data-name=
"LuCI.network#renameNetwork"><a href=
"LuCI.network.html#renameNetwork">renameNetwork
</a></li>
376 <ul class=
"events itemMembers">
381 <li class=
"item" data-name=
"LuCI.network.Device">
383 <a href=
"LuCI.network.Device.html">LuCI.network.Device
</a>
386 <ul class=
"members itemMembers">
389 <ul class=
"typedefs itemMembers">
392 <ul class=
"typedefs itemMembers">
395 <ul class=
"methods itemMembers">
397 <span class=
"subtitle">Methods
</span>
399 <li data-name=
"LuCI.network.Device#getBridgeID"><a href=
"LuCI.network.Device.html#getBridgeID">getBridgeID
</a></li>
401 <li data-name=
"LuCI.network.Device#getBridgeSTP"><a href=
"LuCI.network.Device.html#getBridgeSTP">getBridgeSTP
</a></li>
403 <li data-name=
"LuCI.network.Device#getI18n"><a href=
"LuCI.network.Device.html#getI18n">getI18n
</a></li>
405 <li data-name=
"LuCI.network.Device#getIP6Addrs"><a href=
"LuCI.network.Device.html#getIP6Addrs">getIP6Addrs
</a></li>
407 <li data-name=
"LuCI.network.Device#getIPAddrs"><a href=
"LuCI.network.Device.html#getIPAddrs">getIPAddrs
</a></li>
409 <li data-name=
"LuCI.network.Device#getMAC"><a href=
"LuCI.network.Device.html#getMAC">getMAC
</a></li>
411 <li data-name=
"LuCI.network.Device#getMTU"><a href=
"LuCI.network.Device.html#getMTU">getMTU
</a></li>
413 <li data-name=
"LuCI.network.Device#getName"><a href=
"LuCI.network.Device.html#getName">getName
</a></li>
415 <li data-name=
"LuCI.network.Device#getNetwork"><a href=
"LuCI.network.Device.html#getNetwork">getNetwork
</a></li>
417 <li data-name=
"LuCI.network.Device#getNetworks"><a href=
"LuCI.network.Device.html#getNetworks">getNetworks
</a></li>
419 <li data-name=
"LuCI.network.Device#getPorts"><a href=
"LuCI.network.Device.html#getPorts">getPorts
</a></li>
421 <li data-name=
"LuCI.network.Device#getRXBytes"><a href=
"LuCI.network.Device.html#getRXBytes">getRXBytes
</a></li>
423 <li data-name=
"LuCI.network.Device#getRXPackets"><a href=
"LuCI.network.Device.html#getRXPackets">getRXPackets
</a></li>
425 <li data-name=
"LuCI.network.Device#getShortName"><a href=
"LuCI.network.Device.html#getShortName">getShortName
</a></li>
427 <li data-name=
"LuCI.network.Device#getTXBytes"><a href=
"LuCI.network.Device.html#getTXBytes">getTXBytes
</a></li>
429 <li data-name=
"LuCI.network.Device#getTXPackets"><a href=
"LuCI.network.Device.html#getTXPackets">getTXPackets
</a></li>
431 <li data-name=
"LuCI.network.Device#getType"><a href=
"LuCI.network.Device.html#getType">getType
</a></li>
433 <li data-name=
"LuCI.network.Device#getTypeI18n"><a href=
"LuCI.network.Device.html#getTypeI18n">getTypeI18n
</a></li>
435 <li data-name=
"LuCI.network.Device#getWifiNetwork"><a href=
"LuCI.network.Device.html#getWifiNetwork">getWifiNetwork
</a></li>
437 <li data-name=
"LuCI.network.Device#isBridge"><a href=
"LuCI.network.Device.html#isBridge">isBridge
</a></li>
439 <li data-name=
"LuCI.network.Device#isBridgePort"><a href=
"LuCI.network.Device.html#isBridgePort">isBridgePort
</a></li>
441 <li data-name=
"LuCI.network.Device#isUp"><a href=
"LuCI.network.Device.html#isUp">isUp
</a></li>
444 <ul class=
"events itemMembers">
449 <li class=
"item" data-name=
"LuCI.network.Hosts">
451 <a href=
"LuCI.network.Hosts.html">LuCI.network.Hosts
</a>
454 <ul class=
"members itemMembers">
457 <ul class=
"typedefs itemMembers">
460 <ul class=
"typedefs itemMembers">
463 <ul class=
"methods itemMembers">
465 <span class=
"subtitle">Methods
</span>
467 <li data-name=
"LuCI.network.Hosts#getHostnameByIP6Addr"><a href=
"LuCI.network.Hosts.html#getHostnameByIP6Addr">getHostnameByIP6Addr
</a></li>
469 <li data-name=
"LuCI.network.Hosts#getHostnameByIPAddr"><a href=
"LuCI.network.Hosts.html#getHostnameByIPAddr">getHostnameByIPAddr
</a></li>
471 <li data-name=
"LuCI.network.Hosts#getHostnameByMACAddr"><a href=
"LuCI.network.Hosts.html#getHostnameByMACAddr">getHostnameByMACAddr
</a></li>
473 <li data-name=
"LuCI.network.Hosts#getIP6AddrByMACAddr"><a href=
"LuCI.network.Hosts.html#getIP6AddrByMACAddr">getIP6AddrByMACAddr
</a></li>
475 <li data-name=
"LuCI.network.Hosts#getIPAddrByMACAddr"><a href=
"LuCI.network.Hosts.html#getIPAddrByMACAddr">getIPAddrByMACAddr
</a></li>
477 <li data-name=
"LuCI.network.Hosts#getMACAddrByIP6Addr"><a href=
"LuCI.network.Hosts.html#getMACAddrByIP6Addr">getMACAddrByIP6Addr
</a></li>
479 <li data-name=
"LuCI.network.Hosts#getMACAddrByIPAddr"><a href=
"LuCI.network.Hosts.html#getMACAddrByIPAddr">getMACAddrByIPAddr
</a></li>
481 <li data-name=
"LuCI.network.Hosts#getMACHints"><a href=
"LuCI.network.Hosts.html#getMACHints">getMACHints
</a></li>
484 <ul class=
"events itemMembers">
489 <li class=
"item" data-name=
"LuCI.network.Protocol">
491 <a href=
"LuCI.network.Protocol.html">LuCI.network.Protocol
</a>
494 <ul class=
"members itemMembers">
497 <ul class=
"typedefs itemMembers">
500 <ul class=
"typedefs itemMembers">
503 <ul class=
"methods itemMembers">
505 <span class=
"subtitle">Methods
</span>
507 <li data-name=
"LuCI.network.Protocol#addDevice"><a href=
"LuCI.network.Protocol.html#addDevice">addDevice
</a></li>
509 <li data-name=
"LuCI.network.Protocol#containsDevice"><a href=
"LuCI.network.Protocol.html#containsDevice">containsDevice
</a></li>
511 <li data-name=
"LuCI.network.Protocol#deleteConfiguration"><a href=
"LuCI.network.Protocol.html#deleteConfiguration">deleteConfiguration
</a></li>
513 <li data-name=
"LuCI.network.Protocol#deleteDevice"><a href=
"LuCI.network.Protocol.html#deleteDevice">deleteDevice
</a></li>
515 <li data-name=
"LuCI.network.Protocol#get"><a href=
"LuCI.network.Protocol.html#get">get
</a></li>
517 <li data-name=
"LuCI.network.Protocol#getDevice"><a href=
"LuCI.network.Protocol.html#getDevice">getDevice
</a></li>
519 <li data-name=
"LuCI.network.Protocol#getDevices"><a href=
"LuCI.network.Protocol.html#getDevices">getDevices
</a></li>
521 <li data-name=
"LuCI.network.Protocol#getDNS6Addrs"><a href=
"LuCI.network.Protocol.html#getDNS6Addrs">getDNS6Addrs
</a></li>
523 <li data-name=
"LuCI.network.Protocol#getDNSAddrs"><a href=
"LuCI.network.Protocol.html#getDNSAddrs">getDNSAddrs
</a></li>
525 <li data-name=
"LuCI.network.Protocol#getErrors"><a href=
"LuCI.network.Protocol.html#getErrors">getErrors
</a></li>
527 <li data-name=
"LuCI.network.Protocol#getExpiry"><a href=
"LuCI.network.Protocol.html#getExpiry">getExpiry
</a></li>
529 <li data-name=
"LuCI.network.Protocol#getGateway6Addr"><a href=
"LuCI.network.Protocol.html#getGateway6Addr">getGateway6Addr
</a></li>
531 <li data-name=
"LuCI.network.Protocol#getGatewayAddr"><a href=
"LuCI.network.Protocol.html#getGatewayAddr">getGatewayAddr
</a></li>
533 <li data-name=
"LuCI.network.Protocol#getI18n"><a href=
"LuCI.network.Protocol.html#getI18n">getI18n
</a></li>
535 <li data-name=
"LuCI.network.Protocol#getIfname"><a href=
"LuCI.network.Protocol.html#getIfname">getIfname
</a></li>
537 <li data-name=
"LuCI.network.Protocol#getIP6Addr"><a href=
"LuCI.network.Protocol.html#getIP6Addr">getIP6Addr
</a></li>
539 <li data-name=
"LuCI.network.Protocol#getIP6Addrs"><a href=
"LuCI.network.Protocol.html#getIP6Addrs">getIP6Addrs
</a></li>
541 <li data-name=
"LuCI.network.Protocol#getIP6Prefix"><a href=
"LuCI.network.Protocol.html#getIP6Prefix">getIP6Prefix
</a></li>
543 <li data-name=
"LuCI.network.Protocol#getIPAddr"><a href=
"LuCI.network.Protocol.html#getIPAddr">getIPAddr
</a></li>
545 <li data-name=
"LuCI.network.Protocol#getIPAddrs"><a href=
"LuCI.network.Protocol.html#getIPAddrs">getIPAddrs
</a></li>
547 <li data-name=
"LuCI.network.Protocol#getL2Device"><a href=
"LuCI.network.Protocol.html#getL2Device">getL2Device
</a></li>
549 <li data-name=
"LuCI.network.Protocol#getL3Device"><a href=
"LuCI.network.Protocol.html#getL3Device">getL3Device
</a></li>
551 <li data-name=
"LuCI.network.Protocol#getMetric"><a href=
"LuCI.network.Protocol.html#getMetric">getMetric
</a></li>
553 <li data-name=
"LuCI.network.Protocol#getName"><a href=
"LuCI.network.Protocol.html#getName">getName
</a></li>
555 <li data-name=
"LuCI.network.Protocol#getNetmask"><a href=
"LuCI.network.Protocol.html#getNetmask">getNetmask
</a></li>
557 <li data-name=
"LuCI.network.Protocol#getOpkgPackage"><a href=
"LuCI.network.Protocol.html#getOpkgPackage">getOpkgPackage
</a></li>
559 <li data-name=
"LuCI.network.Protocol#getProtocol"><a href=
"LuCI.network.Protocol.html#getProtocol">getProtocol
</a></li>
561 <li data-name=
"LuCI.network.Protocol#getType"><a href=
"LuCI.network.Protocol.html#getType">getType
</a></li>
563 <li data-name=
"LuCI.network.Protocol#getUptime"><a href=
"LuCI.network.Protocol.html#getUptime">getUptime
</a></li>
565 <li data-name=
"LuCI.network.Protocol#getZoneName"><a href=
"LuCI.network.Protocol.html#getZoneName">getZoneName
</a></li>
567 <li data-name=
"LuCI.network.Protocol#isAlias"><a href=
"LuCI.network.Protocol.html#isAlias">isAlias
</a></li>
569 <li data-name=
"LuCI.network.Protocol#isBridge"><a href=
"LuCI.network.Protocol.html#isBridge">isBridge
</a></li>
571 <li data-name=
"LuCI.network.Protocol#isDynamic"><a href=
"LuCI.network.Protocol.html#isDynamic">isDynamic
</a></li>
573 <li data-name=
"LuCI.network.Protocol#isEmpty"><a href=
"LuCI.network.Protocol.html#isEmpty">isEmpty
</a></li>
575 <li data-name=
"LuCI.network.Protocol#isFloating"><a href=
"LuCI.network.Protocol.html#isFloating">isFloating
</a></li>
577 <li data-name=
"LuCI.network.Protocol#isInstalled"><a href=
"LuCI.network.Protocol.html#isInstalled">isInstalled
</a></li>
579 <li data-name=
"LuCI.network.Protocol#isUp"><a href=
"LuCI.network.Protocol.html#isUp">isUp
</a></li>
581 <li data-name=
"LuCI.network.Protocol#isVirtual"><a href=
"LuCI.network.Protocol.html#isVirtual">isVirtual
</a></li>
583 <li data-name=
"LuCI.network.Protocol#set"><a href=
"LuCI.network.Protocol.html#set">set
</a></li>
586 <ul class=
"events itemMembers">
591 <li class=
"item" data-name=
"LuCI.network.WifiDevice">
593 <a href=
"LuCI.network.WifiDevice.html">LuCI.network.WifiDevice
</a>
596 <ul class=
"members itemMembers">
599 <ul class=
"typedefs itemMembers">
602 <ul class=
"typedefs itemMembers">
605 <ul class=
"methods itemMembers">
607 <span class=
"subtitle">Methods
</span>
609 <li data-name=
"LuCI.network.WifiDevice#addWifiNetwork"><a href=
"LuCI.network.WifiDevice.html#addWifiNetwork">addWifiNetwork
</a></li>
611 <li data-name=
"LuCI.network.WifiDevice#deleteWifiNetwork"><a href=
"LuCI.network.WifiDevice.html#deleteWifiNetwork">deleteWifiNetwork
</a></li>
613 <li data-name=
"LuCI.network.WifiDevice#get"><a href=
"LuCI.network.WifiDevice.html#get">get
</a></li>
615 <li data-name=
"LuCI.network.WifiDevice#getHTModes"><a href=
"LuCI.network.WifiDevice.html#getHTModes">getHTModes
</a></li>
617 <li data-name=
"LuCI.network.WifiDevice#getHWModes"><a href=
"LuCI.network.WifiDevice.html#getHWModes">getHWModes
</a></li>
619 <li data-name=
"LuCI.network.WifiDevice#getI18n"><a href=
"LuCI.network.WifiDevice.html#getI18n">getI18n
</a></li>
621 <li data-name=
"LuCI.network.WifiDevice#getName"><a href=
"LuCI.network.WifiDevice.html#getName">getName
</a></li>
623 <li data-name=
"LuCI.network.WifiDevice#getScanList"><a href=
"LuCI.network.WifiDevice.html#getScanList">getScanList
</a></li>
625 <li data-name=
"LuCI.network.WifiDevice#getWifiNetwork"><a href=
"LuCI.network.WifiDevice.html#getWifiNetwork">getWifiNetwork
</a></li>
627 <li data-name=
"LuCI.network.WifiDevice#getWifiNetworks"><a href=
"LuCI.network.WifiDevice.html#getWifiNetworks">getWifiNetworks
</a></li>
629 <li data-name=
"LuCI.network.WifiDevice#isDisabled"><a href=
"LuCI.network.WifiDevice.html#isDisabled">isDisabled
</a></li>
631 <li data-name=
"LuCI.network.WifiDevice#isUp"><a href=
"LuCI.network.WifiDevice.html#isUp">isUp
</a></li>
633 <li data-name=
"LuCI.network.WifiDevice#set"><a href=
"LuCI.network.WifiDevice.html#set">set
</a></li>
636 <ul class=
"events itemMembers">
641 <li class=
"item" data-name=
"LuCI.network.WifiNetwork">
643 <a href=
"LuCI.network.WifiNetwork.html">LuCI.network.WifiNetwork
</a>
646 <ul class=
"members itemMembers">
649 <ul class=
"typedefs itemMembers">
652 <ul class=
"typedefs itemMembers">
655 <ul class=
"methods itemMembers">
657 <span class=
"subtitle">Methods
</span>
659 <li data-name=
"LuCI.network.WifiNetwork#disconnectClient"><a href=
"LuCI.network.WifiNetwork.html#disconnectClient">disconnectClient
</a></li>
661 <li data-name=
"LuCI.network.WifiNetwork#get"><a href=
"LuCI.network.WifiNetwork.html#get">get
</a></li>
663 <li data-name=
"LuCI.network.WifiNetwork#getActiveBSSID"><a href=
"LuCI.network.WifiNetwork.html#getActiveBSSID">getActiveBSSID
</a></li>
665 <li data-name=
"LuCI.network.WifiNetwork#getActiveEncryption"><a href=
"LuCI.network.WifiNetwork.html#getActiveEncryption">getActiveEncryption
</a></li>
667 <li data-name=
"LuCI.network.WifiNetwork#getActiveMode"><a href=
"LuCI.network.WifiNetwork.html#getActiveMode">getActiveMode
</a></li>
669 <li data-name=
"LuCI.network.WifiNetwork#getActiveModeI18n"><a href=
"LuCI.network.WifiNetwork.html#getActiveModeI18n">getActiveModeI18n
</a></li>
671 <li data-name=
"LuCI.network.WifiNetwork#getActiveSSID"><a href=
"LuCI.network.WifiNetwork.html#getActiveSSID">getActiveSSID
</a></li>
673 <li data-name=
"LuCI.network.WifiNetwork#getAssocList"><a href=
"LuCI.network.WifiNetwork.html#getAssocList">getAssocList
</a></li>
675 <li data-name=
"LuCI.network.WifiNetwork#getBitRate"><a href=
"LuCI.network.WifiNetwork.html#getBitRate">getBitRate
</a></li>
677 <li data-name=
"LuCI.network.WifiNetwork#getBSSID"><a href=
"LuCI.network.WifiNetwork.html#getBSSID">getBSSID
</a></li>
679 <li data-name=
"LuCI.network.WifiNetwork#getChannel"><a href=
"LuCI.network.WifiNetwork.html#getChannel">getChannel
</a></li>
681 <li data-name=
"LuCI.network.WifiNetwork#getCountryCode"><a href=
"LuCI.network.WifiNetwork.html#getCountryCode">getCountryCode
</a></li>
683 <li data-name=
"LuCI.network.WifiNetwork#getDevice"><a href=
"LuCI.network.WifiNetwork.html#getDevice">getDevice
</a></li>
685 <li data-name=
"LuCI.network.WifiNetwork#getFrequency"><a href=
"LuCI.network.WifiNetwork.html#getFrequency">getFrequency
</a></li>
687 <li data-name=
"LuCI.network.WifiNetwork#getI18n"><a href=
"LuCI.network.WifiNetwork.html#getI18n">getI18n
</a></li>
689 <li data-name=
"LuCI.network.WifiNetwork#getID"><a href=
"LuCI.network.WifiNetwork.html#getID">getID
</a></li>
691 <li data-name=
"LuCI.network.WifiNetwork#getIfname"><a href=
"LuCI.network.WifiNetwork.html#getIfname">getIfname
</a></li>
693 <li data-name=
"LuCI.network.WifiNetwork#getMeshID"><a href=
"LuCI.network.WifiNetwork.html#getMeshID">getMeshID
</a></li>
695 <li data-name=
"LuCI.network.WifiNetwork#getMode"><a href=
"LuCI.network.WifiNetwork.html#getMode">getMode
</a></li>
697 <li data-name=
"LuCI.network.WifiNetwork#getName"><a href=
"LuCI.network.WifiNetwork.html#getName">getName
</a></li>
699 <li data-name=
"LuCI.network.WifiNetwork#getNetwork"><a href=
"LuCI.network.WifiNetwork.html#getNetwork">getNetwork
</a></li>
701 <li data-name=
"LuCI.network.WifiNetwork#getNetworkNames"><a href=
"LuCI.network.WifiNetwork.html#getNetworkNames">getNetworkNames
</a></li>
703 <li data-name=
"LuCI.network.WifiNetwork#getNetworks"><a href=
"LuCI.network.WifiNetwork.html#getNetworks">getNetworks
</a></li>
705 <li data-name=
"LuCI.network.WifiNetwork#getNoise"><a href=
"LuCI.network.WifiNetwork.html#getNoise">getNoise
</a></li>
707 <li data-name=
"LuCI.network.WifiNetwork#getShortName"><a href=
"LuCI.network.WifiNetwork.html#getShortName">getShortName
</a></li>
709 <li data-name=
"LuCI.network.WifiNetwork#getSignal"><a href=
"LuCI.network.WifiNetwork.html#getSignal">getSignal
</a></li>
711 <li data-name=
"LuCI.network.WifiNetwork#getSignalLevel"><a href=
"LuCI.network.WifiNetwork.html#getSignalLevel">getSignalLevel
</a></li>
713 <li data-name=
"LuCI.network.WifiNetwork#getSignalPercent"><a href=
"LuCI.network.WifiNetwork.html#getSignalPercent">getSignalPercent
</a></li>
715 <li data-name=
"LuCI.network.WifiNetwork#getSSID"><a href=
"LuCI.network.WifiNetwork.html#getSSID">getSSID
</a></li>
717 <li data-name=
"LuCI.network.WifiNetwork#getTXPower"><a href=
"LuCI.network.WifiNetwork.html#getTXPower">getTXPower
</a></li>
719 <li data-name=
"LuCI.network.WifiNetwork#getTXPowerOffset"><a href=
"LuCI.network.WifiNetwork.html#getTXPowerOffset">getTXPowerOffset
</a></li>
721 <li data-name=
"LuCI.network.WifiNetwork#getWifiDevice"><a href=
"LuCI.network.WifiNetwork.html#getWifiDevice">getWifiDevice
</a></li>
723 <li data-name=
"LuCI.network.WifiNetwork#getWifiDeviceName"><a href=
"LuCI.network.WifiNetwork.html#getWifiDeviceName">getWifiDeviceName
</a></li>
725 <li data-name=
"LuCI.network.WifiNetwork#isClientDisconnectSupported"><a href=
"LuCI.network.WifiNetwork.html#isClientDisconnectSupported">isClientDisconnectSupported
</a></li>
727 <li data-name=
"LuCI.network.WifiNetwork#isDisabled"><a href=
"LuCI.network.WifiNetwork.html#isDisabled">isDisabled
</a></li>
729 <li data-name=
"LuCI.network.WifiNetwork#isUp"><a href=
"LuCI.network.WifiNetwork.html#isUp">isUp
</a></li>
731 <li data-name=
"LuCI.network.WifiNetwork#set"><a href=
"LuCI.network.WifiNetwork.html#set">set
</a></li>
734 <ul class=
"events itemMembers">
739 <li class=
"item" data-name=
"LuCI.poll">
741 <a href=
"LuCI.poll.html">LuCI.poll
</a>
744 <ul class=
"members itemMembers">
747 <ul class=
"typedefs itemMembers">
750 <ul class=
"typedefs itemMembers">
753 <ul class=
"methods itemMembers">
755 <span class=
"subtitle">Methods
</span>
757 <li data-name=
"LuCI.poll#active"><a href=
"LuCI.poll.html#active">active
</a></li>
759 <li data-name=
"LuCI.poll#add"><a href=
"LuCI.poll.html#add">add
</a></li>
761 <li data-name=
"LuCI.poll#remove"><a href=
"LuCI.poll.html#remove">remove
</a></li>
763 <li data-name=
"LuCI.poll#start"><a href=
"LuCI.poll.html#start">start
</a></li>
765 <li data-name=
"LuCI.poll#stop"><a href=
"LuCI.poll.html#stop">stop
</a></li>
768 <ul class=
"events itemMembers">
773 <li class=
"item" data-name=
"LuCI.request">
775 <a href=
"LuCI.request.html">LuCI.request
</a>
778 <ul class=
"members itemMembers">
781 <ul class=
"typedefs itemMembers">
783 <span class=
"subtitle">Typedefs
</span>
785 <li data-name=
"LuCI.request.interceptorFn"><a href=
"LuCI.request.html#.interceptorFn">interceptorFn
</a></li>
787 <li data-name=
"LuCI.request.RequestOptions"><a href=
"LuCI.request.html#.RequestOptions">RequestOptions
</a></li>
790 <ul class=
"typedefs itemMembers">
793 <ul class=
"methods itemMembers">
795 <span class=
"subtitle">Methods
</span>
797 <li data-name=
"LuCI.request#addInterceptor"><a href=
"LuCI.request.html#addInterceptor">addInterceptor
</a></li>
799 <li data-name=
"LuCI.request#expandURL"><a href=
"LuCI.request.html#expandURL">expandURL
</a></li>
801 <li data-name=
"LuCI.request#get"><a href=
"LuCI.request.html#get">get
</a></li>
803 <li data-name=
"LuCI.request#post"><a href=
"LuCI.request.html#post">post
</a></li>
805 <li data-name=
"LuCI.request#removeInterceptor"><a href=
"LuCI.request.html#removeInterceptor">removeInterceptor
</a></li>
807 <li data-name=
"LuCI.request#request"><a href=
"LuCI.request.html#request">request
</a></li>
810 <ul class=
"events itemMembers">
815 <li class=
"item" data-name=
"LuCI.request.poll">
817 <a href=
"LuCI.request.poll.html">LuCI.request.poll
</a>
820 <ul class=
"members itemMembers">
823 <ul class=
"typedefs itemMembers">
825 <span class=
"subtitle">Typedefs
</span>
827 <li data-name=
"LuCI.request.poll~callbackFn"><a href=
"LuCI.request.poll.html#~callbackFn">callbackFn
</a></li>
830 <ul class=
"typedefs itemMembers">
833 <ul class=
"methods itemMembers">
835 <span class=
"subtitle">Methods
</span>
837 <li data-name=
"LuCI.request.poll#active"><a href=
"LuCI.request.poll.html#active">active
</a></li>
839 <li data-name=
"LuCI.request.poll#add"><a href=
"LuCI.request.poll.html#add">add
</a></li>
841 <li data-name=
"LuCI.request.poll#remove"><a href=
"LuCI.request.poll.html#remove">remove
</a></li>
843 <li data-name=
"LuCI.request.poll#start"><a href=
"LuCI.request.poll.html#start">start
</a></li>
845 <li data-name=
"LuCI.request.poll#stop"><a href=
"LuCI.request.poll.html#stop">stop
</a></li>
848 <ul class=
"events itemMembers">
853 <li class=
"item" data-name=
"LuCI.response">
855 <a href=
"LuCI.response.html">LuCI.response
</a>
858 <ul class=
"members itemMembers">
860 <span class=
"subtitle">Members
</span>
862 <li data-name=
"LuCI.response#duration"><a href=
"LuCI.response.html#duration">duration
</a></li>
864 <li data-name=
"LuCI.response#headers"><a href=
"LuCI.response.html#headers">headers
</a></li>
866 <li data-name=
"LuCI.response#ok"><a href=
"LuCI.response.html#ok">ok
</a></li>
868 <li data-name=
"LuCI.response#status"><a href=
"LuCI.response.html#status">status
</a></li>
870 <li data-name=
"LuCI.response#statusText"><a href=
"LuCI.response.html#statusText">statusText
</a></li>
872 <li data-name=
"LuCI.response#url"><a href=
"LuCI.response.html#url">url
</a></li>
875 <ul class=
"typedefs itemMembers">
878 <ul class=
"typedefs itemMembers">
881 <ul class=
"methods itemMembers">
883 <span class=
"subtitle">Methods
</span>
885 <li data-name=
"LuCI.response#blob"><a href=
"LuCI.response.html#blob">blob
</a></li>
887 <li data-name=
"LuCI.response#clone"><a href=
"LuCI.response.html#clone">clone
</a></li>
889 <li data-name=
"LuCI.response#json"><a href=
"LuCI.response.html#json">json
</a></li>
891 <li data-name=
"LuCI.response#text"><a href=
"LuCI.response.html#text">text
</a></li>
894 <ul class=
"events itemMembers">
899 <li class=
"item" data-name=
"LuCI.rpc">
901 <a href=
"LuCI.rpc.html">LuCI.rpc
</a>
904 <ul class=
"members itemMembers">
907 <ul class=
"typedefs itemMembers">
909 <span class=
"subtitle">Typedefs
</span>
911 <li data-name=
"LuCI.rpc.DeclareOptions"><a href=
"LuCI.rpc.html#.DeclareOptions">DeclareOptions
</a></li>
913 <li data-name=
"LuCI.rpc~filterFn"><a href=
"LuCI.rpc.html#~filterFn">filterFn
</a></li>
915 <li data-name=
"LuCI.rpc~interceptorFn"><a href=
"LuCI.rpc.html#~interceptorFn">interceptorFn
</a></li>
917 <li data-name=
"LuCI.rpc~invokeFn"><a href=
"LuCI.rpc.html#~invokeFn">invokeFn
</a></li>
920 <ul class=
"typedefs itemMembers">
923 <ul class=
"methods itemMembers">
925 <span class=
"subtitle">Methods
</span>
927 <li data-name=
"LuCI.rpc#addInterceptor"><a href=
"LuCI.rpc.html#addInterceptor">addInterceptor
</a></li>
929 <li data-name=
"LuCI.rpc#declare"><a href=
"LuCI.rpc.html#declare">declare
</a></li>
931 <li data-name=
"LuCI.rpc#getBaseURL"><a href=
"LuCI.rpc.html#getBaseURL">getBaseURL
</a></li>
933 <li data-name=
"LuCI.rpc#getSessionID"><a href=
"LuCI.rpc.html#getSessionID">getSessionID
</a></li>
935 <li data-name=
"LuCI.rpc#getStatusText"><a href=
"LuCI.rpc.html#getStatusText">getStatusText
</a></li>
937 <li data-name=
"LuCI.rpc#list"><a href=
"LuCI.rpc.html#list">list
</a></li>
939 <li data-name=
"LuCI.rpc#removeInterceptor"><a href=
"LuCI.rpc.html#removeInterceptor">removeInterceptor
</a></li>
941 <li data-name=
"LuCI.rpc#setBaseURL"><a href=
"LuCI.rpc.html#setBaseURL">setBaseURL
</a></li>
943 <li data-name=
"LuCI.rpc#setSessionID"><a href=
"LuCI.rpc.html#setSessionID">setSessionID
</a></li>
946 <ul class=
"events itemMembers">
951 <li class=
"item" data-name=
"LuCI.uci">
953 <a href=
"LuCI.uci.html">LuCI.uci
</a>
956 <ul class=
"members itemMembers">
959 <ul class=
"typedefs itemMembers">
961 <span class=
"subtitle">Typedefs
</span>
963 <li data-name=
"LuCI.uci.ChangeRecord"><a href=
"LuCI.uci.html#.ChangeRecord">ChangeRecord
</a></li>
965 <li data-name=
"LuCI.uci.SectionObject"><a href=
"LuCI.uci.html#.SectionObject">SectionObject
</a></li>
967 <li data-name=
"LuCI.uci~sectionsFn"><a href=
"LuCI.uci.html#~sectionsFn">sectionsFn
</a></li>
970 <ul class=
"typedefs itemMembers">
973 <ul class=
"methods itemMembers">
975 <span class=
"subtitle">Methods
</span>
977 <li data-name=
"LuCI.uci#add"><a href=
"LuCI.uci.html#add">add
</a></li>
979 <li data-name=
"LuCI.uci#apply"><a href=
"LuCI.uci.html#apply">apply
</a></li>
981 <li data-name=
"LuCI.uci#changes"><a href=
"LuCI.uci.html#changes">changes
</a></li>
983 <li data-name=
"LuCI.uci#createSID"><a href=
"LuCI.uci.html#createSID">createSID
</a></li>
985 <li data-name=
"LuCI.uci#get"><a href=
"LuCI.uci.html#get">get
</a></li>
987 <li data-name=
"LuCI.uci#get_first"><a href=
"LuCI.uci.html#get_first">get_first
</a></li>
989 <li data-name=
"LuCI.uci#load"><a href=
"LuCI.uci.html#load">load
</a></li>
991 <li data-name=
"LuCI.uci#move"><a href=
"LuCI.uci.html#move">move
</a></li>
993 <li data-name=
"LuCI.uci#remove"><a href=
"LuCI.uci.html#remove">remove
</a></li>
995 <li data-name=
"LuCI.uci#resolveSID"><a href=
"LuCI.uci.html#resolveSID">resolveSID
</a></li>
997 <li data-name=
"LuCI.uci#save"><a href=
"LuCI.uci.html#save">save
</a></li>
999 <li data-name=
"LuCI.uci#sections"><a href=
"LuCI.uci.html#sections">sections
</a></li>
1001 <li data-name=
"LuCI.uci#set"><a href=
"LuCI.uci.html#set">set
</a></li>
1003 <li data-name=
"LuCI.uci#set_first"><a href=
"LuCI.uci.html#set_first">set_first
</a></li>
1005 <li data-name=
"LuCI.uci#unload"><a href=
"LuCI.uci.html#unload">unload
</a></li>
1007 <li data-name=
"LuCI.uci#unset"><a href=
"LuCI.uci.html#unset">unset
</a></li>
1009 <li data-name=
"LuCI.uci#unset_first"><a href=
"LuCI.uci.html#unset_first">unset_first
</a></li>
1012 <ul class=
"events itemMembers">
1017 <li class=
"item" data-name=
"LuCI.ui">
1018 <span class=
"title">
1019 <a href=
"LuCI.ui.html">LuCI.ui
</a>
1022 <ul class=
"members itemMembers">
1025 <ul class=
"typedefs itemMembers">
1027 <span class=
"subtitle">Typedefs
</span>
1029 <li data-name=
"LuCI.ui.FileUploadReply"><a href=
"LuCI.ui.html#.FileUploadReply">FileUploadReply
</a></li>
1032 <ul class=
"typedefs itemMembers">
1035 <ul class=
"methods itemMembers">
1037 <span class=
"subtitle">Methods
</span>
1039 <li data-name=
"LuCI.ui#addNotification"><a href=
"LuCI.ui.html#addNotification">addNotification
</a></li>
1041 <li data-name=
"LuCI.ui#addValidator"><a href=
"LuCI.ui.html#addValidator">addValidator
</a></li>
1043 <li data-name=
"LuCI.ui#awaitReconnect"><a href=
"LuCI.ui.html#awaitReconnect">awaitReconnect
</a></li>
1045 <li data-name=
"LuCI.ui#createHandlerFn"><a href=
"LuCI.ui.html#createHandlerFn">createHandlerFn
</a></li>
1047 <li data-name=
"LuCI.ui#hideIndicator"><a href=
"LuCI.ui.html#hideIndicator">hideIndicator
</a></li>
1049 <li data-name=
"LuCI.ui#hideModal"><a href=
"LuCI.ui.html#hideModal">hideModal
</a></li>
1051 <li data-name=
"LuCI.ui#instantiateView"><a href=
"LuCI.ui.html#instantiateView">instantiateView
</a></li>
1053 <li data-name=
"LuCI.ui#itemlist"><a href=
"LuCI.ui.html#itemlist">itemlist
</a></li>
1055 <li data-name=
"LuCI.ui#pingDevice"><a href=
"LuCI.ui.html#pingDevice">pingDevice
</a></li>
1057 <li data-name=
"LuCI.ui#showIndicator"><a href=
"LuCI.ui.html#showIndicator">showIndicator
</a></li>
1059 <li data-name=
"LuCI.ui#showModal"><a href=
"LuCI.ui.html#showModal">showModal
</a></li>
1061 <li data-name=
"LuCI.ui#uploadFile"><a href=
"LuCI.ui.html#uploadFile">uploadFile
</a></li>
1064 <ul class=
"events itemMembers">
1069 <li class=
"item" data-name=
"LuCI.ui.AbstractElement">
1070 <span class=
"title">
1071 <a href=
"LuCI.ui.AbstractElement.html">LuCI.ui.AbstractElement
</a>
1074 <ul class=
"members itemMembers">
1077 <ul class=
"typedefs itemMembers">
1079 <span class=
"subtitle">Typedefs
</span>
1081 <li data-name=
"LuCI.ui.AbstractElement.InitOptions"><a href=
"LuCI.ui.AbstractElement.html#.InitOptions">InitOptions
</a></li>
1084 <ul class=
"typedefs itemMembers">
1087 <ul class=
"methods itemMembers">
1089 <span class=
"subtitle">Methods
</span>
1091 <li data-name=
"LuCI.ui.AbstractElement#getValue"><a href=
"LuCI.ui.AbstractElement.html#getValue">getValue
</a></li>
1093 <li data-name=
"LuCI.ui.AbstractElement#isValid"><a href=
"LuCI.ui.AbstractElement.html#isValid">isValid
</a></li>
1095 <li data-name=
"LuCI.ui.AbstractElement#registerEvents"><a href=
"LuCI.ui.AbstractElement.html#registerEvents">registerEvents
</a></li>
1097 <li data-name=
"LuCI.ui.AbstractElement#render"><a href=
"LuCI.ui.AbstractElement.html#render">render
</a></li>
1099 <li data-name=
"LuCI.ui.AbstractElement#setChangeEvents"><a href=
"LuCI.ui.AbstractElement.html#setChangeEvents">setChangeEvents
</a></li>
1101 <li data-name=
"LuCI.ui.AbstractElement#setUpdateEvents"><a href=
"LuCI.ui.AbstractElement.html#setUpdateEvents">setUpdateEvents
</a></li>
1103 <li data-name=
"LuCI.ui.AbstractElement#setValue"><a href=
"LuCI.ui.AbstractElement.html#setValue">setValue
</a></li>
1105 <li data-name=
"LuCI.ui.AbstractElement#triggerValidation"><a href=
"LuCI.ui.AbstractElement.html#triggerValidation">triggerValidation
</a></li>
1108 <ul class=
"events itemMembers">
1113 <li class=
"item" data-name=
"LuCI.ui.changes">
1114 <span class=
"title">
1115 <a href=
"LuCI.ui.changes.html">LuCI.ui.changes
</a>
1118 <ul class=
"members itemMembers">
1121 <ul class=
"typedefs itemMembers">
1124 <ul class=
"typedefs itemMembers">
1127 <ul class=
"methods itemMembers">
1129 <span class=
"subtitle">Methods
</span>
1131 <li data-name=
"LuCI.ui.changes#apply"><a href=
"LuCI.ui.changes.html#apply">apply
</a></li>
1133 <li data-name=
"LuCI.ui.changes#displayChanges"><a href=
"LuCI.ui.changes.html#displayChanges">displayChanges
</a></li>
1135 <li data-name=
"LuCI.ui.changes#renderChangeIndicator"><a href=
"LuCI.ui.changes.html#renderChangeIndicator">renderChangeIndicator
</a></li>
1137 <li data-name=
"LuCI.ui.changes#revert"><a href=
"LuCI.ui.changes.html#revert">revert
</a></li>
1139 <li data-name=
"LuCI.ui.changes#setIndicator"><a href=
"LuCI.ui.changes.html#setIndicator">setIndicator
</a></li>
1142 <ul class=
"events itemMembers">
1147 <li class=
"item" data-name=
"LuCI.ui.Checkbox">
1148 <span class=
"title">
1149 <a href=
"LuCI.ui.Checkbox.html">LuCI.ui.Checkbox
</a>
1152 <ul class=
"members itemMembers">
1155 <ul class=
"typedefs itemMembers">
1157 <span class=
"subtitle">Typedefs
</span>
1159 <li data-name=
"LuCI.ui.Checkbox.InitOptions"><a href=
"LuCI.ui.Checkbox.html#.InitOptions">InitOptions
</a></li>
1162 <ul class=
"typedefs itemMembers">
1165 <ul class=
"methods itemMembers">
1167 <span class=
"subtitle">Methods
</span>
1169 <li data-name=
"LuCI.ui.Checkbox#getValue"><a href=
"LuCI.ui.Checkbox.html#getValue">getValue
</a></li>
1171 <li data-name=
"LuCI.ui.Checkbox#isChecked"><a href=
"LuCI.ui.Checkbox.html#isChecked">isChecked
</a></li>
1173 <li data-name=
"LuCI.ui.Checkbox#isValid"><a href=
"LuCI.ui.Checkbox.html#isValid">isValid
</a></li>
1175 <li data-name=
"LuCI.ui.Checkbox#registerEvents"><a href=
"LuCI.ui.Checkbox.html#registerEvents">registerEvents
</a></li>
1177 <li data-name=
"LuCI.ui.Checkbox#render"><a href=
"LuCI.ui.Checkbox.html#render">render
</a></li>
1179 <li data-name=
"LuCI.ui.Checkbox#setChangeEvents"><a href=
"LuCI.ui.Checkbox.html#setChangeEvents">setChangeEvents
</a></li>
1181 <li data-name=
"LuCI.ui.Checkbox#setUpdateEvents"><a href=
"LuCI.ui.Checkbox.html#setUpdateEvents">setUpdateEvents
</a></li>
1183 <li data-name=
"LuCI.ui.Checkbox#setValue"><a href=
"LuCI.ui.Checkbox.html#setValue">setValue
</a></li>
1185 <li data-name=
"LuCI.ui.Checkbox#triggerValidation"><a href=
"LuCI.ui.Checkbox.html#triggerValidation">triggerValidation
</a></li>
1188 <ul class=
"events itemMembers">
1193 <li class=
"item" data-name=
"LuCI.ui.Combobox">
1194 <span class=
"title">
1195 <a href=
"LuCI.ui.Combobox.html">LuCI.ui.Combobox
</a>
1198 <ul class=
"members itemMembers">
1201 <ul class=
"typedefs itemMembers">
1203 <span class=
"subtitle">Typedefs
</span>
1205 <li data-name=
"LuCI.ui.Combobox.InitOptions"><a href=
"LuCI.ui.Combobox.html#.InitOptions">InitOptions
</a></li>
1208 <ul class=
"typedefs itemMembers">
1211 <ul class=
"methods itemMembers">
1213 <span class=
"subtitle">Methods
</span>
1215 <li data-name=
"LuCI.ui.Combobox#addChoices"><a href=
"LuCI.ui.Combobox.html#addChoices">addChoices
</a></li>
1217 <li data-name=
"LuCI.ui.Combobox#clearChoices"><a href=
"LuCI.ui.Combobox.html#clearChoices">clearChoices
</a></li>
1219 <li data-name=
"LuCI.ui.Combobox#closeAllDropdowns"><a href=
"LuCI.ui.Combobox.html#closeAllDropdowns">closeAllDropdowns
</a></li>
1221 <li data-name=
"LuCI.ui.Combobox#isValid"><a href=
"LuCI.ui.Combobox.html#isValid">isValid
</a></li>
1223 <li data-name=
"LuCI.ui.Combobox#registerEvents"><a href=
"LuCI.ui.Combobox.html#registerEvents">registerEvents
</a></li>
1225 <li data-name=
"LuCI.ui.Combobox#setChangeEvents"><a href=
"LuCI.ui.Combobox.html#setChangeEvents">setChangeEvents
</a></li>
1227 <li data-name=
"LuCI.ui.Combobox#setUpdateEvents"><a href=
"LuCI.ui.Combobox.html#setUpdateEvents">setUpdateEvents
</a></li>
1229 <li data-name=
"LuCI.ui.Combobox#triggerValidation"><a href=
"LuCI.ui.Combobox.html#triggerValidation">triggerValidation
</a></li>
1232 <ul class=
"events itemMembers">
1237 <li class=
"item" data-name=
"LuCI.ui.ComboButton">
1238 <span class=
"title">
1239 <a href=
"LuCI.ui.ComboButton.html">LuCI.ui.ComboButton
</a>
1242 <ul class=
"members itemMembers">
1245 <ul class=
"typedefs itemMembers">
1247 <span class=
"subtitle">Typedefs
</span>
1249 <li data-name=
"LuCI.ui.ComboButton.InitOptions"><a href=
"LuCI.ui.ComboButton.html#.InitOptions">InitOptions
</a></li>
1252 <ul class=
"typedefs itemMembers">
1255 <ul class=
"methods itemMembers">
1257 <span class=
"subtitle">Methods
</span>
1259 <li data-name=
"LuCI.ui.ComboButton#addChoices"><a href=
"LuCI.ui.ComboButton.html#addChoices">addChoices
</a></li>
1261 <li data-name=
"LuCI.ui.ComboButton#clearChoices"><a href=
"LuCI.ui.ComboButton.html#clearChoices">clearChoices
</a></li>
1263 <li data-name=
"LuCI.ui.ComboButton#closeAllDropdowns"><a href=
"LuCI.ui.ComboButton.html#closeAllDropdowns">closeAllDropdowns
</a></li>
1265 <li data-name=
"LuCI.ui.ComboButton#isValid"><a href=
"LuCI.ui.ComboButton.html#isValid">isValid
</a></li>
1267 <li data-name=
"LuCI.ui.ComboButton#registerEvents"><a href=
"LuCI.ui.ComboButton.html#registerEvents">registerEvents
</a></li>
1269 <li data-name=
"LuCI.ui.ComboButton#setChangeEvents"><a href=
"LuCI.ui.ComboButton.html#setChangeEvents">setChangeEvents
</a></li>
1271 <li data-name=
"LuCI.ui.ComboButton#setUpdateEvents"><a href=
"LuCI.ui.ComboButton.html#setUpdateEvents">setUpdateEvents
</a></li>
1273 <li data-name=
"LuCI.ui.ComboButton#triggerValidation"><a href=
"LuCI.ui.ComboButton.html#triggerValidation">triggerValidation
</a></li>
1276 <ul class=
"events itemMembers">
1281 <li class=
"item" data-name=
"LuCI.ui.Dropdown">
1282 <span class=
"title">
1283 <a href=
"LuCI.ui.Dropdown.html">LuCI.ui.Dropdown
</a>
1286 <ul class=
"members itemMembers">
1289 <ul class=
"typedefs itemMembers">
1291 <span class=
"subtitle">Typedefs
</span>
1293 <li data-name=
"LuCI.ui.Dropdown.InitOptions"><a href=
"LuCI.ui.Dropdown.html#.InitOptions">InitOptions
</a></li>
1296 <ul class=
"typedefs itemMembers">
1299 <ul class=
"methods itemMembers">
1301 <span class=
"subtitle">Methods
</span>
1303 <li data-name=
"LuCI.ui.Dropdown#addChoices"><a href=
"LuCI.ui.Dropdown.html#addChoices">addChoices
</a></li>
1305 <li data-name=
"LuCI.ui.Dropdown#clearChoices"><a href=
"LuCI.ui.Dropdown.html#clearChoices">clearChoices
</a></li>
1307 <li data-name=
"LuCI.ui.Dropdown#closeAllDropdowns"><a href=
"LuCI.ui.Dropdown.html#closeAllDropdowns">closeAllDropdowns
</a></li>
1309 <li data-name=
"LuCI.ui.Dropdown#getValue"><a href=
"LuCI.ui.Dropdown.html#getValue">getValue
</a></li>
1311 <li data-name=
"LuCI.ui.Dropdown#isValid"><a href=
"LuCI.ui.Dropdown.html#isValid">isValid
</a></li>
1313 <li data-name=
"LuCI.ui.Dropdown#registerEvents"><a href=
"LuCI.ui.Dropdown.html#registerEvents">registerEvents
</a></li>
1315 <li data-name=
"LuCI.ui.Dropdown#render"><a href=
"LuCI.ui.Dropdown.html#render">render
</a></li>
1317 <li data-name=
"LuCI.ui.Dropdown#setChangeEvents"><a href=
"LuCI.ui.Dropdown.html#setChangeEvents">setChangeEvents
</a></li>
1319 <li data-name=
"LuCI.ui.Dropdown#setUpdateEvents"><a href=
"LuCI.ui.Dropdown.html#setUpdateEvents">setUpdateEvents
</a></li>
1321 <li data-name=
"LuCI.ui.Dropdown#setValue"><a href=
"LuCI.ui.Dropdown.html#setValue">setValue
</a></li>
1323 <li data-name=
"LuCI.ui.Dropdown#triggerValidation"><a href=
"LuCI.ui.Dropdown.html#triggerValidation">triggerValidation
</a></li>
1326 <ul class=
"events itemMembers">
1331 <li class=
"item" data-name=
"LuCI.ui.DynamicList">
1332 <span class=
"title">
1333 <a href=
"LuCI.ui.DynamicList.html">LuCI.ui.DynamicList
</a>
1336 <ul class=
"members itemMembers">
1339 <ul class=
"typedefs itemMembers">
1341 <span class=
"subtitle">Typedefs
</span>
1343 <li data-name=
"LuCI.ui.DynamicList.InitOptions"><a href=
"LuCI.ui.DynamicList.html#.InitOptions">InitOptions
</a></li>
1346 <ul class=
"typedefs itemMembers">
1349 <ul class=
"methods itemMembers">
1351 <span class=
"subtitle">Methods
</span>
1353 <li data-name=
"LuCI.ui.DynamicList#addChoices"><a href=
"LuCI.ui.DynamicList.html#addChoices">addChoices
</a></li>
1355 <li data-name=
"LuCI.ui.DynamicList#clearChoices"><a href=
"LuCI.ui.DynamicList.html#clearChoices">clearChoices
</a></li>
1357 <li data-name=
"LuCI.ui.DynamicList#getValue"><a href=
"LuCI.ui.DynamicList.html#getValue">getValue
</a></li>
1359 <li data-name=
"LuCI.ui.DynamicList#isValid"><a href=
"LuCI.ui.DynamicList.html#isValid">isValid
</a></li>
1361 <li data-name=
"LuCI.ui.DynamicList#registerEvents"><a href=
"LuCI.ui.DynamicList.html#registerEvents">registerEvents
</a></li>
1363 <li data-name=
"LuCI.ui.DynamicList#render"><a href=
"LuCI.ui.DynamicList.html#render">render
</a></li>
1365 <li data-name=
"LuCI.ui.DynamicList#setChangeEvents"><a href=
"LuCI.ui.DynamicList.html#setChangeEvents">setChangeEvents
</a></li>
1367 <li data-name=
"LuCI.ui.DynamicList#setUpdateEvents"><a href=
"LuCI.ui.DynamicList.html#setUpdateEvents">setUpdateEvents
</a></li>
1369 <li data-name=
"LuCI.ui.DynamicList#setValue"><a href=
"LuCI.ui.DynamicList.html#setValue">setValue
</a></li>
1371 <li data-name=
"LuCI.ui.DynamicList#triggerValidation"><a href=
"LuCI.ui.DynamicList.html#triggerValidation">triggerValidation
</a></li>
1374 <ul class=
"events itemMembers">
1379 <li class=
"item" data-name=
"LuCI.ui.FileUpload">
1380 <span class=
"title">
1381 <a href=
"LuCI.ui.FileUpload.html">LuCI.ui.FileUpload
</a>
1384 <ul class=
"members itemMembers">
1387 <ul class=
"typedefs itemMembers">
1389 <span class=
"subtitle">Typedefs
</span>
1391 <li data-name=
"LuCI.ui.FileUpload.InitOptions"><a href=
"LuCI.ui.FileUpload.html#.InitOptions">InitOptions
</a></li>
1394 <ul class=
"typedefs itemMembers">
1397 <ul class=
"methods itemMembers">
1399 <span class=
"subtitle">Methods
</span>
1401 <li data-name=
"LuCI.ui.FileUpload#getValue"><a href=
"LuCI.ui.FileUpload.html#getValue">getValue
</a></li>
1403 <li data-name=
"LuCI.ui.FileUpload#isValid"><a href=
"LuCI.ui.FileUpload.html#isValid">isValid
</a></li>
1405 <li data-name=
"LuCI.ui.FileUpload#registerEvents"><a href=
"LuCI.ui.FileUpload.html#registerEvents">registerEvents
</a></li>
1407 <li data-name=
"LuCI.ui.FileUpload#render"><a href=
"LuCI.ui.FileUpload.html#render">render
</a></li>
1409 <li data-name=
"LuCI.ui.FileUpload#setChangeEvents"><a href=
"LuCI.ui.FileUpload.html#setChangeEvents">setChangeEvents
</a></li>
1411 <li data-name=
"LuCI.ui.FileUpload#setUpdateEvents"><a href=
"LuCI.ui.FileUpload.html#setUpdateEvents">setUpdateEvents
</a></li>
1413 <li data-name=
"LuCI.ui.FileUpload#setValue"><a href=
"LuCI.ui.FileUpload.html#setValue">setValue
</a></li>
1415 <li data-name=
"LuCI.ui.FileUpload#triggerValidation"><a href=
"LuCI.ui.FileUpload.html#triggerValidation">triggerValidation
</a></li>
1418 <ul class=
"events itemMembers">
1423 <li class=
"item" data-name=
"LuCI.ui.Hiddenfield">
1424 <span class=
"title">
1425 <a href=
"LuCI.ui.Hiddenfield.html">LuCI.ui.Hiddenfield
</a>
1428 <ul class=
"members itemMembers">
1431 <ul class=
"typedefs itemMembers">
1434 <ul class=
"typedefs itemMembers">
1437 <ul class=
"methods itemMembers">
1439 <span class=
"subtitle">Methods
</span>
1441 <li data-name=
"LuCI.ui.Hiddenfield#getValue"><a href=
"LuCI.ui.Hiddenfield.html#getValue">getValue
</a></li>
1443 <li data-name=
"LuCI.ui.Hiddenfield#isValid"><a href=
"LuCI.ui.Hiddenfield.html#isValid">isValid
</a></li>
1445 <li data-name=
"LuCI.ui.Hiddenfield#registerEvents"><a href=
"LuCI.ui.Hiddenfield.html#registerEvents">registerEvents
</a></li>
1447 <li data-name=
"LuCI.ui.Hiddenfield#render"><a href=
"LuCI.ui.Hiddenfield.html#render">render
</a></li>
1449 <li data-name=
"LuCI.ui.Hiddenfield#setChangeEvents"><a href=
"LuCI.ui.Hiddenfield.html#setChangeEvents">setChangeEvents
</a></li>
1451 <li data-name=
"LuCI.ui.Hiddenfield#setUpdateEvents"><a href=
"LuCI.ui.Hiddenfield.html#setUpdateEvents">setUpdateEvents
</a></li>
1453 <li data-name=
"LuCI.ui.Hiddenfield#setValue"><a href=
"LuCI.ui.Hiddenfield.html#setValue">setValue
</a></li>
1455 <li data-name=
"LuCI.ui.Hiddenfield#triggerValidation"><a href=
"LuCI.ui.Hiddenfield.html#triggerValidation">triggerValidation
</a></li>
1458 <ul class=
"events itemMembers">
1463 <li class=
"item" data-name=
"LuCI.ui.Select">
1464 <span class=
"title">
1465 <a href=
"LuCI.ui.Select.html">LuCI.ui.Select
</a>
1468 <ul class=
"members itemMembers">
1471 <ul class=
"typedefs itemMembers">
1473 <span class=
"subtitle">Typedefs
</span>
1475 <li data-name=
"LuCI.ui.Select.InitOptions"><a href=
"LuCI.ui.Select.html#.InitOptions">InitOptions
</a></li>
1478 <ul class=
"typedefs itemMembers">
1481 <ul class=
"methods itemMembers">
1483 <span class=
"subtitle">Methods
</span>
1485 <li data-name=
"LuCI.ui.Select#getValue"><a href=
"LuCI.ui.Select.html#getValue">getValue
</a></li>
1487 <li data-name=
"LuCI.ui.Select#isValid"><a href=
"LuCI.ui.Select.html#isValid">isValid
</a></li>
1489 <li data-name=
"LuCI.ui.Select#registerEvents"><a href=
"LuCI.ui.Select.html#registerEvents">registerEvents
</a></li>
1491 <li data-name=
"LuCI.ui.Select#render"><a href=
"LuCI.ui.Select.html#render">render
</a></li>
1493 <li data-name=
"LuCI.ui.Select#setChangeEvents"><a href=
"LuCI.ui.Select.html#setChangeEvents">setChangeEvents
</a></li>
1495 <li data-name=
"LuCI.ui.Select#setUpdateEvents"><a href=
"LuCI.ui.Select.html#setUpdateEvents">setUpdateEvents
</a></li>
1497 <li data-name=
"LuCI.ui.Select#setValue"><a href=
"LuCI.ui.Select.html#setValue">setValue
</a></li>
1499 <li data-name=
"LuCI.ui.Select#triggerValidation"><a href=
"LuCI.ui.Select.html#triggerValidation">triggerValidation
</a></li>
1502 <ul class=
"events itemMembers">
1507 <li class=
"item" data-name=
"LuCI.ui.tabs">
1508 <span class=
"title">
1509 <a href=
"LuCI.ui.tabs.html">LuCI.ui.tabs
</a>
1512 <ul class=
"members itemMembers">
1515 <ul class=
"typedefs itemMembers">
1518 <ul class=
"typedefs itemMembers">
1521 <ul class=
"methods itemMembers">
1523 <span class=
"subtitle">Methods
</span>
1525 <li data-name=
"LuCI.ui.tabs#initTabGroup"><a href=
"LuCI.ui.tabs.html#initTabGroup">initTabGroup
</a></li>
1527 <li data-name=
"LuCI.ui.tabs#isEmptyPane"><a href=
"LuCI.ui.tabs.html#isEmptyPane">isEmptyPane
</a></li>
1530 <ul class=
"events itemMembers">
1535 <li class=
"item" data-name=
"LuCI.ui.Textarea">
1536 <span class=
"title">
1537 <a href=
"LuCI.ui.Textarea.html">LuCI.ui.Textarea
</a>
1540 <ul class=
"members itemMembers">
1543 <ul class=
"typedefs itemMembers">
1545 <span class=
"subtitle">Typedefs
</span>
1547 <li data-name=
"LuCI.ui.Textarea.InitOptions"><a href=
"LuCI.ui.Textarea.html#.InitOptions">InitOptions
</a></li>
1550 <ul class=
"typedefs itemMembers">
1553 <ul class=
"methods itemMembers">
1555 <span class=
"subtitle">Methods
</span>
1557 <li data-name=
"LuCI.ui.Textarea#getValue"><a href=
"LuCI.ui.Textarea.html#getValue">getValue
</a></li>
1559 <li data-name=
"LuCI.ui.Textarea#isValid"><a href=
"LuCI.ui.Textarea.html#isValid">isValid
</a></li>
1561 <li data-name=
"LuCI.ui.Textarea#registerEvents"><a href=
"LuCI.ui.Textarea.html#registerEvents">registerEvents
</a></li>
1563 <li data-name=
"LuCI.ui.Textarea#render"><a href=
"LuCI.ui.Textarea.html#render">render
</a></li>
1565 <li data-name=
"LuCI.ui.Textarea#setChangeEvents"><a href=
"LuCI.ui.Textarea.html#setChangeEvents">setChangeEvents
</a></li>
1567 <li data-name=
"LuCI.ui.Textarea#setUpdateEvents"><a href=
"LuCI.ui.Textarea.html#setUpdateEvents">setUpdateEvents
</a></li>
1569 <li data-name=
"LuCI.ui.Textarea#setValue"><a href=
"LuCI.ui.Textarea.html#setValue">setValue
</a></li>
1571 <li data-name=
"LuCI.ui.Textarea#triggerValidation"><a href=
"LuCI.ui.Textarea.html#triggerValidation">triggerValidation
</a></li>
1574 <ul class=
"events itemMembers">
1579 <li class=
"item" data-name=
"LuCI.ui.Textfield">
1580 <span class=
"title">
1581 <a href=
"LuCI.ui.Textfield.html">LuCI.ui.Textfield
</a>
1584 <ul class=
"members itemMembers">
1587 <ul class=
"typedefs itemMembers">
1589 <span class=
"subtitle">Typedefs
</span>
1591 <li data-name=
"LuCI.ui.Textfield.InitOptions"><a href=
"LuCI.ui.Textfield.html#.InitOptions">InitOptions
</a></li>
1594 <ul class=
"typedefs itemMembers">
1597 <ul class=
"methods itemMembers">
1599 <span class=
"subtitle">Methods
</span>
1601 <li data-name=
"LuCI.ui.Textfield#getValue"><a href=
"LuCI.ui.Textfield.html#getValue">getValue
</a></li>
1603 <li data-name=
"LuCI.ui.Textfield#isValid"><a href=
"LuCI.ui.Textfield.html#isValid">isValid
</a></li>
1605 <li data-name=
"LuCI.ui.Textfield#registerEvents"><a href=
"LuCI.ui.Textfield.html#registerEvents">registerEvents
</a></li>
1607 <li data-name=
"LuCI.ui.Textfield#render"><a href=
"LuCI.ui.Textfield.html#render">render
</a></li>
1609 <li data-name=
"LuCI.ui.Textfield#setChangeEvents"><a href=
"LuCI.ui.Textfield.html#setChangeEvents">setChangeEvents
</a></li>
1611 <li data-name=
"LuCI.ui.Textfield#setUpdateEvents"><a href=
"LuCI.ui.Textfield.html#setUpdateEvents">setUpdateEvents
</a></li>
1613 <li data-name=
"LuCI.ui.Textfield#setValue"><a href=
"LuCI.ui.Textfield.html#setValue">setValue
</a></li>
1615 <li data-name=
"LuCI.ui.Textfield#triggerValidation"><a href=
"LuCI.ui.Textfield.html#triggerValidation">triggerValidation
</a></li>
1618 <ul class=
"events itemMembers">
1623 <li class=
"item" data-name=
"LuCI.view">
1624 <span class=
"title">
1625 <a href=
"LuCI.view.html">LuCI.view
</a>
1628 <ul class=
"members itemMembers">
1631 <ul class=
"typedefs itemMembers">
1634 <ul class=
"typedefs itemMembers">
1637 <ul class=
"methods itemMembers">
1639 <span class=
"subtitle">Methods
</span>
1641 <li data-name=
"LuCI.view#addFooter"><a href=
"LuCI.view.html#addFooter">addFooter
</a></li>
1643 <li data-name=
"LuCI.view#handleReset"><a href=
"LuCI.view.html#handleReset">handleReset
</a></li>
1645 <li data-name=
"LuCI.view#handleSave"><a href=
"LuCI.view.html#handleSave">handleSave
</a></li>
1647 <li data-name=
"LuCI.view#handleSaveApply"><a href=
"LuCI.view.html#handleSaveApply">handleSaveApply
</a></li>
1649 <li data-name=
"LuCI.view#load"><a href=
"LuCI.view.html#load">load
</a></li>
1651 <li data-name=
"LuCI.view#render"><a href=
"LuCI.view.html#render">render
</a></li>
1654 <ul class=
"events itemMembers">
1659 <li class=
"item" data-name=
"LuCI.xhr">
1660 <span class=
"title">
1661 <a href=
"LuCI.xhr.html">LuCI.xhr
</a>
1664 <ul class=
"members itemMembers">
1667 <ul class=
"typedefs itemMembers">
1670 <ul class=
"typedefs itemMembers">
1673 <ul class=
"methods itemMembers">
1675 <span class=
"subtitle">Methods
</span>
1677 <li data-name=
"LuCI.xhr#abort"><a href=
"LuCI.xhr.html#abort">abort
</a></li>
1679 <li data-name=
"LuCI.xhr#busy"><a href=
"LuCI.xhr.html#busy">busy
</a></li>
1681 <li data-name=
"LuCI.xhr#cancel"><a href=
"LuCI.xhr.html#cancel">cancel
</a></li>
1683 <li data-name=
"LuCI.xhr#get"><a href=
"LuCI.xhr.html#get">get
</a></li>
1685 <li data-name=
"LuCI.xhr#post"><a href=
"LuCI.xhr.html#post">post
</a></li>
1687 <li data-name=
"LuCI.xhr#send_form"><a href=
"LuCI.xhr.html#send_form">send_form
</a></li>
1690 <ul class=
"events itemMembers">
1698 <h1 class=
"page-title" data-filename=
"ui.js.html">Source: ui.js
</h1>
1705 <pre id=
"source-code" class=
"prettyprint source "><code>'use strict';
1706 'require validation';
1707 'require baseclass';
1715 var modalDiv = null,
1717 indicatorDiv = null,
1718 tooltipTimeout = null;
1721 * @class AbstractElement
1726 * The `AbstractElement` class serves as abstract base for the different widgets
1727 * implemented by `LuCI.ui`. It provides the common logic for getting and
1728 * setting values, for checking the validity state and for wiring up required
1731 * UI widget instances are usually not supposed to be created by view code
1732 * directly, instead they're implicitely created by `LuCI.form` when
1733 * instantiating CBI forms.
1735 * This class is automatically instantiated as part of `LuCI.ui`. To use it
1736 * in views, use `'require ui'` and refer to `ui.AbstractElement`. To import
1737 * it in external JavaScript, use `L.require(
"ui").then(...)` and access the
1738 * `AbstractElement` property of the class instance value.
1740 var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ {
1742 * @typedef {Object} InitOptions
1743 * @memberof LuCI.ui.AbstractElement
1745 * @property {string} [id]
1746 * Specifies the widget ID to use. It will be used as HTML `id` attribute
1747 * on the toplevel widget DOM node.
1749 * @property {string} [name]
1750 * Specifies the widget name which is set as HTML `name` attribute on the
1751 * corresponding `
<input
>` element.
1753 * @property {boolean} [optional=true]
1754 * Specifies whether the input field allows empty values.
1756 * @property {string} [datatype=string]
1757 * An expression describing the input data validation constraints.
1758 * It defaults to `string` which will allow any value.
1759 * See {@link LuCI.validation} for details on the expression format.
1761 * @property {function} [validator]
1762 * Specifies a custom validator function which is invoked after the
1763 * standard validation constraints are checked. The function should return
1764 * `true` to accept the given input value. Any other return value type is
1765 * converted to a string and treated as validation error message.
1769 * Read the current value of the input widget.
1772 * @memberof LuCI.ui.AbstractElement
1773 * @returns {string|string[]|null}
1774 * The current value of the input element. For simple inputs like text
1775 * fields or selects, the return value type will be a - possibly empty -
1776 * string. Complex widgets such as `DynamicList` instances may result in
1777 * an array of strings or `null` for unset values.
1779 getValue: function() {
1780 if (dom.matches(this.node, 'select') || dom.matches(this.node, 'input'))
1781 return this.node.value;
1787 * Set the current value of the input widget.
1790 * @memberof LuCI.ui.AbstractElement
1791 * @param {string|string[]|null} value
1792 * The value to set the input element to. For simple inputs like text
1793 * fields or selects, the value should be a - possibly empty - string.
1794 * Complex widgets such as `DynamicList` instances may accept string array
1797 setValue: function(value) {
1798 if (dom.matches(this.node, 'select') || dom.matches(this.node, 'input'))
1799 this.node.value = value;
1803 * Check whether the current input value is valid.
1806 * @memberof LuCI.ui.AbstractElement
1807 * @returns {boolean}
1808 * Returns `true` if the current input value is valid or `false` if it does
1809 * not meet the validation constraints.
1811 isValid: function() {
1812 return (this.validState !== false);
1816 * Force validation of the current input value.
1818 * Usually input validation is automatically triggered by various DOM events
1819 * bound to the input widget. In some cases it is required though to manually
1820 * trigger validation runs, e.g. when programmatically altering values.
1823 * @memberof LuCI.ui.AbstractElement
1825 triggerValidation: function() {
1826 if (typeof(this.vfunc) != 'function')
1829 var wasValid = this.isValid();
1833 return (wasValid != this.isValid());
1837 * Dispatch a custom (synthetic) event in response to received events.
1839 * Sets up event handlers on the given target DOM node for the given event
1840 * names that dispatch a custom event of the given type to the widget root
1843 * The primary purpose of this function is to set up a series of custom
1844 * uniform standard events such as `widget-update`, `validation-success`,
1845 * `validation-failure` etc. which are triggered by various different
1846 * widget specific native DOM events.
1849 * @memberof LuCI.ui.AbstractElement
1850 * @param {Node} targetNode
1851 * Specifies the DOM node on which the native event listeners should be
1854 * @param {string} synevent
1855 * The name of the custom event to dispatch to the widget root DOM node.
1857 * @param {string[]} events
1858 * The native DOM events for which event handlers should be registered.
1860 registerEvents: function(targetNode, synevent, events) {
1861 var dispatchFn = L.bind(function(ev) {
1862 this.node.dispatchEvent(new CustomEvent(synevent, { bubbles: true }));
1865 for (var i =
0; i
< events.length; i++)
1866 targetNode.addEventListener(events[i], dispatchFn);
1870 * Setup listeners for native DOM events that may update the widget value.
1872 * Sets up event handlers on the given target DOM node for the given event
1873 * names which may cause the input value to update, such as `keyup` or
1874 * `onclick` events. In contrast to change events, such update events will
1875 * trigger input value validation.
1878 * @memberof LuCI.ui.AbstractElement
1879 * @param {Node} targetNode
1880 * Specifies the DOM node on which the event listeners should be registered.
1882 * @param {...string} events
1883 * The DOM events for which event handlers should be registered.
1885 setUpdateEvents: function(targetNode /*, ... */) {
1886 var datatype = this.options.datatype,
1887 optional = this.options.hasOwnProperty('optional') ? this.options.optional : true,
1888 validate = this.options.validate,
1889 events = this.varargs(arguments,
1);
1891 this.registerEvents(targetNode, 'widget-update', events);
1893 if (!datatype
&& !validate)
1896 this.vfunc = UI.prototype.addValidator.apply(UI.prototype, [
1897 targetNode, datatype || 'string',
1901 this.node.addEventListener('validation-success', L.bind(function(ev) {
1902 this.validState = true;
1905 this.node.addEventListener('validation-failure', L.bind(function(ev) {
1906 this.validState = false;
1911 * Setup listeners for native DOM events that may change the widget value.
1913 * Sets up event handlers on the given target DOM node for the given event
1914 * names which may cause the input value to change completely, such as
1915 * `change` events in a select menu. In contrast to update events, such
1916 * change events will not trigger input value validation but they may cause
1917 * field dependencies to get re-evaluated and will mark the input widget
1921 * @memberof LuCI.ui.AbstractElement
1922 * @param {Node} targetNode
1923 * Specifies the DOM node on which the event listeners should be registered.
1925 * @param {...string} events
1926 * The DOM events for which event handlers should be registered.
1928 setChangeEvents: function(targetNode /*, ... */) {
1929 var tag_changed = L.bind(function(ev) { this.setAttribute('data-changed', true) }, this.node);
1931 for (var i =
1; i
< arguments.length; i++)
1932 targetNode.addEventListener(arguments[i], tag_changed);
1934 this.registerEvents(targetNode, 'widget-change', this.varargs(arguments,
1));
1938 * Render the widget, setup event listeners and return resulting markup.
1941 * @memberof LuCI.ui.AbstractElement
1944 * Returns a DOM Node or DocumentFragment containing the rendered
1947 render: function() {}
1951 * Instantiate a text input widget.
1953 * @constructor Textfield
1955 * @augments LuCI.ui.AbstractElement
1959 * The `Textfield` class implements a standard single line text input field.
1961 * UI widget instances are usually not supposed to be created by view code
1962 * directly, instead they're implicitely created by `LuCI.form` when
1963 * instantiating CBI forms.
1965 * This class is automatically instantiated as part of `LuCI.ui`. To use it
1966 * in views, use `'require ui'` and refer to `ui.Textfield`. To import it in
1967 * external JavaScript, use `L.require(
"ui").then(...)` and access the
1968 * `Textfield` property of the class instance value.
1970 * @param {string} [value=null]
1971 * The initial input value.
1973 * @param {LuCI.ui.Textfield.InitOptions} [options]
1974 * Object describing the widget specific options to initialize the input.
1976 var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
1978 * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
1979 * the following properties are recognized:
1981 * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
1982 * @memberof LuCI.ui.Textfield
1984 * @property {boolean} [password=false]
1985 * Specifies whether the input should be rendered as concealed password field.
1987 * @property {boolean} [readonly=false]
1988 * Specifies whether the input widget should be rendered readonly.
1990 * @property {number} [maxlength]
1991 * Specifies the HTML `maxlength` attribute to set on the corresponding
1992 * `
<input
>` element. Note that this a legacy property that exists for
1993 * compatibility reasons. It is usually better to `maxlength(N)` validation
1996 * @property {string} [placeholder]
1997 * Specifies the HTML `placeholder` attribute which is displayed when the
1998 * corresponding `
<input
>` element is empty.
2000 __init__: function(value, options) {
2002 this.options = Object.assign({
2009 render: function() {
2010 var frameEl = E('div', { 'id': this.options.id });
2012 if (this.options.password) {
2013 frameEl.classList.add('nowrap');
2014 frameEl.appendChild(E('input', {
2016 'style': 'position:absolute; left:-
100000px',
2017 'aria-hidden': true,
2019 'name': this.options.name ? 'password.%s'.format(this.options.name) : null
2023 frameEl.appendChild(E('input', {
2024 'id': this.options.id ? 'widget.' + this.options.id : null,
2025 'name': this.options.name,
2026 'type': this.options.password ? 'password' : 'text',
2027 'class': this.options.password ? 'cbi-input-password' : 'cbi-input-text',
2028 'readonly': this.options.readonly ? '' : null,
2029 'maxlength': this.options.maxlength,
2030 'placeholder': this.options.placeholder,
2031 'value': this.value,
2034 if (this.options.password)
2035 frameEl.appendChild(E('button', {
2036 'class': 'cbi-button cbi-button-neutral',
2037 'title': _('Reveal/hide password'),
2038 'aria-label': _('Reveal/hide password'),
2039 'click': function(ev) {
2040 var e = this.previousElementSibling;
2041 e.type = (e.type === 'password') ? 'text' : 'password';
2042 ev.preventDefault();
2046 return this.bind(frameEl);
2050 bind: function(frameEl) {
2051 var inputEl = frameEl.childNodes[+!!this.options.password];
2053 this.node = frameEl;
2055 this.setUpdateEvents(inputEl, 'keyup', 'blur');
2056 this.setChangeEvents(inputEl, 'change');
2058 dom.bindClassInstance(frameEl, this);
2064 getValue: function() {
2065 var inputEl = this.node.childNodes[+!!this.options.password];
2066 return inputEl.value;
2070 setValue: function(value) {
2071 var inputEl = this.node.childNodes[+!!this.options.password];
2072 inputEl.value = value;
2077 * Instantiate a textarea widget.
2079 * @constructor Textarea
2081 * @augments LuCI.ui.AbstractElement
2085 * The `Textarea` class implements a multiline text area input field.
2087 * UI widget instances are usually not supposed to be created by view code
2088 * directly, instead they're implicitely created by `LuCI.form` when
2089 * instantiating CBI forms.
2091 * This class is automatically instantiated as part of `LuCI.ui`. To use it
2092 * in views, use `'require ui'` and refer to `ui.Textarea`. To import it in
2093 * external JavaScript, use `L.require(
"ui").then(...)` and access the
2094 * `Textarea` property of the class instance value.
2096 * @param {string} [value=null]
2097 * The initial input value.
2099 * @param {LuCI.ui.Textarea.InitOptions} [options]
2100 * Object describing the widget specific options to initialize the input.
2102 var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ {
2104 * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
2105 * the following properties are recognized:
2107 * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
2108 * @memberof LuCI.ui.Textarea
2110 * @property {boolean} [readonly=false]
2111 * Specifies whether the input widget should be rendered readonly.
2113 * @property {string} [placeholder]
2114 * Specifies the HTML `placeholder` attribute which is displayed when the
2115 * corresponding `
<textarea
>` element is empty.
2117 * @property {boolean} [monospace=false]
2118 * Specifies whether a monospace font should be forced for the textarea
2121 * @property {number} [cols]
2122 * Specifies the HTML `cols` attribute to set on the corresponding
2123 * `
<textarea
>` element.
2125 * @property {number} [rows]
2126 * Specifies the HTML `rows` attribute to set on the corresponding
2127 * `
<textarea
>` element.
2129 * @property {boolean} [wrap=false]
2130 * Specifies whether the HTML `wrap` attribute should be set.
2132 __init__: function(value, options) {
2134 this.options = Object.assign({
2143 render: function() {
2144 var frameEl = E('div', { 'id': this.options.id }),
2145 value = (this.value != null) ? String(this.value) : '';
2147 frameEl.appendChild(E('textarea', {
2148 'id': this.options.id ? 'widget.' + this.options.id : null,
2149 'name': this.options.name,
2150 'class': 'cbi-input-textarea',
2151 'readonly': this.options.readonly ? '' : null,
2152 'placeholder': this.options.placeholder,
2153 'style': !this.options.cols ? 'width:
100%' : null,
2154 'cols': this.options.cols,
2155 'rows': this.options.rows,
2156 'wrap': this.options.wrap ? '' : null
2159 if (this.options.monospace)
2160 frameEl.firstElementChild.style.fontFamily = 'monospace';
2162 return this.bind(frameEl);
2166 bind: function(frameEl) {
2167 var inputEl = frameEl.firstElementChild;
2169 this.node = frameEl;
2171 this.setUpdateEvents(inputEl, 'keyup', 'blur');
2172 this.setChangeEvents(inputEl, 'change');
2174 dom.bindClassInstance(frameEl, this);
2180 getValue: function() {
2181 return this.node.firstElementChild.value;
2185 setValue: function(value) {
2186 this.node.firstElementChild.value = value;
2191 * Instantiate a checkbox widget.
2193 * @constructor Checkbox
2195 * @augments LuCI.ui.AbstractElement
2199 * The `Checkbox` class implements a simple checkbox input field.
2201 * UI widget instances are usually not supposed to be created by view code
2202 * directly, instead they're implicitely created by `LuCI.form` when
2203 * instantiating CBI forms.
2205 * This class is automatically instantiated as part of `LuCI.ui`. To use it
2206 * in views, use `'require ui'` and refer to `ui.Checkbox`. To import it in
2207 * external JavaScript, use `L.require(
"ui").then(...)` and access the
2208 * `Checkbox` property of the class instance value.
2210 * @param {string} [value=null]
2211 * The initial input value.
2213 * @param {LuCI.ui.Checkbox.InitOptions} [options]
2214 * Object describing the widget specific options to initialize the input.
2216 var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ {
2218 * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
2219 * the following properties are recognized:
2221 * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
2222 * @memberof LuCI.ui.Checkbox
2224 * @property {string} [value_enabled=
1]
2225 * Specifies the value corresponding to a checked checkbox.
2227 * @property {string} [value_disabled=
0]
2228 * Specifies the value corresponding to an unchecked checkbox.
2230 * @property {string} [hiddenname]
2231 * Specifies the HTML `name` attribute of the hidden input backing the
2232 * checkbox. This is a legacy property existing for compatibility reasons,
2233 * it is required for HTML based form submissions.
2235 __init__: function(value, options) {
2237 this.options = Object.assign({
2244 render: function() {
2245 var id = 'cb%
08x'.format(Math.random() *
0xffffffff);
2246 var frameEl = E('div', {
2247 'id': this.options.id,
2248 'class': 'cbi-checkbox'
2251 if (this.options.hiddenname)
2252 frameEl.appendChild(E('input', {
2254 'name': this.options.hiddenname,
2258 frameEl.appendChild(E('input', {
2260 'name': this.options.name,
2262 'value': this.options.value_enabled,
2263 'checked': (this.value == this.options.value_enabled) ? '' : null,
2264 'data-widget-id': this.options.id ? 'widget.' + this.options.id : null
2267 frameEl.appendChild(E('label', { 'for': id }));
2269 return this.bind(frameEl);
2273 bind: function(frameEl) {
2274 this.node = frameEl;
2276 this.setUpdateEvents(frameEl.lastElementChild.previousElementSibling, 'click', 'blur');
2277 this.setChangeEvents(frameEl.lastElementChild.previousElementSibling, 'change');
2279 dom.bindClassInstance(frameEl, this);
2285 * Test whether the checkbox is currently checked.
2288 * @memberof LuCI.ui.Checkbox
2289 * @returns {boolean}
2290 * Returns `true` when the checkbox is currently checked, otherwise `false`.
2292 isChecked: function() {
2293 return this.node.lastElementChild.previousElementSibling.checked;
2297 getValue: function() {
2298 return this.isChecked()
2299 ? this.options.value_enabled
2300 : this.options.value_disabled;
2304 setValue: function(value) {
2305 this.node.lastElementChild.previousElementSibling.checked = (value == this.options.value_enabled);
2310 * Instantiate a select dropdown or checkbox/radiobutton group.
2312 * @constructor Select
2314 * @augments LuCI.ui.AbstractElement
2318 * The `Select` class implements either a traditional HTML `
<select
>` element
2319 * or a group of checkboxes or radio buttons, depending on whether multiple
2320 * values are enabled or not.
2322 * UI widget instances are usually not supposed to be created by view code
2323 * directly, instead they're implicitely created by `LuCI.form` when
2324 * instantiating CBI forms.
2326 * This class is automatically instantiated as part of `LuCI.ui`. To use it
2327 * in views, use `'require ui'` and refer to `ui.Select`. To import it in
2328 * external JavaScript, use `L.require(
"ui").then(...)` and access the
2329 * `Select` property of the class instance value.
2331 * @param {string|string[]} [value=null]
2332 * The initial input value(s).
2334 * @param {Object
<string, string
>} choices
2335 * Object containing the selectable choices of the widget. The object keys
2336 * serve as values for the different choices while the values are used as
2339 * @param {LuCI.ui.Select.InitOptions} [options]
2340 * Object describing the widget specific options to initialize the inputs.
2342 var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ {
2344 * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
2345 * the following properties are recognized:
2347 * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
2348 * @memberof LuCI.ui.Select
2350 * @property {boolean} [multiple=false]
2351 * Specifies whether multiple choice values may be selected.
2353 * @property {string} [widget=select]
2354 * Specifies the kind of widget to render. May be either `select` or
2355 * `individual`. When set to `select` an HTML `
<select
>` element will be
2356 * used, otherwise a group of checkbox or radio button elements is created,
2357 * depending on the value of the `multiple` option.
2359 * @property {string} [orientation=horizontal]
2360 * Specifies whether checkbox / radio button groups should be rendered
2361 * in a `horizontal` or `vertical` manner. Does not apply to the `select`
2364 * @property {boolean|string[]} [sort=false]
2365 * Specifies if and how to sort choice values. If set to `true`, the choice
2366 * values will be sorted alphabetically. If set to an array of strings, the
2367 * choice sort order is derived from the array.
2369 * @property {number} [size]
2370 * Specifies the HTML `size` attribute to set on the `
<select
>` element.
2371 * Only applicable to the `select` widget type.
2373 * @property {string} [placeholder=-- Please choose --]
2374 * Specifies a placeholder text which is displayed when no choice is
2375 * selected yet. Only applicable to the `select` widget type.
2377 __init__: function(value, choices, options) {
2378 if (!L.isObject(choices))
2381 if (!Array.isArray(value))
2382 value = (value != null
&& value != '') ? [ value ] : [];
2384 if (!options.multiple
&& value.length
> 1)
2387 this.values = value;
2388 this.choices = choices;
2389 this.options = Object.assign({
2392 orientation: 'horizontal'
2395 if (this.choices.hasOwnProperty(''))
2396 this.options.optional = true;
2400 render: function() {
2401 var frameEl = E('div', { 'id': this.options.id }),
2402 keys = Object.keys(this.choices);
2404 if (this.options.sort === true)
2406 else if (Array.isArray(this.options.sort))
2407 keys = this.options.sort;
2409 if (this.options.widget == 'select') {
2410 frameEl.appendChild(E('select', {
2411 'id': this.options.id ? 'widget.' + this.options.id : null,
2412 'name': this.options.name,
2413 'size': this.options.size,
2414 'class': 'cbi-input-select',
2415 'multiple': this.options.multiple ? '' : null
2418 if (this.options.optional)
2419 frameEl.lastChild.appendChild(E('option', {
2421 'selected': (this.values.length ==
0 || this.values[
0] == '') ? '' : null
2422 }, [ this.choices[''] || this.options.placeholder || _('-- Please choose --') ]));
2424 for (var i =
0; i
< keys.length; i++) {
2425 if (keys[i] == null || keys[i] == '')
2428 frameEl.lastChild.appendChild(E('option', {
2430 'selected': (this.values.indexOf(keys[i])
> -
1) ? '' : null
2431 }, [ this.choices[keys[i]] || keys[i] ]));
2435 var brEl = (this.options.orientation === 'horizontal') ? document.createTextNode(' ') : E('br');
2437 for (var i =
0; i
< keys.length; i++) {
2438 frameEl.appendChild(E('label', {}, [
2440 'id': this.options.id ? 'widget.' + this.options.id : null,
2441 'name': this.options.id || this.options.name,
2442 'type': this.options.multiple ? 'checkbox' : 'radio',
2443 'class': this.options.multiple ? 'cbi-input-checkbox' : 'cbi-input-radio',
2445 'checked': (this.values.indexOf(keys[i])
> -
1) ? '' : null
2447 this.choices[keys[i]] || keys[i]
2450 if (i +
1 == this.options.size)
2451 frameEl.appendChild(brEl);
2455 return this.bind(frameEl);
2459 bind: function(frameEl) {
2460 this.node = frameEl;
2462 if (this.options.widget == 'select') {
2463 this.setUpdateEvents(frameEl.firstChild, 'change', 'click', 'blur');
2464 this.setChangeEvents(frameEl.firstChild, 'change');
2467 var radioEls = frameEl.querySelectorAll('input[
type=
"radio"]');
2468 for (var i =
0; i
< radioEls.length; i++) {
2469 this.setUpdateEvents(radioEls[i], 'change', 'click', 'blur');
2470 this.setChangeEvents(radioEls[i], 'change', 'click', 'blur');
2474 dom.bindClassInstance(frameEl, this);
2480 getValue: function() {
2481 if (this.options.widget == 'select')
2482 return this.node.firstChild.value;
2484 var radioEls = frameEl.querySelectorAll('input[
type=
"radio"]');
2485 for (var i =
0; i
< radioEls.length; i++)
2486 if (radioEls[i].checked)
2487 return radioEls[i].value;
2493 setValue: function(value) {
2494 if (this.options.widget == 'select') {
2498 for (var i =
0; i
< this.node.firstChild.options.length; i++)
2499 this.node.firstChild.options[i].selected = (this.node.firstChild.options[i].value == value);
2504 var radioEls = frameEl.querySelectorAll('input[
type=
"radio"]');
2505 for (var i =
0; i
< radioEls.length; i++)
2506 radioEls[i].checked = (radioEls[i].value == value);
2511 * Instantiate a rich dropdown choice widget.
2513 * @constructor Dropdown
2515 * @augments LuCI.ui.AbstractElement
2519 * The `Dropdown` class implements a rich, stylable dropdown menu which
2520 * supports non-text choice labels.
2522 * UI widget instances are usually not supposed to be created by view code
2523 * directly, instead they're implicitely created by `LuCI.form` when
2524 * instantiating CBI forms.
2526 * This class is automatically instantiated as part of `LuCI.ui`. To use it
2527 * in views, use `'require ui'` and refer to `ui.Dropdown`. To import it in
2528 * external JavaScript, use `L.require(
"ui").then(...)` and access the
2529 * `Dropdown` property of the class instance value.
2531 * @param {string|string[]} [value=null]
2532 * The initial input value(s).
2534 * @param {Object
<string, *
>} choices
2535 * Object containing the selectable choices of the widget. The object keys
2536 * serve as values for the different choices while the values are used as
2539 * @param {LuCI.ui.Dropdown.InitOptions} [options]
2540 * Object describing the widget specific options to initialize the dropdown.
2542 var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
2544 * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
2545 * the following properties are recognized:
2547 * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
2548 * @memberof LuCI.ui.Dropdown
2550 * @property {boolean} [optional=true]
2551 * Specifies whether the dropdown selection is optional. In contrast to
2552 * other widgets, the `optional` constraint of dropdowns works differently;
2553 * instead of marking the widget invalid on empty values when set to `false`,
2554 * the user is not allowed to deselect all choices.
2556 * For single value dropdowns that means that no empty
"please select"
2557 * choice is offered and for multi value dropdowns, the last selected choice
2558 * may not be deselected without selecting another choice first.
2560 * @property {boolean} [multiple]
2561 * Specifies whether multiple choice values may be selected. It defaults
2562 * to `true` when an array is passed as input value to the constructor.
2564 * @property {boolean|string[]} [sort=false]
2565 * Specifies if and how to sort choice values. If set to `true`, the choice
2566 * values will be sorted alphabetically. If set to an array of strings, the
2567 * choice sort order is derived from the array.
2569 * @property {string} [select_placeholder=-- Please choose --]
2570 * Specifies a placeholder text which is displayed when no choice is
2573 * @property {string} [custom_placeholder=-- custom --]
2574 * Specifies a placeholder text which is displayed in the text input
2575 * field allowing to enter custom choice values. Only applicable if the
2576 * `create` option is set to `true`.
2578 * @property {boolean} [create=false]
2579 * Specifies whether custom choices may be entered into the dropdown
2582 * @property {string} [create_query=.create-item-input]
2583 * Specifies a CSS selector expression used to find the input element
2584 * which is used to enter custom choice values. This should not normally
2585 * be used except by widgets derived from the Dropdown class.
2587 * @property {string} [create_template=script[
type=
"item-template"]]
2588 * Specifies a CSS selector expression used to find an HTML element
2589 * serving as template for newly added custom choice values.
2591 * Any `{{value}}` placeholder string within the template elements text
2592 * content will be replaced by the user supplied choice value, the
2593 * resulting string is parsed as HTML and appended to the end of the
2594 * choice list. The template markup may specify one HTML element with a
2595 * `data-label-placeholder` attribute which is replaced by a matching
2596 * label value from the `choices` object or with the user supplied value
2597 * itself in case `choices` contains no matching choice label.
2599 * If the template element is not found or if no `create_template` selector
2600 * expression is specified, the default markup for newly created elements is
2601 * `
<li
data-value=
"{{value}}"><span
data-label-placeholder=
"true" /></li
>`.
2603 * @property {string} [create_markup]
2604 * This property allows specifying the markup for custom choices directly
2605 * instead of referring to a template element through CSS selectors.
2607 * Apart from that it works exactly like `create_template`.
2609 * @property {number} [display_items=
3]
2610 * Specifies the maximum amount of choice labels that should be shown in
2611 * collapsed dropdown state before further selected choices are cut off.
2613 * Only applicable when `multiple` is `true`.
2615 * @property {number} [dropdown_items=-
1]
2616 * Specifies the maximum amount of choices that should be shown when the
2617 * dropdown is open. If the amount of available choices exceeds this number,
2618 * the dropdown area must be scrolled to reach further items.
2620 * If set to `-
1`, the dropdown menu will attempt to show all choice values
2621 * and only resort to scrolling if the amount of choices exceeds the available
2622 * screen space above and below the dropdown widget.
2624 * @property {string} [placeholder]
2625 * This property serves as a shortcut to set both `select_placeholder` and
2626 * `custom_placeholder`. Either of these properties will fallback to
2627 * `placeholder` if not specified.
2629 * @property {boolean} [readonly=false]
2630 * Specifies whether the custom choice input field should be rendered
2631 * readonly. Only applicable when `create` is `true`.
2633 * @property {number} [maxlength]
2634 * Specifies the HTML `maxlength` attribute to set on the custom choice
2635 * `
<input
>` element. Note that this a legacy property that exists for
2636 * compatibility reasons. It is usually better to `maxlength(N)` validation
2637 * expression. Only applicable when `create` is `true`.
2639 __init__: function(value, choices, options) {
2640 if (typeof(choices) != 'object')
2643 if (!Array.isArray(value))
2644 this.values = (value != null
&& value != '') ? [ value ] : [];
2646 this.values = value;
2648 this.choices = choices;
2649 this.options = Object.assign({
2651 multiple: Array.isArray(value),
2653 select_placeholder: _('-- Please choose --'),
2654 custom_placeholder: _('-- custom --'),
2658 create_query: '.create-item-input',
2659 create_template: 'script[
type=
"item-template"]'
2664 render: function() {
2666 'id': this.options.id,
2667 'class': 'cbi-dropdown',
2668 'multiple': this.options.multiple ? '' : null,
2669 'optional': this.options.optional ? '' : null,
2672 var keys = Object.keys(this.choices);
2674 if (this.options.sort === true)
2676 else if (Array.isArray(this.options.sort))
2677 keys = this.options.sort;
2679 if (this.options.create)
2680 for (var i =
0; i
< this.values.length; i++)
2681 if (!this.choices.hasOwnProperty(this.values[i]))
2682 keys.push(this.values[i]);
2684 for (var i =
0; i
< keys.length; i++) {
2685 var label = this.choices[keys[i]];
2687 if (dom.elem(label))
2688 label = label.cloneNode(true);
2690 sb.lastElementChild.appendChild(E('li', {
2691 'data-value': keys[i],
2692 'selected': (this.values.indexOf(keys[i])
> -
1) ? '' : null
2693 }, [ label || keys[i] ]));
2696 if (this.options.create) {
2697 var createEl = E('input', {
2699 'class': 'create-item-input',
2700 'readonly': this.options.readonly ? '' : null,
2701 'maxlength': this.options.maxlength,
2702 'placeholder': this.options.custom_placeholder || this.options.placeholder
2705 if (this.options.datatype || this.options.validate)
2706 UI.prototype.addValidator(createEl, this.options.datatype || 'string',
2707 true, this.options.validate, 'blur', 'keyup');
2709 sb.lastElementChild.appendChild(E('li', { 'data-value': '-' }, createEl));
2712 if (this.options.create_markup)
2713 sb.appendChild(E('script', { type: 'item-template' },
2714 this.options.create_markup));
2716 return this.bind(sb);
2720 bind: function(sb) {
2721 var o = this.options;
2723 o.multiple = sb.hasAttribute('multiple');
2724 o.optional = sb.hasAttribute('optional');
2725 o.placeholder = sb.getAttribute('placeholder') || o.placeholder;
2726 o.display_items = parseInt(sb.getAttribute('display-items') || o.display_items);
2727 o.dropdown_items = parseInt(sb.getAttribute('dropdown-items') || o.dropdown_items);
2728 o.create_query = sb.getAttribute('item-create') || o.create_query;
2729 o.create_template = sb.getAttribute('item-template') || o.create_template;
2731 var ul = sb.querySelector('ul'),
2732 more = sb.appendChild(E('span', { class: 'more', tabindex: -
1 }, '···')),
2733 open = sb.appendChild(E('span', { class: 'open', tabindex: -
1 }, '▾')),
2734 canary = sb.appendChild(E('div')),
2735 create = sb.querySelector(this.options.create_query),
2736 ndisplay = this.options.display_items,
2739 if (this.options.multiple) {
2740 var items = ul.querySelectorAll('li');
2742 for (var i =
0; i
< items.length; i++) {
2743 this.transformItem(sb, items[i]);
2745 if (items[i].hasAttribute('selected')
&& ndisplay--
> 0)
2746 items[i].setAttribute('display', n++);
2750 if (this.options.optional
&& !ul.querySelector('li[
data-value=
""]')) {
2751 var placeholder = E('li', { placeholder: '' },
2752 this.options.select_placeholder || this.options.placeholder);
2755 ? ul.insertBefore(placeholder, ul.firstChild)
2756 : ul.appendChild(placeholder);
2759 var items = ul.querySelectorAll('li'),
2760 sel = sb.querySelectorAll('[selected]');
2762 sel.forEach(function(s) {
2763 s.removeAttribute('selected');
2766 var s = sel[
0] || items[
0];
2768 s.setAttribute('selected', '');
2769 s.setAttribute('display', n++);
2775 this.saveValues(sb, ul);
2777 ul.setAttribute('tabindex', -
1);
2778 sb.setAttribute('tabindex',
0);
2780 if (ndisplay
< 0)
2781 sb.setAttribute('more', '')
2783 sb.removeAttribute('more');
2785 if (ndisplay == this.options.display_items)
2786 sb.setAttribute('empty', '')
2788 sb.removeAttribute('empty');
2790 dom.content(more, (ndisplay == this.options.display_items)
2791 ? (this.options.select_placeholder || this.options.placeholder) : '···');
2794 sb.addEventListener('click', this.handleClick.bind(this));
2795 sb.addEventListener('keydown', this.handleKeydown.bind(this));
2796 sb.addEventListener('cbi-dropdown-close', this.handleDropdownClose.bind(this));
2797 sb.addEventListener('cbi-dropdown-select', this.handleDropdownSelect.bind(this));
2799 if ('ontouchstart' in window) {
2800 sb.addEventListener('touchstart', function(ev) { ev.stopPropagation(); });
2801 window.addEventListener('touchstart', this.closeAllDropdowns);
2804 sb.addEventListener('mouseover', this.handleMouseover.bind(this));
2805 sb.addEventListener('focus', this.handleFocus.bind(this));
2807 canary.addEventListener('focus', this.handleCanaryFocus.bind(this));
2809 window.addEventListener('mouseover', this.setFocus);
2810 window.addEventListener('click', this.closeAllDropdowns);
2814 create.addEventListener('keydown', this.handleCreateKeydown.bind(this));
2815 create.addEventListener('focus', this.handleCreateFocus.bind(this));
2816 create.addEventListener('blur', this.handleCreateBlur.bind(this));
2818 var li = findParent(create, 'li');
2820 li.setAttribute('unselectable', '');
2821 li.addEventListener('click', this.handleCreateClick.bind(this));
2826 this.setUpdateEvents(sb, 'cbi-dropdown-open', 'cbi-dropdown-close');
2827 this.setChangeEvents(sb, 'cbi-dropdown-change', 'cbi-dropdown-close');
2829 dom.bindClassInstance(sb, this);
2835 openDropdown: function(sb) {
2836 var st = window.getComputedStyle(sb, null),
2837 ul = sb.querySelector('ul'),
2838 li = ul.querySelectorAll('li'),
2839 fl = findParent(sb, '.cbi-value-field'),
2840 sel = ul.querySelector('[selected]'),
2841 rect = sb.getBoundingClientRect(),
2842 items = Math.min(this.options.dropdown_items, li.length);
2844 document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
2845 s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
2848 sb.setAttribute('open', '');
2850 var pv = ul.cloneNode(true);
2851 pv.classList.add('preview');
2854 fl.classList.add('cbi-dropdown-open');
2856 if ('ontouchstart' in window) {
2857 var vpWidth = Math.max(document.documentElement.clientWidth, window.innerWidth ||
0),
2858 vpHeight = Math.max(document.documentElement.clientHeight, window.innerHeight ||
0),
2861 ul.style.top = sb.offsetHeight + 'px';
2862 ul.style.left = -rect.left + 'px';
2863 ul.style.right = (rect.right - vpWidth) + 'px';
2864 ul.style.maxHeight = (vpHeight *
0.5) + 'px';
2865 ul.style.WebkitOverflowScrolling = 'touch';
2867 function getScrollParent(element) {
2868 var parent = element,
2869 style = getComputedStyle(element),
2870 excludeStaticParent = (style.position === 'absolute');
2872 if (style.position === 'fixed')
2873 return document.body;
2875 while ((parent = parent.parentElement) != null) {
2876 style = getComputedStyle(parent);
2878 if (excludeStaticParent
&& style.position === 'static')
2881 if (/(auto|scroll)/.test(style.overflow + style.overflowY + style.overflowX))
2885 return document.body;
2888 var scrollParent = getScrollParent(sb),
2889 scrollFrom = scrollParent.scrollTop,
2890 scrollTo = scrollFrom + rect.top - vpHeight *
0.5;
2892 var scrollStep = function(timestamp) {
2895 ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight,
0) :
0;
2898 var duration = Math.max(timestamp - start,
1);
2899 if (duration
< 100) {
2900 scrollParent.scrollTop = scrollFrom + (scrollTo - scrollFrom) * (duration /
100);
2901 window.requestAnimationFrame(scrollStep);
2904 scrollParent.scrollTop = scrollTo;
2908 window.requestAnimationFrame(scrollStep);
2911 ul.style.maxHeight = '
1px';
2912 ul.style.top = ul.style.bottom = '';
2914 window.requestAnimationFrame(function() {
2915 var itemHeight = li[Math.max(
0, li.length -
2)].getBoundingClientRect().height,
2917 spaceAbove = rect.top,
2918 spaceBelow = window.innerHeight - rect.height - rect.top;
2920 for (var i =
0; i
< (items == -
1 ? li.length : items); i++)
2921 fullHeight += li[i].getBoundingClientRect().height;
2923 if (fullHeight
<= spaceBelow) {
2924 ul.style.top = rect.height + 'px';
2925 ul.style.maxHeight = spaceBelow + 'px';
2927 else if (fullHeight
<= spaceAbove) {
2928 ul.style.bottom = rect.height + 'px';
2929 ul.style.maxHeight = spaceAbove + 'px';
2931 else if (spaceBelow
>= spaceAbove) {
2932 ul.style.top = rect.height + 'px';
2933 ul.style.maxHeight = (spaceBelow - (spaceBelow % itemHeight)) + 'px';
2936 ul.style.bottom = rect.height + 'px';
2937 ul.style.maxHeight = (spaceAbove - (spaceAbove % itemHeight)) + 'px';
2940 ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight,
0) :
0;
2944 var cboxes = ul.querySelectorAll('[selected] input[
type=
"checkbox"]');
2945 for (var i =
0; i
< cboxes.length; i++) {
2946 cboxes[i].checked = true;
2947 cboxes[i].disabled = (cboxes.length ==
1 && !this.options.optional);
2950 ul.classList.add('dropdown');
2952 sb.insertBefore(pv, ul.nextElementSibling);
2954 li.forEach(function(l) {
2955 l.setAttribute('tabindex',
0);
2958 sb.lastElementChild.setAttribute('tabindex',
0);
2960 this.setFocus(sb, sel || li[
0], true);
2964 closeDropdown: function(sb, no_focus) {
2965 if (!sb.hasAttribute('open'))
2968 var pv = sb.querySelector('ul.preview'),
2969 ul = sb.querySelector('ul.dropdown'),
2970 li = ul.querySelectorAll('li'),
2971 fl = findParent(sb, '.cbi-value-field');
2973 li.forEach(function(l) { l.removeAttribute('tabindex'); });
2974 sb.lastElementChild.removeAttribute('tabindex');
2977 sb.removeAttribute('open');
2978 sb.style.width = sb.style.height = '';
2980 ul.classList.remove('dropdown');
2981 ul.style.top = ul.style.bottom = ul.style.maxHeight = '';
2984 fl.classList.remove('cbi-dropdown-open');
2987 this.setFocus(sb, sb);
2989 this.saveValues(sb, ul);
2993 toggleItem: function(sb, li, force_state) {
2994 if (li.hasAttribute('unselectable'))
2997 if (this.options.multiple) {
2998 var cbox = li.querySelector('input[
type=
"checkbox"]'),
2999 items = li.parentNode.querySelectorAll('li'),
3000 label = sb.querySelector('ul.preview'),
3001 sel = li.parentNode.querySelectorAll('[selected]').length,
3002 more = sb.querySelector('.more'),
3003 ndisplay = this.options.display_items,
3006 if (li.hasAttribute('selected')) {
3007 if (force_state !== true) {
3008 if (sel
> 1 || this.options.optional) {
3009 li.removeAttribute('selected');
3010 cbox.checked = cbox.disabled = false;
3014 cbox.disabled = true;
3019 if (force_state !== false) {
3020 li.setAttribute('selected', '');
3021 cbox.checked = true;
3022 cbox.disabled = false;
3027 while (label
&& label.firstElementChild)
3028 label.removeChild(label.firstElementChild);
3030 for (var i =
0; i
< items.length; i++) {
3031 items[i].removeAttribute('display');
3032 if (items[i].hasAttribute('selected')) {
3033 if (ndisplay--
> 0) {
3034 items[i].setAttribute('display', n++);
3036 label.appendChild(items[i].cloneNode(true));
3038 var c = items[i].querySelector('input[
type=
"checkbox"]');
3040 c.disabled = (sel ==
1 && !this.options.optional);
3044 if (ndisplay
< 0)
3045 sb.setAttribute('more', '');
3047 sb.removeAttribute('more');
3049 if (ndisplay === this.options.display_items)
3050 sb.setAttribute('empty', '');
3052 sb.removeAttribute('empty');
3054 dom.content(more, (ndisplay === this.options.display_items)
3055 ? (this.options.select_placeholder || this.options.placeholder) : '···');
3058 var sel = li.parentNode.querySelector('[selected]');
3060 sel.removeAttribute('display');
3061 sel.removeAttribute('selected');
3064 li.setAttribute('display',
0);
3065 li.setAttribute('selected', '');
3067 this.closeDropdown(sb, true);
3070 this.saveValues(sb, li.parentNode);
3074 transformItem: function(sb, li) {
3075 var cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -
1, onclick: 'event.preventDefault()' })),
3078 while (li.firstChild)
3079 label.appendChild(li.firstChild);
3081 li.appendChild(cbox);
3082 li.appendChild(label);
3086 saveValues: function(sb, ul) {
3087 var sel = ul.querySelectorAll('li[selected]'),
3088 div = sb.lastElementChild,
3089 name = this.options.name,
3093 while (div.lastElementChild)
3094 div.removeChild(div.lastElementChild);
3096 sel.forEach(function (s) {
3097 if (s.hasAttribute('placeholder'))
3102 value: s.hasAttribute('data-value') ? s.getAttribute('data-value') : s.innerText,
3106 div.appendChild(E('input', {
3114 strval += strval.length ? ' ' + v.value : v.value;
3122 if (this.options.multiple)
3123 detail.values = values;
3125 detail.value = values.length ? values[
0] : null;
3129 sb.dispatchEvent(new CustomEvent('cbi-dropdown-change', {
3136 setValues: function(sb, values) {
3137 var ul = sb.querySelector('ul');
3139 if (this.options.create) {
3140 for (var value in values) {
3141 this.createItems(sb, value);
3143 if (!this.options.multiple)
3148 if (this.options.multiple) {
3149 var lis = ul.querySelectorAll('li[data-value]');
3150 for (var i =
0; i
< lis.length; i++) {
3151 var value = lis[i].getAttribute('data-value');
3152 if (values === null || !(value in values))
3153 this.toggleItem(sb, lis[i], false);
3155 this.toggleItem(sb, lis[i], true);
3159 var ph = ul.querySelector('li[placeholder]');
3161 this.toggleItem(sb, ph);
3163 var lis = ul.querySelectorAll('li[data-value]');
3164 for (var i =
0; i
< lis.length; i++) {
3165 var value = lis[i].getAttribute('data-value');
3166 if (values !== null
&& (value in values))
3167 this.toggleItem(sb, lis[i]);
3173 setFocus: function(sb, elem, scroll) {
3174 if (sb
&& sb.hasAttribute
&& sb.hasAttribute('locked-in'))
3177 if (sb.target
&& findParent(sb.target, 'ul.dropdown'))
3180 document.querySelectorAll('.focus').forEach(function(e) {
3181 if (!matchesElem(e, 'input')) {
3182 e.classList.remove('focus');
3189 elem.classList.add('focus');
3192 elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop;
3197 createChoiceElement: function(sb, value, label) {
3198 var tpl = sb.querySelector(this.options.create_template),
3202 markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^
<!--|-->$/, '').trim();
3204 markup = '
<li
data-value=
"{{value}}"><span
data-label-placeholder=
"true" /></li
>';
3206 var new_item = E(markup.replace(/{{value}}/g, '%h'.format(value))),
3207 placeholder = new_item.querySelector('[data-label-placeholder]');
3210 var content = E('span', {}, label || this.choices[value] || [ value ]);
3212 while (content.firstChild)
3213 placeholder.parentNode.insertBefore(content.firstChild, placeholder);
3215 placeholder.parentNode.removeChild(placeholder);
3218 if (this.options.multiple)
3219 this.transformItem(sb, new_item);
3225 createItems: function(sb, value) {
3227 val = (value || '').trim(),
3228 ul = sb.querySelector('ul');
3230 if (!sbox.options.multiple)
3231 val = val.length ? [ val ] : [];
3233 val = val.length ? val.split(/\s+/) : [];
3235 val.forEach(function(item) {
3236 var new_item = null;
3238 ul.childNodes.forEach(function(li) {
3239 if (li.getAttribute
&& li.getAttribute('data-value') === item)
3244 new_item = sbox.createChoiceElement(sb, item);
3246 if (!sbox.options.multiple) {
3247 var old = ul.querySelector('li[created]');
3249 ul.removeChild(old);
3251 new_item.setAttribute('created', '');
3254 new_item = ul.insertBefore(new_item, ul.lastElementChild);
3257 sbox.toggleItem(sb, new_item, true);
3258 sbox.setFocus(sb, new_item, true);
3263 * Remove all existing choices from the dropdown menu.
3265 * This function removes all preexisting dropdown choices from the widget,
3266 * keeping only choices currently being selected unless `reset_values` is
3267 * given, in which case all choices and deselected and removed.
3270 * @memberof LuCI.ui.Dropdown
3271 * @param {boolean} [reset_value=false]
3272 * If set to `true`, deselect and remove selected choices as well instead
3275 clearChoices: function(reset_value) {
3276 var ul = this.node.querySelector('ul'),
3277 lis = ul ? ul.querySelectorAll('li[data-value]') : [],
3278 len = lis.length - (this.options.create ?
1 :
0),
3279 val = reset_value ? null : this.getValue();
3281 for (var i =
0; i
< len; i++) {
3282 var lival = lis[i].getAttribute('data-value');
3284 (!this.options.multiple
&& val != lival) ||
3285 (this.options.multiple
&& val.indexOf(lival) == -
1))
3286 ul.removeChild(lis[i]);
3290 this.setValues(this.node, {});
3294 * Add new choices to the dropdown menu.
3296 * This function adds further choices to an existing dropdown menu,
3297 * ignoring choice values which are already present.
3300 * @memberof LuCI.ui.Dropdown
3301 * @param {string[]} values
3302 * The choice values to add to the dropdown widget.
3304 * @param {Object
<string, *
>} labels
3305 * The choice label values to use when adding dropdown choices. If no
3306 * label is found for a particular choice value, the value itself is used
3307 * as label text. Choice labels may be any valid value accepted by
3308 * {@link LuCI.dom#content}.
3310 addChoices: function(values, labels) {
3312 ul = sb.querySelector('ul'),
3313 lis = ul ? ul.querySelectorAll('li[data-value]') : [];
3315 if (!Array.isArray(values))
3316 values = L.toArray(values);
3318 if (!L.isObject(labels))
3321 for (var i =
0; i
< values.length; i++) {
3324 for (var j =
0; j
< lis.length; j++) {
3325 if (lis[j].getAttribute('data-value') === values[i]) {
3335 this.createChoiceElement(sb, values[i], labels[values[i]]),
3336 ul.lastElementChild);
3341 * Close all open dropdown widgets in the current document.
3343 closeAllDropdowns: function() {
3344 document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
3345 s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
3350 handleClick: function(ev) {
3351 var sb = ev.currentTarget;
3353 if (!sb.hasAttribute('open')) {
3354 if (!matchesElem(ev.target, 'input'))
3355 this.openDropdown(sb);
3358 var li = findParent(ev.target, 'li');
3359 if (li
&& li.parentNode.classList.contains('dropdown'))
3360 this.toggleItem(sb, li);
3361 else if (li
&& li.parentNode.classList.contains('preview'))
3362 this.closeDropdown(sb);
3363 else if (matchesElem(ev.target, 'span.open, span.more'))
3364 this.closeDropdown(sb);
3367 ev.preventDefault();
3368 ev.stopPropagation();
3372 handleKeydown: function(ev) {
3373 var sb = ev.currentTarget;
3375 if (matchesElem(ev.target, 'input'))
3378 if (!sb.hasAttribute('open')) {
3379 switch (ev.keyCode) {
3384 this.openDropdown(sb);
3385 ev.preventDefault();
3389 var active = findParent(document.activeElement, 'li');
3391 switch (ev.keyCode) {
3393 this.closeDropdown(sb);
3398 if (!active.hasAttribute('selected'))
3399 this.toggleItem(sb, active);
3400 this.closeDropdown(sb);
3401 ev.preventDefault();
3407 this.toggleItem(sb, active);
3408 ev.preventDefault();
3413 if (active
&& active.previousElementSibling) {
3414 this.setFocus(sb, active.previousElementSibling);
3415 ev.preventDefault();
3420 if (active
&& active.nextElementSibling) {
3421 this.setFocus(sb, active.nextElementSibling);
3422 ev.preventDefault();
3430 handleDropdownClose: function(ev) {
3431 var sb = ev.currentTarget;
3433 this.closeDropdown(sb, true);
3437 handleDropdownSelect: function(ev) {
3438 var sb = ev.currentTarget,
3439 li = findParent(ev.target, 'li');
3444 this.toggleItem(sb, li);
3445 this.closeDropdown(sb, true);
3449 handleMouseover: function(ev) {
3450 var sb = ev.currentTarget;
3452 if (!sb.hasAttribute('open'))
3455 var li = findParent(ev.target, 'li');
3457 if (li
&& li.parentNode.classList.contains('dropdown'))
3458 this.setFocus(sb, li);
3462 handleFocus: function(ev) {
3463 var sb = ev.currentTarget;
3465 document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
3466 if (s !== sb || sb.hasAttribute('open'))
3467 s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
3472 handleCanaryFocus: function(ev) {
3473 this.closeDropdown(ev.currentTarget.parentNode);
3477 handleCreateKeydown: function(ev) {
3478 var input = ev.currentTarget,
3479 sb = findParent(input, '.cbi-dropdown');
3481 switch (ev.keyCode) {
3483 ev.preventDefault();
3485 if (input.classList.contains('cbi-input-invalid'))
3488 this.createItems(sb, input.value);
3496 handleCreateFocus: function(ev) {
3497 var input = ev.currentTarget,
3498 cbox = findParent(input, 'li').querySelector('input[
type=
"checkbox"]'),
3499 sb = findParent(input, '.cbi-dropdown');
3502 cbox.checked = true;
3504 sb.setAttribute('locked-in', '');
3508 handleCreateBlur: function(ev) {
3509 var input = ev.currentTarget,
3510 cbox = findParent(input, 'li').querySelector('input[
type=
"checkbox"]'),
3511 sb = findParent(input, '.cbi-dropdown');
3514 cbox.checked = false;
3516 sb.removeAttribute('locked-in');
3520 handleCreateClick: function(ev) {
3521 ev.currentTarget.querySelector(this.options.create_query).focus();
3525 setValue: function(values) {
3526 if (this.options.multiple) {
3527 if (!Array.isArray(values))
3528 values = (values != null
&& values != '') ? [ values ] : [];
3532 for (var i =
0; i
< values.length; i++)
3533 v[values[i]] = true;
3535 this.setValues(this.node, v);
3540 if (values != null) {
3541 if (Array.isArray(values))
3542 v[values[
0]] = true;
3547 this.setValues(this.node, v);
3552 getValue: function() {
3553 var div = this.node.lastElementChild,
3554 h = div.querySelectorAll('input[
type=
"hidden"]'),
3557 for (var i =
0; i
< h.length; i++)
3560 return this.options.multiple ? v : v[
0];
3565 * Instantiate a rich dropdown choice widget allowing custom values.
3567 * @constructor Combobox
3569 * @augments LuCI.ui.Dropdown
3573 * The `Combobox` class implements a rich, stylable dropdown menu which allows
3574 * to enter custom values. Historically, comboboxes used to be a dedicated
3575 * widget type in LuCI but nowadays they are direct aliases of dropdown widgets
3576 * with a set of enforced default properties for easier instantiation.
3578 * UI widget instances are usually not supposed to be created by view code
3579 * directly, instead they're implicitely created by `LuCI.form` when
3580 * instantiating CBI forms.
3582 * This class is automatically instantiated as part of `LuCI.ui`. To use it
3583 * in views, use `'require ui'` and refer to `ui.Combobox`. To import it in
3584 * external JavaScript, use `L.require(
"ui").then(...)` and access the
3585 * `Combobox` property of the class instance value.
3587 * @param {string|string[]} [value=null]
3588 * The initial input value(s).
3590 * @param {Object
<string, *
>} choices
3591 * Object containing the selectable choices of the widget. The object keys
3592 * serve as values for the different choices while the values are used as
3595 * @param {LuCI.ui.Combobox.InitOptions} [options]
3596 * Object describing the widget specific options to initialize the dropdown.
3598 var UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ {
3600 * Comboboxes support the same properties as
3601 * [Dropdown.InitOptions]{@link LuCI.ui.Dropdown.InitOptions} but enforce
3602 * specific values for the following properties:
3604 * @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
3605 * @memberof LuCI.ui.Combobox
3607 * @property {boolean} multiple=false
3608 * Since Comboboxes never allow selecting multiple values, this property
3609 * is forcibly set to `false`.
3611 * @property {boolean} create=true
3612 * Since Comboboxes always allow custom choice values, this property is
3613 * forcibly set to `true`.
3615 * @property {boolean} optional=true
3616 * Since Comboboxes are always optional, this property is forcibly set to
3619 __init__: function(value, choices, options) {
3620 this.super('__init__', [ value, choices, Object.assign({
3621 select_placeholder: _('-- Please choose --'),
3622 custom_placeholder: _('-- custom --'),
3634 * Instantiate a combo button widget offering multiple action choices.
3636 * @constructor ComboButton
3638 * @augments LuCI.ui.Dropdown
3642 * The `ComboButton` class implements a button element which can be expanded
3643 * into a dropdown to chose from a set of different action choices.
3645 * UI widget instances are usually not supposed to be created by view code
3646 * directly, instead they're implicitely created by `LuCI.form` when
3647 * instantiating CBI forms.
3649 * This class is automatically instantiated as part of `LuCI.ui`. To use it
3650 * in views, use `'require ui'` and refer to `ui.ComboButton`. To import it in
3651 * external JavaScript, use `L.require(
"ui").then(...)` and access the
3652 * `ComboButton` property of the class instance value.
3654 * @param {string|string[]} [value=null]
3655 * The initial input value(s).
3657 * @param {Object
<string, *
>} choices
3658 * Object containing the selectable choices of the widget. The object keys
3659 * serve as values for the different choices while the values are used as
3662 * @param {LuCI.ui.ComboButton.InitOptions} [options]
3663 * Object describing the widget specific options to initialize the button.
3665 var UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype */ {
3667 * ComboButtons support the same properties as
3668 * [Dropdown.InitOptions]{@link LuCI.ui.Dropdown.InitOptions} but enforce
3669 * specific values for some properties and add aditional button specific
3672 * @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
3673 * @memberof LuCI.ui.ComboButton
3675 * @property {boolean} multiple=false
3676 * Since ComboButtons never allow selecting multiple actions, this property
3677 * is forcibly set to `false`.
3679 * @property {boolean} create=false
3680 * Since ComboButtons never allow creating custom choices, this property
3681 * is forcibly set to `false`.
3683 * @property {boolean} optional=false
3684 * Since ComboButtons must always select one action, this property is
3685 * forcibly set to `false`.
3687 * @property {Object
<string, string
>} [classes]
3688 * Specifies a mapping of choice values to CSS class names. If an action
3689 * choice is selected by the user and if a corresponding entry exists in
3690 * the `classes` object, the class names corresponding to the selected
3691 * value are set on the button element.
3693 * This is useful to apply different button styles, such as colors, to the
3694 * combined button depending on the selected action.
3696 * @property {function} [click]
3697 * Specifies a handler function to invoke when the user clicks the button.
3698 * This function will be called with the button DOM node as `this` context
3699 * and receive the DOM click event as first as well as the selected action
3700 * choice value as second argument.
3702 __init__: function(value, choices, options) {
3703 this.super('__init__', [ value, choices, Object.assign({
3713 render: function(/* ... */) {
3714 var node = UIDropdown.prototype.render.apply(this, arguments),
3715 val = this.getValue();
3717 if (L.isObject(this.options.classes)
&& this.options.classes.hasOwnProperty(val))
3718 node.setAttribute('class', 'cbi-dropdown ' + this.options.classes[val]);
3724 handleClick: function(ev) {
3725 var sb = ev.currentTarget,
3728 if (sb.hasAttribute('open') || dom.matches(t, '.cbi-dropdown
> span.open'))
3729 return UIDropdown.prototype.handleClick.apply(this, arguments);
3731 if (this.options.click)
3732 return this.options.click.call(sb, ev, this.getValue());
3736 toggleItem: function(sb /*, ... */) {
3737 var rv = UIDropdown.prototype.toggleItem.apply(this, arguments),
3738 val = this.getValue();
3740 if (L.isObject(this.options.classes)
&& this.options.classes.hasOwnProperty(val))
3741 sb.setAttribute('class', 'cbi-dropdown ' + this.options.classes[val]);
3743 sb.setAttribute('class', 'cbi-dropdown');
3750 * Instantiate a dynamic list widget.
3752 * @constructor DynamicList
3754 * @augments LuCI.ui.AbstractElement
3758 * The `DynamicList` class implements a widget which allows the user to specify
3759 * an arbitrary amount of input values, either from free formed text input or
3760 * from a set of predefined choices.
3762 * UI widget instances are usually not supposed to be created by view code
3763 * directly, instead they're implicitely created by `LuCI.form` when
3764 * instantiating CBI forms.
3766 * This class is automatically instantiated as part of `LuCI.ui`. To use it
3767 * in views, use `'require ui'` and refer to `ui.DynamicList`. To import it in
3768 * external JavaScript, use `L.require(
"ui").then(...)` and access the
3769 * `DynamicList` property of the class instance value.
3771 * @param {string|string[]} [value=null]
3772 * The initial input value(s).
3774 * @param {Object
<string, *
>} [choices]
3775 * Object containing the selectable choices of the widget. The object keys
3776 * serve as values for the different choices while the values are used as
3777 * choice labels. If omitted, no default choices are presented to the user,
3778 * instead a plain text input field is rendered allowing the user to add
3779 * arbitrary values to the dynamic list.
3781 * @param {LuCI.ui.DynamicList.InitOptions} [options]
3782 * Object describing the widget specific options to initialize the dynamic list.
3784 var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ {
3786 * In case choices are passed to the dynamic list contructor, the widget
3787 * supports the same properties as [Dropdown.InitOptions]{@link LuCI.ui.Dropdown.InitOptions}
3788 * but enforces specific values for some dropdown properties.
3790 * @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
3791 * @memberof LuCI.ui.DynamicList
3793 * @property {boolean} multiple=false
3794 * Since dynamic lists never allow selecting multiple choices when adding
3795 * another list item, this property is forcibly set to `false`.
3797 * @property {boolean} optional=true
3798 * Since dynamic lists use an embedded dropdown to present a list of
3799 * predefined choice values, the dropdown must be made optional to allow
3800 * it to remain unselected.
3802 __init__: function(values, choices, options) {
3803 if (!Array.isArray(values))
3804 values = (values != null
&& values != '') ? [ values ] : [];
3806 if (typeof(choices) != 'object')
3809 this.values = values;
3810 this.choices = choices;
3811 this.options = Object.assign({}, options, {
3818 render: function() {
3820 'id': this.options.id,
3821 'class': 'cbi-dynlist'
3822 }, E('div', { 'class': 'add-item' }));
3825 if (this.options.placeholder != null)
3826 this.options.select_placeholder = this.options.placeholder;
3828 var cbox = new UICombobox(null, this.choices, this.options);
3830 dl.lastElementChild.appendChild(cbox.render());
3833 var inputEl = E('input', {
3834 'id': this.options.id ? 'widget.' + this.options.id : null,
3836 'class': 'cbi-input-text',
3837 'placeholder': this.options.placeholder
3840 dl.lastElementChild.appendChild(inputEl);
3841 dl.lastElementChild.appendChild(E('div', { 'class': 'btn cbi-button cbi-button-add' }, '+'));
3843 if (this.options.datatype || this.options.validate)
3844 UI.prototype.addValidator(inputEl, this.options.datatype || 'string',
3845 true, this.options.validate, 'blur', 'keyup');
3848 for (var i =
0; i
< this.values.length; i++) {
3849 var label = this.choices ? this.choices[this.values[i]] : null;
3851 if (dom.elem(label))
3852 label = label.cloneNode(true);
3854 this.addItem(dl, this.values[i], label);
3857 return this.bind(dl);
3861 bind: function(dl) {
3862 dl.addEventListener('click', L.bind(this.handleClick, this));
3863 dl.addEventListener('keydown', L.bind(this.handleKeydown, this));
3864 dl.addEventListener('cbi-dropdown-change', L.bind(this.handleDropdownChange, this));
3868 this.setUpdateEvents(dl, 'cbi-dynlist-change');
3869 this.setChangeEvents(dl, 'cbi-dynlist-change');
3871 dom.bindClassInstance(dl, this);
3877 addItem: function(dl, value, text, flash) {
3879 new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex':
0 }, [
3880 E('span', {}, [ text || value ]),
3883 'name': this.options.name,
3884 'value': value })]);
3886 dl.querySelectorAll('.item').forEach(function(item) {
3890 var hidden = item.querySelector('input[
type=
"hidden"]');
3892 if (hidden
&& hidden.parentNode !== item)
3895 if (hidden
&& hidden.value === value)
3900 var ai = dl.querySelector('.add-item');
3901 ai.parentNode.insertBefore(new_item, ai);
3904 dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
3916 removeItem: function(dl, item) {
3917 var value = item.querySelector('input[
type=
"hidden"]').value;
3918 var sb = dl.querySelector('.cbi-dropdown');
3920 sb.querySelectorAll('ul
> li').forEach(function(li) {
3921 if (li.getAttribute('data-value') === value) {
3922 if (li.hasAttribute('dynlistcustom'))
3923 li.parentNode.removeChild(li);
3925 li.removeAttribute('unselectable');
3929 item.parentNode.removeChild(item);
3931 dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
3943 handleClick: function(ev) {
3944 var dl = ev.currentTarget,
3945 item = findParent(ev.target, '.item');
3948 this.removeItem(dl, item);
3950 else if (matchesElem(ev.target, '.cbi-button-add')) {
3951 var input = ev.target.previousElementSibling;
3952 if (input.value.length
&& !input.classList.contains('cbi-input-invalid')) {
3953 this.addItem(dl, input.value, null, true);
3960 handleDropdownChange: function(ev) {
3961 var dl = ev.currentTarget,
3962 sbIn = ev.detail.instance,
3963 sbEl = ev.detail.element,
3964 sbVal = ev.detail.value;
3969 sbIn.setValues(sbEl, null);
3970 sbVal.element.setAttribute('unselectable', '');
3972 if (sbVal.element.hasAttribute('created')) {
3973 sbVal.element.removeAttribute('created');
3974 sbVal.element.setAttribute('dynlistcustom', '');
3977 var label = sbVal.text;
3979 if (sbVal.element) {
3982 for (var i =
0; i
< sbVal.element.childNodes.length; i++)
3983 label.appendChild(sbVal.element.childNodes[i].cloneNode(true));
3986 this.addItem(dl, sbVal.value, label, true);
3990 handleKeydown: function(ev) {
3991 var dl = ev.currentTarget,
3992 item = findParent(ev.target, '.item');
3995 switch (ev.keyCode) {
3996 case
8: /* backspace */
3997 if (item.previousElementSibling)
3998 item.previousElementSibling.focus();
4000 this.removeItem(dl, item);
4003 case
46: /* delete */
4004 if (item.nextElementSibling) {
4005 if (item.nextElementSibling.classList.contains('item'))
4006 item.nextElementSibling.focus();
4008 item.nextElementSibling.firstElementChild.focus();
4011 this.removeItem(dl, item);
4015 else if (matchesElem(ev.target, '.cbi-input-text')) {
4016 switch (ev.keyCode) {
4017 case
13: /* enter */
4018 if (ev.target.value.length
&& !ev.target.classList.contains('cbi-input-invalid')) {
4019 this.addItem(dl, ev.target.value, null, true);
4020 ev.target.value = '';
4025 ev.preventDefault();
4032 getValue: function() {
4033 var items = this.node.querySelectorAll('.item
> input[
type=
"hidden"]'),
4034 input = this.node.querySelector('.add-item
> input[
type=
"text"]'),
4037 for (var i =
0; i
< items.length; i++)
4038 v.push(items[i].value);
4040 if (input
&& input.value != null
&& input.value.match(/\S/)
&&
4041 input.classList.contains('cbi-input-invalid') == false
&&
4042 v.filter(function(s) { return s == input.value }).length ==
0)
4043 v.push(input.value);
4049 setValue: function(values) {
4050 if (!Array.isArray(values))
4051 values = (values != null
&& values != '') ? [ values ] : [];
4053 var items = this.node.querySelectorAll('.item');
4055 for (var i =
0; i
< items.length; i++)
4056 if (items[i].parentNode === this.node)
4057 this.removeItem(this.node, items[i]);
4059 for (var i =
0; i
< values.length; i++)
4060 this.addItem(this.node, values[i],
4061 this.choices ? this.choices[values[i]] : null);
4065 * Add new suggested choices to the dynamic list.
4067 * This function adds further choices to an existing dynamic list,
4068 * ignoring choice values which are already present.
4071 * @memberof LuCI.ui.DynamicList
4072 * @param {string[]} values
4073 * The choice values to add to the dynamic lists suggestion dropdown.
4075 * @param {Object
<string, *
>} labels
4076 * The choice label values to use when adding suggested choices. If no
4077 * label is found for a particular choice value, the value itself is used
4078 * as label text. Choice labels may be any valid value accepted by
4079 * {@link LuCI.dom#content}.
4081 addChoices: function(values, labels) {
4082 var dl = this.node.lastElementChild.firstElementChild;
4083 dom.callClassMethod(dl, 'addChoices', values, labels);
4087 * Remove all existing choices from the dynamic list.
4089 * This function removes all preexisting suggested choices from the widget.
4092 * @memberof LuCI.ui.DynamicList
4094 clearChoices: function() {
4095 var dl = this.node.lastElementChild.firstElementChild;
4096 dom.callClassMethod(dl, 'clearChoices');
4101 * Instantiate a hidden input field widget.
4103 * @constructor Hiddenfield
4105 * @augments LuCI.ui.AbstractElement
4109 * The `Hiddenfield` class implements an HTML `
<input
type=
"hidden">` field
4110 * which allows to store form data without exposing it to the user.
4112 * UI widget instances are usually not supposed to be created by view code
4113 * directly, instead they're implicitely created by `LuCI.form` when
4114 * instantiating CBI forms.
4116 * This class is automatically instantiated as part of `LuCI.ui`. To use it
4117 * in views, use `'require ui'` and refer to `ui.Hiddenfield`. To import it in
4118 * external JavaScript, use `L.require(
"ui").then(...)` and access the
4119 * `Hiddenfield` property of the class instance value.
4121 * @param {string|string[]} [value=null]
4122 * The initial input value.
4124 * @param {LuCI.ui.AbstractElement.InitOptions} [options]
4125 * Object describing the widget specific options to initialize the hidden input.
4127 var UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ {
4128 __init__: function(value, options) {
4130 this.options = Object.assign({
4136 render: function() {
4137 var hiddenEl = E('input', {
4138 'id': this.options.id,
4143 return this.bind(hiddenEl);
4147 bind: function(hiddenEl) {
4148 this.node = hiddenEl;
4150 dom.bindClassInstance(hiddenEl, this);
4156 getValue: function() {
4157 return this.node.value;
4161 setValue: function(value) {
4162 this.node.value = value;
4167 * Instantiate a file upload widget.
4169 * @constructor FileUpload
4171 * @augments LuCI.ui.AbstractElement
4175 * The `FileUpload` class implements a widget which allows the user to upload,
4176 * browse, select and delete files beneath a predefined remote directory.
4178 * UI widget instances are usually not supposed to be created by view code
4179 * directly, instead they're implicitely created by `LuCI.form` when
4180 * instantiating CBI forms.
4182 * This class is automatically instantiated as part of `LuCI.ui`. To use it
4183 * in views, use `'require ui'` and refer to `ui.FileUpload`. To import it in
4184 * external JavaScript, use `L.require(
"ui").then(...)` and access the
4185 * `FileUpload` property of the class instance value.
4187 * @param {string|string[]} [value=null]
4188 * The initial input value.
4190 * @param {LuCI.ui.DynamicList.InitOptions} [options]
4191 * Object describing the widget specific options to initialize the file
4194 var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ {
4196 * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
4197 * the following properties are recognized:
4199 * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
4200 * @memberof LuCI.ui.FileUpload
4202 * @property {boolean} [show_hidden=false]
4203 * Specifies whether hidden files should be displayed when browsing remote
4204 * files. Note that this is not a security feature, hidden files are always
4205 * present in the remote file listings received, this option merely controls
4206 * whether they're displayed or not.
4208 * @property {boolean} [enable_upload=true]
4209 * Specifies whether the widget allows the user to upload files. If set to
4210 * `false`, only existing files may be selected. Note that this is not a
4211 * security feature. Whether file upload requests are accepted remotely
4212 * depends on the ACL setup for the current session. This option merely
4213 * controls whether the upload controls are rendered or not.
4215 * @property {boolean} [enable_remove=true]
4216 * Specifies whether the widget allows the user to delete remove files.
4217 * If set to `false`, existing files may not be removed. Note that this is
4218 * not a security feature. Whether file delete requests are accepted
4219 * remotely depends on the ACL setup for the current session. This option
4220 * merely controls whether the file remove controls are rendered or not.
4222 * @property {string} [root_directory=/etc/luci-uploads]
4223 * Specifies the remote directory the upload and file browsing actions take
4224 * place in. Browsing to directories outside of the root directory is
4225 * prevented by the widget. Note that this is not a security feature.
4226 * Whether remote directories are browseable or not solely depends on the
4227 * ACL setup for the current session.
4229 __init__: function(value, options) {
4231 this.options = Object.assign({
4233 enable_upload: true,
4234 enable_remove: true,
4235 root_directory: '/etc/luci-uploads'
4240 bind: function(browserEl) {
4241 this.node = browserEl;
4243 this.setUpdateEvents(browserEl, 'cbi-fileupload-select', 'cbi-fileupload-cancel');
4244 this.setChangeEvents(browserEl, 'cbi-fileupload-select', 'cbi-fileupload-cancel');
4246 dom.bindClassInstance(browserEl, this);
4252 render: function() {
4253 return L.resolveDefault(this.value != null ? fs.stat(this.value) : null).then(L.bind(function(stat) {
4256 if (L.isObject(stat)
&& stat.type != 'directory')
4259 if (this.stat != null)
4260 label = [ this.iconForType(this.stat.type), ' %s (%
1000mB)'.format(this.truncatePath(this.stat.path), this.stat.size) ];
4261 else if (this.value != null)
4262 label = [ this.iconForType('file'), ' %s (%s)'.format(this.truncatePath(this.value), _('File not accessible')) ];
4264 label = [ _('Select file…') ];
4266 return this.bind(E('div', { 'id': this.options.id }, [
4269 'click': UI.prototype.createHandlerFn(this, 'handleFileBrowser')
4272 'class': 'cbi-filebrowser'
4276 'name': this.options.name,
4284 truncatePath: function(path) {
4285 if (path.length
> 50)
4286 path = path.substring(
0,
25) + '…' + path.substring(path.length -
25);
4292 iconForType: function(type) {
4296 'src': L.resource('cbi/link.gif'),
4297 'title': _('Symbolic link'),
4303 'src': L.resource('cbi/folder.gif'),
4304 'title': _('Directory'),
4310 'src': L.resource('cbi/file.gif'),
4318 canonicalizePath: function(path) {
4319 return path.replace(/\/{
2,}/, '/')
4320 .replace(/\/\.(\/|$)/g, '/')
4321 .replace(/[^\/]+\/\.\.(\/|$)/g, '/')
4322 .replace(/\/$/, '');
4326 splitPath: function(path) {
4327 var croot = this.canonicalizePath(this.options.root_directory || '/'),
4328 cpath = this.canonicalizePath(path || '/');
4330 if (cpath.length
<= croot.length)
4333 if (cpath.charAt(croot.length) != '/')
4336 var parts = cpath.substring(croot.length +
1).split(/\//);
4338 parts.unshift(croot);
4344 handleUpload: function(path, list, ev) {
4345 var form = ev.target.parentNode,
4346 fileinput = form.querySelector('input[
type=
"file"]'),
4347 nameinput = form.querySelector('input[
type=
"text"]'),
4348 filename = (nameinput.value != null ? nameinput.value : '').trim();
4350 ev.preventDefault();
4352 if (filename == '' || filename.match(/\//) || fileinput.files[
0] == null)
4355 var existing = list.filter(function(e) { return e.name == filename })[
0];
4357 if (existing != null
&& existing.type == 'directory')
4358 return alert(_('A directory with the same name already exists.'));
4359 else if (existing != null
&& !confirm(_('Overwrite existing file
"%s" ?').format(filename)))
4362 var data = new FormData();
4364 data.append('sessionid', L.env.sessionid);
4365 data.append('filename', path + '/' + filename);
4366 data.append('filedata', fileinput.files[
0]);
4368 return request.post(L.env.cgi_base + '/cgi-upload', data, {
4369 progress: L.bind(function(btn, ev) {
4370 btn.firstChild.data = '%
.2f%%'.format((ev.loaded / ev.total) *
100);
4372 }).then(L.bind(function(path, ev, res) {
4373 var reply = res.json();
4375 if (L.isObject(reply)
&& reply.failure)
4376 alert(_('Upload request failed: %s').format(reply.message));
4378 return this.handleSelect(path, null, ev);
4379 }, this, path, ev));
4383 handleDelete: function(path, fileStat, ev) {
4384 var parent = path.replace(/\/[^\/]+$/, '') || '/',
4385 name = path.replace(/^.+\//, ''),
4388 ev.preventDefault();
4390 if (fileStat.type == 'directory')
4391 msg = _('Do you really want to recursively delete the directory
"%s" ?').format(name);
4393 msg = _('Do you really want to delete
"%s" ?').format(name);
4396 var button = this.node.firstElementChild,
4397 hidden = this.node.lastElementChild;
4399 if (path == hidden.value) {
4400 dom.content(button, _('Select file…'));
4404 return fs.remove(path).then(L.bind(function(parent, ev) {
4405 return this.handleSelect(parent, null, ev);
4406 }, this, parent, ev)).catch(function(err) {
4407 alert(_('Delete request failed: %s').format(err.message));
4413 renderUpload: function(path, list) {
4414 if (!this.options.enable_upload)
4420 'class': 'btn cbi-button-positive',
4421 'click': function(ev) {
4422 var uploadForm = ev.target.nextElementSibling,
4423 fileInput = uploadForm.querySelector('input[
type=
"file"]');
4425 ev.target.style.display = 'none';
4426 uploadForm.style.display = '';
4429 }, _('Upload file…')),
4430 E('div', { 'class': 'upload', 'style': 'display:none' }, [
4433 'style': 'display:none',
4434 'change': function(ev) {
4435 var nameinput = ev.target.parentNode.querySelector('input[
type=
"text"]'),
4436 uploadbtn = ev.target.parentNode.querySelector('button.cbi-button-save');
4438 nameinput.value = ev.target.value.replace(/^.+[\/\\]/, '');
4439 uploadbtn.disabled = false;
4444 'click': function(ev) {
4445 ev.preventDefault();
4446 ev.target.previousElementSibling.click();
4448 }, [ _('Browse…') ]),
4449 E('div', {}, E('input', { 'type': 'text', 'placeholder': _('Filename') })),
4451 'class': 'btn cbi-button-save',
4452 'click': UI.prototype.createHandlerFn(this, 'handleUpload', path, list),
4454 }, [ _('Upload file') ])
4460 renderListing: function(container, path, list) {
4461 var breadcrumb = E('p'),
4464 list.sort(function(a, b) {
4465 var isDirA = (a.type == 'directory'),
4466 isDirB = (b.type == 'directory');
4468 if (isDirA != isDirB)
4469 return isDirA
< isDirB;
4471 return a.name
> b.name;
4474 for (var i =
0; i
< list.length; i++) {
4475 if (!this.options.show_hidden
&& list[i].name.charAt(
0) == '.')
4478 var entrypath = this.canonicalizePath(path + '/' + list[i].name),
4479 selected = (entrypath == this.node.lastElementChild.value),
4480 mtime = new Date(list[i].mtime *
1000);
4482 rows.appendChild(E('li', [
4483 E('div', { 'class': 'name' }, [
4484 this.iconForType(list[i].type),
4488 'style': selected ? 'font-weight:bold' : null,
4489 'click': UI.prototype.createHandlerFn(this, 'handleSelect',
4490 entrypath, list[i].type != 'directory' ? list[i] : null)
4491 }, '%h'.format(list[i].name))
4493 E('div', { 'class': 'mtime hide-xs' }, [
4494 ' %
04d-%
02d-%
02d %
02d:%
02d:%
02d '.format(
4495 mtime.getFullYear(),
4496 mtime.getMonth() +
1,
4503 selected ? E('button', {
4505 'click': UI.prototype.createHandlerFn(this, 'handleReset')
4506 }, [ _('Deselect') ]) : '',
4507 this.options.enable_remove ? E('button', {
4508 'class': 'btn cbi-button-negative',
4509 'click': UI.prototype.createHandlerFn(this, 'handleDelete', entrypath, list[i])
4510 }, [ _('Delete') ]) : ''
4515 if (!rows.firstElementChild)
4516 rows.appendChild(E('em', _('No entries in this directory')));
4518 var dirs = this.splitPath(path),
4521 for (var i =
0; i
< dirs.length; i++) {
4522 cur = cur ? cur + '/' + dirs[i] : dirs[i];
4523 dom.append(breadcrumb, [
4527 'click': UI.prototype.createHandlerFn(this, 'handleSelect', cur || '/', null)
4528 }, dirs[i] != '' ? '%h'.format(dirs[i]) : E('em', '(root)')),
4532 dom.content(container, [
4535 E('div', { 'class': 'right' }, [
4536 this.renderUpload(path, list),
4540 'click': UI.prototype.createHandlerFn(this, 'handleCancel')
4547 handleCancel: function(ev) {
4548 var button = this.node.firstElementChild,
4549 browser = button.nextElementSibling;
4551 browser.classList.remove('open');
4552 button.style.display = '';
4554 this.node.dispatchEvent(new CustomEvent('cbi-fileupload-cancel', {}));
4556 ev.preventDefault();
4560 handleReset: function(ev) {
4561 var button = this.node.firstElementChild,
4562 hidden = this.node.lastElementChild;
4565 dom.content(button, _('Select file…'));
4567 this.handleCancel(ev);
4571 handleSelect: function(path, fileStat, ev) {
4572 var browser = dom.parent(ev.target, '.cbi-filebrowser'),
4573 ul = browser.querySelector('ul');
4575 if (fileStat == null) {
4576 dom.content(ul, E('em', { 'class': 'spinning' }, _('Loading directory contents…')));
4577 L.resolveDefault(fs.list(path), []).then(L.bind(this.renderListing, this, browser, path));
4580 var button = this.node.firstElementChild,
4581 hidden = this.node.lastElementChild;
4583 path = this.canonicalizePath(path);
4585 dom.content(button, [
4586 this.iconForType(fileStat.type),
4587 ' %s (%
1000mB)'.format(this.truncatePath(path), fileStat.size)
4590 browser.classList.remove('open');
4591 button.style.display = '';
4592 hidden.value = path;
4594 this.stat = Object.assign({ path: path }, fileStat);
4595 this.node.dispatchEvent(new CustomEvent('cbi-fileupload-select', { detail: this.stat }));
4600 handleFileBrowser: function(ev) {
4601 var button = ev.target,
4602 browser = button.nextElementSibling,
4603 path = this.stat ? this.stat.path.replace(/\/[^\/]+$/, '') : (this.options.initial_directory || this.options.root_directory);
4605 if (path.indexOf(this.options.root_directory) !=
0)
4606 path = this.options.root_directory;
4608 ev.preventDefault();
4610 return L.resolveDefault(fs.list(path), []).then(L.bind(function(button, browser, path, list) {
4611 document.querySelectorAll('.cbi-filebrowser.open').forEach(function(browserEl) {
4612 dom.findClassInstance(browserEl).handleCancel(ev);
4615 button.style.display = 'none';
4616 browser.classList.add('open');
4618 return this.renderListing(browser, path, list);
4619 }, this, button, browser, path));
4623 getValue: function() {
4624 return this.node.lastElementChild.value;
4628 setValue: function(value) {
4629 this.node.lastElementChild.value = value;
4639 * Provides high level UI helper functionality.
4640 * To import the class in views, use `'require ui'`, to import it in
4641 * external JavaScript, use `L.require(
"ui").then(...)`.
4643 var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
4644 __init__: function() {
4645 modalDiv = document.body.appendChild(
4646 dom.create('div', { id: 'modal_overlay' },
4647 dom.create('div', { class: 'modal', role: 'dialog', 'aria-modal': true })));
4649 tooltipDiv = document.body.appendChild(
4650 dom.create('div', { class: 'cbi-tooltip' }));
4652 /* setup old aliases */
4653 L.showModal = this.showModal;
4654 L.hideModal = this.hideModal;
4655 L.showTooltip = this.showTooltip;
4656 L.hideTooltip = this.hideTooltip;
4657 L.itemlist = this.itemlist;
4659 document.addEventListener('mouseover', this.showTooltip.bind(this), true);
4660 document.addEventListener('mouseout', this.hideTooltip.bind(this), true);
4661 document.addEventListener('focus', this.showTooltip.bind(this), true);
4662 document.addEventListener('blur', this.hideTooltip.bind(this), true);
4664 document.addEventListener('luci-loaded', this.tabs.init.bind(this.tabs));
4665 document.addEventListener('luci-loaded', this.changes.init.bind(this.changes));
4666 document.addEventListener('uci-loaded', this.changes.init.bind(this.changes));
4670 * Display a modal overlay dialog with the specified contents.
4672 * The modal overlay dialog covers the current view preventing interaction
4673 * with the underlying view contents. Only one modal dialog instance can
4674 * be opened. Invoking showModal() while a modal dialog is already open will
4675 * replace the open dialog with a new one having the specified contents.
4677 * Additional CSS class names may be passed to influence the appearence of
4678 * the dialog. Valid values for the classes depend on the underlying theme.
4680 * @see LuCI.dom.content
4682 * @param {string} [title]
4683 * The title of the dialog. If `null`, no title element will be rendered.
4685 * @param {*} contents
4686 * The contents to add to the modal dialog. This should be a DOM node or
4687 * a document fragment in most cases. The value is passed as-is to the
4688 * `dom.content()` function - refer to its documentation for applicable
4691 * @param {...string} [classes]
4692 * A number of extra CSS class names which are set on the modal dialog
4696 * Returns a DOM Node representing the modal dialog element.
4698 showModal: function(title, children /* , ... */) {
4699 var dlg = modalDiv.firstElementChild;
4701 dlg.setAttribute('class', 'modal');
4703 for (var i =
2; i
< arguments.length; i++)
4704 dlg.classList.add(arguments[i]);
4706 dom.content(dlg, dom.create('h4', {}, title));
4707 dom.append(dlg, children);
4709 document.body.classList.add('modal-overlay-active');
4715 * Close the open modal overlay dialog.
4717 * This function will close an open modal dialog and restore the normal view
4718 * behaviour. It has no effect if no modal dialog is currently open.
4720 * Note that this function is stand-alone, it does not rely on `this` and
4721 * will not invoke other class functions so it suitable to be used as event
4722 * handler as-is without the need to bind it first.
4724 hideModal: function() {
4725 document.body.classList.remove('modal-overlay-active');
4729 showTooltip: function(ev) {
4730 var target = findParent(ev.target, '[data-tooltip]');
4735 if (tooltipTimeout !== null) {
4736 window.clearTimeout(tooltipTimeout);
4737 tooltipTimeout = null;
4740 var rect = target.getBoundingClientRect(),
4741 x = rect.left + window.pageXOffset,
4742 y = rect.top + rect.height + window.pageYOffset;
4744 tooltipDiv.className = 'cbi-tooltip';
4745 tooltipDiv.innerHTML = '▲ ';
4746 tooltipDiv.firstChild.data += target.getAttribute('data-tooltip');
4748 if (target.hasAttribute('data-tooltip-style'))
4749 tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));
4751 if ((y + tooltipDiv.offsetHeight)
> (window.innerHeight + window.pageYOffset)) {
4752 y -= (tooltipDiv.offsetHeight + target.offsetHeight);
4753 tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(
2);
4756 tooltipDiv.style.top = y + 'px';
4757 tooltipDiv.style.left = x + 'px';
4758 tooltipDiv.style.opacity =
1;
4760 tooltipDiv.dispatchEvent(new CustomEvent('tooltip-open', {
4762 detail: { target: target }
4767 hideTooltip: function(ev) {
4768 if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv ||
4769 tooltipDiv.contains(ev.target) || tooltipDiv.contains(ev.relatedTarget))
4772 if (tooltipTimeout !== null) {
4773 window.clearTimeout(tooltipTimeout);
4774 tooltipTimeout = null;
4777 tooltipDiv.style.opacity =
0;
4778 tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); },
250);
4780 tooltipDiv.dispatchEvent(new CustomEvent('tooltip-close', { bubbles: true }));
4784 * Add a notification banner at the top of the current view.
4786 * A notification banner is an alert message usually displayed at the
4787 * top of the current view, spanning the entire availibe width.
4788 * Notification banners will stay in place until dismissed by the user.
4789 * Multiple banners may be shown at the same time.
4791 * Additional CSS class names may be passed to influence the appearence of
4792 * the banner. Valid values for the classes depend on the underlying theme.
4794 * @see LuCI.dom.content
4796 * @param {string} [title]
4797 * The title of the notification banner. If `null`, no title element
4800 * @param {*} contents
4801 * The contents to add to the notification banner. This should be a DOM
4802 * node or a document fragment in most cases. The value is passed as-is
4803 * to the `dom.content()` function - refer to its documentation for
4804 * applicable values.
4806 * @param {...string} [classes]
4807 * A number of extra CSS class names which are set on the notification
4811 * Returns a DOM Node representing the notification banner element.
4813 addNotification: function(title, children /*, ... */) {
4814 var mc = document.querySelector('#maincontent') || document.body;
4815 var msg = E('div', {
4816 'class': 'alert-message fade-in',
4817 'style': 'display:flex',
4818 'transitionend': function(ev) {
4819 var node = ev.currentTarget;
4820 if (node.parentNode
&& node.classList.contains('fade-out'))
4821 node.parentNode.removeChild(node);
4824 E('div', { 'style': 'flex:
10' }),
4825 E('div', { 'style': 'flex:
1 1 auto; display:flex' }, [
4828 'style': 'margin-left:auto; margin-top:auto',
4829 'click': function(ev) {
4830 dom.parent(ev.target, '.alert-message').classList.add('fade-out');
4833 }, [ _('Dismiss') ])
4838 dom.append(msg.firstElementChild, E('h4', {}, title));
4840 dom.append(msg.firstElementChild, children);
4842 for (var i =
2; i
< arguments.length; i++)
4843 msg.classList.add(arguments[i]);
4845 mc.insertBefore(msg, mc.firstElementChild);
4851 * Display or update an header area indicator.
4853 * An indicator is a small label displayed in the header area of the screen
4854 * providing few amounts of status information such as item counts or state
4855 * toggle indicators.
4857 * Multiple indicators may be shown at the same time and indicator labels
4858 * may be made clickable to display extended information or to initiate
4861 * Indicators can either use a default `active` or a less accented `inactive`
4862 * style which is useful for indicators representing state toggles.
4864 * @param {string} id
4865 * The ID of the indicator. If an indicator with the given ID already exists,
4866 * it is updated with the given label and style.
4868 * @param {string} label
4869 * The text to display in the indicator label.
4871 * @param {function} [handler]
4872 * A handler function to invoke when the indicator label is clicked/touched
4873 * by the user. If omitted, the indicator is not clickable/touchable.
4875 * Note that this parameter only applies to new indicators, when updating
4876 * existing labels it is ignored.
4878 * @param {string} [style=active]
4879 * The indicator style to use. May be either `active` or `inactive`.
4881 * @returns {boolean}
4882 * Returns `true` when the indicator has been updated or `false` when no
4883 * changes were made.
4885 showIndicator: function(id, label, handler, style) {
4886 if (indicatorDiv == null) {
4887 indicatorDiv = document.body.querySelector('#indicators');
4889 if (indicatorDiv == null)
4893 var handlerFn = (typeof(handler) == 'function') ? handler : null,
4894 indicatorElem = indicatorDiv.querySelector('span[
data-indicator=
"%s"]'.format(id)) ||
4895 indicatorDiv.appendChild(E('span', {
4896 'data-indicator': id,
4897 'data-clickable': handlerFn ? true : null,
4901 if (label == indicatorElem.firstChild.data
&& style == indicatorElem.getAttribute('data-style'))
4904 indicatorElem.firstChild.data = label;
4905 indicatorElem.setAttribute('data-style', (style == 'inactive') ? 'inactive' : 'active');
4910 * Remove an header area indicator.
4912 * This function removes the given indicator label from the header indicator
4913 * area. When the given indicator is not found, this function does nothing.
4915 * @param {string} id
4916 * The ID of the indicator to remove.
4918 * @returns {boolean}
4919 * Returns `true` when the indicator has been removed or `false` when the
4920 * requested indicator was not found.
4922 hideIndicator: function(id) {
4923 var indicatorElem = indicatorDiv ? indicatorDiv.querySelector('span[
data-indicator=
"%s"]'.format(id)) : null;
4925 if (indicatorElem == null)
4928 indicatorDiv.removeChild(indicatorElem);
4933 * Formats a series of label/value pairs into list-like markup.
4935 * This function transforms a flat array of alternating label and value
4936 * elements into a list-like markup, using the values in `separators` as
4937 * separators and appends the resulting nodes to the given parent DOM node.
4939 * Each label is suffixed with `: ` and wrapped into a `
<strong
>` tag, the
4940 * `
<strong
>` element and the value corresponding to the label are
4941 * subsequently wrapped into a `
<span
class=
"nowrap">` element.
4943 * The resulting `
<span
>` element tuples are joined by the given separators
4944 * to form the final markup which is appened to the given parent DOM node.
4946 * @param {Node} node
4947 * The parent DOM node to append the markup to. Any previous child elements
4950 * @param {Array
<*
>} items
4951 * An alternating array of labels and values. The label values will be
4952 * converted to plain strings, the values are used as-is and may be of
4953 * any type accepted by `LuCI.dom.content()`.
4955 * @param {*|Array
<*
>} [separators=[E('br')]]
4956 * A single value or an array of separator values to separate each
4957 * label/value pair with. The function will cycle through the separators
4958 * when joining the pairs. If omitted, the default separator is a sole HTML
4959 * `
<br
>` element. Separator values are used as-is and may be of any type
4960 * accepted by `LuCI.dom.content()`.
4963 * Returns the parent DOM node the formatted markup has been added to.
4965 itemlist: function(node, items, separators) {
4968 if (!Array.isArray(separators))
4969 separators = [ separators || E('br') ];
4971 for (var i =
0; i
< items.length; i +=
2) {
4972 if (items[i+
1] !== null
&& items[i+
1] !== undefined) {
4973 var sep = separators[(i/
2) % separators.length],
4976 children.push(E('span', { class: 'nowrap' }, [
4977 items[i] ? E('strong', items[i] + ': ') : '',
4981 if ((i+
2)
< items.length)
4982 children.push(dom.elem(sep) ? sep.cloneNode(true) : sep);
4986 dom.content(node, children);
4997 * The `tabs` class handles tab menu groups used throughout the view area.
4998 * It takes care of setting up tab groups, tracking their state and handling
5001 * This class is automatically instantiated as part of `LuCI.ui`. To use it
5002 * in views, use `'require ui'` and refer to `ui.tabs`. To import it in
5003 * external JavaScript, use `L.require(
"ui").then(...)` and access the
5004 * `tabs` property of the class instance value.
5006 tabs: baseclass.singleton(/* @lends LuCI.ui.tabs.prototype */ {
5009 var groups = [], prevGroup = null, currGroup = null;
5011 document.querySelectorAll('[data-tab]').forEach(function(tab) {
5012 var parent = tab.parentNode;
5014 if (dom.matches(tab, 'li')
&& dom.matches(parent, 'ul.cbi-tabmenu'))
5017 if (!parent.hasAttribute('data-tab-group'))
5018 parent.setAttribute('data-tab-group', groups.length);
5020 currGroup = +parent.getAttribute('data-tab-group');
5022 if (currGroup !== prevGroup) {
5023 prevGroup = currGroup;
5025 if (!groups[currGroup])
5026 groups[currGroup] = [];
5029 groups[currGroup].push(tab);
5032 for (var i =
0; i
< groups.length; i++)
5033 this.initTabGroup(groups[i]);
5035 document.addEventListener('dependency-update', this.updateTabs.bind(this));
5041 * Initializes a new tab group from the given tab pane collection.
5043 * This function cycles through the given tab pane DOM nodes, extracts
5044 * their tab IDs, titles and active states, renders a corresponding
5045 * tab menu and prepends it to the tab panes common parent DOM node.
5047 * The tab menu labels will be set to the value of the `data-tab-title`
5048 * attribute of each corresponding pane. The last pane with the
5049 * `data-tab-active` attribute set to `true` will be selected by default.
5051 * If no pane is marked as active, the first one will be preselected.
5054 * @memberof LuCI.ui.tabs
5055 * @param {Array
<Node
>|NodeList} panes
5056 * A collection of tab panes to build a tab group menu for. May be a
5057 * plain array of DOM nodes or a NodeList collection, such as the result
5058 * of a `querySelectorAll()` call or the `.childNodes` property of a
5061 initTabGroup: function(panes) {
5062 if (typeof(panes) != 'object' || !('length' in panes) || panes.length ===
0)
5065 var menu = E('ul', { 'class': 'cbi-tabmenu' }),
5066 group = panes[
0].parentNode,
5067 groupId = +group.getAttribute('data-tab-group'),
5070 if (group.getAttribute('data-initialized') === 'true')
5073 for (var i =
0, pane; pane = panes[i]; i++) {
5074 var name = pane.getAttribute('data-tab'),
5075 title = pane.getAttribute('data-tab-title'),
5076 active = pane.getAttribute('data-tab-active') === 'true';
5078 menu.appendChild(E('li', {
5079 'style': this.isEmptyPane(pane) ? 'display:none' : null,
5080 'class': active ? 'cbi-tab' : 'cbi-tab-disabled',
5084 'click': this.switchTab.bind(this)
5091 group.parentNode.insertBefore(menu, group);
5092 group.setAttribute('data-initialized', true);
5094 if (selected === null) {
5095 selected = this.getActiveTabId(panes[
0]);
5097 if (selected
< 0 || selected
>= panes.length || this.isEmptyPane(panes[selected])) {
5098 for (var i =
0; i
< panes.length; i++) {
5099 if (!this.isEmptyPane(panes[i])) {
5106 menu.childNodes[selected].classList.add('cbi-tab');
5107 menu.childNodes[selected].classList.remove('cbi-tab-disabled');
5108 panes[selected].setAttribute('data-tab-active', 'true');
5110 this.setActiveTabId(panes[selected], selected);
5113 panes[selected].dispatchEvent(new CustomEvent('cbi-tab-active', {
5114 detail: { tab: panes[selected].getAttribute('data-tab') }
5117 this.updateTabs(group);
5121 * Checks whether the given tab pane node is empty.
5124 * @memberof LuCI.ui.tabs
5125 * @param {Node} pane
5126 * The tab pane to check.
5128 * @returns {boolean}
5129 * Returns `true` if the pane is empty, else `false`.
5131 isEmptyPane: function(pane) {
5132 return dom.isEmpty(pane, function(n) { return n.classList.contains('cbi-tab-descr') });
5136 getPathForPane: function(pane) {
5137 var path = [], node = null;
5139 for (node = pane ? pane.parentNode : null;
5140 node != null
&& node.hasAttribute != null;
5141 node = node.parentNode)
5143 if (node.hasAttribute('data-tab'))
5144 path.unshift(node.getAttribute('data-tab'));
5145 else if (node.hasAttribute('data-section-id'))
5146 path.unshift(node.getAttribute('data-section-id'));
5149 return path.join('/');
5153 getActiveTabState: function() {
5154 var page = document.body.getAttribute('data-page');
5157 var val = JSON.parse(window.sessionStorage.getItem('tab'));
5158 if (val.page === page
&& L.isObject(val.paths))
5163 window.sessionStorage.removeItem('tab');
5164 return { page: page, paths: {} };
5168 getActiveTabId: function(pane) {
5169 var path = this.getPathForPane(pane);
5170 return +this.getActiveTabState().paths[path] ||
0;
5174 setActiveTabId: function(pane, tabIndex) {
5175 var path = this.getPathForPane(pane);
5178 var state = this.getActiveTabState();
5179 state.paths[path] = tabIndex;
5181 window.sessionStorage.setItem('tab', JSON.stringify(state));
5183 catch (e) { return false; }
5189 updateTabs: function(ev, root) {
5190 (root || document).querySelectorAll('[data-tab-title]').forEach(L.bind(function(pane) {
5191 var menu = pane.parentNode.previousElementSibling,
5192 tab = menu ? menu.querySelector('[
data-tab=
"%s"]'.format(pane.getAttribute('data-tab'))) : null,
5193 n_errors = pane.querySelectorAll('.cbi-input-invalid').length;
5198 if (this.isEmptyPane(pane)) {
5199 tab.style.display = 'none';
5200 tab.classList.remove('flash');
5202 else if (tab.style.display === 'none') {
5203 tab.style.display = '';
5204 requestAnimationFrame(function() { tab.classList.add('flash') });
5208 tab.setAttribute('data-errors', n_errors);
5209 tab.setAttribute('data-tooltip', _('%d invalid field(s)').format(n_errors));
5210 tab.setAttribute('data-tooltip-style', 'error');
5213 tab.removeAttribute('data-errors');
5214 tab.removeAttribute('data-tooltip');
5220 switchTab: function(ev) {
5221 var tab = ev.target.parentNode,
5222 name = tab.getAttribute('data-tab'),
5223 menu = tab.parentNode,
5224 group = menu.nextElementSibling,
5225 groupId = +group.getAttribute('data-tab-group'),
5228 ev.preventDefault();
5230 if (!tab.classList.contains('cbi-tab-disabled'))
5233 menu.querySelectorAll('[data-tab]').forEach(function(tab) {
5234 tab.classList.remove('cbi-tab');
5235 tab.classList.remove('cbi-tab-disabled');
5237 tab.getAttribute('data-tab') === name ? 'cbi-tab' : 'cbi-tab-disabled');
5240 group.childNodes.forEach(function(pane) {
5241 if (dom.matches(pane, '[data-tab]')) {
5242 if (pane.getAttribute('data-tab') === name) {
5243 pane.setAttribute('data-tab-active', 'true');
5244 pane.dispatchEvent(new CustomEvent('cbi-tab-active', { detail: { tab: name } }));
5245 UI.prototype.tabs.setActiveTabId(pane, index);
5248 pane.setAttribute('data-tab-active', 'false');
5258 * @typedef {Object} FileUploadReply
5261 * @property {string} name - Name of the uploaded file without directory components
5262 * @property {number} size - Size of the uploaded file in bytes
5263 * @property {string} checksum - The MD5 checksum of the received file data
5264 * @property {string} sha256sum - The SHA256 checksum of the received file data
5268 * Display a modal file upload prompt.
5270 * This function opens a modal dialog prompting the user to select and
5271 * upload a file to a predefined remote destination path.
5273 * @param {string} path
5274 * The remote file path to upload the local file to.
5276 * @param {Node} [progessStatusNode]
5277 * An optional DOM text node whose content text is set to the progress
5278 * percentage value during file upload.
5280 * @returns {Promise
<LuCI.ui.FileUploadReply
>}
5281 * Returns a promise resolving to a file upload status object on success
5282 * or rejecting with an error in case the upload failed or has been
5283 * cancelled by the user.
5285 uploadFile: function(path, progressStatusNode) {
5286 return new Promise(function(resolveFn, rejectFn) {
5287 UI.prototype.showModal(_('Uploading file…'), [
5288 E('p', _('Please select the file to upload.')),
5289 E('div', { 'style': 'display:flex' }, [
5290 E('div', { 'class': 'left', 'style': 'flex:
1' }, [
5293 style: 'display:none',
5294 change: function(ev) {
5295 var modal = dom.parent(ev.target, '.modal'),
5296 body = modal.querySelector('p'),
5297 upload = modal.querySelector('.cbi-button-action.important'),
5298 file = ev.currentTarget.files[
0];
5305 E('li', {}, [ '%s: %s'.format(_('Name'), file.name.replace(/^.*[\\\/]/, '')) ]),
5306 E('li', {}, [ '%s: %
1024mB'.format(_('Size'), file.size) ])
5310 upload.disabled = false;
5316 'click': function(ev) {
5317 ev.target.previousElementSibling.click();
5319 }, [ _('Browse…') ])
5321 E('div', { 'class': 'right', 'style': 'flex:
1' }, [
5324 'click': function() {
5325 UI.prototype.hideModal();
5326 rejectFn(new Error('Upload has been cancelled'));
5328 }, [ _('Cancel') ]),
5331 'class': 'btn cbi-button-action important',
5333 'click': function(ev) {
5334 var input = dom.parent(ev.target, '.modal').querySelector('input[
type=
"file"]');
5336 if (!input.files[
0])
5339 var progress = E('div', { 'class': 'cbi-progressbar', 'title': '
0%' }, E('div', { 'style': 'width:
0' }));
5341 UI.prototype.showModal(_('Uploading file…'), [ progress ]);
5343 var data = new FormData();
5345 data.append('sessionid', rpc.getSessionID());
5346 data.append('filename', path);
5347 data.append('filedata', input.files[
0]);
5349 var filename = input.files[
0].name;
5351 request.post(L.env.cgi_base + '/cgi-upload', data, {
5353 progress: function(pev) {
5354 var percent = (pev.loaded / pev.total) *
100;
5356 if (progressStatusNode)
5357 progressStatusNode.data = '%
.2f%%'.format(percent);
5359 progress.setAttribute('title', '%
.2f%%'.format(percent));
5360 progress.firstElementChild.style.width = '%
.2f%%'.format(percent);
5362 }).then(function(res) {
5363 var reply = res.json();
5365 UI.prototype.hideModal();
5367 if (L.isObject(reply)
&& reply.failure) {
5368 UI.prototype.addNotification(null, E('p', _('Upload request failed: %s').format(reply.message)));
5369 rejectFn(new Error(reply.failure));
5372 reply.name = filename;
5376 UI.prototype.hideModal();
5388 * Perform a device connectivity test.
5390 * Attempt to fetch a well known ressource from the remote device via HTTP
5391 * in order to test connectivity. This function is mainly useful to wait
5392 * for the router to come back online after a reboot or reconfiguration.
5394 * @param {string} [proto=http]
5395 * The protocol to use for fetching the resource. May be either `http`
5396 * (the default) or `https`.
5398 * @param {string} [host=window.location.host]
5399 * Override the host address to probe. By default the current host as seen
5400 * in the address bar is probed.
5402 * @returns {Promise
<Event
>}
5403 * Returns a promise resolving to a `load` event in case the device is
5404 * reachable or rejecting with an `error` event in case it is not reachable
5405 * or rejecting with `null` when the connectivity check timed out.
5407 pingDevice: function(proto, ipaddr) {
5408 var target = '%s://%s%s?%s'.format(proto || 'http', ipaddr || window.location.host, L.resource('icons/loading.gif'), Math.random());
5410 return new Promise(function(resolveFn, rejectFn) {
5411 var img = new Image();
5413 img.onload = resolveFn;
5414 img.onerror = rejectFn;
5416 window.setTimeout(rejectFn,
1000);
5423 * Wait for device to come back online and reconnect to it.
5425 * Poll each given hostname or IP address and navigate to it as soon as
5426 * one of the addresses becomes reachable.
5428 * @param {...string} [hosts=[window.location.host]]
5429 * The list of IP addresses and host names to check for reachability.
5430 * If omitted, the current value of `window.location.host` is used by
5433 awaitReconnect: function(/* ... */) {
5434 var ipaddrs = arguments.length ? arguments : [ window.location.host ];
5436 window.setTimeout(L.bind(function() {
5437 poll.add(L.bind(function() {
5438 var tasks = [], reachable = false;
5440 for (var i =
0; i
< 2; i++)
5441 for (var j =
0; j
< ipaddrs.length; j++)
5442 tasks.push(this.pingDevice(i ? 'https' : 'http', ipaddrs[j])
5443 .then(function(ev) { reachable = ev.target.src.replace(/^(https?:\/\/[^\/]+).*$/, '$
1/') }, function() {}));
5445 return Promise.all(tasks).then(function() {
5448 window.location = reachable;
5461 * The `changes` class encapsulates logic for visualizing, applying,
5462 * confirming and reverting staged UCI changesets.
5464 * This class is automatically instantiated as part of `LuCI.ui`. To use it
5465 * in views, use `'require ui'` and refer to `ui.changes`. To import it in
5466 * external JavaScript, use `L.require(
"ui").then(...)` and access the
5467 * `changes` property of the class instance value.
5469 changes: baseclass.singleton(/* @lends LuCI.ui.changes.prototype */ {
5471 if (!L.env.sessionid)
5474 return uci.changes().then(L.bind(this.renderChangeIndicator, this));
5478 * Set the change count indicator.
5480 * This function updates or hides the UCI change count indicator,
5481 * depending on the passed change count. When the count is greater
5482 * than
0, the change indicator is displayed or updated, otherwise it
5486 * @memberof LuCI.ui.changes
5487 * @param {number} numChanges
5488 * The number of changes to indicate.
5490 setIndicator: function(n) {
5491 var i = document.querySelector('.uci_change_indicator');
5493 var poll = document.getElementById('xhr_poll_status');
5494 i = poll.parentNode.insertBefore(E('a', {
5496 'class': 'uci_change_indicator label notice',
5497 'click': L.bind(this.displayChanges, this)
5502 dom.content(i, [ _('Unsaved Changes'), ': ', n ]);
5503 i.classList.add('flash');
5504 i.style.display = '';
5505 document.dispatchEvent(new CustomEvent('uci-new-changes'));
5508 i.classList.remove('flash');
5509 i.style.display = 'none';
5510 document.dispatchEvent(new CustomEvent('uci-clear-changes'));
5515 * Update the change count indicator.
5517 * This function updates the UCI change count indicator from the given
5518 * UCI changeset structure.
5521 * @memberof LuCI.ui.changes
5522 * @param {Object
<string, Array
<LuCI.uci.ChangeRecord
>>} changes
5523 * The UCI changeset to count.
5525 renderChangeIndicator: function(changes) {
5528 for (var config in changes)
5529 if (changes.hasOwnProperty(config))
5530 n_changes += changes[config].length;
5532 this.changes = changes;
5533 this.setIndicator(n_changes);
5538 'add-
3': '
<ins
>uci add %
0 <strong
>%
3</strong
> # =%
2</ins
>',
5539 'set-
3': '
<ins
>uci set %
0.
<strong
>%
2</strong
>=%
3</ins
>',
5540 'set-
4': '
<var
><ins
>uci set %
0.%
2.%
3=
<strong
>%
4</strong
></ins
></var
>',
5541 'remove-
2': '
<del
>uci del %
0.
<strong
>%
2</strong
></del
>',
5542 'remove-
3': '
<var
><del
>uci del %
0.%
2.
<strong
>%
3</strong
></del
></var
>',
5543 'order-
3': '
<var
>uci reorder %
0.%
2=
<strong
>%
3</strong
></var
>',
5544 'list-add-
4': '
<var
><ins
>uci add_list %
0.%
2.%
3=
<strong
>%
4</strong
></ins
></var
>',
5545 'list-del-
4': '
<var
><del
>uci del_list %
0.%
2.%
3=
<strong
>%
4</strong
></del
></var
>',
5546 'rename-
3': '
<var
>uci rename %
0.%
2=
<strong
>%
3</strong
></var
>',
5547 'rename-
4': '
<var
>uci rename %
0.%
2.%
3=
<strong
>%
4</strong
></var
>'
5551 * Display the current changelog.
5553 * Open a modal dialog visualizing the currently staged UCI changes
5554 * and offer options to revert or apply the shown changes.
5557 * @memberof LuCI.ui.changes
5559 displayChanges: function() {
5560 var list = E('div', { 'class': 'uci-change-list' }),
5561 dlg = UI.prototype.showModal(_('Configuration') + ' / ' + _('Changes'), [
5562 E('div', { 'class': 'cbi-section' }, [
5563 E('strong', _('Legend:')),
5564 E('div', { 'class': 'uci-change-legend' }, [
5565 E('div', { 'class': 'uci-change-legend-label' }, [
5566 E('ins', '
&#
160;'), ' ', _('Section added') ]),
5567 E('div', { 'class': 'uci-change-legend-label' }, [
5568 E('del', '
&#
160;'), ' ', _('Section removed') ]),
5569 E('div', { 'class': 'uci-change-legend-label' }, [
5570 E('var', {}, E('ins', '
&#
160;')), ' ', _('Option changed') ]),
5571 E('div', { 'class': 'uci-change-legend-label' }, [
5572 E('var', {}, E('del', '
&#
160;')), ' ', _('Option removed') ])]),
5574 E('div', { 'class': 'right' }, [
5577 'click': UI.prototype.hideModal
5578 }, [ _('Dismiss') ]), ' ',
5580 'class': 'cbi-button cbi-button-positive important',
5581 'click': L.bind(this.apply, this, true)
5582 }, [ _('Save
& Apply') ]), ' ',
5584 'class': 'cbi-button cbi-button-reset',
5585 'click': L.bind(this.revert, this)
5586 }, [ _('Revert') ])])])
5589 for (var config in this.changes) {
5590 if (!this.changes.hasOwnProperty(config))
5593 list.appendChild(E('h5', '# /etc/config/%s'.format(config)));
5595 for (var i =
0, added = null; i
< this.changes[config].length; i++) {
5596 var chg = this.changes[config][i],
5597 tpl = this.changeTemplates['%s-%d'.format(chg[
0], chg.length)];
5599 list.appendChild(E(tpl.replace(/%([
01234])/g, function(m0, m1) {
5605 if (added != null
&& chg[
1] == added[
0])
5606 return '@' + added[
1] + '[-
1]';
5611 return
"'%h'".format(chg[
3].replace(/'/g,
"'\"'\
"'"));
5618 if (chg[
0] == 'add')
5619 added = [ chg[
1], chg[
2] ];
5623 list.appendChild(E('br'));
5624 dlg.classList.add('uci-dialog');
5628 displayStatus: function(type, content) {
5630 var message = UI.prototype.showModal('', '');
5632 message.classList.add('alert-message');
5633 DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/));
5636 dom.content(message, content);
5638 if (!this.was_polling) {
5639 this.was_polling = request.poll.active();
5640 request.poll.stop();
5644 UI.prototype.hideModal();
5646 if (this.was_polling)
5647 request.poll.start();
5652 rollback: function(checked) {
5654 this.displayStatus('warning spinning',
5655 E('p', _('Failed to confirm apply within %ds, waiting for rollback…')
5656 .format(L.env.apply_rollback)));
5658 var call = function(r, data, duration) {
5659 if (r.status ===
204) {
5660 UI.prototype.changes.displayStatus('warning', [
5661 E('h4', _('Configuration changes have been rolled back!')),
5662 E('p', _('The device could not be reached within %d seconds after applying the pending changes, which caused the configuration to be rolled back for safety reasons. If you believe that the configuration changes are correct nonetheless, perform an unchecked configuration apply. Alternatively, you can dismiss this warning and edit changes before attempting to apply again, or revert all pending changes to keep the currently working configuration state.').format(L.env.apply_rollback)),
5663 E('div', { 'class': 'right' }, [
5666 'click': L.bind(UI.prototype.changes.displayStatus, UI.prototype.changes, false)
5667 }, [ _('Dismiss') ]), ' ',
5669 'class': 'btn cbi-button-action important',
5670 'click': L.bind(UI.prototype.changes.revert, UI.prototype.changes)
5671 }, [ _('Revert changes') ]), ' ',
5673 'class': 'btn cbi-button-negative important',
5674 'click': L.bind(UI.prototype.changes.apply, UI.prototype.changes, false)
5675 }, [ _('Apply unchecked') ])
5682 var delay = isNaN(duration) ?
0 : Math.max(
1000 - duration,
0);
5683 window.setTimeout(function() {
5684 request.request(L.url('admin/uci/confirm'), {
5686 timeout: L.env.apply_timeout *
1000,
5687 query: { sid: L.env.sessionid, token: L.env.token }
5692 call({ status:
0 });
5695 this.displayStatus('warning', [
5696 E('h4', _('Device unreachable!')),
5697 E('p', _('Could not regain access to the device after applying the configuration changes. You might need to reconnect if you modified network related settings such as the IP address or wireless security credentials.'))
5703 confirm: function(checked, deadline, override_token) {
5705 var ts = Date.now();
5707 this.displayStatus('notice');
5710 this.confirm_auth = { token: override_token };
5712 var call = function(r, data, duration) {
5713 if (Date.now()
>= deadline) {
5714 window.clearTimeout(tt);
5715 UI.prototype.changes.rollback(checked);
5718 else if (r
&& (r.status ===
200 || r.status ===
204)) {
5719 document.dispatchEvent(new CustomEvent('uci-applied'));
5721 UI.prototype.changes.setIndicator(
0);
5722 UI.prototype.changes.displayStatus('notice',
5723 E('p', _('Configuration changes applied.')));
5725 window.clearTimeout(tt);
5726 window.setTimeout(function() {
5727 //UI.prototype.changes.displayStatus(false);
5728 window.location = window.location.href.split('#')[
0];
5729 }, L.env.apply_display *
1000);
5734 var delay = isNaN(duration) ?
0 : Math.max(
1000 - duration,
0);
5735 window.setTimeout(function() {
5736 request.request(L.url('admin/uci/confirm'), {
5738 timeout: L.env.apply_timeout *
1000,
5739 query: UI.prototype.changes.confirm_auth
5740 }).then(call, call);
5744 var tick = function() {
5745 var now = Date.now();
5747 UI.prototype.changes.displayStatus('notice spinning',
5748 E('p', _('Applying configuration changes… %ds')
5749 .format(Math.max(Math.floor((deadline - Date.now()) /
1000),
0))));
5751 if (now
>= deadline)
5754 tt = window.setTimeout(tick,
1000 - (now - ts));
5760 /* wait a few seconds for the settings to become effective */
5761 window.setTimeout(call, Math.max(L.env.apply_holdoff *
1000 - ((ts + L.env.apply_rollback *
1000) - deadline),
1));
5765 * Apply the staged configuration changes.
5767 * Start applying staged configuration changes and open a modal dialog
5768 * with a progress indication to prevent interaction with the view
5769 * during the apply process. The modal dialog will be automatically
5770 * closed and the current view reloaded once the apply process is
5774 * @memberof LuCI.ui.changes
5775 * @param {boolean} [checked=false]
5776 * Whether to perform a checked (`true`) configuration apply or an
5777 * unchecked (`false`) one.
5779 * In case of a checked apply, the configuration changes must be
5780 * confirmed within a specific time interval, otherwise the device
5781 * will begin to roll back the changes in order to restore the previous
5784 apply: function(checked) {
5785 this.displayStatus('notice spinning',
5786 E('p', _('Starting configuration apply…')));
5788 request.request(L.url('admin/uci', checked ? 'apply_rollback' : 'apply_unchecked'), {
5790 query: { sid: L.env.sessionid, token: L.env.token }
5791 }).then(function(r) {
5792 if (r.status === (checked ?
200 :
204)) {
5793 var tok = null; try { tok = r.json(); } catch(e) {}
5794 if (checked
&& tok !== null
&& typeof(tok) === 'object'
&& typeof(tok.token) === 'string')
5795 UI.prototype.changes.confirm_auth = tok;
5797 UI.prototype.changes.confirm(checked, Date.now() + L.env.apply_rollback *
1000);
5799 else if (checked
&& r.status ===
204) {
5800 UI.prototype.changes.displayStatus('notice',
5801 E('p', _('There are no changes to apply')));
5803 window.setTimeout(function() {
5804 UI.prototype.changes.displayStatus(false);
5805 }, L.env.apply_display *
1000);
5808 UI.prototype.changes.displayStatus('warning',
5809 E('p', _('Apply request failed with status
<code
>%h
</code
>')
5810 .format(r.responseText || r.statusText || r.status)));
5812 window.setTimeout(function() {
5813 UI.prototype.changes.displayStatus(false);
5814 }, L.env.apply_display *
1000);
5820 * Revert the staged configuration changes.
5822 * Start reverting staged configuration changes and open a modal dialog
5823 * with a progress indication to prevent interaction with the view
5824 * during the revert process. The modal dialog will be automatically
5825 * closed and the current view reloaded once the revert process is
5829 * @memberof LuCI.ui.changes
5831 revert: function() {
5832 this.displayStatus('notice spinning',
5833 E('p', _('Reverting configuration…')));
5835 request.request(L.url('admin/uci/revert'), {
5837 query: { sid: L.env.sessionid, token: L.env.token }
5838 }).then(function(r) {
5839 if (r.status ===
200) {
5840 document.dispatchEvent(new CustomEvent('uci-reverted'));
5842 UI.prototype.changes.setIndicator(
0);
5843 UI.prototype.changes.displayStatus('notice',
5844 E('p', _('Changes have been reverted.')));
5846 window.setTimeout(function() {
5847 //UI.prototype.changes.displayStatus(false);
5848 window.location = window.location.href.split('#')[
0];
5849 }, L.env.apply_display *
1000);
5852 UI.prototype.changes.displayStatus('warning',
5853 E('p', _('Revert request failed with status
<code
>%h
</code
>')
5854 .format(r.statusText || r.status)));
5856 window.setTimeout(function() {
5857 UI.prototype.changes.displayStatus(false);
5858 }, L.env.apply_display *
1000);
5865 * Add validation constraints to an input element.
5867 * Compile the given type expression and optional validator function into
5868 * a validation function and bind it to the specified input element events.
5870 * @param {Node} field
5871 * The DOM input element node to bind the validation constraints to.
5873 * @param {string} type
5874 * The datatype specification to describe validation constraints.
5875 * Refer to the `LuCI.validation` class documentation for details.
5877 * @param {boolean} [optional=false]
5878 * Specifies whether empty values are allowed (`true`) or not (`false`).
5879 * If an input element is not marked optional it must not be empty,
5880 * otherwise it will be marked as invalid.
5882 * @param {function} [vfunc]
5883 * Specifies a custom validation function which is invoked after the
5884 * other validation constraints are applied. The validation must return
5885 * `true` to accept the passed value. Any other return type is converted
5886 * to a string and treated as validation error message.
5888 * @param {...string} [events=blur, keyup]
5889 * The list of events to bind. Each received event will trigger a field
5890 * validation. If omitted, the `keyup` and `blur` events are bound by
5893 * @returns {function}
5894 * Returns the compiled validator function which can be used to manually
5895 * trigger field validation or to bind it to further events.
5897 * @see LuCI.validation
5899 addValidator: function(field, type, optional, vfunc /*, ... */) {
5903 var events = this.varargs(arguments,
3);
5904 if (events.length ==
0)
5905 events.push('blur', 'keyup');
5908 var cbiValidator = validation.create(field, type, optional, vfunc),
5909 validatorFn = cbiValidator.validate.bind(cbiValidator);
5911 for (var i =
0; i
< events.length; i++)
5912 field.addEventListener(events[i], validatorFn);
5922 * Create a pre-bound event handler function.
5924 * Generate and bind a function suitable for use in event handlers. The
5925 * generated function automatically disables the event source element
5926 * and adds an active indication to it by adding appropriate CSS classes.
5928 * It will also await any promises returned by the wrapped function and
5929 * re-enable the source element after the promises ran to completion.
5932 * The `this` context to use for the wrapped function.
5934 * @param {function|string} fn
5935 * Specifies the function to wrap. In case of a function value, the
5936 * function is used as-is. If a string is specified instead, it is looked
5937 * up in `ctx` to obtain the function to wrap. In both cases the bound
5938 * function will be invoked with `ctx` as `this` context
5940 * @param {...*} extra_args
5941 * Any further parameter as passed as-is to the bound event handler
5942 * function in the same order as passed to `createHandlerFn()`.
5944 * @returns {function|null}
5945 * Returns the pre-bound handler function which is suitable to be passed
5946 * to `addEventListener()`. Returns `null` if the given `fn` argument is
5947 * a string which could not be found in `ctx` or if `ctx[fn]` is not a
5948 * valid function value.
5950 createHandlerFn: function(ctx, fn /*, ... */) {
5951 if (typeof(fn) == 'string')
5954 if (typeof(fn) != 'function')
5957 var arg_offset = arguments.length -
2;
5959 return Function.prototype.bind.apply(function() {
5960 var t = arguments[arg_offset].currentTarget;
5962 t.classList.add('spinning');
5968 Promise.resolve(fn.apply(ctx, arguments)).finally(function() {
5969 t.classList.remove('spinning');
5972 }, this.varargs(arguments,
2, ctx));
5976 * Load specified view class path and set it up.
5978 * Transforms the given view path into a class name, requires it
5979 * using [LuCI.require()]{@link LuCI#require} and asserts that the
5980 * resulting class instance is a descendant of
5981 * [LuCI.view]{@link LuCI.view}.
5983 * By instantiating the view class, its corresponding contents are
5984 * rendered and included into the view area. Any runtime errors are
5985 * catched and rendered using [LuCI.error()]{@link LuCI#error}.
5987 * @param {string} path
5988 * The view path to render.
5990 * @returns {Promise
<LuCI.view
>}
5991 * Returns a promise resolving to the loaded view instance.
5993 instantiateView: function(path) {
5994 var className = 'view.%s'.format(path.replace(/\//g, '.'));
5996 return L.require(className).then(function(view) {
5997 if (!(view instanceof View))
5998 throw new TypeError('Loaded class %s is not a descendant of View'.format(className));
6001 }).catch(function(err) {
6002 dom.content(document.querySelector('#view'), null);
6007 AbstractElement: UIElement,
6010 Textfield: UITextfield,
6011 Textarea: UITextarea,
6012 Checkbox: UICheckbox,
6014 Dropdown: UIDropdown,
6015 DynamicList: UIDynamicList,
6016 Combobox: UICombobox,
6017 ComboButton: UIComboButton,
6018 Hiddenfield: UIHiddenfield,
6019 FileUpload: UIFileUpload
6035 Documentation generated by
<a href=
"https://github.com/jsdoc3/jsdoc">JSDoc
3.6.3</a> on Fri Apr
03 2020 13:
28:
08 GMT+
0200 (Central European Summer Time)
6039 <script>prettyPrint();
</script>
6040 <script src=
"scripts/jaguar.js"></script>