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( [ 'aloha/core', 'aloha/jquery', 'aloha/ext', 'i18n!aloha/nls/i18n', 'aloha/ui', 22 'aloha/repositorymanager', 'aloha/selection', 'aloha/ext-alohaproxy', 23 'aloha/ext-alohareader' 24 ], function ( Aloha, jQuery, Ext, i18n, Ui, RepositoryManager, Selection ) { 25 // TODO add parameter for UI class after refactoring UI to requirejs 26 'use strict'; 27 28 var undefined = void 0; 29 30 var extTemplate = function ( tpl ) { 31 if ( tpl ) { 32 tpl = '<tpl for="."><div class="x-combo-list-item">' + 33 '<tpl if="this.hasRepositoryTemplate(values)">{[ this.renderRepositoryTemplate(values) ]}</tpl>' + 34 '<tpl if="!this.hasRepositoryTemplate(values)">' + tpl + '</tpl>' + 35 '</div></tpl>'; 36 } else { 37 tpl = '<tpl for="."><div class="x-combo-list-item">' + 38 '<tpl if="this.hasRepositoryTemplate(values)">{[ this.renderRepositoryTemplate(values) ]}</tpl>' + 39 '<tpl if="!this.hasRepositoryTemplate(values)"><span><b>{name}</b></span></tpl>' + 40 '</div></tpl>'; 41 } 42 return new Ext.XTemplate( 43 tpl, 44 { 45 46 hasRepositoryTemplate : function ( values ) { 47 var rep = RepositoryManager.getRepository( values.repositoryId ); 48 return rep && rep.hasTemplate(); 49 }, 50 renderRepositoryTemplate : function ( values ) { 51 var rep = RepositoryManager.getRepository( values.repositoryId ); 52 if ( rep && rep.hasTemplate() ) { 53 // create extTemplate if template changed 54 if ( !rep._ExtTPL || rep.template !== rep._ExtTPLcache ) { 55 rep._ExtTPL = new Ext.XTemplate( rep.template ); 56 rep._ExtTPLcache = rep.template; 57 } 58 return rep._ExtTPL.apply( values ); 59 } 60 } 61 } ); 62 }; 63 64 65 // This will store the last attribute value. We need to keep track of this value 66 // due to decide whether to update the value on finish editing 67 var lastAttributeValue; 68 69 Ext.ux.AlohaAttributeField = Ext.extend( Ext.form.ComboBox, { 70 typeAhead : false, 71 mode : 'remote', 72 triggerAction : 'all', 73 width : 300, 74 hideTrigger : true, 75 minChars : 3, 76 valueField : 'id', 77 displayField : 'name', 78 listEmptyText : i18n.t( 'repository.no_item_found' ), 79 loadingText : i18n.t( 'repository.loading' ) + '...', 80 enableKeyEvents : true, 81 store: new Ext.data.Store( { 82 proxy: new Ext.data.AlohaProxy(), 83 reader: new Ext.data.AlohaObjectReader() 84 } ), 85 clickAttached: false, // remember that the click event has been attached to the innerList, as this is not implemented in the combobox 86 tpl : extTemplate(), 87 onSelect : function ( item ) { 88 this.setItem( item.data ); 89 if ( typeof this.alohaButton.onSelect == 'function' ) { 90 this.alohaButton.onSelect.call( this.alohaButton, item.data ); 91 } 92 this.collapse(); 93 }, 94 finishEditing : function () { 95 var target = jQuery( this.getTargetObject() ), color; 96 97 // Remove the highlighting and restore original color if was set before 98 if ( target ) { 99 if ( color = target.attr( 'data-original-background-color' ) ) { 100 jQuery( target ).css( 'background-color', color ); 101 } else { 102 jQuery( target ).css( 'background-color', '' ); 103 } 104 jQuery( target ).removeAttr( 'data-original-background-color' ); 105 } 106 107 // Check whether the attribute was changed since the last focus event. Return early when the attribute was not changed. 108 if ( lastAttributeValue === target.attr( this.targetAttribute ) ) { 109 return; 110 } 111 112 // when no resource item was selected, remove any marking of the target object 113 if ( !this.resourceItem ) { 114 RepositoryManager.markObject( this.targetObject ); 115 } 116 117 if ( this.getValue() === '' ) { 118 if ( this.wrap ) { 119 jQuery( this.wrap.dom.children[0] ).css( 'color', '#AAA' ); 120 } 121 this.setValue( this.placeholder ); 122 } 123 }, 124 listeners: { 125 // repository object types could have changed 126 'beforequery': function ( event ) { 127 if ( this.noQuery ) { 128 event.cancel = true; 129 return; 130 } 131 if ( this.store !== null && this.store.proxy !== null ) { 132 this.store.proxy.setParams( { 133 objectTypeFilter: this.getObjectTypeFilter(), 134 queryString: event.query 135 } ); 136 } 137 }, 138 'afterrender': function ( obj, event ) { 139 jQuery( this.wrap.dom.children[0] ).css( 'color', '#AAA' ); 140 this.setValue( this.placeholder ); 141 }, 142 'keydown': function ( obj, event ) { 143 // on ENTER or ESC leave the editing 144 // just remember here the status and remove cursor on keyup event 145 // Otherwise cursor moves to content and no more blur event happens!!?? 146 if ( event.keyCode == 13 || event.keyCode == 27 ) { 147 if ( this.isExpanded() ) { 148 this.ALOHAwasExpanded = true; 149 } else { 150 this.ALOHAwasExpanded = false; 151 } 152 event.preventDefault(); 153 } 154 }, 155 'keyup': function ( obj, event ) { 156 if ( ( event.keyCode == 13 || event.keyCode == 27 ) && 157 !this.ALOHAwasExpanded ) { 158 // work around stupid behavior when moving focus 159 window.setTimeout( function () { 160 // Set focus to link element and select the object 161 Selection.getRangeObject().select(); 162 }, 0 ); 163 } 164 165 // when a resource item was (initially) set, but the current value 166 // is different from the reference value, we unset the resource item 167 if ( this.resourceItem && 168 this.resourceValue !== this.wrap.dom.children[0].value ) { 169 this.resourceItem = null; 170 this.resourceValue = null; 171 } 172 173 // update attribute, but only if no resource item was selected 174 if ( !this.resourceItem ) { 175 var v = this.wrap.dom.children[0].value; 176 this.setAttribute( this.targetAttribute, v ); 177 } 178 }, 179 'focus': function ( obj, event ) { 180 // set background color to give visual feedback which link is modified 181 var target = jQuery( this.getTargetObject() ), 182 s = target.css( 'background-color' ); 183 184 if ( this.getValue() === this.placeholder ) { 185 this.setValue( '' ); 186 jQuery( this.wrap.dom.children[0] ).css( 'color', 'black' ); 187 } 188 if ( target && target.context && target.context.style && 189 target.context.style[ 'background-color' ] ) { 190 target.attr( 'data-original-background-color', 191 target.context.style[ 'background-color' ] ); 192 } 193 target.css( 'background-color', '#80B5F2' ); 194 }, 195 'blur': function ( obj, event ) { 196 this.finishEditing(); 197 }, 198 'hide': function ( obj, event ) { 199 this.finishEditing(); 200 }, 201 'expand': function ( combo ) { 202 if ( this.noQuery ) { 203 this.collapse(); 204 } 205 if ( !this.clickAttached ) { 206 var that = this; 207 // attach the mousedown event to set the event handled, 208 // so that the editable will not get deactivated 209 this.mon( this.innerList, 'mousedown', function ( event ) { 210 211 Aloha.eventHandled = true; 212 }, this ); 213 // in the mouseup event, the flag will be reset 214 this.mon( this.innerList, 'mouseup', function ( event ) { 215 Aloha.eventHandled = false; 216 }, this ); 217 this.clickAttached = true; 218 } 219 } 220 }, 221 setItem: function ( item, displayField ) { 222 this.resourceItem = item; 223 224 if ( item ) { 225 displayField = ( displayField ) ? displayField : this.displayField; 226 // TODO split display field by '.' and get corresponding attribute, because it could be a properties attribute. 227 var v = item[ displayField ]; 228 // set the value into the field 229 this.setValue( v ); 230 // store the value to be the "reference" value for the currently selected resource item 231 this.resourceValue = v; 232 // set the attribute to the target object 233 this.setAttribute( this.targetAttribute, item[ this.valueField ] ); 234 // call the repository marker 235 RepositoryManager.markObject( this.targetObject, item ); 236 } else { 237 // unset the reference value, since no resource item is selected 238 this.resourceValue = null; 239 } 240 }, 241 getItem: function () { 242 return this.resourceItem; 243 }, 244 // Private hack to allow attribute setting by regex 245 setAttribute: function ( attr, value, regex, reference ) { 246 var setAttr = true, regxp; 247 if ( this.targetObject) { 248 // check if a reference value is submitted to check against with a regex 249 if ( typeof reference != 'undefined' ) { 250 regxp = new RegExp( regex ); 251 if ( !reference.match( regxp ) ) { 252 setAttr = false; 253 } 254 } 255 256 // if no regex was successful or no reference value 257 // was submitted remove the attribute 258 if ( setAttr ) { 259 jQuery( this.targetObject ).attr( attr, value ); 260 } else { 261 jQuery( this.targetObject ).removeAttr( attr ); 262 } 263 } 264 }, 265 setTargetObject : function ( obj, attr ) { 266 var that = this; 267 this.targetObject = obj; 268 this.targetAttribute = attr; 269 this.setItem( null ); 270 271 if ( obj && attr ) { 272 lastAttributeValue = jQuery( obj ).attr( attr ); 273 } 274 275 if ( this.targetObject && this.targetAttribute ) { 276 this.setValue( jQuery( this.targetObject ).attr( this.targetAttribute ) ); 277 } else { 278 this.setValue( '' ); 279 } 280 281 // check whether a repository item is linked to the object 282 var that = this; 283 RepositoryManager.getObject( obj, function ( items ) { 284 if ( items && items.length > 0 ) { 285 that.setItem( items[0] ); 286 } 287 } ); 288 }, 289 getTargetObject : function () { 290 return this.targetObject; 291 }, 292 setObjectTypeFilter : function ( otFilter ) { 293 this.objectTypeFilter = otFilter; 294 }, 295 getObjectTypeFilter : function () { 296 return this.objectTypeFilter; 297 }, 298 noQuery: true 299 }); 300 301 /** 302 * Register the Aloha attribute field 303 * @hide 304 */ 305 Ext.reg( 'alohaattributefield', Ext.ux.AlohaAttributeField ); 306 307 /** 308 * Aloha Attribute Field Button 309 * @namespace Aloha.ui 310 * @class AttributeField 311 */ 312 Ui.AttributeField = Ui.Button.extend( { 313 _constructor: function ( properties ) { 314 /** 315 * @cfg Function called when an element is selected 316 */ 317 this.onSelect = null; 318 this.listenerQueue = []; 319 this.objectTypeFilter = null; 320 this.tpl = null; 321 this.displayField = null; 322 this.valueField = null; 323 324 this.init( properties ); 325 }, 326 327 /** 328 * Create a extjs alohaattributefield 329 * @hide 330 */ 331 getExtConfigProperties: function () { 332 var props = { 333 alohaButton : this, 334 xtype : 'alohaattributefield', 335 rowspan : this.rowspan || undefined, 336 width : this.width || undefined, 337 placeholder : this.placeholder || undefined, 338 id : this.id, 339 cls : this.cls || undefined 340 }; 341 if ( this.valueField ) { 342 props.valueField = this.valueField; 343 } 344 if ( this.displayField ) { 345 props.displayField = this.displayField; 346 } 347 if ( this.minChars ) { 348 props.minChars = this.minChars; 349 } 350 return props; 351 }, 352 353 /** 354 * Sets the target Object of which the Attribute should be modified 355 * @param {jQuery} obj the target object 356 * @param {String} attr Attribute to be modified ex. "href" of a link 357 * @void 358 */ 359 setTargetObject: function ( obj, attr ) { 360 if ( this.extButton ) { 361 this.extButton.setTargetObject( obj, attr ); 362 } 363 }, 364 365 /** 366 * @return {jQuery} object Returns the current target Object 367 */ 368 getTargetObject: function () { 369 return this.extButton ? this.extButton.getTargetObject() : null; 370 }, 371 372 /** 373 * Focus to this field 374 * @void 375 */ 376 focus: function () { 377 if ( this.extButton ) { 378 this.extButton.focus(); 379 if ( this.extButton.getValue().length > 0 ) { 380 this.extButton.selectText( 0, this.extButton.getValue().length ); 381 } 382 } 383 }, 384 385 /** 386 * Adding a listener to the field 387 * @param {String} eventname The name of the event. Ex. 'keyup' 388 * @param {function} handler The function that should be called when the event happens. 389 * @param {Object} scope The scope object which the event should be attached 390 */ 391 addListener: function ( eventName, handler, scope ) { 392 var listener; 393 394 if ( this.extButton ) { 395 this.extButton.addListener( eventName, handler, null ); 396 } else { 397 // if extButton not yet initialized adding listeners could be a problem 398 // so all events are collected in a queue and added on initalizing 399 listener = { 400 'eventName' : eventName, 401 'handler' : handler, 402 'scope' : scope, 403 'options' : null 404 }; 405 this.listenerQueue.push( listener ); 406 } 407 }, 408 409 /** 410 * Sets an attribute optionally based on a regex on reference 411 * @param {String} attr The Attribute name which should be set. Ex. "lang" 412 * @param {String} value The value to set. Ex. "de-AT" 413 * @param {String} regex The regex when the attribute should be set. The regex is applied to the value of refernece. 414 * @param {String} reference The value for the regex. 415 */ 416 setAttribute: function ( attr, value, regex, reference ) { 417 if ( this.extButton ) { 418 this.extButton.setAttribute( attr, value, regex, reference ); 419 } 420 }, 421 422 /** 423 * When at least on objectType is set the value in the Attribute field does a query to all registered repositories. 424 * @param {Array} objectTypeFilter The array of objectTypeFilter to be searched for. 425 * @void 426 */ 427 setObjectTypeFilter: function ( objectTypeFilter ) { 428 if ( this.extButton ) { 429 this.noQuery = false; 430 this.extButton.setObjectType( objectTypeFilter ); 431 } else { 432 if ( !objectTypeFilter ) { 433 objectTypeFilter = 'all'; 434 } 435 this.objectTypeFilter = objectTypeFilter; 436 } 437 }, 438 439 /** 440 * Sets an item to the link tag. 441 * @param {resourceItem} item 442 */ 443 setItem: function ( item , displayField ) { 444 if ( this.extButton ) { 445 this.extButton.setItem( item, displayField ); 446 } 447 }, 448 449 /** 450 * Gets current item set. 451 * @return {resourceItem} item 452 */ 453 getItem: function () { 454 if ( this.extButton ) { 455 return this.extButton.getItem(); 456 } 457 return null; 458 }, 459 460 /** 461 * Returns the current value 462 * @return {String} attributeValue 463 */ 464 getValue: function () { 465 if ( this.extButton ) { 466 return this.extButton.getValue(); 467 } 468 return null; 469 }, 470 471 /** 472 * Sets the current value 473 * @param {String} v an attributeValue 474 */ 475 setValue: function ( v ) { 476 if ( this.extButton ) { 477 this.extButton.setValue( v ); 478 } 479 }, 480 481 /** 482 * Returns the current query value. 483 * @return {String} queryValue 484 */ 485 getQueryValue: function () { 486 if ( this.extButton ) { 487 return this.extButton.getValue(); 488 489 // Petro: 490 // It is not clear why the value was being read in this "low-level" way and 491 // not through `getValue()'. In any case, doing so, occasionally caused 492 // errors, when this.extButton.wrap is `undefined'. We will therefore read 493 // the value in the manner we do above. 494 // return this.extButton.wrap.dom.children[0].value; 495 } 496 return null; 497 }, 498 499 /** 500 * Set the display field, which is displayed in the combobox 501 * @param {String} displayField name of the field to be displayed 502 * @return display field name on success, null otherwise 503 */ 504 setDisplayField: function ( displayField ) { 505 var result; 506 if ( this.extButton ) { 507 result = this.extButton.displayField = displayField; 508 } else { 509 result = this.displayField = displayField; 510 } 511 return result; 512 }, 513 514 /** 515 * Set the row template for autocomplete hints. The default template is: 516 * <span><b>{name}</b><br />{url}</span> 517 * @param {String} tpl template to be rendered for each row 518 * @return template on success or null otherwise 519 */ 520 setTemplate: function ( tpl ) { 521 var result; 522 if ( this.extButton ) { 523 result = this.extButton.tpl = extTemplate( tpl ); 524 } else { 525 result = this.tpl = extTemplate( tpl ); 526 } 527 return result; 528 } 529 530 } ); 531 532 } ); 533