UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

412 lines (352 loc) 14.4 kB
/* Copyright 2008-2009 University of Cambridge Copyright 2008-2010 University of Toronto Copyright 2010-2016 OCAD University Copyright 2013 Raising the Floor - US 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/master/Infusion-LICENSE.txt */ /* global CKEDITOR, tinyMCE */ var fluid_2_0_0 = fluid_2_0_0 || {}; (function ($, fluid) { "use strict"; /************************************* * Shared Rich Text Editor functions * *************************************/ fluid.defaults("fluid.inlineEdit.editorViewAccessor", { gradeNames: ["fluid.viewComponent"], invokers: { value: { funcName: "fluid.inlineEdit.editorViewAccessor.value", args: ["{that}.container", "{that}.options", "{arguments}.0"] } } }); fluid.inlineEdit.editorViewAccessor.value = function (editField, options, newValue) { var editor = options.editorGetFn(editField); if (!editor || editor.length === 0) { if (newValue !== undefined) { $(editField).val(newValue); } return ""; } if (newValue !== undefined) { options.setValueFn(editField, editor, newValue); } else { return options.getValueFn(editor); } }; fluid.defaults("fluid.inlineEdit.richTextViewAccessor", { gradeNames: ["fluid.viewComponent"], invokers: { value: { funcName: "fluid.inlineEdit.richTextViewAccessor.value", args: ["{that}.container", "{arguments}.0"] } } }); fluid.inlineEdit.richTextViewAccessor.value = function (element, newValue) { return fluid.html(element, newValue); }; fluid.inlineEdit.normalizeHTML = function (value) { var togo = $.trim(value.replace(/\s+/g, " ")); togo = togo.replace(/\s+<\//g, "</"); togo = togo.replace(/<([a-z0-9A-Z\/]+)>/g, function (match) { return match.toLowerCase(); }); return togo; }; fluid.inlineEdit.htmlComparator = function (el1, el2) { return fluid.inlineEdit.normalizeHTML(el1) === fluid.inlineEdit.normalizeHTML(el2); }; fluid.inlineEdit.bindRichTextHighlightHandler = function (element, displayModeRenderer, invitationStyle) { element = $(element); var focusOn = function () { displayModeRenderer.addClass(invitationStyle); }; var focusOff = function () { displayModeRenderer.removeClass(invitationStyle); }; element.focus(focusOn); element.blur(focusOff); }; fluid.inlineEdit.setupRichTextEditButton = function (that) { var opts = that.options; var textEditButton = that.locate("textEditButton"); if (textEditButton.length === 0) { var markup = $("<a href='#_' class='flc-inlineEdit-textEditButton'></a>"); markup.text(opts.strings.textEditButton); that.locate("text").after(markup); // Refresh the textEditButton with the newly appended options textEditButton = that.locate("textEditButton"); } return textEditButton; }; /** * Wrap the display text and the textEditButton with the display mode container * for better style control. */ fluid.inlineEdit.richTextDisplayModeRenderer = function (that, edit) { var styles = that.options.styles; var displayModeWrapper = fluid.inlineEdit.setupDisplayModeContainer(styles); var displayModeRenderer = that.viewEl.wrap(displayModeWrapper).parent(); that.textEditButton = fluid.inlineEdit.setupRichTextEditButton(that); displayModeRenderer.append(that.textEditButton); displayModeRenderer.addClass(styles.focus); // Add event handlers. fluid.inlineEdit.bindHoverHandlers(displayModeRenderer, styles.invitation); fluid.inlineEdit.bindMouseHandlers(that.textEditButton, edit); fluid.inlineEdit.bindKeyboardHandlers(that.textEditButton, edit); fluid.inlineEdit.bindRichTextHighlightHandler(that.viewEl, displayModeRenderer, styles.invitation); fluid.inlineEdit.bindRichTextHighlightHandler(that.textEditButton, displayModeRenderer, styles.invitation); return displayModeRenderer; }; /************************ * Tiny MCE Integration * ************************/ var flTinyMCE = fluid.registerNamespace("fluid.inlineEdit.tinyMCE"); fluid.inlineEdit.tinyMCE.getEditor = function (editField) { return tinyMCE.get(editField.prop("id")); }; fluid.inlineEdit.tinyMCE.setValue = function (editField, editor, value) { // without this, there is an intermittent race condition if the editor has been created on this event. $(editField).val(value); editor.setContent(value, {format : "raw"}); }; fluid.inlineEdit.tinyMCE.getValue = function (editor) { return editor.getContent(); }; fluid.defaults("fluid.inlineEdit.tinyMCE.viewAccessor", { gradeNames: ["fluid.inlineEdit.editorViewAccessor"], editorGetFn: flTinyMCE.getEditor, setValueFn: flTinyMCE.setValue, getValueFn: flTinyMCE.getValue }); fluid.inlineEdit.tinyMCE.blurHandlerBinder = function (that) { function focusEditor(editor) { setTimeout(function () { tinyMCE.execCommand("mceFocus", false, that.editField[0].id); editor.selection.select(editor.getBody(), 1); editor.selection.collapse(0); }, 10); } that.events.afterInitEdit.addListener(function (editor) { focusEditor(editor); var editorBody = editor.getBody(); // NB - this section has no effect - on most browsers no focus events // are delivered to the actual body - however, on recent TinyMCE, the // "focusEditor" call DOES deliver a blur which causes FLUID-4681 that.deadMansBlur = fluid.deadMansBlur(that.editField, { cancelByDefault: true, exclusions: {body: $(editorBody), container: that.container}, handler: function () { that[that.options.onBlur](); } }); }); that.events.afterBeginEdit.addListener(function () { var editor = tinyMCE.get(that.editField[0].id); if (editor) { focusEditor(editor); } if (that.deadMansBlur) { that.deadMansBlur.reArm(); } }); that.events.afterFinishEdit.addListener(function () { that.deadMansBlur.noteProceeded(); }); }; fluid.inlineEdit.tinyMCE.editModeRenderer = function (that) { var options = that.options.tinyMCE; options.elements = fluid.allocateSimpleId(that.editField); var oldinit = options.init_instance_callback; options.init_instance_callback = function (instance) { that.events.afterInitEdit.fire(instance); if (oldinit) { oldinit(); } }; // Ensure that instance creation is always asynchronous, to ensure that // blurHandlerBinder always executes BEFORE instance is ready - so that // its afterInitEdit listener is registered in time. All of this architecture // is unsatisfactory, but can't be easily fixed until the whole component is // migrated over to IoC with declarative listener registration. setTimeout(function () { tinyMCE.init(options); }, 1); }; /** * Instantiate a rich-text InlineEdit component that uses an instance of TinyMCE. * * @param {Object} componentContainer the element containing the inline editors * @param {Object} options configuration options for the components */ fluid.defaults("fluid.inlineEdit.tinyMCE", { gradeNames: ["fluid.inlineEdit"], tinyMCE : { mode: "exact", theme: "simple" }, listeners: { onCreate: { "this": "tinyMCE", method: "init", namespace: "initTinyMCE", args: "{that}.options.tinyMCE" } }, useTooltip: true, selectors: { edit: "textarea" }, styles: { invitation: "fl-inlineEdit-richText-invitation", displayView: "fl-inlineEdit-textContainer", text: "" }, strings: { textEditButton: "Edit" }, displayAccessor: { type: "fluid.inlineEdit.richTextViewAccessor" }, editAccessor: { type: "fluid.inlineEdit.tinyMCE.viewAccessor" }, lazyEditView: true, defaultViewText: "Click Edit", modelComparator: fluid.inlineEdit.htmlComparator, onBlur: "finish", blurHandlerBinder: fluid.inlineEdit.tinyMCE.blurHandlerBinder, displayModeRenderer: fluid.inlineEdit.richTextDisplayModeRenderer, editModeRenderer: fluid.inlineEdit.tinyMCE.editModeRenderer }); /**************************** * CKEditor 3.x Integration * ****************************/ var flCKEditor = fluid.registerNamespace("fluid.inlineEdit.CKEditor"); fluid.inlineEdit.CKEditor.getEditor = function (editField) { return CKEDITOR.instances[editField.prop("id")]; }; fluid.inlineEdit.CKEditor.setValue = function (editField, editor, value) { editor.setData(value); }; fluid.inlineEdit.CKEditor.getValue = function (editor) { return editor.getData(); }; fluid.defaults("fluid.inlineEdit.CKEditor.viewAccessor", { gradeNames: ["fluid.inlineEdit.editorViewAccessor"], editorGetFn: flCKEditor.getEditor, setValueFn: flCKEditor.setValue, getValueFn: flCKEditor.getValue }); fluid.inlineEdit.CKEditor.focus = function (editor) { setTimeout(function () { // CKEditor won't focus itself except in a timeout. editor.focus(); }, 0); }; // Special hacked HTML normalisation for CKEditor which spuriously inserts whitespace // just after the first opening tag fluid.inlineEdit.CKEditor.normalizeHTML = function (value) { var togo = fluid.inlineEdit.normalizeHTML(value); var angpos = togo.indexOf(">"); if (angpos !== -1 && angpos < togo.length - 1) { if (togo.charAt(angpos + 1) !== " ") { togo = togo.substring(0, angpos + 1) + " " + togo.substring(angpos + 1); } } return togo; }; fluid.inlineEdit.CKEditor.htmlComparator = function (el1, el2) { return fluid.inlineEdit.CKEditor.normalizeHTML(el1) === fluid.inlineEdit.CKEditor.normalizeHTML(el2); }; fluid.inlineEdit.CKEditor.blurHandlerBinder = function (that) { that.events.afterInitEdit.addListener(fluid.inlineEdit.CKEditor.focus); that.events.afterBeginEdit.addListener(function () { var editor = fluid.inlineEdit.CKEditor.getEditor(that.editField); if (editor) { fluid.inlineEdit.CKEditor.focus(editor); } }); }; fluid.inlineEdit.CKEditor.editModeRenderer = function (that) { var id = fluid.allocateSimpleId(that.editField); $.data(fluid.unwrap(that.editField), "fluid.inlineEdit.CKEditor", that); var editor = CKEDITOR.replace(id, that.options.CKEditor); editor.on("instanceReady", function (e) { fluid.inlineEdit.CKEditor.focus(e.editor); that.events.afterInitEdit.fire(e.editor); }); }; fluid.defaults("fluid.inlineEdit.CKEditor", { gradeNames: ["fluid.inlineEdit"], selectors: { edit: "textarea" }, styles: { invitation: "fl-inlineEdit-richText-invitation", displayView: "fl-inlineEdit-textContainer", text: "" }, strings: { textEditButton: "Edit" }, displayAccessor: { type: "fluid.inlineEdit.richTextViewAccessor" }, editAccessor: { type: "fluid.inlineEdit.CKEditor.viewAccessor" }, lazyEditView: true, defaultViewText: "Click Edit", modelComparator: fluid.inlineEdit.CKEditor.htmlComparator, blurHandlerBinder: fluid.inlineEdit.CKEditor.blurHandlerBinder, displayModeRenderer: fluid.inlineEdit.richTextDisplayModeRenderer, editModeRenderer: fluid.inlineEdit.CKEditor.editModeRenderer, CKEditor: { // CKEditor-specific configuration goes here. } }); /************************ * Dropdown Integration * ************************/ fluid.registerNamespace("fluid.inlineEdit.dropdown"); fluid.inlineEdit.dropdown.editModeRenderer = function (that) { fluid.allocateSimpleId(that.editField); that.editField.selectbox({ finishHandler: function () { that.finish(); } }); return { container: that.editContainer, field: $("input.selectbox", that.editContainer) }; }; fluid.inlineEdit.dropdown.blurHandlerBinder = function (that) { fluid.deadMansBlur(that.editField, { exclusions: {selectBox: $("div.selectbox-wrapper", that.editContainer)}, handler: function () { that.cancel(); } }); }; /** * Instantiate a drop-down InlineEdit component * * @param {Object} container * @param {Object} options */ fluid.defaults("fluid.inlineEdit.dropdown", { gradeNames: ["fluid.inlineEdit"], applyEditPadding: false, blurHandlerBinder: fluid.inlineEdit.dropdown.blurHandlerBinder, editModeRenderer: fluid.inlineEdit.dropdown.editModeRenderer }); })(jQuery, fluid_2_0_0);