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