From 95299ce2a26eed6601e87cef7fcef724f0420a57 Mon Sep 17 00:00:00 2001 From: hackerwins Date: Fri, 24 Apr 2015 23:58:07 +0900 Subject: [PATCH] Fixed missing custom event and refactoring. - refactor: callback and custom event trigger with `bindCustomEvent`. - spec change: callback's name always camel case. ex) `onfocus` to `onFocus` - bug fix: native command trigger twice `change` --- .jshintrc | 2 +- src/js/EventHandler.js | 140 +++++++++++++++------------------------- src/js/Renderer.js | 4 +- src/js/core/dom.js | 22 ++++--- src/js/core/func.js | 15 ++++- src/js/module/Editor.js | 38 +++++------ src/js/summernote.js | 2 +- 7 files changed, 103 insertions(+), 120 deletions(-) diff --git a/.jshintrc b/.jshintrc index bd7cd3b11..db83ad016 100644 --- a/.jshintrc +++ b/.jshintrc @@ -11,7 +11,7 @@ "unused": true, "trailing": true, "white": true, - "maxparams": 18, + "maxparams": 20, "maxdepth": 5, "maxstatements": 300, "maxlen": 140, diff --git a/src/js/EventHandler.js b/src/js/EventHandler.js index a5c50b53a..0abae460b 100644 --- a/src/js/EventHandler.js +++ b/src/js/EventHandler.js @@ -1,5 +1,6 @@ define([ 'summernote/core/agent', + 'summernote/core/func', 'summernote/core/dom', 'summernote/core/async', 'summernote/core/key', @@ -17,7 +18,7 @@ define([ 'summernote/module/LinkDialog', 'summernote/module/ImageDialog', 'summernote/module/HelpDialog' -], function (agent, dom, async, key, list, History, +], function (agent, func, dom, async, key, list, History, Editor, Toolbar, Statusbar, Popover, Handle, Fullscreen, Codeview, DragAndDrop, Clipboard, LinkDialog, ImageDialog, HelpDialog) { @@ -74,6 +75,22 @@ define([ return this.modules[moduleName] || this.modules.editor; }; + /** + * @param {jQuery} $holder + * @param {Object} callbacks + * @param {String} eventNamespace + * @returns {Function} + */ + var bindCustomEvent = this.bindCustomEvent = function ($holder, callbacks, eventNamespace) { + return function () { + var callback = callbacks[func.namespaceToCamel(eventNamespace, 'on')]; + if (callback) { + callback(arguments); + } + return $holder.trigger('summernote.' + eventNamespace, arguments); + }; + }; + /** * insert Images from file array. * @@ -91,27 +108,18 @@ define([ // If onImageUpload options setted if (callbacks.onImageUpload) { - callbacks.onImageUpload(files, modules.editor, $editable); - bindCustomEvent($holder, 'image.upload')([files]); + bindCustomEvent($holder, callbacks, 'image.upload')([files]); // else insert Image as dataURL } else { $.each(files, function (idx, file) { var filename = file.name; if (options.maximumImageFileSize && options.maximumImageFileSize < file.size) { - if (callbacks.onImageUploadError) { - callbacks.onImageUploadError(options.langInfo.image.maximumFileSizeError); - bindCustomEvent($holder, 'image.upload.error')(options.langInfo.image.maximumFileSizeError); - } else { - alert(options.langInfo.image.maximumFileSizeError); - } + bindCustomEvent($holder, callbacks, 'image.upload.error')(options.langInfo.image.maximumFileSizeError); } else { async.readFileAsDataURL(file).then(function (sDataURL) { modules.editor.insertImage($editable, sDataURL, filename); }).fail(function () { - if (callbacks.onImageUploadError) { - callbacks.onImageUploadError(); - bindCustomEvent($holder, 'image.upload.error')(options.langInfo.image.maximumFileSizeError); - } + bindCustomEvent($holder, callbacks, 'image.upload.error')(options.langInfo.image.maximumFileSizeError); }); } }); @@ -282,12 +290,6 @@ define([ $dimensionDisplay.html(dim.c + ' x ' + dim.r); }; - var bindCustomEvent = function ($holder, eventName) { - return function () { - return $holder.trigger('summernote.' + eventName, arguments); - }; - }; - /** * bind KeyMap on keydown * @@ -337,14 +339,6 @@ define([ * * @param {Object} layoutInfo - layout Informations * @param {Object} options - user options include custom event handlers - * @param {function(event)} [options.onenter] - enter key handler - * @param {function(event)} [options.onfocus] - * @param {function(event)} [options.onblur] - * @param {function(event)} [options.onkeyup] - * @param {function(event)} [options.onkeydown] - * @param {function(event)} [options.onpaste] - * @param {function(event)} [options.onToolBarclick] - * @param {function(event)} [options.onChange] */ this.attach = function (layoutInfo, options) { // handlers for editable @@ -401,52 +395,21 @@ define([ var history = new History(layoutInfo.editable()); layoutInfo.editable().data('NoteHistory', history); - // basic event callbacks (lowercase) - // enter, focus, blur, keyup, keydown - if (options.onenter) { - layoutInfo.editable().keypress(function (event) { - if (event.keyCode === key.code.ENTER) { options.onenter(event); } - }); - } - - if (options.onfocus) { layoutInfo.editable().focus(options.onfocus); } - if (options.onblur) { layoutInfo.editable().blur(options.onblur); } - if (options.onkeyup) { layoutInfo.editable().keyup(options.onkeyup); } - if (options.onkeydown) { layoutInfo.editable().keydown(options.onkeydown); } - if (options.onpaste) { layoutInfo.editable().on('paste', options.onpaste); } - - // callbacks for advanced features (camel) - - // onToolbarClick - if (options.onToolbarClick) { - layoutInfo.toolbar().click(options.onToolbarClick); - } - - // onChange - if (options.onChange) { - var hChange = function () { - modules.editor.triggerOnChange(layoutInfo.editable()); - }; - - // [workaround] for old IE - IE8 don't have input events - if (agent.isMSIE) { - var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted'; - layoutInfo.editable().on(sDomEvents, hChange); - } else { - layoutInfo.editable().on('input', hChange); - } - } - // All editor status will be saved on editable with jquery's data // for support multiple editor with singleton object. layoutInfo.editable().data('callbacks', { - onBeforeChange: options.onBeforeChange, + onInit: options.onInit, + onFocus: options.onFocus, + onBlur: options.onBlur, + onKeydown: options.onKeydown, + onKeyup: options.onKeyup, + onMousedown: options.onMousedown, + onEnter: options.onEnter, + onPaste: options.onPaste, + onBeforeCommand: options.onBeforeCommand, onChange: options.onChange, - onAutoSave: options.onAutoSave, onImageUpload: options.onImageUpload, onImageUploadError: options.onImageUploadError, - onFileUpload: options.onFileUpload, - onFileUploadError: options.onFileUpload, onMediaDelete : options.onMediaDelete }); @@ -472,49 +435,48 @@ define([ this.attachCustomEvent = function (layoutInfo, options) { var $holder = layoutInfo.holder(); var $editable = layoutInfo.editable(); + var callbacks = $editable.data('callbacks'); - $editable.on('mousedown', bindCustomEvent($holder, 'mousedown')); - $editable.on('keyup mouseup', bindCustomEvent($holder, 'update')); - $editable.on('scroll', bindCustomEvent($holder, 'scroll')); + $editable.focus(bindCustomEvent($holder, callbacks, 'focus')); + $editable.blur(bindCustomEvent($holder, callbacks, 'blur')); - // basic event callbacks (lowercase) - // enter, focus, blur, keyup, keydown - $editable.keypress(function (event) { + $editable.keydown(function (event) { if (event.keyCode === key.code.ENTER) { - bindCustomEvent($holder, 'enter').call(this, event); + bindCustomEvent($holder, callbacks, 'enter').call(this, event); } + bindCustomEvent($holder, callbacks, 'keydown').call(this, event); }); + $editable.keyup(bindCustomEvent($holder, callbacks, 'keyup')); - $editable.focus(bindCustomEvent($holder, 'focus')); - $editable.blur(bindCustomEvent($holder, 'blur')); - $editable.keyup(bindCustomEvent($holder, 'keyup')); - $editable.keydown(bindCustomEvent($holder, 'keydown')); - $editable.on('paste', bindCustomEvent($holder, 'paste')); + $editable.on('mousedown', bindCustomEvent($holder, callbacks, 'mousedown')); + $editable.on('mouseup', bindCustomEvent($holder, callbacks, 'mouseup')); + $editable.on('scroll', bindCustomEvent($holder, callbacks, 'scroll')); - // callbacks for advanced features (camel) - if (!options.airMode) { - layoutInfo.toolbar().click(bindCustomEvent($holder, 'toolbar.click')); - layoutInfo.popover().click(bindCustomEvent($holder, 'popover.click')); - } + $editable.on('paste', bindCustomEvent($holder, callbacks, 'paste')); // [workaround] for old IE - IE8 don't have input events if (agent.isMSIE) { var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted'; - $editable.on(sDomEvents, bindCustomEvent($holder, 'change')); + $editable.on(sDomEvents, bindCustomEvent($holder, callbacks, 'change')); } else { - $editable.on('input', bindCustomEvent($holder, 'change')); + $editable.on('input', bindCustomEvent($holder, callbacks, 'change')); + } + + // callbacks for advanced features (camel) + if (!options.airMode) { + layoutInfo.toolbar().click(bindCustomEvent($holder, callbacks, 'toolbar.click')); + layoutInfo.popover().click(bindCustomEvent($holder, callbacks, 'popover.click')); } // Textarea: auto filling the code before form submit. if (dom.isTextarea(list.head($holder))) { $holder.closest('form').submit(function (e) { - var contents = $holder.code(); - bindCustomEvent($holder, 'submit').call(this, e, contents); + bindCustomEvent($holder, callbacks, 'submit').call(this, e, $holder.code()); }); } // fire init event - bindCustomEvent($holder, 'init')(layoutInfo); + bindCustomEvent($holder, callbacks, 'init')(layoutInfo); // fire plugin init event for (var i = 0, len = $.summernote.plugins.length; i < len; i++) { diff --git a/src/js/Renderer.js b/src/js/Renderer.js index d87a80dfa..96eb365db 100644 --- a/src/js/Renderer.js +++ b/src/js/Renderer.js @@ -1,5 +1,7 @@ define([ - 'summernote/core/agent', 'summernote/core/dom', 'summernote/core/func' + 'summernote/core/agent', + 'summernote/core/dom', + 'summernote/core/func' ], function (agent, dom, func) { /** * @class Renderer diff --git a/src/js/core/dom.js b/src/js/core/dom.js index 1055741c1..b895a6343 100644 --- a/src/js/core/dom.js +++ b/src/js/core/dom.js @@ -955,6 +955,18 @@ define([ var isTextarea = makePredByNodeName('TEXTAREA'); + /** + * @param {jQuery} $node + * @param {Boolean} [stripLinebreaks] - default: false + */ + var value = function ($node, stripLinebreaks) { + var val = isTextarea($node[0]) ? $node.val() : $node.html(); + if (stripLinebreaks) { + return val.replace(/[\n\r]/g, ''); + } + return val; + }; + /** * @method html * @@ -964,7 +976,7 @@ define([ * @param {Boolean} [isNewlineOnBlock] */ var html = function ($node, isNewlineOnBlock) { - var markup = isTextarea($node[0]) ? $node.val() : $node.html(); + var markup = value($node); if (isNewlineOnBlock) { var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g; @@ -982,14 +994,6 @@ define([ return markup; }; - var value = function ($textarea, stripLinebreaks) { - var val = $textarea.val(); - if (stripLinebreaks) { - return val.replace(/[\n\r]/g, ''); - } - return val; - }; - return { /** @property {String} NBSP_CHAR */ NBSP_CHAR: NBSP_CHAR, diff --git a/src/js/core/func.js b/src/js/core/func.js index b5b1c38af..11bfdf546 100644 --- a/src/js/core/func.js +++ b/src/js/core/func.js @@ -98,6 +98,18 @@ define('summernote/core/func', function () { return inverted; }; + /** + * @param {String} namespace + * @param {String} [prefix] + * @return {String} + */ + var namespaceToCamel = function (namespace, prefix) { + prefix = prefix || ''; + return prefix + namespace.split('.').map(function (name) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + }).join(''); + }; + return { eq: eq, eq2: eq2, @@ -109,7 +121,8 @@ define('summernote/core/func', function () { and: and, uniqueId: uniqueId, rect2bnd: rect2bnd, - invertObject: invertObject + invertObject: invertObject, + namespaceToCamel: namespaceToCamel }; })(); diff --git a/src/js/module/Editor.js b/src/js/module/Editor.js index 2982cbb05..92c1620df 100644 --- a/src/js/module/Editor.js +++ b/src/js/module/Editor.js @@ -17,7 +17,7 @@ define([ * Editor * */ - var Editor = function () { + var Editor = function (handler) { var style = new Style(); var table = new Table(); @@ -111,18 +111,18 @@ define([ return rng ? rng.isOnEditable() && style.current(rng, target) : false; }; - var triggerOnBeforeChange = this.triggerOnBeforeChange = function ($editable) { - var onBeforeChange = $editable.data('callbacks').onBeforeChange; - if (onBeforeChange) { - onBeforeChange($editable.html(), $editable); - } + var triggerOnBeforeChange = function ($editable) { + // TODO find holder + handler.bindCustomEvent( + $(), $editable.data('callbacks'), 'before.command' + ).call($editable.html(), $editable); }; - var triggerOnChange = this.triggerOnChange = function ($editable) { - var onChange = $editable.data('callbacks').onChange; - if (onChange) { - onChange($editable.html(), $editable); - } + var triggerOnChange = function ($editable) { + // TODO find holder + handler.bindCustomEvent( + $(), $editable.data('callbacks'), 'change' + ).call($editable.html(), $editable); }; /** @@ -160,10 +160,13 @@ define([ * @method afterCommand * after command * @param {jQuery} $editable + * @param {Boolean} isPreventTrigger */ - var afterCommand = this.afterCommand = function ($editable) { + var afterCommand = this.afterCommand = function ($editable, isPreventTrigger) { $editable.data('NoteHistory').recordUndo(); - triggerOnChange($editable); + if (!isPreventTrigger) { + triggerOnChange($editable); + } }; /** @@ -285,7 +288,7 @@ define([ document.execCommand(sCmd, false, value); - afterCommand($editable); + afterCommand($editable, true); }; })(commands[idx]); } @@ -723,10 +726,9 @@ define([ beforeCommand($editable); $target.detach(); - var callbacks = $editable.data('callbacks'); - if (callbacks && callbacks.onMediaDelete) { - callbacks.onMediaDelete($target, this, $editable); - } + handler.bindCustomEvent( + $(), $editable.data('callbacks'), 'media.delete' + ).call($target, this.$editable); afterCommand($editable); }; diff --git a/src/js/summernote.js b/src/js/summernote.js index 8c58d39a5..c8416facd 100644 --- a/src/js/summernote.js +++ b/src/js/summernote.js @@ -273,7 +273,7 @@ define([ return isCodeview ? layoutInfo.codable().val() : layoutInfo.editable().html(); } - return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html(); + return dom.value($holder); } // set the HTML contents of note