partkeepr

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

commit d00328da48054f8e2b76eda6004c91791f22af59
parent 4e4f873de2563261118afe761e2336d7d8a172f1
Author: Felicia Hummel <felicia@drachenkatze.org>
Date:   Wed, 24 May 2017 20:25:32 +0200

Merge pull request #857 from partkeepr/PartKeepr-223

Part keepr 223
Diffstat:
Mapp/config/config_partkeepr.yml | 13+++++++++++++
Asrc/PartKeepr/FrontendBundle/Entity/GridPreset.php | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/BaseGrid.js | 55++++++++++++++++++++++++++++++++-----------------------
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/GridPresetButton.js | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/AbstractRenderer.js | 34++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/CurrencyRenderer.js | 15+++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/IconRenderer.js | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/InternalIDRenderer.js | 19+++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/RendererRegistry.js | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartInfoGrid.js | 8++++----
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsGrid.js | 147++++++++++++++++++-------------------------------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js | 156++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/AttachmentRenderer.js | 23+++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/PartParameterRenderer.js | 37+++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/StockLevelRenderer.js | 23+++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js | 7+++----
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js | 232+++++++++++++++++++++++++++++--------------------------------------------------
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/MetaPartRenderer.js | 29+++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/ProjectPartParameterRenderer.js | 37+++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js | 34++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumProperties.js | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumnListGrid.js | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Panel.js | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RendererConfigurationForm.js | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RenderersGrid.js | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Window.js | 40++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/PagingToolbar.js | 7+++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js | 5+++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Ext.ux/StoreMenu.js | 350+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.Column-multipleRendererSupport.js | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.header.Container-addMoreMenu.js | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.view.Table-renderCell.js | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Models/ColumnConfiguration.js | 21+++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Models/ColumnRendererConfiguration.js | 7+++++++
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 25+++++++++++++++++++++++++
Msrc/PartKeepr/PartBundle/Services/PartService.php | 23++++++++++++++++-------
37 files changed, 2491 insertions(+), 353 deletions(-)

