2017-01-11 03:38:55 -08:00
2016-03-25 01:18:05 -07:00
/ * *
* Module containing core application logic .
* @ param { jQuery } $ Insulated jQuery object
* @ param { JSON } settings Insulated ` window.snipeit.settings ` object .
* @ return { IIFE } Immediately invoked . Returns self .
* /
2016-05-24 16:10:04 -07:00
2018-09-28 13:03:52 -07:00
lineOptions = {
2017-05-31 05:23:26 -07:00
legend : {
position : "bottom"
} ,
scales : {
yAxes : [ {
ticks : {
fontColor : "rgba(0,0,0,0.5)" ,
fontStyle : "bold" ,
beginAtZero : true ,
maxTicksLimit : 5 ,
padding : 20
} ,
gridLines : {
drawTicks : false ,
display : false
}
} ] ,
xAxes : [ {
gridLines : {
zeroLineColor : "transparent"
} ,
ticks : {
padding : 20 ,
fontColor : "rgba(0,0,0,0.5)" ,
fontStyle : "bold"
}
} ]
}
} ;
2016-05-24 16:10:04 -07:00
2018-09-28 13:03:52 -07:00
pieOptions = {
2016-05-24 16:10:04 -07:00
//Boolean - Whether we should show a stroke on each segment
segmentShowStroke : true ,
//String - The colour of each segment stroke
segmentStrokeColor : "#fff" ,
//Number - The width of each segment stroke
segmentStrokeWidth : 1 ,
//Number - The percentage of the chart that we cut out of the middle
percentageInnerCutout : 50 , // This is 0 for Pie charts
//Number - Amount of animation steps
animationSteps : 100 ,
//String - Animation easing effect
animationEasing : "easeOutBounce" ,
//Boolean - Whether we animate the rotation of the Doughnut
animateRotate : true ,
//Boolean - Whether we animate scaling the Doughnut from the centre
animateScale : false ,
//Boolean - whether to make the chart responsive to window resizing
responsive : true ,
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
maintainAspectRatio : false ,
//String - A legend template
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li>" +
"<i class='fa fa-circle-o' style='color: <%=segments[i].fillColor%>'></i>" +
"<%if(segments[i].label){%><%=segments[i].label%><%}%> foo</li><%}%></ul>" ,
//String - A tooltip template
tooltipTemplate : "<%=value %> <%=label%> "
} ;
2017-01-11 03:38:55 -08:00
2016-05-24 16:10:04 -07:00
//-----------------
//- END PIE CHART -
//-----------------
2019-05-23 16:56:22 -07:00
var baseUrl = $ ( 'meta[name="baseUrl"]' ) . attr ( 'content' ) ;
2016-05-24 16:10:04 -07:00
2016-03-25 01:18:05 -07:00
( function ( $ , settings ) {
var Components = { } ;
Components . modals = { } ;
// confirm delete modal
Components . modals . confirmDelete = function ( ) {
var $el = $ ( 'table' ) ;
var events = {
'click' : function ( evnt ) {
var $context = $ ( this ) ;
var $dataConfirmModal = $ ( '#dataConfirmModal' ) ;
var href = $context . attr ( 'href' ) ;
var message = $context . attr ( 'data-content' ) ;
var title = $context . attr ( 'data-title' ) ;
$ ( '#myModalLabel' ) . text ( title ) ;
$dataConfirmModal . find ( '.modal-body' ) . text ( message ) ;
2016-12-19 10:42:33 -08:00
$ ( '#deleteForm' ) . attr ( 'action' , href ) ;
2016-03-25 01:18:05 -07:00
$dataConfirmModal . modal ( {
show : true
} ) ;
return false ;
}
} ;
var render = function ( ) {
$el . on ( 'click' , '.delete-asset' , events [ 'click' ] ) ;
} ;
return {
render : render
} ;
} ;
/ * *
* Application start point
* Component definition stays out of load event , execution only happens .
* /
$ ( function ( ) {
new Components . modals . confirmDelete ( ) . render ( ) ;
} ) ;
} ( jQuery , window . snipeit . settings ) ) ;
2016-05-24 16:10:04 -07:00
2017-01-11 03:38:55 -08:00
$ ( document ) . ready ( function ( ) {
2017-01-11 05:51:13 -08:00
/ *
* Slideout help menu
* /
$ ( '.slideout-menu-toggle' ) . on ( 'click' , function ( event ) {
event . preventDefault ( ) ;
// create menu variables
var slideoutMenu = $ ( '.slideout-menu' ) ;
var slideoutMenuWidth = $ ( '.slideout-menu' ) . width ( ) ;
// toggle open class
slideoutMenu . toggleClass ( "open" ) ;
// slide menu
if ( slideoutMenu . hasClass ( "open" ) ) {
slideoutMenu . show ( ) ;
slideoutMenu . animate ( {
right : "0px"
} ) ;
} else {
slideoutMenu . animate ( {
right : - slideoutMenuWidth
} , "-350px" ) ;
slideoutMenu . fadeOut ( ) ;
}
2017-01-11 03:38:55 -08:00
} ) ;
2017-01-11 05:51:13 -08:00
/ *
* iCheck checkbox plugin
* /
2017-01-11 17:07:27 -08:00
$ ( 'input[type="checkbox"].minimal, input[type="radio"].minimal' ) . iCheck ( {
2017-01-11 05:51:13 -08:00
checkboxClass : 'icheckbox_minimal-blue' ,
radioClass : 'iradio_minimal-blue'
} ) ;
2017-01-11 17:07:27 -08:00
2017-01-11 05:51:13 -08:00
/ *
* Select2
* /
2017-01-11 17:07:27 -08:00
2017-01-11 05:51:13 -08:00
var iOS = /iPhone|iPad|iPod/ . test ( navigator . userAgent ) && ! window . MSStream ;
if ( ! iOS )
{
2017-01-25 21:29:23 -08:00
// Vue collision: Avoid overriding a vue select2 instance
// by checking to see if the item has already been select2'd.
2017-05-31 13:26:48 -07:00
$ ( 'select.select2:not(".select2-hidden-accessible")' ) . each ( function ( i , obj ) {
2017-01-25 21:29:23 -08:00
{
$ ( obj ) . select2 ( ) ;
}
} ) ;
2017-01-11 05:51:13 -08:00
}
2017-10-26 02:28:17 -07:00
2018-10-09 15:20:27 -07:00
// $('.datepicker').datepicker();
// var datepicker = $.fn.datepicker.noConflict(); // return $.fn.datepicker to previously assigned value
// $.fn.bootstrapDP = datepicker;
// $('.datepicker').datepicker();
2017-10-26 02:28:17 -07:00
// Crazy select2 rich dropdowns with images!
$ ( '.js-data-ajax' ) . each ( function ( i , item ) {
var link = $ ( item ) ;
var endpoint = link . data ( "endpoint" ) ;
var select = link . data ( "select" ) ;
link . select2 ( {
2018-07-16 14:10:54 -07:00
/ * *
* Adds an empty placeholder , allowing every select2 instance to be cleared .
* This placeholder can be overridden with the "data-placeholder" attribute .
* /
placeholder : '' ,
allowClear : true ,
2017-10-26 02:28:17 -07:00
ajax : {
2017-11-08 00:57:43 -08:00
// the baseUrl includes a trailing slash
2018-07-25 03:00:27 -07:00
url : Ziggy . baseUrl + 'api/v1/' + endpoint + '/selectlist' ,
2017-10-26 02:28:17 -07:00
dataType : 'json' ,
2017-11-04 00:48:54 -07:00
delay : 250 ,
2017-10-26 02:28:17 -07:00
headers : {
"X-Requested-With" : 'XMLHttpRequest' ,
"X-CSRF-TOKEN" : $ ( 'meta[name="csrf-token"]' ) . attr ( 'content' )
} ,
data : function ( params ) {
var data = {
search : params . term ,
2018-04-25 02:39:23 -07:00
page : params . page || 1 ,
assetStatusType : link . data ( "asset-status-type" ) ,
2017-10-26 02:28:17 -07:00
} ;
return data ;
} ,
2021-02-02 15:55:21 -08:00
/ * p r o c e s s R e s u l t s : f u n c t i o n ( d a t a , p a r a m s ) {
2017-10-26 02:28:17 -07:00
params . page = params . page || 1 ;
2017-10-25 20:15:34 -07:00
2017-10-26 02:28:17 -07:00
var answer = {
results : data . items ,
pagination : {
2020-07-16 15:33:38 -07:00
more : data . pagination . more
2017-10-26 02:28:17 -07:00
}
} ;
return answer ;
2021-02-02 15:55:21 -08:00
} , * /
2017-10-26 02:28:17 -07:00
cache : true
} ,
2021-02-02 15:55:21 -08:00
//escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
templateResult : formatDatalistSafe ,
//templateSelection: formatDataSelection
2017-10-25 20:15:34 -07:00
} ) ;
2017-10-26 02:28:17 -07:00
2017-10-25 20:15:34 -07:00
} ) ;
2018-08-01 00:06:28 -07:00
function getSelect2Value ( element ) {
// if the passed object is not a jquery object, assuming 'element' is a selector
if ( ! ( element instanceof jQuery ) ) element = $ ( element ) ;
var select = element . data ( "select2" ) ;
// There's two different locations where the select2-generated input element can be.
searchElement = select . dropdown . $search || select . $container . find ( ".select2-search__field" ) ;
var value = searchElement . val ( ) ;
return value ;
}
$ ( ".select2-hidden-accessible" ) . on ( 'select2:selecting' , function ( e ) {
var data = e . params . args . data ;
var isMouseUp = false ;
var element = $ ( this ) ;
var value = getSelect2Value ( element ) ;
if ( e . params . args . originalEvent ) isMouseUp = e . params . args . originalEvent . type == "mouseup" ;
// if selected item does not match typed text, do not allow it to pass - force close for ajax.
if ( ! isMouseUp ) {
if ( value . toLowerCase ( ) && data . text . toLowerCase ( ) . indexOf ( value ) < 0 ) {
e . preventDefault ( ) ;
element . select2 ( 'close' ) ;
// if it does match, we set a flag in the event (which gets passed to subsequent events), telling it not to worry about the ajax
} else if ( value . toLowerCase ( ) && data . text . toLowerCase ( ) . indexOf ( value ) > - 1 ) {
e . params . args . noForceAjax = true ;
}
}
} ) ;
$ ( ".select2-hidden-accessible" ) . on ( 'select2:closing' , function ( e ) {
var element = $ ( this ) ;
var value = getSelect2Value ( element ) ;
var noForceAjax = false ;
var isMouseUp = false ;
if ( e . params . args . originalSelect2Event ) noForceAjax = e . params . args . originalSelect2Event . noForceAjax ;
if ( e . params . args . originalEvent ) isMouseUp = e . params . args . originalEvent . type == "mouseup" ;
if ( value && ! noForceAjax && ! isMouseUp ) {
var endpoint = element . data ( "endpoint" ) ;
var assetStatusType = element . data ( "asset-status-type" ) ;
$ . ajax ( {
url : Ziggy . baseUrl + 'api/v1/' + endpoint + '/selectlist?search=' + value + '&page=1' + ( assetStatusType ? '&assetStatusType=' + assetStatusType : '' ) ,
dataType : 'json' ,
headers : {
"X-Requested-With" : 'XMLHttpRequest' ,
"X-CSRF-TOKEN" : $ ( 'meta[name="csrf-token"]' ) . attr ( 'content' )
} ,
2018-10-09 15:20:27 -07:00
} ) . done ( function ( response ) {
var currentlySelected = element . select2 ( 'data' ) . map ( function ( x ) {
return + x . id ;
} ) . filter ( function ( x ) {
return x !== 0 ;
} ) ;
2018-08-01 00:06:28 -07:00
// makes sure we're not selecting the same thing twice for multiples
2021-09-23 17:12:45 -07:00
var filteredResponse = response . results . filter ( function ( item ) {
2018-08-01 00:06:28 -07:00
return currentlySelected . indexOf ( + item . id ) < 0 ;
} ) ;
2021-09-23 17:12:45 -07:00
var first = ( currentlySelected . length > 0 ) ? filteredResponse [ 0 ] : response . results [ 0 ] ;
2018-08-01 00:06:28 -07:00
if ( first && first . id ) {
first . selected = true ;
if ( $ ( "option[value='" + first . id + "']" , element ) . length < 1 ) {
var option = new Option ( first . text , first . id , true , true ) ;
element . append ( option ) ;
} else {
var isMultiple = element . attr ( "multiple" ) == "multiple" ;
element . val ( isMultiple ? element . val ( ) . concat ( first . id ) : element . val ( first . id ) ) ;
}
element . trigger ( 'change' ) ;
element . trigger ( {
type : 'select2:select' ,
params : {
data : first
}
} ) ;
}
} ) ;
}
} ) ;
2017-10-26 02:28:17 -07:00
function formatDatalist ( datalist ) {
var loading _markup = '<i class="fa fa-spinner fa-spin" aria-hidden="true"></i> Loading...' ;
if ( datalist . loading ) {
return loading _markup ;
}
2021-02-02 15:55:21 -08:00
var markup = '<div class="clearfix">' ;
markup += '<div class="pull-left" style="padding-right: 10px;">' ;
2017-10-26 02:28:17 -07:00
if ( datalist . image ) {
2020-03-27 22:03:30 -07:00
markup += "<div style='width: 30px;'><img src='" + datalist . image + "' style='max-height: 20px; max-width: 30px;' alt='" + datalist . text + "'></div>" ;
2017-10-26 02:28:17 -07:00
} else {
2021-02-02 15:55:21 -08:00
markup += '<div style="height: 20px; width: 30px;"></div>' ;
2017-10-26 02:28:17 -07:00
}
2017-10-25 20:15:34 -07:00
2017-10-26 02:28:17 -07:00
markup += "</div><div>" + datalist . text + "</div>" ;
markup += "</div>" ;
return markup ;
}
2017-10-25 20:15:34 -07:00
2021-02-02 15:55:21 -08:00
function formatDatalistSafe ( datalist ) {
// console.warn("What in the hell is going on with Select2?!?!!?!?");
// console.warn($.select2);
if ( datalist . loading ) {
return $ ( '<i class="fa fa-spinner fa-spin" aria-hidden="true"></i> Loading...' ) ;
}
var root _div = $ ( "<div class='clearfix'>" ) ;
var left _pull = $ ( "<div class='pull-left' style='padding-right: 10px;'>" ) ;
if ( datalist . image ) {
var inner _div = $ ( "<div style='width: 30px;'>" ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* We are specifically chosing empty alt - text below , because this
* image conveys no additional information , relative to the text
* that will * always * be there in any select2 list that is in use
* in Snipe - IT . If that changes , we would probably want to change
* some signatures of some functions , but right now , we don ' t want
* screen readers to say " HP SuperJet 5000 , ... . picture of HP
* SuperJet 5000. . . " and so on , for every single row in a list of
* assets or models or whatever .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
var img = $ ( "<img src='' style='max-height: 20px; max-width: 30px;' alt=''>" ) ;
// console.warn("Img is: ");
// console.dir(img);
// console.warn("Strigularly, that's: ");
// console.log(img);
img . attr ( "src" , datalist . image ) ;
inner _div . append ( img )
} else {
var inner _div = $ ( "<div style='height: 20px; width: 30px;'></div>" ) ;
}
left _pull . append ( inner _div ) ;
root _div . append ( left _pull ) ;
var name _div = $ ( "<div>" ) ;
name _div . text ( datalist . text ) ;
root _div . append ( name _div )
var safe _html = root _div . get ( 0 ) . outerHTML ;
var old _html = formatDatalist ( datalist ) ;
if ( safe _html != old _html ) {
console . log ( "HTML MISMATCH: " ) ;
console . log ( "FormatDatalistSafe: " ) ;
// console.dir(root_div.get(0));
console . log ( safe _html ) ;
console . log ( "FormatDataList: " ) ;
console . log ( old _html ) ;
}
return root _div ;
}
2017-10-26 02:28:17 -07:00
function formatDataSelection ( datalist ) {
2019-03-18 20:49:32 -07:00
// This a heinous workaround for a known bug in Select2.
// Without this, the rich selectlists are vulnerable to XSS.
// Many thanks to @uberbrady for this fix. It ain't pretty,
// but it resolves the issue until Select2 addresses it on their end.
//
// Bug was reported in 2016 :{
// https://github.com/select2/select2/issues/4587
return datalist . text . replace ( />/g , '>' )
. replace ( /</g , '<' )
. replace ( /"/g , '"' )
. replace ( /'/g , ''' ) ;
2017-10-26 02:28:17 -07:00
}
2017-10-25 20:15:34 -07:00
2017-11-21 20:55:57 -08:00
// This handles the radio button selectors for the checkout-to-foo options
// on asset checkout and also on asset edit
2017-11-21 15:58:31 -08:00
$ ( function ( ) {
$ ( 'input[name=checkout_to_type]' ) . on ( "change" , function ( ) {
var assignto _type = $ ( 'input[name=checkout_to_type]:checked' ) . val ( ) ;
var userid = $ ( '#assigned_user option:selected' ) . val ( ) ;
if ( assignto _type == 'asset' ) {
$ ( '#current_assets_box' ) . fadeOut ( ) ;
$ ( '#assigned_asset' ) . show ( ) ;
$ ( '#assigned_user' ) . hide ( ) ;
$ ( '#assigned_location' ) . hide ( ) ;
$ ( '.notification-callout' ) . fadeOut ( ) ;
2017-11-27 21:20:31 -08:00
2017-11-21 15:58:31 -08:00
} else if ( assignto _type == 'location' ) {
$ ( '#current_assets_box' ) . fadeOut ( ) ;
$ ( '#assigned_asset' ) . hide ( ) ;
$ ( '#assigned_user' ) . hide ( ) ;
$ ( '#assigned_location' ) . show ( ) ;
$ ( '.notification-callout' ) . fadeOut ( ) ;
} else {
2017-11-27 21:20:31 -08:00
2017-11-21 15:58:31 -08:00
$ ( '#assigned_asset' ) . hide ( ) ;
$ ( '#assigned_user' ) . show ( ) ;
$ ( '#assigned_location' ) . hide ( ) ;
if ( userid ) {
$ ( '#current_assets_box' ) . fadeIn ( ) ;
}
$ ( '.notification-callout' ) . fadeIn ( ) ;
2017-11-27 21:20:31 -08:00
2017-11-21 15:58:31 -08:00
}
} ) ;
} ) ;
2018-02-13 17:04:50 -08:00
// ------------------------------------------------
// Deep linking for Bootstrap tabs
// ------------------------------------------------
var taburl = document . location . toString ( ) ;
// Allow full page URL to activate a tab's ID
// ------------------------------------------------
// This allows linking to a tab on page load via the address bar.
// So a URL such as, http://snipe-it.local/hardware/2/#my_tab will
// cause the tab on that page with an ID of “my_tab” to be active.
if ( taburl . match ( '#' ) ) {
$ ( '.nav-tabs a[href="#' + taburl . split ( '#' ) [ 1 ] + '"]' ) . tab ( 'show' ) ;
}
// Allow internal page links to activate a tab's ID.
// ------------------------------------------------
// This allows you to link to a tab from anywhere on the page
// including from within another tab. Also note that internal page
// links either inside or out of the tabs need to include data-toggle="tab"
// Ex: <a href="#my_tab" data-toggle="tab">Click me</a>
$ ( 'a[data-toggle="tab"]' ) . click ( function ( e ) {
var href = $ ( this ) . attr ( "href" ) ;
history . pushState ( null , null , href ) ;
e . preventDefault ( ) ;
$ ( 'a[href="' + $ ( this ) . attr ( 'href' ) + '"]' ) . tab ( 'show' ) ;
} ) ;
// ------------------------------------------------
// End Deep Linking for Bootstrap tabs
// ------------------------------------------------
2018-02-17 00:51:22 -08:00
// Image preview
2019-03-01 15:25:42 -08:00
function readURL ( input , $preview ) {
2018-02-17 00:51:22 -08:00
if ( input . files && input . files [ 0 ] ) {
var reader = new FileReader ( ) ;
reader . onload = function ( e ) {
2019-03-01 15:25:42 -08:00
$preview . attr ( 'src' , e . target . result ) ;
2018-10-09 15:20:27 -07:00
} ;
2018-02-17 00:51:22 -08:00
reader . readAsDataURL ( input . files [ 0 ] ) ;
}
}
function formatBytes ( bytes ) {
if ( bytes < 1024 ) return bytes + " Bytes" ;
2018-07-16 20:09:53 -07:00
else if ( bytes < 1048576 ) return ( bytes / 1024 ) . toFixed ( 2 ) + " KB" ;
else if ( bytes < 1073741824 ) return ( bytes / 1048576 ) . toFixed ( 2 ) + " MB" ;
else return ( bytes / 1073741824 ) . toFixed ( 2 ) + " GB" ;
2018-10-09 15:20:27 -07:00
}
2018-02-17 00:51:22 -08:00
2018-02-16 21:17:41 -08:00
// File size validation
2019-03-01 15:25:42 -08:00
$ ( '.js-uploadFile' ) . bind ( 'change' , function ( ) {
2020-04-22 07:15:24 -07:00
var $this = $ ( this ) ;
var id = '#' + $this . attr ( 'id' ) ;
var status = id + '-status' ;
var $status = $ ( status ) ;
2019-03-01 15:25:42 -08:00
$status . removeClass ( 'text-success' ) . removeClass ( 'text-danger' ) ;
$ ( status + ' .goodfile' ) . remove ( ) ;
$ ( status + ' .badfile' ) . remove ( ) ;
$ ( status + ' .previewSize' ) . hide ( ) ;
$ ( id + '-info' ) . html ( '' ) ;
var max _size = $this . data ( 'maxsize' ) ;
2018-07-16 20:09:53 -07:00
var total _size = 0 ;
2018-02-16 21:17:41 -08:00
2018-07-16 20:09:53 -07:00
for ( var i = 0 ; i < this . files . length ; i ++ ) {
total _size += this . files [ i ] . size ;
2021-10-06 12:26:45 -07:00
$ ( id + '-info' ) . append ( '<span class="label label-default">' + htmlEntities ( this . files [ i ] . name ) + ' (' + formatBytes ( this . files [ i ] . size ) + ')</span> ' ) ;
2018-07-16 20:09:53 -07:00
}
2020-09-15 20:07:46 -07:00
console . log ( 'Max size is: ' + max _size ) ;
console . log ( 'Real size is: ' + total _size ) ;
2018-07-16 20:09:53 -07:00
if ( total _size > max _size ) {
2019-03-01 15:25:42 -08:00
$status . addClass ( 'text-danger' ) . removeClass ( 'help-block' ) . prepend ( '<i class="badfile fa fa-times"></i> ' ) . append ( '<span class="previewSize"> Upload is ' + formatBytes ( total _size ) + '.</span>' ) ;
2018-02-16 21:17:41 -08:00
} else {
2020-09-15 20:07:46 -07:00
2019-03-01 15:25:42 -08:00
$status . addClass ( 'text-success' ) . removeClass ( 'help-block' ) . prepend ( '<i class="goodfile fa fa-check"></i> ' ) ;
2020-04-22 07:15:24 -07:00
var $preview = $ ( id + '-imagePreview' ) ;
2019-03-01 15:25:42 -08:00
readURL ( this , $preview ) ;
$preview . fadeIn ( ) ;
2018-02-16 21:17:41 -08:00
}
2018-07-16 20:09:53 -07:00
2018-02-16 21:17:41 -08:00
} ) ;
2017-01-11 05:51:13 -08:00
} ) ;
2018-02-16 21:17:41 -08:00
2021-10-06 12:26:45 -07:00
function htmlEntities ( str ) {
return String ( str ) . replace ( /&/g , '&' ) . replace ( /</g , '<' ) . replace ( />/g , '>' ) . replace ( /"/g , '"' ) ;
}
2018-02-13 17:04:50 -08:00
2018-12-06 14:05:43 -08:00
/ * *
* Toggle disabled
* /
( function ( $ ) {
$ . fn . toggleDisabled = function ( callback ) {
return this . each ( function ( ) {
var disabled , $this = $ ( this ) ;
if ( $this . attr ( 'disabled' ) ) {
$this . removeAttr ( 'disabled' ) ;
disabled = false ;
} else {
$this . attr ( 'disabled' , 'disabled' ) ;
disabled = true ;
}
2017-01-11 05:51:13 -08:00
2018-12-06 14:05:43 -08:00
if ( callback && typeof callback === 'function' ) {
callback ( this , disabled ) ;
}
} ) ;
} ;
2021-09-23 17:12:45 -07:00
} ) ( jQuery ) ;