/** * Project: AMDA-NG * Name: EpnTapUI.js * @class amdaUI.EpnTapUI * @extends Ext.tab.Panel * @author Nathanael JOURDANE * 24/10/2016: file creation */ // TODO: Déplacer les stores dans un fichier séparé dans js.stores ! /** `productTypesStore`: An ExtJS Store containing the list of the different data product types defined on all granules, on all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron script). This list is used to fill the `productTypeCB` combo box, which is initilized in `EpnTapModule` at the panel creation. - `id`: the data product type IDs, according to the EPN-TAP specification (see https://voparis-confluence.obspm.fr/pages/viewpage.action?pageId=1148225); - `name`: the data product name, according to the EPN-TAP specification (ibid). These IDs and names are hard-defined in the JSon file `generic_data/EpnTapData/dataproduct_types.json`. Notes: - if a granule contains a data product type which is not conform to the EPN-TAP definition (ibid), it is not displayed in this store and an information message is displayed on the JavaScript console during the panel creation. - if a data product type is not present in any of the granules from the EPN-TAP services, it is not present in this store. */ Ext.create('Ext.data.Store', { storeId:'productTypesStore', fields: ['id', 'name'] }); /** `targetClassesStore`: An ExtJS Store containing the list of the different target classes defined on all granules, on all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron script), which match with the selected data product type. This list is used to fill the `targetClassCB` combo box, which is updated by `EpnTapModule` each time a new product type is selected. - `id`: the target class in lowercase, with the underscore between each word; - `name`: the target class, capitalized with spaces between each word (done `EpnTapModule.prettify()`). */ Ext.create('Ext.data.Store', { storeId:'targetClassesStore', fields: ['id', 'name'] }); /** `targetNamesStore`: An ExtJS Store containing the list of the different target names defined on all granules, on all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron script), which match with the selected data product and target class. This list is used to fill the `targetNameCB` combo box, which is updated by `EpnTapModule` each time a new target class (or, by transitivity, product type) is selected. - `id`: the target name in lowercase, with the underscore between each word; - `name`: the target name, capitalized with spaces between each word (done `EpnTapModule.prettify()`). */ Ext.create('Ext.data.Store', { storeId: 'targetNamesStore', fields: ['id', 'name'] }); /** `servicesStore`: An ExtJS Store containing the list of the EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron script), which contains at least one granule matching with the granules filter (the selected data product type, target class and target name). This list is used to fill the `servicesGrid` table, which is updated by `EpnTapModule` each time a new target name (or, by transitivity, target class or product type) is selected. - `id`: the database name of the service, according to the `table_name` column from the `rr.res_table` in the registry database; - `nbResults`: the number of granules matching with the granules filter for this service; - `shortName`: the service short name, according to the `short_name` column from the `rr.resource` table in the registry database; - `title`: the service title, according to the `res_title` column from the `rr.resource` table in the registry database; - `accessURL`: the service access URL, according to the `access_url` column from the `rr.interface` table in the registry database. */ Ext.create('Ext.data.Store', { storeId: 'servicesStore', fields: ['id', 'nbResults', 'shortName', 'title', 'accessURL'] }); /** `granulesStore`: An ExtJS Store containing the list of granules of the selected service (on `servicesGrid`), which match with tge granules filter (the selected data product type, target class and target name). This list is used to fill the `granulesGrid` table, which is updated by `EpnTapModule` each time a new service is selected. - `num`: the line number, according to the order of the query response and the current page (see `currentPageLb`); - `dataproduct_type`: the dataproduct_type EPN-TAP parameter, as defined in https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters. - `target_name`: the target_name EPN-TAP parameter (ibid); - `time_min`: the time_min EPN-TAP parameter (ibid); - `time_max`: the time_max EPN-TAP parameter (ibid); - `access_format`: the access_format EPN-TAP parameter (ibid); - `granule_uid`: the granule_uid EPN-TAP parameter (ibid); - `access_estsize`: the access_estsize EPN-TAP parameter (ibid); - `access_url`: the access_url EPN-TAP parameter (ibid); - `thumbnail_url`: the thumbnail_url EPN-TAP parameter (ibid). */ Ext.create('Ext.data.Store', { storeId:'granulesStore', fields:['num', 'dataproduct_type', 'target_name', 'time_min', 'time_max', 'access_format', 'granule_uid', 'access_estsize', 'access_url', 'thumbnail_url'] }); /** `EpnTapUI`: The view of the AMDA EPN-TAP module, allowing the user to query and display granules information from EPN-TAP services. Note: The controller part of this module is defined in `js/app/controller/EpnTapModule`. */ Ext.define('amdaUI.EpnTapUI', { extend: 'Ext.panel.Panel', alias: 'widget.panelEpnTap', requires: ['amdaUI.IntervalUI'], /** Method constructor, which basically call the `init()` method to create the EpnTap panel. */ constructor: function(config) { this.init(config); this.callParent(arguments); }, /** Create all the EpnTapPanel UI elements, and apply the AMDA module `config` (which includes the created items). When the panel is correctly rendered, the panel triggers `EpnTapModule.onWindowLoaded()`. Note: All the UI elements creation are defined as functions in this init method and not as methods in order to make them private (ie. to avoid `EpnTapUI.createServicesGrid();`, which doesn't make sense). */ init: function(config) { var mod = myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.epntap.id); /************ *** Grids *** ************/ /** Create `epnTapServicesGrid`, an ExtJS grid containing the EPN-TAP services matching with the filter form (`serviceFilterPanel`). For each service, this grid displays: - the service name; - the number of granules matching with the filter. Other informations are available through the tooltip `serviceTooltip`. A click on a service triggers `EpnTapModule.onServiceSelected()`, which basically fills `GranulesGrid` by the service granules. */ var createServicesGrid = function() { return new Ext.grid.Panel({ id: 'epnTapServicesGrid', title: 'Services', store: Ext.data.StoreManager.lookup('servicesStore'), flex: 1, columns: [ {text: 'Name', dataIndex: 'id', flex: 3}, {text: 'Results', dataIndex: 'nbResults', flex: 2} ], renderer: function(value, metadata,record) { return getExpandableImage(value, metadata,record); }, listeners: { 'cellclick': function(grid, td, cellIndex, record) { mod.onServiceSelected(record.data['id']); } }, renderTo: Ext.getBody() }); }; /** Create `epnTapServiceTooltip`, an ExtJS tooltip for the `servicesGrid` rows, in order to display additional information for each service, such as: - short name; - title; - access URL. */ var createServiceTooltip = function() { return new Ext.tip.ToolTip({ id: 'epnTapServiceTooltip', target: Ext.getCmp('epnTapServicesGrid').getView().el, delegate: Ext.getCmp('epnTapServicesGrid').getView().itemSelector, trackMouse: true, listeners: { beforeshow: function updateTipBody(tooltip) { var service = Ext.getCmp('servicesGrid').getView().getRecord(tooltip.triggerElement); var ttContent = '

