/*! DataTables Editor v1.5.6 * * ©2012-2016 SpryMedia Ltd, all rights reserved. * License: editor.datatables.net/license */ /** * @summary DataTables Editor * @description Table editing library for DataTables * @version 1.5.6 * @file dataTables.editor.js * @author SpryMedia Ltd * @contact www.datatables.net/contact */ /*jslint evil: true, undef: true, browser: true */ /*globals jQuery,alert,console */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = function (root, $) { if ( ! root ) { root = window; } if ( ! $ || ! $.fn.dataTable ) { $ = require('datatables.net')(root, $).$; } return factory( $, root, root.document ); }; } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; if ( ! DataTable || ! DataTable.versionCheck || ! DataTable.versionCheck('1.10.7') ) { throw 'Editor requires DataTables 1.10.7 or newer'; } /** * Editor is a plug-in for DataTables which * provides an interface for creating, reading, editing and deleting and entries * (a CRUD interface) in a DataTable. The documentation presented here is * primarily focused on presenting the API for Editor. For a full list of * features, examples and the server interface protocol, please refer to the Editor web-site. * * Note that in this documentation, for brevity, the `DataTable` refers to the * jQuery parameter `jQuery.fn.dataTable` through which it may be accessed. * Therefore, when creating a new Editor instance, use `jQuery.fn.Editor` as * shown in the examples below. * * @class * @param {object} [oInit={}] Configuration object for Editor. Options * are defined by {@link Editor.defaults}. * @requires jQuery 1.7+ * @requires DataTables 1.10+ */ var Editor = function ( opts ) { if ( ! this instanceof Editor ) { alert( "DataTables Editor must be initialised as a 'new' instance'" ); } this._constructor( opts ); }; // Export Editor as a DataTables property DataTable.Editor = Editor; $.fn.DataTable.Editor = Editor; // Internal methods /** * Get an Editor node based on the data-dte-e (element) attribute and return it * as a jQuery object. * @param {string} dis The data-dte-e attribute name to match for the element * @param {node} [ctx=document] The context for the search - recommended this * parameter is included for performance. * @returns {jQuery} jQuery object of found node(s). * @private */ var _editor_el = function ( dis, ctx ) { if ( ctx === undefined ) { ctx = document; } return $('*[data-dte-e="'+dis+'"]', ctx); }; /** @internal Counter for unique event namespaces in the inline control */ var __inlineCounter = 0; var _pluck = function ( a, prop ) { var out = []; $.each( a, function ( idx, el ) { out.push( el[ prop ] ); } ); return out; }; // Field class Editor.Field = function ( opts, classes, host ) { var that = this; var multiI18n = host.i18n.multi; opts = $.extend( true, {}, Editor.Field.defaults, opts ); if ( ! Editor.fieldTypes[ opts.type ] ) { throw "Error adding field - unknown field type "+opts.type; } this.s = $.extend( {}, Editor.Field.settings, { // has to be a shallow copy! type: Editor.fieldTypes[ opts.type ], name: opts.name, classes: classes, host: host, opts: opts, multiValue: false } ); // No id, so assign one to have the label reference work if ( ! opts.id ) { opts.id = 'DTE_Field_'+opts.name; } // Backwards compatibility if ( opts.dataProp ) { opts.data = opts.dataProp; } // If no `data` option is given, then we use the name from the field as the // data prop to read data for the field from DataTables if ( opts.data === '' ) { opts.data = opts.name; } // Get and set functions in the data object for the record var dtPrivateApi = DataTable.ext.oApi; this.valFromData = function ( d ) { // get val from data // wrapper to automatically pass `editor` as the type return dtPrivateApi._fnGetObjectDataFn( opts.data )( d, 'editor' ); }; this.valToData = dtPrivateApi._fnSetObjectDataFn( opts.data ); // set val to data // Field HTML structure var template = $( '
'+ ''+ '
'+ // Field specific HTML is added here if there is any '
'+ '
'+ multiI18n.title+ ''+ multiI18n.info+ ''+ '
'+ '
'+ multiI18n.restore+ '
'+ '
'+ '
'+ '
'+opts.fieldInfo+'
'+ '
'+ '
'); var input = this._typeFn( 'create', opts ); if ( input !== null ) { _editor_el('input-control', template).prepend( input ); } else { template.css('display', "none"); } this.dom = $.extend( true, {}, Editor.Field.models.dom, { container: template, inputControl: _editor_el('input-control', template), label: _editor_el('label', template), fieldInfo: _editor_el('msg-info', template), labelInfo: _editor_el('msg-label', template), fieldError: _editor_el('msg-error', template), fieldMessage: _editor_el('msg-message', template), multi: _editor_el('multi-value', template), multiReturn: _editor_el('msg-multi', template), multiInfo: _editor_el('multi-info', template) } ); // On click - set a common value for the field this.dom.multi.on( 'click', function () { that.val(''); } ); this.dom.multiReturn.on( 'click', function () { that.s.multiValue = true; that._multiValueCheck(); } ); // Field type extension methods - add a method to the field for the public // methods that each field type defines beyond the default ones that already // exist as part of this instance $.each( this.s.type, function ( name, fn ) { if ( typeof fn === 'function' && that[name] === undefined ) { that[ name ] = function () { var args = Array.prototype.slice.call( arguments ); args.unshift( name ); var ret = that._typeFn.apply( that, args ); // Return the given value if there is one, or the field instance // for chaining if there is no value return ret === undefined ? that : ret; }; } } ); }; Editor.Field.prototype = { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public */ def: function ( set ) { var opts = this.s.opts; if ( set === undefined ) { // Backwards compat var def = opts['default'] !== undefined ? opts['default'] : opts.def; return $.isFunction( def ) ? def() : def; } opts.def = set; return this; }, disable: function () { this._typeFn( 'disable' ); return this; }, displayed: function () { var container = this.dom.container; return container.parents('body').length && container.css('display') != 'none' ? true : false; }, enable: function () { this._typeFn( 'enable' ); return this; }, error: function ( msg, fn ) { var classes = this.s.classes; // Add or remove the error class if ( msg ) { this.dom.container.addClass( classes.error ); } else { this.dom.container.removeClass( classes.error ); } return this._msg( this.dom.fieldError, msg, fn ); }, isMultiValue: function () { return this.s.multiValue; }, inError: function () { return this.dom.container.hasClass( this.s.classes.error ); }, input: function () { return this.s.type.input ? this._typeFn( 'input' ) : $('input, select, textarea', this.dom.container); }, focus: function () { if ( this.s.type.focus ) { this._typeFn( 'focus' ); } else { $('input, select, textarea', this.dom.container).focus(); } return this; }, get: function () { // When multi-value a single get is undefined if ( this.isMultiValue() ) { return undefined; } var val = this._typeFn( 'get' ); return val !== undefined ? val : this.def(); }, hide: function ( animate ) { var el = this.dom.container; if ( animate === undefined ) { animate = true; } if ( this.s.host.display() && animate ) { el.slideUp(); } else { el.css( 'display', 'none' ); } return this; }, label: function ( str ) { var label = this.dom.label; if ( str === undefined ) { return label.html(); } label.html( str ); return this; }, message: function ( msg, fn ) { return this._msg( this.dom.fieldMessage, msg, fn ); }, // There is no `multiVal()` as its arguments could be ambiguous // id is an idSrc value _only_ multiGet: function ( id ) { var value; var multiValues = this.s.multiValues; var multiIds = this.s.multiIds; if ( id === undefined ) { // Get an object with the values for each item being edited value = {}; for ( var i=0 ; i') .replace(/</g, '<') .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, '\'') .replace(/ /g, '\n'); }; this.s.multiValue = false; var decode = this.s.opts.entityDecode; if ( decode === undefined || decode === true ) { if ( $.isArray( val ) ) { for ( var i=0, ien=val.length ; i 0 && val !== last ) { different = true; break; } last = val; } } if ( different && this.s.multiValue ) { // Different values this.dom.inputControl.css( { display: 'none' } ); this.dom.multi.css( { display: 'block' } ); } else { // All the same value this.dom.inputControl.css( { display: 'block' } ); this.dom.multi.css( { display: 'none' } ); if ( this.s.multiValue ) { this.val( last ); } } this.dom.multiReturn.css( { display: ids && ids.length > 1 && different && ! this.s.multiValue ? 'block' : 'none' } ); this.s.host._multiInfo(); return true; }, _typeFn: function ( name /*, ... */ ) { // Remove the name from the arguments list, so the rest can be passed // straight into the field type var args = Array.prototype.slice.call( arguments ); args.shift(); // Insert the options as the first parameter - all field type methods // take the field's configuration object as the first parameter args.unshift( this.s.opts ); var fn = this.s.type[ name ]; if ( fn ) { return fn.apply( this.s.host, args ); } } }; Editor.Field.models = {}; /** * Initialisation options that can be given to Editor.Field at initialisation * time. * @namespace */ Editor.Field.defaults = { /** * Class name to assign to the field's container element (in addition to the other * classes that Editor assigns by default). * @type string * @default Empty string */ "className": "", /** * The data property (`mData` in DataTables terminology) that is used to * read from and write to the table. If not given then it will take the same * value as the `name` that is given in the field object. Note that `data` * can be given as null, which will result in Editor not using a DataTables * row property for the value of the field for either getting or setting * data. * * In previous versions of Editor (1.2-) this was called `dataProp`. The old * name can still be used for backwards compatibility, but the new form is * preferred. * @type string * @default Empty string */ "data": "", /** * The default value for the field. Used when creating new rows (editing will * use the currently set value). If given as a function the function will be * executed and the returned value used as the default * * In Editor 1.2 and earlier this field was called `default` - however * `default` is a reserved word in Javascript, so it couldn't be used * unquoted. `default` will still work with Editor 1.3, but the new property * name of `def` is preferred. * @type string|function * @default Empty string */ "def": "", /** * Helpful information text about the field that is shown below the input control. * @type string * @default Empty string */ "fieldInfo": "", /** * The ID of the field. This is used by the `label` HTML tag as the "for" attribute * improved accessibility. Although this using this parameter is not mandatory, * it is a good idea to assign the ID to the DOM element that is the input for the * field (if this is applicable). * @type string * @default Calculated */ "id": "", /** * The label to display for the field input (i.e. the name that is visually * assigned to the field). * @type string * @default Empty string */ "label": "", /** * Helpful information text about the field that is shown below the field label. * @type string * @default Empty string */ "labelInfo": "", /** * The name for the field that is submitted to the server. This is the only * mandatory parameter in the field description object. * @type string * @default null */ "name": null, /** * The input control that is presented to the end user. The options available * are defined by {@link Editor.fieldTypes} and any extensions made * to that object. * @type string * @default text */ "type": "text" }; /** * * @namespace */ Editor.Field.models.settings = { type: null, name: null, classes: null, opts: null, host: null }; /** * * @namespace */ Editor.Field.models.dom = { container: null, label: null, labelInfo: null, fieldInfo: null, fieldError: null, fieldMessage: null }; /* * Models */ /** * Object models container, for the various models that DataTables has available * to it. These models define the objects that are used to hold the active state * and configuration of the table. * @namespace */ Editor.models = {}; /** * Editor makes very few assumptions about how its form will actually be * displayed to the end user (where in the DOM, interaction etc), instead * focusing on providing form interaction controls only. To actually display * a form in the browser we need to use a display controller, and then select * which one we want to use at initialisation time using the `display` * option. For example a display controller could display the form in a * lightbox (as the default display controller does), it could completely * empty the document and put only the form in place, ir could work with * DataTables to use `fnOpen` / `fnClose` to show the form in a "details" row * and so on. * * Editor has two built-in display controllers ('lightbox' and 'envelope'), * but others can readily be created and installed for use as plug-ins. When * creating a display controller plug-in you **must** implement the methods * in this control. Additionally when closing the display internally you * **must** trigger a `requestClose` event which Editor will listen * for and act upon (this allows Editor to ask the user if they are sure * they want to close the form, for example). * @namespace */ Editor.models.displayController = { /** * Initialisation method, called by Editor when itself, initialises. * @param {object} dte The DataTables Editor instance that has requested * the action - this allows access to the Editor API if required. * @returns {object} The object that Editor will use to run the 'open' * and 'close' methods against. If static methods are used then * just return the object that holds the init, open and close methods, * however, this allows the display to be created with a 'new' * instance of an object is the display controller calls for that. * @type function */ "init": function ( dte ) {}, /** * Display the form (add it to the visual display in the document) * @param {object} dte The DataTables Editor instance that has requested * the action - this allows access to the Editor API if required. * @param {element} append The DOM node that contains the form to be * displayed * @param {function} [fn] Callback function that is to be executed when * the form has been displayed. Note that this parameter is optional. */ "open": function ( dte, append, fn ) {}, /** * Hide the form (remove it form the visual display in the document) * @param {object} dte The DataTables Editor instance that has requested * the action - this allows access to the Editor API if required. * @param {function} [fn] Callback function that is to be executed when * the form has been hidden. Note that this parameter is optional. */ "close": function ( dte, fn ) {} }; /** * Model object for input types which are available to fields (assigned to * {@link Editor.fieldTypes}). Any plug-ins which add additional * input types to Editor **must** implement the methods in this object * (dummy functions are given in the model so they can be used as defaults * if extending this object). * * All functions in the model are executed in the Editor's instance scope, * so you have full access to the settings object and the API methods if * required. * @namespace * @example * // Add a simple text input (the 'text' type that is built into Editor * // does this, so you wouldn't implement this exactly as show, but it * // it is a good example. * * var Editor = $.fn.Editor; * * Editor.fieldTypes.myInput = $.extend( true, {}, Editor.models.type, { * "create": function ( conf ) { * // We store the 'input' element in the configuration object so * // we can easily access it again in future. * conf._input = document.createElement('input'); * conf._input.id = conf.id; * return conf._input; * }, * * "get": function ( conf ) { * return conf._input.value; * }, * * "set": function ( conf, val ) { * conf._input.value = val; * }, * * "enable": function ( conf ) { * conf._input.disabled = false; * }, * * "disable": function ( conf ) { * conf._input.disabled = true; * } * } ); */ Editor.models.fieldType = { /** * Create the field - this is called when the field is added to the form. * Note that this is called at initialisation time, or when the * {@link Editor#add} API method is called, not when the form is displayed. * If you need to know when the form is shown, you can use the API to listen * for the `open` event. * @param {object} conf The configuration object for the field in question: * {@link Editor.models.field}. * @returns {element|null} The input element (or a wrapping element if a more * complex input is required) or null if nothing is to be added to the * DOM for this input type. * @type function */ "create": function ( conf ) {}, /** * Get the value from the field * @param {object} conf The configuration object for the field in question: * {@link Editor.models.field}. * @returns {*} The value from the field - the exact value will depend on the * formatting required by the input type control. * @type function */ "get": function ( conf ) {}, /** * Set the value for a field * @param {object} conf The configuration object for the field in question: * {@link Editor.models.field}. * @param {*} val The value to set the field to - the exact value will * depend on the formatting required by the input type control. * @type function */ "set": function ( conf, val ) {}, /** * Enable the field - i.e. allow user interface * @param {object} conf The configuration object for the field in question: * {@link Editor.models.field}. * @type function */ "enable": function ( conf ) {}, /** * Disable the field - i.e. disallow user interface * @param {object} conf The configuration object for the field in question: * {@link Editor.models.field}. * @type function */ "disable": function ( conf ) {} }; /** * Settings object for Editor - this provides the state for each instance of * Editor and can be accessed through the instance's `s` property. Note that the * settings object is considered to be "private" and thus is liable to change * between versions. As such if you do read any of the setting parameters, * please keep this in mind when upgrading! * @namespace */ Editor.models.settings = { /** * URL to submit Ajax data to. * This is directly set by the initialisation parameter / default of the same name. * @type string * @default null */ "ajaxUrl": null, /** * Ajax submit function. * This is directly set by the initialisation parameter / default of the same name. * @type function * @default null */ "ajax": null, /** * Data source for get and set data actions. This allows Editor to perform * as an Editor for virtually any data source simply by defining additional * data sources. * @type object * @default null */ "dataSource": null, /** * DataTable selector, can be anything that the Api supports * This is directly set by the initialisation parameter / default of the same name. * @type string * @default null */ "domTable": null, /** * The initialisation object that was given by the user - stored for future reference. * This is directly set by the initialisation parameter / default of the same name. * @type string * @default null */ "opts": null, /** * The display controller object for the Form. * This is directly set by the initialisation parameter / default of the same name. * @type string * @default null */ "displayController": null, /** * The form fields - see {@link Editor.models.field} for details of the * objects held in this array. * @type object * @default null */ "fields": {}, /** * Field order - order that the fields will appear in on the form. Array of strings, * the names of the fields. * @type array * @default null */ "order": [], /** * The ID of the row being edited (set to -1 on create and remove actions) * @type string * @default null */ "id": -1, /** * Flag to indicate if the form is currently displayed (true) or not (false) * @type string * @default null */ "displayed": false, /** * Flag to indicate if the form is current in a processing state (true) or not (false) * @type string * @default null */ "processing": false, /** * Developer provided identifier for the elements to be edited (i.e. at * `dt-type row-selector` to select rows to edit or delete. * @type array * @default null */ "modifier": null, /** * The current form action - 'create', 'edit' or 'remove'. If no current action then * it is set to null. * @type string * @default null */ "action": null, /** * JSON property from which to read / write the row's ID property. * @type string * @default null */ "idSrc": null }; /** * Model of the buttons that can be used with the {@link Editor#buttons} * method for creating and displaying buttons (also the {@link Editor#button} * argument option for the {@link Editor#create}, {@link Editor#edit} and * {@link Editor#remove} methods). Although you don't need to extend this object, * it is available for reference to show the options available. * @namespace */ Editor.models.button = { /** * The text to put into the button. This can be any HTML string you wish as * it will be rendered as HTML (allowing images etc to be shown inside the * button). * @type string * @default null */ "label": null, /** * Callback function which the button is activated. For example for a 'submit' * button you would call the {@link Editor#submit} API method, while for a cancel button * you would call the {@link Editor#close} API method. Note that the function is executed * in the scope of the Editor instance, so you can call the Editor's API methods * using the `this` keyword. * @type function * @default null */ "fn": null, /** * The CSS class(es) to apply to the button which can be useful for styling buttons * which preform different functions each with a distinctive visual appearance. * @type string * @default null */ "className": null }; /** * This is really an internal namespace * * @namespace */ Editor.models.formOptions = { /** * Action to take when the return key is pressed when focused in a form * element. Cam be `submit` or `none`. Could also be `blur` or `close`, but * why would you ever want that. Replaces `submitOnReturn` from 1.4. * * @type string */ onReturn: 'submit', /** * Action to take on blur. Can be `close`, `submit` or `none`. Replaces * `submitOnBlur` from 1.4 * * @type string */ onBlur: 'close', /** * Action to take when the lightbox background is clicked - can be `close`, * `submit`, `blur` or `none`. Replaces `blurOnBackground` from 1.4 * * @type string */ onBackground: 'blur', /** * Close for at the end of the Ajax request. Can be `close` or `none`. * Replaces `closeOnComplete` from 1.4. * * @type string */ onComplete: 'close', /** * Action to take when the `esc` key is pressed when focused in the form - * can be `close`, `submit`, `blur` or `none` * * @type string */ onEsc: 'close', /** * Data to submit to the server when submitting a form. If an option is * selected that results in no data being submitted, the Ajax request will * not be made Can be `all`, `changed` or `allIfChanged`. This effects the * edit action only. * * @type string */ submit: 'all', /** * Field identifier to focus on * * @type null|integer|string */ focus: 0, /** * Buttons to show in the form * * @type string|boolean|array|object */ buttons: true, /** * Form title * * @type string|boolean */ title: true, /** * Form message * * @type string|boolean */ message: true, /** * DataTables redraw option * * @type string|boolean */ drawType: false }; /* * Display controllers */ /** * Display controllers. See {@link Editor.models.displayController} for * full information about the display controller options for Editor. The display * controllers given in this object can be utilised by specifying the * {@link Editor.defaults.display} option. * @namespace */ Editor.display = {}; (function(window, document, $, DataTable) { var self; Editor.display.lightbox = $.extend( true, {}, Editor.models.displayController, { /* * API methods */ "init": function ( dte ) { self._init(); return self; }, "open": function ( dte, append, callback ) { if ( self._shown ) { if ( callback ) { callback(); } return; } self._dte = dte; var content = self._dom.content; content.children().detach(); content .append( append ) .append( self._dom.close ); self._shown = true; self._show( callback ); }, "close": function ( dte, callback ) { if ( !self._shown ) { if ( callback ) { callback(); } return; } self._dte = dte; self._hide( callback ); self._shown = false; }, node: function ( dte ) { return self._dom.wrapper[0]; }, /* * Private methods */ "_init": function () { if ( self._ready ) { return; } var dom = self._dom; dom.content = $('div.DTED_Lightbox_Content', self._dom.wrapper); dom.wrapper.css( 'opacity', 0 ); dom.background.css( 'opacity', 0 ); }, "_show": function ( callback ) { var that = this; var dom = self._dom; // Mobiles have very poor position fixed abilities, so we need to know // when using mobile A media query isn't good enough if ( window.orientation !== undefined ) { $('body').addClass( 'DTED_Lightbox_Mobile' ); } // Adjust size for the content dom.content.css( 'height', 'auto' ); dom.wrapper.css( { top: -self.conf.offsetAni } ); $('body') .append( self._dom.background ) .append( self._dom.wrapper ); self._heightCalc(); dom.wrapper .stop() .animate( { opacity: 1, top: 0 }, callback ); dom.background .stop() .animate( { opacity: 1 } ); // Event handlers - assign on show (and unbind on hide) rather than init // since we might need to refer to different editor instances - 12563 dom.close.bind( 'click.DTED_Lightbox', function (e) { self._dte.close(); } ); dom.background.bind( 'click.DTED_Lightbox', function (e) { self._dte.background(); } ); $('div.DTED_Lightbox_Content_Wrapper', dom.wrapper).bind( 'click.DTED_Lightbox', function (e) { if ( $(e.target).hasClass('DTED_Lightbox_Content_Wrapper') ) { self._dte.background(); } } ); $(window).bind( 'resize.DTED_Lightbox', function () { self._heightCalc(); } ); self._scrollTop = $('body').scrollTop(); // For smaller screens we need to hide the other elements in the // document since iOS and Android both mess up display:fixed when // the virtual keyboard is shown if ( window.orientation !== undefined ) { var kids = $('body').children().not( dom.background ).not( dom.wrapper ); $('body').append( '
' ); $('div.DTED_Lightbox_Shown').append( kids ); } }, "_heightCalc": function () { // Set the max-height for the form content var dom = self._dom; var maxHeight = $(window).height() - (self.conf.windowPadding*2) - $('div.DTE_Header', dom.wrapper).outerHeight() - $('div.DTE_Footer', dom.wrapper).outerHeight(); $('div.DTE_Body_Content', dom.wrapper).css( 'maxHeight', maxHeight ); }, "_hide": function ( callback ) { var dom = self._dom; if ( !callback ) { callback = function () {}; } if ( window.orientation !== undefined ) { var show = $('div.DTED_Lightbox_Shown'); show.children().appendTo('body'); show.remove(); } // Restore scroll state $('body') .removeClass( 'DTED_Lightbox_Mobile' ) .scrollTop( self._scrollTop ); dom.wrapper .stop() .animate( { opacity: 0, top: self.conf.offsetAni }, function () { $(this).detach(); callback(); } ); dom.background .stop() .animate( { opacity: 0 }, function () { $(this).detach(); } ); // Event handlers dom.close.unbind( 'click.DTED_Lightbox' ); dom.background.unbind( 'click.DTED_Lightbox' ); $('div.DTED_Lightbox_Content_Wrapper', dom.wrapper).unbind( 'click.DTED_Lightbox' ); $(window).unbind( 'resize.DTED_Lightbox' ); }, /* * Private properties */ "_dte": null, "_ready": false, "_shown": false, "_dom": { "wrapper": $( '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
' ), "background": $( '
' ), "close": $( '
' ), "content": null } } ); self = Editor.display.lightbox; self.conf = { "offsetAni": 25, "windowPadding": 25 }; }(window, document, jQuery, jQuery.fn.dataTable)); (function(window, document, $, DataTable) { var self; Editor.display.envelope = $.extend( true, {}, Editor.models.displayController, { /* * API methods */ "init": function ( dte ) { self._dte = dte; self._init(); return self; }, "open": function ( dte, append, callback ) { self._dte = dte; $(self._dom.content).children().detach(); self._dom.content.appendChild( append ); self._dom.content.appendChild( self._dom.close ); self._show( callback ); }, "close": function ( dte, callback ) { self._dte = dte; self._hide( callback ); }, node: function ( dte ) { return self._dom.wrapper[0]; }, /* * Private methods */ "_init": function () { if ( self._ready ) { return; } self._dom.content = $('div.DTED_Envelope_Container', self._dom.wrapper)[0]; document.body.appendChild( self._dom.background ); document.body.appendChild( self._dom.wrapper ); // For IE6-8 we need to make it a block element to read the opacity... self._dom.background.style.visbility = 'hidden'; self._dom.background.style.display = 'block'; self._cssBackgroundOpacity = $(self._dom.background).css('opacity'); self._dom.background.style.display = 'none'; self._dom.background.style.visbility = 'visible'; }, "_show": function ( callback ) { var that = this; var formHeight; if ( !callback ) { callback = function () {}; } // Adjust size for the content self._dom.content.style.height = 'auto'; var style = self._dom.wrapper.style; style.opacity = 0; style.display = 'block'; var targetRow = self._findAttachRow(); var height = self._heightCalc(); var width = targetRow.offsetWidth; style.display = 'none'; style.opacity = 1; // Prep the display self._dom.wrapper.style.width = width+"px"; self._dom.wrapper.style.marginLeft = -(width/2)+"px"; self._dom.wrapper.style.top = ($(targetRow).offset().top + targetRow.offsetHeight)+"px"; self._dom.content.style.top = ((-1 * height) - 20)+"px"; // Start animating in the background self._dom.background.style.opacity = 0; self._dom.background.style.display = 'block'; $(self._dom.background).animate( { 'opacity': self._cssBackgroundOpacity }, 'normal' ); // Animate in the display $(self._dom.wrapper).fadeIn(); // Slide the slider down to 'open' the view if ( self.conf.windowScroll ) { // Scroll the window so we can see the editor first $('html,body').animate( { "scrollTop": $(targetRow).offset().top + targetRow.offsetHeight - self.conf.windowPadding }, function () { // Now open the editor $(self._dom.content).animate( { "top": 0 }, 600, callback ); } ); } else { // Just open the editor without moving the document position $(self._dom.content).animate( { "top": 0 }, 600, callback ); } // Event handlers $(self._dom.close).bind( 'click.DTED_Envelope', function (e) { self._dte.close(); } ); $(self._dom.background).bind( 'click.DTED_Envelope', function (e) { self._dte.background(); } ); $('div.DTED_Lightbox_Content_Wrapper', self._dom.wrapper).bind( 'click.DTED_Envelope', function (e) { if ( $(e.target).hasClass('DTED_Envelope_Content_Wrapper') ) { self._dte.background(); } } ); $(window).bind( 'resize.DTED_Envelope', function () { self._heightCalc(); } ); }, "_heightCalc": function () { var formHeight; formHeight = self.conf.heightCalc ? self.conf.heightCalc( self._dom.wrapper ) : $(self._dom.content).children().height(); // Set the max-height for the form content var maxHeight = $(window).height() - (self.conf.windowPadding*2) - $('div.DTE_Header', self._dom.wrapper).outerHeight() - $('div.DTE_Footer', self._dom.wrapper).outerHeight(); $('div.DTE_Body_Content', self._dom.wrapper).css('maxHeight', maxHeight); return $(self._dte.dom.wrapper).outerHeight(); }, "_hide": function ( callback ) { if ( !callback ) { callback = function () {}; } $(self._dom.content).animate( { "top": -(self._dom.content.offsetHeight+50) }, 600, function () { $([self._dom.wrapper, self._dom.background]).fadeOut( 'normal', callback ); } ); // Event handlers $(self._dom.close).unbind( 'click.DTED_Lightbox' ); $(self._dom.background).unbind( 'click.DTED_Lightbox' ); $('div.DTED_Lightbox_Content_Wrapper', self._dom.wrapper).unbind( 'click.DTED_Lightbox' ); $(window).unbind( 'resize.DTED_Lightbox' ); }, "_findAttachRow": function () { var dt = $(self._dte.s.table).DataTable(); // Figure out where we want to put the form display if ( self.conf.attach === 'head' ) { return dt.table().header(); } else if ( self._dte.s.action === 'create' ) { return dt.table().header(); } else { return dt.row( self._dte.s.modifier ).node(); } }, /* * Private properties */ "_dte": null, "_ready": false, "_cssBackgroundOpacity": 1, // read from the CSS dynamically, but stored for future reference "_dom": { "wrapper": $( '
'+ '
'+ '
'+ '
'+ '
' )[0], "background": $( '
' )[0], "close": $( '
×
' )[0], "content": null } } ); // Assign to 'self' for easy referencing of our own object! self = Editor.display.envelope; // Configuration object - can be accessed globally using // $.fn.Editor.display.envelope.conf (!) self.conf = { "windowPadding": 50, "heightCalc": null, "attach": "row", "windowScroll": true }; }(window, document, jQuery, jQuery.fn.dataTable)); /* * Prototype includes */ /** * Add a new field to the from. This is the method that is called automatically when * fields are given in the initialisation objects as {@link Editor.defaults.fields}. * @memberOf Editor * @param {object|array} field The object that describes the field (the full * object is described by {@link Editor.model.field}. Note that multiple * fields can be given by passing in an array of field definitions. * @param {string} [after] Existing field to insert the new field after. This * can be `undefined` (insert at end), `null` (insert at start) or `string` * the field name to insert after. */ Editor.prototype.add = function ( cfg, after ) { // Allow multiple fields to be added at the same time if ( $.isArray( cfg ) ) { for ( var i=0, iLen=cfg.length ; i
' ); var container = $( '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
' ); if ( show ) { container.appendTo( 'body' ); background.appendTo( 'body' ); } var liner = container.children().eq(0); var table = liner.children(); var close = table.children(); liner.append( this.dom.formError ); table.prepend( this.dom.form ); if ( opts.message ) { liner.prepend( this.dom.formInfo ); } if ( opts.title ) { liner.prepend( this.dom.header ); } if ( opts.buttons ) { table.append( this.dom.buttons ); } var pair = $().add( container ).add( background ); this._closeReg( function ( submitComplete ) { pair.animate( { opacity: 0 }, function () { pair.detach(); $(window).off( 'resize.'+namespace ); // Clear error messages "offline" that._clearDynamicInfo(); } ); } ); // Close event handlers background.click( function () { that.blur(); } ); close.click( function () { that._close(); } ); this.bubblePosition(); pair.animate( { opacity: 1 } ); this._focus( this.s.includeFields, opts.focus ); this._postopen( 'bubble' ); return this; }; /** * Reposition the editing bubble (`bubble()`) when it is visible. This can be * used to update the bubble position if other elements on the page change * position. Editor will automatically call this method on window resize. * * @return {Editor} Editor instance, for chaining */ Editor.prototype.bubblePosition = function () { var wrapper = $('div.DTE_Bubble'), liner = $('div.DTE_Bubble_Liner'), nodes = this.s.bubbleNodes; // Average the node positions to insert the container var position = { top: 0, left: 0, right: 0, bottom: 0 }; $.each( nodes, function (i, node) { var pos = $(node).offset(); position.top += pos.top; position.left += pos.left; position.right += pos.left + node.offsetWidth; position.bottom += pos.top + node.offsetHeight; } ); position.top /= nodes.length; position.left /= nodes.length; position.right /= nodes.length; position.bottom /= nodes.length; var top = position.top, left = (position.left + position.right) / 2, width = liner.outerWidth(), visLeft = left - (width / 2), visRight = visLeft + width, docWidth = $(window).width(), padding = 15, classes = this.classes.bubble; wrapper.css( { top: top, left: left } ); // Correct for overflow from the top of the document by positioning below // the field if needed if ( liner.length && liner.offset().top < 0 ) { wrapper .css( 'top', position.bottom ) .addClass( 'below' ); } else { wrapper.removeClass( 'below' ); } // Attempt to correct for overflow to the right of the document if ( visRight+padding > docWidth ) { var diff = visRight - docWidth; // If left overflowing, that takes priority liner.css( 'left', visLeft < padding ? -(visLeft-padding) : -(diff+padding) ); } else { // Correct overflow to the left liner.css( 'left', visLeft < padding ? -(visLeft-padding) : 0 ); } return this; }; /** * Setup the buttons that will be shown in the footer of the form - calling this * method will replace any buttons which are currently shown in the form. * @param {array|object} buttons A single button definition to add to the form or * an array of objects with the button definitions to add more than one button. * The options for the button definitions are fully defined by the * {@link Editor.models.button} object. * @param {string} buttons.label The text to put into the button. This can be any * HTML string you wish as it will be rendered as HTML (allowing images etc to * be shown inside the button). * @param {function} [buttons.fn] Callback function which the button is activated. * For example for a 'submit' button you would call the {@link Editor#submit} method, * while for a cancel button you would call the {@link Editor#close} method. Note that * the function is executed in the scope of the Editor instance, so you can call * the Editor's API methods using the `this` keyword. * @param {string} [buttons.className] The CSS class(es) to apply to the button * which can be useful for styling buttons which preform different functions * each with a distinctive visual appearance. * @return {Editor} Editor instance, for chaining */ Editor.prototype.buttons = function ( buttons ) { var that = this; if ( buttons === '_basic' ) { // Special string to create a basic button - undocumented buttons = [ { label: this.i18n[ this.s.action ].submit, fn: function () { this.submit(); } } ]; } else if ( ! $.isArray( buttons ) ) { // Allow a single button to be passed in as an object with an array buttons = [ buttons ]; } $(this.dom.buttons).empty(); $.each( buttons, function ( i, btn ) { if ( typeof btn === 'string' ) { btn = { label: btn, fn: function () { this.submit(); } }; } $( ''+ '
'+ '
'+ ''+ ''+ '
'+ '
'+ ''+ ''+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
' ); conf._input = container; conf._enabled = true; _buttonText( conf ); if ( window.FileReader && conf.dragDrop !== false ) { container.find('div.drop span').text( conf.dragDropText || "Drag and drop a file here to upload" ); var dragDrop = container.find('div.drop'); dragDrop .on( 'drop', function (e) { if ( conf._enabled ) { Editor.upload( editor, conf, e.originalEvent.dataTransfer.files, _buttonText, dropCallback ); dragDrop.removeClass('over'); } return false; } ) .on( 'dragleave dragexit', function (e) { if ( conf._enabled ) { dragDrop.removeClass('over'); } return false; } ) .on( 'dragover', function (e) { if ( conf._enabled ) { dragDrop.addClass('over'); } return false; } ); // When an Editor is open with a file upload input there is a // reasonable chance that the user will miss the drop point when // dragging and dropping. Rather than loading the file in the browser, // we want nothing to happen, otherwise the form will be lost. editor .on( 'open', function () { $('body').on( 'dragover.DTE_Upload drop.DTE_Upload', function (e) { return false; } ); } ) .on( 'close', function () { $('body').off( 'dragover.DTE_Upload drop.DTE_Upload' ); } ); } else { container.addClass( 'noDrop' ); container.append( container.find('div.rendered') ); } container.find('div.clearValue button').on( 'click', function () { Editor.fieldTypes.upload.set.call( editor, conf, '' ); } ); container.find('input[type=file]').on('change', function () { Editor.upload( editor, conf, this.files, _buttonText, function (ids) { dropCallback.call( editor, ids ); // Clear the value so change will happen on the next file select, // even if it is the same file container.find('input[type=file]').val(''); } ); } ); return container; } // Typically a change event caused by the end user will be added to a queue that // the browser will handle when no other script is running. However, using // `$().trigger()` will cause it to happen immediately, so in order to simulate // the standard browser behaviour we use setTimeout. This also means that // `dependent()` and other change event listeners will trigger when the field // values have all been set, rather than as they are being set - 31594 function _triggerChange ( input ) { setTimeout( function () { input.trigger( 'change', {editor: true, editorSet: true} ); // editorSet legacy }, 0 ); } // A number of the fields in this file use the same get, set, enable and disable // methods (specifically the text based controls), so in order to reduce the code // size, we just define them once here in our own local base model for the field // types. var baseFieldType = $.extend( true, {}, Editor.models.fieldType, { get: function ( conf ) { return conf._input.val(); }, set: function ( conf, val ) { conf._input.val( val ); _triggerChange( conf._input ); }, enable: function ( conf ) { conf._input.prop( 'disabled', false ); }, disable: function ( conf ) { conf._input.prop( 'disabled', true ); } } ); fieldTypes.hidden = { create: function ( conf ) { conf._val = conf.value; return null; }, get: function ( conf ) { return conf._val; }, set: function ( conf, val ) { conf._val = val; } }; fieldTypes.readonly = $.extend( true, {}, baseFieldType, { create: function ( conf ) { conf._input = $('').attr( $.extend( { id: Editor.safeId( conf.id ), type: 'text', readonly: 'readonly' }, conf.attr || {} ) ); return conf._input[0]; } } ); fieldTypes.text = $.extend( true, {}, baseFieldType, { create: function ( conf ) { conf._input = $('').attr( $.extend( { id: Editor.safeId( conf.id ), type: 'text' }, conf.attr || {} ) ); return conf._input[0]; } } ); fieldTypes.password = $.extend( true, {}, baseFieldType, { create: function ( conf ) { conf._input = $('').attr( $.extend( { id: Editor.safeId( conf.id ), type: 'password' }, conf.attr || {} ) ); return conf._input[0]; } } ); fieldTypes.textarea = $.extend( true, {}, baseFieldType, { create: function ( conf ) { conf._input = $('