partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit 8b764b7a5d96d7f411e89e9b72cffe5bde442080
parent 483212434c8bda26f1f2b1ccfaa09346c1312878
Author: Felicitus <felicitus@felicitus.org>
Date:   Sun, 30 Aug 2015 15:47:19 +0200

Externalized part category store, re-added category combobox, reverted to classic theme for easier migration

Diffstat:
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Editor/PartEditor.js | 908++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartCategoryTree.js | 28+---------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/CategoryComboBox.js | 154++-----------------------------------------------------------------------------
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Data/store/PartCategoryStore.js | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 8++++----
5 files changed, 531 insertions(+), 628 deletions(-)

diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Editor/PartEditor.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Editor/PartEditor.js @@ -4,452 +4,470 @@ * <p>The PartEditor provides an editing form for a part. It contains multiple tabs, one for each nested record.</p> */ Ext.define('PartKeepr.PartEditor', { - extend: 'PartKeepr.Editor', - - // Assigned model - model: 'PartKeepr.PartBundle.Entity.Part', - - // Layout stuff - border: false, - layout: 'fit', - bodyStyle: 'background:#DBDBDB;', - - /** - * Initializes the editor fields - */ - initComponent: function () { - // Defines the overall height of all fields, used to calculate the anchoring for the description field - var overallHeight = (this.partMode == "create") ? '-280' : '-235'; - - this.nameField = Ext.create("Ext.form.field.Text", { - name: 'name', - fieldLabel: i18n("Name"), - allowBlank: false, - labelWidth: 150 - }); - - this.storageLocationComboBox = Ext.create("PartKeepr.StorageLocationComboBox", - { - fieldLabel: i18n("Storage Location"), - name: 'storageLocation', - allowBlank: false, - labelWidth: 150 - }); - - this.storageLocationComboBox.store.on("load", function () { - // Re-trigger validation because of asynchronous loading of the storage location field, - // which would be marked invalid because validation happens immediately, but after loading - // the storage locations, the field is valid, but not re-validated. - - // This workaround is done twice; once after the store is loaded and once when we start editing, - // because we don't know which event will come first - this.getForm().isValid(); - }, this); - - this.footprintNone = Ext.create("Ext.form.field.Radio", { - boxLabel: i18n("None"), - name: 'footprint_mode', - margin: { - right: 5 - }, - value: "unset", - listeners: { - scope: this, - change: function (field, newValue) { - if (newValue === true) { - this.footprintComboBox.clearValue(); - } - } - } - }); - - this.footprintSet = Ext.create("Ext.form.field.Radio", { - name: 'footprint_mode', - margin: { - right: 5 - }, - value: "set" - }); - - /* - * Creates the footprint combo box. We listen for the "change" event, because we need to set the footprint - * comboboxes to the right state, depending on the selection. Another way would be to patch the combobox - * to support "null" values, however, this is a major change within ExtJS and probably not supported for - * updates of ExtJS. - */ - this.footprintComboBox = Ext.create("PartKeepr.FootprintComboBox", { - name: 'footprint', - flex: 1, - listeners: { - scope: this, - change: function (field, newValue) { - if (newValue !== 0) { - this.footprintSet.setValue(true); - } - } - } - }); - - // Defines the basic editor fields - var basicEditorFields = [ - this.nameField, - { - xtype: 'textfield', - fieldLabel: i18n("Description"), - name: 'description' - },{ - layout: 'column', - bodyStyle: 'background:#DBDBDB', - border: false, - items: [{ - xtype: 'numberfield', - fieldLabel: i18n('Minimum Stock'), - allowDecimals: false, - allowBlank: false, - labelWidth: 150, - name: 'minStockLevel', - value: 0, - columnWidth: 0.5, - minValue: 0 - },{ - xtype: 'PartUnitComboBox', - fieldLabel: i18n("Part Unit"), - columnWidth: 0.5, - margin: "0 0 0 5", - name: 'partUnit', - value: PartKeepr.getApplication().getDefaultPartUnit().get("id") - }] - },{ - xtype: 'CategoryComboBox', - fieldLabel: i18n("Category"), - name: 'category' - }, - this.storageLocationComboBox, - { - xtype: 'fieldcontainer', - layout: 'hbox', - fieldLabel: i18n("Footprint"), - defaults: { - hideLabel: true - }, - items: [ - this.footprintNone, - this.footprintSet, - this.footprintComboBox - ] - },{ - xtype: 'textarea', - fieldLabel: i18n("Comment"), - name: 'comment', - anchor: '100% ' + overallHeight - }, - { - xtype: 'fieldcontainer', - layout: 'hbox', - fieldLabel: i18n("Status"), - defaults: { - hideLabel: true - }, - items: [{ - xtype: 'textfield', - fieldLabel: i18n("Status"), - flex: 1, - name: 'status' - },{ - xtype: 'checkbox', - hideEmptyLabel: false, - fieldLabel: '', - margins: { - left: 5 - }, - boxLabel: i18n("Needs Review"), - name: 'needsReview' - }] - },{ - xtype: 'textfield', - fieldLabel: i18n("Condition"), - name: 'partCondition' - },{ + extend: 'PartKeepr.Editor', + + // Assigned model + model: 'PartKeepr.PartBundle.Entity.Part', + + // Layout stuff + border: false, + layout: 'fit', + bodyStyle: 'background:#DBDBDB;', + + /** + * Initializes the editor fields + */ + initComponent: function () + { + // Defines the overall height of all fields, used to calculate the anchoring for the description field + var overallHeight = (this.partMode == "create") ? '-280' : '-235'; + + this.nameField = Ext.create("Ext.form.field.Text", { + name: 'name', + fieldLabel: i18n("Name"), + allowBlank: false, + labelWidth: 150 + }); + + this.storageLocationComboBox = Ext.create("PartKeepr.StorageLocationComboBox", + { + fieldLabel: i18n("Storage Location"), + name: 'storageLocation', + allowBlank: false, + labelWidth: 150 + }); + + this.storageLocationComboBox.store.on("load", function () + { + // Re-trigger validation because of asynchronous loading of the storage location field, + // which would be marked invalid because validation happens immediately, but after loading + // the storage locations, the field is valid, but not re-validated. + + // This workaround is done twice; once after the store is loaded and once when we start editing, + // because we don't know which event will come first + this.getForm().isValid(); + }, this); + + this.footprintNone = Ext.create("Ext.form.field.Radio", { + boxLabel: i18n("None"), + name: 'footprint_mode', + margin: { + right: 5 + }, + value: "unset", + listeners: { + scope: this, + change: function (field, newValue) + { + if (newValue === true) { + this.footprintComboBox.clearValue(); + } + } + } + }); + + this.footprintSet = Ext.create("Ext.form.field.Radio", { + name: 'footprint_mode', + margin: { + right: 5 + }, + value: "set" + }); + + /* + * Creates the footprint combo box. We listen for the "change" event, because we need to set the footprint + * comboboxes to the right state, depending on the selection. Another way would be to patch the combobox + * to support "null" values, however, this is a major change within ExtJS and probably not supported for + * updates of ExtJS. + */ + this.footprintComboBox = Ext.create("PartKeepr.FootprintComboBox", { + name: 'footprint', + flex: 1, + listeners: { + scope: this, + change: function (field, newValue) + { + if (newValue !== 0) { + this.footprintSet.setValue(true); + } + } + } + }); + + // Defines the basic editor fields + var basicEditorFields = [ + this.nameField, + { + xtype: 'textfield', + fieldLabel: i18n("Description"), + name: 'description' + }, { + layout: 'column', + bodyStyle: 'background:#DBDBDB', + border: false, + items: [ + { + xtype: 'numberfield', + fieldLabel: i18n('Minimum Stock'), + allowDecimals: false, + allowBlank: false, + labelWidth: 150, + name: 'minStockLevel', + value: 0, + columnWidth: 0.5, + minValue: 0 + }, { + xtype: 'PartUnitComboBox', + fieldLabel: i18n("Part Unit"), + columnWidth: 0.5, + margin: "0 0 0 5", + name: 'partUnit', + value: PartKeepr.getApplication().getDefaultPartUnit().get("id") + } + ] + }, { + xtype: 'CategoryComboBox', + fieldLabel: i18n("Category"), + name: 'category', + displayField: "name", + store: Ext.create("PartKeepr.data.store.PartCategoryStore"), + rootVisible: false + }, + this.storageLocationComboBox, + { + xtype: 'fieldcontainer', + layout: 'hbox', + fieldLabel: i18n("Footprint"), + defaults: { + hideLabel: true + }, + items: [ + this.footprintNone, + this.footprintSet, + this.footprintComboBox + ] + }, { + xtype: 'textarea', + fieldLabel: i18n("Comment"), + name: 'comment', + anchor: '100% ' + overallHeight + }, + { + xtype: 'fieldcontainer', + layout: 'hbox', + fieldLabel: i18n("Status"), + defaults: { + hideLabel: true + }, + items: [ + { + xtype: 'textfield', + fieldLabel: i18n("Status"), + flex: 1, + name: 'status' + }, { + xtype: 'checkbox', + hideEmptyLabel: false, + fieldLabel: '', + margins: { + left: 5 + }, + boxLabel: i18n("Needs Review"), + name: 'needsReview' + } + ] + }, { + xtype: 'textfield', + fieldLabel: i18n("Condition"), + name: 'partCondition' + }, { xtype: 'fieldcontainer', layout: 'hbox', - items : [{ - xtype: 'textfield', - labelWidth: 150, - fieldLabel: i18n("Internal Part Number"), - name: 'internalPartNumber', - flex: 1 - },{ - xtype: 'displayfield', - margins: { - left: 5 + items: [ + { + xtype: 'textfield', + labelWidth: 150, + fieldLabel: i18n("Internal Part Number"), + name: 'internalPartNumber', + flex: 1 + }, { + xtype: 'displayfield', + margins: { + left: 5 + }, + fieldLabel: i18n("Internal ID"), + name: 'id' + } + ] + + } + ]; + + // Creates the distributor grid + this.partDistributorGrid = Ext.create("PartKeepr.PartDistributorGrid", { + title: i18n("Distributors"), + iconCls: 'icon-lorry', + layout: 'fit' + }); + + // Creates the manufacturer grid + this.partManufacturerGrid = Ext.create("PartKeepr.PartManufacturerGrid", { + title: i18n("Manufacturers"), + iconCls: 'icon-building', + layout: 'fit' + }); + + // Creates the parameter grid + this.partParameterGrid = Ext.create("PartKeepr.PartParameterGrid", { + title: i18n("Parameters"), + iconCls: 'icon-table', + layout: 'fit' + }); + + // Creates the attachment grid + this.partAttachmentGrid = Ext.create("PartKeepr.PartAttachmentGrid", { + title: i18n("Attachments"), + iconCls: 'icon-attach', + layout: 'fit' + }); + + // Adds stock level fields for new items + if (this.partMode && this.partMode == "create") { + this.initialStockLevel = Ext.create("Ext.form.field.Number", { + fieldLabel: i18n("Initial Stock Level"), + name: "initialStockLevel", + labelWidth: 150, + columnWidth: 0.5 + }); + + this.initialStockLevelUser = Ext.create("PartKeepr.UserComboBox", { + fieldLabel: i18n("Stock User"), + name: 'initialStockLevelUser', + columnWidth: 0.5, + margin: "0 0 0 5" + }); + + basicEditorFields.push({ + layout: 'column', + bodyStyle: 'background:#DBDBDB', + border: false, + items: [ + this.initialStockLevel, + this.initialStockLevelUser + ] + }); + + this.initialStockLevelPrice = Ext.create("PartKeepr.CurrencyField", { + fieldLabel: i18n('Price'), + labelWidth: 150, + columnWidth: 0.5, + name: 'initialStockLevelPrice' + }); + + this.initialStockLevelPricePerItem = Ext.create("Ext.form.field.Checkbox", { + boxLabel: i18n("Per Item"), + columnWidth: 0.5, + margin: "0 0 0 5", + name: 'initialStockLevelPricePerItem' + }); + + basicEditorFields.push({ + layout: 'column', + bodyStyle: 'background:#DBDBDB', + border: false, + items: [ + this.initialStockLevelPrice, + this.initialStockLevelPricePerItem + ] + }); + + + } + + // Create a tab panel of all fields + this.items = { + xtype: 'tabpanel', + border: false, + plain: true, + items: [ + { + iconCls: 'icon-brick', + xtype: 'panel', + border: false, + autoScroll: false, + layout: 'anchor', + defaults: { + anchor: '100%', + labelWidth: 150 }, - fieldLabel: i18n("Internal ID"), - name: 'id' - }] - - }]; - - // Creates the distributor grid - this.partDistributorGrid = Ext.create("PartKeepr.PartDistributorGrid", { - title: i18n("Distributors"), - iconCls: 'icon-lorry', - layout: 'fit' - }); - - // Creates the manufacturer grid - this.partManufacturerGrid = Ext.create("PartKeepr.PartManufacturerGrid", { - title: i18n("Manufacturers"), - iconCls: 'icon-building', - layout: 'fit' - }); - - // Creates the parameter grid - this.partParameterGrid = Ext.create("PartKeepr.PartParameterGrid", { - title: i18n("Parameters"), - iconCls: 'icon-table', - layout: 'fit' - }); - - // Creates the attachment grid - this.partAttachmentGrid = Ext.create("PartKeepr.PartAttachmentGrid", { - title: i18n("Attachments"), - iconCls: 'icon-attach', - layout: 'fit' - }); - - // Adds stock level fields for new items - if (this.partMode && this.partMode == "create") { - this.initialStockLevel = Ext.create("Ext.form.field.Number", { - fieldLabel: i18n("Initial Stock Level"), - name: "initialStockLevel", - labelWidth: 150, - columnWidth: 0.5 - }); - - this.initialStockLevelUser = Ext.create("PartKeepr.UserComboBox", { - fieldLabel: i18n("Stock User"), - name: 'initialStockLevelUser', - columnWidth: 0.5, - margin: "0 0 0 5" - }); - - basicEditorFields.push({ - layout: 'column', - bodyStyle: 'background:#DBDBDB', - border: false, - items: [ - this.initialStockLevel, - this.initialStockLevelUser - ] - }); - - this.initialStockLevelPrice = Ext.create("PartKeepr.CurrencyField", { - fieldLabel: i18n('Price'), - labelWidth: 150, - columnWidth: 0.5, - name: 'initialStockLevelPrice' - }); - - this.initialStockLevelPricePerItem = Ext.create("Ext.form.field.Checkbox", { - boxLabel: i18n("Per Item"), - columnWidth: 0.5, - margin: "0 0 0 5", - name: 'initialStockLevelPricePerItem' - }); - - basicEditorFields.push({ - layout: 'column', - bodyStyle: 'background:#DBDBDB', - border: false, - items: [ - this.initialStockLevelPrice, - this.initialStockLevelPricePerItem - ] - }); - - - } - - // Create a tab panel of all fields - this.items = { - xtype: 'tabpanel', - border: false, - plain: true, - items: [{ - iconCls: 'icon-brick', - xtype: 'panel', - border: false, - autoScroll: false, - layout: 'anchor', - defaults: { - anchor: '100%', - labelWidth: 150 - }, - bodyStyle: 'background:#DBDBDB;padding: 10px;', - title: i18n("Basic Data"), - items: basicEditorFields - }, - this.partDistributorGrid, - this.partManufacturerGrid, - this.partParameterGrid, - this.partAttachmentGrid - ] - }; - - this.on("startEdit", this.onEditStart, this, { delay: 200 }); - this.on("itemSaved", this._onItemSaved, this); - - this.callParent(); - - this.on("itemSave", this.onItemSave, this); - - }, - /** - * Cleans up the record prior saving. - */ - onItemSave: function () { - var removeRecords = [], j; - - /** - * Iterate through all records and check if a valid distributor - * ID is assigned. If not, the record is removed as it is assumed - * that the record is invalid and being removed. - */ - for (j=0;j<this.record.distributors().getCount();j++) { - if (this.record.distributors().getAt(j).get("distributor_id") === 0) { - removeRecords.push(this.record.distributors().getAt(j)); - } - } - - if (removeRecords.length > 0) { - this.record.distributors().remove(removeRecords); - } - - removeRecords = []; - - /** - * Iterate through all records and check if a valid parameter - * ID is assigned. If not, the record is removed as it is assumed - * that the record is invalid and being removed. - */ - - for (j=0;j<this.record.parameters().getCount();j++) { - if (this.record.parameters().getAt(j).get("unit_id") === 0) { - removeRecords.push(this.record.parameters().getAt(j)); - } - } - - if (removeRecords.length > 0) { - this.record.parameters().remove(removeRecords); - } - - removeRecords = []; - - /** - * Iterate through all records and check if a valid manufacturer - * ID is assigned. If not, the record is removed as it is assumed - * that the record is invalid and being removed. - */ - - for (j=0;j<this.record.manufacturers().getCount();j++) { - if (this.record.manufacturers().getAt(j).get("manufacturer_id") === 0) { - removeRecords.push(this.record.manufacturers().getAt(j)); - } - } - - if (removeRecords.length > 0) { - this.record.manufacturers().remove(removeRecords); - } - - /** - * Check if the storage location is valid. If not, try an exact, case-insensitive match for the - * storage location name and inject that into the record. - */ - if (isNaN(this.record.get("storageLocation"))) { - var storageLocationRecord = this.storageLocationComboBox.getStore().findRecord( - "name", - this.storageLocationComboBox.getValue(), - 0, false, false, true) ; - - this.record.set("storageLocation", storageLocationRecord.get("id")); - } - - // Force footprint to be "null" when the checkbox is checked. - if (this.footprintNone.getValue() === true) { - this.record.set("footprint", 0); - } - - }, - onEditStart: function () { - this.bindChildStores(); - this.nameField.focus(); - - // Re-trigger validation because of asynchronous loading of the storage location field, - // which would be marked invalid because validation happens immediately, but after loading - // the storage locations, the field is valid, but not re-validated. - - // This workaround is done twice; once after the store is loaded and once when we start editing, - // because we don't know which event will come first - this.getForm().isValid(); - - if (this.record.get("footprint") === 0) { - this.footprintNone.setValue(true); - } else { - this.footprintSet.setValue(true); - } - }, - _onItemSaved: function () { - this.fireEvent("partSaved", this.record); - - if (this.keepOpenCheckbox.getValue() !== true && this.createCopyCheckbox.getValue() !== true) { - this.fireEvent("editorClose", this); - } else { - var newItem; - if (this.partMode == "create") { - if (this.copyPartDataCheckbox.getValue() === true) { - data = this.record.getData(true); - data.id = null; - newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); - newItem.setDataWithAssociations(data); - - this.editItem(newItem); - } else { - newItem = Ext.create("PartKeepr.PartBundle.Entity.Part", this.partDefaults); - this.editItem(newItem); - } - } else { - var data = this.record.getData(true); - data.id = null; - newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); - newItem.setDataWithAssociations(data); - - this.editItem(newItem); - } - - - - - } - }, - bindChildStores: function () { - this.partDistributorGrid.bindStore(this.record.distributors()); - this.partManufacturerGrid.bindStore(this.record.manufacturers()); - this.partParameterGrid.bindStore(this.record.parameters()); - this.partAttachmentGrid.bindStore(this.record.attachments()); - }, - setTitle: function (title) { - var tmpTitle; - - if (this.record.phantom) { - tmpTitle = i18n("Add Part"); - } else { - tmpTitle = i18n("Edit Part"); - } - - if (title !== "") { - tmpTitle = tmpTitle + ": " + title; - } - - this.fireEvent("_titleChange", tmpTitle); - } + bodyStyle: 'background:#DBDBDB;padding: 10px;', + title: i18n("Basic Data"), + items: basicEditorFields + }, + this.partDistributorGrid, + this.partManufacturerGrid, + this.partParameterGrid, + this.partAttachmentGrid + ] + }; + + this.on("startEdit", this.onEditStart, this, {delay: 200}); + this.on("itemSaved", this._onItemSaved, this); + + this.callParent(); + + this.on("itemSave", this.onItemSave, this); + + }, + /** + * Cleans up the record prior saving. + */ + onItemSave: function () + { + var removeRecords = [], j; + + /** + * Iterate through all records and check if a valid distributor + * ID is assigned. If not, the record is removed as it is assumed + * that the record is invalid and being removed. + */ + for (j = 0; j < this.record.distributors().getCount(); j++) { + if (this.record.distributors().getAt(j).get("distributor_id") === 0) { + removeRecords.push(this.record.distributors().getAt(j)); + } + } + + if (removeRecords.length > 0) { + this.record.distributors().remove(removeRecords); + } + + removeRecords = []; + + /** + * Iterate through all records and check if a valid parameter + * ID is assigned. If not, the record is removed as it is assumed + * that the record is invalid and being removed. + */ + + for (j = 0; j < this.record.parameters().getCount(); j++) { + if (this.record.parameters().getAt(j).get("unit_id") === 0) { + removeRecords.push(this.record.parameters().getAt(j)); + } + } + + if (removeRecords.length > 0) { + this.record.parameters().remove(removeRecords); + } + + removeRecords = []; + + /** + * Iterate through all records and check if a valid manufacturer + * ID is assigned. If not, the record is removed as it is assumed + * that the record is invalid and being removed. + */ + + for (j = 0; j < this.record.manufacturers().getCount(); j++) { + if (this.record.manufacturers().getAt(j).get("manufacturer_id") === 0) { + removeRecords.push(this.record.manufacturers().getAt(j)); + } + } + + if (removeRecords.length > 0) { + this.record.manufacturers().remove(removeRecords); + } + + /** + * Check if the storage location is valid. If not, try an exact, case-insensitive match for the + * storage location name and inject that into the record. + */ + if (isNaN(this.record.get("storageLocation"))) { + var storageLocationRecord = this.storageLocationComboBox.getStore().findRecord( + "name", + this.storageLocationComboBox.getValue(), + 0, false, false, true); + + this.record.set("storageLocation", storageLocationRecord.get("id")); + } + + // Force footprint to be "null" when the checkbox is checked. + if (this.footprintNone.getValue() === true) { + this.record.set("footprint", 0); + } + + }, + onEditStart: function () + { + this.bindChildStores(); + this.nameField.focus(); + + // Re-trigger validation because of asynchronous loading of the storage location field, + // which would be marked invalid because validation happens immediately, but after loading + // the storage locations, the field is valid, but not re-validated. + + // This workaround is done twice; once after the store is loaded and once when we start editing, + // because we don't know which event will come first + this.getForm().isValid(); + + if (this.record.get("footprint") === 0) { + this.footprintNone.setValue(true); + } else { + this.footprintSet.setValue(true); + } + }, + _onItemSaved: function () + { + this.fireEvent("partSaved", this.record); + + if (this.keepOpenCheckbox.getValue() !== true && this.createCopyCheckbox.getValue() !== true) { + this.fireEvent("editorClose", this); + } else { + var newItem; + if (this.partMode == "create") { + if (this.copyPartDataCheckbox.getValue() === true) { + data = this.record.getData(true); + data.id = null; + newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); + newItem.setDataWithAssociations(data); + + this.editItem(newItem); + } else { + newItem = Ext.create("PartKeepr.PartBundle.Entity.Part", this.partDefaults); + this.editItem(newItem); + } + } else { + var data = this.record.getData(true); + data.id = null; + newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); + newItem.setDataWithAssociations(data); + + this.editItem(newItem); + } + + + } + }, + bindChildStores: function () + { + this.partDistributorGrid.bindStore(this.record.distributors()); + this.partManufacturerGrid.bindStore(this.record.manufacturers()); + this.partParameterGrid.bindStore(this.record.parameters()); + this.partAttachmentGrid.bindStore(this.record.attachments()); + }, + setTitle: function (title) + { + var tmpTitle; + + if (this.record.phantom) { + tmpTitle = i18n("Add Part"); + } else { + tmpTitle = i18n("Edit Part"); + } + + if (title !== "") { + tmpTitle = tmpTitle + ": " + title; + } + + this.fireEvent("_titleChange", tmpTitle); + } }); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartCategoryTree.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartCategoryTree.js @@ -8,33 +8,7 @@ Ext.define("PartKeepr.PartCategoryTree", { initComponent: function () { - this.store = Ext.create("Ext.data.TreeStore", - { - remoteSort: false, - folderSort: true, - rootVisible: true, - autoLoad: true, - sorters: [ - { - property: 'name', - direction: 'ASC' - } - ], - root: { - "@id": "@local-tree-root" - }, - model: "PartKeepr.PartBundle.Entity.PartCategory", - proxy: { - ignoreLoadId: '@local-tree-root', - url: "/api/part_categories/getExtJSRootNode", - type: "Hydra", - appendId: false, - reader: { - type: 'json' - } - - } - }); + this.store = Ext.create("PartKeepr.data.store.PartCategoryStore"); this.callParent(); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/CategoryComboBox.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/CategoryComboBox.js @@ -1,154 +1,4 @@ Ext.define("PartKeepr.CategoryComboBox",{ - extend:"Ext.form.field.Picker", - alias: 'widget.CategoryComboBox', - requires:["Ext.tree.Panel"], - selectedValue: null, - - trigger2Cls: Ext.baseCSSPrefix + 'form-reload-trigger', - - initComponent: function(){ - var self = this; - - Ext.apply(self,{ - pickerAlign:"tl-bl?", - editable: false - - }); - - self.callParent(); - - this.createPicker(); - - }, - onTrigger1Click: function () { - this.onTriggerClick(); - }, - onTrigger2Click: function () { - this.collapse(); - this.picker.loadCategories(); - this.expand(); - }, - /** - * Override the expand method so that it doesn't expand immediately when data is being loaded. Defers the expand - * function for 100 milliseconds until the picker data has been loaded. - */ - expand: function () { - if (!this.picker.loaded) { - Ext.defer(this.expand, 100, this); - return; - } else { - this.callParent(); - } - }, - createPicker: function(){ - var self = this; - - self.picker = new PartKeepr.CategoryTree({ - height:290, - categoryService: 'PartCategory', - categoryModel: 'PartKeepr.PartCategory', - floating: true, - focusOnToFront: false, - shadow: false, - ownerCt: this.ownerCt - }); - - self.picker.on({ - itemclick: Ext.bind(function(sm, record){ - this.setValue(record.get("name"), true); - this.setSelectedValue(record.get("id")); - this.collapse(); - },this), - show: { - fn: Ext.bind(function(cmp) { - var record = this.picker.getSelectionModel().getLastSelected(); - - this.picker.getView().focusRow(record); - }, this), - delay: 50 - } - }); - - self.picker.getView().on("render", Ext.bind(function () { - var record = this.picker.getSelectionModel().getLastSelected(); - this.picker.getView().ensureVisible(record); - - this.picker.getView().focusRow(record); - },this)); - - return self.picker; - }, - setSelectedValue: function (id) { - this.selectedValue = id; - }, - getValue: function () { - return this.selectedValue; - }, - setValue: function (val, parent) { - if (parent) { - this.callParent([val]); - } - - if (!this.picker) { return; } - - if (!this.picker.loaded) { - this.picker.on("categoriesLoaded", function () { this._setValue(val); }, this); - } else { - this._setValue(val); - } - }, - _setValue: function (val) { - var r = this.findById(val); - - /* We have found a record. Apply it */ - if (r !== null) { - this.setSelectedValue(r.get("id")); - this.setValue(r.get("name"), true); - - if (this.picker.getView().rendered) { - this._selectRecords(r); - } else { - this.picker.getView().on("render", function () { this._selectRecords(r); }, this); - } - - } - }, - _selectRecords: function (r) { - this.picker.getView().select(r); - this.picker.getView().ensureVisible(r); - this.picker.getView().scrollIntoView(r); - }, - findById: function (id) { - return this.picker.getRootNode().findChild("id", id, true); - }, - alignPicker: function() { - // override the original method because otherwise the height of the treepanel would be always 0 - var me = this, - picker, isAbove, - aboveSfx = '-above'; - - if (this.isExpanded) { - picker = me.getPicker(); - if (me.matchFieldWidth) { - // Auto the height (it will be constrained by min and max width) unless there are no records to display. - picker.setWidth( me.bodyEl.getWidth()); - } - if (picker.isFloating()) { - picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset); - - // add the {openCls}-above class if the picker was aligned above - // the field due to hitting the bottom of the viewport - isAbove = picker.el.getY() < me.inputEl.getY(); - me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx); - picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx); - } - } - }, - getErrors: function(value) { - if (this.getValue() === null) { - return [ i18n("You need to select a category")]; - } - - return []; - } + extend:"Ext.ux.TreePicker", + alias: 'widget.CategoryComboBox' }); \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/store/PartCategoryStore.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/store/PartCategoryStore.js @@ -0,0 +1,60 @@ +Ext.define('PartKeepr.data.store.PartCategoryStore', { + extend: 'Ext.data.TreeStore', + + /** + * The store ID to use + */ + storeId: 'PartCategoryStore', + + /** + * Don't sort remotely as this is a tree store + */ + remoteSort: false, + + /** + * Sort folders alphabetically + */ + folderSort: true, + + /** + * Hide the root node by default + */ + rootVisible: false, + + /** + * Automatically load the store + */ + autoLoad: true, + + /** + * Sort by name ascending by default + */ + sorters: [ + { + property: 'name', + direction: 'ASC' + } + ], + + /** + * Virtual Root Node + */ + root: { + "@id": "@local-tree-root" + }, + + /** + * The model to use + */ + model: "PartKeepr.PartBundle.Entity.PartCategory", + + proxy: { + ignoreLoadId: '@local-tree-root', + url: "/api/part_categories/getExtJSRootNode", + type: "Hydra", + appendId: false, + reader: { + type: 'json' + } + } +});+ \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -14,9 +14,8 @@ <!-- Include the ExtJS CSS Theme --> {% stylesheets filter='cssrewrite' - 'js/packages/extjs6/build/classic/theme-crisp/resources/theme-crisp-all_1.css' - 'js/packages/extjs6/build/classic/theme-crisp/resources/theme-crisp-all_2.css' - 'js/packages/extjs6/build/packages/ux/classic/crisp/resources/ux-all.css' + 'js/packages/extjs6/build/classic/theme-classic/resources/theme-classic-all.css' + 'js/packages/extjs6/build/packages/ux/classic/classic/resources/ux-all.css' 'js/packages/extjs6/build/packages/charts/classic/neptune/resources/charts-all.css' 'bundles/partkeeprfrontend/css/PartKeepr.css' %} @@ -39,7 +38,7 @@ output='js/compiled/main.js' 'js/packages/extjs6/build/ext-all-debug.js' 'js/packages/extjs6/build/packages/charts/classic/charts.js' - 'js/packages/extjs6/build/packages/ux/classic/ux.js' + 'js/packages/extjs6/build/packages/ux/classic/ux-debug.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/field/Array.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/Crypto/isaac.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/Crypto/bcrypt.js' @@ -68,6 +67,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraModel.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraTreeModel.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraReader.js' + '@PartKeeprFrontendBundle/Resources/public/js/Data/store/PartCategoryStore.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.tree.View-missingMethods.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.form.Basic-AssociationSupport.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statusbar.js'