UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

969 lines (856 loc) 35.3 kB
/* Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/main/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt */ "use strict"; fluid.registerNamespace("fluid.inlineEdit"); fluid.inlineEdit.switchToViewMode = function (that) { that.editContainer.hide(); that.displayModeRenderer.show(); }; fluid.inlineEdit.cancel = function (that) { if (that.isEditing()) { // Roll the edit field back to its old value and close it up. // This setTimeout is necessary on Firefox, since any attempt to modify the // input control value during the stack processing the ESCAPE key will be ignored. setTimeout(function () { that.editView.value(that.model.value); }, 1); fluid.inlineEdit.switchToViewMode(that); that.events.afterFinishEdit.fire(that.model.value, that.model.value, that.editField[0], that.viewEl[0]); } }; fluid.inlineEdit.finish = function (that) { var newValue = that.editView.value(); var oldValue = that.model.value; var viewNode = that.viewEl[0]; var editNode = that.editField[0]; var ret = that.events.onFinishEdit.fire(newValue, oldValue, editNode, viewNode); if (ret === false) { return; } that.updateModelValue(newValue); that.events.afterFinishEdit.fire(newValue, oldValue, editNode, viewNode); fluid.inlineEdit.switchToViewMode(that); }; /** * Do not allow the textEditButton to regain focus upon completion unless * the keypress is enter or esc. * * @param {Object} that - The component itself. */ fluid.inlineEdit.bindEditFinish = function (that) { that.submitOnEnter = that.options.submitOnEnter; if (that.submitOnEnter === undefined) { that.submitOnEnter = "textarea" !== fluid.unwrap(that.editField).nodeName.toLowerCase(); } function keyCode(evt) { // Fix for handling arrow key presses. See FLUID-760. return evt.keyCode ? evt.keyCode : (evt.which ? evt.which : 0); } var button = that.textEditButton || $(); var escHandler = function (evt) { var code = keyCode(evt); if (code === $.ui.keyCode.ESCAPE) { button.trigger("focus"); fluid.inlineEdit.cancel(that); return false; } }; var finishHandler = function (evt) { var code = keyCode(evt); if (code !== $.ui.keyCode.ENTER) { button.trigger("blur"); return true; } else { fluid.inlineEdit.finish(that); button.trigger("focus"); } return false; }; if (that.submitOnEnter) { that.editContainer.on("keypress", finishHandler); } that.editContainer.on("keydown", escHandler); }; fluid.inlineEdit.bindBlurHandler = function (that) { if (that.options.blurHandlerBinder) { that.options.blurHandlerBinder(that); } else { var blurHandler = function () { if (that.isEditing()) { fluid.inlineEdit.finish(that); } return false; }; that.editField.on("blur", blurHandler); } }; fluid.inlineEdit.initializeEditView = function (that, initial) { if (!that.editInitialized) { fluid.inlineEdit.renderEditContainer(that, !that.options.lazyEditView || !initial); if (!that.options.lazyEditView || !initial) { that.events.onCreateEditView.fire(); if (that.textEditButton) { fluid.inlineEdit.bindEditFinish(that); } fluid.inlineEdit.bindBlurHandler(that); that.editView.refreshView(that); that.editInitialized = true; } } }; fluid.inlineEdit.edit = function (that) { fluid.inlineEdit.initializeEditView(that, false); var viewEl = that.viewEl; var displayText = that.displayView.value(); that.updateModelValue(that.model.value === "" ? "" : displayText); if (that.options.applyEditPadding) { that.editField.width(Math.max(viewEl.width() + that.options.paddings.edit, that.options.paddings.minimumEdit)); } that.displayModeRenderer.hide(); that.editContainer.show(); // Work around for FLUID-726 // Without 'setTimeout' the finish handler gets called with the event and the edit field is inactivated. setTimeout(function () { that.editField.trigger("focus"); if (that.options.selectOnEdit) { that.editField[0].select(); } }, 0); that.events.afterBeginEdit.fire(); }; fluid.inlineEdit.clearEmptyViewStyles = function (textEl, styles, originalViewPadding) { textEl.removeClass(styles.defaultViewStyle); textEl.css("padding-right", originalViewPadding); textEl.removeClass(styles.emptyDefaultViewText); }; fluid.inlineEdit.showDefaultViewText = function (that) { that.displayView.value(that.options.strings.defaultViewText); that.viewEl.css("padding-right", that.existingPadding); that.viewEl.addClass(that.options.styles.defaultViewStyle); }; fluid.inlineEdit.showNothing = function (that) { that.displayView.value(""); if (that.viewEl.css("display") === "inline") { that.viewEl.css("display", "inline-block"); } }; fluid.inlineEdit.showEditedText = function (that) { that.displayView.value(that.model.value); fluid.inlineEdit.clearEmptyViewStyles(that.viewEl, that.options.styles, that.existingPadding); }; /** * Updates the state of the inline editor in the DOM, based on changes that may have * happened to the model. * * @param {fluid.inlineEdit} that - an instance of `fluid.inlineEdit` * @param {Object} source - (optional) An object identifying the source of the change (see ChangeApplier documentation) */ fluid.inlineEdit.refreshView = function (that, source) { that.displayView.refreshView(that, source); if (that.editView) { that.editView.refreshView(that, source); } }; /** * Pushes external changes to the model into the inline editor, refreshing its * rendering in the DOM. The modelChanged event will fire. * * @param {fluid.inlineEdit} that - an instance of `fluid.inlineEdit` * @param {String} newValue - The bare value of the model, i.e. the string being edited * @param {Object} [source] - (optional) "source" (perhaps a DOM element) which triggered this event */ fluid.inlineEdit.updateModelValue = function (that, newValue, source) { var comparator = that.options.modelComparator; var unchanged = comparator ? comparator(that.model.value, newValue) : that.model.value === newValue; if (!unchanged) { var oldModel = $.extend(true, {}, that.model); that.model.value = newValue; that.events.modelChanged.fire(that.model, oldModel, source); that.refreshView(source); } }; fluid.inlineEdit.editHandler = function (that) { var prevent = that.events.onBeginEdit.fire(); if (prevent === false) { return false; } fluid.inlineEdit.edit(that); return true; }; // Initialize the tooltip once the document is ready. // For more details, see http://issues.fluidproject.org/browse/FLUID-1030 fluid.inlineEdit.initTooltips = function (that) { var tooltipOptions = { content: that.options.tooltipText, position: { my: "left top", at: "left bottom+25%", // add a 25% offset to keep the tooltip from overlapping the element it is for // setting the "of" property to ensure that the tooltip is positioned relative to that.viewEl // even when keyboard focus is on that.textEditButton of: that.viewEl }, target: "*", delay: that.options.tooltipDelay, styles: { tooltip: that.options.styles.tooltip } }; fluid.tooltip(that.viewEl, tooltipOptions); if (that.textEditButton) { fluid.tooltip(that.textEditButton, tooltipOptions); } }; fluid.inlineEdit.calculateInitialPadding = function (viewEl) { var padding = viewEl.css("padding-right"); return padding ? parseFloat(padding) : 0; }; /** * Set up and style the edit field. If an edit field is not provided, * default markup is created for the edit field * * @param {String} editStyle - The default styling for the edit field. * @param {Object} editField - The existing edit field. * @param {Object} editFieldMarkup - The edit field markup provided by the integrator. * * @return {Object} The styled edit field. */ fluid.inlineEdit.setupEditField = function (editStyle, editField, editFieldMarkup) { var eField = $(editField); eField = eField.length ? eField : $(editFieldMarkup); eField.addClass(editStyle); return eField; }; /** * Set up the edit container and append the edit field to the container. If an edit container * is not provided, default markup is created. * * @param {Object} displayContainer - The display mode container * @param {Object} editField - The edit field that is to be appended to the edit container * @param {Object} editContainer - The edit container markup provided by the integrator * @param {Object} editContainerMarkup - The edit container markup provided by the integrator. * * @return {Object} The edit container containing the edit field */ fluid.inlineEdit.setupEditContainer = function (displayContainer, editField, editContainer, editContainerMarkup) { var eContainer = $(editContainer); eContainer = eContainer.length ? eContainer : $(editContainerMarkup); displayContainer.after(eContainer); eContainer.append(editField); return eContainer; }; /** * Default renderer for the edit mode view. * * @param {fluid.inlineEdit} that - The fluid.inlineEdit component itself. * @return {Object} An object containing: * - container The edit container containing the edit field * - field The styled edit field */ fluid.inlineEdit.defaultEditModeRenderer = function (that) { var editField = fluid.inlineEdit.setupEditField(that.options.styles.edit, that.editField, that.options.markup.editField); var editContainer = fluid.inlineEdit.setupEditContainer(that.displayModeRenderer, editField, that.editContainer, that.options.markup.editContainer); var editModeInstruction = fluid.inlineEdit.setupEditModeInstruction(that.options.styles.editModeInstruction, that.options.strings.editModeInstruction, that.options.markup.editModeInstruction); var id = fluid.allocateSimpleId(editModeInstruction); editField.attr("aria-describedby", id); fluid.inlineEdit.positionEditModeInstruction(editModeInstruction, editContainer, editField); // Package up the container and field for the component. return { container: editContainer, field: editField }; }; /** Configures the edit container and view, and uses the component's editModeRenderer to render * the edit container. * * @param {Object} that - The component itself. * @param {Boolean} lazyEditView - If true, will delay rendering of the edit container; Default is false */ fluid.inlineEdit.renderEditContainer = function (that, lazyEditView) { that.editContainer = that.locate("editContainer"); that.editField = that.locate("edit"); if (that.editContainer.length !== 1) { if (that.editContainer.length > 1) { fluid.fail("InlineEdit did not find a unique container for selector " + that.options.selectors.editContainer + ": " + fluid.dumpEl(that.editContainer)); } } if (!lazyEditView) { return; } // do not invoke the renderer, unless this is the "final" effective time var editElms = that.options.editModeRenderer(that); if (editElms) { that.editContainer = editElms.container; that.editField = editElms.field; } }; /** Set up the edit mode instruction with aria in edit mode * @param {String} editModeInstructionStyle - The default styling for the instruction * @param {String} editModeInstructionText - The default instruction text * @param {Object} editModeInstructionMarkup - The markup to modify. * @return {jQuery} The displayed instruction in edit mode */ fluid.inlineEdit.setupEditModeInstruction = function (editModeInstructionStyle, editModeInstructionText, editModeInstructionMarkup) { var editModeInstruction = $(editModeInstructionMarkup); editModeInstruction.addClass(editModeInstructionStyle); editModeInstruction.text(editModeInstructionText); return editModeInstruction; }; /** * Positions the edit mode instruction directly beneath the edit container * * @param {Object} editModeInstruction - The displayed instruction in edit mode * @param {Object} editContainer - The edit container in edit mode * @param {Object} editField - The edit field in edit mode */ fluid.inlineEdit.positionEditModeInstruction = function (editModeInstruction, editContainer, editField) { editContainer.append(editModeInstruction); editField.on("focus", function () { editModeInstruction.show(); var editFieldPosition = editField.offset(); // For FLUID-5980 (https://issues.fluidproject.org/browse/FLUID-5980) // // From the jQuery height docs (http://api.jquery.com/height/) // "As of jQuery 1.8, this may require retrieving the CSS height plus // box-sizing property and then subtracting any potential border and // padding on each element when the element has box-sizing: border-box. // To avoid this penalty, use .css( "height" ) rather than .height()." var editFieldHeight = parseInt(editField.css("height"), 10); editModeInstruction.css({left: editFieldPosition.left}); editModeInstruction.css({top: editFieldPosition.top + editFieldHeight + 5}); }); }; /** * Set up and style the display mode container for the viewEl and the textEditButton * * @param {Object} styles - The default styling for the display mode container * @param {Object} displayModeWrapper - The markup used to generate the display mode container * * @return {jQuery} The styled display mode container */ fluid.inlineEdit.setupDisplayModeContainer = function (styles, displayModeWrapper) { var displayModeContainer = $(displayModeWrapper); displayModeContainer = displayModeContainer.length ? displayModeContainer : $("<span></span>"); displayModeContainer.addClass(styles.displayView); return displayModeContainer; }; /** Retrieve the display text from the DOM. * @param {Object} viewEl - The view element. * @param {String} textStyle - The classes to apply to the view element. * @return {jQuery} The view element. */ fluid.inlineEdit.setupDisplayText = function (viewEl, textStyle) { /* Remove the display from the tab order to prevent users to think they * are able to access the inline edit field, but they cannot since the * keyboard event binding is only on the button. */ viewEl.attr("tabindex", "-1"); viewEl.addClass(textStyle); return viewEl; }; /** * Set up the textEditButton. Append a background image with appropriate * descriptive text to the button. * * @param {Object} that - The component itself. * @param {Object} model - The model data. * @return {jQuery} The accessible button located after the display text */ fluid.inlineEdit.setupTextEditButton = function (that, model) { var opts = that.options; var textEditButton = that.locate("textEditButton"); if (textEditButton.length === 0) { var markup = $(that.options.markup.textEditButton); markup.addClass(opts.styles.textEditButton); markup.text(opts.tooltipText); /** * Set text for the button and listen * for modelChanged to keep it updated */ fluid.inlineEdit.updateTextEditButton(markup, model.value || opts.strings.defaultViewText, opts.strings.textEditButton); that.events.modelChanged.addListener(function () { fluid.inlineEdit.updateTextEditButton(markup, model.value || opts.strings.defaultViewText, opts.strings.textEditButton); }); that.locate("text").after(markup); // Refresh the textEditButton with the newly appended options textEditButton = that.locate("textEditButton"); } return textEditButton; }; /** * Update the textEditButton text with the current value of the field. * * @param {Object} textEditButton - the textEditButton * @param {String} value - The current value of the inline editable text * @param {String} stringTemplate - The string template to use in producing the button text. */ fluid.inlineEdit.updateTextEditButton = function (textEditButton, value, stringTemplate) { var buttonText = fluid.stringTemplate(stringTemplate, { text: value }); textEditButton.text(buttonText); }; /** * Bind mouse hover event handler to the display mode container. * * @param {Object} displayModeRenderer - The display mode container * @param {String} invitationStyle - The default styling for the display mode container on mouse hover */ fluid.inlineEdit.bindHoverHandlers = function (displayModeRenderer, invitationStyle) { var over = function () { displayModeRenderer.addClass(invitationStyle); }; var out = function () { displayModeRenderer.removeClass(invitationStyle); }; displayModeRenderer.on("mouseenter", over); displayModeRenderer.on("mouseleave", out); }; /** * Bind keyboard focus and blur event handlers to an element * * Note: This function is an unsupported, NON-API function * * @param {Object} element - The element to which the event handlers are bound * @param {Object} displayModeRenderer - The display mode container * @param {Object} styles - The default styling for the display mode container on mouse hover * @param {Object} strings - String messages to use if there is no model value. * @param {Object} model - Model data to display. */ fluid.inlineEdit.bindHighlightHandler = function (element, displayModeRenderer, styles, strings, model) { element = $(element); var makeFocusSwitcher = function (focusOn) { return function () { displayModeRenderer.toggleClass(styles.focus, focusOn); displayModeRenderer.toggleClass(styles.invitation, focusOn); if (!model || !model.value) { displayModeRenderer.prevObject.text(focusOn ? strings.defaultFocussedViewText : strings.defaultViewText); } }; }; element.on("focus", makeFocusSwitcher(true)); element.on("blur", makeFocusSwitcher(false)); }; /** * Bind mouse click handler to an element * * @param {Object} element - The element to which the event handler is bound * @param {Object} edit - Function to invoke the edit mode * */ fluid.inlineEdit.bindMouseHandlers = function (element, edit) { element = $(element); var triggerGuard = fluid.inlineEdit.makeEditTriggerGuard(element, edit); element.on("click", function (e) { triggerGuard(e); return false; }); }; /** * Bind keyboard press handler to an element * * @param {Object} element - The element to which the event handler is bound * @param {Object} edit - Function to invoke the edit mode * */ fluid.inlineEdit.bindKeyboardHandlers = function (element, edit) { element = $(element); element.attr("role", "button"); var guard = fluid.inlineEdit.makeEditTriggerGuard(element, edit); fluid.activatable(element, function (event) { return guard(event); }); }; /** * Creates an event handler that will trigger the edit mode if caused by something other * than standard HTML controls. The event handler will return false if entering edit mode. * * @param {Object} jElement - The element to trigger the edit mode * @param {Object} edit - Function to invoke the edit mode * * @return {Function} The event handler function */ fluid.inlineEdit.makeEditTriggerGuard = function (jElement, edit) { var element = fluid.unwrap(jElement); return function (event) { // FLUID-2017 - avoid triggering edit mode when operating standard HTML controls. Ultimately this // might need to be extensible, in more complex authouring scenarios. var outer = fluid.findAncestor(event.target, function (elem) { if (/input|select|textarea|button|a/i.test(elem.nodeName) || elem === element) { return true; } }); if (outer === element) { edit(); return false; } }; }; /* Bind all user-facing event handlers required by the component */ fluid.inlineEdit.bindEventHandlers = function (that, edit, displayModeContainer) { var styles = that.options.styles; fluid.inlineEdit.bindHoverHandlers(displayModeContainer, styles.invitation); fluid.inlineEdit.bindMouseHandlers(that.viewEl, edit); fluid.inlineEdit.bindMouseHandlers(that.textEditButton, edit); fluid.inlineEdit.bindKeyboardHandlers(that.textEditButton, edit); fluid.inlineEdit.bindHighlightHandler(that.viewEl, displayModeContainer, that.options.styles, that.options.strings, that.model); fluid.inlineEdit.bindHighlightHandler(that.textEditButton, displayModeContainer, that.options.styles, that.options.strings, that.model); }; /** Render the display mode view. * * @param {fluid.inlineEdit} that - The component itself. * @param {Function} edit - Function to invoke the edit mode * @param {Object} model - Model data to display. * @return {jQuery} The display container containing the display text and textEditbutton for display mode view. */ fluid.inlineEdit.defaultDisplayModeRenderer = function (that, edit, model) { var styles = that.options.styles; var displayModeWrapper = fluid.inlineEdit.setupDisplayModeContainer(styles); var displayModeContainer = that.viewEl.wrap(displayModeWrapper).parent(); that.textEditButton = fluid.inlineEdit.setupTextEditButton(that, model); displayModeContainer.append(that.textEditButton); fluid.inlineEdit.bindEventHandlers(that, edit, displayModeContainer); return displayModeContainer; }; fluid.inlineEdit.getNodeName = function (element) { return fluid.unwrap(element).nodeName.toLowerCase(); }; fluid.defaults("fluid.inlineEdit.standardAccessor", { gradeNames: ["fluid.viewComponent"], members: { nodeName: { expander: { funcName: "fluid.inlineEdit.getNodeName", args: "{that}.container" } } }, invokers: { value: { funcName: "fluid.inlineEdit.standardAccessor.value", args: ["{that}.nodeName", "{that}.container", "{arguments}.0"] } } }); fluid.inlineEdit.standardAccessor.value = function (nodeName, element, newValue) { return fluid[nodeName === "input" || nodeName === "textarea" ? "value" : "text"]($(element), newValue); }; fluid.defaults("fluid.inlineEdit.standardDisplayView", { gradeNames: ["fluid.viewComponent"], invokers: { refreshView: { funcName: "fluid.inlineEdit.standardDisplayView.refreshView", args: ["{fluid.inlineEdit}", "{that}.container", "{arguments}.0"] } } }); fluid.inlineEdit.standardDisplayView.refreshView = function (componentThat) { if (componentThat.model.value) { fluid.inlineEdit.showEditedText(componentThat); } else if (componentThat.options.strings.defaultViewText) { fluid.inlineEdit.showDefaultViewText(componentThat); } else { fluid.inlineEdit.showNothing(componentThat); } // If necessary, pad the view element enough that it will be evident to the user. if (componentThat.viewEl.text().trim().length === 0) { componentThat.viewEl.addClass(componentThat.options.styles.emptyDefaultViewText); if (componentThat.existingPadding < componentThat.options.paddings.minimumView) { componentThat.viewEl.css("padding-right", componentThat.options.paddings.minimumView); } } }; fluid.defaults("fluid.inlineEdit.standardEditView", { gradeNames: ["fluid.viewComponent"], invokers: { refreshView: { funcName: "fluid.inlineEdit.standardEditView.refreshView", args: ["{fluid.inlineEdit}", "{that}.container", "{arguments}.0"] } } }); fluid.inlineEdit.standardEditView.refreshView = function (componentThat, editField, source) { if (!source || (editField && editField.index(source) === -1)) { componentThat.editView.value(componentThat.model.value); } }; fluid.inlineEdit.setup = function (that) { // Hide the edit container to start if (that.editContainer) { that.editContainer.hide(); } // Add tooltip handler if required and available if (that.tooltipEnabled()) { fluid.inlineEdit.initTooltips(that); } }; // TODO: Should really be part of a "collateral" or "shadow model" fluid.inlineEdit.setIsEditing = function (that, state) { that.isEditingState = state; }; /** * Determines if the tooltip feature is enabled. * * @param {Boolean} useTooltip - A boolean indicating fi the tooltip should be enabled (true) or disabled (false) * * @return {Boolean} - Returns true if the tooltip feature is turned on, false if not */ fluid.inlineEdit.tooltipEnabled = function (useTooltip) { return useTooltip && $.fn.tooltip; }; // Backwards compatibility for users of the 1.4.x and below Infusion API - new users are recommended to directly attach // a "fluid.undo" as a subcomponent with appropriate configuration - express this using FLUID-5022 system when it is available fluid.inlineEdit.processUndoDecorator = function (that) { if (that.options.componentDecorators) { var decorators = fluid.makeArray(that.options.componentDecorators); var decorator = decorators[0]; if (typeof(decorator) === "string") { decorator = {type: decorator}; } if (decorator.type === "fluid.undoDecorator") { that.decorators = [fluid.constructChild(that, "undo", { type: "fluid.undo", options: decorator.options})]; } } }; /** * Instantiates a new Inline Edit component * * @param {Object} componentContainer - a selector, jQuery, or a DOM element representing the component's container * @param {Object} options - a collection of options settings */ fluid.defaults("fluid.inlineEdit", { // WARNING: fluid.inlineEdit, via fluid.undoable, inherits from fluid.modelComponent but it does not use the ChangeApplier to // update its model. This will lead to corruption if it participates in any model relays and will not be supported in future // Infusion releases. gradeNames: ["fluid.undoable", "fluid.viewComponent"], mergePolicy: { "strings.defaultViewText": "defaultViewText" }, members: { isEditingState: false, viewEl: { expander: { funcName: "fluid.inlineEdit.setupDisplayText", args: ["{that}.dom.text", "{that}.options.styles.text"] } }, existingPadding: { expander: { funcName: "fluid.inlineEdit.calculateInitialPadding", args: "{that}.viewEl" } }, displayModeRenderer: { expander: { func: "{that}.options.displayModeRenderer", args: ["{that}", "{that}.edit", "{that}.model"] } } }, invokers: { // Switches to edit mode. edit: { funcName: "fluid.inlineEdit.editHandler", args: "{that}" }, // Determines if the component is currently in edit mode. // Returns true if edit mode shown, false if view mode is shown isEditing: { funcName: "fluid.identity", args: "{that}.isEditingState" }, // Finishes editing, switching back to view mode. finish: { funcName: "fluid.inlineEdit.finish", args: "{that}" }, // Cancels the in-progress edit and switches back to view mode cancel: { funcName: "fluid.inlineEdit.cancel", args: "{that}" }, // Determines if the tooltip feature is enabled. tooltipEnabled: { funcName: "fluid.inlineEdit.tooltipEnabled", args: "{that}.options.useTooltip" }, // Updates the state of the inline editor in the DOM refreshView: { funcName: "fluid.inlineEdit.refreshView", args: ["{that}", "{arguments}.0"] }, updateModelValue: { funcName: "fluid.inlineEdit.updateModelValue", args: ["{that}", "{arguments}.0", "{arguments}.1"] // newValue, source }, updateModel: { funcName: "fluid.inlineEdit.updateModelValue", args: ["{that}", "{arguments}.0.value", "{arguments}.1"] // newModel, source } }, components: { displayView: { type: "{that}.options.displayView.type", container: "{that}.viewEl", options: { gradeNames: "{fluid.inlineEdit}.options.displayAccessor.type" } }, editView: { type: "{that}.options.editView.type", createOnEvent: "onCreateEditView", container: "{that}.editField", options: { gradeNames: "{fluid.inlineEdit}.options.editAccessor.type" } } }, model: { value: { expander: { func: "{that}.displayView.value"} } }, selectors: { text: ".flc-inlineEdit-text", editContainer: ".flc-inlineEdit-editContainer", edit: ".flc-inlineEdit-edit", textEditButton: ".flc-inlineEdit-textEditButton" }, styles: { text: "fl-inlineEdit-text", edit: "fl-inlineEdit-edit", invitation: "fl-inlineEdit-invitation", defaultViewStyle: "fl-inlineEdit-emptyText-invitation", emptyDefaultViewText: "fl-inlineEdit-emptyDefaultViewText", focus: "fl-inlineEdit-focus", tooltip: "fl-inlineEdit-tooltip", editModeInstruction: "fl-inlineEdit-editModeInstruction", displayView: "fl-inlineEdit-simple-editableText fl-inlineEdit-textContainer", textEditButton: "fl-hidden-accessible" }, events: { modelChanged: null, onBeginEdit: "preventable", afterBeginEdit: null, onFinishEdit: "preventable", afterFinishEdit: null, afterInitEdit: null, onCreateEditView: null }, listeners: { onCreate: [{ func: "{that}.refreshView" }, { funcName: "fluid.inlineEdit.initializeEditView", args: ["{that}", true] }, { funcName: "fluid.inlineEdit.setup", args: "{that}" }, { funcName: "fluid.inlineEdit.processUndoDecorator", args: "{that}" }], onBeginEdit: { funcName: "fluid.inlineEdit.setIsEditing", args: ["{that}", true] }, afterFinishEdit: { funcName: "fluid.inlineEdit.setIsEditing", args: ["{that}", false] } }, strings: { textEditButton: "Edit text %text", editModeInstruction: "Escape to cancel, Enter or Tab when finished", defaultViewText: "Click here to edit", /* this will override the direct option */ defaultFocussedViewText: "Click here or press enter to edit" }, markup: { editField: "<input type='text' class='flc-inlineEdit-edit'/>", editContainer: "<span></span>", editModeInstruction: "<p></p>", textEditButton: "<a href='#_' class='flc-inlineEdit-textEditButton'></a>" }, paddings: { edit: 10, minimumEdit: 80, minimumView: 60 }, applyEditPadding: true, blurHandlerBinder: null, // set this to true or false to cause unconditional submission, otherwise it will // be inferred from the edit element tag type. submitOnEnter: undefined, modelComparator: null, displayAccessor: { type: "fluid.inlineEdit.standardAccessor" }, displayView: { type: "fluid.inlineEdit.standardDisplayView" }, editAccessor: { type: "fluid.inlineEdit.standardAccessor" }, editView: { type: "fluid.inlineEdit.standardEditView" }, displayModeRenderer: fluid.inlineEdit.defaultDisplayModeRenderer, editModeRenderer: fluid.inlineEdit.defaultEditModeRenderer, lazyEditView: false, /** View Mode Tooltip Settings **/ useTooltip: true, // this is here for backwards API compatibility, but should be in the strings block tooltipText: "Select or press Enter to edit", tooltipDelay: 1000, selectOnEdit: false }); /* * Creates a whole list of inline editors as subcomponents of the supplied component */ fluid.setupInlineEdits = function (that, editables) { // TODO: create useful framework for automated construction of component definitions, possibly using Model Transformation - FLUID-5022 return fluid.transform(editables, function (editable, i) { var componentDef = { type: "fluid.inlineEdit", container: editable }; return fluid.constructChild(that, "inlineEdit-" + i, componentDef); }); }; fluid.defaults("fluid.inlineEditsComponent", { gradeNames: ["fluid.viewComponent"], distributeOptions: { source: "{that}.options", // TODO: Appalling requirement to evade FLUID-5887 check - otherwise all of this fluid.modelComponent material is broadcast down to each component. // "source" distributions are silly and dangerous in any case, but they have become fairly widely used, together with the expectation that the // material from "defaults" can be broadcast too. But clearly material that is from base grade defaults is unwelcome to be distributed. // This seems to imply that we've got no option but to start supporting "provenance" in options and defaults - highly expensive. exclusions: ["workflows", "invokers.locate", "members.inlineEdits", "members.modelRelay", "container", "members.container", "members.dom", "members.applier", "members.model", "selectors.editables", "events"], removeSource: true, target: "{that > fluid.inlineEdit}.options" }, members: { inlineEdits: { expander: { funcName: "fluid.setupInlineEdits", args: ["{that}", "{that}.dom.editables"] } } }, selectors: { editables: ".flc-inlineEditable" } }); fluid.inlineEdits = function (container, options) { var that = fluid.inlineEditsComponent(container, options); return that.inlineEdits; };