PartEditor.js (18975B)
1 /** 2 * @class PartKeepr.PartEditor 3 4 * <p>The PartEditor provides an editing form for a part. It contains multiple tabs, one for each nested record.</p> 5 */ 6 Ext.define('PartKeepr.PartEditor', { 7 extend: 'PartKeepr.Editor', 8 9 // Assigned model 10 model: 'PartKeepr.PartBundle.Entity.Part', 11 bodyPadding: '0 0 0 0', 12 // Layout stuff 13 border: false, 14 layout: 'fit', 15 editAfterSave: false, 16 17 /** 18 * Initializes the editor fields 19 */ 20 initComponent: function () 21 { 22 // Defines the overall height of all fields, used to calculate the anchoring for the description field 23 var overallHeight = (this.partMode == "create") ? 320: 265; 24 25 this.nameField = Ext.create("Ext.form.field.Text", { 26 name: 'name', 27 fieldLabel: i18n("Name"), 28 allowBlank: false, 29 labelWidth: 150 30 }); 31 32 this.storageLocationComboBox = Ext.create("PartKeepr.StorageLocationPicker", 33 { 34 fieldLabel: i18n("Storage Location"), 35 name: 'storageLocation', 36 allowBlank: false, 37 labelWidth: 150 38 }); 39 40 this.footprintNone = Ext.create("Ext.form.field.Radio", { 41 boxLabel: i18n("None"), 42 name: 'footprint_mode', 43 value: "unset", 44 width: 70, 45 listeners: { 46 scope: this, 47 change: function (field, newValue) 48 { 49 if (newValue === true) { 50 this.footprintComboBox.clearValue(); 51 } 52 } 53 } 54 }); 55 56 this.footprintSet = Ext.create("Ext.form.field.Radio", { 57 name: 'footprint_mode', 58 width: 20, 59 value: "set" 60 }); 61 62 /* 63 * Creates the footprint combo box. We listen for the "change" event, because we need to set the footprint 64 * comboboxes to the right state, depending on the selection. Another way would be to patch the combobox 65 * to support "null" values, however, this is a major change within ExtJS and probably not supported for 66 * updates of ExtJS. 67 */ 68 this.footprintComboBox = Ext.create("PartKeepr.FootprintComboBox", { 69 name: 'footprint', 70 returnObject: true, 71 flex: 1, 72 listeners: { 73 scope: this, 74 change: function (field, newValue) 75 { 76 if (typeof(newValue) === 'object') { 77 this.footprintSet.setValue(true); 78 } 79 } 80 } 81 }); 82 83 // Defines the basic editor fields 84 var basicEditorFields = [ 85 this.nameField, 86 { 87 xtype: 'textfield', 88 fieldLabel: i18n("Description"), 89 allowBlank: this.isOptional("description"), 90 name: 'description' 91 }, { 92 layout: 'column', 93 xtype: 'fieldcontainer', 94 margin: { 95 bottom: "0 5px 5px 0" 96 }, 97 border: false, 98 items: [ 99 { 100 xtype: 'numberfield', 101 fieldLabel: i18n('Minimum Stock'), 102 allowDecimals: false, 103 allowBlank: false, 104 labelWidth: 150, 105 name: 'minStockLevel', 106 value: 0, 107 columnWidth: 0.5, 108 minValue: 0 109 }, { 110 padding: "0 0 0 5px", 111 xtype: 'PartUnitComboBox', 112 fieldLabel: i18n("Measurement Unit"), 113 labelWidth: 120, 114 columnWidth: 0.5, 115 returnObject: true, 116 name: 'partUnit' 117 } 118 ] 119 }, { 120 xtype: 'CategoryComboBox', 121 fieldLabel: i18n("Category"), 122 name: 'category', 123 displayField: "name", 124 returnObject: true 125 }, 126 this.storageLocationComboBox, 127 { 128 xtype: 'fieldcontainer', 129 layout: 'hbox', 130 fieldLabel: i18n("Footprint"), 131 defaults: { 132 hideLabel: true 133 }, 134 items: [ 135 this.footprintNone, 136 this.footprintSet, 137 this.footprintComboBox 138 ] 139 }, { 140 xtype: 'textarea', 141 fieldLabel: i18n("Comment"), 142 name: 'comment', 143 allowBlank: this.isOptional("comment"), 144 anchor: '100% ' + (-overallHeight).toString() 145 }, 146 { 147 xtype: 'textfield', 148 fieldLabel: i18n("Production Remarks"), 149 name: 'productionRemarks', 150 allowBlank: this.isOptional("productionRemarks"), 151 },{ 152 xtype: 'fieldcontainer', 153 layout: 'hbox', 154 fieldLabel: i18n("Status"), 155 defaults: { 156 hideLabel: true 157 }, 158 items: [ 159 { 160 xtype: 'textfield', 161 fieldLabel: i18n("Status"), 162 flex: 1, 163 allowBlank: this.isOptional("status"), 164 name: 'status' 165 }, { 166 xtype: 'checkbox', 167 hideEmptyLabel: false, 168 fieldLabel: '', 169 boxLabel: i18n("Needs Review"), 170 name: 'needsReview' 171 } 172 ] 173 }, { 174 xtype: 'textfield', 175 fieldLabel: i18n("Condition"), 176 name: 'partCondition', 177 allowBlank: this.isOptional("partCondition"), 178 }, { 179 xtype: 'fieldcontainer', 180 layout: 'hbox', 181 items: [ 182 { 183 xtype: 'textfield', 184 labelWidth: 150, 185 fieldLabel: i18n("Internal Part Number"), 186 name: 'internalPartNumber', 187 allowBlank: this.isOptional("internalPartNumber"), 188 flex: 1 189 }, { 190 xtype: 'displayfield', 191 qtip: i18n("The first number is the ID in decimal, the second number is the ID in base36"), 192 fieldLabel: i18n("Internal ID"), 193 listeners: { 194 render: function (c) 195 { 196 Ext.QuickTips.register({ 197 target: c.getEl(), 198 text: c.qtip 199 }); 200 } 201 }, 202 itemId: 'idField', 203 name: '@id', 204 fieldStyle: { 205 color: "blue", 206 "text-decoration": "underline", 207 }, 208 renderer: function (value) 209 { 210 var values = value.split("/"); 211 var idstr = values[values.length - 1]; 212 var idint = parseInt(idstr); 213 214 return idstr + " (#" + idint.toString(36) + ")"; 215 } 216 } 217 ] 218 219 } 220 ]; 221 222 // Creates the distributor grid 223 this.partDistributorGrid = Ext.create("PartKeepr.PartDistributorGrid", { 224 title: i18n("Distributors"), 225 iconCls: 'web-icon lorry', 226 layout: 'fit' 227 }); 228 229 // Creates the manufacturer grid 230 this.partManufacturerGrid = Ext.create("PartKeepr.PartManufacturerGrid", { 231 title: i18n("Manufacturers"), 232 iconCls: 'fugue-icon building', 233 layout: 'fit' 234 }); 235 236 // Creates the attachment grid 237 this.partParameterGrid = Ext.create("PartKeepr.PartParameterGrid", { 238 title: i18n("Part Parameters"), 239 iconCls: 'fugue-icon table', 240 layout: 'fit' 241 }); 242 243 // Creates the attachment grid 244 this.partAttachmentGrid = Ext.create("PartKeepr.PartAttachmentGrid", { 245 title: i18n("Attachments"), 246 iconCls: 'web-icon attach', 247 layout: 'fit' 248 }); 249 250 // Adds stock level fields for new items 251 if (this.partMode && this.partMode == "create") { 252 this.initialStockLevel = Ext.create("Ext.form.field.Number", { 253 fieldLabel: i18n("Initial Stock Level"), 254 name: "initialStockLevel", 255 labelWidth: 150, 256 columnWidth: 0.5 257 }); 258 259 this.initialStockLevelUser = Ext.create("PartKeepr.UserComboBox", { 260 fieldLabel: i18n("Stock User"), 261 name: 'initialStockLevelUser', 262 columnWidth: 0.5, 263 returnObject: true 264 }); 265 266 basicEditorFields.push({ 267 xtype: 'container', 268 layout: 'column', 269 border: false, 270 items: [ 271 this.initialStockLevel, 272 this.initialStockLevelUser 273 ] 274 }); 275 276 this.initialStockLevelPrice = Ext.create("PartKeepr.CurrencyField", { 277 fieldLabel: i18n('Price'), 278 labelWidth: 150, 279 columnWidth: 0.5, 280 name: 'initialStockLevelPrice' 281 }); 282 283 this.initialStockLevelPricePerItem = Ext.create("Ext.form.field.Checkbox", { 284 boxLabel: i18n("Per Item"), 285 columnWidth: 0.5, 286 name: 'initialStockLevelPricePerItem' 287 }); 288 289 basicEditorFields.push({ 290 xtype: 'container', 291 layout: 'column', 292 border: false, 293 items: [ 294 this.initialStockLevelPrice, 295 this.initialStockLevelPricePerItem 296 ] 297 }); 298 299 300 } 301 302 // Create a tab panel of all fields 303 this.items = { 304 xtype: 'tabpanel', 305 border: false, 306 items: [ 307 { 308 iconCls: 'web-icon brick', 309 ui: 'default-framed', 310 xtype: 'panel', 311 autoScroll: false, 312 layout: 'anchor', 313 defaults: { 314 anchor: '100%', 315 labelWidth: 150 316 }, 317 title: i18n("Basic Data"), 318 items: basicEditorFields 319 }, 320 this.partDistributorGrid, 321 this.partManufacturerGrid, 322 this.partParameterGrid, 323 this.partAttachmentGrid 324 ] 325 }; 326 327 this.on("startEdit", this.onEditStart, this, {delay: 200}); 328 this.on("itemSaved", this._onItemSaved, this); 329 330 this.callParent(); 331 332 this.on("itemSave", this.onItemSave, this); 333 this.down("#idField").on("beforedestroy", this.onBeforeDestroy, this.down("#idField")); 334 335 }, 336 /** 337 * Unregisters the quick tip immediately prior destroying 338 */ 339 onBeforeDestroy: function (field) { 340 Ext.QuickTips.unregister(field.getEl()); 341 }, 342 /** 343 * Cleans up the record prior saving. 344 */ 345 onItemSave: function () 346 { 347 var removeRecords = [], j, errors = [], 348 minDistributorCount = PartKeepr.getApplication().getSystemPreference( 349 "partkeepr.part.constraints.distributorCount", 0), 350 minManufacturerCount = PartKeepr.getApplication().getSystemPreference( 351 "partkeepr.part.constraints.manufacturerCount", 0), 352 minAttachmentCount = PartKeepr.getApplication().getSystemPreference( 353 "partkeepr.part.constraints.attachmentCount", 0); 354 355 /** 356 * Iterate through all records and check if a valid distributor 357 * ID is assigned. If not, the record is removed as it is assumed 358 * that the record is invalid and being removed. 359 */ 360 for (j = 0; j < this.record.distributors().getCount(); j++) { 361 if (this.record.distributors().getAt(j).getDistributor() === null) { 362 removeRecords.push(this.record.distributors().getAt(j)); 363 } 364 } 365 366 if (removeRecords.length > 0) { 367 this.record.distributors().remove(removeRecords); 368 } 369 370 if (this.record.distributors().getCount() < minDistributorCount) { 371 errors.push( 372 Ext.String.format(i18n("The number of distributors must be greater than {0}"), minDistributorCount)); 373 } 374 375 removeRecords = []; 376 377 /** 378 * Iterate through all records and check if a valid manufacturer 379 * ID is assigned. If not, the record is removed as it is assumed 380 * that the record is invalid and being removed. 381 */ 382 383 /*for (j = 0; j < this.record.manufacturers().getCount(); j++) { 384 if (this.record.manufacturers().getAt(j).getManufacturer() === null) { 385 removeRecords.push(this.record.manufacturers().getAt(j)); 386 } 387 }*/ 388 389 if (removeRecords.length > 0) { 390 this.record.manufacturers().remove(removeRecords); 391 } 392 393 if (this.record.manufacturers().getCount() < minManufacturerCount) { 394 errors.push( 395 Ext.String.format(i18n("The number of manufacturers must be greater than {0}"), minManufacturerCount)); 396 } 397 398 if (this.record.attachments().getCount() < minAttachmentCount) { 399 errors.push( 400 Ext.String.format(i18n("The number of attachments must be greater than {0}"), minAttachmentCount)); 401 } 402 403 // Force footprint to be "null" when the checkbox is checked. 404 if (this.footprintNone.getValue() === true) { 405 this.record.setFootprint(null); 406 } 407 408 if (this.initialStockLevel) { 409 var initialStockLevel = this.initialStockLevel.getValue(); 410 411 if (this.record.phantom && initialStockLevel > 0) { 412 var stockLevel = Ext.create("PartKeepr.StockBundle.Entity.StockEntry"); 413 stockLevel.set("stockLevel", initialStockLevel); 414 stockLevel.setUser(this.initialStockLevelUser.getValue()); 415 416 if (this.initialStockLevelPricePerItem.getValue() === false) { 417 stockLevel.set("price", this.initialStockLevelPrice.getValue() / initialStockLevel); 418 } else { 419 stockLevel.set("price", this.initialStockLevelPrice.getValue()); 420 } 421 422 this.record.stockLevels().add(stockLevel); 423 } 424 } 425 426 if (errors.length > 0) { 427 Ext.Msg.alert(i18n("Error"), errors.join("<br/>")); 428 return false; 429 } 430 }, 431 onEditStart: function () 432 { 433 this.bindChildStores(); 434 this.nameField.focus(); 435 436 // Re-trigger validation because of asynchronous loading of the storage location field, 437 // which would be marked invalid because validation happens immediately, but after loading 438 // the storage locations, the field is valid, but not re-validated. 439 440 // This workaround is done twice; once after the store is loaded and once when we start editing, 441 // because we don't know which event will come first 442 this.getForm().isValid(); 443 444 if (this.record.getFootprint() !== null) { 445 this.footprintSet.setValue(true); 446 } else { 447 this.footprintNone.setValue(true); 448 } 449 }, 450 _onItemSaved: function () 451 { 452 this.fireEvent("partSaved", this.record); 453 454 if (this.keepOpenCheckbox.getValue() !== true && this.createCopyCheckbox.getValue() !== true) { 455 this.fireEvent("editorClose", this); 456 } else { 457 var newItem, data; 458 459 if (this.partMode == "create") { 460 if (this.copyPartDataCheckbox.getValue() === true) { 461 data = this.record.getData(); 462 delete data["@id"]; 463 464 newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); 465 newItem.set(data); 466 newItem.setAssociationData(this.record.getAssociationData()); 467 newItem.stockLevels().removeAll(); 468 newItem.set("stockLevel", 0); 469 this.editItem(newItem); 470 471 } else { 472 newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); 473 newItem.setPartUnit(PartKeepr.getApplication().getDefaultPartUnit()); 474 475 newItem.setCategory(this.record.getCategory()); 476 477 this.editItem(newItem); 478 } 479 } else { 480 data = this.record.getData(); 481 delete data["@id"]; 482 483 newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); 484 newItem.set(data); 485 newItem.setAssociationData(this.record.getAssociationData()); 486 this.editItem(newItem); 487 } 488 } 489 }, 490 bindChildStores: function () 491 { 492 this.partDistributorGrid.bindStore(this.record.distributors()); 493 this.partManufacturerGrid.bindStore(this.record.manufacturers()); 494 this.partAttachmentGrid.bindStore(this.record.attachments()); 495 this.partParameterGrid.bindStore(this.record.parameters()); 496 }, 497 onCancelEdit: function () { 498 this.record.distributors().rejectChanges(); 499 this.record.manufacturers().rejectChanges(); 500 this.record.attachments().rejectChanges(); 501 this.record.parameters().rejectChanges(); 502 this.callParent(arguments); 503 }, 504 setTitle: function (title) 505 { 506 var tmpTitle; 507 508 if (this.record.phantom) { 509 tmpTitle = i18n("Add Part"); 510 } else { 511 tmpTitle = i18n("Edit Part"); 512 } 513 514 if (title !== "") { 515 tmpTitle = tmpTitle + ": " + title; 516 } 517 518 this.fireEvent("_titleChange", tmpTitle); 519 }, 520 isOptional: function (field) 521 { 522 var fields = PartKeepr.getApplication().getSystemPreference("partkeepr.part.requiredFields", []); 523 524 if (Ext.Array.contains(fields, field)) { 525 return false; 526 } else { 527 return true; 528 } 529 } 530 });