/*! jQuery UI - v1.11.4+CommonJS - 2015-08-28 * http://jqueryui.com * Includes: widget.js * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ (function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define([ "jquery" ], factory ); } else if ( typeof exports === "object" ) { // Node/CommonJS factory( require( "jquery" ) ); } else { // Browser globals factory( jQuery ); } }(function( $ ) { /*! * jQuery UI Widget 1.11.4 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/jQuery.widget/ */ var widget_uuid = 0, widget_slice = Array.prototype.slice; $.cleanData = (function( orig ) { return function( elems ) { var events, elem, i; for ( i = 0; (elem = elems[i]) != null; i++ ) { try { // Only trigger remove when necessary to save time events = $._data( elem, "events" ); if ( events && events.remove ) { $( elem ).triggerHandler( "remove" ); } // http://bugs.jquery.com/ticket/8235 } catch ( e ) {} } orig( elems ); }; })( $.cleanData ); $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) proxiedPrototype = {}, namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } // create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] }); basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = (function() { var _super = function() { return base.prototype[ prop ].apply( this, arguments ); }, _superApply = function( args ) { return base.prototype[ prop ].apply( this, args ); }; return function() { var __super = this._super, __superApply = this._superApply, returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; })(); }); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName }); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); }); // remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); return constructor; }; $.widget.extend = function( target ) { var input = widget_slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = widget_slice.call( arguments, 1 ), returnValue = this; if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); if ( options === "instance" ) { returnValue = instance; return false; } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } }); } else { // Allow multiple hashes to be passed on init if ( args.length ) { options = $.widget.extend.apply( null, [ options ].concat(args) ); } this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} ); if ( instance._init ) { instance._init(); } } else { $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
", options: { disabled: false, // callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = widget_uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key, parts, curOption, i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled", !!value ); // If the widget is becoming disabled, then nothing is interactive if ( value ) { this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } } return this; }, enable: function() { return this._setOptions({ disabled: false }); }, disable: function() { return this._setOptions({ disabled: true }); }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^([\w:-]*)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); // Clear the stack to avoid memory leaks (#10056) this.bindings = $( this.bindings.not( element ).get() ); this.focusable = $( this.focusable.not( element ).get() ); this.hoverable = $( this.hoverable.not( element ).get() ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions, effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue(function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); }); } }; }); var widget = $.widget; })); /* * jQuery Iframe Transport Plugin * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2011, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * https://opensource.org/licenses/MIT */ /* global define, require, window, document, JSON */ ;(function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define(['jquery'], factory); } else if (typeof exports === 'object') { // Node/CommonJS: factory(require('jquery')); } else { // Browser globals: factory(window.jQuery); } }(function ($) { 'use strict'; // Helper variable to create unique names for the transport iframes: var counter = 0, jsonAPI = $, jsonParse = 'parseJSON'; if ('JSON' in window && 'parse' in JSON) { jsonAPI = JSON; jsonParse = 'parse'; } // The iframe transport accepts four additional options: // options.fileInput: a jQuery collection of file input fields // options.paramName: the parameter name for the file form data, // overrides the name property of the file input field(s), // can be a string or an array of strings. // options.formData: an array of objects with name and value properties, // equivalent to the return data of .serializeArray(), e.g.: // [{name: 'a', value: 1}, {name: 'b', value: 2}] // options.initialIframeSrc: the URL of the initial iframe src, // by default set to "javascript:false;" $.ajaxTransport('iframe', function (options) { if (options.async) { // javascript:false as initial iframe src // prevents warning popups on HTTPS in IE6: /*jshint scripturl: true */ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', /*jshint scripturl: false */ form, iframe, addParamChar; return { send: function (_, completeCallback) { form = $('
'); form.attr('accept-charset', options.formAcceptCharset); addParamChar = /\?/.test(options.url) ? '&' : '?'; // XDomainRequest only supports GET and POST: if (options.type === 'DELETE') { options.url = options.url + addParamChar + '_method=DELETE'; options.type = 'POST'; } else if (options.type === 'PUT') { options.url = options.url + addParamChar + '_method=PUT'; options.type = 'POST'; } else if (options.type === 'PATCH') { options.url = options.url + addParamChar + '_method=PATCH'; options.type = 'POST'; } // IE versions below IE8 cannot set the name property of // elements that have already been added to the DOM, // so we set the name along with the iframe HTML markup: counter += 1; iframe = $( '' ).bind('load', function () { var fileInputClones, paramNames = $.isArray(options.paramName) ? options.paramName : [options.paramName]; iframe .unbind('load') .bind('load', function () { var response; // Wrap in a try/catch block to catch exceptions thrown // when trying to access cross-domain iframe contents: try { response = iframe.contents(); // Google Chrome and Firefox do not throw an // exception when calling iframe.contents() on // cross-domain requests, so we unify the response: if (!response.length || !response[0].firstChild) { throw new Error(); } } catch (e) { response = undefined; } // The complete callback returns the // iframe content document as response object: completeCallback( 200, 'success', {'iframe': response} ); // Fix for IE endless progress bar activity bug // (happens on form submits to iframe targets): $('') .appendTo(form); window.setTimeout(function () { // Removing the form in a setTimeout call // allows Chrome's developer tools to display // the response result form.remove(); }, 0); }); form .prop('target', iframe.prop('name')) .prop('action', options.url) .prop('method', options.type); if (options.formData) { $.each(options.formData, function (index, field) { $('') .prop('name', field.name) .val(field.value) .appendTo(form); }); } if (options.fileInput && options.fileInput.length && options.type === 'POST') { fileInputClones = options.fileInput.clone(); // Insert a clone for each file input field: options.fileInput.after(function (index) { return fileInputClones[index]; }); if (options.paramName) { options.fileInput.each(function (index) { $(this).prop( 'name', paramNames[index] || options.paramName ); }); } // Appending the file input fields to the hidden form // removes them from their original location: form .append(options.fileInput) .prop('enctype', 'multipart/form-data') // enctype must be set as encoding for IE: .prop('encoding', 'multipart/form-data'); // Remove the HTML5 form attribute from the input(s): options.fileInput.removeAttr('form'); } form.submit(); // Insert the file input fields at their original location // by replacing the clones with the originals: if (fileInputClones && fileInputClones.length) { options.fileInput.each(function (index, input) { var clone = $(fileInputClones[index]); // Restore the original name and form properties: $(input) .prop('name', clone.prop('name')) .attr('form', clone.attr('form')); clone.replaceWith(input); }); } }); form.append(iframe).appendTo(document.body); }, abort: function () { if (iframe) { // javascript:false as iframe src aborts the request // and prevents warning popups on HTTPS in IE6. // concat is used to avoid the "Script URL" JSLint error: iframe .unbind('load') .prop('src', initialIframeSrc); } if (form) { form.remove(); } } }; } }); // The iframe transport returns the iframe content document as response. // The following adds converters from iframe to text, json, html, xml // and script. // Please note that the Content-Type for JSON responses has to be text/plain // or text/html, if the browser doesn't include application/json in the // Accept header, else IE will show a download dialog. // The Content-Type for XML responses on the other hand has to be always // application/xml or text/xml, so IE properly parses the XML response. // See also // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation $.ajaxSetup({ converters: { 'iframe text': function (iframe) { return iframe && $(iframe[0].body).text(); }, 'iframe json': function (iframe) { return iframe && jsonAPI[jsonParse]($(iframe[0].body).text()); }, 'iframe html': function (iframe) { return iframe && $(iframe[0].body).html(); }, 'iframe xml': function (iframe) { var xmlDoc = iframe && iframe[0]; return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || $(xmlDoc.body).html()); }, 'iframe script': function (iframe) { return iframe && $.globalEval($(iframe[0].body).text()); } } }); })); /* * jQuery File Upload Plugin * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * https://opensource.org/licenses/MIT */ /* jshint nomen:false */ /* global define, require, window, document, location, Blob, FormData */ ;(function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define([ 'jquery', 'jquery-ui/ui/widget' ], factory); } else if (typeof exports === 'object') { // Node/CommonJS: factory( require('jquery'), require('./vendor/jquery.ui.widget') ); } else { // Browser globals: factory(window.jQuery); } }(function ($) { 'use strict'; // Detect file input support, based on // http://viljamis.com/blog/2012/file-upload-support-on-mobile/ $.support.fileInput = !(new RegExp( // Handle devices which give false positives for the feature detection: '(Android (1\\.[0156]|2\\.[01]))' + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + '|(w(eb)?OSBrowser)|(webOS)' + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' ).test(window.navigator.userAgent) || // Feature detection for all other devices: $('').prop('disabled')); // The FileReader API is not actually used, but works as feature detection, // as some Safari versions (5?) support XHR file uploads via the FormData API, // but not non-multipart XHR file uploads. // window.XMLHttpRequestUpload is not available on IE10, so we check for // window.ProgressEvent instead to detect XHR2 file upload capability: $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader); $.support.xhrFormDataFileUpload = !!window.FormData; // Detect support for Blob slicing (required for chunked uploads): $.support.blobSlice = window.Blob && (Blob.prototype.slice || Blob.prototype.webkitSlice || Blob.prototype.mozSlice); // Helper function to create drag handlers for dragover/dragenter/dragleave: function getDragHandler(type) { var isDragOver = type === 'dragover'; return function (e) { e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; var dataTransfer = e.dataTransfer; if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && this._trigger( type, $.Event(type, {delegatedEvent: e}) ) !== false) { e.preventDefault(); if (isDragOver) { dataTransfer.dropEffect = 'copy'; } } }; } // The fileupload widget listens for change events on file input fields defined // via fileInput setting and paste or drop events of the given dropZone. // In addition to the default jQuery Widget methods, the fileupload widget // exposes the "add" and "send" methods, to add or directly send files using // the fileupload API. // By default, files added via file input selection, paste, drag & drop or // "add" method are uploaded immediately, but it is possible to override // the "add" callback option to queue file uploads. $.widget('blueimp.fileupload', { options: { // The drop target element(s), by the default the complete document. // Set to null to disable drag & drop support: dropZone: $(document), // The paste target element(s), by the default undefined. // Set to a DOM node or jQuery object to enable file pasting: pasteZone: undefined, // The file input field(s), that are listened to for change events. // If undefined, it is set to the file input fields inside // of the widget element on plugin initialization. // Set to null to disable the change listener. fileInput: undefined, // By default, the file input field is replaced with a clone after // each input field change event. This is required for iframe transport // queues and allows change events to be fired for the same file // selection, but can be disabled by setting the following option to false: replaceFileInput: true, // The parameter name for the file form data (the request argument name). // If undefined or empty, the name property of the file input field is // used, or "files[]" if the file input name property is also empty, // can be a string or an array of strings: paramName: undefined, // By default, each file of a selection is uploaded using an individual // request for XHR type uploads. Set to false to upload file // selections in one request each: singleFileUploads: true, // To limit the number of files uploaded with one XHR request, // set the following option to an integer greater than 0: limitMultiFileUploads: undefined, // The following option limits the number of files uploaded with one // XHR request to keep the request size under or equal to the defined // limit in bytes: limitMultiFileUploadSize: undefined, // Multipart file uploads add a number of bytes to each uploaded file, // therefore the following option adds an overhead for each file used // in the limitMultiFileUploadSize configuration: limitMultiFileUploadSizeOverhead: 512, // Set the following option to true to issue all file upload requests // in a sequential order: sequentialUploads: false, // To limit the number of concurrent uploads, // set the following option to an integer greater than 0: limitConcurrentUploads: undefined, // Set the following option to true to force iframe transport uploads: forceIframeTransport: false, // Set the following option to the location of a redirect url on the // origin server, for cross-domain iframe transport uploads: redirect: undefined, // The parameter name for the redirect url, sent as part of the form // data and set to 'redirect' if this option is empty: redirectParamName: undefined, // Set the following option to the location of a postMessage window, // to enable postMessage transport uploads: postMessage: undefined, // By default, XHR file uploads are sent as multipart/form-data. // The iframe transport is always using multipart/form-data. // Set to false to enable non-multipart XHR uploads: multipart: true, // To upload large files in smaller chunks, set the following option // to a preferred maximum chunk size. If set to 0, null or undefined, // or the browser does not support the required Blob API, files will // be uploaded as a whole. maxChunkSize: undefined, // When a non-multipart upload or a chunked multipart upload has been // aborted, this option can be used to resume the upload by setting // it to the size of the already uploaded bytes. This option is most // useful when modifying the options object inside of the "add" or // "send" callbacks, as the options are cloned for each file upload. uploadedBytes: undefined, // By default, failed (abort or error) file uploads are removed from the // global progress calculation. Set the following option to false to // prevent recalculating the global progress data: recalculateProgress: true, // Interval in milliseconds to calculate and trigger progress events: progressInterval: 100, // Interval in milliseconds to calculate progress bitrate: bitrateInterval: 500, // By default, uploads are started automatically when adding files: autoUpload: true, // Error and info messages: messages: { uploadedBytes: 'Uploaded bytes exceed file size' }, // Translation function, gets the message key to be translated // and an object with context specific data as arguments: i18n: function (message, context) { message = this.messages[message] || message.toString(); if (context) { $.each(context, function (key, value) { message = message.replace('{' + key + '}', value); }); } return message; }, // Additional form data to be sent along with the file uploads can be set // using this option, which accepts an array of objects with name and // value properties, a function returning such an array, a FormData // object (for XHR file uploads), or a simple object. // The form of the first fileInput is given as parameter to the function: formData: function (form) { return form.serializeArray(); }, // The add callback is invoked as soon as files are added to the fileupload // widget (via file input selection, drag & drop, paste or add API call). // If the singleFileUploads option is enabled, this callback will be // called once for each file in the selection for XHR file uploads, else // once for each file selection. // // The upload starts when the submit method is invoked on the data parameter. // The data object contains a files property holding the added files // and allows you to override plugin options as well as define ajax settings. // // Listeners for this callback can also be bound the following way: // .bind('fileuploadadd', func); // // data.submit() returns a Promise object and allows to attach additional // handlers using jQuery's Deferred callbacks: // data.submit().done(func).fail(func).always(func); add: function (e, data) { if (e.isDefaultPrevented()) { return false; } if (data.autoUpload || (data.autoUpload !== false && $(this).fileupload('option', 'autoUpload'))) { data.process().done(function () { data.submit(); }); } }, // Other callbacks: // Callback for the submit event of each file upload: // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); // Callback for the start of each file upload request: // send: function (e, data) {}, // .bind('fileuploadsend', func); // Callback for successful uploads: // done: function (e, data) {}, // .bind('fileuploaddone', func); // Callback for failed (abort or error) uploads: // fail: function (e, data) {}, // .bind('fileuploadfail', func); // Callback for completed (success, abort or error) requests: // always: function (e, data) {}, // .bind('fileuploadalways', func); // Callback for upload progress events: // progress: function (e, data) {}, // .bind('fileuploadprogress', func); // Callback for global upload progress events: // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); // Callback for uploads start, equivalent to the global ajaxStart event: // start: function (e) {}, // .bind('fileuploadstart', func); // Callback for uploads stop, equivalent to the global ajaxStop event: // stop: function (e) {}, // .bind('fileuploadstop', func); // Callback for change events of the fileInput(s): // change: function (e, data) {}, // .bind('fileuploadchange', func); // Callback for paste events to the pasteZone(s): // paste: function (e, data) {}, // .bind('fileuploadpaste', func); // Callback for drop events of the dropZone(s): // drop: function (e, data) {}, // .bind('fileuploaddrop', func); // Callback for dragover events of the dropZone(s): // dragover: function (e) {}, // .bind('fileuploaddragover', func); // Callback for the start of each chunk upload request: // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); // Callback for successful chunk uploads: // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); // Callback for failed (abort or error) chunk uploads: // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); // Callback for completed (success, abort or error) chunk upload requests: // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); // The plugin options are used as settings object for the ajax calls. // The following are jQuery ajax settings required for the file uploads: processData: false, contentType: false, cache: false, timeout: 0 }, // A list of options that require reinitializing event listeners and/or // special initialization code: _specialOptions: [ 'fileInput', 'dropZone', 'pasteZone', 'multipart', 'forceIframeTransport' ], _blobSlice: $.support.blobSlice && function () { var slice = this.slice || this.webkitSlice || this.mozSlice; return slice.apply(this, arguments); }, _BitrateTimer: function () { this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); this.loaded = 0; this.bitrate = 0; this.getBitrate = function (now, loaded, interval) { var timeDiff = now - this.timestamp; if (!this.bitrate || !interval || timeDiff > interval) { this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; this.loaded = loaded; this.timestamp = now; } return this.bitrate; }; }, _isXHRUpload: function (options) { return !options.forceIframeTransport && ((!options.multipart && $.support.xhrFileUpload) || $.support.xhrFormDataFileUpload); }, _getFormData: function (options) { var formData; if ($.type(options.formData) === 'function') { return options.formData(options.form); } if ($.isArray(options.formData)) { return options.formData; } if ($.type(options.formData) === 'object') { formData = []; $.each(options.formData, function (name, value) { formData.push({name: name, value: value}); }); return formData; } return []; }, _getTotal: function (files) { var total = 0; $.each(files, function (index, file) { total += file.size || 1; }); return total; }, _initProgressObject: function (obj) { var progress = { loaded: 0, total: 0, bitrate: 0 }; if (obj._progress) { $.extend(obj._progress, progress); } else { obj._progress = progress; } }, _initResponseObject: function (obj) { var prop; if (obj._response) { for (prop in obj._response) { if (obj._response.hasOwnProperty(prop)) { delete obj._response[prop]; } } } else { obj._response = {}; } }, _onProgress: function (e, data) { if (e.lengthComputable) { var now = ((Date.now) ? Date.now() : (new Date()).getTime()), loaded; if (data._time && data.progressInterval && (now - data._time < data.progressInterval) && e.loaded !== e.total) { return; } data._time = now; loaded = Math.floor( e.loaded / e.total * (data.chunkSize || data._progress.total) ) + (data.uploadedBytes || 0); // Add the difference from the previously loaded state // to the global loaded counter: this._progress.loaded += (loaded - data._progress.loaded); this._progress.bitrate = this._bitrateTimer.getBitrate( now, this._progress.loaded, data.bitrateInterval ); data._progress.loaded = data.loaded = loaded; data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( now, loaded, data.bitrateInterval ); // Trigger a custom progress event with a total data property set // to the file size(s) of the current upload and a loaded data // property calculated accordingly: this._trigger( 'progress', $.Event('progress', {delegatedEvent: e}), data ); // Trigger a global progress event for all current file uploads, // including ajax calls queued for sequential file uploads: this._trigger( 'progressall', $.Event('progressall', {delegatedEvent: e}), this._progress ); } }, _initProgressListener: function (options) { var that = this, xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); // Accesss to the native XHR object is required to add event listeners // for the upload progress event: if (xhr.upload) { $(xhr.upload).bind('progress', function (e) { var oe = e.originalEvent; // Make sure the progress event properties get copied over: e.lengthComputable = oe.lengthComputable; e.loaded = oe.loaded; e.total = oe.total; that._onProgress(e, options); }); options.xhr = function () { return xhr; }; } }, _isInstanceOf: function (type, obj) { // Cross-frame instanceof check return Object.prototype.toString.call(obj) === '[object ' + type + ']'; }, _initXHRData: function (options) { var that = this, formData, file = options.files[0], // Ignore non-multipart setting if not supported: multipart = options.multipart || !$.support.xhrFileUpload, paramName = $.type(options.paramName) === 'array' ? options.paramName[0] : options.paramName; options.headers = $.extend({}, options.headers); if (options.contentRange) { options.headers['Content-Range'] = options.contentRange; } if (!multipart || options.blob || !this._isInstanceOf('File', file)) { options.headers['Content-Disposition'] = 'attachment; filename="' + encodeURI(file.name) + '"'; } if (!multipart) { options.contentType = file.type || 'application/octet-stream'; options.data = options.blob || file; } else if ($.support.xhrFormDataFileUpload) { if (options.postMessage) { // window.postMessage does not allow sending FormData // objects, so we just add the File/Blob objects to // the formData array and let the postMessage window // create the FormData object out of this array: formData = this._getFormData(options); if (options.blob) { formData.push({ name: paramName, value: options.blob }); } else { $.each(options.files, function (index, file) { formData.push({ name: ($.type(options.paramName) === 'array' && options.paramName[index]) || paramName, value: file }); }); } } else { if (that._isInstanceOf('FormData', options.formData)) { formData = options.formData; } else { formData = new FormData(); $.each(this._getFormData(options), function (index, field) { formData.append(field.name, field.value); }); } if (options.blob) { formData.append(paramName, options.blob, file.name); } else { $.each(options.files, function (index, file) { // This check allows the tests to run with // dummy objects: if (that._isInstanceOf('File', file) || that._isInstanceOf('Blob', file)) { formData.append( ($.type(options.paramName) === 'array' && options.paramName[index]) || paramName, file, file.uploadName || file.name ); } }); } } options.data = formData; } // Blob reference is not needed anymore, free memory: options.blob = null; }, _initIframeSettings: function (options) { var targetHost = $('').prop('href', options.url).prop('host'); // Setting the dataType to iframe enables the iframe transport: options.dataType = 'iframe ' + (options.dataType || ''); // The iframe transport accepts a serialized array as form data: options.formData = this._getFormData(options); // Add redirect url to form data on cross-domain uploads: if (options.redirect && targetHost && targetHost !== location.host) { options.formData.push({ name: options.redirectParamName || 'redirect', value: options.redirect }); } }, _initDataSettings: function (options) { if (this._isXHRUpload(options)) { if (!this._chunkedUpload(options, true)) { if (!options.data) { this._initXHRData(options); } this._initProgressListener(options); } if (options.postMessage) { // Setting the dataType to postmessage enables the // postMessage transport: options.dataType = 'postmessage ' + (options.dataType || ''); } } else { this._initIframeSettings(options); } }, _getParamName: function (options) { var fileInput = $(options.fileInput), paramName = options.paramName; if (!paramName) { paramName = []; fileInput.each(function () { var input = $(this), name = input.prop('name') || 'files[]', i = (input.prop('files') || [1]).length; while (i) { paramName.push(name); i -= 1; } }); if (!paramName.length) { paramName = [fileInput.prop('name') || 'files[]']; } } else if (!$.isArray(paramName)) { paramName = [paramName]; } return paramName; }, _initFormSettings: function (options) { // Retrieve missing options from the input field and the // associated form, if available: if (!options.form || !options.form.length) { options.form = $(options.fileInput.prop('form')); // If the given file input doesn't have an associated form, // use the default widget file input's form: if (!options.form.length) { options.form = $(this.options.fileInput.prop('form')); } } options.paramName = this._getParamName(options); if (!options.url) { options.url = options.form.prop('action') || location.href; } // The HTTP request method must be "POST" or "PUT": options.type = (options.type || ($.type(options.form.prop('method')) === 'string' && options.form.prop('method')) || '' ).toUpperCase(); if (options.type !== 'POST' && options.type !== 'PUT' && options.type !== 'PATCH') { options.type = 'POST'; } if (!options.formAcceptCharset) { options.formAcceptCharset = options.form.attr('accept-charset'); } }, _getAJAXSettings: function (data) { var options = $.extend({}, this.options, data); this._initFormSettings(options); this._initDataSettings(options); return options; }, // jQuery 1.6 doesn't provide .state(), // while jQuery 1.8+ removed .isRejected() and .isResolved(): _getDeferredState: function (deferred) { if (deferred.state) { return deferred.state(); } if (deferred.isResolved()) { return 'resolved'; } if (deferred.isRejected()) { return 'rejected'; } return 'pending'; }, // Maps jqXHR callbacks to the equivalent // methods of the given Promise object: _enhancePromise: function (promise) { promise.success = promise.done; promise.error = promise.fail; promise.complete = promise.always; return promise; }, // Creates and returns a Promise object enhanced with // the jqXHR methods abort, success, error and complete: _getXHRPromise: function (resolveOrReject, context, args) { var dfd = $.Deferred(), promise = dfd.promise(); context = context || this.options.context || promise; if (resolveOrReject === true) { dfd.resolveWith(context, args); } else if (resolveOrReject === false) { dfd.rejectWith(context, args); } promise.abort = dfd.promise; return this._enhancePromise(promise); }, // Adds convenience methods to the data callback argument: _addConvenienceMethods: function (e, data) { var that = this, getPromise = function (args) { return $.Deferred().resolveWith(that, args).promise(); }; data.process = function (resolveFunc, rejectFunc) { if (resolveFunc || rejectFunc) { data._processQueue = this._processQueue = (this._processQueue || getPromise([this])).then( function () { if (data.errorThrown) { return $.Deferred() .rejectWith(that, [data]).promise(); } return getPromise(arguments); } ).then(resolveFunc, rejectFunc); } return this._processQueue || getPromise([this]); }; data.submit = function () { if (this.state() !== 'pending') { data.jqXHR = this.jqXHR = (that._trigger( 'submit', $.Event('submit', {delegatedEvent: e}), this ) !== false) && that._onSend(e, this); } return this.jqXHR || that._getXHRPromise(); }; data.abort = function () { if (this.jqXHR) { return this.jqXHR.abort(); } this.errorThrown = 'abort'; that._trigger('fail', null, this); return that._getXHRPromise(false); }; data.state = function () { if (this.jqXHR) { return that._getDeferredState(this.jqXHR); } if (this._processQueue) { return that._getDeferredState(this._processQueue); } }; data.processing = function () { return !this.jqXHR && this._processQueue && that ._getDeferredState(this._processQueue) === 'pending'; }; data.progress = function () { return this._progress; }; data.response = function () { return this._response; }; }, // Parses the Range header from the server response // and returns the uploaded bytes: _getUploadedBytes: function (jqXHR) { var range = jqXHR.getResponseHeader('Range'), parts = range && range.split('-'), upperBytesPos = parts && parts.length > 1 && parseInt(parts[1], 10); return upperBytesPos && upperBytesPos + 1; }, // Uploads a file in multiple, sequential requests // by splitting the file up in multiple blob chunks. // If the second parameter is true, only tests if the file // should be uploaded in chunks, but does not invoke any // upload requests: _chunkedUpload: function (options, testOnly) { options.uploadedBytes = options.uploadedBytes || 0; var that = this, file = options.files[0], fs = file.size, ub = options.uploadedBytes, mcs = options.maxChunkSize || fs, slice = this._blobSlice, dfd = $.Deferred(), promise = dfd.promise(), jqXHR, upload; if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || options.data) { return false; } if (testOnly) { return true; } if (ub >= fs) { file.error = options.i18n('uploadedBytes'); return this._getXHRPromise( false, options.context, [null, 'error', file.error] ); } // The chunk upload method: upload = function () { // Clone the options object for each chunk upload: var o = $.extend({}, options), currentLoaded = o._progress.loaded; o.blob = slice.call( file, ub, ub + mcs, file.type ); // Store the current chunk size, as the blob itself // will be dereferenced after data processing: o.chunkSize = o.blob.size; // Expose the chunk bytes position range: o.contentRange = 'bytes ' + ub + '-' + (ub + o.chunkSize - 1) + '/' + fs; // Process the upload data (the blob and potential form data): that._initXHRData(o); // Add progress listeners for this chunk upload: that._initProgressListener(o); jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || that._getXHRPromise(false, o.context)) .done(function (result, textStatus, jqXHR) { ub = that._getUploadedBytes(jqXHR) || (ub + o.chunkSize); // Create a progress event if no final progress event // with loaded equaling total has been triggered // for this chunk: if (currentLoaded + o.chunkSize - o._progress.loaded) { that._onProgress($.Event('progress', { lengthComputable: true, loaded: ub - o.uploadedBytes, total: ub - o.uploadedBytes }), o); } options.uploadedBytes = o.uploadedBytes = ub; o.result = result; o.textStatus = textStatus; o.jqXHR = jqXHR; that._trigger('chunkdone', null, o); that._trigger('chunkalways', null, o); if (ub < fs) { // File upload not yet complete, // continue with the next chunk: upload(); } else { dfd.resolveWith( o.context, [result, textStatus, jqXHR] ); } }) .fail(function (jqXHR, textStatus, errorThrown) { o.jqXHR = jqXHR; o.textStatus = textStatus; o.errorThrown = errorThrown; that._trigger('chunkfail', null, o); that._trigger('chunkalways', null, o); dfd.rejectWith( o.context, [jqXHR, textStatus, errorThrown] ); }); }; this._enhancePromise(promise); promise.abort = function () { return jqXHR.abort(); }; upload(); return promise; }, _beforeSend: function (e, data) { if (this._active === 0) { // the start callback is triggered when an upload starts // and no other uploads are currently running, // equivalent to the global ajaxStart event: this._trigger('start'); // Set timer for global bitrate progress calculation: this._bitrateTimer = new this._BitrateTimer(); // Reset the global progress values: this._progress.loaded = this._progress.total = 0; this._progress.bitrate = 0; } // Make sure the container objects for the .response() and // .progress() methods on the data object are available // and reset to their initial state: this._initResponseObject(data); this._initProgressObject(data); data._progress.loaded = data.loaded = data.uploadedBytes || 0; data._progress.total = data.total = this._getTotal(data.files) || 1; data._progress.bitrate = data.bitrate = 0; this._active += 1; // Initialize the global progress values: this._progress.loaded += data.loaded; this._progress.total += data.total; }, _onDone: function (result, textStatus, jqXHR, options) { var total = options._progress.total, response = options._response; if (options._progress.loaded < total) { // Create a progress event if no final progress event // with loaded equaling total has been triggered: this._onProgress($.Event('progress', { lengthComputable: true, loaded: total, total: total }), options); } response.result = options.result = result; response.textStatus = options.textStatus = textStatus; response.jqXHR = options.jqXHR = jqXHR; this._trigger('done', null, options); }, _onFail: function (jqXHR, textStatus, errorThrown, options) { var response = options._response; if (options.recalculateProgress) { // Remove the failed (error or abort) file upload from // the global progress calculation: this._progress.loaded -= options._progress.loaded; this._progress.total -= options._progress.total; } response.jqXHR = options.jqXHR = jqXHR; response.textStatus = options.textStatus = textStatus; response.errorThrown = options.errorThrown = errorThrown; this._trigger('fail', null, options); }, _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { // jqXHRorResult, textStatus and jqXHRorError are added to the // options object via done and fail callbacks this._trigger('always', null, options); }, _onSend: function (e, data) { if (!data.submit) { this._addConvenienceMethods(e, data); } var that = this, jqXHR, aborted, slot, pipe, options = that._getAJAXSettings(data), send = function () { that._sending += 1; // Set timer for bitrate progress calculation: options._bitrateTimer = new that._BitrateTimer(); jqXHR = jqXHR || ( ((aborted || that._trigger( 'send', $.Event('send', {delegatedEvent: e}), options ) === false) && that._getXHRPromise(false, options.context, aborted)) || that._chunkedUpload(options) || $.ajax(options) ).done(function (result, textStatus, jqXHR) { that._onDone(result, textStatus, jqXHR, options); }).fail(function (jqXHR, textStatus, errorThrown) { that._onFail(jqXHR, textStatus, errorThrown, options); }).always(function (jqXHRorResult, textStatus, jqXHRorError) { that._onAlways( jqXHRorResult, textStatus, jqXHRorError, options ); that._sending -= 1; that._active -= 1; if (options.limitConcurrentUploads && options.limitConcurrentUploads > that._sending) { // Start the next queued upload, // that has not been aborted: var nextSlot = that._slots.shift(); while (nextSlot) { if (that._getDeferredState(nextSlot) === 'pending') { nextSlot.resolve(); break; } nextSlot = that._slots.shift(); } } if (that._active === 0) { // The stop callback is triggered when all uploads have // been completed, equivalent to the global ajaxStop event: that._trigger('stop'); } }); return jqXHR; }; this._beforeSend(e, options); if (this.options.sequentialUploads || (this.options.limitConcurrentUploads && this.options.limitConcurrentUploads <= this._sending)) { if (this.options.limitConcurrentUploads > 1) { slot = $.Deferred(); this._slots.push(slot); pipe = slot.then(send); } else { this._sequence = this._sequence.then(send, send); pipe = this._sequence; } // Return the piped Promise object, enhanced with an abort method, // which is delegated to the jqXHR object of the current upload, // and jqXHR callbacks mapped to the equivalent Promise methods: pipe.abort = function () { aborted = [undefined, 'abort', 'abort']; if (!jqXHR) { if (slot) { slot.rejectWith(options.context, aborted); } return send(); } return jqXHR.abort(); }; return this._enhancePromise(pipe); } return send(); }, _onAdd: function (e, data) { var that = this, result = true, options = $.extend({}, this.options, data), files = data.files, filesLength = files.length, limit = options.limitMultiFileUploads, limitSize = options.limitMultiFileUploadSize, overhead = options.limitMultiFileUploadSizeOverhead, batchSize = 0, paramName = this._getParamName(options), paramNameSet, paramNameSlice, fileSet, i, j = 0; if (!filesLength) { return false; } if (limitSize && files[0].size === undefined) { limitSize = undefined; } if (!(options.singleFileUploads || limit || limitSize) || !this._isXHRUpload(options)) { fileSet = [files]; paramNameSet = [paramName]; } else if (!(options.singleFileUploads || limitSize) && limit) { fileSet = []; paramNameSet = []; for (i = 0; i < filesLength; i += limit) { fileSet.push(files.slice(i, i + limit)); paramNameSlice = paramName.slice(i, i + limit); if (!paramNameSlice.length) { paramNameSlice = paramName; } paramNameSet.push(paramNameSlice); } } else if (!options.singleFileUploads && limitSize) { fileSet = []; paramNameSet = []; for (i = 0; i < filesLength; i = i + 1) { batchSize += files[i].size + overhead; if (i + 1 === filesLength || ((batchSize + files[i + 1].size + overhead) > limitSize) || (limit && i + 1 - j >= limit)) { fileSet.push(files.slice(j, i + 1)); paramNameSlice = paramName.slice(j, i + 1); if (!paramNameSlice.length) { paramNameSlice = paramName; } paramNameSet.push(paramNameSlice); j = i + 1; batchSize = 0; } } } else { paramNameSet = paramName; } data.originalFiles = files; $.each(fileSet || files, function (index, element) { var newData = $.extend({}, data); newData.files = fileSet ? element : [element]; newData.paramName = paramNameSet[index]; that._initResponseObject(newData); that._initProgressObject(newData); that._addConvenienceMethods(e, newData); result = that._trigger( 'add', $.Event('add', {delegatedEvent: e}), newData ); return result; }); return result; }, _replaceFileInput: function (data) { var input = data.fileInput, inputClone = input.clone(true), restoreFocus = input.is(document.activeElement); // Add a reference for the new cloned file input to the data argument: data.fileInputClone = inputClone; $('
').append(inputClone)[0].reset(); // Detaching allows to insert the fileInput on another form // without loosing the file input value: input.after(inputClone).detach(); // If the fileInput had focus before it was detached, // restore focus to the inputClone. if (restoreFocus) { inputClone.focus(); } // Avoid memory leaks with the detached file input: $.cleanData(input.unbind('remove')); // Replace the original file input element in the fileInput // elements set with the clone, which has been copied including // event handlers: this.options.fileInput = this.options.fileInput.map(function (i, el) { if (el === input[0]) { return inputClone[0]; } return el; }); // If the widget has been initialized on the file input itself, // override this.element with the file input clone: if (input[0] === this.element[0]) { this.element = inputClone; } }, _handleFileTreeEntry: function (entry, path) { var that = this, dfd = $.Deferred(), entries = [], dirReader, errorHandler = function (e) { if (e && !e.entry) { e.entry = entry; } // Since $.when returns immediately if one // Deferred is rejected, we use resolve instead. // This allows valid files and invalid items // to be returned together in one set: dfd.resolve([e]); }, successHandler = function (entries) { that._handleFileTreeEntries( entries, path + entry.name + '/' ).done(function (files) { dfd.resolve(files); }).fail(errorHandler); }, readEntries = function () { dirReader.readEntries(function (results) { if (!results.length) { successHandler(entries); } else { entries = entries.concat(results); readEntries(); } }, errorHandler); }; path = path || ''; if (entry.isFile) { if (entry._file) { // Workaround for Chrome bug #149735 entry._file.relativePath = path; dfd.resolve(entry._file); } else { entry.file(function (file) { file.relativePath = path; dfd.resolve(file); }, errorHandler); } } else if (entry.isDirectory) { dirReader = entry.createReader(); readEntries(); } else { // Return an empy list for file system items // other than files or directories: dfd.resolve([]); } return dfd.promise(); }, _handleFileTreeEntries: function (entries, path) { var that = this; return $.when.apply( $, $.map(entries, function (entry) { return that._handleFileTreeEntry(entry, path); }) ).then(function () { return Array.prototype.concat.apply( [], arguments ); }); }, _getDroppedFiles: function (dataTransfer) { dataTransfer = dataTransfer || {}; var items = dataTransfer.items; if (items && items.length && (items[0].webkitGetAsEntry || items[0].getAsEntry)) { return this._handleFileTreeEntries( $.map(items, function (item) { var entry; if (item.webkitGetAsEntry) { entry = item.webkitGetAsEntry(); if (entry) { // Workaround for Chrome bug #149735: entry._file = item.getAsFile(); } return entry; } return item.getAsEntry(); }) ); } return $.Deferred().resolve( $.makeArray(dataTransfer.files) ).promise(); }, _getSingleFileInputFiles: function (fileInput) { fileInput = $(fileInput); var entries = fileInput.prop('webkitEntries') || fileInput.prop('entries'), files, value; if (entries && entries.length) { return this._handleFileTreeEntries(entries); } files = $.makeArray(fileInput.prop('files')); if (!files.length) { value = fileInput.prop('value'); if (!value) { return $.Deferred().resolve([]).promise(); } // If the files property is not available, the browser does not // support the File API and we add a pseudo File object with // the input value as name with path information removed: files = [{name: value.replace(/^.*\\/, '')}]; } else if (files[0].name === undefined && files[0].fileName) { // File normalization for Safari 4 and Firefox 3: $.each(files, function (index, file) { file.name = file.fileName; file.size = file.fileSize; }); } return $.Deferred().resolve(files).promise(); }, _getFileInputFiles: function (fileInput) { if (!(fileInput instanceof $) || fileInput.length === 1) { return this._getSingleFileInputFiles(fileInput); } return $.when.apply( $, $.map(fileInput, this._getSingleFileInputFiles) ).then(function () { return Array.prototype.concat.apply( [], arguments ); }); }, _onChange: function (e) { var that = this, data = { fileInput: $(e.target), form: $(e.target.form) }; this._getFileInputFiles(data.fileInput).always(function (files) { data.files = files; if (that.options.replaceFileInput) { that._replaceFileInput(data); } if (that._trigger( 'change', $.Event('change', {delegatedEvent: e}), data ) !== false) { that._onAdd(e, data); } }); }, _onPaste: function (e) { var items = e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.items, data = {files: []}; if (items && items.length) { $.each(items, function (index, item) { var file = item.getAsFile && item.getAsFile(); if (file) { data.files.push(file); } }); if (this._trigger( 'paste', $.Event('paste', {delegatedEvent: e}), data ) !== false) { this._onAdd(e, data); } } }, _onDrop: function (e) { e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; var that = this, dataTransfer = e.dataTransfer, data = {}; if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { e.preventDefault(); this._getDroppedFiles(dataTransfer).always(function (files) { data.files = files; if (that._trigger( 'drop', $.Event('drop', {delegatedEvent: e}), data ) !== false) { that._onAdd(e, data); } }); } }, _onDragOver: getDragHandler('dragover'), _onDragEnter: getDragHandler('dragenter'), _onDragLeave: getDragHandler('dragleave'), _initEventHandlers: function () { if (this._isXHRUpload(this.options)) { this._on(this.options.dropZone, { dragover: this._onDragOver, drop: this._onDrop, // event.preventDefault() on dragenter is required for IE10+: dragenter: this._onDragEnter, // dragleave is not required, but added for completeness: dragleave: this._onDragLeave }); this._on(this.options.pasteZone, { paste: this._onPaste }); } if ($.support.fileInput) { this._on(this.options.fileInput, { change: this._onChange }); } }, _destroyEventHandlers: function () { this._off(this.options.dropZone, 'dragenter dragleave dragover drop'); this._off(this.options.pasteZone, 'paste'); this._off(this.options.fileInput, 'change'); }, _destroy: function () { this._destroyEventHandlers(); }, _setOption: function (key, value) { var reinit = $.inArray(key, this._specialOptions) !== -1; if (reinit) { this._destroyEventHandlers(); } this._super(key, value); if (reinit) { this._initSpecialOptions(); this._initEventHandlers(); } }, _initSpecialOptions: function () { var options = this.options; if (options.fileInput === undefined) { options.fileInput = this.element.is('input[type="file"]') ? this.element : this.element.find('input[type="file"]'); } else if (!(options.fileInput instanceof $)) { options.fileInput = $(options.fileInput); } if (!(options.dropZone instanceof $)) { options.dropZone = $(options.dropZone); } if (!(options.pasteZone instanceof $)) { options.pasteZone = $(options.pasteZone); } }, _getRegExp: function (str) { var parts = str.split('/'), modifiers = parts.pop(); parts.shift(); return new RegExp(parts.join('/'), modifiers); }, _isRegExpOption: function (key, value) { return key !== 'url' && $.type(value) === 'string' && /^\/.*\/[igm]{0,3}$/.test(value); }, _initDataAttributes: function () { var that = this, options = this.options, data = this.element.data(); // Initialize options set via HTML5 data-attributes: $.each( this.element[0].attributes, function (index, attr) { var key = attr.name.toLowerCase(), value; if (/^data-/.test(key)) { // Convert hyphen-ated key to camelCase: key = key.slice(5).replace(/-[a-z]/g, function (str) { return str.charAt(1).toUpperCase(); }); value = data[key]; if (that._isRegExpOption(key, value)) { value = that._getRegExp(value); } options[key] = value; } } ); }, _create: function () { this._initDataAttributes(); this._initSpecialOptions(); this._slots = []; this._sequence = this._getXHRPromise(true); this._sending = this._active = 0; this._initProgressObject(this); this._initEventHandlers(); }, // This method is exposed to the widget API and allows to query // the number of active uploads: active: function () { return this._active; }, // This method is exposed to the widget API and allows to query // the widget upload progress. // It returns an object with loaded, total and bitrate properties // for the running uploads: progress: function () { return this._progress; }, // This method is exposed to the widget API and allows adding files // using the fileupload API. The data parameter accepts an object which // must have a files property and can contain additional options: // .fileupload('add', {files: filesList}); add: function (data) { var that = this; if (!data || this.options.disabled) { return; } if (data.fileInput && !data.files) { this._getFileInputFiles(data.fileInput).always(function (files) { data.files = files; that._onAdd(null, data); }); } else { data.files = $.makeArray(data.files); this._onAdd(null, data); } }, // This method is exposed to the widget API and allows sending files // using the fileupload API. The data parameter accepts an object which // must have a files or fileInput property and can contain additional options: // .fileupload('send', {files: filesList}); // The method returns a Promise object for the file upload call. send: function (data) { if (data && !this.options.disabled) { if (data.fileInput && !data.files) { var that = this, dfd = $.Deferred(), promise = dfd.promise(), jqXHR, aborted; promise.abort = function () { aborted = true; if (jqXHR) { return jqXHR.abort(); } dfd.reject(null, 'abort', 'abort'); return promise; }; this._getFileInputFiles(data.fileInput).always( function (files) { if (aborted) { return; } if (!files.length) { dfd.reject(); return; } data.files = files; jqXHR = that._onSend(null, data); jqXHR.then( function (result, textStatus, jqXHR) { dfd.resolve(result, textStatus, jqXHR); }, function (jqXHR, textStatus, errorThrown) { dfd.reject(jqXHR, textStatus, errorThrown); } ); } ); return this._enhancePromise(promise); } data.files = $.makeArray(data.files); if (data.files.length) { return this._onSend(null, data); } } return this._getXHRPromise(false, data && data.context); } }); })); /*! * pickadate.js v3.5.6, 2015/04/20 * By Amsul, http://amsul.ca * Hosted on http://amsul.github.io/pickadate.js * Licensed under MIT */ (function ( factory ) { // AMD. if ( typeof define == 'function' && define.amd ) define( 'picker', ['jquery'], factory ) // Node.js/browserify. else if ( typeof exports == 'object' ) module.exports = factory( require('jquery') ) // Browser globals. else this.Picker = factory( jQuery ) }(function( $ ) { var $window = $( window ) var $document = $( document ) var $html = $( document.documentElement ) var supportsTransitions = document.documentElement.style.transition != null /** * The picker constructor that creates a blank picker. */ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) { // If there’s no element, return the picker constructor. if ( !ELEMENT ) return PickerConstructor var IS_DEFAULT_THEME = false, // The state of the picker. STATE = { id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) ) }, // Merge the defaults and options passed. SETTINGS = COMPONENT ? $.extend( true, {}, COMPONENT.defaults, OPTIONS ) : OPTIONS || {}, // Merge the default classes with the settings classes. CLASSES = $.extend( {}, PickerConstructor.klasses(), SETTINGS.klass ), // The element node wrapper into a jQuery object. $ELEMENT = $( ELEMENT ), // Pseudo picker constructor. PickerInstance = function() { return this.start() }, // The picker prototype. P = PickerInstance.prototype = { constructor: PickerInstance, $node: $ELEMENT, /** * Initialize everything */ start: function() { // If it’s already started, do nothing. if ( STATE && STATE.start ) return P // Update the picker states. STATE.methods = {} STATE.start = true STATE.open = false STATE.type = ELEMENT.type // Confirm focus state, convert into text input to remove UA stylings, // and set as readonly to prevent keyboard popup. ELEMENT.autofocus = ELEMENT == getActiveElement() ELEMENT.readOnly = !SETTINGS.editable ELEMENT.id = ELEMENT.id || STATE.id if ( ELEMENT.type != 'text' ) { ELEMENT.type = 'text' } // Create a new picker component with the settings. P.component = new COMPONENT(P, SETTINGS) // Create the picker root and then prepare it. P.$root = $( '
' ) prepareElementRoot() // Create the picker holder and then prepare it. P.$holder = $( createWrappedComponent() ).appendTo( P.$root ) prepareElementHolder() // If there’s a format for the hidden input element, create the element. if ( SETTINGS.formatSubmit ) { prepareElementHidden() } // Prepare the input element. prepareElement() // Insert the hidden input as specified in the settings. if ( SETTINGS.containerHidden ) $( SETTINGS.containerHidden ).append( P._hidden ) else $ELEMENT.after( P._hidden ) // Insert the root as specified in the settings. if ( SETTINGS.container ) $( SETTINGS.container ).append( P.$root ) else $ELEMENT.after( P.$root ) // Bind the default component and settings events. P.on({ start: P.component.onStart, render: P.component.onRender, stop: P.component.onStop, open: P.component.onOpen, close: P.component.onClose, set: P.component.onSet }).on({ start: SETTINGS.onStart, render: SETTINGS.onRender, stop: SETTINGS.onStop, open: SETTINGS.onOpen, close: SETTINGS.onClose, set: SETTINGS.onSet }) // Once we’re all set, check the theme in use. IS_DEFAULT_THEME = isUsingDefaultTheme( P.$holder[0] ) // If the element has autofocus, open the picker. if ( ELEMENT.autofocus ) { P.open() } // Trigger queued the “start” and “render” events. return P.trigger( 'start' ).trigger( 'render' ) }, //start /** * Render a new picker */ render: function( entireComponent ) { // Insert a new component holder in the root or box. if ( entireComponent ) { P.$holder = $( createWrappedComponent() ) prepareElementHolder() P.$root.html( P.$holder ) } else P.$root.find( '.' + CLASSES.box ).html( P.component.nodes( STATE.open ) ) // Trigger the queued “render” events. return P.trigger( 'render' ) }, //render /** * Destroy everything */ stop: function() { // If it’s already stopped, do nothing. if ( !STATE.start ) return P // Then close the picker. P.close() // Remove the hidden field. if ( P._hidden ) { P._hidden.parentNode.removeChild( P._hidden ) } // Remove the root. P.$root.remove() // Remove the input class, remove the stored data, and unbind // the events (after a tick for IE - see `P.close`). $ELEMENT.removeClass( CLASSES.input ).removeData( NAME ) setTimeout( function() { $ELEMENT.off( '.' + STATE.id ) }, 0) // Restore the element state ELEMENT.type = STATE.type ELEMENT.readOnly = false // Trigger the queued “stop” events. P.trigger( 'stop' ) // Reset the picker states. STATE.methods = {} STATE.start = false return P }, //stop /** * Open up the picker */ open: function( dontGiveFocus ) { // If it’s already open, do nothing. if ( STATE.open ) return P // Add the “active” class. $ELEMENT.addClass( CLASSES.active ) aria( ELEMENT, 'expanded', true ) // * A Firefox bug, when `html` has `overflow:hidden`, results in // killing transitions :(. So add the “opened” state on the next tick. // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 setTimeout( function() { // Add the “opened” class to the picker root. P.$root.addClass( CLASSES.opened ) aria( P.$root[0], 'hidden', false ) }, 0 ) // If we have to give focus, bind the element and doc events. if ( dontGiveFocus !== false ) { // Set it as open. STATE.open = true // Prevent the page from scrolling. if ( IS_DEFAULT_THEME ) { $html. css( 'overflow', 'hidden' ). css( 'padding-right', '+=' + getScrollbarWidth() ) } // Pass focus to the root element’s jQuery object. focusPickerOnceOpened() // Bind the document events. $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) { var target = event.target // If the target of the event is not the element, close the picker picker. // * Don’t worry about clicks or focusins on the root because those don’t bubble up. // Also, for Firefox, a click on an `option` element bubbles up directly // to the doc. So make sure the target wasn't the doc. // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling, // which causes the picker to unexpectedly close when right-clicking it. So make // sure the event wasn’t a right-click. if ( target != ELEMENT && target != document && event.which != 3 ) { // If the target was the holder that covers the screen, // keep the element focused to maintain tabindex. P.close( target === P.$holder[0] ) } }).on( 'keydown.' + STATE.id, function( event ) { var // Get the keycode. keycode = event.keyCode, // Translate that to a selection change. keycodeToMove = P.component.key[ keycode ], // Grab the target. target = event.target // On escape, close the picker and give focus. if ( keycode == 27 ) { P.close( true ) } // Check if there is a key movement or “enter” keypress on the element. else if ( target == P.$holder[0] && ( keycodeToMove || keycode == 13 ) ) { // Prevent the default action to stop page movement. event.preventDefault() // Trigger the key movement action. if ( keycodeToMove ) { PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] ) } // On “enter”, if the highlighted item isn’t disabled, set the value and close. else if ( !P.$root.find( '.' + CLASSES.highlighted ).hasClass( CLASSES.disabled ) ) { P.set( 'select', P.component.item.highlight ) if ( SETTINGS.closeOnSelect ) { P.close( true ) } } } // If the target is within the root and “enter” is pressed, // prevent the default action and trigger a click on the target instead. else if ( $.contains( P.$root[0], target ) && keycode == 13 ) { event.preventDefault() target.click() } }) } // Trigger the queued “open” events. return P.trigger( 'open' ) }, //open /** * Close the picker */ close: function( giveFocus ) { // If we need to give focus, do it before changing states. if ( giveFocus ) { if ( SETTINGS.editable ) { ELEMENT.focus() } else { // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :| // The focus is triggered *after* the close has completed - causing it // to open again. So unbind and rebind the event at the next tick. P.$holder.off( 'focus.toOpen' ).focus() setTimeout( function() { P.$holder.on( 'focus.toOpen', handleFocusToOpenEvent ) }, 0 ) } } // Remove the “active” class. $ELEMENT.removeClass( CLASSES.active ) aria( ELEMENT, 'expanded', false ) // * A Firefox bug, when `html` has `overflow:hidden`, results in // killing transitions :(. So remove the “opened” state on the next tick. // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 setTimeout( function() { // Remove the “opened” and “focused” class from the picker root. P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused ) aria( P.$root[0], 'hidden', true ) }, 0 ) // If it’s already closed, do nothing more. if ( !STATE.open ) return P // Set it as closed. STATE.open = false // Allow the page to scroll. if ( IS_DEFAULT_THEME ) { $html. css( 'overflow', '' ). css( 'padding-right', '-=' + getScrollbarWidth() ) } // Unbind the document events. $document.off( '.' + STATE.id ) // Trigger the queued “close” events. return P.trigger( 'close' ) }, //close /** * Clear the values */ clear: function( options ) { return P.set( 'clear', null, options ) }, //clear /** * Set something */ set: function( thing, value, options ) { var thingItem, thingValue, thingIsObject = $.isPlainObject( thing ), thingObject = thingIsObject ? thing : {} // Make sure we have usable options. options = thingIsObject && $.isPlainObject( value ) ? value : options || {} if ( thing ) { // If the thing isn’t an object, make it one. if ( !thingIsObject ) { thingObject[ thing ] = value } // Go through the things of items to set. for ( thingItem in thingObject ) { // Grab the value of the thing. thingValue = thingObject[ thingItem ] // First, if the item exists and there’s a value, set it. if ( thingItem in P.component.item ) { if ( thingValue === undefined ) thingValue = null P.component.set( thingItem, thingValue, options ) } // Then, check to update the element value and broadcast a change. if ( thingItem == 'select' || thingItem == 'clear' ) { $ELEMENT. val( thingItem == 'clear' ? '' : P.get( thingItem, SETTINGS.format ) ). trigger( 'change' ) } } // Render a new picker. P.render() } // When the method isn’t muted, trigger queued “set” events and pass the `thingObject`. return options.muted ? P : P.trigger( 'set', thingObject ) }, //set /** * Get something */ get: function( thing, format ) { // Make sure there’s something to get. thing = thing || 'value' // If a picker state exists, return that. if ( STATE[ thing ] != null ) { return STATE[ thing ] } // Return the submission value, if that. if ( thing == 'valueSubmit' ) { if ( P._hidden ) { return P._hidden.value } thing = 'value' } // Return the value, if that. if ( thing == 'value' ) { return ELEMENT.value } // Check if a component item exists, return that. if ( thing in P.component.item ) { if ( typeof format == 'string' ) { var thingValue = P.component.get( thing ) return thingValue ? PickerConstructor._.trigger( P.component.formats.toString, P.component, [ format, thingValue ] ) : '' } return P.component.get( thing ) } }, //get /** * Bind events on the things. */ on: function( thing, method, internal ) { var thingName, thingMethod, thingIsObject = $.isPlainObject( thing ), thingObject = thingIsObject ? thing : {} if ( thing ) { // If the thing isn’t an object, make it one. if ( !thingIsObject ) { thingObject[ thing ] = method } // Go through the things to bind to. for ( thingName in thingObject ) { // Grab the method of the thing. thingMethod = thingObject[ thingName ] // If it was an internal binding, prefix it. if ( internal ) { thingName = '_' + thingName } // Make sure the thing methods collection exists. STATE.methods[ thingName ] = STATE.methods[ thingName ] || [] // Add the method to the relative method collection. STATE.methods[ thingName ].push( thingMethod ) } } return P }, //on /** * Unbind events on the things. */ off: function() { var i, thingName, names = arguments; for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) { thingName = names[i] if ( thingName in STATE.methods ) { delete STATE.methods[thingName] } } return P }, /** * Fire off method events. */ trigger: function( name, data ) { var _trigger = function( name ) { var methodList = STATE.methods[ name ] if ( methodList ) { methodList.map( function( method ) { PickerConstructor._.trigger( method, P, [ data ] ) }) } } _trigger( '_' + name ) _trigger( name ) return P } //trigger } //PickerInstance.prototype /** * Wrap the picker holder components together. */ function createWrappedComponent() { // Create a picker wrapper holder return PickerConstructor._.node( 'div', // Create a picker wrapper node PickerConstructor._.node( 'div', // Create a picker frame PickerConstructor._.node( 'div', // Create a picker box node PickerConstructor._.node( 'div', // Create the components nodes. P.component.nodes( STATE.open ), // The picker box class CLASSES.box ), // Picker wrap class CLASSES.wrap ), // Picker frame class CLASSES.frame ), // Picker holder class CLASSES.holder, 'tabindex="-1"' ) //endreturn } //createWrappedComponent /** * Prepare the input element with all bindings. */ function prepareElement() { $ELEMENT. // Store the picker data by component name. data(NAME, P). // Add the “input” class name. addClass(CLASSES.input). // If there’s a `data-value`, update the value of the element. val( $ELEMENT.data('value') ? P.get('select', SETTINGS.format) : ELEMENT.value ) // Only bind keydown events if the element isn’t editable. if ( !SETTINGS.editable ) { $ELEMENT. // On focus/click, open the picker. on( 'focus.' + STATE.id + ' click.' + STATE.id, function(event) { event.preventDefault() P.open() }). // Handle keyboard event based on the picker being opened or not. on( 'keydown.' + STATE.id, handleKeydownEvent ) } // Update the aria attributes. aria(ELEMENT, { haspopup: true, expanded: false, readonly: false, owns: ELEMENT.id + '_root' }) } /** * Prepare the root picker element with all bindings. */ function prepareElementRoot() { aria( P.$root[0], 'hidden', true ) } /** * Prepare the holder picker element with all bindings. */ function prepareElementHolder() { P.$holder. on({ // For iOS8. keydown: handleKeydownEvent, 'focus.toOpen': handleFocusToOpenEvent, blur: function() { // Remove the “target” class. $ELEMENT.removeClass( CLASSES.target ) }, // When something within the holder is focused, stop from bubbling // to the doc and remove the “focused” state from the root. focusin: function( event ) { P.$root.removeClass( CLASSES.focused ) event.stopPropagation() }, // When something within the holder is clicked, stop it // from bubbling to the doc. 'mousedown click': function( event ) { var target = event.target // Make sure the target isn’t the root holder so it can bubble up. if ( target != P.$holder[0] ) { event.stopPropagation() // * For mousedown events, cancel the default action in order to // prevent cases where focus is shifted onto external elements // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120). // Also, for Firefox, don’t prevent action on the `option` element. if ( event.type == 'mousedown' && !$( target ).is( 'input, select, textarea, button, option' )) { event.preventDefault() // Re-focus onto the holder so that users can click away // from elements focused within the picker. P.$holder[0].focus() } } } }). // If there’s a click on an actionable element, carry out the actions. on( 'click', '[data-pick], [data-nav], [data-clear], [data-close]', function() { var $target = $( this ), targetData = $target.data(), targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ), // * For IE, non-focusable elements can be active elements as well // (http://stackoverflow.com/a/2684561). activeElement = getActiveElement() activeElement = activeElement && ( activeElement.type || activeElement.href ) // If it’s disabled or nothing inside is actively focused, re-focus the element. if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) { P.$holder[0].focus() } // If something is superficially changed, update the `highlight` based on the `nav`. if ( !targetDisabled && targetData.nav ) { P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } ) } // If something is picked, set `select` then close with focus. else if ( !targetDisabled && 'pick' in targetData ) { P.set( 'select', targetData.pick ) if ( SETTINGS.closeOnSelect ) { P.close( true ) } } // If a “clear” button is pressed, empty the values and close with focus. else if ( targetData.clear ) { P.clear() if ( SETTINGS.closeOnClear ) { P.close( true ) } } else if ( targetData.close ) { P.close( true ) } }) //P.$holder } /** * Prepare the hidden input element along with all bindings. */ function prepareElementHidden() { var name if ( SETTINGS.hiddenName === true ) { name = ELEMENT.name ELEMENT.name = '' } else { name = [ typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '', typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' ] name = name[0] + ELEMENT.name + name[1] } P._hidden = $( '' )[0] $ELEMENT. // If the value changes, update the hidden input with the correct format. on('change.' + STATE.id, function() { P._hidden.value = ELEMENT.value ? P.get('select', SETTINGS.formatSubmit) : '' }) } // Wait for transitions to end before focusing the holder. Otherwise, while // using the `container` option, the view jumps to the container. function focusPickerOnceOpened() { if (IS_DEFAULT_THEME && supportsTransitions) { P.$holder.find('.' + CLASSES.frame).one('transitionend', function() { P.$holder[0].focus() }) } else { P.$holder[0].focus() } } function handleFocusToOpenEvent(event) { // Stop the event from propagating to the doc. event.stopPropagation() // Add the “target” class. $ELEMENT.addClass( CLASSES.target ) // Add the “focused” class to the root. P.$root.addClass( CLASSES.focused ) // And then finally open the picker. P.open() } // For iOS8. function handleKeydownEvent( event ) { var keycode = event.keyCode, // Check if one of the delete keys was pressed. isKeycodeDelete = /^(8|46)$/.test(keycode) // For some reason IE clears the input value on “escape”. if ( keycode == 27 ) { P.close( true ) return false } // Check if `space` or `delete` was pressed or the picker is closed with a key movement. if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) { // Prevent it from moving the page and bubbling to doc. event.preventDefault() event.stopPropagation() // If `delete` was pressed, clear the values and close the picker. // Otherwise open the picker. if ( isKeycodeDelete ) { P.clear().close() } else { P.open() } } } // Return a new picker instance. return new PickerInstance() } //PickerConstructor /** * The default classes and prefix to use for the HTML classes. */ PickerConstructor.klasses = function( prefix ) { prefix = prefix || 'picker' return { picker: prefix, opened: prefix + '--opened', focused: prefix + '--focused', input: prefix + '__input', active: prefix + '__input--active', target: prefix + '__input--target', holder: prefix + '__holder', frame: prefix + '__frame', wrap: prefix + '__wrap', box: prefix + '__box' } } //PickerConstructor.klasses /** * Check if the default theme is being used. */ function isUsingDefaultTheme( element ) { var theme, prop = 'position' // For IE. if ( element.currentStyle ) { theme = element.currentStyle[prop] } // For normal browsers. else if ( window.getComputedStyle ) { theme = getComputedStyle( element )[prop] } return theme == 'fixed' } /** * Get the width of the browser’s scrollbar. * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js */ function getScrollbarWidth() { if ( $html.height() <= $window.height() ) { return 0 } var $outer = $( '
' ). appendTo( 'body' ) // Get the width without scrollbars. var widthWithoutScroll = $outer[0].offsetWidth // Force adding scrollbars. $outer.css( 'overflow', 'scroll' ) // Add the inner div. var $inner = $( '
' ).appendTo( $outer ) // Get the width with scrollbars. var widthWithScroll = $inner[0].offsetWidth // Remove the divs. $outer.remove() // Return the difference between the widths. return widthWithoutScroll - widthWithScroll } /** * PickerConstructor helper methods. */ PickerConstructor._ = { /** * Create a group of nodes. Expects: * ` { min: {Integer}, max: {Integer}, i: {Integer}, node: {String}, item: {Function} } * ` */ group: function( groupObject ) { var // Scope for the looped object loopObjectScope, // Create the nodes list nodesList = '', // The counter starts from the `min` counter = PickerConstructor._.trigger( groupObject.min, groupObject ) // Loop from the `min` to `max`, incrementing by `i` for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) { // Trigger the `item` function within scope of the object loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] ) // Splice the subgroup and create nodes out of the sub nodes nodesList += PickerConstructor._.node( groupObject.node, loopObjectScope[ 0 ], // the node loopObjectScope[ 1 ], // the classes loopObjectScope[ 2 ] // the attributes ) } // Return the list of nodes return nodesList }, //group /** * Create a dom node string */ node: function( wrapper, item, klass, attribute ) { // If the item is false-y, just return an empty string if ( !item ) return '' // If the item is an array, do a join item = $.isArray( item ) ? item.join( '' ) : item // Check for the class klass = klass ? ' class="' + klass + '"' : '' // Check for any attributes attribute = attribute ? ' ' + attribute : '' // Return the wrapped item return '<' + wrapper + klass + attribute + '>' + item + '' }, //node /** * Lead numbers below 10 with a zero. */ lead: function( number ) { return ( number < 10 ? '0': '' ) + number }, /** * Trigger a function otherwise return the value. */ trigger: function( callback, scope, args ) { return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback }, /** * If the second character is a digit, length is 2 otherwise 1. */ digits: function( string ) { return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1 }, /** * Tell if something is a date object. */ isDate: function( value ) { return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() ) }, /** * Tell if something is an integer. */ isInteger: function( value ) { return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0 }, /** * Create ARIA attribute strings. */ ariaAttr: ariaAttr } //PickerConstructor._ /** * Extend the picker with a component and defaults. */ PickerConstructor.extend = function( name, Component ) { // Extend jQuery. $.fn[ name ] = function( options, action ) { // Grab the component data. var componentData = this.data( name ) // If the picker is requested, return the data object. if ( options == 'picker' ) { return componentData } // If the component data exists and `options` is a string, carry out the action. if ( componentData && typeof options == 'string' ) { return PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] ) } // Otherwise go through each matched element and if the component // doesn’t exist, create a new picker using `this` element // and merging the defaults and options with a deep copy. return this.each( function() { var $this = $( this ) if ( !$this.data( name ) ) { new PickerConstructor( this, name, Component, options ) } }) } // Set the defaults. $.fn[ name ].defaults = Component.defaults } //PickerConstructor.extend function aria(element, attribute, value) { if ( $.isPlainObject(attribute) ) { for ( var key in attribute ) { ariaSet(element, key, attribute[key]) } } else { ariaSet(element, attribute, value) } } function ariaSet(element, attribute, value) { element.setAttribute( (attribute == 'role' ? '' : 'aria-') + attribute, value ) } function ariaAttr(attribute, data) { if ( !$.isPlainObject(attribute) ) { attribute = { attribute: data } } data = '' for ( var key in attribute ) { var attr = (key == 'role' ? '' : 'aria-') + key, attrVal = attribute[key] data += attrVal == null ? '' : attr + '="' + attribute[key] + '"' } return data } // IE8 bug throws an error for activeElements within iframes. function getActiveElement() { try { return document.activeElement } catch ( err ) { } } // Expose the picker constructor. return PickerConstructor })); /*! * Date picker for pickadate.js v3.5.6 * http://amsul.github.io/pickadate.js/date.htm */ (function ( factory ) { // AMD. if ( typeof define == 'function' && define.amd ) define( ['picker', 'jquery'], factory ) // Node.js/browserify. else if ( typeof exports == 'object' ) module.exports = factory( require('./picker.js'), require('jquery') ) // Browser globals. else factory( Picker, jQuery ) }(function( Picker, $ ) { /** * Globals and constants */ var DAYS_IN_WEEK = 7, WEEKS_IN_CALENDAR = 6, _ = Picker._ /** * The date picker constructor */ function DatePicker( picker, settings ) { var calendar = this, element = picker.$node[ 0 ], elementValue = element.value, elementDataValue = picker.$node.data( 'value' ), valueString = elementDataValue || elementValue, formatString = elementDataValue ? settings.formatSubmit : settings.format, isRTL = function() { return element.currentStyle ? // For IE. element.currentStyle.direction == 'rtl' : // For normal browsers. getComputedStyle( picker.$root[0] ).direction == 'rtl' } calendar.settings = settings calendar.$node = picker.$node // The queue of methods that will be used to build item objects. calendar.queue = { min: 'measure create', max: 'measure create', now: 'now create', select: 'parse create validate', highlight: 'parse navigate create validate', view: 'parse create validate viewset', disable: 'deactivate', enable: 'activate' } // The component's item object. calendar.item = {} calendar.item.clear = null calendar.item.disable = ( settings.disable || [] ).slice( 0 ) calendar.item.enable = -(function( collectionDisabled ) { return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1 })( calendar.item.disable ) calendar. set( 'min', settings.min ). set( 'max', settings.max ). set( 'now' ) // When there’s a value, set the `select`, which in turn // also sets the `highlight` and `view`. if ( valueString ) { calendar.set( 'select', valueString, { format: formatString, defaultValue: true }) } // If there’s no value, default to highlighting “today”. else { calendar. set( 'select', null ). set( 'highlight', calendar.item.now ) } // The keycode to movement mapping. calendar.key = { 40: 7, // Down 38: -7, // Up 39: function() { return isRTL() ? -1 : 1 }, // Right 37: function() { return isRTL() ? 1 : -1 }, // Left go: function( timeChange ) { var highlightedObject = calendar.item.highlight, targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange ) calendar.set( 'highlight', targetDate, { interval: timeChange } ) this.render() } } // Bind some picker events. picker. on( 'render', function() { picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() { var value = this.value if ( value ) { picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] ) picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' ) } }) picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() { var value = this.value if ( value ) { picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] ) picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' ) } }) }, 1 ). on( 'open', function() { var includeToday = '' if ( calendar.disabled( calendar.get('now') ) ) { includeToday = ':not(.' + settings.klass.buttonToday + ')' } picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false ) }, 1 ). on( 'close', function() { picker.$root.find( 'button, select' ).attr( 'disabled', true ) }, 1 ) } //DatePicker /** * Set a datepicker item object. */ DatePicker.prototype.set = function( type, value, options ) { var calendar = this, calendarItem = calendar.item // If the value is `null` just set it immediately. if ( value === null ) { if ( type == 'clear' ) type = 'select' calendarItem[ type ] = value return calendar } // Otherwise go through the queue of methods, and invoke the functions. // Update this as the time unit, and set the final value as this item. // * In the case of `enable`, keep the queue but set `disable` instead. // And in the case of `flip`, keep the queue but set `enable` instead. calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) { value = calendar[ method ]( type, value, options ) return value }).pop() // Check if we need to cascade through more updates. if ( type == 'select' ) { calendar.set( 'highlight', calendarItem.select, options ) } else if ( type == 'highlight' ) { calendar.set( 'view', calendarItem.highlight, options ) } else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) { if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) { calendar.set( 'select', calendarItem.select, options ) } if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) { calendar.set( 'highlight', calendarItem.highlight, options ) } } return calendar } //DatePicker.prototype.set /** * Get a datepicker item object. */ DatePicker.prototype.get = function( type ) { return this.item[ type ] } //DatePicker.prototype.get /** * Create a picker date object. */ DatePicker.prototype.create = function( type, value, options ) { var isInfiniteValue, calendar = this // If there’s no value, use the type as the value. value = value === undefined ? type : value // If it’s infinity, update the value. if ( value == -Infinity || value == Infinity ) { isInfiniteValue = value } // If it’s an object, use the native date object. else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) { value = value.obj } // If it’s an array, convert it into a date and make sure // that it’s a valid date – otherwise default to today. else if ( $.isArray( value ) ) { value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] ) value = _.isDate( value ) ? value : calendar.create().obj } // If it’s a number or date object, make a normalized date. else if ( _.isInteger( value ) || _.isDate( value ) ) { value = calendar.normalize( new Date( value ), options ) } // If it’s a literal true or any other case, set it to now. else /*if ( value === true )*/ { value = calendar.now( type, value, options ) } // Return the compiled object. return { year: isInfiniteValue || value.getFullYear(), month: isInfiniteValue || value.getMonth(), date: isInfiniteValue || value.getDate(), day: isInfiniteValue || value.getDay(), obj: isInfiniteValue || value, pick: isInfiniteValue || value.getTime() } } //DatePicker.prototype.create /** * Create a range limit object using an array, date object, * literal “true”, or integer relative to another time. */ DatePicker.prototype.createRange = function( from, to ) { var calendar = this, createDate = function( date ) { if ( date === true || $.isArray( date ) || _.isDate( date ) ) { return calendar.create( date ) } return date } // Create objects if possible. if ( !_.isInteger( from ) ) { from = createDate( from ) } if ( !_.isInteger( to ) ) { to = createDate( to ) } // Create relative dates. if ( _.isInteger( from ) && $.isPlainObject( to ) ) { from = [ to.year, to.month, to.date + from ]; } else if ( _.isInteger( to ) && $.isPlainObject( from ) ) { to = [ from.year, from.month, from.date + to ]; } return { from: createDate( from ), to: createDate( to ) } } //DatePicker.prototype.createRange /** * Check if a date unit falls within a date range object. */ DatePicker.prototype.withinRange = function( range, dateUnit ) { range = this.createRange(range.from, range.to) return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick } /** * Check if two date range objects overlap. */ DatePicker.prototype.overlapRanges = function( one, two ) { var calendar = this // Convert the ranges into comparable dates. one = calendar.createRange( one.from, one.to ) two = calendar.createRange( two.from, two.to ) return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) || calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to ) } /** * Get the date today. */ DatePicker.prototype.now = function( type, value, options ) { value = new Date() if ( options && options.rel ) { value.setDate( value.getDate() + options.rel ) } return this.normalize( value, options ) } /** * Navigate to next/prev month. */ DatePicker.prototype.navigate = function( type, value, options ) { var targetDateObject, targetYear, targetMonth, targetDate, isTargetArray = $.isArray( value ), isTargetObject = $.isPlainObject( value ), viewsetObject = this.item.view/*, safety = 100*/ if ( isTargetArray || isTargetObject ) { if ( isTargetObject ) { targetYear = value.year targetMonth = value.month targetDate = value.date } else { targetYear = +value[0] targetMonth = +value[1] targetDate = +value[2] } // If we’re navigating months but the view is in a different // month, navigate to the view’s year and month. if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) { targetYear = viewsetObject.year targetMonth = viewsetObject.month } // Figure out the expected target year and month. targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 ) targetYear = targetDateObject.getFullYear() targetMonth = targetDateObject.getMonth() // If the month we’re going to doesn’t have enough days, // keep decreasing the date until we reach the month’s last date. while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) { targetDate -= 1 /*safety -= 1 if ( !safety ) { throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.' }*/ } value = [ targetYear, targetMonth, targetDate ] } return value } //DatePicker.prototype.navigate /** * Normalize a date by setting the hours to midnight. */ DatePicker.prototype.normalize = function( value/*, options*/ ) { value.setHours( 0, 0, 0, 0 ) return value } /** * Measure the range of dates. */ DatePicker.prototype.measure = function( type, value/*, options*/ ) { var calendar = this // If it’s anything false-y, remove the limits. if ( !value ) { value = type == 'min' ? -Infinity : Infinity } // If it’s a string, parse it. else if ( typeof value == 'string' ) { value = calendar.parse( type, value ) } // If it's an integer, get a date relative to today. else if ( _.isInteger( value ) ) { value = calendar.now( type, value, { rel: value } ) } return value } ///DatePicker.prototype.measure /** * Create a viewset object based on navigation. */ DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) { return this.create([ dateObject.year, dateObject.month, 1 ]) } /** * Validate a date as enabled and shift if needed. */ DatePicker.prototype.validate = function( type, dateObject, options ) { var calendar = this, // Keep a reference to the original date. originalDateObject = dateObject, // Make sure we have an interval. interval = options && options.interval ? options.interval : 1, // Check if the calendar enabled dates are inverted. isFlippedBase = calendar.item.enable === -1, // Check if we have any enabled dates after/before now. hasEnabledBeforeTarget, hasEnabledAfterTarget, // The min & max limits. minLimitObject = calendar.item.min, maxLimitObject = calendar.item.max, // Check if we’ve reached the limit during shifting. reachedMin, reachedMax, // Check if the calendar is inverted and at least one weekday is enabled. hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) { // If there’s a date, check where it is relative to the target. if ( $.isArray( value ) ) { var dateTime = calendar.create( value ).pick if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true } // Return only integers for enabled weekdays. return _.isInteger( value ) }).length/*, safety = 100*/ // Cases to validate for: // [1] Not inverted and date disabled. // [2] Inverted and some dates enabled. // [3] Not inverted and out of range. // // Cases to **not** validate for: // • Navigating months. // • Not inverted and date enabled. // • Inverted and all dates disabled. // • ..and anything else. if ( !options || (!options.nav && !options.defaultValue) ) if ( /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) || /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) || /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) ) ) { // When inverted, flip the direction if there aren’t any enabled weekdays // and there are no enabled dates in the direction of the interval. if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) { interval *= -1 } // Keep looping until we reach an enabled date. while ( /*safety &&*/ calendar.disabled( dateObject ) ) { /*safety -= 1 if ( !safety ) { throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.' }*/ // If we’ve looped into the next/prev month with a large interval, return to the original date and flatten the interval. if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) { dateObject = originalDateObject interval = interval > 0 ? 1 : -1 } // If we’ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit. if ( dateObject.pick <= minLimitObject.pick ) { reachedMin = true interval = 1 dateObject = calendar.create([ minLimitObject.year, minLimitObject.month, minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1) ]) } else if ( dateObject.pick >= maxLimitObject.pick ) { reachedMax = true interval = -1 dateObject = calendar.create([ maxLimitObject.year, maxLimitObject.month, maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1) ]) } // If we’ve reached both limits, just break out of the loop. if ( reachedMin && reachedMax ) { break } // Finally, create the shifted date using the interval and keep looping. dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ]) } } //endif // Return the date object settled on. return dateObject } //DatePicker.prototype.validate /** * Check if a date is disabled. */ DatePicker.prototype.disabled = function( dateToVerify ) { var calendar = this, // Filter through the disabled dates to check if this is one. isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) { // If the date is a number, match the weekday with 0index and `firstDay` check. if ( _.isInteger( dateToDisable ) ) { return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7 } // If it’s an array or a native JS date, create and match the exact date. if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) { return dateToVerify.pick === calendar.create( dateToDisable ).pick } // If it’s an object, match a date within the “from” and “to” range. if ( $.isPlainObject( dateToDisable ) ) { return calendar.withinRange( dateToDisable, dateToVerify ) } }) // If this date matches a disabled date, confirm it’s not inverted. isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) { return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' || $.isPlainObject( dateToDisable ) && dateToDisable.inverted }).length // Check the calendar “enabled” flag and respectively flip the // disabled state. Then also check if it’s beyond the min/max limits. return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || dateToVerify.pick < calendar.item.min.pick || dateToVerify.pick > calendar.item.max.pick } //DatePicker.prototype.disabled /** * Parse a string into a usable type. */ DatePicker.prototype.parse = function( type, value, options ) { var calendar = this, parsingObject = {} // If it’s already parsed, we’re good. if ( !value || typeof value != 'string' ) { return value } // We need a `.format` to parse the value with. if ( !( options && options.format ) ) { options = options || {} options.format = calendar.settings.format } // Convert the format into an array and then map through it. calendar.formats.toArray( options.format ).map( function( label ) { var // Grab the formatting label. formattingLabel = calendar.formats[ label ], // The format length is from the formatting label function or the // label length without the escaping exclamation (!) mark. formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length // If there's a format label, split the value up to the format length. // Then add it to the parsing object with appropriate label. if ( formattingLabel ) { parsingObject[ label ] = value.substr( 0, formatLength ) } // Update the value as the substring from format length to end. value = value.substr( formatLength ) }) // Compensate for month 0index. return [ parsingObject.yyyy || parsingObject.yy, +( parsingObject.mm || parsingObject.m ) - 1, parsingObject.dd || parsingObject.d ] } //DatePicker.prototype.parse /** * Various formats to display the object in. */ DatePicker.prototype.formats = (function() { // Return the length of the first word in a collection. function getWordLengthFromCollection( string, collection, dateObject ) { // Grab the first word from the string. // Regex pattern from http://stackoverflow.com/q/150033 var word = string.match( /[^\x00-\x7F]+|\w+/ )[ 0 ] // If there's no month index, add it to the date object if ( !dateObject.mm && !dateObject.m ) { dateObject.m = collection.indexOf( word ) + 1 } // Return the length of the word. return word.length } // Get the length of the first word in a string. function getFirstWordLength( string ) { return string.match( /\w+/ )[ 0 ].length } return { d: function( string, dateObject ) { // If there's string, then get the digits length. // Otherwise return the selected date. return string ? _.digits( string ) : dateObject.date }, dd: function( string, dateObject ) { // If there's a string, then the length is always 2. // Otherwise return the selected date with a leading zero. return string ? 2 : _.lead( dateObject.date ) }, ddd: function( string, dateObject ) { // If there's a string, then get the length of the first word. // Otherwise return the short selected weekday. return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ] }, dddd: function( string, dateObject ) { // If there's a string, then get the length of the first word. // Otherwise return the full selected weekday. return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ] }, m: function( string, dateObject ) { // If there's a string, then get the length of the digits // Otherwise return the selected month with 0index compensation. return string ? _.digits( string ) : dateObject.month + 1 }, mm: function( string, dateObject ) { // If there's a string, then the length is always 2. // Otherwise return the selected month with 0index and leading zero. return string ? 2 : _.lead( dateObject.month + 1 ) }, mmm: function( string, dateObject ) { var collection = this.settings.monthsShort // If there's a string, get length of the relevant month from the short // months collection. Otherwise return the selected month from that collection. return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] }, mmmm: function( string, dateObject ) { var collection = this.settings.monthsFull // If there's a string, get length of the relevant month from the full // months collection. Otherwise return the selected month from that collection. return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] }, yy: function( string, dateObject ) { // If there's a string, then the length is always 2. // Otherwise return the selected year by slicing out the first 2 digits. return string ? 2 : ( '' + dateObject.year ).slice( 2 ) }, yyyy: function( string, dateObject ) { // If there's a string, then the length is always 4. // Otherwise return the selected year. return string ? 4 : dateObject.year }, // Create an array by splitting the formatting string passed. toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) }, // Format an object into a string using the formatting options. toString: function ( formatString, itemObject ) { var calendar = this return calendar.formats.toArray( formatString ).map( function( label ) { return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' ) }).join( '' ) } } })() //DatePicker.prototype.formats /** * Check if two date units are the exact. */ DatePicker.prototype.isDateExact = function( one, two ) { var calendar = this // When we’re working with weekdays, do a direct comparison. if ( ( _.isInteger( one ) && _.isInteger( two ) ) || ( typeof one == 'boolean' && typeof two == 'boolean' ) ) { return one === two } // When we’re working with date representations, compare the “pick” value. if ( ( _.isDate( one ) || $.isArray( one ) ) && ( _.isDate( two ) || $.isArray( two ) ) ) { return calendar.create( one ).pick === calendar.create( two ).pick } // When we’re working with range objects, compare the “from” and “to”. if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to ) } return false } /** * Check if two date units overlap. */ DatePicker.prototype.isDateOverlap = function( one, two ) { var calendar = this, firstDay = calendar.settings.firstDay ? 1 : 0 // When we’re working with a weekday index, compare the days. if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) { one = one % 7 + firstDay return one === calendar.create( two ).day + 1 } if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) { two = two % 7 + firstDay return two === calendar.create( one ).day + 1 } // When we’re working with range objects, check if the ranges overlap. if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { return calendar.overlapRanges( one, two ) } return false } /** * Flip the “enabled” state. */ DatePicker.prototype.flipEnable = function(val) { var itemObject = this.item itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) } /** * Mark a collection of dates as “disabled”. */ DatePicker.prototype.deactivate = function( type, datesToDisable ) { var calendar = this, disabledItems = calendar.item.disable.slice(0) // If we’re flipping, that’s all we need to do. if ( datesToDisable == 'flip' ) { calendar.flipEnable() } else if ( datesToDisable === false ) { calendar.flipEnable(1) disabledItems = [] } else if ( datesToDisable === true ) { calendar.flipEnable(-1) disabledItems = [] } // Otherwise go through the dates to disable. else { datesToDisable.map(function( unitToDisable ) { var matchFound // When we have disabled items, check for matches. // If something is matched, immediately break out. for ( var index = 0; index < disabledItems.length; index += 1 ) { if ( calendar.isDateExact( unitToDisable, disabledItems[index] ) ) { matchFound = true break } } // If nothing was found, add the validated unit to the collection. if ( !matchFound ) { if ( _.isInteger( unitToDisable ) || _.isDate( unitToDisable ) || $.isArray( unitToDisable ) || ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to ) ) { disabledItems.push( unitToDisable ) } } }) } // Return the updated collection. return disabledItems } //DatePicker.prototype.deactivate /** * Mark a collection of dates as “enabled”. */ DatePicker.prototype.activate = function( type, datesToEnable ) { var calendar = this, disabledItems = calendar.item.disable, disabledItemsCount = disabledItems.length // If we’re flipping, that’s all we need to do. if ( datesToEnable == 'flip' ) { calendar.flipEnable() } else if ( datesToEnable === true ) { calendar.flipEnable(1) disabledItems = [] } else if ( datesToEnable === false ) { calendar.flipEnable(-1) disabledItems = [] } // Otherwise go through the disabled dates. else { datesToEnable.map(function( unitToEnable ) { var matchFound, disabledUnit, index, isExactRange // Go through the disabled items and try to find a match. for ( index = 0; index < disabledItemsCount; index += 1 ) { disabledUnit = disabledItems[index] // When an exact match is found, remove it from the collection. if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) { matchFound = disabledItems[index] = null isExactRange = true break } // When an overlapped match is found, add the “inverted” state to it. else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) { if ( $.isPlainObject( unitToEnable ) ) { unitToEnable.inverted = true matchFound = unitToEnable } else if ( $.isArray( unitToEnable ) ) { matchFound = unitToEnable if ( !matchFound[3] ) matchFound.push( 'inverted' ) } else if ( _.isDate( unitToEnable ) ) { matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ] } break } } // If a match was found, remove a previous duplicate entry. if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) { if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) { disabledItems[index] = null break } } // In the event that we’re dealing with an exact range of dates, // make sure there are no “inverted” dates because of it. if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) { if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) { disabledItems[index] = null break } } // If something is still matched, add it into the collection. if ( matchFound ) { disabledItems.push( matchFound ) } }) } // Return the updated collection. return disabledItems.filter(function( val ) { return val != null }) } //DatePicker.prototype.activate /** * Create a string for the nodes in the picker. */ DatePicker.prototype.nodes = function( isOpen ) { var calendar = this, settings = calendar.settings, calendarItem = calendar.item, nowObject = calendarItem.now, selectedObject = calendarItem.select, highlightedObject = calendarItem.highlight, viewsetObject = calendarItem.view, disabledCollection = calendarItem.disable, minLimitObject = calendarItem.min, maxLimitObject = calendarItem.max, // Create the calendar table head using a copy of weekday labels collection. // * We do a copy so we don't mutate the original array. tableHead = (function( collection, fullCollection ) { // If the first day should be Monday, move Sunday to the end. if ( settings.firstDay ) { collection.push( collection.shift() ) fullCollection.push( fullCollection.shift() ) } // Create and return the table head group. return _.node( 'thead', _.node( 'tr', _.group({ min: 0, max: DAYS_IN_WEEK - 1, i: 1, node: 'th', item: function( counter ) { return [ collection[ counter ], settings.klass.weekdays, 'scope=col title="' + fullCollection[ counter ] + '"' ] } }) ) ) //endreturn })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead // Create the nav for next/prev month. createMonthNav = function( next ) { // Otherwise, return the created month tag. return _.node( 'div', ' ', settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + ( // If the focused month is outside the range, disabled the button. ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) || ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ? ' ' + settings.klass.navDisabled : '' ), 'data-nav=' + ( next || -1 ) + ' ' + _.ariaAttr({ role: 'button', controls: calendar.$node[0].id + '_table' }) + ' ' + 'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"' ) //endreturn }, //createMonthNav // Create the month label. createMonthLabel = function() { var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull // If there are months to select, add a dropdown menu. if ( settings.selectMonths ) { return _.node( 'select', _.group({ min: 0, max: 11, i: 1, node: 'option', item: function( loopedMonth ) { return [ // The looped month and no classes. monthsCollection[ loopedMonth ], 0, // Set the value and selected index. 'value=' + loopedMonth + ( viewsetObject.month == loopedMonth ? ' selected' : '' ) + ( ( ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) || ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month ) ) ? ' disabled' : '' ) ] } }), settings.klass.selectMonth, ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 'title="' + settings.labelMonthSelect + '"' ) } // If there's a need for a month selector return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month ) }, //createMonthLabel // Create the year label. createYearLabel = function() { var focusedYear = viewsetObject.year, // If years selector is set to a literal "true", set it to 5. Otherwise // divide in half to get half before and half after focused year. numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 ) // If there are years to select, add a dropdown menu. if ( numberYears ) { var minYear = minLimitObject.year, maxYear = maxLimitObject.year, lowestYear = focusedYear - numberYears, highestYear = focusedYear + numberYears // If the min year is greater than the lowest year, increase the highest year // by the difference and set the lowest year to the min year. if ( minYear > lowestYear ) { highestYear += minYear - lowestYear lowestYear = minYear } // If the max year is less than the highest year, decrease the lowest year // by the lower of the two: available and needed years. Then set the // highest year to the max year. if ( maxYear < highestYear ) { var availableYears = lowestYear - minYear, neededYears = highestYear - maxYear lowestYear -= availableYears > neededYears ? neededYears : availableYears highestYear = maxYear } return _.node( 'select', _.group({ min: lowestYear, max: highestYear, i: 1, node: 'option', item: function( loopedYear ) { return [ // The looped year and no classes. loopedYear, 0, // Set the value and selected index. 'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' ) ] } }), settings.klass.selectYear, ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 'title="' + settings.labelYearSelect + '"' ) } // Otherwise just return the year focused return _.node( 'div', focusedYear, settings.klass.year ) } //createYearLabel // Create and return the entire calendar. return _.node( 'div', ( settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel() ) + createMonthNav() + createMonthNav( 1 ), settings.klass.header ) + _.node( 'table', tableHead + _.node( 'tbody', _.group({ min: 0, max: WEEKS_IN_CALENDAR - 1, i: 1, node: 'tr', item: function( rowCounter ) { // If Monday is the first day and the month starts on Sunday, shift the date back a week. var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0 return [ _.group({ min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index max: function() { return this.min + DAYS_IN_WEEK - 1 }, i: 1, node: 'td', item: function( targetDate ) { // Convert the time date from a relative date to a target date. targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ]) var isSelected = selectedObject && selectedObject.pick == targetDate.pick, isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick, isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick, formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] ) return [ _.node( 'div', targetDate.date, (function( klasses ) { // Add the `infocus` or `outfocus` classes based on month in view. klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus ) // Add the `today` class if needed. if ( nowObject.pick == targetDate.pick ) { klasses.push( settings.klass.now ) } // Add the `selected` class if something's selected and the time matches. if ( isSelected ) { klasses.push( settings.klass.selected ) } // Add the `highlighted` class if something's highlighted and the time matches. if ( isHighlighted ) { klasses.push( settings.klass.highlighted ) } // Add the `disabled` class if something's disabled and the object matches. if ( isDisabled ) { klasses.push( settings.klass.disabled ) } return klasses.join( ' ' ) })([ settings.klass.day ]), 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({ role: 'gridcell', label: formattedDate, selected: isSelected && calendar.$node.val() === formattedDate ? true : null, activedescendant: isHighlighted ? true : null, disabled: isDisabled ? true : null }) ), '', _.ariaAttr({ role: 'presentation' }) ] //endreturn } }) ] //endreturn } }) ), settings.klass.table, 'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({ role: 'grid', controls: calendar.$node[0].id, readonly: true }) ) + // * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”. _.node( 'div', _.node( 'button', settings.today, settings.klass.buttonToday, 'type=button data-pick=' + nowObject.pick + ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id }) ) + _.node( 'button', settings.clear, settings.klass.buttonClear, 'type=button data-clear=1' + ( isOpen ? '' : ' disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id }) ) + _.node('button', settings.close, settings.klass.buttonClose, 'type=button data-close=true ' + ( isOpen ? '' : ' disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id }) ), settings.klass.footer ) //endreturn } //DatePicker.prototype.nodes /** * The date picker defaults. */ DatePicker.defaults = (function( prefix ) { return { // The title label to use for the month nav buttons labelMonthNext: 'Next month', labelMonthPrev: 'Previous month', // The title label to use for the dropdown selectors labelMonthSelect: 'Select a month', labelYearSelect: 'Select a year', // Months and weekdays monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], // Today and clear today: 'Today', clear: 'Clear', close: 'Close', // Picker close behavior closeOnSelect: true, closeOnClear: true, // The format to show on the `input` element format: 'd mmmm, yyyy', // Classes klass: { table: prefix + 'table', header: prefix + 'header', navPrev: prefix + 'nav--prev', navNext: prefix + 'nav--next', navDisabled: prefix + 'nav--disabled', month: prefix + 'month', year: prefix + 'year', selectMonth: prefix + 'select--month', selectYear: prefix + 'select--year', weekdays: prefix + 'weekday', day: prefix + 'day', disabled: prefix + 'day--disabled', selected: prefix + 'day--selected', highlighted: prefix + 'day--highlighted', now: prefix + 'day--today', infocus: prefix + 'day--infocus', outfocus: prefix + 'day--outfocus', footer: prefix + 'footer', buttonClear: prefix + 'button--clear', buttonToday: prefix + 'button--today', buttonClose: prefix + 'button--close' } } })( Picker.klasses().picker + '__' ) /** * Extend the picker to add the date picker. */ Picker.extend( 'pickadate', DatePicker ) })); var $ = jQuery; $('#fileupload').fileupload({ dataType: 'json', add: function (e, data) { $('.submit-form').off('click').on('click', function(e) { e.preventDefault(); if ( doValidation() ) { var response = grecaptcha.getResponse(); if ( response.length === 0 ) { showError('Please confirm you are a human to continue.'); } else if (fileValidation(data.files)) { $('.tbc_message.error').hide(); $('.loading').show(); $.each(data.files, function (index, file) { console.log('/' + file.name.replace(/ /g,'-')); console.log('done'); console.log(fd); var fd = new FormData(); fd.append('file',file); fd.append('action', 'file_uplod'); $.ajax({ type : "post", url: tbc_ajax_url, data:fd, contentType: false, processData: false, success: function(response) { if(response == "success"){ $('.loading').hide(); $('#file_form').submit(); } if(response == "error"){ showError('Oops something happened, please try again.'); } } }); }); } else { showError('Invalid file extension. Please try again.'); } } else { showError('Please make sure all required fields are completed and try again.'); } }); } }).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled'); function doValidation() { var valid_submission = true; $('.tbcform input.required, .tbcform select.required').each(function(index, value) { if ( $(this).hasClass('email') ) { if ( $(this).val() === '' ) { valid_submission = false; } else { var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; if ( ! regex.test($(this).val()) ) { valid_submission = false; } } } else { if ( $(this).val() === '' ) { valid_submission = false; } } }); return valid_submission; } function fileValidation(files) { var valid_submission = true; var allowedExtensions = /(\.jpg|\.jpeg|\.png|\.pdf|\.zip|\.tif|\.ai|\.idn|\.eps)$/i; $.each(files, function (index, file) { if(!allowedExtensions.exec(file.name)){ valid_submission = false; } }); return valid_submission; } function showError(message) { $('.loading').hide(); $('.tbc_message.error p').text(message).parent().show().focus(); $('html, body').animate({ scrollTop: $('.tbc_message').offset().top - 115 }, 800); } $('.datepicker').pickadate({ format: 'mmmm d, yyyy' });