UNPKG

gijgo

Version:

Gijgo is a set of free open source javascript controls distributed under MIT License. All widgets are high performance, built on top of the jQuery JavaScript Library with built-in support for Bootstrap, Material Design and Font Awesome. They are designed

657 lines (586 loc) 25.1 kB
/* * Gijgo Editor v1.4.0 * http://gijgo.com/editor * * Copyright 2014, 2017 gijgo.com * Released under the MIT license */ if (typeof (gj) === 'undefined') { gj = {}; } gj.widget = function () { var self = this; self.xhr = null; self.generateGUID = function () { function s4() { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; self.mouseX = function (e) { if (e) { if (e.pageX) { return e.pageX; } else if (e.clientX) { return e.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); } else if (e.touches && e.touches.length) { return e.touches[0].pageX; } else if (e.changedTouches && e.changedTouches.length) { return e.changedTouches[0].pageX; } else if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) { return e.originalEvent.touches[0].pageX; } else if (e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) { return e.originalEvent.touches[0].pageX; } } return null; }; self.mouseY = function (e) { if (e) { if (e.pageY) { return e.pageY; } else if (e.clientY) { return e.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); } else if (e.touches && e.touches.length) { return e.touches[0].pageY; } else if (e.changedTouches && e.changedTouches.length) { return e.changedTouches[0].pageY; } else if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) { return e.originalEvent.touches[0].pageY; } else if (e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) { return e.originalEvent.touches[0].pageY; } } return null; }; }; gj.widget.prototype.init = function (jsConfig, type) { var option, clientConfig, fullConfig; clientConfig = $.extend(true, {}, this.getHTMLConfig() || {}); $.extend(true, clientConfig, jsConfig || {}); fullConfig = this.getConfig(clientConfig, type); this.attr('data-guid', fullConfig.guid); this.data(fullConfig); // Initialize events configured as options for (option in fullConfig) { if (gj[type].events.hasOwnProperty(option)) { this.on(option, fullConfig[option]); delete fullConfig[option]; } } // Initialize all plugins for (plugin in gj[type].plugins) { if (gj[type].plugins.hasOwnProperty(plugin)) { gj[type].plugins[plugin].configure(this, fullConfig, clientConfig); } } return this; }; gj.widget.prototype.getConfig = function (clientConfig, type) { var config, uiLibrary, iconsLibrary, plugin; config = $.extend(true, {}, gj[type].config.base); uiLibrary = clientConfig.hasOwnProperty('uiLibrary') ? clientConfig.uiLibrary : config.uiLibrary; if (gj[type].config[uiLibrary]) { $.extend(true, config, gj[type].config[uiLibrary]); } iconsLibrary = clientConfig.hasOwnProperty('iconsLibrary') ? clientConfig.iconsLibrary : config.iconsLibrary; if (gj[type].config[iconsLibrary]) { $.extend(true, config, gj[type].config[iconsLibrary]); } for (plugin in gj[type].plugins) { if (gj[type].plugins.hasOwnProperty(plugin)) { $.extend(true, config, gj[type].plugins[plugin].config.base); if (gj[type].plugins[plugin].config[uiLibrary]) { $.extend(true, config, gj[type].plugins[plugin].config[uiLibrary]); } if (gj[type].plugins[plugin].config[iconsLibrary]) { $.extend(true, config, gj[type].plugins[plugin].config[iconsLibrary]); } } } $.extend(true, config, clientConfig); if (!config.guid) { config.guid = this.generateGUID(); } return config; } gj.widget.prototype.getHTMLConfig = function () { var result = this.data(), attrs = this[0].attributes; if (attrs['width']) { result.width = attrs['width'].nodeValue; } if (attrs['height']) { result.height = attrs['height'].nodeValue; } if (attrs['align']) { result.align = attrs['align'].nodeValue; } if (result && result.source) { result.dataSource = result.source; delete result.source; } return result; }; gj.widget.prototype.createDoneHandler = function () { var $widget = this; return function (response) { if (typeof (response) === 'string' && JSON) { response = JSON.parse(response); } gj[$widget.data('type')].methods.render($widget, response); }; }; gj.widget.prototype.createErrorHandler = function () { var $widget = this; return function (response) { if (response && response.statusText && response.statusText !== 'abort') { alert(response.statusText); } }; }; gj.widget.prototype.reload = function (params) { var ajaxOptions, result, data = this.data(), type = this.data('type'); if (data.dataSource === undefined) { gj[type].methods.useHtmlDataSource(this, data); } $.extend(data.params, params); if ($.isArray(data.dataSource)) { result = gj[type].methods.filter(this); gj[type].methods.render(this, result); } else if (typeof(data.dataSource) === 'string') { ajaxOptions = { url: data.dataSource, data: data.params }; if (this.xhr) { this.xhr.abort(); } this.xhr = $.ajax(ajaxOptions).done(this.createDoneHandler()).fail(this.createErrorHandler()); } else if (typeof (data.dataSource) === 'object') { if (!data.dataSource.data) { data.dataSource.data = {}; } $.extend(data.dataSource.data, data.params); ajaxOptions = $.extend(true, {}, data.dataSource); //clone dataSource object if (ajaxOptions.dataType === 'json' && typeof(ajaxOptions.data) === 'object') { ajaxOptions.data = JSON.stringify(ajaxOptions.data); } if (!ajaxOptions.success) { ajaxOptions.success = this.createDoneHandler(); } if (!ajaxOptions.error) { ajaxOptions.error = this.createErrorHandler(); } if (this.xhr) { this.xhr.abort(); } this.xhr = $.ajax(ajaxOptions); } return this; } gj.documentManager = { events: {}, subscribeForEvent: function (eventName, widgetId, callback) { if (!gj.documentManager.events[eventName] || gj.documentManager.events[eventName].length === 0) { gj.documentManager.events[eventName] = [{ widgetId: widgetId, callback: callback }]; $(document).on(eventName, gj.documentManager.executeCallbacks); } else if (!gj.documentManager.events[eventName][widgetId]) { gj.documentManager.events[eventName].push({ widgetId: widgetId, callback: callback }); } else { throw "Event " + eventName + " for widget with guid='" + widgetId + "' is already attached."; } }, executeCallbacks: function (e) { var callbacks = gj.documentManager.events[e.type]; if (callbacks) { for (var i = 0; i < callbacks.length; i++) { callbacks[i].callback(e); } } }, unsubscribeForEvent: function (eventName, widgetId) { var success = false, events = gj.documentManager.events[eventName]; if (events) { for (var i = 0; i < events.length; i++) { if (events[i].widgetId === widgetId) { events.splice(i, 1); success = true; if (events.length === 0) { $(document).off(eventName); delete gj.documentManager.events[eventName]; } } } } if (!success) { throw 'The "' + eventName + '" for widget with guid="' + widgetId + '" can\'t be removed.'; } } }; if (typeof (gj.editor) === 'undefined') { gj.editor = { plugins: {}, messages: [] }; } gj.editor.messages['en-us'] = { bold: 'Bold', italic: 'Italic', strikethrough: 'Strikethrough', underline: 'Underline', listBulleted: 'List Bulleted', listNumbered: 'List Numbered', indentDecrease: 'Indent Decrease', indentIncrease: 'Indent Increase', alignLeft: 'Align Left', alignCenter: 'Align Center', alignRight: 'Align Right', alignJustify: 'Align Justify', undo: 'Undo', redo: 'Redo' }; /* global window alert jQuery */ /** * @widget Editor * @plugin Base */ gj.editor.config = { base: { /** The height of the editor. Numeric values are treated as pixels. * @type number|string * @default 300 * @example sample <!-- editor, materialicons --> * <div id="editor"></div> * <script> * $('#editor').editor({ * height: 500 * }); * </script> */ height: 300, /** The width of the editor. Numeric values are treated as pixels. * @type number|string * @default undefined * @example sample <!-- editor, materialicons --> * <div id="editor"></div> * <script> * $('#editor').editor({ * width: 900 * }); * </script> */ width: undefined, /** The name of the UI library that is going to be in use. Currently we support only Material Design and Bootstrap. * @additionalinfo The css files for Bootstrap should be manually included to the page if you use bootstrap as uiLibrary. * @type string (materialdesign|bootstrap|bootstrap4) * @default 'materialdesign' * @example Material.Design <!-- editor, materialicons --> * <div id="editor"></div> * <script> * $('#editor').editor({ uiLibrary: 'materialdesign' }); * </script> * @example Bootstrap.3 <!-- fontawesome, bootstrap, editor --> * <div class="container"><div id="editor"></div></div> * <script> * $('#editor').editor({ * uiLibrary: 'bootstrap' * }); * </script> * @example Bootstrap.4 <!-- fontawesome, bootstrap4, editor --> * <div class="container"><div id="editor"></div></div> * <script> * $('#editor').editor({ * uiLibrary: 'bootstrap4' * }); * </script> */ uiLibrary: 'materialdesign', /** The name of the icons library that is going to be in use. Currently we support Material Icons and Font Awesome. * @additionalinfo If you use Bootstrap as uiLibrary, then the iconsLibrary is set to font awesome by default.<br/> * If you use Material Design as uiLibrary, then the iconsLibrary is set to Material Icons by default.<br/> * The css files for Material Icons or Font Awesome should be manually included to the page where the grid is in use. * @type (materialicons|fontawesome) * @default 'materialicons' * @example Base.Theme.Material.Icons <!-- materialicons, bootstrap, editor --> * <div id="editor"></div> * <script> * $('#editor').editor({ * uiLibrary: 'bootstrap', * iconsLibrary: 'materialicons' * }); * </script> */ iconsLibrary: 'materialicons', /** The language that needs to be in use. * @type string * @default 'en-us' * @example French <!-- materialicons, editor --> * <script src="../../dist/modular/editor/js/messages/messages.fr-fr.js"></script> * <div id="editor">Hover buttons in the toolbar in order to see localized tooltips</div> * <script> * $("#editor").editor({ * locale: 'fr-fr' * }); * </script> * @example German <!-- materialicons, editor --> * <script src="../../dist/modular/editor/js/messages/messages.de-de.js"></script> * <div id="editor">Hover <b><u>buttons</u></b> in the toolbar in order to see localized tooltips</div> * <script> * $("#editor").editor({ * locale: 'de-de' * }); * </script> */ locale: 'en-us', buttons: undefined, style: { wrapper: 'gj-editor-md', buttonsGroup: 'gj-button-md-group', button: 'gj-button-md', buttonActive: 'active' } }, bootstrap: { style: { wrapper: 'gj-editor-bootstrap', buttonsGroup: 'btn-group', button: 'btn btn-default gj-cursor-pointer', buttonActive: 'active' }, iconsLibrary: 'fontawesome' }, bootstrap4: { style: { wrapper: 'gj-editor-bootstrap', buttonsGroup: 'btn-group', button: 'btn btn-secondary gj-cursor-pointer', buttonActive: 'active' }, iconsLibrary: 'fontawesome' }, materialicons: { icons: { bold: '<i class="material-icons">format_bold</i>', italic: '<i class="material-icons">format_italic</i>', strikethrough: '<i class="material-icons">strikethrough_s</i>', underline: '<i class="material-icons">format_underlined</i>', listBulleted: '<i class="material-icons">format_list_bulleted</i>', listNumbered: '<i class="material-icons">format_list_numbered</i>', indentDecrease: '<i class="material-icons">format_indent_decrease</i>', indentIncrease: '<i class="material-icons">format_indent_increase</i>', alignLeft: '<i class="material-icons">format_align_left</i>', alignCenter: '<i class="material-icons">format_align_center</i>', alignRight: '<i class="material-icons">format_align_right</i>', alignJustify: '<i class="material-icons">format_align_justify</i>', undo: '<i class="material-icons">undo</i>', redo: '<i class="material-icons">redo</i>' } }, fontawesome: { icons: { bold: '<i class="fa fa-bold" aria-hidden="true"></i>', italic: '<i class="fa fa-italic" aria-hidden="true"></i>', strikethrough: '<i class="fa fa-strikethrough" aria-hidden="true"></i>', underline: '<i class="fa fa-underline" aria-hidden="true"></i>', listBulleted: '<i class="fa fa-list-ul" aria-hidden="true"></i>', listNumbered: '<i class="fa fa-list-ol" aria-hidden="true"></i>', indentDecrease: '<i class="fa fa-indent" aria-hidden="true"></i>', indentIncrease: '<i class="fa fa-outdent" aria-hidden="true"></i>', alignLeft: '<i class="fa fa-align-left" aria-hidden="true"></i>', alignCenter: '<i class="fa fa-align-center" aria-hidden="true"></i>', alignRight: '<i class="fa fa-align-right" aria-hidden="true"></i>', alignJustify: '<i class="fa fa-align-justify" aria-hidden="true"></i>', undo: '<i class="fa fa-undo" aria-hidden="true"></i>', redo: '<i class="fa fa-repeat" aria-hidden="true"></i>' } } }; gj.editor.methods = { init: function (jsConfig) { gj.widget.prototype.init.call(this, jsConfig, 'editor'); this.attr('data-editor', 'true'); gj.editor.methods.initialize(this); return this; }, initialize: function ($editor) { var self = this, data = $editor.data(), $group, $btn, $body = $editor.children('div[data-role="body"]'), $toolbar = $editor.children('div[data-role="toolbar"]'); gj.editor.methods.localization(data); $editor.addClass(data.style.wrapper); if (data.width) { $editor.width(data.width); } if ($body.length === 0) { $editor.wrapInner('<div data-role="body"></div>'); $body = $editor.children('div[data-role="body"]'); } $body.attr('contenteditable', true); $body.on('mouseup keyup mouseout', function () { self.updateToolbar($editor, $toolbar); }); if ($toolbar.length === 0) { $toolbar = $('<div data-role="toolbar"></div>'); $body.before($toolbar); for (var group in data.buttons) { $group = $('<div />').addClass(data.style.buttonsGroup); for (var btn in data.buttons[group]) { $btn = $(data.buttons[group][btn]); $btn.on('click', function () { gj.editor.methods.executeCmd($editor, $body, $toolbar, $(this)); }); $group.append($btn); } $toolbar.append($group); } } $body.height(data.height - $toolbar.outerHeight()); }, localization: function (data) { var msg = gj.editor.messages[data.locale]; if (typeof (data.buttons) === 'undefined') { data.buttons = [ [ '<button type="button" class="' + data.style.button + '" title="' + msg.bold + '" data-role="bold">' + data.icons.bold + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.italic + '" data-role="italic">' + data.icons.italic + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.strikethrough + '" data-role="strikethrough">' + data.icons.strikethrough + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.underline + '" data-role="underline">' + data.icons.underline + '</button>' ], [ '<button type="button" class="' + data.style.button + '" title="' + msg.listBulleted + '" data-role="insertunorderedlist">' + data.icons.listBulleted + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.listNumbered + '" data-role="insertorderedlist">' + data.icons.listNumbered + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.indentDecrease + '" data-role="outdent">' + data.icons.indentDecrease + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.indentIncrease + '" data-role="indent">' + data.icons.indentIncrease + '</button>' ], [ '<button type="button" class="' + data.style.button + '" title="' + msg.alignLeft + '" data-role="justifyleft">' + data.icons.alignLeft + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.alignCenter + '" data-role="justifycenter">' + data.icons.alignCenter + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.alignRight + '" data-role="justifyright">' + data.icons.alignRight + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.alignJustify + '" data-role="justifyfull">' + data.icons.alignJustify + '</button>' ], [ '<button type="button" class="' + data.style.button + '" title="' + msg.undo + '" data-role="undo">' + data.icons.undo + '</button>', '<button type="button" class="' + data.style.button + '" title="' + msg.redo + '" data-role="redo">' + data.icons.redo + '</button>' ] ]; } }, updateToolbar: function ($editor, $toolbar) { var data = $editor.data(); $buttons = $toolbar.find('[data-role]').each(function() { var $btn = $(this), cmd = $btn.attr('data-role'); if (cmd && document.queryCommandEnabled(cmd) && document.queryCommandValue(cmd) === "true") { $btn.addClass(data.style.buttonActive); } else { $btn.removeClass(data.style.buttonActive); } }); gj.editor.events.change($editor); }, executeCmd: function ($editor, $body, $toolbar, $btn) { $body.focus(); document.execCommand($btn.attr('data-role'), false); gj.editor.methods.updateToolbar($editor, $toolbar); }, content: function ($editor, html) { var $body = $editor.children('div[data-role="body"]'); if (typeof (html) === "undefined") { return $body.html(); } else { return $body.html(html); } }, destroy: function ($editor) { if ($editor.attr('data-editor') === 'true') { $editor.removeClass($editor.data().style.wrapper); $editor.removeData(); $editor.removeAttr('data-guid'); $editor.removeAttr('data-editor'); $editor.off(); $editor.empty(); } return $editor; } }; gj.editor.events = { /** * Triggered when the editor text is changed. * * @event change * @param {object} e - event data * @example sample <!-- editor, materialicons --> * <div id="editor"></div> * <script> * $('#editor').editor({ * change: function (e) { * alert('Change is fired'); * } * }); * </script> */ change: function ($editor) { return $editor.triggerHandler('change'); } }; gj.editor.widget = function ($element, jsConfig) { var self = this, methods = gj.editor.methods; /** Get or set html content in the body. * @method * @return string * @example Get <!-- editor, materialicons --> * <button class="gj-button-md" onclick="alert($editor.content())">Get Content</button> * <hr/> * <div id="editor"></div> * <script> * var $editor = $('#editor').editor(); * </script> * @example Set <!-- editor, materialicons --> * <button class="gj-button-md" onclick="$editor.content('<h1>new value</h1>')">Set Content</button> * <hr/> * <div id="editor"></div> * <script> * var $editor = $('#editor').editor(); * </script> */ self.content = function (html) { return methods.content(this, html); }; /** Remove editor functionality from the element. * @method * @return jquery element * @example sample <!-- editor, materialicons --> * <button class="gj-button-md" onclick="editor.destroy()">Destroy</button> * <div id="editor"></div> * <script> * var editor = $('#editor').editor(); * </script> */ self.destroy = function () { return methods.destroy(this); }; $.extend($element, self); if ('true' !== $element.attr('data-editor')) { methods.init.call($element, jsConfig); } return $element; }; gj.editor.widget.prototype = new gj.widget(); gj.editor.widget.constructor = gj.editor.widget; (function ($) { $.fn.editor = function (method) { var $widget; if (this && this.length) { if (typeof method === 'object' || !method) { return new gj.editor.widget(this, method); } else { $widget = new gj.editor.widget(this, null); if ($widget[method]) { return $widget[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else { throw 'Method ' + method + ' does not exist.'; } } } }; })(jQuery);