' + service.get('shortName') + '

'; ttContent += '

' + service.get('title') + '

'; ttContent += '

' + service.get('accessURL') + '

'; tooltip.update(ttContent); } }, renderTo: Ext.getBody() }); }; /** Create `epnTapGranulesGrid`, an ExtJS grid containing the granules of the selected service in `epnTapServiceGrid`. For each granule, this grid displays: - the row number; - the dataproduct type; - the target name; - the min and max times; - the format; - the UID (granule identifier); - the estimated size; - the URL; - the thumbnail. For more information about these parameters, see https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters. Each of these information are displayed in a specific rendering to improve user experience. Other informations are available through the tooltip `granuleTooltip`. A click on a granule triggers `EpnTapModule.onGranuleSelected()`. */ var createGranulesGrid = function() { var txtRender = function(val) { return '

' + val + '

'; }; var linkRender = function(val) { return 'data'; }; var imgRender = function(val) { return ''; }; var dptRender = function(val) { return (val in mod.productTypeDict) ? '

' + mod.productTypeDict[val] + '

' : '' + val + ''; }; var formatRender = function(val) { return (val in mod.mimetypeDict) ? mod.mimetypeDict[val] : '' + val + ''; }; var sizeRender = function(val) { var size = parseInt(val); if (isNaN(size)) { return ''; } else if (size >= 1024*1024) { return (size/(1024*1024)).toPrecision(3) + 'Go'; } else if (size >= 1024) { return (size/1024).toPrecision(3) + 'Mo'; } else { return size + 'Ko'; } }; return new Ext.grid.Panel({ id: 'epnTapGranulesGrid', title: 'Granules', store: Ext.data.StoreManager.lookup('granulesStore'), flex: 5, columns: [ { text: 'Num', dataIndex: 'num', flex: 1, renderer: txtRender }, { text: 'Type', dataIndex: 'dataproduct_type', flex: 2, renderer: dptRender }, { text: 'Target', dataIndex: 'target_name', flex: 2, renderer: txtRender }, { text: 'Time min', dataIndex: 'time_min', flex: 2, renderer: txtRender }, { text: 'Time max', dataIndex: 'time_max', flex: 2, renderer: txtRender }, { text: 'Format', dataIndex: 'access_format', flex: 2, renderer: formatRender }, { text: 'uid', dataIndex: 'granule_uid', flex: 2, renderer: txtRender }, { text: 'Size', dataIndex: 'access_estsize', flex: 1, renderer: sizeRender }, { text: 'URL', dataIndex: 'access_url', flex: 1, renderer: linkRender }, { text: 'Thumb.', dataIndex: 'thumbnail_url', flex: 1, renderer: imgRender} ], listeners: { 'cellclick': function(grid, td, cellIndex, record) { mod.onGranuleSelected(record.data['id']); } }, renderTo: Ext.getBody() }); }; /** Create `epnTapGranuleTooltip`, an ExtJS tooltip for the `granulesGrid` rows, in order to display additional information for each granule which is currently only the granule thumbnail, in full size. */ var createGranuleTooltip = function() { return new Ext.tip.ToolTip({ id: 'epnTapGranuleTooltip', target: Ext.getCmp('granulesGrid').getView().el, delegate: Ext.getCmp('granulesGrid').getView().itemSelector, trackMouse: true, listeners: { beforeshow: function updateTipBody(tooltip) { var thumb = Ext.getCmp('granulesGrid').getView().getRecord(tooltip.triggerElement).get('thumbnail_url'); tooltip.update(''); } }, renderTo: Ext.getBody() }); }; /** Create `epnTapGridsPanel`, an ExtJS Panel, containing `epnTapServicesGrid` and `epnTapGranulesGrid`. After the rendering of the grids, it triggers `epnTapModule.onWindowLoaded()`, which basically fill `epnTapServicesGrid` for the first time. */ var createGridsPanel = function() { var self = this; return new Ext.panel.Panel({ id: 'epnTapGridsPanel', region: 'center', height: 350, layout: { type: 'hbox', pack: 'start', align: 'stretch' }, items: [ createServicesGrid(), createGranulesGrid() ], listeners: { afterrender: function() { mod.onWindowLoaded(self); } } }); }; /*************************** *** Service filter panel *** ***************************/ /** Create `epnTapProductTypeCB`, an ExtJS ComboBox, containing a list of product types as defined in `epnTapProductTypesStore`, which is initilized by `EpnTapModule`. The selection of a produt type triggers `EpnTapModule.onProductTypeCBChanged()`, which basically update `epnTapTargetClassCB` and `epnTapGranulesGrid`. */ var createProductTypeCB = function() { return new Ext.form.field.ComboBox({ id: 'epnTapProductTypeCB', fieldLabel: 'Product type', store: Ext.data.StoreManager.lookup('productTypesStore'), queryMode: 'local', displayField: 'name', valueField: 'id', name: 'productType', editable: false, listeners: { 'select': function(combo) { mod.onProductTypeCBChanged(combo.value); } } }); }; /** Create `epnTapTargetClassCB`, an ExtJS ComboBox, containing a list of target classes corresponding to the selected product type, as defined in `targetClassesStore`, which is initilized by `EpnTapModule`. The selection of a target class triggers the `EpnTapModule.onTargetClassCBChanged()`, which basically updates `targetNameCB` and `granulesGrid`. */ var createTargetClassCB = function() { return new Ext.form.field.ComboBox({ id: 'epnTapTargetClassCB', fieldLabel: 'Target class', store: Ext.data.StoreManager.lookup('targetClassesStore'), queryMode: 'local', displayField: 'name', valueField: 'id', name: 'targetClass', editable: false, listeners: { 'select': function(combo) { mod.onTargetClassCBChanged(combo.value); } } }); }; /** Create `epnTapTargetNameCB`, an ExtJS ComboBox, containing a list of target names corresponding to the selected target class, as defined in `targetNamesStore`, which is initilized by `EpnTapModule`. The selection of a target name triggers `EpnTapModule.onTargetNameCBChanged()`, which basically updates `granulesGrid`. */ var createTargetNameCB = function() { return new Ext.form.field.ComboBox({ id: 'epnTapTargetNameCB', fieldLabel: 'Target name', store: Ext.data.StoreManager.lookup('targetNamesStore'), queryMode: 'local', displayField: 'name', valueField: 'id', name: 'targetName', triggerAction: 'all', typeAhead: true, mode: 'remote', minChars: 2, forceSelection: true, listeners: { 'select': function(combo) { mod.onTargetNameCBChanged(combo.value); } } }); }; /** Create `epnTapServiceFilterPanel`, an ExtJS Panel containing two containers: - the left container, containing the combo boxes (for product type, target class and target name) and the navigation panel; - the right container, containing the time selector. */ var createServiceFilterPanel = function() { return new Ext.panel.Panel({ id: 'epnTapServiceFilterPanel', region : 'north', layout: { type: 'hbox', pack: 'start', align: 'stretch' }, defaults: { margin: 5 }, items: [{ // Left part xtype : 'container', layout: 'form', flex: 2, items: [ createProductTypeCB(), createTargetClassCB(), createTargetNameCB(), { xtype: 'panel', layout: { type: 'hbox', pack: 'start', align: 'stretch' }, border: false, items: [ createRowPerPageNf(), createNavigationPanel() ] } ] }, { // Right part xtype : 'form', id: 'epnTapIntervalSelector', layout: 'form', border: 'false', flex: 2, items: [ createTimeSelector() ] }] }); }; /** Create `epnTapTimeSelector`, an IntervalUI object, allowing the user to select a time interval (by filling two dates and/or a duration). See `js/app/views/IntervalUI.js` for more information about this component. */ var createTimeSelector = function() { return Ext.create('amdaUI.IntervalUI', { id: 'epnTapTimeSelector', durationLimit: 99999 }); }; /*********************** *** Navigation panel *** ***********************/ /** Create `epnTapRowsPerPageNf`, a ExtJS Number field, allowing the user to select the number of rows to display in `epnTapGranulesGrid`. When a new number is entered, it triggers `EpnTapModule.onRowsPerPageChanged()`. */ var createRowPerPageNf = function() { return new Ext.form.field.Number({ id: 'epnTapRowsPerPageNf', fieldLabel: 'Rows per page', margin: '4 0 4 0', width: 160, height: 20, value: 20, minValue: 1, maxValue: 2000, listeners: { 'change': function(rowPerPageNf, newValue) { mod.onRowsPerPageChanged(newValue); } } }); }; /** Create `epnTapNavigationPanel`, an ExtJSPanel containing several elements in order to navigate through the different pages of the query result. If the number of results is highter than the `epnTapRowsPerPageNf` field value, the result appears to be displayed in different pages. This panel is used to select and display the page number, mainly with these following elements: - `epnTapFirstPageBtn`: an ExtJS Button, used to come back to the first page of result, handling `EpnTapModule.onFirstPageBtnClicked()`; - `epnTapPreviousPageBtn`: an ExtJS Button, used to come back to the previous page of result, handling `EpnTapModule.onPreviousPageBtnClicked()`; - `epnTapCurrentPageLb`: an ExtJS Label, displaying the actual current page; TODO: use a Number field instead! - `epnTapTotalPagesLb`: an ExtJS Label, displaying the total page number of results (according to the `epnTapRowsPerPageNf` field value); - `epnTapNextPageBtn`: an ExtJS Button, used to go to the next page of result, handling `EpnTapModule.onNextPageBtnClicked()`; - `epnTapLastPageBtn`: an ExtJS Button, used to come back to the last page of result, handling `EpnTapModule.onLastPageBtnClicked()`. Note: Pages are not actually a "graphical filter": when the user navigate through the pages, a new query is send to the server with the corresponding range, which improves the response time on large requests. */ var createNavigationPanel = function() { return new Ext.panel.Panel({ name: 'epnTapNavigationPanel', border: false, margin: '2 0 2 50', defaults: { margin: '0 5 0 5', width: 20, xtype: 'button', disabled: true}, items: [{ xtype: 'label', text: 'Page:' }, { id: 'epnTapFirstPageBtn', text: '|<', tooltip: 'First page', handler: function() { mod.onFirstPageBtnClicked(); } }, { id: 'epnTapPreviousPageBtn', text: '<', tooltip: 'Previous page', handler: function() { mod.onPreviousPageBtnClicked(); } }, { xtype: 'label', id: 'epnTapCurrentPageLb', tooltip: 'Current page', text: '-' }, { xtype: 'label', text: '/' }, { xtype: 'label', id: 'epnTapTotalPagesLb', tooltip: 'Total pages', text: '-' }, { id: 'epnTapNextPageBtn', text: '>', tooltip: 'Next page', handler: function() { mod.onNextPageBtnClicked(); } }, { id: 'epnTapLastPageBtn', text: '>|', tooltip: 'Last page', handler: function() { mod.onLastPageBtnClicked(); } }] }); }; /******************* *** Other panels *** *******************/ /** Create `epnTapInfoPanel`, an ExtJS Panel used to display a brief user guide about how to use this module. */ var createInfoPanel = function() { return new Ext.panel.Panel({ id: 'epnTapInfoPanel', region: 'south', title: 'Information', collapsible: true, flex: 0, height: 100, autoHide: false, bodyStyle: 'padding: 5px', iconCls: 'icon-information', loader: { autoLoad: true, url: helpDir + 'epnTapHOWTO' } }); }; // TODO tester ceci: // config.title = 'EPN-TAP'; // config.layout = 'border'; // config.items = [ // createServiceFilterPanel(config.targetName), // createGridsPanel(), // createInfoPanel() // ]; // Ext.apply(this, Ext.apply(arguments, config)); var myConf = { width: 1000, height: 550, layout: 'border', items: [ createServiceFilterPanel(), createGridsPanel(), createInfoPanel() ] }; Ext.apply(this, Ext.apply(arguments, myConf)); } });