4 * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
5 * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
9 * beforeload (for internal use only)
26 * beforecommand:composer
27 * aftercommand:composer
32 (function(wysihtml5) {
36 // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
38 // Whether the editor should look like the textarea (by adopting styles)
40 // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
42 // Whether toolbar is displayed after init by script automatically.
43 // Can be set to false if toolobar is set to display only on editable area focus
44 showToolbarAfterInit: true,
45 // Whether urls, entered by the user should automatically become clickable-links
47 // Includes table editing events and cell selection tracking
49 // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
51 // Object which includes parser rules to apply when html gets cleaned
52 // See parser_rules/*.js for examples
53 parserRules: { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
54 // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
55 pasteParserRulesets: null,
56 // Parser method to use when the user inserts content
57 parser: wysihtml5.dom.parse,
58 // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
59 composerClassName: "wysihtml5-editor",
60 // Class name to add to the body when the wysihtml5 editor is supported
61 bodyClassName: "wysihtml5-supported",
62 // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
64 // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
66 // Placeholder text to use, defaults to the placeholder attribute on the textarea element
67 placeholderText: undef,
68 // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
69 supportTouchDevices: true,
70 // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
72 // Whether to use div instead of secure iframe
73 contentEditableMode: false,
74 // Classname of container that editor should not touch and pass through
75 // Pass false to disable
76 uneditableContainerClassname: "wysihtml5-uneditable-container",
77 // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
78 // Also copied source is based directly on selection -
79 // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
80 // If falsy value is passed source override is also disabled
81 copyedFromMarking: '<meta name="copied-from" content="wysihtml5">'
84 wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
85 /** @scope wysihtml5.Editor.prototype */ {
86 constructor: function(editableElement, config) {
87 this.editableElement = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
88 this.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
89 this._isCompatible = wysihtml5.browser.supported();
91 if (this.editableElement.nodeName.toLowerCase() != "textarea") {
92 this.config.contentEditableMode = true;
93 this.config.noTextarea = true;
95 if (!this.config.noTextarea) {
96 this.textarea = new wysihtml5.views.Textarea(this, this.editableElement, this.config);
97 this.currentView = this.textarea;
100 // Sort out unsupported/unwanted browsers here
101 if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
103 setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
107 // Add class name to body, to indicate that the editor is supported
108 wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
110 this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
111 this.currentView = this.composer;
113 if (typeof(this.config.parser) === "function") {
117 this.on("beforeload", this.handleBeforeLoad);
120 handleBeforeLoad: function() {
121 if (!this.config.noTextarea) {
122 this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
124 if (this.config.toolbar) {
125 this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit);
129 isCompatible: function() {
130 return this._isCompatible;
134 this.currentView.clear();
138 getValue: function(parse, clearInternals) {
139 return this.currentView.getValue(parse, clearInternals);
142 setValue: function(html, parse) {
143 this.fire("unset_placeholder");
149 this.currentView.setValue(html, parse);
153 cleanUp: function() {
154 this.currentView.cleanUp();
157 focus: function(setToEnd) {
158 this.currentView.focus(setToEnd);
163 * Deactivate editor (make it readonly)
165 disable: function() {
166 this.currentView.disable();
174 this.currentView.enable();
178 isEmpty: function() {
179 return this.currentView.isEmpty();
182 hasPlaceholderSet: function() {
183 return this.currentView.hasPlaceholderSet();
186 parse: function(htmlOrElement, clearInternals) {
187 var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
188 var returnValue = this.config.parser(htmlOrElement, {
189 "rules": this.config.parserRules,
190 "cleanUp": this.config.cleanUp,
191 "context": parseContext,
192 "uneditableClass": this.config.uneditableContainerClassname,
193 "clearInternals" : clearInternals
195 if (typeof(htmlOrElement) === "object") {
196 wysihtml5.quirks.redraw(htmlOrElement);
202 * Prepare html parser logic
203 * - Observes for paste and drop
205 _initParser: function() {
210 if (wysihtml5.browser.supportsModenPaste()) {
211 this.on("paste:composer", function(event) {
212 event.preventDefault();
213 oldHtml = wysihtml5.dom.getPastedHtml(event);
215 that._cleanAndPaste(oldHtml);
220 this.on("beforepaste:composer", function(event) {
221 event.preventDefault();
222 wysihtml5.dom.getPastedHtmlWithDiv(that.composer, function(pastedHTML) {
224 that._cleanAndPaste(pastedHTML);
232 _cleanAndPaste: function (oldHtml) {
233 var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
234 "referenceNode": this.composer.element,
235 "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
236 "uneditableClass": this.config.uneditableContainerClassname
238 this.composer.selection.deleteContents();
239 this.composer.selection.insertHTML(cleanHtml);