1 /** 2 * @fileOverview 3 * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>) 4 * @license Copyright (c) 2009-2016 LabKey Corporation 5 * <p/> 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * <p/> 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * <p/> 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * <p/> 18 */ 19 Ext.namespace("LABKEY","LABKEY.ext"); 20 21 22 /** 23 * Constructs a new LabKey FormPanel using the supplied configuration. 24 * @class <p><font color="red">DEPRECATED - </font> Consider using 25 * <a href="http://www.extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel">Ext.form.FormPanel</a> instead. </p> 26 * <p>LabKey extension to the 27 * <a href="http://www.extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel">Ext.form.FormPanel</a>. 28 * This class understands various LabKey metadata formats and can simplify generating basic forms. 29 * When a LABKEY.ext.FormPanel is created with additional metadata, it will try to intelligently construct fields 30 * of the appropriate type.</p> 31 * <p>If you use any of the LabKey APIs that extend Ext APIs, you must either make your code open source or 32 * <a href="https://www.labkey.org/Documentation/wiki-page.view?name=extDevelopment">purchase an Ext license</a>.</p> 33 * <p>Additional Documentation: 34 * <ul> 35 * <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=javascriptTutorial">Tutorial: Create Applications with the JavaScript API</a></li> 36 * </ul> 37 * </p> 38 * @constructor 39 * @augments Ext.form.FormPanel 40 * @param config Configuration properties. This may contain any of the configuration properties supported 41 * by the <a href="http://www.extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel">Ext.form.FormPanel</a>, 42 * plus those listed here. 43 * Also, items may specify a ToolTip config in the helpPopup property to display a LabKey-style "?" help tip. 44 * Note that the selectRowsResults object (see {@link LABKEY.Query.SelectRowsResults}) includes both columnModel and metaData, so you don't need to specify all three. 45 * @param {object} [config.metaData] as returned by {@link LABKEY.Query.selectRows}. See {@link LABKEY.Query.SelectRowsResults}. 46 * @param {object} [config.columnModel] as returned by {@link LABKEY.Query.selectRows}. See {@link LABKEY.Query.SelectRowsResults}. 47 * @param {LABKEY.Query.SelectRowsResults} [config.selectRowsResults] as returned by {@link LABKEY.Query.selectRows}. 48 * @param {boolean} [config.addAllFields='false'] If true, all fields specified in the metaData are automatically created. 49 * @param {object} [config.values] Provides initial values to populate the form. 50 * @param {object} [config.errorEl] If specified, form errors will be written to this element; otherwise, a MsgBox will be used. 51 * @param {string} [config.containerPath] Alternate default container for queries (e.g. for lookups) 52 * @param {boolean} [config.lazyCreateStore] If false, any lookup stores will be created immediately. If true, any lookup stores will be created when the component is created. (default true) 53 * 54 * @example 55 <script type="text/javascript"> 56 function onSuccess(data) // e.g. callback from Query.selectRows 57 { 58 function submitHandler(formPanel) 59 { 60 var form = formPanel.getForm(); 61 if (form.isValid()) 62 { 63 var rows = formPanel.getFormValues(); 64 save(rows); 65 } 66 else 67 { 68 Ext.MessageBox.alert("Error Saving", "There are errors in the form."); 69 } 70 } 71 72 function cancelHandler() 73 { 74 // Replace with real handler code 75 Ext.MessageBox.alert("Cancelled", "The submission was cancelled."); 76 } 77 78 var formPanel = new LABKEY.ext.FormPanel( 79 { 80 selectRowsResults:data, 81 addAllFields:true, 82 buttons:[{text:"Submit", handler: function (b, e) { submitHandler(formPanel); }}, {text: "Cancel", handler: cancelHandler}], 83 items:[{name:'myField', fieldLabel:'My Field', helpPopup:{title:'help', html:'read the manual'}}] 84 }); 85 formPanel.render('formDiv'); 86 } 87 </script> 88 <div id='formDiv'/> 89 */ 90 91 LABKEY.ext.FormPanel = Ext.extend(Ext.form.FormPanel, 92 { 93 constructor : function(config) 94 { 95 this.allFields = this.initFieldDefaults(config); 96 if (Ext.isArray(config.items)) 97 config.items.push({ xtype: 'hidden', name: 'X-LABKEY-CSRF', value: LABKEY.CSRF }); 98 return LABKEY.ext.FormPanel.superclass.constructor.call(this, config); 99 }, 100 101 defaultType : 'textfield', 102 allFields : [], 103 104 initComponent : function() 105 { 106 LABKEY.ext.FormPanel.superclass.initComponent.call(this); 107 this.addEvents( 108 'beforeapplydefaults', 109 'applydefaults' 110 ); 111 112 // add all fields that we're not added explicitly 113 if (this.addAllFields && this.allFields.length) 114 { 115 // get a list of fields that were already constructed 116 var existing = {}; 117 if (this.items) 118 { 119 this.items.each(function(c) 120 { 121 if (c.isFormField) 122 { 123 var name = c.hiddenName || c.name; 124 existing[name] = name; 125 } 126 }); 127 } 128 for (var i=0;i<this.allFields.length;i++) 129 { 130 var c = this.allFields[i]; 131 var name = c.hiddenName || c.name; 132 // Don't render URL values as a separate input field 133 if (!existing[name] && name.indexOf(LABKEY.Query.URL_COLUMN_PREFIX) != 0) 134 this.add(c); 135 } 136 } 137 }, 138 139 140 /* called from Ext.Container.initComponent() when adding items, before onRender() */ 141 applyDefaults : function(c) 142 { 143 this.fireEvent('beforeapplydefaults', this, c); 144 if (this.fieldDefaults) 145 { 146 if (typeof c == 'string') 147 { 148 } 149 else if (!c.events) 150 { 151 var name = c.name; 152 if (name && this.fieldDefaults[name]) 153 Ext.applyIf(c, this.fieldDefaults[name]); 154 } 155 else 156 { 157 } 158 } 159 var applied = LABKEY.ext.FormPanel.superclass.applyDefaults.call(this, c); 160 this.fireEvent('applydefaults', this, applied); 161 return applied; 162 }, 163 164 /* gets called before doLayout() */ 165 onRender : function(ct, position) 166 { 167 LABKEY.ext.FormPanel.superclass.onRender.call(this, ct, position); 168 this.el.addClass('extContainer'); 169 }, 170 171 // private 172 initFieldDefaults : function(config) 173 { 174 var columnModel = config.columnModel; 175 var metaData = config.metaData; 176 var properties = config.properties; 177 178 if (config.selectRowsResults) 179 { 180 if (!columnModel) 181 columnModel = config.selectRowsResults.columnModel; 182 if (!metaData) 183 config.metaData = config.selectRowsResults.metaData; 184 if (config.selectRowsResults.rowCount) 185 config.values = config.selectRowsResults.rows; 186 } 187 var fields = config.metaData ? config.metaData.fields : null; 188 189 var defaults = config.fieldDefaults = config.fieldDefaults || {}; 190 var items = [], i; 191 192 function findColumn(id) { 193 if (columnModel) 194 for (var i = 0; i < columnModel.length; i++) 195 if (columnModel[i].dataIndex == id) 196 return columnModel[i]; 197 return null; 198 } 199 200 if (fields || properties) 201 { 202 var count = fields ? fields.length : properties.length; 203 for (i=0 ; i<count ; i++) 204 { 205 var field = this.getFieldEditorConfig( 206 { 207 containerPath: (config.containerPath || LABKEY.container.path), 208 lazyCreateStore: config.lazyCreateStore 209 }, 210 fields?fields[i]:{}, 211 properties?properties[i]:{}, 212 columnModel?columnModel[i]:{} 213 ); 214 var name = field.originalConfig.name; 215 var d = defaults[name]; 216 defaults[name] = Ext.applyIf(defaults[name] || {}, field); 217 218 items.push({name:name}); 219 } 220 } 221 222 if (config.values) 223 { 224 if (!Ext.isArray(config.values)) 225 config.values = [ config.values ]; 226 227 var values = config.values; 228 229 // UNDONE: primary keys should be readonly when editing multiple rows. 230 231 var multiRowEdit = values.length > 1; 232 for (i = 0; i < values.length; i++) 233 { 234 var vals = values[i]; 235 for (var id in vals) 236 { 237 if (!(id in defaults)) 238 defaults[id] = {}; 239 240 // In multi-row edit case: skip if we've already discovered the values for this id are different across rows. 241 if (multiRowEdit && defaults[id].allRowsSameValue === false) 242 continue; 243 244 var v = vals[id]; 245 if (typeof v == 'function') 246 continue; 247 if (v && typeof v == 'object' && 'value' in v) 248 v = v.value; 249 250 if ('xtype' in defaults[id] && defaults[id].xtype == 'checkbox') 251 { 252 var checked = v ? true : false; 253 if (v == "false") 254 v = false; 255 if (multiRowEdit && i > 0 && v != defaults[id].checked) 256 { 257 defaults[id].checked = false; 258 defaults[id].allRowsSameValue = false; 259 260 // UNDONE: Ext checkboxes don't have an 'unset' state 261 // Don't require a value for this field. 262 defaults[id].required = false; 263 defaults[id].allowBlank = true; 264 } 265 else 266 defaults[id].checked = v; 267 } 268 else 269 { 270 if (multiRowEdit && i > 0 && v != defaults[id].value) 271 { 272 defaults[id].value = undefined; 273 defaults[id].allRowsSameValue = false; 274 defaults[id].emptyText = "Selected rows have different values for this field."; 275 276 // Don't require a value for this field. Allows a '[none]' entry for ComboBox and empty text fields. 277 defaults[id].required = false; 278 defaults[id].allowBlank = true; 279 } 280 else 281 defaults[id].value = v; 282 } 283 } 284 } 285 } 286 287 return items; 288 }, 289 290 getFieldEditorConfig : function () 291 { 292 return LABKEY.ext.FormHelper.getFieldEditorConfig.apply(null, arguments); 293 }, 294 295 // we want to hook error handling to provide a mechanism to show form errors (vs field errors) 296 // form errors are reported on imaginary field named "_form" 297 createForm : function() 298 { 299 var f = LABKEY.ext.FormPanel.superclass.createForm.call(this); 300 f.formPanel = this; 301 f.findField = function(id) 302 { 303 if (id == "_form") 304 return this.formPanel; 305 return Ext.form.BasicForm.prototype.findField.call(this,id); 306 }; 307 return f; 308 }, 309 310 // Look for form level errors that BasicForm won't handle 311 // CONSIDER: move implementation to getForm().markInvalid() 312 // CONSIDER: find 'unbound' errors and move to form level 313 markInvalid : function(errors) 314 { 315 var formMessage; 316 317 if (typeof errors == "string") 318 { 319 formMessage = errors; 320 errors = null; 321 } 322 else if (Ext.isArray(errors)) 323 { 324 for(var i = 0, len = errors.length; i < len; i++) 325 { 326 var fieldError = errors[i]; 327 if (!("id" in fieldError) || "_form" == fieldError.id) 328 formMessage = fieldError.msg; 329 } 330 } 331 else if (typeof errors == "object" && "_form" in errors) 332 { 333 formMessage = errors._form; 334 } 335 336 if (errors) 337 { 338 this.getForm().markInvalid(errors); 339 } 340 341 if (formMessage) 342 { 343 if (this.errorEl) 344 Ext.get(this.errorEl).update(Ext.util.Format.htmlEncode(formMessage)); 345 else 346 Ext.Msg.alert("Error", formMessage); 347 } 348 }, 349 350 /** 351 * Returns an Array of form value Objects. The returned values will first be populated with 352 * with {@link #values} or {@link #selectRowsResponse.values} then with the form's values. 353 * If the form was not initially populated with {@link #values}, a signle element Array with 354 * just the form's values will be returned. 355 */ 356 getFormValues : function () 357 { 358 // First, get all dirty form field values 359 var fieldValues = this.getForm().getFieldValues(true); 360 for (var key in fieldValues) 361 { 362 if (typeof fieldValues[key] == "string") 363 fieldValues[key] = fieldValues[key].trim(); 364 } 365 366 // 10887: Include checkboxes that weren't included in the call to .getFieldValues(true). 367 this.getForm().items.each(function (f) { 368 if (f instanceof Ext.form.Checkbox && !f.isDirty()) 369 { 370 var name = f.getName(); 371 var key = fieldValues[name]; 372 var val = f.getValue(); 373 if (Ext.isDefined(key)) { 374 if (Ext.isArray(key)) { 375 fieldValues[name].push(val); 376 } else { 377 fieldValues[name] = [key, val]; 378 } 379 } else { 380 fieldValues[name] = val; 381 } 382 } 383 }); 384 385 // Finally, populate the data array with form values overriding the initial values. 386 var initialValues = this.initialConfig.values || []; 387 var len = initialValues.length || 1; 388 var result = []; 389 for (var i = 0; i < len; i++) 390 { 391 var data = {}; 392 var initialVals = initialValues[i]; 393 if (initialVals) 394 { 395 for (var key in initialVals) 396 { 397 var v = initialVals[key]; 398 if (v && typeof v == 'object' && 'value' in v) 399 v = v.value; 400 data[key] = v; 401 } 402 } 403 Ext.apply(data, fieldValues); 404 result.push(data); 405 } 406 return result; 407 } 408 }); 409 410 LABKEY.ext.FormHelper = 411 { 412 _textMeasure : null, 413 414 /** 415 * Uses the given meta-data to generate a field config object. 416 * 417 * This function accepts a mish-mash of config parameters to be easily adapted to 418 * various different metadata formats. 419 * 420 * @param {string} [config.type] e.g. 'string','int','boolean','float', or 'date' 421 * @param {object} [config.editable] 422 * @param {object} [config.required] 423 * @param {string} [config.label] used to generate fieldLabel 424 * @param {string} [config.name] used to generate fieldLabel (if header is null) 425 * @param {string} [config.caption] used to generate fieldLabel (if label is null) 426 * @param {integer} [config.cols] if input is a textarea, sets the width (style:width is better) 427 * @param {integer} [config.rows] if input is a textarea, sets the height (style:height is better) 428 * @param {string} [config.lookup.schemaName] the schema used for the lookup. schemaName also supported 429 * @param {string} [config.lookup.queryName] the query used for the lookup. queryName also supported 430 * @param {Array} [config.lookup.columns] The columns used by the lookup store. If not set, the <code>[keyColumn, displayColumn]</code> will be used. 431 * @param {string} [config.lookup.keyColumn] 432 * @param {string} [config.lookup.displayColumn] 433 * @param {string} [config.lookup.sort] The sort used by the lookup store. 434 * @param {boolean} [config.lookups] use lookups=false to prevent creating default combobox for lookup columns 435 * @param {object} [config.ext] is a standard Ext config object that will be merged with the computed field config 436 * e.g. ext:{width:120, tpl:new Ext.Template(...)} 437 * @param {object} [config.lookup.store] advanced! Pass in your own custom store for a lookup field 438 * @param {boolean} [config.lazyCreateStore] If false, the store will be created immediately. If true, the store will be created when the component is created. (default true) 439 * 440 * Will accept multiple config parameters which will be combined. 441 * 442 * @private 443 */ 444 getFieldEditorConfig: function(c) 445 { 446 /* CONSIDER for 10.3, new prototype (with backward compatibility check) 447 * getFieldEditorConfig(extConfig, [metadata,...]) 448 */ 449 450 // Combine the metadata provided into one config object 451 var config = {editable:true, required:false, ext:{}}; 452 for (var i=arguments.length-1 ; i>= 0 ; --i) 453 { 454 var ext = config.ext; 455 if (arguments[i]) 456 { 457 ext = Ext.apply(ext, arguments[i].ext); 458 Ext.apply(config, arguments[i]); 459 } 460 config.ext = ext; 461 } 462 463 var h = Ext.util.Format.htmlEncode; 464 var lc = function(s){return !s?s:Ext.util.Format.lowercase(s);}; 465 466 config.type = lc(config.jsonType) || lc(config.type) || lc(config.typeName) || 'string'; 467 468 var field = 469 { 470 //added 'caption' for assay support 471 fieldLabel: h(config.label) || h(config.caption) || h(config.header) || h(config.name), 472 name: config.name, 473 originalConfig: config, 474 allowBlank: !config.required, 475 disabled: !config.editable 476 }; 477 478 if (config.tooltip && !config.helpPopup) 479 field.helpPopup = config.tooltip; 480 481 if (config.lookup && false !== config.lookups) 482 { 483 var l = config.lookup; 484 var store = config.lookup.store || config.store; 485 if (store && store == config.store) 486 console.debug("use config.lookup.store"); 487 488 if (Ext.isObject(store) && store.events) 489 field.store = store; 490 else 491 field.store = LABKEY.ext.FormHelper.getLookupStoreConfig(config); 492 493 if (field.store && config.lazyCreateStore === false) 494 field.store = LABKEY.ext.FormHelper.getLookupStore(field); 495 496 Ext.apply(field, { 497 xtype: 'combo', 498 forceSelection:true, 499 typeAhead: false, 500 hiddenName: config.name, 501 hiddenId : (new Ext.Component()).getId(), 502 triggerAction: 'all', 503 displayField: l.displayColumn, 504 valueField: l.keyColumn, 505 tpl : '<tpl for="."><div class="x-combo-list-item">{[values["' + l.displayColumn + '"]]}</div></tpl>', //FIX: 5860 506 listClass: 'labkey-grid-editor' 507 }); 508 } 509 else if (config.hidden) 510 { 511 field.xtype = 'hidden'; 512 } 513 else 514 { 515 switch (config.type) 516 { 517 case "boolean": 518 field.xtype = 'checkbox'; 519 break; 520 case "int": 521 field.xtype = 'numberfield'; 522 field.allowDecimals = false; 523 break; 524 case "float": 525 field.xtype = 'numberfield'; 526 field.allowDecimals = true; 527 break; 528 case "date": 529 field.xtype = 'datefield'; 530 field.format = Date.patterns.ISO8601Long; 531 field.altFormats = LABKEY.Utils.getDateAltFormats(); 532 break; 533 case "string": 534 if (config.inputType == 'file') 535 { 536 field.xtype = 'textfield'; 537 field.inputType = 'file'; 538 break; 539 } 540 else if (config.inputType=='textarea') 541 { 542 field.xtype = 'textarea'; 543 field.width = 500; 544 field.height = 60; 545 if (!this._textMeasure) 546 { 547 this._textMeasure = {}; 548 var ta = Ext.DomHelper.append(document.body,{tag:'textarea', rows:10, cols:80, id:'_hiddenTextArea', style:{display:'none'}}); 549 this._textMeasure.height = Math.ceil(Ext.util.TextMetrics.measure(ta,"GgYyJjZ==").height * 1.2); 550 this._textMeasure.width = Math.ceil(Ext.util.TextMetrics.measure(ta,"ABCXYZ").width / 6.0); 551 } 552 if (config.rows) 553 { 554 if (config.rows == 1) 555 field.height = undefined; 556 else 557 { 558 // estimate at best! 559 var textHeight = this._textMeasure.height * config.rows; 560 if (textHeight) 561 field.height = textHeight; 562 } 563 } 564 if (config.cols) 565 { 566 var textWidth = this._textMeasure.width * config.cols; 567 if (textWidth) 568 field.width = textWidth; 569 } 570 571 } 572 break; 573 default: 574 field.xtype = 'textfield'; 575 } 576 577 } 578 579 if (config.ext) 580 Ext.apply(field,config.ext); 581 582 // Ext.form.ComboBox defaults to mode=='remote', however, we often want to default to 'local' 583 // We don't want the combo cause a requery (see Combo.doQuery()) when we expect the store 584 // to be loaded exactly once. Just treat like a local store in this case. 585 // NOTE: if the user over-rides the field.store, they may have to explicitly set the mode to 'remote', even 586 // though 'remote' is the Ext.form.ComboBox default 587 if (field.xtype == 'combo' && Ext.isDefined(field.store) && field.store.autoLoad && field.triggerAction != 'query' && !Ext.isDefined(field.mode)) 588 field.mode = 'local'; 589 590 return field; 591 }, 592 593 /** 594 * same as getFieldEditorConfig, but actually constructs the editor 595 */ 596 getFieldEditor : function(config, defaultType) 597 { 598 var field = LABKEY.ext.FormHelper.getFieldEditorConfig(config); 599 return Ext.ComponentMgr.create(field, defaultType || 'textfield'); 600 }, 601 602 // private 603 getLookupStore : function(storeId, c) 604 { 605 if (typeof(storeId) != 'string') 606 { 607 c = storeId; 608 storeId = LABKEY.ext.FormHelper.getLookupStoreId(c); 609 } 610 611 // Check if store has already been created. 612 if (Ext.isObject(c.store) && c.store.events) 613 return c.store; 614 615 var store = Ext.StoreMgr.lookup(storeId); 616 if (!store) 617 { 618 var config = c.store || LABKEY.ext.FormHelper.getLookupStoreConfig(c); 619 config.storeId = storeId; 620 store = Ext.create(config, 'labkey-store'); 621 } 622 return store; 623 }, 624 625 // private 626 // Ext.StoreMgr uses 'storeId' to lookup stores. A store will add itself to the Ext.StoreMgr when constructed. 627 getLookupStoreId : function (c) 628 { 629 if (c.store && c.store.storeId) 630 return c.store.storeId; 631 632 if (c.lookup) 633 return [c.lookup.schemaName || c.lookup.schema , c.lookup.queryName || c.lookup.table, c.lookup.keyColumn, c.lookup.displayColumn].join('||'); 634 635 return c.name; 636 }, 637 638 // private 639 getLookupStoreConfig : function(c) 640 { 641 // UNDONE: avoid self-joins 642 // UNDONE: core.UsersData 643 // UNDONE: container column 644 var l = c.lookup; 645 // normalize lookup 646 l.queryName = l.queryName || l.table; 647 l.schemaName = l.schemaName || l.schema; 648 649 if (l.schemaName == 'core' && l.queryName =='UsersData') 650 l.queryName = 'Users'; 651 652 var config = { 653 xtype: "labkey-store", 654 storeId: LABKEY.ext.FormHelper.getLookupStoreId(c), 655 schemaName: l.schemaName, 656 queryName: l.queryName, 657 containerPath: l.container || l.containerPath || c.containerPath || LABKEY.container.path, 658 autoLoad: true 659 }; 660 661 if (l.viewName) 662 config.viewName = l.viewName; 663 664 if (l.columns) 665 config.columns = l.columns; 666 else 667 { 668 var columns = []; 669 if (l.keyColumn) 670 columns.push(l.keyColumn); 671 if (l.displayColumn && l.displayColumn != l.keyColumn) 672 columns.push(l.displayColumn); 673 if (columns.length == 0) 674 columns = ['*']; 675 config.columns = columns; 676 } 677 678 if (l.sort) 679 config.sort = l.sort; 680 else 681 config.sort = l.displayColumn; 682 683 if (!c.required) 684 { 685 config.nullRecord = { 686 displayColumn: l.displayColumn, 687 nullCaption: c.lookupNullCaption || "[none]" 688 }; 689 } 690 691 return config; 692 } 693 }; 694 695 696 LABKEY.ext.Checkbox = Ext.extend(Ext.form.Checkbox, 697 { 698 onRender : function(ct, position) 699 { 700 LABKEY.ext.Checkbox.superclass.onRender.call(this, ct, position); 701 if (this.name) 702 { 703 var marker = LABKEY.fieldMarker + this.name; 704 Ext.DomHelper.insertAfter(this.el, {tag:"input", type:"hidden", name:marker}); 705 } 706 } 707 }); 708 709 710 LABKEY.ext.DatePicker = Ext.extend(Ext.DatePicker, { }); 711 712 713 LABKEY.ext.DateField = Ext.extend(Ext.form.DateField, 714 { 715 onTriggerClick : function(){ 716 if(this.disabled) 717 { 718 return; 719 } 720 if(this.menu == null) 721 { 722 this.menu = new Ext.menu.DateMenu({ 723 cls:'extContainer', // NOTE change from super.onTriggerClick() 724 hideOnClick: false, 725 focusOnSelect: false 726 }); 727 } 728 this.onFocus(); 729 Ext.apply(this.menu.picker, 730 { 731 minDate : this.minValue, 732 maxDate : this.maxValue, 733 disabledDatesRE : this.disabledDatesRE, 734 disabledDatesText : this.disabledDatesText, 735 disabledDays : this.disabledDays, 736 disabledDaysText : this.disabledDaysText, 737 format : this.format, 738 showToday : this.showToday, 739 minText : String.format(this.minText, this.formatDate(this.minValue)), 740 maxText : String.format(this.maxText, this.formatDate(this.maxValue)) 741 }); 742 this.menu.picker.setValue(this.getValue() || new Date()); 743 this.menu.show(this.el, "tl-bl?"); 744 this.menuEvents('on'); 745 } 746 }); 747 748 749 LABKEY.ext.ComboPlugin = function () { 750 var combo = null; 751 752 return { 753 init : function (combo) { 754 this.combo = combo; 755 if (this.combo.store) 756 { 757 this.combo.mon(this.combo.store, { 758 load: this.resizeList, 759 // fired when the store is filtered or sorted 760 //datachanged: this.resizeList, 761 add: this.resizeList, 762 remove: this.resizeList, 763 update: this.resizeList, 764 buffer: 100, 765 scope: this 766 }); 767 } 768 769 if (Ext.isObject(this.combo.store) && this.combo.store.events) 770 { 771 this.combo.initialValue = this.combo.value; 772 if (this.combo.store.getCount()) 773 { 774 this.initialLoad(); 775 this.resizeList(); 776 } 777 else 778 { 779 this.combo.mon(this.combo.store, 'load', this.initialLoad, this, {single: true}); 780 } 781 } 782 }, 783 784 initialLoad : function() 785 { 786 if (this.combo.initialValue) 787 { 788 this.combo.setValue(this.combo.initialValue); 789 } 790 }, 791 792 resizeList : function () 793 { 794 // bail early if ComboBox was set to an explicit width 795 if (Ext.isDefined(this.combo.listWidth)) 796 return; 797 798 // CONSIDER: set maxListWidth or listWidth instead of calling .doResize(w) below? 799 var w = this.measureList(); 800 801 // NOTE: same as Ext.form.ComboBox.onResize except doesn't call super. 802 if(!isNaN(w) && this.combo.isVisible() && this.combo.list){ 803 this.combo.doResize(w); 804 }else{ 805 this.combo.bufferSize = w; 806 } 807 }, 808 809 measureList : function () 810 { 811 if (!this.tm) 812 { 813 // XXX: should we share a TextMetrics instance across ComboBoxen using a hidden span? 814 var el = this.combo.el ? this.combo.el : Ext.DomHelper.append(document.body, {tag:'span', style:{display:'none'}}); 815 this.tm = Ext.util.TextMetrics.createInstance(el); 816 } 817 818 var w = this.combo.el ? this.combo.el.getWidth(true) : 0; 819 var tpl = null; 820 if (this.combo.rendered) 821 { 822 if (this.combo.view && this.combo.view.tpl instanceof Ext.Template) 823 tpl = this.combo.view.tpl; 824 else if (this.combo.tpl instanceof Ext.Template) 825 tpl = this.combo.tpl; 826 } 827 828 this.combo.store.each(function (r) { 829 var html; 830 if (tpl) 831 html = tpl.apply(r.data); 832 else 833 html = r.get(this.combo.displayField); 834 w = Math.max(w, Math.ceil(this.tm.getWidth(html))); 835 }, this); 836 837 if (this.combo.list) 838 w += this.combo.list.getFrameWidth('lr'); 839 840 // for vertical scrollbar 841 w += 20; 842 843 return w; 844 } 845 } 846 }; 847 Ext.preg('labkey-combo', LABKEY.ext.ComboPlugin); 848 849 LABKEY.ext.ComboBox = Ext.extend(Ext.form.ComboBox, { 850 constructor: function (config) { 851 config.plugins = config.plugins || []; 852 config.plugins.push(LABKEY.ext.ComboPlugin); 853 854 LABKEY.ext.ComboBox.superclass.constructor.call(this, config); 855 }, 856 857 initList : function () { 858 // Issue 18401: Customize view folder picker truncates long folder paths 859 // Add displayField as the qtip text for item that are likely to be truncated. 860 if (!this.tpl) { 861 var cls = 'x-combo-list'; 862 this.tpl = '<tpl for="."><div ext:qtip="{[values[\'' + this.displayField + '\'] && values[\'' + this.displayField + '\'].length > 50 ? values[\'' + this.displayField + '\'] : \'\']}" class="'+cls+'-item">{' + this.displayField + ':htmlEncode}</div></tpl>'; 863 } 864 865 LABKEY.ext.ComboBox.superclass.initList.call(this); 866 } 867 }); 868 869 /** 870 * The following overwrite allows tooltips on labels within form layouts. 871 * The field have to be a property named "gtip" in the corresponding 872 * config object. 873 */ 874 Ext.override(Ext.layout.FormLayout, { 875 setContainer: Ext.layout.FormLayout.prototype.setContainer.createSequence(function(ct) { 876 // the default field template used by all form layouts 877 var t = new Ext.Template( 878 '<div class="x-form-item {itemCls}" tabIndex="-1">', 879 '<label for="{id}" style="{labelStyle}" class="x-form-item-label {guidedCls}"><span {guidedTip}>{label}{labelSeparator}</span></label>', 880 '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">', 881 '</div><div class="{clearCls}"></div>', 882 '</div>' 883 ); 884 t.disableFormats = true; 885 t.compile(); 886 Ext.layout.FormLayout.prototype.fieldTpl = t; 887 }), 888 889 getTemplateArgs : function(field) { 890 var noLabelSep = !field.fieldLabel || field.hideLabel, 891 itemCls = (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : ''); 892 893 // IE9 quirks needs an extra, identifying class on wrappers of TextFields 894 if (Ext.isIE9 && Ext.isIEQuirks && field instanceof Ext.form.TextField) { 895 itemCls += ' x-input-wrapper'; 896 } 897 898 return { 899 id : field.id, 900 label : field.fieldLabel, 901 itemCls : itemCls, 902 clearCls : field.clearCls || 'x-form-clear-left', 903 labelStyle : this.getLabelStyle(field.labelStyle), 904 elementStyle : this.elementStyle||'', 905 labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator), 906 guidedTip : (field.gtip === undefined ? '' : ' ext:gtip="'+field.gtip+'"'), 907 guidedCls : (field.gtip === undefined ? '' : 'g-tip-label') 908 }; 909 } 910 }); 911 912 Ext.reg('checkbox', LABKEY.ext.Checkbox); 913 Ext.reg('combo', LABKEY.ext.ComboBox); 914 Ext.reg('datefield', LABKEY.ext.DateField); 915 Ext.reg('labkey-form', LABKEY.ext.FormPanel); 916 917