2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('content-editable', function (Y, NAME) {
10 /*jshint maxlen: 500 */
12 * Creates a component to work with an elemment.
13 * @class ContentEditable
14 * @for ContentEditable
15 * @extends Y.Plugin.Base
18 * @submodule content-editable
24 EVENT_CONTENT_READY = 'contentready',
25 EVENT_READY = 'ready',
30 CONTAINER = 'container',
31 CONTENT_EDITABLE = 'contentEditable',
35 INNER_HTML = 'innerHTML',
37 PARENT_NODE = 'parentNode',
42 ContentEditable = function() {
43 ContentEditable.superclass.constructor.apply(this, arguments);
46 Y.extend(ContentEditable, Y.Plugin.Base, {
49 * Internal reference set when render is called.
57 * Internal reference to the YUI instance bound to the element
65 * Initializes the ContentEditable instance
69 initializer: function() {
70 var host = this.get(HOST);
76 this._eventHandles = [];
78 this.publish(EVENT_READY, {
80 defaultFn: this._defReadyFn
85 * Destroys the instance.
89 destructor: function() {
90 new Y.EventHandle(this._eventHandles).detach();
92 this._container.removeAttribute(CONTENT_EDITABLE);
96 * Generic handler for all DOM events fired by the Editor container. This handler
97 * takes the current EventFacade and augments it to fire on the ContentEditable host. It adds two new properties
98 * to the EventFacade called frameX and frameY which adds the scroll and xy position of the ContentEditable element
99 * to the original pageX and pageY of the event so external nodes can be positioned over the element.
100 * In case of ContentEditable element these will be equal to pageX and pageY of the container.
102 * @method _onDomEvent
103 * @param {Event.Facade} e
105 _onDomEvent: function(e) {
108 e.frameX = e.frameY = 0;
110 if (e.pageX > 0 || e.pageY > 0) {
111 if (e.type.substring(0, 3) !== KEY) {
112 xy = this._container.getXY();
119 e.frameTarget = e.target;
120 e.frameCurrentTarget = e.currentTarget;
123 this.fire('dom:' + e.type, e);
127 * Simple pass thru handler for the paste event so we can do content cleanup
130 * @param {Event.Facade} e
132 _DOMPaste: function(e) {
133 var inst = this.getInstance(),
134 data = EMPTY, win = inst.config.win;
136 if (e._event.originalTarget) {
137 data = e._event.originalTarget;
140 if (e._event.clipboardData) {
141 data = e._event.clipboardData.getData(TEXT);
144 if (win.clipboardData) {
145 data = win.clipboardData.getData(TEXT);
147 if (data === EMPTY) { // Could be empty, or failed
149 if (!win.clipboardData.setData(TEXT, data)) {
155 e.frameTarget = e.target;
156 e.frameCurrentTarget = e.currentTarget;
162 getData: function() {
167 Y.log('Failed to collect clipboard data', 'warn', 'contenteditable');
169 e.clipboardData = null;
172 this.fire('dom:paste', e);
176 * Binds DOM events and fires the ready event
178 * @method _defReadyFn
180 _defReadyFn: function() {
181 var inst = this.getInstance(),
182 container = this.get(CONTAINER);
185 ContentEditable.DOM_EVENTS,
186 function(value, key) {
187 var fn = Y.bind(this._onDomEvent, this),
188 kfn = ((Y.UA.ie && ContentEditable.THROTTLE_TIME > 0) ? Y.throttle(fn, ContentEditable.THROTTLE_TIME) : fn);
190 if (!inst.Node.DOM_EVENTS[key]) {
191 inst.Node.DOM_EVENTS[key] = 1;
195 if (key !== FOCUS && key !== BLUR && key !== PASTE) {
196 if (key.substring(0, 3) === KEY) {
197 //Throttle key events in IE
198 this._eventHandles.push(container.on(key, kfn, container));
200 this._eventHandles.push(container.on(key, fn, container));
208 inst.Node.DOM_EVENTS.paste = 1;
210 this._eventHandles.push(
211 container.on(PASTE, Y.bind(this._DOMPaste, this), container),
212 container.on(FOCUS, Y.bind(this._onDomEvent, this), container),
213 container.on(BLUR, Y.bind(this._onDomEvent, this), container)
216 inst.__use = inst.use;
218 inst.use = Y.bind(this.use, this);
222 * Called once the content is available in the ContentEditable element and calls the final use call
224 * @method _onContentReady
225 * on the internal instance so that the modules are loaded properly.
227 _onContentReady: function(event) {
231 var inst = this.getInstance(),
232 args = Y.clone(this.get(USE));
234 this.fire(EVENT_CONTENT_READY);
236 Y.log('On content available', 'info', 'contenteditable');
239 inst.config.doc = YNode.getDOMNode(event.target);
242 args.push(Y.bind(function() {
243 Y.log('Callback from final internal use call', 'info', 'contenteditable');
245 if (inst.EditorSelection) {
246 inst.EditorSelection.DEFAULT_BLOCK_TAG = this.get('defaultblock');
248 inst.EditorSelection.ROOT = this.get(CONTAINER);
251 this.fire(EVENT_READY);
254 Y.log('Calling use on internal instance: ' + args, 'info', 'contentEditable');
256 inst.use.apply(inst, args);
261 * Retrieves defaultblock value from host attribute
263 * @method _getDefaultBlock
266 _getDefaultBlock: function() {
267 return this._getHostValue('defaultblock');
271 * Retrieves dir value from host attribute
276 _getDir: function() {
277 return this._getHostValue('dir');
281 * Retrieves extracss value from host attribute
283 * @method _getExtraCSS
286 _getExtraCSS: function() {
287 return this._getHostValue('extracss');
291 * Get the content from the container
294 * @param {String} html The raw HTML from the container.
297 _getHTML: function() {
301 container = this.get(CONTAINER);
303 html = container.get(INNER_HTML);
310 * Retrieves a value from host attribute
312 * @method _getHostValue
313 * @param {attr} The attribute which value should be returned from the host
314 * @return {String|Object}
316 _getHostValue: function(attr) {
317 var host = this.get(HOST);
320 return host.get(attr);
325 * Set the content of the container
328 * @param {String} html The raw HTML to set to the container.
331 _setHTML: function(html) {
333 var container = this.get(CONTAINER);
335 container.set(INNER_HTML, html);
337 //This needs to be wrapped in a contentready callback for the !_ready state
338 this.once(EVENT_CONTENT_READY, Y.bind(this._setHTML, this, html));
345 * Set's the linked CSS on the instance.
347 * @method _setLinkedCSS
348 * @param {css} String The linkedcss value
351 _setLinkedCSS: function(css) {
353 var inst = this.getInstance();
356 //This needs to be wrapped in a contentready callback for the !_ready state
357 this.once(EVENT_CONTENT_READY, Y.bind(this._setLinkedCSS, this, css));
364 * Set's the dir (language direction) attribute on the container.
367 * @param {value} String The language direction
370 _setDir: function(value) {
374 container = this.get(CONTAINER);
376 container.setAttribute('dir', value);
378 //This needs to be wrapped in a contentready callback for the !_ready state
379 this.once(EVENT_CONTENT_READY, Y.bind(this._setDir, this, value));
386 * Set's the extra CSS on the instance.
388 * @method _setExtraCSS
389 * @param {css} String The CSS style to be set as extra css
392 _setExtraCSS: function(css) {
395 var inst = this.getInstance(),
396 head = inst.one('head');
398 if (this._extraCSSNode) {
399 this._extraCSSNode.remove();
402 this._extraCSSNode = YNode.create('<style>' + css + '</style>');
404 head.append(this._extraCSSNode);
407 //This needs to be wrapped in a contentready callback for the !_ready state
408 this.once(EVENT_CONTENT_READY, Y.bind(this._setExtraCSS, this, css));
415 * Set's the language value on the instance.
418 * @param {value} String The language to be set
421 _setLang: function(value) {
425 container = this.get(CONTAINER);
427 container.setAttribute('lang', value);
429 //This needs to be wrapped in a contentready callback for the !_ready state
430 this.once(EVENT_CONTENT_READY, Y.bind(this._setLang, this, value));
437 * Called from the first YUI instance that sets up the internal instance.
438 * This loads the content into the ContentEditable element and attaches the contentready event.
440 * @method _instanceLoaded
441 * @param {YUI} inst The internal YUI instance bound to the ContentEditable element
443 _instanceLoaded: function(inst) {
444 this._instance = inst;
446 this._onContentReady();
448 var doc = this._instance.config.doc;
452 //Force other browsers into non CSS styling
453 doc.execCommand('styleWithCSS', false, false);
454 doc.execCommand('insertbronreturn', false, false);
461 * Validates linkedcss property
463 * @method _validateLinkedCSS
466 _validateLinkedCSS: function(value) {
467 return Lang.isString(value) || Lang.isArray(value);
470 //BEGIN PUBLIC METHODS
472 * This is a scoped version of the normal YUI.use method & is bound to the ContentEditable element
473 * At setup, the inst.use method is mapped to this method.
477 Y.log('Calling augmented use after ready', 'info', 'contenteditable');
479 var inst = this.getInstance(),
480 args = Y.Array(arguments),
483 if (Lang.isFunction(args[args.length - 1])) {
484 callback = args.pop();
488 args.push(function() {
489 Y.log('Internal callback from augmented use', 'info', 'contenteditable');
491 callback.apply(inst, arguments);
495 return inst.__use.apply(inst, args);
499 * A delegate method passed to the instance's delegate method
501 * @param {String} type The type of event to listen for
502 * @param {Function} fn The method to attach
503 * @param {String, Node} cont The container to act as a delegate, if no "sel" passed, the container is assumed.
504 * @param {String} sel The selector to match in the event (optional)
505 * @return {EventHandle} The Event handle returned from Y.delegate
507 delegate: function(type, fn, cont, sel) {
508 var inst = this.getInstance();
511 Y.log('Delegate events can not be attached until after the ready event has fired.', 'error', 'contenteditable');
519 cont = this.get(CONTAINER);
522 return inst.delegate(type, fn, cont, sel);
526 * Get a reference to the internal YUI instance.
527 * @method getInstance
528 * @return {YUI} The internal YUI instance
530 getInstance: function() {
531 return this._instance;
536 * @param {String/HTMLElement/Node} node The node to render to
537 * @return {ContentEditable}
540 render: function(node) {
543 if (this._rendered) {
544 Y.log('Container already rendered.', 'warn', 'contentEditable');
550 this.set(CONTAINER, node);
553 container = this.get(CONTAINER);
556 container = YNode.create(ContentEditable.HTML);
558 Y.one('body').prepend(container);
560 this.set(CONTAINER, container);
563 this._rendered = true;
565 this._container.setAttribute(CONTENT_EDITABLE, true);
567 args = Y.clone(this.get(USE));
569 fn = Y.bind(function() {
572 inst.host = this.get(HOST); //Cross reference to Editor
574 inst.log = Y.log; //Dump the instance logs to the parent instance.
576 Y.log('Creating new internal instance with node-base only', 'info', 'contenteditable');
577 inst.use('node-base', Y.bind(this._instanceLoaded, this));
582 Y.log('Adding new modules to main instance: ' + args, 'info', 'contenteditable');
583 Y.use.apply(Y, args);
589 * Set the focus to the container
591 * @param {Function} fn Callback function to execute after focus happens
592 * @return {ContentEditable}
596 this._container.focus();
601 * Show the iframe instance
603 * @return {ContentEditable}
607 this._container.show();
615 * Hide the iframe instance
617 * @return {ContentEditable}
621 this._container.hide();
628 * The throttle time for key events in IE
630 * @property THROTTLE_TIME
637 * The DomEvents that the frame automatically attaches and bubbles
639 * @property DOM_EVENTS
656 * The template string used to create the ContentEditable element
664 * The name of the class (contentEditable)
669 NAME: 'contentEditable',
672 * The namespace on which ContentEditable plugin will reside.
676 * @default 'contentEditable'
679 NS: CONTENT_EDITABLE,
683 * The default text direction for this ContentEditable element. Default: ltr
689 validator: Lang.isString,
695 * The container to set contentEditable=true or to create on render.
696 * @attribute container
697 * @type String/HTMLElement/Node
700 setter: function(n) {
701 this._container = Y.one(n);
703 return this._container;
708 * The string to inject as Editor content. Default '<br>'
716 validator: Lang.isString,
721 * The default tag to use for block level items, defaults to: p
722 * @attribute defaultblock
726 validator: Lang.isString,
727 value: TAG_PARAGRAPH,
728 valueFn: '_getDefaultBlock'
732 * A string of CSS to add to the Head of the Editor
733 * @attribute extracss
738 setter: '_setExtraCSS',
739 validator: Lang.isString,
740 valueFn: '_getExtraCSS'
744 * Set the id of the new Node. (optional)
751 getter: function(id) {
753 id = 'inlineedit-' + Y.guid();
761 * The default language. Default: en-US
766 validator: Lang.isString,
773 * An array of url's to external linked style sheets
774 * @attribute linkedcss
778 setter: '_setLinkedCSS',
779 validator: '_validateLinkedCSS'
784 * The Node instance of the container.
792 return this._container;
797 * Array of modules to include in the scoped YUI instance at render time. Default: ['node-base', 'editor-selection', 'stylesheet']
803 validator: Lang.isArray,
805 value: ['node-base', 'editor-selection', 'stylesheet']
810 Y.namespace('Plugin');
812 Y.Plugin.ContentEditable = ContentEditable;
814 }, '3.13.0', {"requires": ["node-base", "editor-selection", "stylesheet", "plugin"]});