1 /*! 2 * This file is part of Aloha Editor Project http://aloha-editor.org 3 * Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com 4 * Contributors http://aloha-editor.org/contribution.php 5 * Licensed unter the terms of http://www.aloha-editor.org/license.html 6 * 7 * Aloha Editor is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU Affero General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * ( at your option ) any later version.* 11 * 12 * Aloha Editor is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 define( [ 22 'aloha/core', 23 'util/class', 24 'aloha/jquery', 25 'aloha/pluginmanager', 26 'aloha/floatingmenu', 27 'aloha/selection', 28 'aloha/markup', 29 'aloha/contenthandlermanager', 30 'aloha/console' 31 ], function( Aloha, Class, jQuery, PluginManager, FloatingMenu, Selection, 32 Markup, ContentHandlerManager, console ) { 33 'use strict'; 34 35 var unescape = window.unescape, 36 GENTICS = window.GENTICS, 37 38 // True, if the next editable activate event should not be handled 39 ignoreNextActivateEvent = false; 40 41 // default supported and custom content handler settings 42 // @TODO move to new config when implemented in Aloha 43 Aloha.defaults.contentHandler = {}; 44 Aloha.defaults.contentHandler.initEditable = [ 'sanitize' ]; 45 Aloha.defaults.contentHandler.getContents = [ 'sanitize' ]; 46 47 // The insertHtml contenthandler ( paste ) will, by default, use all 48 // registered content handlers. 49 //Aloha.defaults.contentHandler.insertHtml = void 0; 50 51 if ( typeof Aloha.settings.contentHandler === 'undefined' ) { 52 Aloha.settings.contentHandler = {}; 53 } 54 55 var defaultContentSerializer = function(editableElement){ 56 return jQuery(editableElement).html(); 57 }; 58 59 var contentSerializer = defaultContentSerializer; 60 61 /** 62 * Editable object 63 * @namespace Aloha 64 * @class Editable 65 * @method 66 * @constructor 67 * @param {Object} obj jQuery object reference to the object 68 */ 69 Aloha.Editable = Class.extend( { 70 71 _constructor: function( obj ) { 72 // check wheter the object has an ID otherwise generate and set 73 // globally unique ID 74 if ( !obj.attr( 'id' ) ) { 75 obj.attr( 'id', GENTICS.Utils.guid() ); 76 } 77 78 // store object reference 79 this.obj = obj; 80 this.originalObj = obj; 81 this.ready = false; 82 83 // delimiters, timer and idle for smartContentChange 84 // smartContentChange triggers -- tab: '\u0009' - space: '\u0020' - enter: 'Enter' 85 this.sccDelimiters = [ ':', ';', '.', '!', '?', ',', 86 unescape( '%u0009' ), unescape( '%u0020' ), 'Enter' ]; 87 this.sccIdle = 5000; 88 this.sccDelay = 500; 89 this.sccTimerIdle = false; 90 this.sccTimerDelay = false; 91 92 // see keyset http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html 93 this.keyCodeMap = { 94 93 : 'Apps', // The Application key 95 18 : 'Alt', // The Alt ( Menu ) key. 96 20 : 'CapsLock', // The Caps Lock ( Capital ) key. 97 17 : 'Control', // The Control ( Ctrl ) key. 98 40 : 'Down', // The Down Arrow key. 99 35 : 'End', // The End key. 100 13 : 'Enter', // The Enter key. 101 112 : 'F1', // The F1 key. 102 113 : 'F2', // The F2 key. 103 114 : 'F3', // The F3 key. 104 115 : 'F4', // The F4 key. 105 116 : 'F5', // The F5 key. 106 117 : 'F6', // The F6 key. 107 118 : 'F7', // The F7 key. 108 119 : 'F8', // The F8 key. 109 120 : 'F9', // The F9 key. 110 121 : 'F10', // The F10 key. 111 122 : 'F11', // The F11 key. 112 123 : 'F12', // The F12 key. 113 114 // Anybody knows the keycode for F13-F24? 115 36 : 'Home', // The Home key. 116 45 : 'Insert', // The Insert ( Ins ) key. 117 37 : 'Left', // The Left Arrow key. 118 224 : 'Meta', // The Meta key. 119 34 : 'PageDown', // The Page Down ( Next ) key. 120 33 : 'PageUp', // The Page Up key. 121 19 : 'Pause', // The Pause key. 122 44 : 'PrintScreen', // The Print Screen ( PrintScrn, SnapShot ) key. 123 39 : 'Right', // The Right Arrow key. 124 145 : 'Scroll', // The scroll lock key 125 16 : 'Shift', // The Shift key. 126 38 : 'Up', // The Up Arrow key. 127 91 : 'Win', // The left Windows Logo key. 128 92 : 'Win' // The right Windows Logo key. 129 }; 130 131 this.placeholderClass = 'aloha-placeholder'; 132 133 Aloha.registerEditable( this ); 134 135 this.init(); 136 }, 137 138 /** 139 * Initialize the editable 140 * @return void 141 * @hide 142 */ 143 init: function() { 144 var me = this; 145 146 // TODO make editables their own settings. 147 this.settings = Aloha.settings; 148 149 // smartContentChange settings 150 // @TODO move to new config when implemented in Aloha 151 if ( Aloha.settings && Aloha.settings.smartContentChange ) { 152 if ( Aloha.settings.smartContentChange.delimiters ) { 153 this.sccDelimiters = Aloha.settings.smartContentChange.delimiters; 154 } 155 156 if ( Aloha.settings.smartContentChange.idle ) { 157 this.sccIdle = Aloha.settings.smartContentChange.idle; 158 } 159 160 if ( Aloha.settings.smartContentChange.delay ) { 161 this.sccDelay = Aloha.settings.smartContentChange.delay; 162 } 163 } 164 165 // check if Aloha can handle the obj as Editable 166 if ( !this.check( this.obj ) ) { 167 //Aloha.log( 'warn', this, 'Aloha cannot handle {' + this.obj[0].nodeName + '}' ); 168 this.destroy(); 169 return; 170 } 171 172 // apply content handler to clean up content 173 if ( typeof Aloha.settings.contentHandler.initEditable === 'undefined' ) { 174 Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable; 175 } 176 177 var content = me.obj.html(); 178 content = ContentHandlerManager.handleContent( content, { 179 contenthandler: Aloha.settings.contentHandler.initEditable 180 } ); 181 me.obj.html( content ); 182 183 // only initialize the editable when Aloha is fully ready (including plugins) 184 Aloha.bind( 'aloha-ready', function() { 185 // initialize the object 186 me.obj.addClass( 'aloha-editable' ).contentEditable( true ); 187 188 // add focus event to the object to activate 189 me.obj.mousedown( function( e ) { 190 // check whether the mousedown was already handled 191 if ( !Aloha.eventHandled ) { 192 Aloha.eventHandled = true; 193 return me.activate( e ); 194 } 195 } ); 196 197 me.obj.mouseup( function( e ) { 198 Aloha.eventHandled = false; 199 } ); 200 201 me.obj.focus( function( e ) { 202 return me.activate( e ); 203 } ); 204 205 // by catching the keydown we can prevent the browser from doing its own thing 206 // if it does not handle the keyStroke it returns true and therefore all other 207 // events (incl. browser's) continue 208 me.obj.keydown( function( event ) { 209 me.keyCode = event.which; 210 return Markup.preProcessKeyStrokes( event ); 211 } ); 212 213 // handle keypress 214 me.obj.keypress( function( event ) { 215 // triggers a smartContentChange to get the right charcode 216 // To test try http://www.w3.org/2002/09/tests/keys.html 217 Aloha.activeEditable.smartContentChange( event ); 218 } ); 219 220 // handle shortcut keys 221 me.obj.keyup( function( event ) { 222 if ( event.keyCode === 27 ) { 223 Aloha.deactivateEditable(); 224 return false; 225 } 226 } ); 227 228 // register the onSelectionChange Event with the Editable field 229 me.obj.contentEditableSelectionChange( function( event ) { 230 Selection.onChange( me.obj, event ); 231 return me.obj; 232 } ); 233 234 // mark the editable as unmodified 235 me.setUnmodified(); 236 237 // we don't do the sanitizing on aloha ready, since some plugins add elements into the content and bind events to it. 238 // if we sanitize by replacing the html, all events would get lost. TODO: think about a better solution for the sanitizing, without 239 // destroying the events 240 // // apply content handler to clean up content 241 // var content = me.obj.html(); 242 // if ( typeof Aloha.settings.contentHandler.initEditable === 'undefined' ) { 243 // Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable; 244 // } 245 // content = ContentHandlerManager.handleContent( content, { 246 // contenthandler: Aloha.settings.contentHandler.initEditable 247 // } ); 248 // me.obj.html( content ); 249 250 me.snapshotContent = me.getContents(); 251 252 // FF bug: check for empty editable contents ( no <br>; no whitespace ) 253 if ( jQuery.browser.mozilla ) { 254 me.initEmptyEditable(); 255 } 256 257 me.initPlaceholder(); 258 259 me.ready = true; 260 261 // throw a new event when the editable has been created 262 263 /** 264 * @event editableCreated fires after a new editable has been created, eg. via $( '#editme' ).aloha() 265 * The event is triggered in Aloha's global scope Aloha 266 * @param {Event} e the event object 267 * @param {Array} a an array which contains a reference to the currently created editable on its first position 268 */ 269 Aloha.trigger( 'aloha-editable-created', [ me ] ); 270 } ); 271 }, 272 273 /** 274 * True, if this editable is active for editing 275 * @property 276 * @type boolean 277 */ 278 isActive: false, 279 280 /** 281 * stores the original content to determine if it has been modified 282 * @hide 283 */ 284 originalContent: null, 285 286 /** 287 * every time a selection is made in the current editable the selection has to 288 * be saved for further use 289 * @hide 290 */ 291 range: undefined, 292 293 /** 294 * Check if object can be edited by Aloha Editor 295 * @return {boolean } editable true if Aloha Editor can handle else false 296 * @hide 297 */ 298 299 check: function() { 300 /* TODO check those elements 301 'map', 'meter', 'object', 'output', 'progress', 'samp', 302 'time', 'area', 'datalist', 'figure', 'kbd', 'keygen', 303 'mark', 'math', 'wbr', 'area', 304 */ 305 306 // Extract El 307 var me = this, 308 obj = this.obj, 309 el = obj.get( 0 ), 310 nodeName = el.nodeName.toLowerCase(), 311 312 // supported elements 313 textElements = [ 'a', 'abbr', 'address', 'article', 'aside', 314 'b', 'bdo', 'blockquote', 'cite', 'code', 'command', 315 'del', 'details', 'dfn', 'div', 'dl', 'em', 'footer', 316 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'i', 317 'ins', 'menu', 'nav', 'p', 'pre', 'q', 'ruby', 318 'section', 'small', 'span', 'strong', 'sub', 'sup', 319 'var' ], 320 i, div; 321 322 for ( i = 0; i < textElements.length; ++i ) { 323 if ( nodeName === textElements[ i ] ) { 324 return true; 325 } 326 } 327 328 // special handled elements 329 switch ( nodeName ) { 330 case 'label': 331 case 'button': 332 // TODO need some special handling. 333 break; 334 case 'textarea': 335 // Create a div alongside the textarea 336 div = jQuery( '<div id="' + this.getId() + 337 '-aloha" class="aloha-textarea" />' ) 338 .insertAfter( obj ); 339 340 // Resize the div to the textarea and 341 // Populate the div with the value of the textarea 342 // Then, hide the textarea 343 div.height( obj.height() ) 344 .width( obj.width() ) 345 .html( obj.val() ); 346 347 obj.hide(); 348 349 // Attach a onsubmit to the form to place the HTML of the 350 // div back into the textarea 351 obj.parents( 'form:first' ).submit( function() { 352 obj.val( me.getContents() ); 353 } ); 354 355 // Swap textarea reference with the new div 356 this.obj = div; 357 358 // Supported 359 return true; 360 default: 361 break; 362 } 363 364 // the following elements are not supported 365 /* 366 'canvas', 'audio', 'br', 'embed', 'fieldset', 'hgroup', 'hr', 367 'iframe', 'img', 'input', 'map', 'script', 'select', 'style', 368 'svg', 'table', 'ul', 'video', 'ol', 'form', 'noscript', 369 */ 370 return false; 371 }, 372 373 /** 374 * Init Placeholder 375 * 376 * @return void 377 */ 378 initPlaceholder: function() { 379 if ( Aloha.settings.placeholder && this.isEmpty() ) { 380 this.addPlaceholder(); 381 } 382 }, 383 384 /** 385 * Check if the conteneditable is empty. 386 * 387 * @return {Boolean} 388 */ 389 isEmpty: function() { 390 var editableTrimedContent = jQuery.trim( this.getContents() ), 391 onlyBrTag = ( editableTrimedContent === '<br>' ) ? true : false; 392 return ( editableTrimedContent.length === 0 || onlyBrTag ); 393 }, 394 395 /** 396 * Check if the editable div is not empty. Fixes a FF browser bug 397 * see issue: https://github.com/alohaeditor/Aloha-Editor/issues/269 398 * 399 * @return {undefined} 400 */ 401 initEmptyEditable: function( ) { 402 var obj = this.obj; 403 if ( this.empty( this.getContents() ) ) { 404 jQuery( obj ).prepend( '<br class="aloha-cleanme" />' ); 405 } 406 }, 407 408 /** 409 * Add placeholder in editable 410 * 411 * @return void 412 */ 413 addPlaceholder: function() { 414 var div = jQuery( '<div>' ), 415 span = jQuery( '<span>' ), 416 el, 417 obj = this.obj; 418 419 if ( GENTICS.Utils.Dom.allowsNesting( obj[0], div[0] ) ) { 420 el = div; 421 } else { 422 el = span; 423 } 424 425 jQuery( obj ).append( el.addClass( this.placeholderClass ) ); 426 jQuery.each( 427 Aloha.settings.placeholder, 428 function( selector, selectorConfig ) { 429 if ( obj.is( selector ) ) { 430 el.html( selectorConfig ); 431 } 432 } 433 ); 434 435 // remove browser br 436 jQuery( 'br', obj ).remove(); 437 438 // delete div, span, el; 439 }, 440 441 /** 442 * remove placeholder from contenteditable. If setCursor is true, 443 * will also set the cursor to the start of the selection. However, 444 * this will be ASYNCHRONOUS, so if you rely on the fact that 445 * the placeholder is removed after calling this method, setCursor 446 * should be false ( or not set ) 447 * 448 * @return void 449 */ 450 removePlaceholder: function( obj, setCursor ) { 451 var placeholderClass = this.placeholderClass, 452 range; 453 454 // remove browser br 455 // jQuery( 'br', obj ).remove(); 456 457 // set the cursor // remove placeholder 458 if ( setCursor === true ) { 459 range = Selection.getRangeObject(); 460 if ( !range.select ) { 461 return; 462 } 463 range.startContainer = range.endContainer = obj.get( 0 ); 464 range.startOffset = range.endOffset = 0; 465 range.select(); 466 467 window.setTimeout( function() { 468 jQuery( '.' + placeholderClass, obj ).remove(); 469 }, 20 ); 470 471 } else { 472 jQuery( '.' + placeholderClass, obj ).remove(); 473 } 474 }, 475 476 /** 477 * destroy the editable 478 * @return void 479 */ 480 destroy: function() { 481 // leave the element just to get sure 482 if ( this === Aloha.getActiveEditable() ) { 483 this.blur(); 484 485 // also hide the floating menu if the current editable was active 486 FloatingMenu.hide(); 487 } 488 489 // special handled elements 490 switch ( this.originalObj.get( 0 ).nodeName.toLowerCase() ) { 491 case 'label': 492 case 'button': 493 // TODO need some special handling. 494 break; 495 case 'textarea': 496 // restore content to original textarea 497 this.originalObj.val( this.getContents() ); 498 this.obj.remove(); 499 this.originalObj.show(); 500 break; 501 default: 502 break; 503 } 504 505 // now the editable is not ready any more 506 this.ready = false; 507 508 // remove the placeholder if needed. 509 this.removePlaceholder( this.obj ); 510 511 // initialize the object and disable contentEditable 512 // unbind all events 513 // TODO should only unbind the specific handlers. 514 this.obj.removeClass( 'aloha-editable' ) 515 .contentEditable( false ) 516 .unbind( 'mousedown click dblclick focus keydown keypress keyup' ); 517 518 /* TODO remove this event, it should implemented as bind and unbind 519 // register the onSelectionChange Event with the Editable field 520 this.obj.contentEditableSelectionChange( function( event ) { 521 Aloha.Selection.onChange( me.obj, event ); 522 return me.obj; 523 } ); 524 */ 525 526 // throw a new event when the editable has been created 527 /** 528 * @event editableCreated fires after a new editable has been destroyes, eg. via $( '#editme' ).mahalo() 529 * The event is triggered in Aloha's global scope Aloha 530 * @param {Event} e the event object 531 * @param {Array} a an array which contains a reference to the currently created editable on its first position 532 */ 533 Aloha.trigger( 'aloha-editable-destroyed', [ this ] ); 534 535 // finally register the editable with Aloha 536 Aloha.unregisterEditable( this ); 537 }, 538 539 /** 540 * marks the editables current state as unmodified. Use this method to inform the editable 541 * that it's contents have been saved 542 * @method 543 */ 544 setUnmodified: function() { 545 this.originalContent = this.getContents(); 546 }, 547 548 /** 549 * check if the editable has been modified during the edit process# 550 * @method 551 * @return boolean true if the editable has been modified, false otherwise 552 */ 553 isModified: function() { 554 return this.originalContent !== this.getContents(); 555 }, 556 557 /** 558 * String representation of the object 559 * @method 560 * @return Aloha.Editable 561 */ 562 toString: function() { 563 return 'Aloha.Editable'; 564 }, 565 566 /** 567 * check whether the editable has been disabled 568 */ 569 isDisabled: function() { 570 return !this.obj.contentEditable() 571 || this.obj.contentEditable() === 'false'; 572 }, 573 574 /** 575 * disable this editable 576 * a disabled editable cannot be written on by keyboard 577 */ 578 disable: function() { 579 return this.isDisabled() || this.obj.contentEditable( false ); 580 }, 581 582 /** 583 * enable this editable 584 * reenables a disabled editable to be writteable again 585 */ 586 enable: function() { 587 return this.isDisabled() && this.obj.contentEditable( true ); 588 }, 589 590 591 /** 592 * activates an Editable for editing 593 * disables all other active items 594 * @method 595 */ 596 activate: function( e ) { 597 // get active Editable before setting the new one. 598 var oldActive = Aloha.getActiveEditable(); 599 600 // We need to ommit this call when this flag is set to true. 601 // This flag will only be set to true before the removePlaceholder method 602 // is called since that method invokes a focus event which will again trigger 603 // this method. We want to avoid double invokation of this method. 604 if ( ignoreNextActivateEvent ) { 605 ignoreNextActivateEvent = false; 606 return; 607 } 608 609 // handle special case in which a nested editable is focused by a click 610 // in this case the "focus" event would be triggered on the parent element 611 // which actually shifts the focus away to it's parent. this if is here to 612 // prevent this situation 613 if ( e && e.type === 'focus' && oldActive !== null 614 && oldActive.obj.parent().get( 0 ) === e.currentTarget ) { 615 return; 616 } 617 618 // leave immediately if this is already the active editable 619 if ( this.isActive || this.isDisabled() ) { 620 // we don't want parent editables to be triggered as well, so return false 621 return; 622 } 623 624 this.obj.addClass( 'aloha-editable-active' ); 625 626 Aloha.activateEditable( this ); 627 628 ignoreNextActivateEvent = true; 629 this.removePlaceholder ( this.obj, true ); 630 ignoreNextActivateEvent = false; 631 632 this.isActive = true; 633 634 /** 635 * @event editableActivated fires after the editable has been activated by clicking on it. 636 * This event is triggered in Aloha's global scope Aloha 637 638 * @param {Event} e the event object 639 * @param {Array} a an array which contains a reference to last active editable on its first position, as well 640 * as the currently active editable on it's second position 641 */ 642 // trigger a 'general' editableActivated event 643 Aloha.trigger( 'aloha-editable-activated', { 644 'oldActive' : oldActive, 645 'editable' : this 646 } ); 647 }, 648 649 /** 650 * handle the blur event 651 * this must not be attached to the blur event, which will trigger far too often 652 * eg. when a table within an editable is selected 653 * @hide 654 */ 655 blur: function() { 656 this.obj.blur(); 657 this.isActive = false; 658 this.initPlaceholder(); 659 this.obj.removeClass( 'aloha-editable-active' ); 660 661 /** 662 * @event editableDeactivated fires after the editable has been activated by clicking on it. 663 * This event is triggered in Aloha's global scope Aloha 664 * @param {Event} e the event object 665 * @param {Array} a an array which contains a reference to this editable 666 */ 667 Aloha.trigger( 'aloha-editable-deactivated', { editable : this } ); 668 669 /** 670 * @event smartContentChanged 671 */ 672 Aloha.activeEditable.smartContentChange( { type : 'blur' }, null ); 673 }, 674 675 /** 676 * check if the string is empty 677 * used for zerowidth check 678 * @return true if empty or string is null, false otherwise 679 * @hide 680 */ 681 empty: function( str ) { 682 // br is needed for chrome 683 return ( null === str ) 684 || ( jQuery.trim( str ) === '' || str === '<br/>' ); 685 }, 686 687 /** 688 * Get the contents of this editable as a HTML string 689 * @method 690 * @return contents of the editable 691 */ 692 getContents: function( asObject ) { 693 var clonedObj = this.obj.clone( false ); 694 695 // do core cleanup 696 clonedObj.find( '.aloha-cleanme' ).remove(); 697 this.removePlaceholder( clonedObj ); 698 PluginManager.makeClean( clonedObj ); 699 700 return asObject ? clonedObj.contents() : contentSerializer(clonedObj[0]); 701 }, 702 703 /** 704 * Get the id of this editable 705 * @method 706 * @return id of this editable 707 */ 708 getId: function() { 709 return this.obj.attr( 'id' ); 710 }, 711 712 /** 713 * Generates and signals a smartContentChange event. 714 * 715 * A smart content change occurs when a special editing action, or a 716 * combination of interactions are performed by the user during the 717 * course of editing within an editable. 718 * The smart content change event would therefore signal to any 719 * component that is listening to this event, that content has been 720 * inserted into the editable that may need to be prococessed in a 721 * special way 722 * This is used for smart actions within the content/while editing. 723 * @param {Event} event 724 * @hide 725 */ 726 smartContentChange: function( event ) { 727 var me = this, 728 uniChar = null, 729 re, 730 match; 731 732 // ignore meta keys like crtl+v or crtl+l and so on 733 if ( event && ( event.metaKey || event.crtlKey || event.altKey ) ) { 734 return false; 735 } 736 737 if ( event && event.originalEvent ) { 738 // regex to strip unicode 739 re = new RegExp( "U\\+(\\w{4})" ); 740 match = re.exec( event.originalEvent.keyIdentifier ); 741 742 // Use keyIdentifier if available 743 if ( event.originalEvent.keyIdentifier && 1 === 2 ) { 744 // @fixme: Because of "&& 1 === 2" above, this block is 745 // unreachable code 746 if ( match !== null ) { 747 uniChar = unescape( '%u' + match[1] ); 748 } 749 if ( uniChar === null ) { 750 uniChar = event.originalEvent.keyIdentifier; 751 } 752 753 // FF & Opera don't support keyIdentifier 754 } else { 755 // Use among browsers reliable which http://api.jquery.com/keypress 756 uniChar = ( this.keyCodeMap[ this.keyCode ] || 757 String.fromCharCode( event.which ) || 'unknown' ); 758 } 759 } 760 761 // handle "Enter" -- it's not "U+1234" -- when returned via "event.originalEvent.keyIdentifier" 762 // reference: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html 763 if ( jQuery.inArray( uniChar, this.sccDelimiters ) >= 0 ) { 764 clearTimeout( this.sccTimerIdle ); 765 clearTimeout( this.sccTimerDelay ); 766 767 this.sccTimerDelay = setTimeout( function() { 768 Aloha.trigger( 'aloha-smart-content-changed', { 769 'editable' : me, 770 'keyIdentifier' : event.originalEvent.keyIdentifier, 771 'keyCode' : event.keyCode, 772 'char' : uniChar, 773 'triggerType' : 'keypress', // keypress, timer, blur, paste 774 'snapshotContent' : me.getSnapshotContent() 775 } ); 776 777 console.debug( 'Aloha.Editable', 778 'smartContentChanged: event type keypress triggered' ); 779 /* 780 var r = Aloha.Selection.rangeObject; 781 if ( r.isCollapsed() && r.startContainer.nodeType == 3 ) { 782 var posDummy = jQuery( '<span id="GENTICS-Aloha-PosDummy" />' ); 783 GENTICS.Utils.Dom.insertIntoDOM( 784 posDummy, 785 r, 786 this.obj, 787 null, 788 false, 789 false 790 ); 791 console.log( posDummy.offset().top, posDummy.offset().left ); 792 GENTICS.Utils.Dom.removeFromDOM( 793 posDummy, 794 r, 795 false 796 ); 797 r.select(); 798 } 799 */ 800 }, this.sccDelay ); 801 802 } else if ( event && event.type === 'paste' ) { 803 Aloha.trigger( 'aloha-smart-content-changed', { 804 'editable' : me, 805 'keyIdentifier' : null, 806 'keyCode' : null, 807 'char' : null, 808 'triggerType' : 'paste', 809 'snapshotContent' : me.getSnapshotContent() 810 } ); 811 812 } else if ( event && event.type === 'blur' ) { 813 Aloha.trigger( 'aloha-smart-content-changed', { 814 815 'editable' : me, 816 'keyIdentifier' : null, 817 'keyCode' : null, 818 'char' : null, 819 'triggerType' : 'blur', 820 'snapshotContent' : me.getSnapshotContent() 821 } ); 822 823 } else if ( uniChar !== null ) { 824 // in the rare case idle time is lower then delay time 825 clearTimeout( this.sccTimerDelay ); 826 clearTimeout( this.sccTimerIdle ); 827 this.sccTimerIdle = setTimeout( function() { 828 Aloha.trigger( 'aloha-smart-content-changed', { 829 'editable' : me, 830 'keyIdentifier' : null, 831 'keyCode' : null, 832 'char' : null, 833 'triggerType' : 'idle', 834 'snapshotContent' : me.getSnapshotContent() 835 } ); 836 }, this.sccIdle ); 837 } 838 }, 839 840 /** 841 * Get a snapshot of the active editable as a HTML string 842 * @hide 843 * @return snapshot of the editable 844 */ 845 getSnapshotContent: function() { 846 var ret = this.snapshotContent; 847 this.snapshotContent = this.getContents(); 848 return ret; 849 } 850 } ); 851 852 /** 853 * Sets the serializer function to be used for the contents of all editables. 854 * 855 * The default content serializer will just call the jQuery.html() 856 * function on the editable element (which gets the innerHTML property). 857 * 858 * This method is a static class method and will affect the result 859 * of editable.getContents() for all editables that have been or 860 * will be constructed. 861 * 862 * @param serializerFunction 863 * A function that accepts a DOM element and returns the serialized 864 * XHTML of the element contents (excluding the start and end tag of 865 * the passed element). 866 */ 867 Aloha.Editable.setContentSerializer = function( serializerFunction ) { 868 contentSerializer = serializerFunction; 869 }; 870 } ); 871