/** * Project: AMDA-NG * Name: EpnTapUI.js * @class amdaUI.EpnTapUI * @extends Ext.tab.Panel * @author Nathanael JOURDANE * 24/10/2016: file creation */ /** `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.ArrayStore', { 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: [ {name: 'id', type: 'string'}, {name: 'short_name', type: 'string'}, {name: 'res_title', type: 'string'}, {name: 'ivoid', type: 'string'}, {name: 'access_url', type: 'string'}, {name: 'table_name', type: 'string'}, {name: 'content_type', type: 'string'}, {name: 'creator_seq', type: 'string'}, {name: 'content_level', type: 'string'}, {name: 'reference_url', type: 'string'}, {name: 'created', type: 'date', dateFormat: 'c'}, {name: 'updated', type: 'date', dateFormat: 'c'}, {name: 'nb_results', type: 'integer'} ] }); /** `metadataStore`: [...] */ 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_id', 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' url : 'generic_data/EpnTapData/metadata.json' } }); /** `granulesStore`: An ExtJS Store containing the list of granules of the selected service (on `servicesGrid`), which match with the 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'], // 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) { /************ *** 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, height: 405, columns: [ {text: 'Name', dataIndex: 'short_name', flex: 1}, {text: '~Nb results', dataIndex: 'nb_results', flex: 1} ], renderer: function(value, metadata, record) { return getExpandableImage(value, metadata,record); }, 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 column_titles = {'res_title': 'Title', 'ivoid': 'ivoid', 'access_url': 'Access URL', 'table_name': 'Table name', 'content_type': 'content type', 'creator_seq': 'Creator', 'content_level': 'Content level', 'reference_url': 'reference URL', 'created': 'Created on', 'updated': 'Updated on'}; var service = epnTapServicesGrid.getView().getRecord(tooltip.triggerElement); var ttContent = '

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

'); } } }); return epnTapServicesGrid; }; /** Create `epnTapGranulesGrid`, an ExtJS grid containing the granules of the selected service in `epnTapServicesGrid`. 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) { productTypeDict = { 'im': 'Image', 'ma': 'Map', 'sp': 'Spectrum', 'ds': 'Dynamic spectrum', 'sc': 'Spectral cube', 'pr': 'Profile', 'vo': 'Volume', 'mo': 'Movie', 'cu': 'Cube', 'ts': 'Time series', 'ca': 'Catalogue', 'ci': 'Catalogue item' }; return (val in productTypeDict) ? '

' + productTypeDict[val] + '

' : '' + val + ''; }; var formatRender = function(val) { // A dictionnary used to associate the mimetype (i.e "application/fits") to a pretty printed word (i.e "fits"). mimetypeDict = { 'application/fits': 'fits', 'application/x-pds': 'pds', 'image/x-pds': 'pds', 'application/gml+xml': 'gml', 'application/json': 'json', 'application/octet-stream': 'bin, idl, envi or matlab', 'application/pdf': 'pdf', 'application/postscript': 'ps', 'application/vnd.geo+json': 'geojson', 'application/vnd.google-earth.kml+xml': 'kml', 'application/vnd.google-earth.kmz': 'kmz', 'application/vnd.ms-excel': 'xls', 'application/x-asdm': 'asdm', 'application/x-cdf': 'cdf', 'application/x-cdf-istp': 'cdf', 'application/x-cdf-pds4': 'cdf', 'application/x-cef1': 'cef1', 'application/x-cef2': 'cef2', 'application/x-directory': 'dir', 'application/x-fits-bintable': 'bintable', 'application/x-fits-euro3d': 'euro3d', 'application/x-fits-mef': 'mef', 'application/x-geotiff': 'geotiff', 'application/x-hdf': 'hdf', 'application/x-netcdf': 'nc', 'application/x-netcdf4': 'nc', 'application/x-tar': 'tar', 'application/x-tar-gzip': 'gtar', 'application/x-votable+xml': 'votable', 'application/x-votable+xml;content=datalink': 'votable', 'application/zip': 'zip', 'image/fits': 'fits', 'image/gif': 'gif', 'image/jpeg': 'jpeg', 'image/png': 'png', 'image/tiff': 'tiff', 'image/x-fits-gzip': 'fits', 'image/x-fits-hcompress': 'fits', 'text/csv': 'csv', 'text/html': 'html', 'text/plain': 'txt', 'text/tab-separated-values': 'tsv', 'text/xml': 'xml', 'video/mpeg': 'mpeg', 'video/quicktime': 'mov', 'video/x-msvideo': 'avi' }; return (val in mimetypeDict) ? 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: 4, height: 405, 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} ], 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(''); } } }); 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() ], renderTo: Ext.getBody() }); }; /*************************** *** 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', valueField: 'id', maxWidth: 100, displayField: 'name', name: 'productType', editable: false }); }; /** 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', maxWidth: 100, name: 'targetClass', editable: false }); }; /** 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, maxWidth: 100, mode: 'remote', minChars: 2, forceSelection: true }); }; /** 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' }, defaults: { margin: 5 }, items: [{ // Left part xtype : 'container', 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() ] }], renderTo: Ext.getBody() }); }; /** 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 }); }; /** 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' }, { id: 'epnTapPreviousPageBtn', text: '<', tooltip: 'Previous page' }, { 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' }, { id: 'epnTapLastPageBtn', text: '>|', tooltip: 'Last page' }] }); }; /******************* *** 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' }, renderTo: Ext.getBody() }); }; var myConf = { id: 'epnTapWindow', layout: { type: 'vbox', align: 'stretch' }, items: [ createServiceFilterPanel(), createGridsPanel(), createInfoPanel() ] }; Ext.apply(this, Ext.apply(arguments, myConf)); } });