/** * 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'] }); Ext.create('Ext.data.Store', { storeId: 'servicesInfoStore', fields: ['id', 'access_url', 'title', 'short_name', 'columns'], proxy: { type: 'ajax', url : '../../../generic_data/EpnTapData/services.json', reader: { type: 'json', root: 'services' } } }); Ext.create('Ext.data.ArrayStore', { storeId: 'metadataStore', fields: [ {name: 'dataproduct_type', type: 'string', mapping: 0}, {name: 'target_class', type: 'string', mapping: 1}, {name: 'target_name', type: 'string', mapping: 2}, {name: 'service_name', type: 'string', mapping: 3}, {name: 'nb_results', type: 'integer', mapping: 4}, {name: 'time_min', type: 'date', dateFormat: 'd/m/Y', mapping: 5}, {name: 'time_max', type: 'date', dateFormat: 'd/m/Y', mapping: 6} ], proxy: { type: 'ajax', url : '../../../generic_data/EpnTapData/metadata.json' } }); /** `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', 'nb_results', 'short_name', 'title', 'access_url'] }); /** `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'], }); // { // "im": { // dataproduct type // "star": { // target class // "sun": { // target name // "hfc1ar": [ // service // 948627, // "15/01/1996", // "09/10/2013" /* {'datasets': [ ['im', 'star', 'sun', 'hfc1ar', 948627, '15/01/1996', '09/10/2013'], ['im', 'star', 'sun', 'climso', 94863, '04/01/2015', '09/01/2017'] ] } */ // Ext.define("dataproductType", { // extend: 'Ext.data.Model', // fields: [ // 'id', 'name' // ], // hasMany: {model: 'TargetClass', name: 'target_class'}, // proxy: { // type: 'rest', // url : 'services.json', // reader: { // type: 'json', // root: 'users' // } // } // }); // // Ext.define("TargetClass", { // extend: 'Ext.data.Model', // fields: [ // 'id' // ], // hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'}, // belongsTo: 'dataproductType' // }); // // Ext.define("OrderItem", { // extend: 'Ext.data.Model', // fields: [ // 'id', 'price', 'quantity', 'order_id', 'product_id' // ], // // belongsTo: ['Order', {model: 'Product', associationKey: 'product'}] // }); // // Ext.define("Product", { // extend: 'Ext.data.Model', // fields: [ // 'id', 'name' // ], // // hasMany: 'OrderItem' // }); /** `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'], // width: 800, // height: 600, /** 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 = Ext.create('amdaDesktop.EpnTapModule'); /************ *** 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 an ExtJS Tooltip, on each row: - short name; - title; - access URL. A click on a service triggers `EpnTapModule.onServiceSelected()`, which basically fills `GranulesGrid` by the service granules. */ var createServicesGrid = function() { var epnTapServicesGrid = new Ext.grid.Panel({ id: 'epnTapServicesGrid', title: 'Services', store: Ext.data.StoreManager.lookup('servicesStore'), flex: 1, layout: 'fit', scroll: 'vertical', autoScroll: 'auto', // height: 410, columns: [ {text: 'Name', dataIndex: 'id', flex: 3}, {text: 'Results', dataIndex: 'nb_results', flex: 2} ], renderer: function(value, metadata, record) { return getExpandableImage(value, metadata,record); }, listeners: { 'cellclick': function(grid, td, cellIndex, record) { mod.onServiceSelected(record.data['id']); } }, viewConfig: { forceFit: true, getRowClass: function(record, index) { if (record.get('nb_results') === 0) { return 'disabled_row'; } } }, renderTo: Ext.getBody() }); Ext.create('Ext.tip.ToolTip', { target: epnTapServicesGrid.getView().el, delegate: epnTapServicesGrid.getView().itemSelector, trackMouse: true, listeners: { beforeshow: function updateTipBody(tooltip) { var service = epnTapServicesGrid.getView().getRecord(tooltip.triggerElement); var ttContent = '

' + service.get('short_name') + '

'; ttContent += '

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

'; ttContent += '

' + service.get('access_url') + '

'; tooltip.update(ttContent); } }, renderTo: Ext.getBody() }); return epnTapServicesGrid; }; /** 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. Each of these information are displayed in a specific rendering to improve user experience. For more information about these parameters, see https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters. Other informations are available through an ExtJS Tooltip on each row: - currently only the granule thumbnail, in full size. 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'; } }; var epnTapGranulesGrid = new Ext.grid.Panel({ id: 'epnTapGranulesGrid', title: 'Granules', store: Ext.data.StoreManager.lookup('granulesStore'), flex: 5, // layout: 'fit', // scroll: 'vertical', // autoScroll: 'auto', // height: 500, 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() }); Ext.create('Ext.tip.ToolTip', { target: epnTapGranulesGrid.getView().el, delegate: epnTapGranulesGrid.getView().itemSelector, trackMouse: true, listeners: { beforeshow: function updateTipBody(tooltip) { var thumb = epnTapGranulesGrid.getView().getRecord(tooltip.triggerElement).get('thumbnail_url'); tooltip.update(''); } }, renderTo: Ext.getBody() }); return epnTapGranulesGrid; }; /** 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', layout: { type: 'hbox' }, height: 530, items: [ createServicesGrid(), createGranulesGrid() ], listeners: { afterrender: function() { mod.initWindow(); } } }); }; /*************************** *** 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: 3, 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 = { id: 'epnTapWindow', layout: { type: 'vbox', align: 'stretch' }, items: [ createServiceFilterPanel(), createGridsPanel(), createInfoPanel() ] }; Ext.apply(this, Ext.apply(arguments, myConf)); } });