/** * Make table cell content editable. * * The `.uiRichtable()` method makes table cells editable, which are marked with the attribute data-ep-editable="true". * * The cells content must be wrapped in a HTML-Element as `span` or `div`. The `data-ep-attribute`-value * represents the name of the objects attribute on which the saveAction should be executed. The innerHTML will be * saved. * * If the edited values should be saved, the `Input` in the first cell in the following example must be * an `input`-element, containing the object id of the destinated object in the `value`-attribute. * First cell must have the class `Checkbox` and contain an `input` node of type `checkbox`. This `checkbox` will be * selected, once the surrounding table row is clicked. * <td class="Checkbox"><input type="checkbox" value="12345" /></td> * * ### Examples * Apply editable feature to a table. * * JavaScript: * * ep('#contentTable').uiRichtable({ * 'saveAction': 'Save', * 'region': jQuery.i18n.settings.region, * 'editIcon': true * }); * * HTML: * * <table id="contentTable" class="ContentList"> * <tr> * <td class="Checkbox">Input</td> * <td class="OneQuarter" data-ep-editable="true"><span data-ep-attribute="Name">Text</span></td> * <td class="OneQuarter AlignRight editable50" data-ep-editable="true"><div data-ep-attribute="Price" data-ep-additional="CurrencyID">199,95 €</div></td> * <td class="OneQuarter AlignRight editable40" data-ep-editable="true"><div data-ep-attribute="StockLevel">0</div></td> * </tr> * </table> * * * @class jQuery.ui.uiRichtable * @extends jQuery.widget * * @uses ep * @uses jQuery.ui.widget * @uses jQuery.event.special.load * @uses ep.fn.contextOrientation * @uses ep.ui.textedit * @uses ep.ui.validate * @uses ep.ui.tooltip * @since 6.15.0 */ /** * @cfg {String} saveAction Name of the SaveAction */ /** * @cfg {String} region region-string like de-DE */ /** * @cfg {String} currency currency-string representing the current currency */ /** * @cfg {Boolean} editIcon A boolean indication whether an edit icon should be shown. */ /** * @cfg {Boolean} alterIcon A boolean indicator whether the edit icon should always be shown on the right side (false) or depending by the horizontal alignment (true) */ /** * See `jQuery.ui.uiRichtable` for details. * * @param {Object} [options] A map of additional options pass to the method. * @param {String} saveAction Name of the SaveAction * @param {String} region region-string like de-DE * @param {String} currency currency-string representing the current currency * @param {Boolean/Integer} shrink false if no shrink required, otherwise shrink up to integer * @param {Boolean} editIcon A boolean indication whether an edit icon should be shown. * @param {Boolean} alterIcon A boolean indicator whether the edit icon should always be shown on the right side (false) or depending by the horizontal alignment (true) * * @method uiRichtable * @member jQuery * * @since 6.15.0 */ /* * @copyright © Copyright 2006-2012, epages GmbH, All Rights Reserved. * * @module ep.ui.richtable */ define("ep/ui/richtable", [ "jquery", "ep", "util/string", '$dict!ep/dict', "jquery/ui/widget", "jquery/event/special/load", "ep/ui/validate", "ep/ui/textedit", "ep/fn/contextorientation", "ep/ui/tooltip", "$ready!" ], function($, ep, str, dict){ /* * @dictionary ep.dict * * @translation {NOT_VALID} */ // helper methods var getRow = function( elem ){ return elem = elem.is(":checkbox") ? elem.closest("tr") : elem; }, getCheckbox = function( elem ){ return elem = elem.is(":checkbox") ? elem : elem.find("> td:first-child input[type=checkbox]"); }, toggleRow = function( elem, bool ){ var checkbox = getCheckbox(elem), row = getRow(elem); row.toggleClass("RowSelected", bool===undefined ? checkbox.is(":checked") : bool); }, toggleCheckbox = function( elem, bool ){ var checkbox = getCheckbox(elem); if (bool===undefined || bool !== checkbox.is(":checked")) { checkbox.trigger({ type: "click", originalTarget: checkbox[0] }); } }, translations = { NOT_VALID: dict.translate('NOT_VALID') }, // exclude from toggle row selections unity = (ep.config.unity !== undefined && ep.config.unity === true) ? true : false; $.widget("ui.uiRichtable", { STR_EDIT_ICON: '<i class="ep-richtable-edit-icon ' + ((unity) ? 'fa fa-lg fa-pencil' : 'Sprite ico_s_edit') + '"></i>', unity: unity, options: { // grossPrice: "c2", // format for net/gross prices editIcon: true, // indicator if a edit icon should be shown alterIcon: false, // flag if the edit icon should be shown always right (false) or depending by align (true) region: $.i18n.settings.region, // region string currency: $.i18n.settings.currency, // currency acronym saveAction: "Save", // save method shrink: false, // shrink headers with class 'ep-richtable-shrink-header' to provided min-length in this option noselect: [ // Selectors to prevent row selection "a", // Links and child elements "a *", ":input", // Input elements ".ep-uiInput-custom", // Custom UI input elements ".epEditWrapper", // Editable elements and editable elements ".epEditWrapper *" ] }, _create: function(){ var self = this, o = self.options, $shrinkElem, shrinkParentWidth, titleString, shrinkVar; if(self.element[0]===undefined){ return; } // get the currency format (default "c2": two decimal places) o.grossPrice = (o.grossPrice===undefined)? "c2" : o.grossPrice; self.element .addClass("RowSelection ep-uirichtable") // delegate checkbox for select all .on("change.uiRichtable", "thead > tr > *:first-child input[type=checkbox]", function(){ var isChecked = $(this).is(":checked"); self.element .find("> tbody > tr > td:first-child input[type=checkbox]") .each(function(){ toggleCheckbox( $(this), isChecked ); }); }) // delegate checkbox for row selection .on("change.uiRichtable", "tbody > tr > td:first-child input[type=checkbox]", function () { toggleRow($(this)); }) // delegate cell click .on("click.uiRichtable", "tbody > tr > td", function (event) { var target = $(event.originalTarget), cell = $(this), row = cell.parent(); // prevent row selection if target is an image if(/\b(select)\b/.test(target[0].tagName.toLowerCase())){ return; } // Inline editing if (cell.data("epEditable")) { self._handleEditable(row, cell, target); } // Check selectbox else if (target.length && !target.is(o.noselect.join(", "))) { toggleCheckbox(row.find("> td:first-child input[type=checkbox]")); } }) // delegate mouseenter editable cell for initalize .on("mouseenter", "tbody > tr > td[data-ep-editable]:not(.RowSelectionInputCell)", function(){ var o = self.options, cell = $(this).addClass("RowSelectionInputCell"), wrapper = cell.find("*[data-ep-attribute]"); if( wrapper.length > 0 ){ wrapper.addClass("epEditWrapper ClickIcon"); // if an edit icon should be shown if( o.editIcon ){ if( !o.alterIcon || cell.css("text-align") != "right" ){ wrapper .append(self.STR_EDIT_ICON) .data("epTextAlign", "left"); if( cell.css("text-align") == "right" ){ cell.addClass("ep-Textedit-right"); } } else{ wrapper .prepend(self.STR_EDIT_ICON) .data("epTextAlign", "right"); cell.addClass("ep-Textedit-right"); } } switch( wrapper.data("epAttribute").toLowerCase() ){ case "name": wrapper.css("display", "inline-block"); break; } } }); // register event to handle select fields in richtables (unity only) if (self.unity) { self.element // register handler for select fields .on("change.uiRichtable", "tbody > tr > td select.ep-richtable-select", function () { // $(this).data('epCorresponding'): array of objects // object: { // name: attribute name of Object, // selector: DOM element containing the value for the property 'name', // value: method to get the value (html, text, val) or fixed value (number, string) // } var $this = $(this), row = $this.closest('tr'), selectValue = $this.val(), data = {}, element, value; // if another element needs to be updated by the new select box value if ($this.data('epUpdate')) { $($this.data('epUpdate').selector).data($this.data('epUpdate').attribute, $this.val()); } // prepare xhr object only if valid data exist if (selectValue) { // value from select field data[$this.attr('name')] = selectValue; // loop over corresponding elements $.each($this.data('epCorresponding'), function (index, item) { element = $(item.selector); if (element && element[item.value]) { value = (typeof element[item.value] === "function") ? element[item.value]() : item.value; if (value || value === 0) { data[item.name] = value; } } }); // Ajax parameters data.ChangeAction = self.options.saveAction; data.ObjectID = getCheckbox(row).data('id') ? getCheckbox(row).data('id') : getCheckbox(row).val(); data.ViewAction = 'JSONViewResponse'; self._send(data); } }) } // add handling to adjust column sizes to their content if (self.unity && !isNaN(o.shrink)) { $("thead > tr .ep-richtable-shrink-header").each(function(index, elem) { $shrinkElem = $(elem); $shrinkElem.hide(); shrinkParentWidth = $shrinkElem.closest('td')[0].getBoundingClientRect().width; $shrinkElem.show(); titleString = $shrinkElem.text(); shrinkVar = titleString.length - 1; while ($shrinkElem.outerWidth() >= shrinkParentWidth && shrinkVar >= o.shrink) { $shrinkElem.text(str.shrink(titleString,shrinkVar)); shrinkVar--; } if ($shrinkElem.attr('title') === undefined) { $shrinkElem.attr('title', titleString); } }); } }, _handleEditable: function( row, cell, target ){ var self = this, o = self.options, wrapper = ep(".epEditWrapper", cell), // element which should be editable oValidate, // object/hash for the uiValidate widget oTextEditOptions; // object/hash for the uiTextedit widget // if the element is not editable yet if( wrapper.data('epUiTextedit') === undefined ){ // mark that the element is just created wrapper.prop("justCreated", true); switch( wrapper.data("epAttribute") ){ case "Name": // value of the data-ep-attribute correlate to the Save attribute oValidate = { maxlength: 100 }; break; case "ManufacturerPrice": case "Price": // value of the data-ep-attribute correlate to the Save attribute oValidate = { type: "number", format: o.grossPrice, region: o.region, currency: o.currency, min: -2147483648, max: 2147483647, needFormat: true }; break; case "StockLevel": // value of the data-ep-attribute correlate to the Save attribute oValidate = { type: "number", format: "n2", region: o.region, min: -2147483648, max: 2147483647 }; break; case "Weight": oValidate = { type: "number", format: "n2", region: o.region, min: 0, max: 2147483647 }; break; default: oValidate = {}; break; } oValidate.valid = false; // object/hash for the uiTextedit widget oTextEditOptions = { action : self.options.saveAction, // SaveAction "Save" objectId : getCheckbox(row).data('id') ? getCheckbox(row).data('id') : getCheckbox(row).val(), // objectId name : wrapper.data("epAttribute"), // name/value pair (from data-ep-attribute) stripHTML : true, validate : oValidate, preventDefaultOnEnter : true, // callback called before element becomes editable onBeforeOpen: function(input){ var icon = wrapper.find(".ep-richtable-edit-icon"), iconWidth; // remove edit icon if necessary if(self.options.editIcon){ iconWidth = icon.outerWidth() + parseInt(icon.css('margin-left')); ep(".ep-richtable-edit-icon", wrapper).remove(); } // show element input.show(); input.attr("disabled", false); if(self.options.editIcon && wrapper.parent().hasClass('ep-Textedit-right')){ input.css('margin-right', iconWidth + 'px'); } wrapper.css("width","98%"); cell.find("*.hideOnEditable").hide(); }, onAfterOpen: function(input){ // if element was just created if( wrapper.prop("justCreated") ){ wrapper.prop("justCreated", false); // prevent an invalid message if input is empty if(input.val().length==0){ input[0].formInvalid = false; input.blur(); wrapper.trigger("click.uiTextedit"); //input.focus(); } } }, // Called before data gets written to *wrapper*. formatOnWriteBack: function(input, evt) { // Format input value. var value = (oValidate.needFormat===true && oValidate.type=="number" && !isNaN( $.i18n.parseNumber(input.val()))) ? $.i18n.formatNumber($.i18n.parseNumber(input.val()), oValidate.format, {currency: self.options.currency, region: self.options.region}) : input.val(); input.val(value); if ((typeof evt!="undefined") && evt.keyCode==27) { // On "ESC" *onAfterClose* will not be called. wrapper.hide(); var invalid = input.is(":input:invalid"); // short delay for webkit-engines to prevent default action setTimeout(function(){ self._setContent(input, wrapper, true); if (!invalid) { wrapper.show(); input.hide(); } else { wrapper.hide(); input.show(); } wrapper.css("width",""); cell.find("*.hideOnEditable").show(); },10); } }, // Called after element becomes uneditable and data has been saved to server. onAfterClose: function(input){ self._setContent(input, wrapper, true); // If HasSubOwnPrices has just been set, // update the DOM and the corresponding server action. var ownpriceWasSet = wrapper.data("epOwnprice"); //#JSCOVERAGE_IF false if (ownpriceWasSet !== undefined) { // Update style and title of *wrapper* enclosing *cell*. cell.removeClass("InheritedValue"); cell.attr("title", wrapper.data("epOwnpriceTitle")); // Update *additional* option of *uiTextedit*. var additional = wrapper.uiTextedit("option","additional"); delete additional[ownpriceWasSet]; wrapper.uiTextedit("option","additional",additional); // Update DOM of *wrapper*. wrapper .removeData(["epOwnprice","epOwnpriceTitle"]) .removeAttr("data-ep-ownprice") .removeAttr("data-ep-ownprice-title"); } //#JSCOVERAGE_ENDIF wrapper.css("width",""); cell.find("*.hideOnEditable").show(); } }; // if additional parameters are needed for the SaveAction if(wrapper.data("epAdditional")!==undefined){ oTextEditOptions.additional = {}; // e.g. oTextEditOptions.additional["CurrencyID"] = "EUR" oTextEditOptions.additional[wrapper.data("epAdditional")] = self.options.currency; } // if additional parameters are needed for the SaveAction if(wrapper.data("epOwnprice")!==undefined){ oTextEditOptions.additional = (oTextEditOptions.additional===undefined)? {} : oTextEditOptions.additional; // e.g. oTextEditOptions.additional["HasSubOwnPrices"] = 1 oTextEditOptions.additional[wrapper.data("epOwnprice")] = 1; } // if additional parameters are needed for the SaveAction if(wrapper.data("epWeightunit")!==undefined){ if (wrapper.data("epWeightunit")) { oTextEditOptions.additional = (oTextEditOptions.additional===undefined)? {} : oTextEditOptions.additional; // e.g. oTextEditOptions.additional["HasSubOwnPrices"] = 1 oTextEditOptions.additional['WeightUnit'] = wrapper.data("epWeightunit"); } else { self._handleTooltip({ context: wrapper, interactiveElement: cell, message: translations.NOT_VALID }); return; } } // build the uiTextedit widget wrapper.uiTextedit(oTextEditOptions); } // trigger click event to show the editable element wrapper.uiTextedit("open"); }, _setContent: function( input, wrapper, disabled ){ var self = this, value = input.val(); input .attr("disabled", disabled) .prop('defaultValue', value) .trigger("change"); // if an edit icons should be shown if( self.options.editIcon ){ if( !self.options.alterIcon || wrapper.data("epTextAlign") != "right" ){ wrapper.append(self.STR_EDIT_ICON); } else{ wrapper.prepend(self.STR_EDIT_ICON); } } }, // creates a tooltip _initTooltip: function (options) { var context = options.context; this.tooltip = $('<div>') .uiTooltip({ interactive: true, event: 'hover', // offsetAdjust: [3, - 2], hideDelay: 1400, context: context, orientation: 'bottom right', hideDelay: 200, oldhandling: true }); }, // handle tooltips _handleTooltip: function (options) { var self = this, context = options.context; // create a tooltip if necessary if (self.tooltip === undefined) { self._initTooltip({ context: context }); } // update tooltip options self.tooltip.uiTooltip('option', 'context', context); self.tooltip.html(options.message); self.tooltip.uiTooltip('show'); options.interactiveElement.one('mouseleave', function () { self.tooltip.uiTooltip('hide'); }); }, _send: function (data) { var self = this, o = self.options; ep.ajax({ type: 'post', dataType: 'json', data: data }); } }); return ep; });