diff --git a/README.md b/README.md index 0e746d0..e03217c 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,58 @@ ArteJS [![Build Status](https://secure.travis-ci.org/vistaprint/ArteJS.png?branc ArteJS is a powerful, extensible, configurable, flexible and cross-browser rich text editor with a simple API that produces consistent, valid and concise html. [ArteJS Examples / Documentation](http://vistaprint.github.io/ArteJS/ "ArteJS Examples / Documentation") + +### Options + +You can initialize your Arte editor with some custom options, passing a literal object in the first argument. + +```js +$("#editor").Arte(options); +``` + +The available options are listed below with their default values: + +```js +$("#editor").Arte({ + + // Set of initial styles applied to rich text editor + styles: { + "min-height": "200px", + "height": "inherit" + }, + + // Collection of classes applied to rich text editor + classes: [], + + // Initial value of the text editor + value: "Please enter text ...", + + // Editor Type: plain text or rich text + editorType: "plainText", + + // attach event handlers + on: null +}); +``` + +### Commands + +After you initialize your Arte editor, you can call commands: + +```js +var arte = $("#editor").Arte(options); + +// make the selection bold +arte.Arte("bold"); + +// set the fontsize of the selection to 12px +arte.Arte("fontSize", "12px"); + +// Alternative: + +arte.each(function() { + this.bold(); + this.fontSize("12px"); +}); + +``` diff --git a/src/editor/core/Arte.js b/src/editor/core/Arte.js index b5aac5d..d804d61 100644 --- a/src/editor/core/Arte.js +++ b/src/editor/core/Arte.js @@ -1,19 +1,26 @@ -/** +/** * @fileoverview jQuery wrapper around the Rich text editor * Usage: * 1) $(selector).Arte() * Converts the matched elements into rich text editor using default options or returns and existing instance + * @returns {jQuery selector of Arte.TextArea} * 2) $(selector).Arte({ options }); * Converts the matched elements into rich text editor using the options supplied or returns and existing instance + * @returns {jQuery selector of Arte.TextArea} * 3) $(selector).Arte(command, arguments) * Execute a rich text command with arguments + * @returns {jQuery selector of return value of commands called} + * */ (function($) { $.Arte = $.Arte || {}; $.fn.Arte = function(options, args) { - var result = []; rangy.init(); - this.each(function() { + + if (!this.length) { + return this; + } + return this.map(function() { var $this = $(this); var editor = $this.data("Arte"); if (options && typeof(options) === "string") { @@ -28,7 +35,7 @@ } var returnValue = editor[methodName].call(editor, args); - result.push(returnValue); + return returnValue; } else { // If $this is not a rich text editor, construct the editor if (!editor) { @@ -37,9 +44,8 @@ editor = new $.Arte.TextArea(options); $this.data("Arte", editor); } - result.push(editor); + return editor; } }); - return $(result); }; })(jQuery); diff --git a/src/editor/core/Commands.js b/src/editor/core/Commands.js index 3807352..a18dc6f 100644 --- a/src/editor/core/Commands.js +++ b/src/editor/core/Commands.js @@ -1,4 +1,4 @@ -/// @dependencies: Arte.js, TextArea.js +/// @dependencies: Arte.js, TextArea.js /** * @fileoverview extends Arte prototype to add rich text commands */ @@ -11,12 +11,20 @@ var commandAttr = constants.commandAttrType; /** * Determine the command attribute using the options + * @param {String} commandName - name of the command get attrType for + * @param {Object} options + * @param {Object} [options.commandAttrType] - pass a commandAttrType in the options if you want to use it + * @param {Object} [options.styleName] - will use styleName as the attrType if you pass one in + * @param {Object} [options.className] - will use className as the attrType if you pass one in + * @param {Object} [options.tagName] - will use tagName as the attrType if you pass one in + * @return {String} returns the attrType in this order + * 1. If it was defined in the config for the command use that + * 2. If no options object then return commandAttrType object from configuration + * 3. If options defines a commandAttrType use that + * 4. If a styleName/className/tagName is passed in options use the styleName/className/tagName + * properties from $.Arte.constants as the attrType */ var commandAttrType = function(commandName, options) { - // commandAttrType is selected based on the following precedence - // 1) As defined in the options - // 2) Infer from the options - // 3) Use default var commandConfig = configuration.commands[commandName]; if (commandConfig.commandAttrType) { return commandConfig.commandAttrType; @@ -41,8 +49,10 @@ return attrType; }; - /* + /** * Executes a rich text command + * @param {String} commandName - name of the command to call + * @param {Object} options */ var exec = function(commandName, options) { var commandOptions = constructCommandOptions.call(this, commandName, options); @@ -50,7 +60,7 @@ commandOptions.execute = true; this.triggerEvent(constants.eventNames.onbeforecommand, commandOptions); - if (!commandOptions.execute) { // The client requested the cancelation of this command + if (!commandOptions.execute) { // The client requested the cancellation of this command return; } @@ -59,6 +69,17 @@ this.triggerEvent(constants.eventNames.oncommand, commandOptions); }; + /** + * Get the correct command value based on the given parameters + * @param {String} commandName - name of the command get the value for + * @param {String} attrType - attrType you are using the command with + * @param {Object|String} options - either an options object or it can just be a string containing the commandValue + * @param {Object} [options.styleValue] - if attrType == styleName and you pass this in then styleValue will be the the commandValue + * @param {Object} [options.className] - if attrType == className and you pass this in then className will be the the commandValue + * @param {Object} [options.commandValue] - if you pass in a commandValue that will be returned + * @return {String} try to return commandValue using options that were passed in, + * if none are found return the default value for the command specified in the configuration + */ var getCommandValueOrDefault = function(commandName, attrType, options) { if (options && attrType === commandAttr.styleName && options.styleValue) { return options.styleValue; @@ -79,6 +100,15 @@ } }; + /** + * Get the tagName for the given command + * @param {String} commandName - name of the command get the tagName for + * @param {String} attrType - attrType you are using the command with - used to find the right tag + * (for example bold uses the B tag if we want to apply tags or a SPAN if applying styles/classes) + * @param {Object} options + * @param {String} options.tagName - if you already know the tagName and put it in the options it will be returned back + * @return {String} return tagName for the given command or if there is none just return default inline/block tag based on command type + */ var getTagNameOrDefault = function(commandName, attrType, options) { if (options && options.tagName) { return options.tagName; @@ -99,6 +129,12 @@ configuration.defaultInlineTag : configuration.defaultBlockTag; }; + /** + * Creates command options to use for the command based on the configuration + * @param {String} commandName - name of the command generate the options for + * @param {Object} options - a object containing various options + * @return {Object} return tagName for the given command or if there is none just return default inline/block tag based on command type + */ var constructCommandOptions = function(commandName, options) { var attr = commandAttrType(commandName, options); var commandConfig = configuration.commands[commandName]; @@ -184,7 +220,15 @@ "textAlign": function(options) { exec.apply(this, ["textAlign", options]); }, - // Apply the styles/classes to the content editable element + /** + * Toggles styles + * @param {Object} [options] + * @param {jQuery} [options.element] - pass in your own element or use the rich text area by default + * @param {Object} [options.styleName] - name of style you would like to toggle on the element (e.g. font-weight) + * @param {Object} [options.styleValue] - value for the styleName defined above + * that you would like to toggle on the element (e.g. bold) + * @param {Object} [options.className] - name of css class you would like to toggle on the element (e.g. arte-font-weight) + */ "toggleStyleOnElement": function(options) { var element = options.element || this.$el; if (options && options.styleName) { diff --git a/src/editor/core/Configuration.js b/src/editor/core/Configuration.js index e048609..518e661 100644 --- a/src/editor/core/Configuration.js +++ b/src/editor/core/Configuration.js @@ -1,4 +1,4 @@ -/* +/* * This file lists the configuration and constants used by ArteJS */ (function($) { @@ -113,7 +113,7 @@ commandAttrType: constants.commandAttrType.styleName, /* - * An attributed added to the dom element to identify that dom element as rich text field + * An attribute added to the dom element to identify that dom element as rich text field */ textFieldIdentifier: "rteTextField", diff --git a/src/editor/core/PluginManager.js b/src/editor/core/PluginManager.js index ba6fdb7..07f7a60 100644 --- a/src/editor/core/PluginManager.js +++ b/src/editor/core/PluginManager.js @@ -13,16 +13,16 @@ register: function(name, plugin) { this.plugins[name] = plugin; }, - /* + /** * Initializes the plugin * @param {Arte} an instance of Arte */ init: function(richTextEditor) { richTextEditor.pluginInstances = richTextEditor.pluginInstances || []; for (var pluginName in this.plugins) { - var pluginInstanse = new this.plugins[pluginName](); - pluginInstanse.init(richTextEditor); - richTextEditor.pluginInstances.push(pluginInstanse); + var pluginInstance = new this.plugins[pluginName](); + pluginInstance.init(richTextEditor); + richTextEditor.pluginInstances.push(pluginInstance); } } }; diff --git a/src/editor/core/TextArea.js b/src/editor/core/TextArea.js index 7c9827f..5d8baef 100644 --- a/src/editor/core/TextArea.js +++ b/src/editor/core/TextArea.js @@ -1,4 +1,4 @@ -/// dependencies: Arte.js +/// dependencies: Arte.js (function($) { $.Arte = $.Arte || {}; $.Arte.TextArea = function(options) { @@ -70,7 +70,7 @@ } me._container.appendChild(me.el); me.$el = $(me.el); - // Use an existing DIV or TEXTAREA if it already exists + // Use an existing DIV or TEXTAREA if it already exists } else { me.el = me._container.childNodes[0]; if (me.el.tagName === "DIV") { @@ -170,13 +170,19 @@ }; $.extend($.Arte.TextArea.prototype, { - // Get innerHtml of the contentEditable element + /** + * Get or Set innerHtml of the contentEditable element + * @params {string} value string to set innerHTML of element to + * @returns {string} returns 'innerHTML' of the contentEditable element + * if in rich text mode or 'value' of the element if in plaintext mode + * if getting value, otherwise returns nothing + */ "value": function(value) { var constants = $.Arte.constants; var prop = this.editorType === constants.editorTypes.richText ? "innerHTML" : "value"; var currentValue = this.el[prop]; - if (typeof(value) === "undefined") { + if (typeof (value) === "undefined") { return currentValue; } @@ -184,6 +190,7 @@ return; } + //TODO should we return this so that we can chain things? this.el[prop] = value; this._currentOuterValue = this._container.innerHTML; this.triggerEvent(constants.eventNames.onvaluechange, { @@ -191,9 +198,14 @@ src: "external" }); }, - // Get outerHtml of the contentEditable element + + /** + * Gets or sets outerHtml of the contentEditable element + * @params {string} value html string to set outerHTML of element to + * @returns {string} returns 'outerHTML' of the contentEditable element with contentEditable tag removed + */ "outerValue": function(value) { - if (typeof(value) === "undefined") { + if (typeof (value) === "undefined") { var clone = this.$element.clone(); clone.children().removeAttr("contenteditable"); return clone.html(); @@ -204,6 +216,11 @@ this.el.setAttribute("class", newElement.getAttribute("class") || ""); this.value(newElement.innerHTML); }, + + /** + * Calls a focus event on the contentEditable element, moves the cursor + * to the end of that element, and fires an onselectionchange event + */ "focus": function() { var me = this; var focusHandler = function() { @@ -214,13 +231,23 @@ me.$el.on("focus", focusHandler); me.$el.focus(); }, + + /** + * Triggers the event passed in on the contentEditable element with data provided + * @params {string} name - name of the event you want to trigger + * @params {object} data - extra parameters to pass into the event handler + */ "triggerEvent": function(name, data) { this.$element.trigger(name, $.extend(data, { textArea: this })); }, + + /** + * Removes Arte from this element (Converts the rich text editor to non-editable state and remove rich text state information) + * @params {Object} options - pass in 'removeContent' in options object to also clear the element of all text and formatting + */ "destroy": function(options) { - // Converts the rich text editor to non-editable state and remove rich text state information this.$element.removeData("Arte"); this.$element.removeAttr($.Arte.configuration.textFieldIdentifier); this.$element.off(); @@ -233,12 +260,17 @@ this.$element.empty(); } }, + /** - * on/off methods to support attaching events handler using a rich text instance + * Listen to events on the Arte.TextAreas element (same as adding events directly to the element) */ on: function(type, handler) { this.$element.on(type, handler); }, + + /** + * Stop listening to events on the Arte.TextAreas element (same as adding events directly to the element) + */ off: function(type, handler) { this.$element.off(type, handler); } diff --git a/src/editor/core/Util.js b/src/editor/core/Util.js index a0f46d7..2d73c28 100644 --- a/src/editor/core/Util.js +++ b/src/editor/core/Util.js @@ -1,11 +1,13 @@ -/** +/** * @fileoverview: Various utility functions */ (function($) { $.Arte = $.Arte || {}; $.Arte.util = { - /* + /** * Ensure that if there is a user selection, it is inside of the selected element. + * @param {jQuery object} jElement - the element to check the selection against + * @return {boolean} whether or not there is a selection in the given element */ isSelectionInElement: function(jElement) { var selection = rangy.getSelection(); @@ -13,7 +15,7 @@ return range && (range.startContainer === jElement.get(0) || jElement.has(range.startContainer).length !== 0); }, - /* + /** * Move cursor to the end of the input element * @param {htmlElement} element */ @@ -26,7 +28,7 @@ selection.setSingleRange(range); }, - /* + /** * Evaluates if all elements in the collection match the condition specified in the callback function * @param {Array|Object} collection of objects to evaluate * @param {function} callback a function that returns true/false for each object @@ -40,7 +42,7 @@ }); return result; }, - /* + /** * Evaluates if any elements in the collection match the condition specified in the callback function * @param {Array|Object} collection of objects to evaluate * @param {function} callback a function that returns true/false for each object @@ -54,7 +56,7 @@ }); return result; }, - /* + /** * Filter a collection based on the result of the callback function * @param {Array|Object} collection of objects to evaluate * @param {function} callback a function that returns true/false for each object @@ -71,6 +73,9 @@ }, /** * Returns selected text nodes + * @param {boolean} allowCollapsedSelection - whether or not to return the parent of the node + * with the cursor if the selection is collapsed + * @return {Array} array of selected text nodes */ getSelectedTextNodes: function(allowCollapsedSelection) { var selectedNodes = $(); @@ -88,7 +93,7 @@ return selectedNodes; } - // In case we don"t have a valid selection, + // In case we don't have a valid selection, range = rangy.createRangyRange(); range.selectNodeContents(this.$el.get(0)); selectedNodes = rangy.util.getTextNodes(range); @@ -99,8 +104,12 @@ } return selectedNodes; }, - /* - * Identify the ArteJS command configuration from className, styleName or tagName + /** + * Identify the ArteJS command configuration from className, styleName or tagName. + * @param {Object} options - an object containing either a commandName, className, styleName, + * or tagName attribute + * @return {Object} returns the command configuration that matched to the parameter passed in or null + * if no commands are found */ getCommandConfig: function(options) { var result = null; @@ -115,7 +124,7 @@ return configuration.commands[options.commandName]; } - /* Infer the command from the properties in the options. */ + // Infer the command from the properties in the options. for (var command in configuration.commands) { var commandConfig = configuration.commands[command]; diff --git a/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js b/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js index c79f59d..95ad8d8 100644 --- a/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js +++ b/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js @@ -157,7 +157,7 @@ * Create a dom element with options * @param {object} a set of dom element creation options * tagName, attr, styleName+styleValue, className - * @return {jNode} A jQuery element + * @return {jQuery} A jQuery element */ createContainer: function(options) { var tagName = options.applierTagName || "div"; diff --git a/src/editor/lib/rangy-extensions/rangy-extensions.js b/src/editor/lib/rangy-extensions/rangy-extensions.js index 668d5ab..8ffb98c 100644 --- a/src/editor/lib/rangy-extensions/rangy-extensions.js +++ b/src/editor/lib/rangy-extensions/rangy-extensions.js @@ -190,19 +190,19 @@ $(document).ready(function() { * @return rangyRange object */ - function createRangeFromElements(startElement, endElement, excludStartEndElements) { + function createRangeFromElements(startElement, endElement, excludeStartEndElements) { var range = rangy.createRangyRange(); - var startOp = excludStartEndElements ? "setStartAfter" : "setStartBefore"; - var endop = excludStartEndElements ? "setEndBefore" : "setEndAfter"; + var startOp = excludeStartEndElements ? "setStartAfter" : "setStartBefore"; + var endOp = excludeStartEndElements ? "setEndBefore" : "setEndAfter"; range[startOp](startElement); - range[endop](endElement); + range[endOp](endElement); return range; } /** * Get non-whitespace text nodes * @param {rangyRange} Range object - * @return Collection of matched text nodes + * @return {array} array of matched text nodes */ function getTextNodes(range) { if (!range.collapsed) { diff --git a/src/editor/plugins/InsertCommand.js b/src/editor/plugins/InsertCommand.js index 02da12f..4374a55 100644 --- a/src/editor/plugins/InsertCommand.js +++ b/src/editor/plugins/InsertCommand.js @@ -4,6 +4,13 @@ (function(pluginManager) { var InsertCommand = function() { var publicApi = { + /** + * Appends content to end of element if there is no selection. + * If there is a selection insert content at cursor position and replace all content in selection. + * Selects new content when done. + * @param {Object} options - options object will be passed into the onbeforeinsert/onafterinsert events + * @param {Object} options.commandValue - the html you want to insert + */ insert: function(options) { $.extend(options, { execute: true diff --git a/src/editor/plugins/StateDetector.js b/src/editor/plugins/StateDetector.js index 6231abf..4c77ba8 100644 --- a/src/editor/plugins/StateDetector.js +++ b/src/editor/plugins/StateDetector.js @@ -76,6 +76,13 @@ // Extend the prototype of the TextArea to expose the public API $.extend($.Arte.TextArea.prototype, { + /** + * Same as getAllStates however will only return the states that are applied to every node in the selection + * @param {String} [commandName] If provided, only result the state of the given command (ie: fontFamily, bold, etc) + * @return {Object} returns object containing each commandName and the value only if it is applied + * to every single node in the selection (if there are multiple nodes in the selection + * and they have different values for the given command then the value will be false/null) + */ getState: function(commandName) { var selectedNodes = $.Arte.util.getSelectedTextNodes.apply(this, [true]); return getSelectedNodesState(selectedNodes, commandName); @@ -84,7 +91,9 @@ /** * Get an array of all the states found within the current selection * (ie: if the current selection has both a bold and a non-bold component, get two results representing that) - * @param {commandName} string. Optional. If provided, only result the state of the given command (ie: fontFamily, bold, etc) + * @param {String} [commandName] If provided, only return the state of the given command (ie: fontFamily, bold, etc) + * @return {Array} returns array containing an object for each selected node + * that lists out the commandName and it's value for that node */ getAllStates: function(commandName) { var selectedNodes = $.Arte.util.getSelectedTextNodes.apply(this, [true]);