/*! DataTables Editor v1.4.0 * * ©2012-2014 SpryMedia Ltd, all rights reserved. * License: editor.datatables.net/license */ /** * @summary DataTables Editor * @description Table editing library for DataTables * @version 1.4.0 * @file dataTables.editor.js * @author SpryMedia Ltd * @contact www.datatables.net/contact */ /*jslint evil: true, undef: true, browser: true */ /*globals jQuery,alert,console */ (function( window, document, undefined ) { var factory = function( $, DataTable ) { "use strict"; if ( ! DataTable || ! DataTable.versionCheck || ! DataTable.versionCheck('1.10') ) { throw 'Editor requires DataTables 1.10 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}. The options which must be set * are `ajaxUrl` and `domTable`. * @requires jQuery 1.7+ * @requires DataTables 1.9+ * @requires TableTools 2.1+ - note that TableTools is only required if you want to use * the row selection and button controls TableTools provides, but is not mandatory * for Editor. If used, the TableTools should be loaded before Editor. * * @example * // Basic initialisation - this example shows a table with 2 columns, each of which is editable * // as a text input and provides add, edit and delete buttons by making use of TableTools * // (Editor provides three buttons that extend the abilities of TableTools). * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajaxUrl": "php/index.php", * "domTable": "#example", * "fields": [ { * "label": "Browser:", * "name": "browser" * }, { * "label": "Rendering engine:", * "name": "engine" * }, { * "label": "Platform:", * "name": "platform" * }, { * "label": "Version:", * "name": "version" * }, { * "label": "CSS grade:", * "name": "grade" * } * ] * } ); * * $('#example').dataTable( { * "sDom": "Tfrtip", * "sAjaxSource": "php/index.php", * "aoColumns": [ * { "mData": "browser" }, * { "mData": "engine" }, * { "mData": "platform" }, * { "mData": "version", "sClass": "center" }, * { "mData": "grade", "sClass": "center" } * ], * "oTableTools": { * "sRowSelect": "multi", * "aButtons": [ * { "sExtends": "dte_create", "dte": editor }, * { "sExtends": "dte_edit", "dte": editor }, * { "sExtends": "dte_remove", "dte": editor } * ] * } * } ); * } ); */ 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; // Field class Editor.Field = function ( opts, classes, host ) { var that = this; opts = $.extend( true, {}, Editor.Field.defaults, opts ); 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 } ); // 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 '
'+ '
'+ '
'+opts.fieldInfo+'
'+ '
'+ '
'); var input = this._typeFn( 'create', opts ); if ( input !== null ) { _editor_el('input', template).prepend( input ); } else { template.css('display', "none"); } this.dom = $.extend( true, {}, Editor.Field.models.dom, { container: 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) } ); // 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 = { dataSrc: function () { return this.s.opts.data; }, valFromData: null, valToData: null, destroy: function () { // remove el this.dom.container.remove(); // field's own destroy method if there is one this._typeFn( 'destroy' ); return this; }, 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 ); }, 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 () { 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 ); }, name: function () { return this.s.opts.name; }, node: function () { return this.dom.container[0]; }, set: function ( val ) { return this._typeFn( 'set', val ); }, show: function ( animate ) { var el = this.dom.container; if ( animate === undefined ) { animate = true; } if ( this.s.host.display() && animate ) { el.slideDown(); } else { el.css( 'display', 'block' ); } return this; }, val: function ( val ) { return val === undefined ? this.get() : this.set( val ); }, _errorNode: function () { return this.dom.fieldError; }, _msg: function ( el, msg, fn ) { if ( el.parent().is(":visible") ) { el.html( msg ); if ( msg ) { el.slideDown( fn ); // fn can be undefined - so jQuery won't execute it } else { el.slideUp( fn ); } } else { // Not visible, so immediately set, or blank out the element el .html( msg || '' ) .css( 'display', msg ? 'block' : 'none' ); if ( fn ) { fn(); } } return this; }, _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 = { /** * * @type boolean * @default null */ "submitOnReturn": true, /** * * @type boolean * @default null */ "submitOnBlur": false, /** * * @type boolean * @default null */ "blurOnBackground": true, /** * * @type boolean * @default null */ "closeOnComplete": true, /** * * @type string * @default "close" */ "onEsc": "close", /** * * @type null|integer|string * @default 0 */ "focus": 0, /** * * @type string|boolean|array|object * @default null */ "buttons": true, /** * * @type string|boolean * @default null */ "title": true, /** * * @type string|boolean * @default null */ "message": true }; /* * 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; }, /* * 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.animate( { opacity: 1, top: 0 }, callback ); dom.background.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.blur(); } ); $('div.DTED_Lightbox_Content_Wrapper', dom.wrapper).bind( 'click.DTED_Lightbox', function (e) { if ( $(e.target).hasClass('DTED_Lightbox_Content_Wrapper') ) { self._dte.blur(); } } ); $(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.animate( { opacity: 0, top: self.conf.offsetAni }, function () { $(this).detach(); callback(); } ); dom.background.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 ); }, /* * 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.blur(); } ); $('div.DTED_Lightbox_Content_Wrapper', self._dom.wrapper).bind( 'click.DTED_Envelope', function (e) { if ( $(e.target).hasClass('DTED_Envelope_Content_Wrapper') ) { self._dte.blur(); } } ); $(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. */ Editor.prototype.add = function ( cfg ) { // Allow multiple fields to be added at the same time if ( $.isArray( cfg ) ) { for ( var i=0, iLen=cfg.length ; i'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
' ) .appendTo( 'body' ); var background = $( '
' ) .appendTo( 'body' ); // Add fields to the container this._displayReorder( fields ); 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( fields, 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; wrapper.css( { top: top, left: left } ); // 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(); } }; } $( '