2019-02-07 17:29:29 +00:00
'use strict' ;
2019-02-07 18:10:34 +00:00
'require uci' ;
2019-05-28 16:49:11 +00:00
'require validation' ;
2019-02-07 17:29:29 +00:00
2019-01-07 14:26:08 +00:00
var modalDiv = null ,
tooltipDiv = null ,
tooltipTimeout = null ;
2019-02-07 17:29:29 +00:00
var UIElement = L . Class . extend ( {
getValue : function ( ) {
if ( L . dom . matches ( this . node , 'select' ) || L . dom . matches ( this . node , 'input' ) )
return this . node . value ;
return null ;
} ,
setValue : function ( value ) {
if ( L . dom . matches ( this . node , 'select' ) || L . dom . matches ( this . node , 'input' ) )
this . node . value = value ;
} ,
isValid : function ( ) {
2019-04-01 14:00:10 +00:00
return ( this . validState !== false ) ;
} ,
triggerValidation : function ( ) {
if ( typeof ( this . vfunc ) != 'function' )
return false ;
var wasValid = this . isValid ( ) ;
this . vfunc ( ) ;
return ( wasValid != this . isValid ( ) ) ;
2019-02-07 17:29:29 +00:00
} ,
registerEvents : function ( targetNode , synevent , events ) {
var dispatchFn = L . bind ( function ( ev ) {
this . node . dispatchEvent ( new CustomEvent ( synevent , { bubbles : true } ) ) ;
} , this ) ;
for ( var i = 0 ; i < events . length ; i ++ )
targetNode . addEventListener ( events [ i ] , dispatchFn ) ;
} ,
setUpdateEvents : function ( targetNode /*, ... */ ) {
2019-04-01 14:00:10 +00:00
var datatype = this . options . datatype ,
optional = this . options . hasOwnProperty ( 'optional' ) ? this . options . optional : true ,
validate = this . options . validate ,
events = this . varargs ( arguments , 1 ) ;
this . registerEvents ( targetNode , 'widget-update' , events ) ;
if ( ! datatype && ! validate )
return ;
this . vfunc = L . ui . addValidator . apply ( L . ui , [
targetNode , datatype || 'string' ,
optional , validate
] . concat ( events ) ) ;
this . node . addEventListener ( 'validation-success' , L . bind ( function ( ev ) {
this . validState = true ;
} , this ) ) ;
this . node . addEventListener ( 'validation-failure' , L . bind ( function ( ev ) {
this . validState = false ;
} , this ) ) ;
2019-02-07 17:29:29 +00:00
} ,
setChangeEvents : function ( targetNode /*, ... */ ) {
this . registerEvents ( targetNode , 'widget-change' , this . varargs ( arguments , 1 ) ) ;
}
} ) ;
2019-04-01 14:00:10 +00:00
var UITextfield = UIElement . extend ( {
_ _init _ _ : function ( value , options ) {
this . value = value ;
this . options = Object . assign ( {
optional : true ,
password : false
} , options ) ;
} ,
render : function ( ) {
var frameEl = E ( 'div' , { 'id' : this . options . id } ) ;
if ( this . options . password ) {
frameEl . classList . add ( 'nowrap' ) ;
frameEl . appendChild ( E ( 'input' , {
'type' : 'password' ,
'style' : 'position:absolute; left:-100000px' ,
'aria-hidden' : true ,
'tabindex' : - 1 ,
'name' : this . options . name ? 'password.%s' . format ( this . options . name ) : null
} ) ) ;
}
frameEl . appendChild ( E ( 'input' , {
2019-07-09 12:07:09 +00:00
'id' : this . options . id ? 'widget.' + this . options . id : null ,
2019-04-01 14:00:10 +00:00
'name' : this . options . name ,
'type' : this . options . password ? 'password' : 'text' ,
'class' : this . options . password ? 'cbi-input-password' : 'cbi-input-text' ,
'readonly' : this . options . readonly ? '' : null ,
'maxlength' : this . options . maxlength ,
'placeholder' : this . options . placeholder ,
'value' : this . value ,
} ) ) ;
if ( this . options . password )
frameEl . appendChild ( E ( 'button' , {
'class' : 'cbi-button cbi-button-neutral' ,
'title' : _ ( 'Reveal/hide password' ) ,
'aria-label' : _ ( 'Reveal/hide password' ) ,
'click' : function ( ev ) {
var e = this . previousElementSibling ;
e . type = ( e . type === 'password' ) ? 'text' : 'password' ;
ev . preventDefault ( ) ;
}
} , '∗ ' ) ) ;
return this . bind ( frameEl ) ;
} ,
bind : function ( frameEl ) {
var inputEl = frameEl . childNodes [ + ! ! this . options . password ] ;
this . node = frameEl ;
this . setUpdateEvents ( inputEl , 'keyup' , 'blur' ) ;
this . setChangeEvents ( inputEl , 'change' ) ;
L . dom . bindClassInstance ( frameEl , this ) ;
return frameEl ;
} ,
getValue : function ( ) {
var inputEl = this . node . childNodes [ + ! ! this . options . password ] ;
return inputEl . value ;
} ,
setValue : function ( value ) {
var inputEl = this . node . childNodes [ + ! ! this . options . password ] ;
inputEl . value = value ;
}
} ) ;
var UICheckbox = UIElement . extend ( {
_ _init _ _ : function ( value , options ) {
this . value = value ;
this . options = Object . assign ( {
value _enabled : '1' ,
value _disabled : '0'
} , options ) ;
} ,
render : function ( ) {
var frameEl = E ( 'div' , {
'id' : this . options . id ,
'class' : 'cbi-checkbox'
} ) ;
if ( this . options . hiddenname )
frameEl . appendChild ( E ( 'input' , {
'type' : 'hidden' ,
'name' : this . options . hiddenname ,
'value' : 1
} ) ) ;
frameEl . appendChild ( E ( 'input' , {
2019-07-09 12:07:09 +00:00
'id' : this . options . id ? 'widget.' + this . options . id : null ,
2019-04-01 14:00:10 +00:00
'name' : this . options . name ,
'type' : 'checkbox' ,
'value' : this . options . value _enabled ,
'checked' : ( this . value == this . options . value _enabled ) ? '' : null
} ) ) ;
return this . bind ( frameEl ) ;
} ,
bind : function ( frameEl ) {
this . node = frameEl ;
this . setUpdateEvents ( frameEl . lastElementChild , 'click' , 'blur' ) ;
this . setChangeEvents ( frameEl . lastElementChild , 'change' ) ;
L . dom . bindClassInstance ( frameEl , this ) ;
return frameEl ;
} ,
isChecked : function ( ) {
return this . node . lastElementChild . checked ;
} ,
getValue : function ( ) {
return this . isChecked ( )
? this . options . value _enabled
: this . options . value _disabled ;
} ,
setValue : function ( value ) {
this . node . lastElementChild . checked = ( value == this . options . value _enabled ) ;
}
} ) ;
var UISelect = UIElement . extend ( {
_ _init _ _ : function ( value , choices , options ) {
if ( typeof ( choices ) != 'object' )
choices = { } ;
if ( ! Array . isArray ( value ) )
value = ( value != null && value != '' ) ? [ value ] : [ ] ;
2019-07-19 08:39:54 +00:00
if ( ! options . multiple && value . length > 1 )
2019-04-01 14:00:10 +00:00
value . length = 1 ;
this . values = value ;
this . choices = choices ;
this . options = Object . assign ( {
2019-07-19 08:39:54 +00:00
multiple : false ,
2019-04-01 14:00:10 +00:00
widget : 'select' ,
orientation : 'horizontal'
} , options ) ;
2019-07-28 15:24:12 +00:00
if ( this . choices . hasOwnProperty ( '' ) )
this . options . optional = true ;
2019-04-01 14:00:10 +00:00
} ,
render : function ( ) {
2019-07-09 12:07:09 +00:00
var frameEl = E ( 'div' , { 'id' : this . options . id } ) ,
2019-04-01 14:00:10 +00:00
keys = Object . keys ( this . choices ) ;
if ( this . options . sort === true )
keys . sort ( ) ;
else if ( Array . isArray ( this . options . sort ) )
keys = this . options . sort ;
if ( this . options . widget == 'select' ) {
2019-07-09 12:07:09 +00:00
frameEl . appendChild ( E ( 'select' , {
'id' : this . options . id ? 'widget.' + this . options . id : null ,
2019-04-01 14:00:10 +00:00
'name' : this . options . name ,
'size' : this . options . size ,
'class' : 'cbi-input-select' ,
2019-07-19 08:39:54 +00:00
'multiple' : this . options . multiple ? '' : null
2019-07-09 12:07:09 +00:00
} ) ) ;
2019-04-01 14:00:10 +00:00
2019-07-28 15:24:12 +00:00
if ( this . options . optional )
2019-07-09 12:07:09 +00:00
frameEl . lastChild . appendChild ( E ( 'option' , {
2019-04-01 14:00:10 +00:00
'value' : '' ,
'selected' : ( this . values . length == 0 || this . values [ 0 ] == '' ) ? '' : null
} , this . choices [ '' ] || this . options . placeholder || _ ( '-- Please choose --' ) ) ) ;
for ( var i = 0 ; i < keys . length ; i ++ ) {
if ( keys [ i ] == null || keys [ i ] == '' )
continue ;
2019-07-09 12:07:09 +00:00
frameEl . lastChild . appendChild ( E ( 'option' , {
2019-04-01 14:00:10 +00:00
'value' : keys [ i ] ,
'selected' : ( this . values . indexOf ( keys [ i ] ) > - 1 ) ? '' : null
} , this . choices [ keys [ i ] ] || keys [ i ] ) ) ;
}
}
else {
var brEl = ( this . options . orientation === 'horizontal' ) ? document . createTextNode ( ' ' ) : E ( 'br' ) ;
for ( var i = 0 ; i < keys . length ; i ++ ) {
frameEl . appendChild ( E ( 'label' , { } , [
E ( 'input' , {
2019-07-09 12:07:09 +00:00
'id' : this . options . id ? 'widget.' + this . options . id : null ,
2019-04-01 14:00:10 +00:00
'name' : this . options . id || this . options . name ,
2019-07-19 08:39:54 +00:00
'type' : this . options . multiple ? 'checkbox' : 'radio' ,
'class' : this . options . multiple ? 'cbi-input-checkbox' : 'cbi-input-radio' ,
2019-04-01 14:00:10 +00:00
'value' : keys [ i ] ,
'checked' : ( this . values . indexOf ( keys [ i ] ) > - 1 ) ? '' : null
} ) ,
this . choices [ keys [ i ] ] || keys [ i ]
] ) ) ;
if ( i + 1 == this . options . size )
frameEl . appendChild ( brEl ) ;
}
}
return this . bind ( frameEl ) ;
} ,
bind : function ( frameEl ) {
this . node = frameEl ;
if ( this . options . widget == 'select' ) {
2019-07-22 15:17:10 +00:00
this . setUpdateEvents ( frameEl . firstChild , 'change' , 'click' , 'blur' ) ;
this . setChangeEvents ( frameEl . firstChild , 'change' ) ;
2019-04-01 14:00:10 +00:00
}
else {
var radioEls = frameEl . querySelectorAll ( 'input[type="radio"]' ) ;
for ( var i = 0 ; i < radioEls . length ; i ++ ) {
this . setUpdateEvents ( radioEls [ i ] , 'change' , 'click' , 'blur' ) ;
this . setChangeEvents ( radioEls [ i ] , 'change' , 'click' , 'blur' ) ;
}
}
L . dom . bindClassInstance ( frameEl , this ) ;
return frameEl ;
} ,
getValue : function ( ) {
if ( this . options . widget == 'select' )
2019-07-11 12:39:42 +00:00
return this . node . firstChild . value ;
2019-04-01 14:00:10 +00:00
var radioEls = frameEl . querySelectorAll ( 'input[type="radio"]' ) ;
for ( var i = 0 ; i < radioEls . length ; i ++ )
if ( radioEls [ i ] . checked )
return radioEls [ i ] . value ;
return null ;
} ,
setValue : function ( value ) {
if ( this . options . widget == 'select' ) {
if ( value == null )
value = '' ;
2019-07-12 07:17:14 +00:00
for ( var i = 0 ; i < this . node . firstChild . options . length ; i ++ )
this . node . firstChild . options [ i ] . selected = ( this . node . firstChild . options [ i ] . value == value ) ;
2019-04-01 14:00:10 +00:00
return ;
}
var radioEls = frameEl . querySelectorAll ( 'input[type="radio"]' ) ;
for ( var i = 0 ; i < radioEls . length ; i ++ )
radioEls [ i ] . checked = ( radioEls [ i ] . value == value ) ;
}
} ) ;
2019-02-07 17:29:29 +00:00
var UIDropdown = UIElement . extend ( {
_ _init _ _ : function ( value , choices , options ) {
if ( typeof ( choices ) != 'object' )
choices = { } ;
if ( ! Array . isArray ( value ) )
2019-04-01 14:00:10 +00:00
this . values = ( value != null && value != '' ) ? [ value ] : [ ] ;
2019-02-07 17:29:29 +00:00
else
this . values = value ;
this . choices = choices ;
this . options = Object . assign ( {
sort : true ,
2019-07-19 08:39:54 +00:00
multiple : Array . isArray ( value ) ,
2019-02-07 17:29:29 +00:00
optional : true ,
select _placeholder : _ ( '-- Please choose --' ) ,
custom _placeholder : _ ( '-- custom --' ) ,
display _items : 3 ,
2019-05-28 13:28:53 +00:00
dropdown _items : - 1 ,
2019-02-07 17:29:29 +00:00
create : false ,
create _query : '.create-item-input' ,
create _template : 'script[type="item-template"]'
} , options ) ;
} ,
render : function ( ) {
var sb = E ( 'div' , {
'id' : this . options . id ,
'class' : 'cbi-dropdown' ,
2019-07-19 08:39:54 +00:00
'multiple' : this . options . multiple ? '' : null ,
2019-02-07 17:29:29 +00:00
'optional' : this . options . optional ? '' : null ,
} , E ( 'ul' ) ) ;
var keys = Object . keys ( this . choices ) ;
if ( this . options . sort === true )
keys . sort ( ) ;
else if ( Array . isArray ( this . options . sort ) )
keys = this . options . sort ;
if ( this . options . create )
for ( var i = 0 ; i < this . values . length ; i ++ )
if ( ! this . choices . hasOwnProperty ( this . values [ i ] ) )
keys . push ( this . values [ i ] ) ;
for ( var i = 0 ; i < keys . length ; i ++ )
sb . lastElementChild . appendChild ( E ( 'li' , {
'data-value' : keys [ i ] ,
'selected' : ( this . values . indexOf ( keys [ i ] ) > - 1 ) ? '' : null
} , this . choices [ keys [ i ] ] || keys [ i ] ) ) ;
if ( this . options . create ) {
var createEl = E ( 'input' , {
'type' : 'text' ,
'class' : 'create-item-input' ,
2019-04-01 14:00:10 +00:00
'readonly' : this . options . readonly ? '' : null ,
'maxlength' : this . options . maxlength ,
2019-02-07 17:29:29 +00:00
'placeholder' : this . options . custom _placeholder || this . options . placeholder
} ) ;
if ( this . options . datatype )
2019-04-01 14:00:10 +00:00
L . ui . addValidator ( createEl , this . options . datatype ,
true , null , 'blur' , 'keyup' ) ;
2019-02-07 17:29:29 +00:00
sb . lastElementChild . appendChild ( E ( 'li' , { 'data-value' : '-' } , createEl ) ) ;
}
2019-04-01 14:00:10 +00:00
if ( this . options . create _markup )
sb . appendChild ( E ( 'script' , { type : 'item-template' } ,
this . options . create _markup ) ) ;
2019-02-07 17:29:29 +00:00
return this . bind ( sb ) ;
} ,
bind : function ( sb ) {
var o = this . options ;
2019-07-19 08:39:54 +00:00
o . multiple = sb . hasAttribute ( 'multiple' ) ;
2019-02-07 17:29:29 +00:00
o . optional = sb . hasAttribute ( 'optional' ) ;
o . placeholder = sb . getAttribute ( 'placeholder' ) || o . placeholder ;
o . display _items = parseInt ( sb . getAttribute ( 'display-items' ) || o . display _items ) ;
o . dropdown _items = parseInt ( sb . getAttribute ( 'dropdown-items' ) || o . dropdown _items ) ;
o . create _query = sb . getAttribute ( 'item-create' ) || o . create _query ;
o . create _template = sb . getAttribute ( 'item-template' ) || o . create _template ;
var ul = sb . querySelector ( 'ul' ) ,
more = sb . appendChild ( E ( 'span' , { class : 'more' , tabindex : - 1 } , '···' ) ) ,
open = sb . appendChild ( E ( 'span' , { class : 'open' , tabindex : - 1 } , '▾' ) ) ,
canary = sb . appendChild ( E ( 'div' ) ) ,
create = sb . querySelector ( this . options . create _query ) ,
ndisplay = this . options . display _items ,
n = 0 ;
2019-07-19 08:39:54 +00:00
if ( this . options . multiple ) {
2019-02-07 17:29:29 +00:00
var items = ul . querySelectorAll ( 'li' ) ;
for ( var i = 0 ; i < items . length ; i ++ ) {
this . transformItem ( sb , items [ i ] ) ;
if ( items [ i ] . hasAttribute ( 'selected' ) && ndisplay -- > 0 )
items [ i ] . setAttribute ( 'display' , n ++ ) ;
}
}
else {
if ( this . options . optional && ! ul . querySelector ( 'li[data-value=""]' ) ) {
var placeholder = E ( 'li' , { placeholder : '' } ,
this . options . select _placeholder || this . options . placeholder ) ;
ul . firstChild
? ul . insertBefore ( placeholder , ul . firstChild )
: ul . appendChild ( placeholder ) ;
}
var items = ul . querySelectorAll ( 'li' ) ,
sel = sb . querySelectorAll ( '[selected]' ) ;
sel . forEach ( function ( s ) {
s . removeAttribute ( 'selected' ) ;
} ) ;
var s = sel [ 0 ] || items [ 0 ] ;
if ( s ) {
s . setAttribute ( 'selected' , '' ) ;
s . setAttribute ( 'display' , n ++ ) ;
}
ndisplay -- ;
}
this . saveValues ( sb , ul ) ;
ul . setAttribute ( 'tabindex' , - 1 ) ;
sb . setAttribute ( 'tabindex' , 0 ) ;
if ( ndisplay < 0 )
sb . setAttribute ( 'more' , '' )
else
sb . removeAttribute ( 'more' ) ;
if ( ndisplay == this . options . display _items )
sb . setAttribute ( 'empty' , '' )
else
sb . removeAttribute ( 'empty' ) ;
2019-06-06 18:49:19 +00:00
L . dom . content ( more , ( ndisplay == this . options . display _items )
? ( this . options . select _placeholder || this . options . placeholder ) : '···' ) ;
2019-02-07 17:29:29 +00:00
sb . addEventListener ( 'click' , this . handleClick . bind ( this ) ) ;
sb . addEventListener ( 'keydown' , this . handleKeydown . bind ( this ) ) ;
sb . addEventListener ( 'cbi-dropdown-close' , this . handleDropdownClose . bind ( this ) ) ;
sb . addEventListener ( 'cbi-dropdown-select' , this . handleDropdownSelect . bind ( this ) ) ;
if ( 'ontouchstart' in window ) {
sb . addEventListener ( 'touchstart' , function ( ev ) { ev . stopPropagation ( ) ; } ) ;
window . addEventListener ( 'touchstart' , this . closeAllDropdowns ) ;
}
else {
sb . addEventListener ( 'mouseover' , this . handleMouseover . bind ( this ) ) ;
sb . addEventListener ( 'focus' , this . handleFocus . bind ( this ) ) ;
canary . addEventListener ( 'focus' , this . handleCanaryFocus . bind ( this ) ) ;
window . addEventListener ( 'mouseover' , this . setFocus ) ;
window . addEventListener ( 'click' , this . closeAllDropdowns ) ;
}
if ( create ) {
create . addEventListener ( 'keydown' , this . handleCreateKeydown . bind ( this ) ) ;
create . addEventListener ( 'focus' , this . handleCreateFocus . bind ( this ) ) ;
create . addEventListener ( 'blur' , this . handleCreateBlur . bind ( this ) ) ;
var li = findParent ( create , 'li' ) ;
li . setAttribute ( 'unselectable' , '' ) ;
li . addEventListener ( 'click' , this . handleCreateClick . bind ( this ) ) ;
}
this . node = sb ;
this . setUpdateEvents ( sb , 'cbi-dropdown-open' , 'cbi-dropdown-close' ) ;
this . setChangeEvents ( sb , 'cbi-dropdown-change' , 'cbi-dropdown-close' ) ;
L . dom . bindClassInstance ( sb , this ) ;
return sb ;
} ,
openDropdown : function ( sb ) {
var st = window . getComputedStyle ( sb , null ) ,
ul = sb . querySelector ( 'ul' ) ,
li = ul . querySelectorAll ( 'li' ) ,
fl = findParent ( sb , '.cbi-value-field' ) ,
sel = ul . querySelector ( '[selected]' ) ,
rect = sb . getBoundingClientRect ( ) ,
items = Math . min ( this . options . dropdown _items , li . length ) ;
document . querySelectorAll ( '.cbi-dropdown[open]' ) . forEach ( function ( s ) {
s . dispatchEvent ( new CustomEvent ( 'cbi-dropdown-close' , { } ) ) ;
} ) ;
sb . setAttribute ( 'open' , '' ) ;
var pv = ul . cloneNode ( true ) ;
pv . classList . add ( 'preview' ) ;
if ( fl )
fl . classList . add ( 'cbi-dropdown-open' ) ;
if ( 'ontouchstart' in window ) {
var vpWidth = Math . max ( document . documentElement . clientWidth , window . innerWidth || 0 ) ,
vpHeight = Math . max ( document . documentElement . clientHeight , window . innerHeight || 0 ) ,
scrollFrom = window . pageYOffset ,
scrollTo = scrollFrom + rect . top - vpHeight * 0.5 ,
start = null ;
ul . style . top = sb . offsetHeight + 'px' ;
ul . style . left = - rect . left + 'px' ;
ul . style . right = ( rect . right - vpWidth ) + 'px' ;
ul . style . maxHeight = ( vpHeight * 0.5 ) + 'px' ;
ul . style . WebkitOverflowScrolling = 'touch' ;
var scrollStep = function ( timestamp ) {
if ( ! start ) {
start = timestamp ;
ul . scrollTop = sel ? Math . max ( sel . offsetTop - sel . offsetHeight , 0 ) : 0 ;
}
var duration = Math . max ( timestamp - start , 1 ) ;
if ( duration < 100 ) {
document . body . scrollTop = scrollFrom + ( scrollTo - scrollFrom ) * ( duration / 100 ) ;
window . requestAnimationFrame ( scrollStep ) ;
}
else {
document . body . scrollTop = scrollTo ;
}
} ;
window . requestAnimationFrame ( scrollStep ) ;
}
else {
ul . style . maxHeight = '1px' ;
ul . style . top = ul . style . bottom = '' ;
window . requestAnimationFrame ( function ( ) {
2019-05-28 13:28:53 +00:00
var itemHeight = li [ Math . max ( 0 , li . length - 2 ) ] . getBoundingClientRect ( ) . height ,
fullHeight = 0 ,
spaceAbove = rect . top ,
spaceBelow = window . innerHeight - rect . height - rect . top ;
for ( var i = 0 ; i < ( items == - 1 ? li . length : items ) ; i ++ )
fullHeight += li [ i ] . getBoundingClientRect ( ) . height ;
if ( fullHeight <= spaceBelow ) {
ul . style . top = rect . height + 'px' ;
ul . style . maxHeight = spaceBelow + 'px' ;
}
else if ( fullHeight <= spaceAbove ) {
ul . style . bottom = rect . height + 'px' ;
ul . style . maxHeight = spaceAbove + 'px' ;
}
else if ( spaceBelow >= spaceAbove ) {
ul . style . top = rect . height + 'px' ;
ul . style . maxHeight = ( spaceBelow - ( spaceBelow % itemHeight ) ) + 'px' ;
}
else {
ul . style . bottom = rect . height + 'px' ;
ul . style . maxHeight = ( spaceAbove - ( spaceAbove % itemHeight ) ) + 'px' ;
}
2019-02-07 17:29:29 +00:00
ul . scrollTop = sel ? Math . max ( sel . offsetTop - sel . offsetHeight , 0 ) : 0 ;
} ) ;
}
var cboxes = ul . querySelectorAll ( '[selected] input[type="checkbox"]' ) ;
for ( var i = 0 ; i < cboxes . length ; i ++ ) {
cboxes [ i ] . checked = true ;
cboxes [ i ] . disabled = ( cboxes . length == 1 && ! this . options . optional ) ;
} ;
ul . classList . add ( 'dropdown' ) ;
sb . insertBefore ( pv , ul . nextElementSibling ) ;
li . forEach ( function ( l ) {
l . setAttribute ( 'tabindex' , 0 ) ;
} ) ;
sb . lastElementChild . setAttribute ( 'tabindex' , 0 ) ;
this . setFocus ( sb , sel || li [ 0 ] , true ) ;
} ,
closeDropdown : function ( sb , no _focus ) {
if ( ! sb . hasAttribute ( 'open' ) )
return ;
var pv = sb . querySelector ( 'ul.preview' ) ,
ul = sb . querySelector ( 'ul.dropdown' ) ,
li = ul . querySelectorAll ( 'li' ) ,
fl = findParent ( sb , '.cbi-value-field' ) ;
li . forEach ( function ( l ) { l . removeAttribute ( 'tabindex' ) ; } ) ;
sb . lastElementChild . removeAttribute ( 'tabindex' ) ;
sb . removeChild ( pv ) ;
sb . removeAttribute ( 'open' ) ;
sb . style . width = sb . style . height = '' ;
ul . classList . remove ( 'dropdown' ) ;
ul . style . top = ul . style . bottom = ul . style . maxHeight = '' ;
if ( fl )
fl . classList . remove ( 'cbi-dropdown-open' ) ;
if ( ! no _focus )
this . setFocus ( sb , sb ) ;
this . saveValues ( sb , ul ) ;
} ,
toggleItem : function ( sb , li , force _state ) {
if ( li . hasAttribute ( 'unselectable' ) )
return ;
2019-07-19 08:39:54 +00:00
if ( this . options . multiple ) {
2019-02-07 17:29:29 +00:00
var cbox = li . querySelector ( 'input[type="checkbox"]' ) ,
items = li . parentNode . querySelectorAll ( 'li' ) ,
label = sb . querySelector ( 'ul.preview' ) ,
sel = li . parentNode . querySelectorAll ( '[selected]' ) . length ,
more = sb . querySelector ( '.more' ) ,
ndisplay = this . options . display _items ,
n = 0 ;
if ( li . hasAttribute ( 'selected' ) ) {
if ( force _state !== true ) {
if ( sel > 1 || this . options . optional ) {
li . removeAttribute ( 'selected' ) ;
cbox . checked = cbox . disabled = false ;
sel -- ;
}
else {
cbox . disabled = true ;
}
}
}
else {
if ( force _state !== false ) {
li . setAttribute ( 'selected' , '' ) ;
cbox . checked = true ;
cbox . disabled = false ;
sel ++ ;
}
}
while ( label && label . firstElementChild )
label . removeChild ( label . firstElementChild ) ;
for ( var i = 0 ; i < items . length ; i ++ ) {
items [ i ] . removeAttribute ( 'display' ) ;
if ( items [ i ] . hasAttribute ( 'selected' ) ) {
if ( ndisplay -- > 0 ) {
items [ i ] . setAttribute ( 'display' , n ++ ) ;
if ( label )
label . appendChild ( items [ i ] . cloneNode ( true ) ) ;
}
var c = items [ i ] . querySelector ( 'input[type="checkbox"]' ) ;
if ( c )
c . disabled = ( sel == 1 && ! this . options . optional ) ;
}
}
if ( ndisplay < 0 )
sb . setAttribute ( 'more' , '' ) ;
else
sb . removeAttribute ( 'more' ) ;
if ( ndisplay === this . options . display _items )
sb . setAttribute ( 'empty' , '' ) ;
else
sb . removeAttribute ( 'empty' ) ;
2019-06-06 18:49:19 +00:00
L . dom . content ( more , ( ndisplay === this . options . display _items )
? ( this . options . select _placeholder || this . options . placeholder ) : '···' ) ;
2019-02-07 17:29:29 +00:00
}
else {
var sel = li . parentNode . querySelector ( '[selected]' ) ;
if ( sel ) {
sel . removeAttribute ( 'display' ) ;
sel . removeAttribute ( 'selected' ) ;
}
li . setAttribute ( 'display' , 0 ) ;
li . setAttribute ( 'selected' , '' ) ;
this . closeDropdown ( sb , true ) ;
}
this . saveValues ( sb , li . parentNode ) ;
} ,
transformItem : function ( sb , li ) {
var cbox = E ( 'form' , { } , E ( 'input' , { type : 'checkbox' , tabindex : - 1 , onclick : 'event.preventDefault()' } ) ) ,
label = E ( 'label' ) ;
while ( li . firstChild )
label . appendChild ( li . firstChild ) ;
li . appendChild ( cbox ) ;
li . appendChild ( label ) ;
} ,
saveValues : function ( sb , ul ) {
var sel = ul . querySelectorAll ( 'li[selected]' ) ,
div = sb . lastElementChild ,
name = this . options . name ,
strval = '' ,
values = [ ] ;
while ( div . lastElementChild )
div . removeChild ( div . lastElementChild ) ;
sel . forEach ( function ( s ) {
if ( s . hasAttribute ( 'placeholder' ) )
return ;
var v = {
text : s . innerText ,
value : s . hasAttribute ( 'data-value' ) ? s . getAttribute ( 'data-value' ) : s . innerText ,
element : s
} ;
div . appendChild ( E ( 'input' , {
type : 'hidden' ,
name : name ,
value : v . value
} ) ) ;
values . push ( v ) ;
strval += strval . length ? ' ' + v . value : v . value ;
} ) ;
var detail = {
instance : this ,
element : sb
} ;
2019-07-19 08:39:54 +00:00
if ( this . options . multiple )
2019-02-07 17:29:29 +00:00
detail . values = values ;
else
detail . value = values . length ? values [ 0 ] : null ;
sb . value = strval ;
sb . dispatchEvent ( new CustomEvent ( 'cbi-dropdown-change' , {
bubbles : true ,
detail : detail
} ) ) ;
} ,
setValues : function ( sb , values ) {
var ul = sb . querySelector ( 'ul' ) ;
if ( this . options . create ) {
for ( var value in values ) {
this . createItems ( sb , value ) ;
2019-07-19 08:39:54 +00:00
if ( ! this . options . multiple )
2019-02-07 17:29:29 +00:00
break ;
}
}
2019-07-19 08:39:54 +00:00
if ( this . options . multiple ) {
2019-02-07 17:29:29 +00:00
var lis = ul . querySelectorAll ( 'li[data-value]' ) ;
for ( var i = 0 ; i < lis . length ; i ++ ) {
var value = lis [ i ] . getAttribute ( 'data-value' ) ;
if ( values === null || ! ( value in values ) )
this . toggleItem ( sb , lis [ i ] , false ) ;
else
this . toggleItem ( sb , lis [ i ] , true ) ;
}
}
else {
var ph = ul . querySelector ( 'li[placeholder]' ) ;
if ( ph )
this . toggleItem ( sb , ph ) ;
var lis = ul . querySelectorAll ( 'li[data-value]' ) ;
for ( var i = 0 ; i < lis . length ; i ++ ) {
var value = lis [ i ] . getAttribute ( 'data-value' ) ;
if ( values !== null && ( value in values ) )
this . toggleItem ( sb , lis [ i ] ) ;
}
}
} ,
setFocus : function ( sb , elem , scroll ) {
if ( sb && sb . hasAttribute && sb . hasAttribute ( 'locked-in' ) )
return ;
if ( sb . target && findParent ( sb . target , 'ul.dropdown' ) )
return ;
document . querySelectorAll ( '.focus' ) . forEach ( function ( e ) {
if ( ! matchesElem ( e , 'input' ) ) {
e . classList . remove ( 'focus' ) ;
e . blur ( ) ;
}
} ) ;
if ( elem ) {
elem . focus ( ) ;
elem . classList . add ( 'focus' ) ;
if ( scroll )
elem . parentNode . scrollTop = elem . offsetTop - elem . parentNode . offsetTop ;
}
} ,
createItems : function ( sb , value ) {
var sbox = this ,
val = ( value || '' ) . trim ( ) ,
ul = sb . querySelector ( 'ul' ) ;
2019-07-19 08:39:54 +00:00
if ( ! sbox . options . multiple )
2019-02-07 17:29:29 +00:00
val = val . length ? [ val ] : [ ] ;
else
val = val . length ? val . split ( /\s+/ ) : [ ] ;
val . forEach ( function ( item ) {
var new _item = null ;
ul . childNodes . forEach ( function ( li ) {
if ( li . getAttribute && li . getAttribute ( 'data-value' ) === item )
new _item = li ;
} ) ;
if ( ! new _item ) {
var markup ,
tpl = sb . querySelector ( sbox . options . create _template ) ;
if ( tpl )
markup = ( tpl . textContent || tpl . innerHTML || tpl . firstChild . data ) . replace ( /^<!--|-->$/ , '' ) . trim ( ) ;
else
markup = '<li data-value="{{value}}">{{value}}</li>' ;
2019-07-22 15:18:15 +00:00
new _item = E ( markup . replace ( /{{value}}/g , '%h' . format ( item ) ) ) ;
2019-02-07 17:29:29 +00:00
2019-07-19 08:39:54 +00:00
if ( sbox . options . multiple ) {
2019-02-07 17:29:29 +00:00
sbox . transformItem ( sb , new _item ) ;
}
else {
var old = ul . querySelector ( 'li[created]' ) ;
if ( old )
ul . removeChild ( old ) ;
new _item . setAttribute ( 'created' , '' ) ;
}
new _item = ul . insertBefore ( new _item , ul . lastElementChild ) ;
}
sbox . toggleItem ( sb , new _item , true ) ;
sbox . setFocus ( sb , new _item , true ) ;
} ) ;
} ,
closeAllDropdowns : function ( ) {
document . querySelectorAll ( '.cbi-dropdown[open]' ) . forEach ( function ( s ) {
s . dispatchEvent ( new CustomEvent ( 'cbi-dropdown-close' , { } ) ) ;
} ) ;
} ,
handleClick : function ( ev ) {
var sb = ev . currentTarget ;
if ( ! sb . hasAttribute ( 'open' ) ) {
if ( ! matchesElem ( ev . target , 'input' ) )
this . openDropdown ( sb ) ;
}
else {
var li = findParent ( ev . target , 'li' ) ;
if ( li && li . parentNode . classList . contains ( 'dropdown' ) )
this . toggleItem ( sb , li ) ;
else if ( li && li . parentNode . classList . contains ( 'preview' ) )
this . closeDropdown ( sb ) ;
2019-05-28 13:28:53 +00:00
else if ( matchesElem ( ev . target , 'span.open, span.more' ) )
this . closeDropdown ( sb ) ;
2019-02-07 17:29:29 +00:00
}
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
} ,
handleKeydown : function ( ev ) {
var sb = ev . currentTarget ;
if ( matchesElem ( ev . target , 'input' ) )
return ;
if ( ! sb . hasAttribute ( 'open' ) ) {
switch ( ev . keyCode ) {
case 37 :
case 38 :
case 39 :
case 40 :
this . openDropdown ( sb ) ;
ev . preventDefault ( ) ;
}
}
else {
var active = findParent ( document . activeElement , 'li' ) ;
switch ( ev . keyCode ) {
case 27 :
this . closeDropdown ( sb ) ;
break ;
case 13 :
if ( active ) {
if ( ! active . hasAttribute ( 'selected' ) )
this . toggleItem ( sb , active ) ;
this . closeDropdown ( sb ) ;
ev . preventDefault ( ) ;
}
break ;
case 32 :
if ( active ) {
this . toggleItem ( sb , active ) ;
ev . preventDefault ( ) ;
}
break ;
case 38 :
if ( active && active . previousElementSibling ) {
this . setFocus ( sb , active . previousElementSibling ) ;
ev . preventDefault ( ) ;
}
break ;
case 40 :
if ( active && active . nextElementSibling ) {
this . setFocus ( sb , active . nextElementSibling ) ;
ev . preventDefault ( ) ;
}
break ;
}
}
} ,
handleDropdownClose : function ( ev ) {
var sb = ev . currentTarget ;
this . closeDropdown ( sb , true ) ;
} ,
handleDropdownSelect : function ( ev ) {
var sb = ev . currentTarget ,
li = findParent ( ev . target , 'li' ) ;
if ( ! li )
return ;
this . toggleItem ( sb , li ) ;
this . closeDropdown ( sb , true ) ;
} ,
handleMouseover : function ( ev ) {
var sb = ev . currentTarget ;
if ( ! sb . hasAttribute ( 'open' ) )
return ;
var li = findParent ( ev . target , 'li' ) ;
if ( li && li . parentNode . classList . contains ( 'dropdown' ) )
this . setFocus ( sb , li ) ;
} ,
handleFocus : function ( ev ) {
var sb = ev . currentTarget ;
document . querySelectorAll ( '.cbi-dropdown[open]' ) . forEach ( function ( s ) {
if ( s !== sb || sb . hasAttribute ( 'open' ) )
s . dispatchEvent ( new CustomEvent ( 'cbi-dropdown-close' , { } ) ) ;
} ) ;
} ,
handleCanaryFocus : function ( ev ) {
this . closeDropdown ( ev . currentTarget . parentNode ) ;
} ,
handleCreateKeydown : function ( ev ) {
var input = ev . currentTarget ,
sb = findParent ( input , '.cbi-dropdown' ) ;
switch ( ev . keyCode ) {
case 13 :
ev . preventDefault ( ) ;
if ( input . classList . contains ( 'cbi-input-invalid' ) )
return ;
this . createItems ( sb , input . value ) ;
input . value = '' ;
input . blur ( ) ;
break ;
}
} ,
handleCreateFocus : function ( ev ) {
var input = ev . currentTarget ,
cbox = findParent ( input , 'li' ) . querySelector ( 'input[type="checkbox"]' ) ,
sb = findParent ( input , '.cbi-dropdown' ) ;
if ( cbox )
cbox . checked = true ;
sb . setAttribute ( 'locked-in' , '' ) ;
} ,
handleCreateBlur : function ( ev ) {
var input = ev . currentTarget ,
cbox = findParent ( input , 'li' ) . querySelector ( 'input[type="checkbox"]' ) ,
sb = findParent ( input , '.cbi-dropdown' ) ;
if ( cbox )
cbox . checked = false ;
sb . removeAttribute ( 'locked-in' ) ;
} ,
handleCreateClick : function ( ev ) {
ev . currentTarget . querySelector ( this . options . create _query ) . focus ( ) ;
} ,
setValue : function ( values ) {
2019-07-19 08:39:54 +00:00
if ( this . options . multiple ) {
2019-02-07 17:29:29 +00:00
if ( ! Array . isArray ( values ) )
2019-04-01 14:00:10 +00:00
values = ( values != null && values != '' ) ? [ values ] : [ ] ;
2019-02-07 17:29:29 +00:00
var v = { } ;
for ( var i = 0 ; i < values . length ; i ++ )
v [ values [ i ] ] = true ;
this . setValues ( this . node , v ) ;
}
else {
var v = { } ;
if ( values != null ) {
if ( Array . isArray ( values ) )
v [ values [ 0 ] ] = true ;
else
v [ values ] = true ;
}
this . setValues ( this . node , v ) ;
}
} ,
getValue : function ( ) {
var div = this . node . lastElementChild ,
h = div . querySelectorAll ( 'input[type="hidden"]' ) ,
v = [ ] ;
for ( var i = 0 ; i < h . length ; i ++ )
v . push ( h [ i ] . value ) ;
2019-07-19 08:39:54 +00:00
return this . options . multiple ? v : v [ 0 ] ;
2019-02-07 17:29:29 +00:00
}
} ) ;
var UICombobox = UIDropdown . extend ( {
_ _init _ _ : function ( value , choices , options ) {
this . super ( '__init__' , [ value , choices , Object . assign ( {
select _placeholder : _ ( '-- Please choose --' ) ,
custom _placeholder : _ ( '-- custom --' ) ,
2019-05-28 13:28:53 +00:00
dropdown _items : - 1 ,
2019-04-01 14:00:10 +00:00
sort : true
2019-02-07 17:29:29 +00:00
} , options , {
2019-07-19 08:39:54 +00:00
multiple : false ,
2019-02-07 17:29:29 +00:00
create : true ,
optional : true
} ) ] ) ;
}
} ) ;
var UIDynamicList = UIElement . extend ( {
_ _init _ _ : function ( values , choices , options ) {
if ( ! Array . isArray ( values ) )
2019-04-01 14:00:10 +00:00
values = ( values != null && values != '' ) ? [ values ] : [ ] ;
2019-02-07 17:29:29 +00:00
if ( typeof ( choices ) != 'object' )
choices = null ;
this . values = values ;
this . choices = choices ;
this . options = Object . assign ( { } , options , {
2019-07-19 08:39:54 +00:00
multiple : false ,
2019-02-07 17:29:29 +00:00
optional : true
} ) ;
} ,
render : function ( ) {
var dl = E ( 'div' , {
'id' : this . options . id ,
'class' : 'cbi-dynlist'
} , E ( 'div' , { 'class' : 'add-item' } ) ) ;
if ( this . choices ) {
var cbox = new UICombobox ( null , this . choices , this . options ) ;
dl . lastElementChild . appendChild ( cbox . render ( ) ) ;
}
else {
var inputEl = E ( 'input' , {
2019-07-09 12:07:09 +00:00
'id' : this . options . id ? 'widget.' + this . options . id : null ,
2019-02-07 17:29:29 +00:00
'type' : 'text' ,
'class' : 'cbi-input-text' ,
'placeholder' : this . options . placeholder
} ) ;
dl . lastElementChild . appendChild ( inputEl ) ;
dl . lastElementChild . appendChild ( E ( 'div' , { 'class' : 'cbi-button cbi-button-add' } , '+' ) ) ;
2019-04-01 14:00:10 +00:00
if ( this . options . datatype )
L . ui . addValidator ( inputEl , this . options . datatype ,
true , null , 'blur' , 'keyup' ) ;
2019-02-07 17:29:29 +00:00
}
for ( var i = 0 ; i < this . values . length ; i ++ )
this . addItem ( dl , this . values [ i ] ,
this . choices ? this . choices [ this . values [ i ] ] : null ) ;
return this . bind ( dl ) ;
} ,
bind : function ( dl ) {
dl . addEventListener ( 'click' , L . bind ( this . handleClick , this ) ) ;
dl . addEventListener ( 'keydown' , L . bind ( this . handleKeydown , this ) ) ;
dl . addEventListener ( 'cbi-dropdown-change' , L . bind ( this . handleDropdownChange , this ) ) ;
this . node = dl ;
this . setUpdateEvents ( dl , 'cbi-dynlist-change' ) ;
this . setChangeEvents ( dl , 'cbi-dynlist-change' ) ;
L . dom . bindClassInstance ( dl , this ) ;
return dl ;
} ,
addItem : function ( dl , value , text , flash ) {
var exists = false ,
new _item = E ( 'div' , { 'class' : flash ? 'item flash' : 'item' , 'tabindex' : 0 } , [
E ( 'span' , { } , text || value ) ,
E ( 'input' , {
'type' : 'hidden' ,
'name' : this . options . name ,
'value' : value } ) ] ) ;
dl . querySelectorAll ( '.item, .add-item' ) . forEach ( function ( item ) {
if ( exists )
return ;
var hidden = item . querySelector ( 'input[type="hidden"]' ) ;
if ( hidden && hidden . parentNode !== item )
hidden = null ;
if ( hidden && hidden . value === value )
exists = true ;
else if ( ! hidden || hidden . value >= value )
exists = ! ! item . parentNode . insertBefore ( new _item , item ) ;
} ) ;
dl . dispatchEvent ( new CustomEvent ( 'cbi-dynlist-change' , {
bubbles : true ,
detail : {
instance : this ,
element : dl ,
value : value ,
add : true
}
} ) ) ;
} ,
removeItem : function ( dl , item ) {
var value = item . querySelector ( 'input[type="hidden"]' ) . value ;
var sb = dl . querySelector ( '.cbi-dropdown' ) ;
if ( sb )
sb . querySelectorAll ( 'ul > li' ) . forEach ( function ( li ) {
if ( li . getAttribute ( 'data-value' ) === value ) {
if ( li . hasAttribute ( 'dynlistcustom' ) )
li . parentNode . removeChild ( li ) ;
else
li . removeAttribute ( 'unselectable' ) ;
}
} ) ;
item . parentNode . removeChild ( item ) ;
dl . dispatchEvent ( new CustomEvent ( 'cbi-dynlist-change' , {
bubbles : true ,
detail : {
instance : this ,
element : dl ,
value : value ,
remove : true
}
} ) ) ;
} ,
handleClick : function ( ev ) {
var dl = ev . currentTarget ,
item = findParent ( ev . target , '.item' ) ;
if ( item ) {
this . removeItem ( dl , item ) ;
}
else if ( matchesElem ( ev . target , '.cbi-button-add' ) ) {
var input = ev . target . previousElementSibling ;
if ( input . value . length && ! input . classList . contains ( 'cbi-input-invalid' ) ) {
this . addItem ( dl , input . value , null , true ) ;
input . value = '' ;
}
}
} ,
handleDropdownChange : function ( ev ) {
var dl = ev . currentTarget ,
sbIn = ev . detail . instance ,
sbEl = ev . detail . element ,
sbVal = ev . detail . value ;
if ( sbVal === null )
return ;
sbIn . setValues ( sbEl , null ) ;
sbVal . element . setAttribute ( 'unselectable' , '' ) ;
if ( sbVal . element . hasAttribute ( 'created' ) ) {
sbVal . element . removeAttribute ( 'created' ) ;
sbVal . element . setAttribute ( 'dynlistcustom' , '' ) ;
}
this . addItem ( dl , sbVal . value , sbVal . text , true ) ;
} ,
handleKeydown : function ( ev ) {
var dl = ev . currentTarget ,
item = findParent ( ev . target , '.item' ) ;
if ( item ) {
switch ( ev . keyCode ) {
case 8 : /* backspace */
if ( item . previousElementSibling )
item . previousElementSibling . focus ( ) ;
this . removeItem ( dl , item ) ;
break ;
case 46 : /* delete */
if ( item . nextElementSibling ) {
if ( item . nextElementSibling . classList . contains ( 'item' ) )
item . nextElementSibling . focus ( ) ;
else
item . nextElementSibling . firstElementChild . focus ( ) ;
}
this . removeItem ( dl , item ) ;
break ;
}
}
else if ( matchesElem ( ev . target , '.cbi-input-text' ) ) {
switch ( ev . keyCode ) {
case 13 : /* enter */
if ( ev . target . value . length && ! ev . target . classList . contains ( 'cbi-input-invalid' ) ) {
this . addItem ( dl , ev . target . value , null , true ) ;
ev . target . value = '' ;
ev . target . blur ( ) ;
ev . target . focus ( ) ;
}
ev . preventDefault ( ) ;
break ;
}
}
} ,
getValue : function ( ) {
var items = this . node . querySelectorAll ( '.item > input[type="hidden"]' ) ,
2019-07-30 11:29:11 +00:00
input = this . node . querySelector ( '.add-item > input[type="text"]' ) ,
2019-02-07 17:29:29 +00:00
v = [ ] ;
for ( var i = 0 ; i < items . length ; i ++ )
v . push ( items [ i ] . value ) ;
2019-07-30 11:29:11 +00:00
if ( input && input . value != null && input . value . match ( /\S/ ) &&
2019-07-30 11:33:13 +00:00
input . classList . contains ( 'cbi-input-invalid' ) == false &&
2019-07-30 11:29:11 +00:00
v . filter ( function ( s ) { return s == input . value } ) . length == 0 )
v . push ( input . value ) ;
2019-02-07 17:29:29 +00:00
return v ;
} ,
setValue : function ( values ) {
if ( ! Array . isArray ( values ) )
2019-04-01 14:00:10 +00:00
values = ( values != null && values != '' ) ? [ values ] : [ ] ;
2019-02-07 17:29:29 +00:00
var items = this . node . querySelectorAll ( '.item' ) ;
for ( var i = 0 ; i < items . length ; i ++ )
if ( items [ i ] . parentNode === this . node )
this . removeItem ( this . node , items [ i ] ) ;
for ( var i = 0 ; i < values . length ; i ++ )
this . addItem ( this . node , values [ i ] ,
this . choices ? this . choices [ values [ i ] ] : null ) ;
}
} ) ;
2019-04-01 14:00:10 +00:00
var UIHiddenfield = UIElement . extend ( {
_ _init _ _ : function ( value , options ) {
this . value = value ;
this . options = Object . assign ( {
} , options ) ;
} ,
render : function ( ) {
var hiddenEl = E ( 'input' , {
'id' : this . options . id ,
'type' : 'hidden' ,
'value' : this . value
} ) ;
return this . bind ( hiddenEl ) ;
} ,
bind : function ( hiddenEl ) {
this . node = hiddenEl ;
L . dom . bindClassInstance ( hiddenEl , this ) ;
return hiddenEl ;
} ,
getValue : function ( ) {
return this . node . value ;
} ,
setValue : function ( value ) {
this . node . value = value ;
}
} ) ;
2019-02-07 17:29:29 +00:00
2019-01-07 14:26:08 +00:00
return L . Class . extend ( {
_ _init _ _ : function ( ) {
modalDiv = document . body . appendChild (
L . dom . create ( 'div' , { id : 'modal_overlay' } ,
L . dom . create ( 'div' , { class : 'modal' , role : 'dialog' , 'aria-modal' : true } ) ) ) ;
tooltipDiv = document . body . appendChild (
L . dom . create ( 'div' , { class : 'cbi-tooltip' } ) ) ;
/* setup old aliases */
L . showModal = this . showModal ;
L . hideModal = this . hideModal ;
L . showTooltip = this . showTooltip ;
L . hideTooltip = this . hideTooltip ;
L . itemlist = this . itemlist ;
document . addEventListener ( 'mouseover' , this . showTooltip . bind ( this ) , true ) ;
document . addEventListener ( 'mouseout' , this . hideTooltip . bind ( this ) , true ) ;
document . addEventListener ( 'focus' , this . showTooltip . bind ( this ) , true ) ;
document . addEventListener ( 'blur' , this . hideTooltip . bind ( this ) , true ) ;
document . addEventListener ( 'luci-loaded' , this . tabs . init . bind ( this . tabs ) ) ;
2019-02-07 18:10:34 +00:00
document . addEventListener ( 'luci-loaded' , this . changes . init . bind ( this . changes ) ) ;
document . addEventListener ( 'uci-loaded' , this . changes . init . bind ( this . changes ) ) ;
2019-01-07 14:26:08 +00:00
} ,
/* Modal dialog */
2019-06-13 13:01:00 +00:00
showModal : function ( title , children /* , ... */ ) {
2019-01-07 14:26:08 +00:00
var dlg = modalDiv . firstElementChild ;
dlg . setAttribute ( 'class' , 'modal' ) ;
2019-06-13 13:01:00 +00:00
for ( var i = 2 ; i < arguments . length ; i ++ )
dlg . classList . add ( arguments [ i ] ) ;
2019-01-07 14:26:08 +00:00
L . dom . content ( dlg , L . dom . create ( 'h4' , { } , title ) ) ;
L . dom . append ( dlg , children ) ;
document . body . classList . add ( 'modal-overlay-active' ) ;
return dlg ;
} ,
hideModal : function ( ) {
document . body . classList . remove ( 'modal-overlay-active' ) ;
} ,
/* Tooltip */
showTooltip : function ( ev ) {
var target = findParent ( ev . target , '[data-tooltip]' ) ;
if ( ! target )
return ;
if ( tooltipTimeout !== null ) {
window . clearTimeout ( tooltipTimeout ) ;
tooltipTimeout = null ;
}
var rect = target . getBoundingClientRect ( ) ,
x = rect . left + window . pageXOffset ,
y = rect . top + rect . height + window . pageYOffset ;
tooltipDiv . className = 'cbi-tooltip' ;
tooltipDiv . innerHTML = '▲ ' ;
tooltipDiv . firstChild . data += target . getAttribute ( 'data-tooltip' ) ;
if ( target . hasAttribute ( 'data-tooltip-style' ) )
tooltipDiv . classList . add ( target . getAttribute ( 'data-tooltip-style' ) ) ;
if ( ( y + tooltipDiv . offsetHeight ) > ( window . innerHeight + window . pageYOffset ) ) {
y -= ( tooltipDiv . offsetHeight + target . offsetHeight ) ;
tooltipDiv . firstChild . data = '▼ ' + tooltipDiv . firstChild . data . substr ( 2 ) ;
}
tooltipDiv . style . top = y + 'px' ;
tooltipDiv . style . left = x + 'px' ;
tooltipDiv . style . opacity = 1 ;
tooltipDiv . dispatchEvent ( new CustomEvent ( 'tooltip-open' , {
bubbles : true ,
detail : { target : target }
} ) ) ;
} ,
hideTooltip : function ( ev ) {
if ( ev . target === tooltipDiv || ev . relatedTarget === tooltipDiv ||
tooltipDiv . contains ( ev . target ) || tooltipDiv . contains ( ev . relatedTarget ) )
return ;
if ( tooltipTimeout !== null ) {
window . clearTimeout ( tooltipTimeout ) ;
tooltipTimeout = null ;
}
tooltipDiv . style . opacity = 0 ;
tooltipTimeout = window . setTimeout ( function ( ) { tooltipDiv . removeAttribute ( 'style' ) ; } , 250 ) ;
tooltipDiv . dispatchEvent ( new CustomEvent ( 'tooltip-close' , { bubbles : true } ) ) ;
} ,
/* Widget helper */
itemlist : function ( node , items , separators ) {
var children = [ ] ;
if ( ! Array . isArray ( separators ) )
separators = [ separators || E ( 'br' ) ] ;
for ( var i = 0 ; i < items . length ; i += 2 ) {
if ( items [ i + 1 ] !== null && items [ i + 1 ] !== undefined ) {
var sep = separators [ ( i / 2 ) % separators . length ] ,
cld = [ ] ;
children . push ( E ( 'span' , { class : 'nowrap' } , [
items [ i ] ? E ( 'strong' , items [ i ] + ': ' ) : '' ,
items [ i + 1 ]
] ) ) ;
if ( ( i + 2 ) < items . length )
children . push ( L . dom . elem ( sep ) ? sep . cloneNode ( true ) : sep ) ;
}
}
L . dom . content ( node , children ) ;
return node ;
} ,
/* Tabs */
tabs : L . Class . singleton ( {
init : function ( ) {
var groups = [ ] , prevGroup = null , currGroup = null ;
document . querySelectorAll ( '[data-tab]' ) . forEach ( function ( tab ) {
var parent = tab . parentNode ;
if ( ! parent . hasAttribute ( 'data-tab-group' ) )
parent . setAttribute ( 'data-tab-group' , groups . length ) ;
currGroup = + parent . getAttribute ( 'data-tab-group' ) ;
if ( currGroup !== prevGroup ) {
prevGroup = currGroup ;
if ( ! groups [ currGroup ] )
groups [ currGroup ] = [ ] ;
}
groups [ currGroup ] . push ( tab ) ;
} ) ;
for ( var i = 0 ; i < groups . length ; i ++ )
this . initTabGroup ( groups [ i ] ) ;
document . addEventListener ( 'dependency-update' , this . updateTabs . bind ( this ) ) ;
this . updateTabs ( ) ;
if ( ! groups . length )
this . setActiveTabId ( - 1 , - 1 ) ;
} ,
initTabGroup : function ( panes ) {
if ( typeof ( panes ) != 'object' || ! ( 'length' in panes ) || panes . length === 0 )
return ;
var menu = E ( 'ul' , { 'class' : 'cbi-tabmenu' } ) ,
group = panes [ 0 ] . parentNode ,
groupId = + group . getAttribute ( 'data-tab-group' ) ,
selected = null ;
for ( var i = 0 , pane ; pane = panes [ i ] ; i ++ ) {
var name = pane . getAttribute ( 'data-tab' ) ,
title = pane . getAttribute ( 'data-tab-title' ) ,
active = pane . getAttribute ( 'data-tab-active' ) === 'true' ;
menu . appendChild ( E ( 'li' , {
'class' : active ? 'cbi-tab' : 'cbi-tab-disabled' ,
'data-tab' : name
} , E ( 'a' , {
'href' : '#' ,
'click' : this . switchTab . bind ( this )
} , title ) ) ) ;
if ( active )
selected = i ;
}
group . parentNode . insertBefore ( menu , group ) ;
if ( selected === null ) {
selected = this . getActiveTabId ( groupId ) ;
if ( selected < 0 || selected >= panes . length )
selected = 0 ;
menu . childNodes [ selected ] . classList . add ( 'cbi-tab' ) ;
menu . childNodes [ selected ] . classList . remove ( 'cbi-tab-disabled' ) ;
panes [ selected ] . setAttribute ( 'data-tab-active' , 'true' ) ;
this . setActiveTabId ( groupId , selected ) ;
}
} ,
getActiveTabState : function ( ) {
var page = document . body . getAttribute ( 'data-page' ) ;
try {
var val = JSON . parse ( window . sessionStorage . getItem ( 'tab' ) ) ;
if ( val . page === page && Array . isArray ( val . groups ) )
return val ;
}
catch ( e ) { }
window . sessionStorage . removeItem ( 'tab' ) ;
return { page : page , groups : [ ] } ;
} ,
getActiveTabId : function ( groupId ) {
return + this . getActiveTabState ( ) . groups [ groupId ] || 0 ;
} ,
setActiveTabId : function ( groupId , tabIndex ) {
try {
var state = this . getActiveTabState ( ) ;
state . groups [ groupId ] = tabIndex ;
window . sessionStorage . setItem ( 'tab' , JSON . stringify ( state ) ) ;
}
catch ( e ) { return false ; }
return true ;
} ,
updateTabs : function ( ev ) {
document . querySelectorAll ( '[data-tab-title]' ) . forEach ( function ( pane ) {
var menu = pane . parentNode . previousElementSibling ,
tab = menu . querySelector ( '[data-tab="%s"]' . format ( pane . getAttribute ( 'data-tab' ) ) ) ,
n _errors = pane . querySelectorAll ( '.cbi-input-invalid' ) . length ;
if ( ! pane . firstElementChild ) {
tab . style . display = 'none' ;
tab . classList . remove ( 'flash' ) ;
}
else if ( tab . style . display === 'none' ) {
tab . style . display = '' ;
requestAnimationFrame ( function ( ) { tab . classList . add ( 'flash' ) } ) ;
}
if ( n _errors ) {
tab . setAttribute ( 'data-errors' , n _errors ) ;
tab . setAttribute ( 'data-tooltip' , _ ( '%d invalid field(s)' ) . format ( n _errors ) ) ;
tab . setAttribute ( 'data-tooltip-style' , 'error' ) ;
}
else {
tab . removeAttribute ( 'data-errors' ) ;
tab . removeAttribute ( 'data-tooltip' ) ;
}
} ) ;
} ,
switchTab : function ( ev ) {
var tab = ev . target . parentNode ,
name = tab . getAttribute ( 'data-tab' ) ,
menu = tab . parentNode ,
group = menu . nextElementSibling ,
groupId = + group . getAttribute ( 'data-tab-group' ) ,
index = 0 ;
ev . preventDefault ( ) ;
if ( ! tab . classList . contains ( 'cbi-tab-disabled' ) )
return ;
menu . querySelectorAll ( '[data-tab]' ) . forEach ( function ( tab ) {
tab . classList . remove ( 'cbi-tab' ) ;
tab . classList . remove ( 'cbi-tab-disabled' ) ;
tab . classList . add (
tab . getAttribute ( 'data-tab' ) === name ? 'cbi-tab' : 'cbi-tab-disabled' ) ;
} ) ;
group . childNodes . forEach ( function ( pane ) {
if ( L . dom . matches ( pane , '[data-tab]' ) ) {
if ( pane . getAttribute ( 'data-tab' ) === name ) {
pane . setAttribute ( 'data-tab-active' , 'true' ) ;
L . ui . tabs . setActiveTabId ( groupId , index ) ;
}
else {
pane . setAttribute ( 'data-tab-active' , 'false' ) ;
}
index ++ ;
}
} ) ;
}
2019-02-07 17:29:29 +00:00
} ) ,
2019-02-07 18:10:34 +00:00
/* UCI Changes */
changes : L . Class . singleton ( {
init : function ( ) {
if ( ! L . env . sessionid )
return ;
return uci . changes ( ) . then ( L . bind ( this . renderChangeIndicator , this ) ) ;
} ,
setIndicator : function ( n ) {
var i = document . querySelector ( '.uci_change_indicator' ) ;
if ( i == null ) {
var poll = document . getElementById ( 'xhr_poll_status' ) ;
i = poll . parentNode . insertBefore ( E ( 'a' , {
'href' : '#' ,
'class' : 'uci_change_indicator label notice' ,
'click' : L . bind ( this . displayChanges , this )
} ) , poll ) ;
}
if ( n > 0 ) {
L . dom . content ( i , [ _ ( 'Unsaved Changes' ) , ': ' , n ] ) ;
i . classList . add ( 'flash' ) ;
i . style . display = '' ;
}
else {
i . classList . remove ( 'flash' ) ;
i . style . display = 'none' ;
}
} ,
renderChangeIndicator : function ( changes ) {
var n _changes = 0 ;
for ( var config in changes )
if ( changes . hasOwnProperty ( config ) )
n _changes += changes [ config ] . length ;
this . changes = changes ;
this . setIndicator ( n _changes ) ;
} ,
changeTemplates : {
'add-3' : '<ins>uci add %0 <strong>%3</strong> # =%2</ins>' ,
'set-3' : '<ins>uci set %0.<strong>%2</strong>=%3</ins>' ,
'set-4' : '<var><ins>uci set %0.%2.%3=<strong>%4</strong></ins></var>' ,
'remove-2' : '<del>uci del %0.<strong>%2</strong></del>' ,
'remove-3' : '<var><del>uci del %0.%2.<strong>%3</strong></del></var>' ,
'order-3' : '<var>uci reorder %0.%2=<strong>%3</strong></var>' ,
'list-add-4' : '<var><ins>uci add_list %0.%2.%3=<strong>%4</strong></ins></var>' ,
'list-del-4' : '<var><del>uci del_list %0.%2.%3=<strong>%4</strong></del></var>' ,
'rename-3' : '<var>uci rename %0.%2=<strong>%3</strong></var>' ,
'rename-4' : '<var>uci rename %0.%2.%3=<strong>%4</strong></var>'
} ,
displayChanges : function ( ) {
var list = E ( 'div' , { 'class' : 'uci-change-list' } ) ,
dlg = L . ui . showModal ( _ ( 'Configuration' ) + ' / ' + _ ( 'Changes' ) , [
E ( 'div' , { 'class' : 'cbi-section' } , [
E ( 'strong' , _ ( 'Legend:' ) ) ,
E ( 'div' , { 'class' : 'uci-change-legend' } , [
E ( 'div' , { 'class' : 'uci-change-legend-label' } , [
E ( 'ins' , ' ' ) , ' ' , _ ( 'Section added' ) ] ) ,
E ( 'div' , { 'class' : 'uci-change-legend-label' } , [
E ( 'del' , ' ' ) , ' ' , _ ( 'Section removed' ) ] ) ,
E ( 'div' , { 'class' : 'uci-change-legend-label' } , [
E ( 'var' , { } , E ( 'ins' , ' ' ) ) , ' ' , _ ( 'Option changed' ) ] ) ,
E ( 'div' , { 'class' : 'uci-change-legend-label' } , [
E ( 'var' , { } , E ( 'del' , ' ' ) ) , ' ' , _ ( 'Option removed' ) ] ) ] ) ,
E ( 'br' ) , list ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'input' , {
'type' : 'button' ,
'class' : 'btn' ,
'click' : L . ui . hideModal ,
'value' : _ ( 'Dismiss' )
} ) , ' ' ,
E ( 'input' , {
'type' : 'button' ,
'class' : 'cbi-button cbi-button-positive important' ,
'click' : L . bind ( this . apply , this , true ) ,
'value' : _ ( 'Save & Apply' )
} ) , ' ' ,
E ( 'input' , {
'type' : 'button' ,
'class' : 'cbi-button cbi-button-reset' ,
'click' : L . bind ( this . revert , this ) ,
'value' : _ ( 'Revert' )
} ) ] ) ] )
] ) ;
for ( var config in this . changes ) {
if ( ! this . changes . hasOwnProperty ( config ) )
continue ;
list . appendChild ( E ( 'h5' , '# /etc/config/%s' . format ( config ) ) ) ;
for ( var i = 0 , added = null ; i < this . changes [ config ] . length ; i ++ ) {
var chg = this . changes [ config ] [ i ] ,
tpl = this . changeTemplates [ '%s-%d' . format ( chg [ 0 ] , chg . length ) ] ;
list . appendChild ( E ( tpl . replace ( /%([01234])/g , function ( m0 , m1 ) {
switch ( + m1 ) {
case 0 :
return config ;
case 2 :
if ( added != null && chg [ 1 ] == added [ 0 ] )
return '@' + added [ 1 ] + '[-1]' ;
else
return chg [ 1 ] ;
case 4 :
2019-07-22 15:18:32 +00:00
return "'%h'" . format ( chg [ 3 ] . replace ( /'/g , "'\"'\"'" ) ) ;
2019-02-07 18:10:34 +00:00
default :
return chg [ m1 - 1 ] ;
}
} ) ) ) ;
if ( chg [ 0 ] == 'add' )
added = [ chg [ 1 ] , chg [ 2 ] ] ;
}
}
list . appendChild ( E ( 'br' ) ) ;
dlg . classList . add ( 'uci-dialog' ) ;
} ,
displayStatus : function ( type , content ) {
if ( type ) {
var message = L . ui . showModal ( '' , '' ) ;
message . classList . add ( 'alert-message' ) ;
DOMTokenList . prototype . add . apply ( message . classList , type . split ( /\s+/ ) ) ;
if ( content )
L . dom . content ( message , content ) ;
if ( ! this . was _polling ) {
this . was _polling = L . Request . poll . active ( ) ;
L . Request . poll . stop ( ) ;
}
}
else {
L . ui . hideModal ( ) ;
if ( this . was _polling )
L . Request . poll . start ( ) ;
}
} ,
rollback : function ( checked ) {
if ( checked ) {
this . displayStatus ( 'warning spinning' ,
E ( 'p' , _ ( 'Failed to confirm apply within %ds, waiting for rollback…' )
. format ( L . env . apply _rollback ) ) ) ;
var call = function ( r , data , duration ) {
if ( r . status === 204 ) {
L . ui . changes . displayStatus ( 'warning' , [
E ( 'h4' , _ ( 'Configuration has been rolled back!' ) ) ,
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 ) ) ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'input' , {
'type' : 'button' ,
'class' : 'btn' ,
'click' : L . bind ( L . ui . changes . displayStatus , L . ui . changes , false ) ,
'value' : _ ( 'Dismiss' )
} ) , ' ' ,
E ( 'input' , {
'type' : 'button' ,
'class' : 'btn cbi-button-action important' ,
'click' : L . bind ( L . ui . changes . revert , L . ui . changes ) ,
'value' : _ ( 'Revert changes' )
} ) , ' ' ,
E ( 'input' , {
'type' : 'button' ,
'class' : 'btn cbi-button-negative important' ,
'click' : L . bind ( L . ui . changes . apply , L . ui . changes , false ) ,
'value' : _ ( 'Apply unchecked' )
} )
] )
] ) ;
return ;
}
var delay = isNaN ( duration ) ? 0 : Math . max ( 1000 - duration , 0 ) ;
window . setTimeout ( function ( ) {
L . Request . request ( L . url ( 'admin/uci/confirm' ) , {
method : 'post' ,
timeout : L . env . apply _timeout * 1000 ,
query : { sid : L . env . sessionid , token : L . env . token }
} ) . then ( call ) ;
} , delay ) ;
} ;
call ( { status : 0 } ) ;
}
else {
this . displayStatus ( 'warning' , [
E ( 'h4' , _ ( 'Device unreachable!' ) ) ,
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.' ) )
] ) ;
}
} ,
confirm : function ( checked , deadline , override _token ) {
var tt ;
var ts = Date . now ( ) ;
this . displayStatus ( 'notice' ) ;
if ( override _token )
this . confirm _auth = { token : override _token } ;
var call = function ( r , data , duration ) {
if ( Date . now ( ) >= deadline ) {
window . clearTimeout ( tt ) ;
L . ui . changes . rollback ( checked ) ;
return ;
}
else if ( r && ( r . status === 200 || r . status === 204 ) ) {
document . dispatchEvent ( new CustomEvent ( 'uci-applied' ) ) ;
L . ui . changes . setIndicator ( 0 ) ;
L . ui . changes . displayStatus ( 'notice' ,
E ( 'p' , _ ( 'Configuration has been applied.' ) ) ) ;
window . clearTimeout ( tt ) ;
window . setTimeout ( function ( ) {
//L.ui.changes.displayStatus(false);
window . location = window . location . href . split ( '#' ) [ 0 ] ;
} , L . env . apply _display * 1000 ) ;
return ;
}
var delay = isNaN ( duration ) ? 0 : Math . max ( 1000 - duration , 0 ) ;
window . setTimeout ( function ( ) {
L . Request . request ( L . url ( 'admin/uci/confirm' ) , {
method : 'post' ,
timeout : L . env . apply _timeout * 1000 ,
query : L . ui . changes . confirm _auth
2019-07-26 10:25:46 +00:00
} ) . then ( call , call ) ;
2019-02-07 18:10:34 +00:00
} , delay ) ;
} ;
var tick = function ( ) {
var now = Date . now ( ) ;
L . ui . changes . displayStatus ( 'notice spinning' ,
E ( 'p' , _ ( 'Waiting for configuration to get applied… %ds' )
. format ( Math . max ( Math . floor ( ( deadline - Date . now ( ) ) / 1000 ) , 0 ) ) ) ) ;
if ( now >= deadline )
return ;
tt = window . setTimeout ( tick , 1000 - ( now - ts ) ) ;
ts = now ;
} ;
tick ( ) ;
/* wait a few seconds for the settings to become effective */
window . setTimeout ( call , Math . max ( L . env . apply _holdoff * 1000 - ( ( ts + L . env . apply _rollback * 1000 ) - deadline ) , 1 ) ) ;
} ,
apply : function ( checked ) {
this . displayStatus ( 'notice spinning' ,
E ( 'p' , _ ( 'Starting configuration apply…' ) ) ) ;
L . Request . request ( L . url ( 'admin/uci' , checked ? 'apply_rollback' : 'apply_unchecked' ) , {
method : 'post' ,
query : { sid : L . env . sessionid , token : L . env . token }
} ) . then ( function ( r ) {
if ( r . status === ( checked ? 200 : 204 ) ) {
var tok = null ; try { tok = r . json ( ) ; } catch ( e ) { }
if ( checked && tok !== null && typeof ( tok ) === 'object' && typeof ( tok . token ) === 'string' )
L . ui . changes . confirm _auth = tok ;
L . ui . changes . confirm ( checked , Date . now ( ) + L . env . apply _rollback * 1000 ) ;
}
else if ( checked && r . status === 204 ) {
L . ui . changes . displayStatus ( 'notice' ,
E ( 'p' , _ ( 'There are no changes to apply' ) ) ) ;
window . setTimeout ( function ( ) {
L . ui . changes . displayStatus ( false ) ;
} , L . env . apply _display * 1000 ) ;
}
else {
L . ui . changes . displayStatus ( 'warning' ,
2019-07-13 06:41:23 +00:00
E ( 'p' , _ ( 'Apply request failed with status <code>%h</code>' )
2019-02-07 18:10:34 +00:00
. format ( r . responseText || r . statusText || r . status ) ) ) ;
window . setTimeout ( function ( ) {
L . ui . changes . displayStatus ( false ) ;
} , L . env . apply _display * 1000 ) ;
}
} ) ;
} ,
revert : function ( ) {
this . displayStatus ( 'notice spinning' ,
E ( 'p' , _ ( 'Reverting configuration…' ) ) ) ;
L . Request . request ( L . url ( 'admin/uci/revert' ) , {
method : 'post' ,
query : { sid : L . env . sessionid , token : L . env . token }
} ) . then ( function ( r ) {
if ( r . status === 200 ) {
document . dispatchEvent ( new CustomEvent ( 'uci-reverted' ) ) ;
L . ui . changes . setIndicator ( 0 ) ;
L . ui . changes . displayStatus ( 'notice' ,
E ( 'p' , _ ( 'Changes have been reverted.' ) ) ) ;
window . setTimeout ( function ( ) {
//L.ui.changes.displayStatus(false);
window . location = window . location . href . split ( '#' ) [ 0 ] ;
} , L . env . apply _display * 1000 ) ;
}
else {
L . ui . changes . displayStatus ( 'warning' ,
E ( 'p' , _ ( 'Revert request failed with status <code>%h</code>' )
. format ( r . statusText || r . status ) ) ) ;
window . setTimeout ( function ( ) {
L . ui . changes . displayStatus ( false ) ;
} , L . env . apply _display * 1000 ) ;
}
} ) ;
}
} ) ,
2019-04-01 14:00:10 +00:00
addValidator : function ( field , type , optional , vfunc /*, ... */ ) {
2019-02-07 17:29:29 +00:00
if ( type == null )
return ;
var events = this . varargs ( arguments , 3 ) ;
if ( events . length == 0 )
events . push ( 'blur' , 'keyup' ) ;
try {
2019-05-28 16:49:11 +00:00
var cbiValidator = L . validation . create ( field , type , optional , vfunc ) ,
2019-02-07 17:29:29 +00:00
validatorFn = cbiValidator . validate . bind ( cbiValidator ) ;
for ( var i = 0 ; i < events . length ; i ++ )
field . addEventListener ( events [ i ] , validatorFn ) ;
validatorFn ( ) ;
2019-04-01 14:00:10 +00:00
return validatorFn ;
2019-02-07 17:29:29 +00:00
}
catch ( e ) { }
} ,
/* Widgets */
2019-04-01 14:00:10 +00:00
Textfield : UITextfield ,
Checkbox : UICheckbox ,
Select : UISelect ,
2019-02-07 17:29:29 +00:00
Dropdown : UIDropdown ,
DynamicList : UIDynamicList ,
2019-04-01 14:00:10 +00:00
Combobox : UICombobox ,
Hiddenfield : UIHiddenfield
2019-01-07 14:26:08 +00:00
} ) ;