diff --git a/app/config/config_partkeepr.yml b/app/config/config_partkeepr.yml @@ -336,6 +336,19 @@ services: arguments: - { groups: [ "default" ] } + resource.grid_preset: + parent: "api.resource" + arguments: [ "PartKeepr\FrontendBundle\Entity\GridPreset" ] + tags: [ { name: "api.resource" } ] + calls: + - method: "initFilters" + arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] + - method: "initNormalizationContext" + arguments: [ { groups: [ "default" ] } ] + - method: "initDenormalizationContext" + arguments: + - { groups: [ "default" ] } + resource.part.collection_operation.custom_post: class: "Dunglas\ApiBundle\Api\Operation\Operation" public: false diff --git a/src/PartKeepr/FrontendBundle/Entity/GridPreset.php b/src/PartKeepr/FrontendBundle/Entity/GridPreset.php @@ -0,0 +1,96 @@ +<?php + +namespace PartKeepr\FrontendBundle\Entity; + +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\CoreBundle\Entity\BaseEntity; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Stores the grid presets. + * + * @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(name="name_grid_unique", columns={"grid", "name"})}) + * @ORM\Entity() + * @TargetService(uri="/api/grid_presets") + */ +class GridPreset extends BaseEntity +{ + /** + * Holds the grid. + * + * @ORM\Column(length=255) + * @Groups({"default"}) + * + * @var string + */ + private $grid; + + /** + * Holds the grid preset name. + * + * @ORM\Column(length=255) + * @Groups({"default"}) + * + * @var string + */ + private $name; + + /** + * Defines the preset configuration. + * + * @ORM\Column(type="text") + * @Groups({"default"}) + * + * @var string + */ + private $configuration; + + /** + * @return string + */ + public function getGrid() + { + return $this->grid; + } + + /** + * @param string $grid + */ + public function setGrid($grid) + { + $this->grid = $grid; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getConfiguration() + { + return $this->configuration; + } + + /** + * @param string $configuration + */ + public function setConfiguration($configuration) + { + $this->configuration = $configuration; + } +} diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/BaseGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/BaseGrid.js @@ -1,26 +1,36 @@ /** - * Defines an abstract grid which includes the grid menu plugin. - * + * Defines an abstract grid which includes the grid menu plugin. + * */ Ext.define('PartKeepr.BaseGrid', { - extend: 'Ext.grid.Panel', - alias: 'widget.BaseGrid', + extend: 'Ext.grid.Panel', + alias: 'widget.BaseGrid', - /** - * Initializes the component - */ - initComponent: function () { - - /** - * Check if the plugins already exist (e.g. by a superclass). If yes, assume it is an array, and append - * the plugin to it. - */ - if (this.plugins) { - this.plugins.push('gridmenu'); - } else { - this.plugins = [ 'gridmenu' ]; - } - - this.callParent(); - } -});- \ No newline at end of file + renderers: [], + + /** + * Initializes the component + */ + initComponent: function () + { + + /** + * Check if the plugins already exist (e.g. by a superclass). If yes, assume it is an array, and append + * the plugin to it. + */ + if (this.plugins) + { + this.plugins.push('gridmenu'); + } else + { + this.plugins = ['gridmenu']; + } + + this.defaultColumnConfiguration = this.columns; + + this.callParent(); + }, + getDefaultColumnConfiguration: function () { + return this.defaultColumnConfiguration; + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/GridPresetButton.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/GridPresetButton.js @@ -0,0 +1,63 @@ +Ext.define("PartKeepr.Components.Grid.GridPresetButton", { + extend: "Ext.button.Button", + + iconCls: 'fugue-icon folder-open-table', + tooltip: i18n("Choose preset…"), + overflowText: i18n("Choose preset…"), + + grid: null, + + initComponent: function () + { + this.menu = Ext.create("Ext.ux.menu.StoreMenu", { + model: "PartKeepr.FrontendBundle.Entity.GridPreset", + nameField: 'name', + items: [{ + text: i18n("Default"), + iconCls: "fugue-icon inbox-table", + default: true + }], + offset: 2, + listeners: { + click: this.onPresetSelect, + scope: this + } + }); + this.callParent(arguments); + + if (this.grid !== null) { + this.setGrid(this.grid); + } + }, + setGrid: function (grid) + { + this.menu.store.setFilters(); + this.menu.store.addFilter({ + property: "grid", + operator: "=", + value: grid.$className + }); + + this.grid = grid; + }, + onPresetSelect: function (menu, item) + { + var config; + + if (item.default) + { + config = this.grid.getDefaultColumnConfiguration(); + this.grid.reconfigure(this.grid.store, config); + return; + } + var matchedIndex = this.menu.store.findExact("name", item.text); + + if (matchedIndex !== -1) + { + var record = this.menu.store.getAt(matchedIndex); + config = Ext.decode(record.get("configuration")); + + this.grid.reconfigure(this.grid.store, config); + } + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/AbstractRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/AbstractRenderer.js @@ -0,0 +1,34 @@ +Ext.define("PartKeepr.Components.Grid.Renderers.AbstractRenderer", { + name: null, + description: null, + config: { + rendererConfig: {} + }, + constructor: function (config) + { + this.initConfig(config); + + return this; + }, + getRendererConfigItem: function (renderObj, item, defaultValue) + { + var config = renderObj.getRendererConfig(); + + if (typeof(config) !== "object") + { + return defaultValue; + } + + if (config === null) + { + return defaultValue; + } + + if (config[item] === undefined) + { + return defaultValue; + } + + return config[item]; + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/CurrencyRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/CurrencyRenderer.js @@ -0,0 +1,15 @@ +Ext.define("PartKeepr.Components.Grid.Renderers.CurrencyRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.currency', + + renderer: function (value) + { + return PartKeepr.getApplication().formatCurrency(value); + }, + + statics: { + rendererName: i18n("Currency Renderer"), + rendererDescription: i18n("Renders a value with the system defined currency") + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/IconRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/IconRenderer.js @@ -0,0 +1,51 @@ +Ext.define("PartKeepr.Components.Grid.Renderers.IconRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.icon', + + config: { + rendererConfig: { + iconCls: '', + invert: false, + title: '' + } + }, + + + renderer: function (value, metaData, record, rowIndex, colIndex, store, view, renderObj) + { + var invert = renderObj.getRendererConfigItem(renderObj, "invert", false), + title = renderObj.getRendererConfigItem(renderObj, "title", ""), + iconCls = renderObj.getRendererConfigItem(renderObj, "iconCls", "web-icon ,fugue-icon fruit"); + + if (value ||invert) + { + return '<span class="' + iconCls + '" title="' + title + '"/>'; + } + + return ""; + }, + + statics: { + rendererName: i18n("Icon Renderer"), + rendererDescription: i18n("Renders an icon"), + rendererConfigs: { + iconCls: { + type: 'string', + description: i18n("The icon CSS class to render"), + title: i18n("CSS Class") + + }, + invert: { + type: 'boolean', + description: i18n("Render the icon if the column is zero"), + title: i18n("Invert") + }, + title: { + type: 'string', + title: i18n("Hover Title"), + description: i18n("The title to display when the mouse is hovered above the icon") + } + } + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/InternalIDRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/InternalIDRenderer.js @@ -0,0 +1,19 @@ +Ext.define("PartKeepr.Components.Grid.Renderers.InternalIDRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.internalID', + + renderer: function (value) + { + var values = value.split("/"); + var idstr = values[values.length - 1]; + var idint = parseInt(idstr); + + return idstr + " (#" + idint.toString(36) + ")"; + }, + + statics: { + rendererName: i18n("ID Renderer"), + rendererDescription: i18n("Renders an ID in both base36 as well as integer format") + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/RendererRegistry.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Grid/Renderers/RendererRegistry.js @@ -0,0 +1,44 @@ +Ext.define("PartKeepr.Components.Grid.Renderers.RendererRegistry", { + statics: { + aliasPrefix: "columnRenderer.", + + lookupRenderer: function (rtype) + { + return Ext.ClassManager.getByAlias(this.aliasPrefix + rtype); + }, + getRenderersForEntity: function (targetEntity) + { + var renderers = Ext.ClassManager.getNamesByExpression(this.aliasPrefix + "*"); + var finalRenderers = [], renderer; + + for (var i = 0; i < renderers.length; i++) + { + renderer = Ext.ClassManager.get(renderers[i]); + + if (renderer.restrictToEntity && renderer.restrictToEntity instanceof Array) + { + if (renderer.restrictToEntity.indexOf(targetEntity) !== -1) + { + finalRenderers.push(renderers[i]); + } + } else + { + finalRenderers.push(renderers[i]); + } + } + + return finalRenderers; + }, + getRType: function (className) { + var aliases = Ext.ClassManager.getAliasesByName(className); + + for (var i=0;i<aliases.length;i++) { + if (Ext.String.startsWith(aliases[i], this.aliasPrefix)) { + return aliases[i].substr(this.aliasPrefix.length); + } + } + + return ""; + } + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartInfoGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartInfoGrid.js @@ -24,7 +24,7 @@ Ext.define("PartKeepr.Components.Part.PartInfoGrid", { }, createDate: { displayName: i18n("Create Date"), - type: 'date', + type: 'date' }, status: { displayName: i18n("Status") @@ -37,7 +37,7 @@ Ext.define("PartKeepr.Components.Part.PartInfoGrid", { type: 'boolean' }, internalPartNumber: { - displayName: i18n("Internal Part Number"), + displayName: i18n("Internal Part Number") }, projectNames: { displayName: i18n("Used in Projects") @@ -77,8 +77,8 @@ Ext.define("PartKeepr.Components.Part.PartInfoGrid", { displayName: i18n("Comment") }, internalPartNumber: { - displayName: i18n("Internal Part Number"), - }, + displayName: i18n("Internal Part Number") + } }, listeners: { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsGrid.js @@ -176,7 +176,6 @@ Ext.define('PartKeepr.PartsGrid', { this.topToolbar.insert(1, this.createMetaPartButton); - this.mapSearchHotkey(); }, /** @@ -251,67 +250,76 @@ Ext.define('PartKeepr.PartsGrid', { dataIndex: "", width: 30, tooltip: i18n("Has attachments?"), - renderer: this.iconRenderer + renderers: [{ + rtype: 'partAttachment' + }] }, { text: '<span class="web-icon flag_orange"></span>', dataIndex: "needsReview", width: 30, tooltip: i18n("Needs Review?"), - renderer: this.reviewRenderer + renderers: [{ + rtype: 'icon', + rendererConfig: { + iconCls: 'web-icon flag_orange' + } + }] }, { text: '<span class="web-icon bricks"></span>', dataIndex: "metaPart", width: 30, tooltip: i18n("Meta Part"), - renderer: this.metaPartRenderer + renderers: [{ + rtype: 'icon', + rendererConfig: { + iconCls: 'web-icon bricks' + } + }] }, { header: i18n("Name"), dataIndex: 'name', flex: 1, - minWidth: 150, - renderer: Ext.util.Format.htmlEncode + minWidth: 150 }, { header: i18n("Description"), dataIndex: 'description', flex: 2, - minWidth: 150, - renderer: Ext.util.Format.htmlEncode + minWidth: 150 }, { header: i18n("Storage Location"), - dataIndex: 'storageLocation.name', - renderer: this.storageLocationRenderer + dataIndex: 'storageLocation.name' }, { header: i18n("Status"), - dataIndex: "status", - renderer: Ext.util.Format.htmlEncode - }, { + dataIndex: "status"}, + { header: i18n("Condition"), - dataIndex: "partCondition", - renderer: Ext.util.Format.htmlEncode + dataIndex: "partCondition" }, { header: i18n("Stock"), dataIndex: 'stockLevel', editor: { xtype: 'textfield', allowBlank: false - }, - renderer: this.stockLevelRenderer + } }, { header: i18n("Min. Stock"), dataIndex: 'minStockLevel', - renderer: this.stockLevelRenderer + renderers: [{ + rtype: "stockLevel" + }] }, { header: i18n("Avg. Price"), dataIndex: 'averagePrice', align: 'right', - renderer: this.averagePriceRenderer + renderers: [{ + rtype: "currency" + }] }, { header: i18n("Footprint"), - dataIndex: 'footprint.name', - renderer: this.footprintRenderer + dataIndex: 'footprint.name' }, { header: i18n("Category"), - renderer: this.categoryPathRenderer, + dataIndex: "category.categoryPath", hidden: true }, { header: i18n("Create Date"), @@ -320,102 +328,13 @@ Ext.define('PartKeepr.PartsGrid', { }, { header: i18n("Internal ID"), dataIndex: '@id', - renderer: function (value) - { - var values = value.split("/"); - var idstr = values[values.length - 1]; - var idint = parseInt(idstr); - - return idstr + " (#" + idint.toString(36) + ")"; - - } + renderers: [{ + rtype: "internalID" + }] } ]; }, - averagePriceRenderer: function (val) - { - return PartKeepr.getApplication().formatCurrency(val); - }, - /** - * Renders the storage location - */ - storageLocationRenderer: function (val, q, rec) - { - if (rec.getStorageLocation() !== null) { - return rec.getStorageLocation().get("name"); - } - }, - /** - * Renders the storage location - */ - categoryPathRenderer: function (val, q, rec) - { - if (rec.getCategory() !== null) { - return rec.getCategory().get("categoryPath"); - } - }, - /** - * Renders the storage location - */ - footprintRenderer: function (val, q, rec) - { - if (rec.getFootprint()) { - return rec.getFootprint().get("name"); - } - }, - /** - * Used as renderer for the stock level columns. - * - * If a part contains a non-default unit, we display it. - * Otherwise we hide it. - */ - stockLevelRenderer: function (val, q, rec) - { - if (rec.getPartUnit()) { - return val + " " + rec.getPartUnit().get("shortName"); - } else { - return val; - } - }, - /** - * Used as renderer for the icon column. - */ - iconRenderer: function (val, q, rec) - { - var ret = ""; - if (rec.attachments().getCount() > 0) { - ret += '<span class="web-icon fugue-icon paper-clip" title="' + i18n("Has attachments") + '"/>'; - } - - return ret; - }, - /** - * Used as renderer for the review column. - */ - reviewRenderer: function (val, q, rec) - { - var ret = ""; - - if (rec.get("needsReview") === true) { - ret += '<span class="web-icon flag_orange" title="' + i18n("Needs review") + '"></span>'; - } - - return ret; - }, - /** - * Used as renderer for the meta part column. - */ - metaPartRenderer: function (val, q, rec) - { - var ret = ""; - - if (rec.get("metaPart") === true) { - ret += '<span class="web-icon bricks" title="' + i18n("Meta Part") + '"></span>'; - } - - return ret; - }, /** * Sets the category. Triggers a store reload with a category filter. */ diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js @@ -51,9 +51,11 @@ Ext.define('PartKeepr.PartManager', { ddGroup: 'CategoryTree' }; - if (this.compactLayout) { + if (this.compactLayout) + { treeConfig.region = 'center'; - } else { + } else + { treeConfig.floatable = false; treeConfig.split = true; treeConfig.width = 300; // @todo Make this configurable @@ -72,10 +74,15 @@ Ext.define('PartKeepr.PartManager', { this.detail.on("editPart", this.onEditPart, this); var gridConfig = { - title: i18n("Parts List"), region: 'center', layout: 'fit', store: this.getStore() + title: i18n("Parts List"), + region: 'center', + layout: 'fit', + store: this.getStore(), + itemId: "partsGrid" }; - if (this.dragAndDrop) { + if (this.dragAndDrop) + { gridConfig.viewConfig = { plugins: { ddGroup: 'PartTree', @@ -116,10 +123,12 @@ Ext.define('PartKeepr.PartManager', { items: [this.detail, this.stockLevel] }; - if (this.compactLayout) { + if (this.compactLayout) + { detailPanelConfig.height = 300; detailPanelConfig.region = 'south'; - } else { + } else + { detailPanelConfig.width = 300; } @@ -157,7 +166,8 @@ Ext.define('PartKeepr.PartManager', { { var parts = []; - for (var i = 0; i < selection.length; i++) { + for (var i = 0; i < selection.length; i++) + { parts.push(selection[i].get("part")); } @@ -166,7 +176,8 @@ Ext.define('PartKeepr.PartManager', { this.grid.store.on("update", function (store, record) { - if (this.detail.record !== null && this.detail.record.getId() == record.getId()) { + if (this.detail.record !== null && this.detail.record.getId() == record.getId()) + { this.detail.setValues(record); } }, this); @@ -179,12 +190,15 @@ Ext.define('PartKeepr.PartManager', { i, j, attachments, attachment; - for (i = 0; i < data.getCount(); i++) { + for (i = 0; i < data.getCount(); i++) + { attachments = data.getAt(i).attachments().getData(); - for (j = 0; j < attachments.getCount(); j++) { + for (j = 0; j < attachments.getCount(); j++) + { attachment = attachments.getAt(j); - if (attachment.get("isImage")) { + if (attachment.get("isImage")) + { this.thumbnailView.getStore().add({ "@id": attachment.get("@id"), "part": data.getAt(i) @@ -234,7 +248,8 @@ Ext.define('PartKeepr.PartManager', { this.thumbnailViewToolbar.onLoad(); }, this); - if (this.compactLayout) { + if (this.compactLayout) + { // Create two border layouts: One for the center panel and one for the left panel. Each border layout // has two columns each, containing Categories+Part Details and Part List+Part Filter Panel. this.items = [ @@ -257,7 +272,8 @@ Ext.define('PartKeepr.PartManager', { items: [this.tabPanel, this.filterPanel] } ]; - } else { + } else + { // The regular 3-column layout. The tree, then the part list+part filter, then the part details. this.items = [ this.tree, { @@ -288,12 +304,14 @@ Ext.define('PartKeepr.PartManager', { value: this.getChildrenIds(record) }); - if (record.parentNode.isRoot()) { + if (record.parentNode.isRoot()) + { // Workaround for big installations: Passing all child categories for the root node // to the filter exceeds the HTTP URI length. See // https://github.com/partkeepr/PartKeepr/issues/473 this.store.removeFilter(filter); - } else { + } else + { this.store.addFilter(filter); } }, @@ -311,8 +329,10 @@ Ext.define('PartKeepr.PartManager', { { var childNodes = [node]; - if (node.hasChildNodes()) { - for (var i = 0; i < node.childNodes.length; i++) { + if (node.hasChildNodes()) + { + for (var i = 0; i < node.childNodes.length; i++) + { childNodes = childNodes.concat(this.getChildrenIds(node.childNodes[i])); } } @@ -328,7 +348,8 @@ Ext.define('PartKeepr.PartManager', { { var r = this.grid.getSelectionModel().getSelection(); - if (r.length != 1) { + if (r.length != 1) + { return; } @@ -337,7 +358,8 @@ Ext.define('PartKeepr.PartManager', { var node = rootNode.findChild("@id", cat, true); - if (node) { + if (node) + { this.tree.getView().ensureVisible(node); this.tree.getView().focusNode(node); } @@ -400,11 +422,10 @@ Ext.define('PartKeepr.PartManager', { j.editor.editItem(newItem); j.show(); }, - onAddMetaPart: function () { - var defaults; - var j = Ext.create("PartKeepr.Components.Part.Editor.MetaPartEditorWindow", { - - }); + onAddMetaPart: function () + { + var defaults = {}; + var j = Ext.create("PartKeepr.Components.Part.Editor.MetaPartEditorWindow", {}); var defaultPartUnit = PartKeepr.getApplication().getPartUnitStore().findRecord("default", true); @@ -414,9 +435,11 @@ Ext.define('PartKeepr.PartManager', { metaPart: true }); - if (this.getSelectedCategory() !== null) { + if (this.getSelectedCategory() !== null) + { record.setCategory(this.getSelectedCategory()); - } else { + } else + { record.setCategory(this.tree.getRootNode().firstChild); } @@ -457,7 +480,8 @@ Ext.define('PartKeepr.PartManager', { { var r = this.grid.getSelectionModel().getLastSelected(); - if (btn == "yes") { + if (btn == "yes") + { this.detailPanel.collapse(); this.detail.clear(); r.erase(); @@ -478,9 +502,11 @@ Ext.define('PartKeepr.PartManager', { var record = Ext.create("PartKeepr.PartBundle.Entity.Part", defaults); - if (this.getSelectedCategory() !== null) { + if (this.getSelectedCategory() !== null) + { record.setCategory(this.getSelectedCategory()); - } else { + } else + { record.setCategory(this.tree.getRootNode().firstChild); } @@ -499,9 +525,11 @@ Ext.define('PartKeepr.PartManager', { { var editorWindow; - if (part.get("metaPart") === true) { + if (part.get("metaPart") === true) + { editorWindow = Ext.create("PartKeepr.Components.Part.Editor.MetaPartEditorWindow"); - } else { + } else + { editorWindow = Ext.create("PartKeepr.PartEditorWindow"); } @@ -522,11 +550,14 @@ Ext.define('PartKeepr.PartManager', { */ onItemSelect: function () { - if (this.grid.getSelection().length > 1) { + if (this.grid.getSelection().length > 1) + { this.detailPanel.collapse(); this.tree.syncButton.disable(); - } else { - if (this.grid.getSelection().length == 1) { + } else + { + if (this.grid.getSelection().length == 1) + { var selection = this.grid.getSelection(); var r = selection[0]; @@ -537,7 +568,8 @@ Ext.define('PartKeepr.PartManager', { this.stockLevel.part = r.getId(); this.tree.syncButton.enable(); - } else { + } else + { this.tree.syncButton.disable(); } } @@ -576,10 +608,12 @@ Ext.define('PartKeepr.PartManager', { this.store.on('write', function (store, operation) { var success = operation.wasSuccessful(); - if (success) { + if (success) + { Ext.each(operation.records, function (record) { - if (record.dirty) { + if (record.dirty) + { record.commit(); } }); @@ -599,57 +633,73 @@ Ext.define('PartKeepr.PartManager', { var minSiPrefix = "", siPrefix = "", maxSiPrefix = "", unit = "", minValue = "", maxValue = "", value = "", minMaxCombined = ""; - if (partParameter.get("valueType") === "string") { + if (partParameter.get("valueType") === "string") + { return partParameter.get("stringValue"); } - if (partParameter.getUnit() instanceof PartKeepr.UnitBundle.Entity.Unit) { + if (partParameter.getUnit() instanceof PartKeepr.UnitBundle.Entity.Unit) + { unit = partParameter.getUnit().get("symbol"); } - if (partParameter.getMinSiPrefix() instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) { + if (partParameter.getMinSiPrefix() instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) + { minSiPrefix = partParameter.getMinSiPrefix().get("symbol"); } - if (partParameter.getSiPrefix() instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) { + if (partParameter.getSiPrefix() instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) + { siPrefix = partParameter.getSiPrefix().get("symbol"); } - if (partParameter.getMaxSiPrefix() instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) { + if (partParameter.getMaxSiPrefix() instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) + { maxSiPrefix = partParameter.getMaxSiPrefix().get("symbol"); } - if (partParameter.get("value") !== null && partParameter.get("value") !== "") { + if (partParameter.get("value") !== null && partParameter.get("value") !== "") + { value = partParameter.get("value"); } - if (partParameter.get("minValue") !== null && partParameter.get("minValue") !== "") { + if (partParameter.get("minValue") !== null && partParameter.get("minValue") !== "") + { minValue = partParameter.get("minValue"); } - if (partParameter.get("maxValue") !== null && partParameter.get("maxValue") !== "") { + if (partParameter.get("maxValue") !== null && partParameter.get("maxValue") !== "") + { maxValue = partParameter.get("maxValue"); } - if (minValue !== "" && maxValue !== "") { + if (minValue !== "" && maxValue !== "") + { minMaxCombined = minValue + minSiPrefix + "…" + maxValue + maxSiPrefix + unit; - } else { - if (minValue !== "") { + } else + { + if (minValue !== "") + { minMaxCombined = i18n("Min.") + minValue + minSiPrefix + unit; } - if (maxValue !== "") { + if (maxValue !== "") + { minMaxCombined = i18n("Max.") + maxValue + maxSiPrefix + unit; } } - if (value !== "") { - if (minMaxCombined !== "") { + if (value !== "") + { + if (minMaxCombined !== "") + { return value + siPrefix + unit + " (" + minMaxCombined + ")"; - } else { + } else + { return value + siPrefix + unit; } - } else { + } else + { return minMaxCombined; } } diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/AttachmentRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/AttachmentRenderer.js @@ -0,0 +1,23 @@ +Ext.define("PartKeepr.Components.Part.Renderers.AttachmentRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.partAttachment', + + renderer: function (val, q, rec) + { + var ret = ""; + if (rec.attachments().getCount() > 0) + { + ret += '<span class="web-icon fugue-icon paper-clip" title="' + i18n("Has attachments") + '"/>'; + } + + return ret; + }, + + statics: { + rendererName: i18n("Attachment Renderer"), + rendererDescription: i18n("Renders an attachment icon if one or more attachments exist"), + + restrictToEntity: ["PartKeepr.PartBundle.Entity.Part"] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/PartParameterRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/PartParameterRenderer.js @@ -0,0 +1,37 @@ +Ext.define("PartKeepr.Components.Part.Renderers.PartParameterRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.partParameter', + + renderer: function (value, metaData, record, rowIndex, colIndex, store, view, renderObj) + { + var i; + var partParameterName = renderObj.getRendererConfigItem(renderObj, "parameterName", false); + + for (i = 0; i < renderObj.getPartParameters(record).getCount(); i++) + { + if (renderObj.getPartParameters(record).getAt(i).get("name") === partParameterName) + { + return PartKeepr.PartManager.formatParameter( + renderObj.getPartParameters(record).getAt(i)); + } + } + + return ""; + }, + getPartParameters: function (record) { + return record.parameters(); + }, + + statics: { + rendererName: i18n("Part Parameter Renderer"), + rendererDescription: i18n("Renders a specific part parameter"), + rendererConfigs: { + parameterName: { + type: 'partParameter', + title: i18n("Part Parameter Name") + } + }, + restrictToEntity: ["PartKeepr.PartBundle.Entity.Part"] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/StockLevelRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/Renderers/StockLevelRenderer.js @@ -0,0 +1,23 @@ +Ext.define("PartKeepr.Components.Part.Renderers.StockLevelRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.stockLevel', + + renderer: function (val, q, rec) + { + if (rec.getPartUnit()) + { + return val + " " + rec.getPartUnit().get("shortName"); + } else + { + return val; + } + }, + + statics: { + rendererName: i18n("Stock Level Renderer"), + rendererDescription: i18n("Renders the stock level including the part unit"), + + restrictToEntity: ["PartKeepr.PartBundle.Entity.Part"] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js @@ -29,7 +29,7 @@ Ext.define('PartKeepr.ProjectPartGrid', { queryMode: 'local', editable: false, forceSelection: true, - allowBlank: false, + allowBlank: false }, renderer: function (v) { if (v === "percent") { @@ -98,8 +98,7 @@ Ext.define('PartKeepr.ProjectPartGrid', { { this.editing = Ext.create('Ext.grid.plugin.CellEditing', { - clicksToEdit: 1, - + clicksToEdit: 1 }); this.plugins = [this.editing]; @@ -156,7 +155,7 @@ Ext.define('PartKeepr.ProjectPartGrid', { tooltip: i18n("Import"), iconCls: "fugue-icon database-import", disabled: this.store.isLoading() - }), + }) ]; diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js @@ -104,7 +104,7 @@ Ext.define('PartKeepr.ProjectReportView', { }, { text: i18n("Production Remarks"), dataIndex: "productionRemarks" - },{ + }, { text: i18n("Storage Location"), renderer: function (v, m, r) { @@ -135,7 +135,9 @@ Ext.define('PartKeepr.ProjectReportView', { ] }; - this.reportResult = Ext.create("PartKeepr.BaseGrid", { + var gridPresetButton = Ext.create("PartKeepr.Components.Grid.GridPresetButton"); + + this.reportResult = Ext.create("PartKeepr.Components.Project.ProjectReportResultGrid", { flex: 1, features: [ { @@ -150,116 +152,7 @@ Ext.define('PartKeepr.ProjectReportView', { }, this.editing ], - columns: [ - { - header: i18n("Quantity"), dataIndex: 'quantity', - width: 50, - renderer: function (v, s, rec) - { - var i, total; - - if (rec.get("metaPart")) { - total = 0; - for (i = 0; i < rec.subParts().getCount(); i++) { - if (rec.subParts().getAt(i).get("use")) { - total += rec.subParts().getAt(i).get("stockToUse"); - } - } - - return total + " / " + v; - } else { - return v; - } - } - }, { - header: i18n("Part Name"), - renderer: function (val, p, rec) - { - var part = rec.getPart(), icon; - - if (part !== null) { - if (part.get("metaPart")) { - icon = "bricks"; - } else { - icon = "brick"; - } - return '<span class="web-icon ' + icon + '"></span> ' + Ext.util.Format.htmlEncode( - part.get("name")); - } - }, - flex: 1 - }, { - header: i18n("Part Description"), - renderer: function (val, p, rec) - { - return rec.getPart().get("description"); - }, - flex: 1 - }, { - header: i18n("Remarks"), - dataIndex: 'remarks', - flex: 1 - }, { - header: i18n("Production Remarks"), - dataIndex: 'productionRemarks', - flex: 1 - },{ - header: i18n("Projects"), - dataIndex: 'projectNames', - flex: 1 - }, { - header: i18n("Storage Location"), dataIndex: 'storageLocation_name', - width: 100 - }, { - header: i18n("Available"), dataIndex: 'available', - width: 75 - }, { - header: i18n("Distributor"), - dataIndex: 'distributor', - renderer: function (val, p, rec) - { - if (rec.getDistributor() !== null) { - return rec.getDistributor().get("name"); - } - }, - flex: 1, - editor: { - xtype: 'DistributorComboBox', - returnObject: true, - triggerAction: 'query', - ignoreQuery: true, - forceSelection: true, - editable: false - } - }, { - header: i18n("Distributor Order Number"), dataIndex: 'distributor_order_number', - flex: 1, - editor: { - xtype: 'textfield' - } - }, { - header: i18n("Price per Item"), dataIndex: 'price', - renderer: PartKeepr.getApplication().formatCurrency, - width: 100 - }, { - header: i18n("Sum"), - dataIndex: 'sum', - renderer: PartKeepr.getApplication().formatCurrency, - summaryType: 'sum', - summaryRenderer: PartKeepr.getApplication().formatCurrency, - width: 100 - }, { - header: i18n("Amount to Order"), dataIndex: 'missing', - width: 100 - }, { - header: i18n("Sum (Order)"), - dataIndex: 'sum_order', - renderer: PartKeepr.getApplication().formatCurrency, - summaryType: 'sum', - summaryRenderer: PartKeepr.getApplication().formatCurrency, - width: 100 - } - ], + store: this.projectReportStore, bbar: [ Ext.create("PartKeepr.Exporter.GridExporterButton", { @@ -268,11 +161,18 @@ Ext.define('PartKeepr.ProjectReportView', { tooltip: i18n("Export"), iconCls: "fugue-icon application-export", disabled: this.store.isLoading() - }) + }), + gridPresetButton, + { + xtype: 'button', + listeners: { + click: this.foo, + scope: this + } + } ] } - ) - ; + ); this.createReportButton = Ext.create('Ext.button.Button', { xtype: 'button', @@ -353,19 +253,29 @@ Ext.define('PartKeepr.ProjectReportView', { this.callParent(); + + gridPresetButton.setGrid(this.reportResult); + }, + foo: function () { + this.reportResult.getView().refresh(); }, - onApplyMetaPartsClick: function (button) { + onApplyMetaPartsClick: function (button) + { var parentRecord = button.up("grid").parentRecord; this.convertMetaPartsToParts(parentRecord); }, - convertMetaPartsToParts: function (record) { + convertMetaPartsToParts: function (record) + { + console.log("convertMetaPartsToParts"); var i, projectReportItem, subPart; - for (i=0;i<record.subParts().getCount();i++) { + for (i = 0; i < record.subParts().getCount(); i++) + { subPart = record.subParts().getAt(i); - if (subPart.get("use")) { + if (subPart.get("use")) + { projectReportItem = Ext.create("PartKeepr.ProjectBundle.Entity.ProjectReport"); projectReportItem.set("quantity", subPart.get("stockToUse")); projectReportItem.set("storageLocation_name", subPart.getStorageLocation().get("name")); @@ -382,44 +292,54 @@ Ext.define('PartKeepr.ProjectReportView', { } this.reportResult.getStore().remove(record); - this.reportResult.getView().refresh(); + //this.reportResult.getView().refresh(); }, onCheckStateChange: function (check, rowIndex, checked, record) { - if (checked) { - if (record.get("stockToUse") == 0 || record.get("stockToUse") === undefined) { + console.log("onCheckStateChange"); + + if (checked) + { + if (record.get("stockToUse") == 0 || record.get("stockToUse") === undefined) + { record.set("stockToUse", record.get("stockLevel")); } } Ext.defer(this.updateSubGrid, 100, this, [check.up("grid")]); - this.reportResult.getView().refresh(); }, onAfterSubGridEdit: function (editor, context) { - if (context.value > context.record.get("stockLevel")) { + console.log("onAfterSubGridEdit"); + if (context.value > context.record.get("stockLevel")) + { context.record.set("stockToUse", context.originalValue); - } else { + } else + { context.record.set("stockToUse", context.value); } Ext.defer(this.updateSubGrid, 100, this, [context.grid]); - this.reportResult.getView().refresh(); }, - updateSubGrid: function (grid) { + updateSubGrid: function (grid) + { var subParts = grid.parentRecord.subParts(); var i, total; total = 0; - for (i = 0; i < subParts.getCount(); i++) { - if (subParts.getAt(i).get("use")) { + for (i = 0; i < subParts.getCount(); i++) + { + if (subParts.getAt(i).get("use")) + { total += subParts.getAt(i).get("stockToUse"); } } - if (total >= grid.parentRecord.get("quantity")) { + if (total >= grid.parentRecord.get("quantity")) + { grid.down("#applyPartsButton").enable(); - } else { + } else + { grid.down("#applyPartsButton").disable(); } }, @@ -428,17 +348,20 @@ Ext.define('PartKeepr.ProjectReportView', { * * Filters the distributor list and show only distributors which are assigned to the particular item. * @param e + * @param context */ onBeforeEdit: function (e, context) { - if (context.field !== "distributor") { + if (context.field !== "distributor") + { return; } var distributors = context.record.getPart().distributors(); var filterIds = []; - for (var i = 0; i < distributors.count(); i++) { + for (var i = 0; i < distributors.count(); i++) + { filterIds.push(distributors.getAt(i).getDistributor().getId()); } @@ -462,16 +385,17 @@ Ext.define('PartKeepr.ProjectReportView', { }, removeStocks: function (btn) { - if (btn == "yes") { + if (btn == "yes") + { var store = this.reportResult.getStore(); var removals = []; - for (var i = 0; i < store.count(); i++) { + for (var i = 0; i < store.count(); i++) + { var item = store.getAt(i); - removals.push({ part: item.getPart().getId(), amount: item.get("quantity"), @@ -488,11 +412,15 @@ Ext.define('PartKeepr.ProjectReportView', { }, onEdit: function (editor, context) { - if (context.field == "distributor" && context.record.getDistributor() !== null) { + console.log("onEdit"); + if (context.field == "distributor" && context.record.getDistributor() !== null) + { var partDistributors = context.record.getPart().distributors(); - for (var i = 0; i < partDistributors.count(); i++) { - if (partDistributors.getAt(i).getDistributor().getId() == context.record.getDistributor().getId()) { + for (var i = 0; i < partDistributors.count(); i++) + { + if (partDistributors.getAt(i).getDistributor().getId() == context.record.getDistributor().getId()) + { context.record.set("price", partDistributors.getAt(i).get("price")); context.record.set("distributor_order_number", partDistributors.getAt(i).get("orderNumber")); context.record.set("sum_order", context.record.get("missing") * context.record.get("price")); @@ -501,7 +429,7 @@ Ext.define('PartKeepr.ProjectReportView', { } } - this.reportResult.getView().refresh(true); + //this.reportResult.getView().refresh(true); }, onAutoFillClick: function () @@ -513,24 +441,30 @@ Ext.define('PartKeepr.ProjectReportView', { var activeRecord; var currentPrice; - for (var i = 0; i < partCount; i++) { + for (var i = 0; i < partCount; i++) + { activeRecord = this.reportResult.store.getAt(i); firstPositive = true; lowestPrice = 0; cheapestDistributor = null; - for (var j = 0; j < activeRecord.getPart().distributors().count(); j++) { + for (var j = 0; j < activeRecord.getPart().distributors().count(); j++) + { activeDistributor = activeRecord.getPart().distributors().getAt(j); currentPrice = parseFloat(activeDistributor.get("price")); - if (currentPrice != 0) { - if (firstPositive) { + if (currentPrice != 0) + { + if (firstPositive) + { lowestPrice = currentPrice; cheapestDistributor = activeDistributor; firstPositive = false; } - else { - if (currentPrice < lowestPrice) { + else + { + if (currentPrice < lowestPrice) + { lowestPrice = currentPrice; cheapestDistributor = activeDistributor; } @@ -538,7 +472,8 @@ Ext.define('PartKeepr.ProjectReportView', { } } - if (cheapestDistributor !== null) { + if (cheapestDistributor !== null) + { activeRecord.setDistributor(cheapestDistributor.getDistributor()); activeRecord.set("distributor_order_number", cheapestDistributor.get("orderNumber")); activeRecord.set("price", cheapestDistributor.get("price")); @@ -558,7 +493,8 @@ Ext.define('PartKeepr.ProjectReportView', { var projects = []; - for (var i = 0; i < selection.length; i++) { + for (var i = 0; i < selection.length; i++) + { projects.push({project: selection[i].getId(), quantity: selection[i].get("quantity")}); } diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js @@ -0,0 +1,92 @@ +Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { + extend: "PartKeepr.BaseGrid", + + + initComponent: function () + { + this.columns = [ + { + header: i18n("Qty"), dataIndex: 'quantity', + width: 50, + renderers: [{ + rtype: "projectReportQuantity" + }] + }, { + header: i18n("Part Name"), + renderers: [{ + rtype: "projectReportMetaPart" + }], + flex: 1 + }, { + header: i18n("Part Description"), + dataIndex: "part.description", + flex: 1 + }, { + header: i18n("Remarks"), + dataIndex: 'remarks', + flex: 1 + }, { + header: i18n("Production Remarks"), + dataIndex: 'productionRemarks', + flex: 1 + }, { + header: i18n("Projects"), + dataIndex: 'projectNames', + flex: 1 + }, { + header: i18n("Storage Location"), dataIndex: 'storageLocation_name', + width: 100 + }, { + header: i18n("Available"), dataIndex: 'available', + width: 75 + }, { + header: i18n("Distributor"), + dataIndex: 'distributor.name', + flex: 1, + editor: { + xtype: 'DistributorComboBox', + returnObject: true, + triggerAction: 'query', + ignoreQuery: true, + forceSelection: true, + editable: false + } + }, { + header: i18n("Distributor Order Number"), dataIndex: 'distributor_order_number', + flex: 1, + editor: { + xtype: 'textfield' + } + }, { + header: i18n("Price per Item"), dataIndex: 'price', + renderers: [{ + rtype: 'currency' + }], + width: 100 + }, { + header: i18n("Sum"), + dataIndex: 'sum', + renderers: [{ + rtype: 'currency' + }], + summaryType: 'sum', + summaryRenderer: PartKeepr.getApplication().formatCurrency, + width: 100 + }, { + header: i18n("Amount to Order"), dataIndex: 'missing', + width: 100 + }, { + header: i18n("Sum (Order)"), + dataIndex: 'sum_order', + renderers: [{ + rtype: 'currency' + }], + summaryType: 'sum', + summaryRenderer: PartKeepr.getApplication().formatCurrency, + width: 100 + } + ]; + + this.callParent(arguments); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/MetaPartRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/MetaPartRenderer.js @@ -0,0 +1,29 @@ +Ext.define("PartKeepr.Components.ProjectReport.Renderers.MetaPartRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.projectReportMetaPart', + + renderer: function (val, q, rec) + { + var part = rec.getPart(), icon; + + if (part !== null) + { + if (part.get("metaPart")) + { + icon = "bricks"; + } else + { + icon = "brick"; + } + return '<span class="web-icon ' + icon + '"></span> ' + Ext.util.Format.htmlEncode( + part.get("name")); + } + }, + statics: { + rendererName: i18n("Project Report MetaPart Renderer"), + rendererDescription: i18n("Renders a specific icon if the part is a meta part"), + + restrictToEntity: ["PartKeepr.PartBundle.Entity.ProjectReport"] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/ProjectPartParameterRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/ProjectPartParameterRenderer.js @@ -0,0 +1,37 @@ +Ext.define("PartKeepr.Components.Project.Renderers.ProjectPartParameterRenderer", { + extend: "PartKeepr.Components.Part.Renderers.PartParameterRenderer", + + alias: 'columnRenderer.projectPartParameter', + + renderer: function (value, metaData, record, rowIndex, colIndex, store, view, renderObj) + { + var i; + var partParameterName = renderObj.getRendererConfigItem(renderObj, "parameterName", false); + + for (i = 0; i < renderObj.getPartParameters(record).getCount(); i++) + { + if (renderObj.getPartParameters(record).getAt(i).get("name") === partParameterName) + { + return PartKeepr.PartManager.formatParameter( + renderObj.getPartParameters(record).getAt(i)); + } + } + + return ""; + }, + getPartParameters: function (record) { + return record.getPart().parameters(); + }, + + statics: { + rendererName: i18n("Part Parameter Renderer"), + rendererDescription: i18n("Renders a specific part parameter"), + rendererConfigs: { + parameterName: { + type: 'partParameter', + title: i18n("Part Parameter Name") + } + }, + restrictToEntity: ["PartKeepr.ProjectBundle.Entity.ProjectReport"] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js @@ -0,0 +1,34 @@ +Ext.define("PartKeepr.Components.ProjectReport.Renderers.QuantityRenderer", { + extend: "PartKeepr.Components.Grid.Renderers.AbstractRenderer", + + alias: 'columnRenderer.projectReportQuantity', + + renderer: function (v, q, rec) + { + var i, total; + + if (rec.get("metaPart")) + { + total = 0; + for (i = 0; i < rec.subParts().getCount(); i++) + { + if (rec.subParts().getAt(i).get("use")) + { + total += rec.subParts().getAt(i).get("stockToUse"); + } + } + + return total + " / " + v; + } else + { + return v; + } + }, + + statics: { + rendererName: i18n("Project Report Quantity Renderer"), + rendererDescription: i18n("Renders the amount of required metadata quantities"), + + restrictToEntity: ["PartKeepr.PartBundle.Entity.ProjectReport"] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumProperties.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumProperties.js @@ -0,0 +1,149 @@ +Ext.define("PartKeepr.Components.Widgets.ColumnConfigurator.ColumnProperties", { + extend: 'Ext.form.Panel', + + scrollable: true, + items: [{ + xtype: 'fieldcontainer', + fieldLabel: i18n("Field"), + layout: 'hbox', + items: [ + { + flex: 1, + xtype: 'textfield', + itemId: "field", + bind: '{column.dataIndex}', + emptyText: i18n("Select a field"), + readOnly: true + }, + { + width: 100, + xtype: 'button', + itemId: "selectField", + text: i18n("Select field") + } + ] + }, { + xtype: 'textfield', + bind: "{column.text}", + fieldLabel: i18n("Title") + }, { + xtype: 'textfield', + bind: "{column.tooltip}", + fieldLabel: i18n("Tooltip") + }, { + xtype: 'fieldcontainer', + fieldLabel: i18n("Width"), + items: [{ + xtype: 'fieldcontainer', + layout: 'hbox', + items: [{ + xtype: 'radio', + name: 'widthMode', + itemId: "widthModeFixed", + boxLabel: i18n("Fixed"), + bind: { + value: "{!isFlex}" + } + }, { + bind: { + value: "{column.width}", + disabled: "{isFlex}" + }, + + xtype: 'numberfield' + }] + }, { + xtype: 'fieldcontainer', + layout: 'hbox', + items: [{ + xtype: 'radio', + itemId: "widthModeFlex", + name: "widthMode", + bind: { + value: "{isFlex}" + }, + boxLabel: i18n("Ratio") + }, { + xtype: 'numberfield', + itemId: 'flexValue', + bind: { + value: "{column.flex}", + disabled: "{!isFlex}" + } + }] + }] + + }, { + xtype: 'checkbox', + hideEmptyLabel: false, + boxLabel: i18n("Hidden"), + bind: { + value: "{column.hidden}" + } + }, { + xtype: 'fieldcontainer', + flex: 1, + layout: 'fit', + fieldLabel: i18n("Renderers"), + itemId: 'renderersContainer', + + items: [{ + xtype: 'partkeepr.renderersGrid', + itemId: "renderers" + }] + }], + + sourceModel: null, + layout: { + type: 'vbox', + align: 'stretch', + pack: 'start' + }, + defaults: { + anchor: '100%', + labelWidth: 80 + }, + + initComponent: function () + { + this.callParent(arguments); + + this.down("#selectField").on("click", this.onFieldSelectClick, this); + this.down("#widthModeFlex").on("change", this.onWidthModeChanged, this); + this.down("#widthModeFixed").on("change", this.onWidthModeChanged, this); + this.down("#renderers").setSourceModel(this.sourceModel); + }, + + onFieldSelectClick: function () + { + this.modelFieldSelectorWindow = Ext.create("PartKeepr.Components.Widgets.FieldSelectorWindow", { + sourceModel: this.sourceModel + }); + this.modelFieldSelectorWindow.on("fieldSelect", function (field) + { + this.down("#field").setValue(field.data.data.name); + }, this); + this.modelFieldSelectorWindow.show(); + }, + loadRecord: function (record) + { + this.callParent(arguments); + + if (record.get("flex") > 0) + { + record.set("width", 0); + } + + this.down("#renderers").bindStore(record.renderers()); + }, + onWidthModeChanged: function () + { + if (this.down("#widthModeFixed").getValue()) + { + this.getRecord().set("widthMode", "fixed"); + } else + { + this.getRecord().set("widthMode", "flex"); + } + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumnListGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumnListGrid.js @@ -0,0 +1,253 @@ +Ext.define("PartKeepr.Components.Widgets.ColumnConfigurator.ColumnListGrid", { + extend: "Ext.grid.Panel", + layout: 'fit', + columns: [ + { + header: "Title", + dataIndex: 'text', + menuDisabled: true, + resizable: false, + flex: 1 + }, { + header: "Field", + menuDisabled: true, + resizable: false, + dataIndex: 'dataIndex', + flex: 1 + }, { + header: "index", + dataIndex: "index", + width: 20 + } + ], + viewConfig: { + markDirty: false + }, + + sortableColumns: false, + store: { + model: 'PartKeepr.Models.ColumnConfiguration', + sorters: [{ + property: 'index', + direction: 'ASC' + }] + }, + bind: { + selection: '{column}' + }, + initComponent: function () + { + this.dockedItems = [ + { + xtype: 'toolbar', + dock: 'bottom', + items: [{ + iconCls: 'web-icon accept', + itemId: "apply", + disabled: false, + text: i18n("Apply") + }, { + iconCls: 'web-icon cancel', + itemId: "cancel", + disabled: false, + text: i18n("Cancel") + }, '-', { + iconCls: 'fugue-icon table-insert-column', + itemId: "addField", + disabled: false, + tooltip: i18n("Add new field") + }, { + iconCls: 'fugue-icon table-delete-column', + itemId: "deleteField", + disabled: true, + tooltip: i18n("Delete field") + }, '-',{ + iconCls: 'fugue-icon arrow-stop-090', + itemId: "move-top", + disabled: true, + tooltip: i18n("Move to top") + }, { + iconCls: 'fugue-icon arrow-090', + tooltip: i18n("Move up"), + disabled: true, + itemId: "move-up" + }, { + iconCls: 'fugue-icon arrow-270', + itemId: "move-down", + disabled: true, + tooltip: i18n("Move down") + }, { + iconCls: 'fugue-icon arrow-stop-270', + itemId: "move-bottom", + disabled: true, + tooltip: i18n("Move to bottom") + },'-', { + iconCls: 'fugue-icon arrow-return-180', + itemId: "restoreDefaults", + tooltip: i18n("Restore Defaults") + }] + }, { + xtype: 'toolbar', + dock: 'bottom', + items: { + xtype: 'presetcombo', + model: 'PartKeepr.FrontendBundle.Entity.GridPreset', + itemId: 'gridPresetCombo', + displayField: 'name', + blankConfiguration: [ + { + + } + ], + width: 300 + + } + }]; + this.callParent(); + + this.down("#move-top").on("click", this.moveTop, this); + this.down("#move-up").on("click", this.moveUp, this); + this.down("#move-down").on("click", this.moveDown, this); + this.down("#move-bottom").on("click", this.moveBottom, this); + + this.down("#deleteField").on("click", this.deleteField, this); + this.down("#addField").on("click", this.addField, this); + + this.getStore().on("update", this.updateButtonState, this); + this.on("selectionchange", this.updateButtonState, this); + + this.on("select", this.updateButtonState, this); + }, + addField: function () + { + var i, max = 0; + + for (i = 0; i < this.getStore().getCount(); i++) + { + if (this.getStore().getAt(i).get("index") > max) + { + max = this.getStore().getAt(i).get("index"); + } + } + var j = Ext.create("PartKeepr.Models.ColumnConfiguration", { + index: max + 1, + widthMode: "flex", + flex: 1 + }); + + this.getStore().add(j); + + this.ensureVisible(j); + this.setSelection(j); + }, + deleteField: function () + { + var oldIdx = this.getStore().indexOf(this.getSelection()[0]); + this.getStore().remove(this.getSelection()[0]); + + this.setSelection(this.getStore().getAt(oldIdx)); + }, + updateButtonState: function () + { + var record; + + if (this.getSelection().length !== 1) + { + this.down("#deleteField").disable(); + this.down("#move-top").disable(); + this.down("#move-up").disable(); + this.down("#move-bottom").disable(); + this.down("#move-down").disable(); + + return; + } + + record = this.getSelection()[0]; + this.down("#deleteField").enable(); + + if (this.getStore().indexOf(record) === 0) + { + this.down("#move-top").disable(); + this.down("#move-up").disable(); + } else + { + this.down("#move-top").enable(); + this.down("#move-up").enable(); + } + + if (this.getStore().indexOf(record) === this.getStore().getCount() - 1) + { + this.down("#move-bottom").disable(); + this.down("#move-down").disable(); + } else + { + this.down("#move-bottom").enable(); + this.down("#move-down").enable(); + } + }, + moveTop: function () + { + var record, sort; + + if (this.getSelection().length === 1) + { + for (sort = this.getStore().getCount() - 1; sort > -1; sort--) + { + this.getStore().getAt(sort).set("index", sort + 1); + } + record = this.getSelection()[0]; + record.set("index", 0); + } + + this.ensureVisible(record); + }, + moveBottom: function () + { + var record, sort; + + if (this.getSelection().length === 1) + { + record = this.getSelection()[0]; + record.set("index", this.getStore().getCount()); + + for (sort = 0; sort < this.getStore().getCount(); sort++) + { + this.getStore().getAt(sort).set("index", sort); + } + } + + this.ensureVisible(record); + }, + moveDown: function () + { + var record, nextRecord, sort, myIndex; + + if (this.getSelection().length === 1) + { + record = this.getSelection()[0]; + myIndex = this.getStore().indexOf(record); + nextRecord = this.getStore().getAt(myIndex + 1); + sort = nextRecord.get("index"); + nextRecord.set("index", record.get("index")); + record.set("index", sort); + } + + this.ensureVisible(record); + }, + moveUp: function () + { + var record, previousRecord, sort, myIndex; + + if (this.getSelection().length === 1) + { + record = this.getSelection()[0]; + myIndex = this.getStore().indexOf(record); + previousRecord = this.getStore().getAt(myIndex - 1); + sort = previousRecord.get("index"); + previousRecord.set("index", record.get("index")); + record.set("index", sort); + } + + this.ensureVisible(record); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Panel.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Panel.js @@ -0,0 +1,206 @@ +Ext.define("PartKeepr.Components.Widgets.ColumnConfigurator.Panel", { + extend: "Ext.panel.Panel", + + layout: 'border', + grid: null, + + originalColumnConfigurations: [], + + viewModel: { + data: { + column: null + }, + formulas: { + isFlex: function (get) + { + + return get("column.widthMode") === "flex"; + } + } + }, + + initComponent: function () + { + this.columnListGrid = Ext.create("PartKeepr.Components.Widgets.ColumnConfigurator.ColumnListGrid", { + region: 'west', + width: 400, + split: true + }); + + this.columnProperties = Ext.create("PartKeepr.Components.Widgets.ColumnConfigurator.ColumnProperties", { + region: 'center', + sourceModel: this.grid.getStore().getModel() + } + ); + + this.items = [ + this.columnListGrid, + this.columnProperties + ]; + + this.callParent(); + + this.columnListGrid.on("select", this.onColumnSelect, this); + this.columnListGrid.getStore().on("add", this.onAdd, this); + this.down("#restoreDefaults").on("click", this.restoreDefaults, this); + this.columnListGrid.getStore().on("datachanged", this.preview, this); + this.columnListGrid.getStore().on("update", this.preview, this); + + this.down("#gridPresetCombo").getStore().addFilter({ + property: "grid", + operator: "=", + value: this.grid.$className + }); + + this.down("#gridPresetCombo").on("selectPreset", this.onPresetSelect, this); + this.down("#gridPresetCombo").setAdditionalFields([ + { + fieldName: "grid", + value: this.grid.$className + } + ]); + + this.down("#renderers").on("change", this.preview, this); + + this.autoPreviewTask = new Ext.util.DelayedTask(this.doPreview, this, null, true); + }, + restoreDefaults: function () + { + if (this.grid instanceof PartKeepr.BaseGrid) + { + this.grid.reconfigure(this.grid.store, this.grid.getDefaultColumnConfiguration()); + this.applyColumnConfigurationFromGrid(); + } + }, + onPresetSelect: function (configuration) + { + this.grid.reconfigure(this.grid.store, configuration); + this.applyColumnConfigurationFromGrid(); + + this.down("#gridPresetCombo").setConfiguration(configuration); + }, + onColumnSelect: function (grid, record) + { + this.editData(record); + }, + onAdd: function (store, records) + { + if (records.length === 1) + { + this.editData(records[0]); + } + }, + editData: function (record) + { + this.columnProperties.loadRecord(record); + }, + preview: function () + { + this.down("#gridPresetCombo").setConfiguration(this.getColumnConfigurations()); + this.autoPreviewTask.delay(200); + }, + doPreview: function () + { + this.grid.reconfigure(this.grid.store, this.getColumnConfigurations()); + }, + getColumnConfigurations: function () + { + var i, j, rtype; + + var config = {}, columnConfigurations = [], fieldsToCopy = this.getFieldsToCopy(); + + var data = this.columnListGrid.getStore().getData(); + + for (i = 0; i < data.getCount(); i++) + { + config = {}; + for (j = 0; j < fieldsToCopy.length; j++) + { + config[fieldsToCopy[j]] = data.getAt(i).get(fieldsToCopy[j]); + } + + if (data.getAt(i).get("widthMode") === "flex") + { + delete config.width; + } else + { + delete config.flex; + } + + config.renderers = []; + + for (j = 0; j < data.getAt(i).renderers().getCount(); j++) + { + + rtype = data.getAt(i).renderers().getAt(j).get("rtype"); + + if (typeof(PartKeepr.Components.Grid.Renderers.RendererRegistry.lookupRenderer(rtype)) !== "undefined") + { + config.renderers.push({ + rtype: rtype, + rendererConfig: Ext.decode(data.getAt(i).renderers().getAt(j).get("config")) + }); + } + } + columnConfigurations.push(config); + + } + + return columnConfigurations; + }, + applyColumnConfigurationFromGrid: function () + { + var columns = this.grid.getColumns(); + var i, j; + var columnRecord; + this.originalColumnConfigurations = []; + var columnConfig; + var fieldsToCopy = this.getFieldsToCopy(); + + this.columnListGrid.getStore().removeAll(); + + for (i = 0; i < columns.length; i++) + { + columnRecord = Ext.create("PartKeepr.Models.ColumnConfiguration"); + columnConfig = {}; + + for (j = 0; j < fieldsToCopy.length; j++) + { + columnRecord.set(fieldsToCopy[j], columns[i][fieldsToCopy[j]]); + columnConfig[fieldsToCopy[j]] = columns[i][fieldsToCopy[j]]; + } + + if (columnConfig["flex"] > 0) + { + columnRecord.set("widthMode", "flex"); + } else + { + columnRecord.set("widthMode", "width"); + } + + columnConfig["renderers"] = columns[i]["renderers"]; + this.originalColumnConfigurations.push(columnConfig); + columnRecord.set("index", i); + + if (columns[i].renderers instanceof Array) + { + for (j = 0; j < columns[i].renderers.length; j++) + { + columnRecord.renderers().add(Ext.create("PartKeepr.Models.ColumnRendererConfiguration", { + rtype: columns[i].renderers[j].rtype, + config: Ext.encode(columns[i].renderers[j].rendererConfig) + })); + } + } + + columnRecord.renderers().on("datachanged", this.preview, this); + columnRecord.renderers().on("update", this.preview, this); + + this.columnListGrid.getStore().add(columnRecord); + } + }, + getFieldsToCopy: function () + { + return ["dataIndex", "text", "hidden", "flex", "width", "tooltip"]; + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RendererConfigurationForm.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RendererConfigurationForm.js @@ -0,0 +1,110 @@ +Ext.define("PartKeepr.Components.Widgets.ColumnConfigurator.RendererConfigurationForm", { + extend: "Ext.form.Panel", + + renderer: null, + rendererConfiguration: null, + + scrollable: true, + + bbar: { + items: [{ + xtype: 'button', + itemId: "apply", + text: i18n("Apply") + }, { + xtype: 'button', + itemId: "cancel", + text: i18n("Cancel") + }] + }, + defaults: { + labelWidth: 200 + }, + initComponent: function () + { + var renderer = PartKeepr.Components.Grid.Renderers.RendererRegistry.lookupRenderer(this.renderer); + + + if (typeof(renderer) !== undefined) + { + this.items = this.createFormConfig(renderer); + + } + + if (typeof(this.rendererConfiguration) === "string") { + this.rendererConfiguration = Ext.decode(this.rendererConfiguration); + } + + this.callParent(arguments); + this.getForm().setValues(this.rendererConfiguration); + + }, + createFormConfig: function (renderer) + { + var configIterator, config, field, fields = [], useDescriptionElemement; + + for (configIterator in renderer.rendererConfigs) + { + config = renderer.rendererConfigs[configIterator]; + + Ext.applyIf(config, { + type: "", + description: "" + }); + + useDescriptionElemement = false; + + switch (config.type) + { + case "boolean": + field = { + xtype: 'checkbox', + name: configIterator, + boxLabel: config.description, + fieldLabel: config.title + }; + + break; + case "string": + field = { + xtype: 'textfield', + name: configIterator, + fieldLabel: config.title, + title: config.description + }; + + useDescriptionElemement = true; + break; + case "partParameter": + field = { + xtype: 'PartParameterComboBox', + name: configIterator, + fieldLabel: config.title + }; + break; + default: + field = {}; + } + + fields.push(field); + + if (useDescriptionElemement) + { + fields.push({ + xtype: 'displayfield', + value: config.description, + hideEmptyLabel: false + }); + } + } + + if (fields.length === 0) { + fields.push({ + xtype: 'displayfield', + value: i18n("The selected renderer cannot be configured") + }); + } + + return fields; + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RenderersGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RenderersGrid.js @@ -0,0 +1,182 @@ +Ext.define("PartKeepr.Components.Widgets.ColumnConfigurator.RenderersGrid", { + extend: "Ext.grid.Panel", + + xtype: 'partkeepr.renderersGrid', + + plugins: { + ptype: 'cellediting', + clicksToEdit: 1 + }, + + bbar: [{ + itemId: "addRenderer", + text: i18n("Add") + }, { + itemId: "removeRenderer", + text: i18n("Remove"), + disabled: true, + bind: { + disabled: '{!renderers.selection}' + } + }], + + reference: "renderers", + + initComponent: function () + { + this.rendererStore = Ext.create("Ext.data.Store"); + + this.columns = [{ + text: i18n("Renderer"), + dataIndex: 'rtype', + renderer: function (val, q, rec) + { + var targetClass = PartKeepr.Components.Grid.Renderers.RendererRegistry.lookupRenderer( + rec.get("rtype")); + + if (typeof(targetClass) === "undefined") + { + return i18n("unknown"); + } + + if (targetClass.rendererName) + { + return targetClass.rendererName; + } + + return targetClass.$className; + }, + editor: { + xtype: 'combobox', + store: this.rendererStore, + displayField: 'rendererName', + valueField: 'rendererType', + queryMode: 'local', + forceSelection: true, + editable: false, + clicksToEdit: 1, + matchFieldWidth: false, + listConfig: { + itemTpl: [ + '<div>{rendererName}: {rendererDescription}</div>' + ] + } + }, + flex: 1 + }, { + text: i18n("Description"), + renderer: function (val, q, rec) + { + var targetClass = PartKeepr.Components.Grid.Renderers.RendererRegistry.lookupRenderer( + rec.get("rtype")); + + if (typeof(targetClass) === "undefined") + { + return i18n("unknown"); + } + + if (targetClass.rendererDescription) + { + return targetClass.rendererDescription; + } + + return targetClass.$className; + }, + flex: 2 + }, { + text: i18n("Configuration"), + dataIndex: "config", + editor: { + xtype: 'textarea' + } + }, { + xtype: 'actioncolumn', + items: [ + { + iconCls: 'fugue-icon pencil', + tooltip: i18n("Configure"), + handler: function (view, rowIndex, colIndex, item, e, record) + { + var config = record.get("config"); + + if (typeof config === "undefined") + { + config = {}; + record.set("config", config); + } + + this.configure(record); + }, + scope: this + } + ] + }]; + + this.callParent(arguments); + + this.down("#addRenderer").on("click", this.addRenderer, this); + this.down("#removeRenderer").on("click", this.removeRenderer, this); + }, + configure: function (record) + { + var i = Ext.create("PartKeepr.Components.Widgets.ColumnConfigurator.RendererConfigurationForm", { + renderer: record.get("rtype"), + rendererConfiguration: record.get("config") + }); + + var j = Ext.create("Ext.window.Window", { + layout: 'fit', + minWidth: 500, + minHeight: 200, + title: i18n("Renderer Configuration"), + modal: true, + items: [i] + }); + + i.down("#apply").on("click", function () + { + record.set("config", Ext.encode(i.getForm().getValues())); + j.destroy(); + + }); + + i.down("#cancel").on("click", function () + { + j.destroy(); + }); + + j.show(); + }, + setSourceModel: function (sourceModel) + { + this.sourceModel = sourceModel; + + var rendererClasses = PartKeepr.Components.Grid.Renderers.RendererRegistry.getRenderersForEntity( + this.sourceModel.$className); + + for (var i = 0; i < rendererClasses.length; i++) + { + this.rendererStore.add({ + rendererClass: rendererClasses[i], + rendererName: Ext.ClassManager.get(rendererClasses[i]).rendererName, + rendererDescription: Ext.ClassManager.get(rendererClasses[i]).rendererDescription, + rendererType: PartKeepr.Components.Grid.Renderers.RendererRegistry.getRType(rendererClasses[i]) + }); + } + }, + addRenderer: function () + { + this.getStore().add({config: "null"}); + }, + removeRenderer: function () + { + var selection = this.getSelection(); + + if (selection.length !== 1) + { + return; + } + + this.getStore().remove(selection[0]); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Window.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Window.js @@ -0,0 +1,40 @@ +Ext.define("PartKeepr.Components.Widgets.ColumnConfigurator.Window", { + extend: "Ext.window.Window", + + width: 800, + height: 400, + + title: i18n("Configure Columns"), + + layout: 'fit', + modal: true, + + grid: null, + + keepConfiguration: false, + + initComponent: function () { + this.columnConfiguratorPanel = Ext.create("PartKeepr.Components.Widgets.ColumnConfigurator.Panel", { + grid: this.grid + }); + this.items = this.columnConfiguratorPanel; + this.callParent(arguments); + + this.down("#apply").on("click", this.apply, this); + this.down("#cancel").on("click", this.doClose, this); + }, + apply: function () { + this.keepConfiguration = true; + this.close(); + }, + doClose: function (keepConfig) { + if (!this.keepConfiguration) + { + this.grid.reconfigure(this.grid.getStore(), this.columnConfiguratorPanel.originalColumnConfigurations); + } + this.callParent(arguments); + }, + applyColumnConfigurationFromGrid: function () { + this.columnConfiguratorPanel.applyColumnConfigurationFromGrid(this.grid); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/PagingToolbar.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/PagingToolbar.js @@ -11,12 +11,14 @@ Ext.define("PartKeepr.PagingToolbar", { itemId: 'export', tooltip: i18n("Export"), iconCls: "fugue-icon application-export", + overflowText: i18n("Export"), disabled: this.store.isLoading() })); items.push(Ext.create("PartKeepr.Importer.GridImporterButton", { itemId: 'import', tooltip: i18n("Import"), + overflowText: i18n("Import"), iconCls: "fugue-icon database-import", disabled: this.store.isLoading() })); @@ -25,12 +27,17 @@ Ext.define("PartKeepr.PagingToolbar", { itemId: 'addFilter', xtype: 'button', tooltip: i18n("Add Filter"), + overflowText: i18n("Add Filter"), iconCls: "fugue-icon funnel--plus", disabled: this.store.isLoading(), handler: this.onAddFilterClick, scope: this }); + items.push(Ext.create("PartKeepr.Components.Grid.GridPresetButton", { + grid: this.grid + })); + items.push(Ext.create({ itemId: 'filter', xtype: 'button', diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js @@ -33,6 +33,11 @@ Ext.define("PartKeepr.data.HydraModel", { if (ret === undefined) { // The field is undefined, attempt to retrieve data via associations + + if (typeof(fieldName) !== "string") { + return undefined; + } + var parts = fieldName.split("."); if (parts.length < 2) { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Ext.ux/StoreMenu.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Ext.ux/StoreMenu.js @@ -0,0 +1,350 @@ +/** + * Ext.ux.menu.StoreMenu Addon + * + * Inspired from the Ext.ux.menu.StoreMenu for ExtJs 3 by Marco Wienkoop + * + * This version is a complete rewrite and enhanced for ExtJs 4 with some of the old features removed. + * @author Joe Kuan + * @docauthor Joe Kuan + * Joe Kuan <kuan.joe@gmail.com> + * + * #Demo and Download + * Here are the links for the online [demo](http://joekuan.org/demos/StoreMenu_ExtJs_4/) and [github](http://github.com/JoeKuan/StoreMenu_ExtJs_4) + * download. For usage, see [license](http://github.com/JoeKuan/StoreMenu_ExtJs_4/blob/master/License). + * + * #Creating &amp; Applying StoreMenu + * + * Suppose we define a JSON Store for a list of menus as follows: + * + * @example + * Ext.define('Menu', { + * extend: 'Ext.data.Model', + * fields: [ 'id', 'text', 'iconCls' ] + * }); + * + * var store = Ext.create('Ext.data.Store', { + * model: 'Menu', + * proxy: { + * type: 'ajax', + * url: 'demo/menu.php', + * reader: { + * type: 'json', + * root: 'root' + * } + * } + * }); + * + * The *id*, *text* and *iconCls* fields are mapped to menu item config. + * + * Assume the file menu.php returns the following in JSON: + * @example + * { "success": true, + * "root": [{ + * "id": 1, "text": "Menu 1" + * },{ + * "id": 2, "text": "Menu 2", "iconCls": "calendar" + * },{ + * "id": 3, "text": "Menu 3" + * } + * ] + * } + * + * To produce a simple Window with store menu inside a toolbar, here is the code + * @example + * Ext.create('Ext.window.Window', { + * height: 380, + * width: 450, + * title: 'Menu Store for ExtJs 4', + * tbar: [{ + * menu: Ext.create('Ext.ux.menu.StoreMenu', { + * store: store, + * itemsHandler: function(item, evt) { + * Ext.example.msg("Store Menu", + * "You click on item with id " + item.id); + * } + * }), + * // Need this for empty menu - no items option + * showEmptyMenu: true, + * text: 'Menu Demo 1' + * }] + * }).show(); + * + * Clicking any of the menus dynamically generated will call itemsHandler. To differentiate + * between the menus, each menu item is created with the id option which is taken from the + * id field of the store + * + * #StoreMenu without *items* option + * The menu rendering policy has been changed since ExtJs 4.2.1 so that menu is not rendered + * if the {@link Ext.menu.Menu#cfg-items} option is empty. Subsequently, this will stop the + * store from loading, hence no dynamic menus will be displayed. In order to force the + * StoreMenu to render, an option, {@link Ext.button.Button#cfg-showEmptyMenu}, + * is needed to pass to the owner button in this scenario. + * + * #Creating Specific Menus + * For more specific menus, StoreMenu supports object specifier through single field name, *config*. + * Store record with *config* field is expected to contain required options for creating menu objects, such as xtype. + * Menus like: menucheckitem, separator can be specified through this scheme. + * Moreover, this can be mixed with normal menu item creation which the data model definition + * includes both field name schemes. The following shows an example for creating specific menus along with default + * menu item through the store. + * @example + * Ext.define('Menu', { + * extend: 'Ext.data.Model', + * fields: [ 'id', 'text', 'iconCls', 'config' ] + * }); + * + * The server side is configured to return the following menus in JSON: + * @example + * { "root": [{ "config": { "id": "2A", "text": "Menu 2A", "xtype": "menucheckitem"} }, + * { "config": { "xtype": "menuseparator"} }, + * { "id": "2C", "text": "Menu 2C" } + * ] + * } + * + * #Creating Submenus + * The StoreMenu also supports submenu entries (single level only). The server will need to + * return additional fields for including submenu entries (see menuField) and id string for + * their handlers (see smHandlers). + * The following is what the server side should return for submenus + * @example + * { "root": [{ "id": "3A", "text": "Menu 3A", + * "menu": [{ "id": "3A-1", "text": "Submenu 3A-1", "smHandler": "submenu3A1" }, + * { "id": "3A-2", "text": "Submenu 3A-2", "smHandler": "submenu3A2" } + * ] + * } + * To bind with the submenu handlers, we can create the StoreMenu as follows: + * @example + * var menu3 = Ext.create('Ext.ux.menu.StoreMenu', { + * store: store, + * smHandlers: { + * submenu3A1: function(item) { + * Ext.example.msg("Third Menu Store", "This is submenu handler specific for menu 3A-1"); + * }, + * submenu3A2: function(item) { + * Ext.example.msg("Third Menu Store", "This is submenu handler specific for menu 3A-2"); + * } + * } + * }); + * + */ +Ext.define("Ext.ux.menu.StoreMenu", { + extend: 'Ext.menu.Menu', + alias: 'widget.storemenu', + + config: { + /*** + * message shown next to the store menu when it is loading + */ + loadingText: 'Loading...', + /** + * offset is to use with static menus, i.e. the offset position for + * the dynamic menus to start. E.g. if two static menus are included inside the items + * option, by setting *offset* to 2 the dynamic menus start below the static + * menus. Also a separator is added between static and dynamic menus + */ + offset: 0, + /** + * reload the store everytime the top menu is expanded + */ + autoReload: true, + /** + * nameField is to map the field for the menu title returning from the + * store. + */ + nameField: 'text', + + /** + * + * optional Field of the store + */ + url: 'url', + + /** + * idField is to map the menu id entry from the store + */ + idField: 'id', + /** + * iconField is to map the menu icon (iconCls) + */ + iconField: 'iconCls', + /** + * itemsHandler is the general menu handler for the __first level__ menus. + * For submenu handler, see subMenuHandler + */ + itemsHandler: Ext.emptyFn, + /** + * configField is used for specific menu types other than + * menu item (default). The config field returned from the server + * side is expected to hold all the required options to create the + * specific menu + */ + configField: 'config', + /** + * the field name containing the list of submenu entries in the store + */ + menuField: 'menu' + }, + + /*** + * @property {Object} store + * Data store for the menus &amp; submenus entries + */ + store: null, + + // Keep track of what menu items have been added + storeMenus: [], + + loaded: false, + loadMask: null, + + onMenuLoad: function () + { + if (!this.loaded || this.autoReload) + { + this.store.load(); + } + }, + + updateMenuItems: function (loadedState, records) + { + + for (var i = 0; i < this.storeMenus.length; i++) + { + this.remove(this.storeMenus[i]); + } + this.storeMenus = []; + + if (loadedState) + { + + // If offset is specified, it means we have to put the + // dynamic menus after the static menus. We put a separator + // to separate both + var count = 0; + if (this.offset) + { + this.storeMenus.push(this.insert(this.offset, {xtype: 'menuseparator'})); + count = 1; + } + + Ext.each(records, function (record) + { + + var menuSettings = {}; + var patterns = {}; + patterns.protocol = '^(http(s)?(:\/\/)){1}(www[.])?'; + patterns.domain = '([a-zA-Z0-9-_\.])+'; + patterns.params = '([.][a-zA-Z0-9(-|\/|=|?)?]+)'; + var regex = new RegExp(patterns.protocol + patterns.domain + patterns.params + "$"); + + + if (record.data[this.configField]) + { + Ext.apply(menuSettings, record.data[this.configField]); + } else + { + menuSettings = { + id: record.data[this.idField], + text: record.data[this.nameField], + iconCls: record.data[this.iconField], + handler: this.itemsHandler + }; + regex.test(record.data[this.url]) ? menuSettings.url = record.data[this.url] : ""; + } + + if (record.data[this.menuField]) + { + menuSettings.menu = {items: []}; + Ext.each(record.data[this.menuField], function (menuitem) + { + // Make sure the handler name is defined + if (menuitem.smHandler && this.smHandlers[menuitem.smHandler]) + { + menuSettings.menu.items.push({ + id: menuitem[this.idField], + text: menuitem[this.nameField], + iconCls: menuitem[this.iconField], + url: menuItem[this.url], + handler: this.smHandlers[menuitem.smHandler] + }); + } + }, this); + } + + this.storeMenus.push(this.insert(this.offset + count, menuSettings)); + count++; + }, this) + + } else + { + this.storeMenus.push(this.insert(this.offset, + '<span class="loading-indicator">' + + this.loadingText + '</span>')); + } + + this.loaded = loadedState; + }, + + onBeforeLoad: function () + { + this.updateMenuItems(false); + }, + + onLoad: function (store, records) + { + this.updateMenuItems(true, records); + }, + + /*** + * Set a submenu handler. + * @param handlerType {String} the id value for the handler function + * @param handler {Function} the handler implementation - See menuitem handler for function parameters + */ + setSubMenuHandler: function (handlerType, handler) + { + this.smHandlers[handlerType] = handler; + }, + + /** + * A utility method for changing parameters of the underlying + * JSON store. Values inside params object will overwrite the store's + * existing parameters with the same name + * @param {Object} params an object of parameters to be set in the store + */ + setParams: function (params) + { + Ext.apply(this.store.getProxy().extraParams, params); + }, + + setStore: function (store) + { + this.store = store; + }, + + /** + * @cfg smHandlers {Object} is an object holding all the submenu handler implementations. + * Inside the object, option name is the identifier for the handler function which is the option value. + * (See the [submenu section] (#submenu) for example) + */ + smHandlers: {}, + + initComponent: function (config) + { + this.callParent(arguments); + + if (!this.store) + { + //at least url/proxy or data need to be given in config when initiating this + // component + this.store = Ext.create('Ext.data.Store', { + model: this.model, + remoteFilter: true + }); + } + + this.on("beforeshow", this.onMenuLoad, this); + this.store.on('beforeload', this.onBeforeLoad, this); + this.store.on('load', this.onLoad, this); + } + +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.Column-multipleRendererSupport.js b/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.Column-multipleRendererSupport.js @@ -0,0 +1,86 @@ +/** + * Enhances grid columns to support multiple renderers. + */ +Ext.define("PartKeepr.ExtJS.Enhancements.Grid.MultipleRendererSupport", { + override: "Ext.grid.Column", + + rendererInstances: [], + + initComponent: function () + { + var i, rendererDef, rendererClass; + + if (this.$className !== "Ext.grid.column.Column") { + return this.callParent(arguments); + } + + this.originalRenderer = this.renderer; + this.originalScope = this.scope; + this.scope = this; + this.renderer = this.doRender; + + + this.callParent(arguments); + + + if (!this.renderers) + { + this.renderers = []; + } + + + if (!(this.renderers instanceof Array)) + { + this.renderers = [this.renderers]; + } + + this.rendererInstances = []; + + for (i = 0; i < this.renderers.length; i++) + { + rendererDef = this.renderers[i]; + + if (typeof(rendererDef) === "string") + { + rendererClass = PartKeepr.Components.Grid.Renderers.RendererRegistry.lookupRenderer(rendererDef); + + this.rendererInstances.push(Ext.create(rendererClass)); + continue; + } + + if (typeof(rendererDef) === "object" && rendererDef.rtype) + { + rendererClass = PartKeepr.Components.Grid.Renderers.RendererRegistry.lookupRenderer(rendererDef.rtype); + this.rendererInstances.push(Ext.create(rendererClass, rendererDef)); + continue; + } + + if (rendererDef instanceof PartKeepr.Components.Grid.Renderers.AbstractRenderer) + { + Ext.raise("Passing a renderer instance is prohibited!"); + continue; + } + + Ext.raise("No valid renderers definition found for entry:"); + Ext.raise(rendererDef); + } + }, + doRender: function (value, metaData, record, rowIndex, colIndex, store, view) + { + var i; + + value = Ext.util.Format.htmlEncode(value); + + for (i = 0; i < this.rendererInstances.length; i++) + { + value = this.rendererInstances[i].renderer.call(this.originalScope, value, metaData, record, rowIndex, colIndex, store, view, this.rendererInstances[i]); + } + + if (typeof(this.originalRenderer) === "function") + { + return this.originalRenderer.call(this.originalScope, value, metaData, record, rowIndex, colIndex, store, view); + } else { + return value; + } + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.header.Container-addMoreMenu.js b/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.header.Container-addMoreMenu.js @@ -0,0 +1,52 @@ +Ext.define("PartKeepr.ExtJS.Enhancements.addMoreMenu", { + override: "Ext.grid.header.Container", + + /** + * Returns an array of menu CheckItems corresponding to all immediate children + * of the passed Container which have been configured as hideable. + */ + getColumnMenu: function (headerContainer) + { + var menuItems = [], + i = 0, + item, + items = headerContainer.query('>gridcolumn[hideable]'), + itemsLn = items.length, + menuItem; + + for (; i < itemsLn; i++) + { + item = items[i]; + menuItem = new Ext.menu.CheckItem({ + text: item.menuText || item.text, + checked: !item.hidden, + hideOnClick: false, + headerId: item.id, + menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined, + checkHandler: this.onColumnCheckChange, + scope: this + }); + menuItems.push(menuItem); + } + + menuItems.push('-'); + + menuItems.push(Ext.menu.CheckItem({ + text: i18n("Customize…"), + iconCls: 'fugue-icon table--pencil', + menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined, + handler: this.foo, + scope: this + })); + + // Prevent creating a submenu if we have no items + return menuItems.length ? menuItems : null; + }, + foo: function () { + var j = Ext.create("PartKeepr.Components.Widgets.ColumnConfigurator.Window", { + grid: this.grid + }); + j.applyColumnConfigurationFromGrid(); + j.show(); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.view.Table-renderCell.js b/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.view.Table-renderCell.js @@ -0,0 +1,113 @@ +/** + * Overrides the renderCell method so it uses the record.get() call and not the dataIndex on the record's data + * array itself. This allows to use nested queries like storageLocation.name + */ +Ext.define("PartKeepr.ExtJS.Enhancements.renderCell", { + override: 'Ext.view.Table', + + /** + * @private + * Emits the HTML representing a single grid cell into the passed output stream (which is an array of strings). + * + * @param {Ext.grid.column.Column} column The column definition for which to render a cell. + * @param {Ext.data.Model} record The record being edited + * @param {Number} recordIndex The row index (zero based within the {@link #store}) for which to render the cell. + * @param {Number} rowIndex The row index (zero based within this view for which to render the cell. + * @param {Number} columnIndex The column index (zero based) for which to render the cell. + * @param {String[]} out The output stream into which the HTML strings are appended. + */ + renderCell: function (column, record, recordIndex, rowIndex, columnIndex, out) { + var me = this, + fullIndex, + selModel = me.selectionModel, + cellValues = me.cellValues, + classes = cellValues.classes, + fieldValue, + cellTpl = me.cellTpl, + enableTextSelection = column.enableTextSelection, + value, clsInsertPoint, + lastFocused = me.navigationModel.getPosition(); + + fieldValue = record.get(column.dataIndex); + + // Only use the view's setting if it's not been overridden on the column + if (enableTextSelection == null) { + enableTextSelection = me.enableTextSelection; + } + + cellValues.record = record; + cellValues.column = column; + cellValues.recordIndex = recordIndex; + cellValues.rowIndex = rowIndex; + cellValues.columnIndex = cellValues.cellIndex = columnIndex; + cellValues.align = column.textAlign; + cellValues.innerCls = column.innerCls; + cellValues.tdCls = cellValues.tdStyle = cellValues.tdAttr = cellValues.style = ""; + cellValues.unselectableAttr = enableTextSelection ? '' : 'unselectable="on"'; + + // Begin setup of classes to add to cell + classes[1] = column.getCellId(); + + // On IE8, array[len] = 'foo' is twice as fast as array.push('foo') + // So keep an insertion point and use assignment to help IE! + clsInsertPoint = 2; + + if (column.renderer && column.renderer.call) { + fullIndex = me.ownerCt.columnManager.getHeaderIndex(column); + value = column.renderer.call(column.usingDefaultRenderer ? column : column.scope || me.ownerCt, fieldValue, cellValues, record, recordIndex, fullIndex, me.dataSource, me); + if (cellValues.css) { + // This warning attribute is used by the compat layer + // TODO: remove when compat layer becomes deprecated + record.cssWarning = true; + cellValues.tdCls += ' ' + cellValues.css; + cellValues.css = null; + } + + // Add any tdCls which was added to the cellValues by the renderer. + if (cellValues.tdCls) { + classes[clsInsertPoint++] = cellValues.tdCls; + } + } else { + value = fieldValue; + } + + cellValues.value = (value == null || value.length === 0) ? column.emptyCellText : value; + + if (column.tdCls) { + classes[clsInsertPoint++] = column.tdCls; + } + if (me.markDirty && record.dirty && record.isModified(column.dataIndex)) { + classes[clsInsertPoint++] = me.dirtyCls; + + if (column.dirtyTextElementId) { + cellValues.tdAttr = (cellValues.tdAttr ? cellValues.tdAttr + ' ' : '') + + 'aria-describedby="' + column.dirtyTextElementId + '"'; + } + } + if (column.isFirstVisible) { + classes[clsInsertPoint++] = me.firstCls; + } + if (column.isLastVisible) { + classes[clsInsertPoint++] = me.lastCls; + } + if (!enableTextSelection) { + classes[clsInsertPoint++] = me.unselectableCls; + } + if (selModel && (selModel.isCellModel || selModel.isSpreadsheetModel) && selModel.isCellSelected(me, recordIndex, column)) { + classes[clsInsertPoint++] = me.selectedCellCls; + } + if (lastFocused && lastFocused.record.id === record.id && lastFocused.column === column) { + classes[clsInsertPoint++] = me.focusedItemCls; + } + + // Chop back array to only what we've set + classes.length = clsInsertPoint; + + cellValues.tdCls = classes.join(' '); + + cellTpl.applyOut(cellValues, out); + + // Dereference objects since cellValues is a persistent var in the XTemplate's scope chain + cellValues.column = cellValues.record = null; + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ColumnConfiguration.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ColumnConfiguration.js @@ -0,0 +1,21 @@ +Ext.define("PartKeepr.Models.ColumnConfiguration", { + extend: "Ext.data.Model", + fields: [ + {name: 'dataIndex', type: 'string'}, + {name: 'text', type: 'string'}, + {name: 'hidden', type: 'boolean'}, + {name: 'flex', type: 'number'}, + {name: 'width', type: 'number'}, + {name: 'tooltip', type: 'string'}, + {name: 'widthMode', type: 'string'}, + {name: 'index', type: 'number'} + ], + hasMany: [ + { + name: 'renderers', + associationKey: 'renderers', + model: 'PartKeepr.Models.ColumnRendererConfiguration' + } + ] + +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ColumnRendererConfiguration.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ColumnRendererConfiguration.js @@ -0,0 +1,7 @@ +Ext.define("PartKeepr.Models.ColumnRendererConfiguration", { + extend: "Ext.data.Model", + fields: [ + {name: 'rtype', type: 'string'}, + {name: 'config', type: 'string'} + ] +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -69,6 +69,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraTreeModel.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/ModelStore.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.form.field.Checkbox.EXTJS-21886.js' + '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.view.Table-renderCell.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.field.Date-ISO8601.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> @@ -86,6 +87,16 @@ {% javascripts output='js/compiled/main2.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/i18n.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/CurrencyStore.js' + '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.Column-multipleRendererSupport.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Grid/Renderers/AbstractRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Grid/Renderers/RendererRegistry.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Grid/Renderers/IconRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Part/Renderers/AttachmentRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Part/Renderers/PartParameterRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/Renderers/ProjectPartParameterRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Part/Renderers/StockLevelRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Grid/Renderers/CurrencyRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Grid/Renderers/InternalIDRenderer.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/ReflectionFieldTreeModel.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/EntityQueryPanel.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/EntityPicker.js' @@ -99,10 +110,21 @@ '@PartKeeprFrontendBundle/Resources/public/js/Components/Importer/ImporterManyToOneConfiguration.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Importer/ImporterFieldConfiguration.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Importer/ImportFieldMatcherGrid.js' + '@PartKeeprFrontendBundle/Resources/public/js/Ext.ux/StoreMenu.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Grid/GridPresetButton.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/OperatorStore.js' + '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.header.Container-addMoreMenu.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Panel.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/Window.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumnListGrid.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/ColumProperties.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RenderersGrid.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/ColumnConfigurator/RendererConfigurationForm.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/FilterExpression.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/FilterExpressionWindow.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/ModelTreeMaker/ModelTreeMaker.js' + '@PartKeeprFrontendBundle/Resources/public/js/Models/ColumnRendererConfiguration.js' + '@PartKeeprFrontendBundle/Resources/public/js/Models/ColumnConfiguration.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/Blob.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/FileSaver.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/PagingToolbar.js' @@ -118,6 +140,8 @@ '@PartKeeprFrontendBundle/Resources/public/js/Data/store/TipOfTheDayHistoryStore.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/SystemPreferenceStore.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/UserProvidersStore.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/Renderers/MetaPartRenderer.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js' '@PartKeeprFrontendBundle/Resources/public/js/Models/ProjectReport.js' '@PartKeeprFrontendBundle/Resources/public/js/Models/ProjectReportList.js' '@PartKeeprFrontendBundle/Resources/public/js/Models/SystemInformationRecord.js' @@ -263,6 +287,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Components/StorageLocation/StorageLocationMultiAddDialog.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/StorageLocation/StorageLocationNavigation.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/ProjectReport.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statistics/StatisticsChart.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statistics/StatisticsChartPanel.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statistics/SummaryStatisticsPanel.js' diff --git a/src/PartKeepr/PartBundle/Services/PartService.php b/src/PartKeepr/PartBundle/Services/PartService.php @@ -176,16 +176,25 @@ class PartService if (count($results) > 1) { $result = call_user_func_array("array_intersect", $results); } else { - $result = $results[0]; + if (count($results) === 1) { + $result = $results[0]; + } else { + $result = []; + } } - $qb = $this->entityManager->createQueryBuilder(); - $qb->select("p")->from("PartKeeprPartBundle:Part", "p") - ->where( - $qb->expr()->in("p.id", ":result")); + if (count($result) > 0) { + $qb = $this->entityManager->createQueryBuilder(); + $qb->select("p")->from("PartKeeprPartBundle:Part", "p") + ->where( + $qb->expr()->in("p.id", ":result") + ); - $qb->setParameter(":result", $result); + $qb->setParameter(":result", $result); - return $qb->getQuery()->getResult(); + return $qb->getQuery()->getResult(); + } else { + return []; + } } }