Commit 428eb66e9a862ce84908e6bc08a0d11403d813ae

Authored by Nathanael Jourdane
1 parent 80d30e39

Use JS standard coding convention

Showing 2 changed files with 758 additions and 758 deletions   Show diff stats
js/app/controllers/EpnTapModule.js
1 /** 1 /**
2 * Project : AMDA-NG 2 * Project : AMDA-NG
3 - * Name : EpnTapModule.js 3 + * Name : EpnTapModule.js
4 * @class amdaDesktop.EpnTapModule 4 * @class amdaDesktop.EpnTapModule
5 * @extends amdaDesktop.AmdaModule 5 * @extends amdaDesktop.AmdaModule
6 * @brief EpnTap Module controller definition 6 * @brief EpnTap Module controller definition
@@ -9,175 +9,175 @@ @@ -9,175 +9,175 @@
9 9
10 Ext.define('amdaDesktop.EpnTapModule', { 10 Ext.define('amdaDesktop.EpnTapModule', {
11 11
12 - extend: 'amdaDesktop.AmdaModule',  
13 - requires: ['amdaUI.EpnTapUI'],  
14 - contentId : 'EpnTapUI',  
15 -  
16 - /** The alias name of the module view. */  
17 - // uiType: 'panelEpnTap',  
18 - uiType : 'panelEpnTap',  
19 -  
20 - /** The text displayed on the *help button* tooltip. */  
21 - helpTitle: 'Help on EPN-TAP Module',  
22 -  
23 - /** The name of the documentation file related to the module. */  
24 - helpFile : 'epnTapHelp',  
25 -  
26 - /**  
27 - Module initialisation. Called the first time that the user open the epnTap module.  
28 - */  
29 - init: function() {  
30 - this.launcher = {  
31 - text: this.title,  
32 - iconCls: this.icon,  
33 - handler: this.createWindow,  
34 - scope: this  
35 - };  
36 - },  
37 -  
38 - /**  
39 - Called each time the epntap module is loaded.  
40 - - `target`: an array of 3 values: [target_name, dataproduct_type]; or null.  
41 - */  
42 - loadTarget: function(filter) {  
43 - this.aquireElements();  
44 - this.addListeners();  
45 -  
46 - this.servicesStore.each(function(record) {  
47 - record.set('nb_results', -1);  
48 - }, this);  
49 - this.granulesStore.removeAll();  
50 -  
51 - if(filter) {  
52 - this.targetNameCB.setRawValue(filter['targetName']);  
53 - this.productTypeCB.select(filter['productType']);  
54 - this.getServices();  
55 - }  
56 - },  
57 -  
58 - aquireElements: function() {  
59 - // UI elements  
60 - this.servicesGrid = Ext.getCmp('epnTapServicesGrid');  
61 - this.granulesGrid = Ext.getCmp('epnTapGranulesGrid');  
62 - this.productTypeCB = Ext.getCmp('epnTapProductTypeCB');  
63 - this.targetNameCB = Ext.getCmp('epnTapTargetNameCB');  
64 - this.timeSelector = Ext.getCmp('epnTapTimeSelector');  
65 - this.getBtn = Ext.getCmp('epnTapGetBtn');  
66 -  
67 - // stores elements  
68 - this.servicesStore = Ext.data.StoreManager.lookup('servicesStore');  
69 - this.granulesStore = Ext.data.StoreManager.lookup('granulesStore');  
70 - this.productTypesStore = Ext.data.StoreManager.lookup('productTypesStore');  
71 - this.targetNamesStore = Ext.data.StoreManager.lookup('targetNamesStore');  
72 - },  
73 -  
74 - addListeners: function() {  
75 - this.targetNameCB.on('change', function() {  
76 - this.updateGetBtnStatus();  
77 - }, this);  
78 -  
79 - this.productTypeCB.on('change', function() {  
80 - this.updateGetBtnStatus();  
81 - }, this);  
82 -  
83 - this.servicesGrid.on('cellclick', function(grid, td, cellIndex, record) {  
84 - this.onServiceSelected(record);  
85 - }, this);  
86 -  
87 - this.getBtn.on('click', function() {  
88 - this.getServices();  
89 - }, this);  
90 - },  
91 -  
92 - /**********************  
93 - *** Utils functions ***  
94 - **********************/  
95 -  
96 - updateGetBtnStatus: function() {  
97 - var shouldEnabled = this.targetNameCB.rawValue.length > 0 && this.productTypeCB.rawValue.length > 0;  
98 - if(shouldEnabled) {  
99 - this.getBtn.enable();  
100 - } else {  
101 - this.getBtn.disable();  
102 - }  
103 - },  
104 -  
105 - /*************  
106 - *** Events ***  
107 - *************/  
108 -  
109 - /**  
110 - Trigerred when the 'Get results' button is clicked.  
111 - */  
112 - getServices: function() {  
113 - this.granulesStore.removeAll();  
114 - var targetName = this.targetNameCB.rawValue;  
115 - var productTypes = this.productTypeCB.value.join(';');  
116 - var timeMin = Ext.Date.format(this.timeSelector.getStartTime(), 'd/m/Y H:i:s'); // start time  
117 - var timeMax = Ext.Date.format(this.timeSelector.getStopTime(), 'd/m/Y H:i:s'); // stop time  
118 -  
119 - loadMask.show();  
120 - this.servicesStore.each(function(record) {  
121 - // TODO: use store.load() method instead and add 'success' and 'enable' columns in the store  
122 - Ext.Ajax.request({  
123 - url: 'php/epntap.php',  
124 - method: 'GET',  
125 - headers: {'Content-Type': 'application/json'},  
126 - params: {  
127 - 'action': 'getNbResults',  
128 - 'serviceId': record.data['id'],  
129 - 'url': record.data['access_url'],  
130 - 'tableName': record.data['table_name'],  
131 - 'targetNames': targetName,  
132 - 'productTypes': productTypes,  
133 - 'timeMin': timeMin,  
134 - 'timeMax': timeMax  
135 - },  
136 - // timeout: 3000,  
137 - success: function(response, options) {  
138 - var record = this.servicesStore.getById(options.params['serviceId']);  
139 - var responseObj = Ext.decode(response.responseText);  
140 - this.updateService(record, responseObj['success'] ? responseObj['data'] : -2, responseObj['msg']);  
141 - },  
142 - failure: function(response, options) {  
143 - var record = this.servicesStore.getById(options.params['serviceId']);  
144 - this.updateService(record, -1, response.statusText);  
145 - },  
146 - scope: this  
147 - });  
148 - }, this);  
149 - },  
150 -  
151 - /**  
152 - Update the nb_result field of the services store (see `EpnTapUI.servicesStore`), according to the field values in `serviceFilterPanel`.  
153 - */  
154 - updateService: function(record, nbRes, info) {  
155 - record.set('nb_results', nbRes);  
156 - record.set('info', info);  
157 - this.servicesStore.sort();  
158 - loadMask.hide();  
159 - },  
160 -  
161 - /**  
162 - Trigerred when a row is clicked in `servicesGrid` table (see `EpnTapUI.createServicesGrid()`). Among other things,  
163 - send a new query and fill `granulesGrid`.  
164 - */  
165 - onServiceSelected: function(record) {  
166 - this.granulesStore.selectedService = record.data.id;  
167 - Ext.Ajax.suspendEvent('requestexception');  
168 - Ext.Ajax.abortAll();  
169 -  
170 - if(record.get('nb_results') > 0) {  
171 - this.granulesStore.removeAll();  
172 - this.granulesStore.load({  
173 - callback: function (records, operation, success) {  
174 - Ext.Ajax.resumeEvents('requestexception');  
175 - // console.log(Ext.decode(operation.response.responseText));  
176 - },  
177 - start: 0,  
178 - limit: this.granulesStore.pageSize,  
179 - scope: this  
180 - });  
181 - }  
182 - }  
183 -}); 12 + extend: 'amdaDesktop.AmdaModule',
  13 + requires: ['amdaUI.EpnTapUI'],
  14 + contentId: 'EpnTapUI',
  15 +
  16 + /** The alias name of the module view. */
  17 + // uiType: 'panelEpnTap',
  18 + uiType: 'panelEpnTap',
  19 +
  20 + /** The text displayed on the *help button* tooltip. */
  21 + helpTitle: 'Help on EPN-TAP Module',
  22 +
  23 + /** The name of the documentation file related to the module. */
  24 + helpFile: 'epnTapHelp',
  25 +
  26 + /**
  27 + Module initialisation. Called the first time that the user open the epnTap module.
  28 + */
  29 + init: function () {
  30 + this.launcher = {
  31 + text: this.title,
  32 + iconCls: this.icon,
  33 + handler: this.createWindow,
  34 + scope: this
  35 + }
  36 + },
  37 +
  38 + /**
  39 + Called each time the epntap module is loaded.
  40 + - `target`: an array of 3 values: [target_name, dataproduct_type]; or null.
  41 + */
  42 + loadTarget: function (filter) {
  43 + this.aquireElements()
  44 + this.addListeners()
  45 +
  46 + this.servicesStore.each(function (record) {
  47 + record.set('nb_results', -1)
  48 + }, this)
  49 + this.granulesStore.removeAll()
  50 +
  51 + if (filter) {
  52 + this.targetNameCB.setRawValue(filter['targetName'])
  53 + this.productTypeCB.select(filter['productType'])
  54 + this.getServices()
  55 + }
  56 + },
  57 +
  58 + aquireElements: function () {
  59 + // UI elements
  60 + this.servicesGrid = Ext.getCmp('epnTapServicesGrid')
  61 + this.granulesGrid = Ext.getCmp('epnTapGranulesGrid')
  62 + this.productTypeCB = Ext.getCmp('epnTapProductTypeCB')
  63 + this.targetNameCB = Ext.getCmp('epnTapTargetNameCB')
  64 + this.timeSelector = Ext.getCmp('epnTapTimeSelector')
  65 + this.getBtn = Ext.getCmp('epnTapGetBtn')
  66 +
  67 + // stores elements
  68 + this.servicesStore = Ext.data.StoreManager.lookup('servicesStore')
  69 + this.granulesStore = Ext.data.StoreManager.lookup('granulesStore')
  70 + this.productTypesStore = Ext.data.StoreManager.lookup('productTypesStore')
  71 + this.targetNamesStore = Ext.data.StoreManager.lookup('targetNamesStore')
  72 + },
  73 +
  74 + addListeners: function () {
  75 + this.targetNameCB.on('change', function () {
  76 + this.updateGetBtnStatus()
  77 + }, this)
  78 +
  79 + this.productTypeCB.on('change', function () {
  80 + this.updateGetBtnStatus()
  81 + }, this)
  82 +
  83 + this.servicesGrid.on('cellclick', function (grid, td, cellIndex, record) {
  84 + this.onServiceSelected(record)
  85 + }, this)
  86 +
  87 + this.getBtn.on('click', function () {
  88 + this.getServices()
  89 + }, this)
  90 + },
  91 +
  92 + /**********************
  93 + *** Utils functions ***
  94 + **********************/
  95 +
  96 + updateGetBtnStatus: function () {
  97 + var shouldEnabled = this.targetNameCB.rawValue.length > 0 && this.productTypeCB.rawValue.length > 0
  98 + if (shouldEnabled) {
  99 + this.getBtn.enable()
  100 + } else {
  101 + this.getBtn.disable()
  102 + }
  103 + },
  104 +
  105 + /*************
  106 + *** Events ***
  107 + *************/
  108 +
  109 + /**
  110 + Trigerred when the 'Get results' button is clicked.
  111 + */
  112 + getServices: function () {
  113 + this.granulesStore.removeAll()
  114 + var targetName = this.targetNameCB.rawValue
  115 + var productTypes = this.productTypeCB.value.join(';')
  116 + var timeMin = Ext.Date.format(this.timeSelector.getStartTime(), 'd/m/Y H:i:s') // start time
  117 + var timeMax = Ext.Date.format(this.timeSelector.getStopTime(), 'd/m/Y H:i:s') // stop time
  118 +
  119 + loadMask.show()
  120 + this.servicesStore.each(function (record) {
  121 + // TODO: use store.load() method instead and add 'success' and 'enable' columns in the store
  122 + Ext.Ajax.request({
  123 + url: 'php/epntap.php',
  124 + method: 'GET',
  125 + headers: {'Content-Type': 'application/json'},
  126 + params: {
  127 + 'action': 'getNbResults',
  128 + 'serviceId': record.data['id'],
  129 + 'url': record.data['access_url'],
  130 + 'tableName': record.data['table_name'],
  131 + 'targetNames': targetName,
  132 + 'productTypes': productTypes,
  133 + 'timeMin': timeMin,
  134 + 'timeMax': timeMax
  135 + },
  136 + // timeout: 3000,
  137 + success: function (response, options) {
  138 + var record = this.servicesStore.getById(options.params['serviceId'])
  139 + var responseObj = Ext.decode(response.responseText)
  140 + this.updateService(record, responseObj['success'] ? responseObj['data'] : -2, responseObj['msg'])
  141 + },
  142 + failure: function (response, options) {
  143 + var record = this.servicesStore.getById(options.params['serviceId'])
  144 + this.updateService(record, -1, response.statusText)
  145 + },
  146 + scope: this
  147 + })
  148 + }, this)
  149 + },
  150 +
  151 + /**
  152 + Update the nb_result field of the services store (see `EpnTapUI.servicesStore`), according to the field values in `serviceFilterPanel`.
  153 + */
  154 + updateService: function (record, nbRes, info) {
  155 + record.set('nb_results', nbRes)
  156 + record.set('info', info)
  157 + this.servicesStore.sort()
  158 + loadMask.hide()
  159 + },
  160 +
  161 + /**
  162 + Trigerred when a row is clicked in `servicesGrid` table (see `EpnTapUI.createServicesGrid()`). Among other things,
  163 + send a new query and fill `granulesGrid`.
  164 + */
  165 + onServiceSelected: function (record) {
  166 + this.granulesStore.selectedService = record.data.id
  167 + Ext.Ajax.suspendEvent('requestexception')
  168 + Ext.Ajax.abortAll()
  169 +
  170 + if (record.get('nb_results') > 0) {
  171 + this.granulesStore.removeAll()
  172 + this.granulesStore.load({
  173 + callback: function (records, operation, success) {
  174 + Ext.Ajax.resumeEvents('requestexception')
  175 + // console.log(Ext.decode(operation.response.responseText));
  176 + },
  177 + start: 0,
  178 + limit: this.granulesStore.pageSize,
  179 + scope: this
  180 + })
  181 + }
  182 + }
  183 +})
js/app/views/EpnTapUI.js
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 * 24/10/2016: file creation 7 * 24/10/2016: file creation
8 */ 8 */
9 9
10 -Ext.require(['Ext.grid.plugin.BufferedRenderer']); 10 +Ext.require(['Ext.grid.plugin.BufferedRenderer'])
11 /** 11 /**
12 `productTypesStore`: An ExtJS Store containing the list of the different data product types defined on all granules, on 12 `productTypesStore`: An ExtJS Store containing the list of the different data product types defined on all granules, on
13 all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron 13 all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron
@@ -16,7 +16,7 @@ script). @@ -16,7 +16,7 @@ script).
16 This list is used to fill the `productTypeCB` combo box, which is initilized in `EpnTapModule` at the panel creation. 16 This list is used to fill the `productTypeCB` combo box, which is initilized in `EpnTapModule` at the panel creation.
17 17
18 - `id`: the data product type IDs, according to the EPN-TAP specification (see 18 - `id`: the data product type IDs, according to the EPN-TAP specification (see
19 - https://voparis-confluence.obspm.fr/pages/viewpage.action?pageId=1148225); 19 + https://voparis-confluence.obspm.fr/pages/viewpage.action?pageId=1148225);
20 - `name`: the data product name, according to the EPN-TAP specification (ibid). 20 - `name`: the data product name, according to the EPN-TAP specification (ibid).
21 21
22 These IDs and names are hard-defined in the JSon file `generic_data/EpnTapData/dataproduct_types.json`. 22 These IDs and names are hard-defined in the JSon file `generic_data/EpnTapData/dataproduct_types.json`.
@@ -28,26 +28,26 @@ in this store and an information message is displayed on the JavaScript console @@ -28,26 +28,26 @@ in this store and an information message is displayed on the JavaScript console
28 store. 28 store.
29 */ 29 */
30 Ext.create('Ext.data.Store', { 30 Ext.create('Ext.data.Store', {
31 - storeId: 'productTypesStore',  
32 - autoLoad: true,  
33 - fields: ['id', 'name', 'desc'],  
34 - data: [  
35 - {'id': 'all', 'name': '--All--', 'desc': 'Select all produt types.'},  
36 - {'id': 'clear', 'name': '--Clear--', 'desc': 'Clear the selection.'},  
37 - {'id': 'im', 'name': 'Image', 'desc': '2D series of values depending on 2 spatial axes, with measured parameters.'},  
38 - {'id': 'ma', 'name': 'Map', 'desc': '2D series of values depending on 2 spatial axes, with derived parameters.'},  
39 - {'id': 'sp', 'name': 'Spectrum', 'desc': '1D series of values depending on a spectral axis (or Frequency, Energy, Mass,...).'},  
40 - {'id': 'ds', 'name': 'Dynamic spectrum', 'desc': '2D series of values depending on time and on a spectral axis (Frequency, Energy, Mass,...), FoV is homogeneous.'},  
41 - {'id': 'sc', 'name': 'Spectral cube', 'desc': '3D series of values depending on 2 spatial axes and on a spectral axis (Frequency, Energy, Mass,..).'},  
42 - {'id': 'pr', 'name': 'Profile', 'desc': '1D series of values depending on a spatial axis.'},  
43 - {'id': 'vo', 'name': 'Volume', 'desc': '3D series of values depending on 3 spatial axes (spatial coordinates or tabulated values in a volumic grid).'},  
44 - {'id': 'mo', 'name': 'Movie', 'desc': '3D series of values depending on 2 spatial axes and on time.'},  
45 - // {'id': 'cu', 'name': 'Cube', 'desc': '.'},  
46 - {'id': 'ts', 'name': 'Time series', 'desc': '1D series of values depending on time.'},  
47 - {'id': 'ca', 'name': 'Catalogue', 'desc': '1D list of elements.'},  
48 - {'id': 'ci', 'name': 'Catalogue item', 'desc': '0D list of elements.'}  
49 - ]  
50 -}); 31 + storeId: 'productTypesStore',
  32 + autoLoad: true,
  33 + fields: ['id', 'name', 'desc'],
  34 + data: [
  35 + {'id': 'all', 'name': '--All--', 'desc': 'Select all produt types.'},
  36 + {'id': 'clear', 'name': '--Clear--', 'desc': 'Clear the selection.'},
  37 + {'id': 'im', 'name': 'Image', 'desc': '2D series of values depending on 2 spatial axes, with measured parameters.'},
  38 + {'id': 'ma', 'name': 'Map', 'desc': '2D series of values depending on 2 spatial axes, with derived parameters.'},
  39 + {'id': 'sp', 'name': 'Spectrum', 'desc': '1D series of values depending on a spectral axis (or Frequency, Energy, Mass,...).'},
  40 + {'id': 'ds', 'name': 'Dynamic spectrum', 'desc': '2D series of values depending on time and on a spectral axis (Frequency, Energy, Mass,...), FoV is homogeneous.'},
  41 + {'id': 'sc', 'name': 'Spectral cube', 'desc': '3D series of values depending on 2 spatial axes and on a spectral axis (Frequency, Energy, Mass,..).'},
  42 + {'id': 'pr', 'name': 'Profile', 'desc': '1D series of values depending on a spatial axis.'},
  43 + {'id': 'vo', 'name': 'Volume', 'desc': '3D series of values depending on 3 spatial axes (spatial coordinates or tabulated values in a volumic grid).'},
  44 + {'id': 'mo', 'name': 'Movie', 'desc': '3D series of values depending on 2 spatial axes and on time.'},
  45 + // {'id': 'cu', 'name': 'Cube', 'desc': '.'},
  46 + {'id': 'ts', 'name': 'Time series', 'desc': '1D series of values depending on time.'},
  47 + {'id': 'ca', 'name': 'Catalogue', 'desc': '1D list of elements.'},
  48 + {'id': 'ci', 'name': 'Catalogue item', 'desc': '0D list of elements.'}
  49 + ]
  50 +})
51 51
52 /** 52 /**
53 `targetNamesStore`: An ExtJS Store containing the list of the different target names defined on all granules, on 53 `targetNamesStore`: An ExtJS Store containing the list of the different target names defined on all granules, on
@@ -61,23 +61,23 @@ This list is used to fill the `targetNameCB` combo box, which is updated by `Epn @@ -61,23 +61,23 @@ This list is used to fill the `targetNameCB` combo box, which is updated by `Epn
61 - `name`: the target name, capitalized with spaces between each word (done `EpnTapModule.prettify()`). 61 - `name`: the target name, capitalized with spaces between each word (done `EpnTapModule.prettify()`).
62 */ 62 */
63 Ext.create('Ext.data.Store', { 63 Ext.create('Ext.data.Store', {
64 - storeId: 'targetNamesStore',  
65 - fields: ['id', 'text', 'name', 'type', 'parent', 'aliases'],  
66 - proxy: {  
67 - type: 'ajax',  
68 - url: 'php/epntap.php',  
69 - extraParams: { action: 'resolver' }  
70 - },  
71 - errorDisplayed: false,  
72 - listeners: {  
73 - load: function(store, records, successful) {  
74 - if(!successful && !store.errorDisplayed) {  
75 - Ext.Msg.alert('Error', 'Can not load results from the resolver. Please enter target names manually.');  
76 - store.errorDisplayed = true;  
77 - }  
78 - }  
79 - }  
80 -}); 64 + storeId: 'targetNamesStore',
  65 + fields: ['id', 'text', 'name', 'type', 'parent', 'aliases'],
  66 + proxy: {
  67 + type: 'ajax',
  68 + url: 'php/epntap.php',
  69 + extraParams: { action: 'resolver' }
  70 + },
  71 + errorDisplayed: false,
  72 + listeners: {
  73 + load: function (store, records, successful) {
  74 + if (!successful && !store.errorDisplayed) {
  75 + Ext.Msg.alert('Error', 'Can not load results from the resolver. Please enter target names manually.')
  76 + store.errorDisplayed = true
  77 + }
  78 + }
  79 + }
  80 +})
81 81
82 /** 82 /**
83 `servicesStore`: An ExtJS Store containing the list of the EPN-TAP services (defined in 83 `servicesStore`: An ExtJS Store containing the list of the EPN-TAP services (defined in
@@ -88,51 +88,51 @@ This list is used to fill the `servicesGrid` table, which is updated by `EpnTapM @@ -88,51 +88,51 @@ This list is used to fill the `servicesGrid` table, which is updated by `EpnTapM
88 (or, by transitivity, target class or product type) is selected. 88 (or, by transitivity, target class or product type) is selected.
89 89
90 - `id`: the database name of the service, according to the `table_name` column from the `rr.res_table` in the 90 - `id`: the database name of the service, according to the `table_name` column from the `rr.res_table` in the
91 - registry database; 91 + registry database;
92 - `nbResults`: the number of granules matching with the granules filter for this service; 92 - `nbResults`: the number of granules matching with the granules filter for this service;
93 - `shortName`: the service short name, according to the `short_name` column from the `rr.resource` table in the registry 93 - `shortName`: the service short name, according to the `short_name` column from the `rr.resource` table in the registry
94 - database; 94 + database;
95 - `title`: the service title, according to the `res_title` column from the `rr.resource` table in the registry database; 95 - `title`: the service title, according to the `res_title` column from the `rr.resource` table in the registry database;
96 - `accessURL`: the service access URL, according to the `access_url` column from the `rr.interface` table in the 96 - `accessURL`: the service access URL, according to the `access_url` column from the `rr.interface` table in the
97 - registry database. 97 + registry database.
98 */ 98 */
99 Ext.create('Ext.data.Store', { 99 Ext.create('Ext.data.Store', {
100 - storeId: 'servicesStore',  
101 - autoLoad: true,  
102 - fields: [  
103 - {name: 'id', type: 'string'},  
104 - {name: 'short_name', type: 'string'},  
105 - {name: 'res_title', type: 'string'},  
106 - {name: 'ivoid', type: 'string'},  
107 - {name: 'access_url', type: 'string'},  
108 - {name: 'table_name', type: 'string'},  
109 - {name: 'content_type', type: 'string'},  
110 - {name: 'creator_seq', type: 'string'},  
111 - {name: 'content_level', type: 'string'},  
112 - {name: 'reference_url', type: 'string'},  
113 - {name: 'created', type: 'date', dateFormat: 'c'},  
114 - {name: 'updated', type: 'date', dateFormat: 'c'},  
115 - {name: 'nb_results', type: 'integer'},  
116 - {name: 'info', type: 'string'}  
117 - ],  
118 - proxy: {  
119 - type: 'ajax',  
120 - url: 'php/epntap.php',  
121 - extraParams : {action: 'getServices'}  
122 - },  
123 - sorters: [  
124 - {property: 'nb_results', direction: 'DESC'},  
125 - {property: 'short_name', direction: 'ASC'}  
126 - ],  
127 - listeners: {  
128 - // beforeload: function(s, operation) { console.log(operation); },  
129 - load: function(store, records, successful) {  
130 - if(!successful) {  
131 - Ext.Msg.alert('Error', 'Can not get epntap services from registries.');  
132 - }  
133 - }  
134 - }  
135 -}); 100 + storeId: 'servicesStore',
  101 + autoLoad: true,
  102 + fields: [
  103 + {name: 'id', type: 'string'},
  104 + {name: 'short_name', type: 'string'},
  105 + {name: 'res_title', type: 'string'},
  106 + {name: 'ivoid', type: 'string'},
  107 + {name: 'access_url', type: 'string'},
  108 + {name: 'table_name', type: 'string'},
  109 + {name: 'content_type', type: 'string'},
  110 + {name: 'creator_seq', type: 'string'},
  111 + {name: 'content_level', type: 'string'},
  112 + {name: 'reference_url', type: 'string'},
  113 + {name: 'created', type: 'date', dateFormat: 'c'},
  114 + {name: 'updated', type: 'date', dateFormat: 'c'},
  115 + {name: 'nb_results', type: 'integer'},
  116 + {name: 'info', type: 'string'}
  117 + ],
  118 + proxy: {
  119 + type: 'ajax',
  120 + url: 'php/epntap.php',
  121 + extraParams: {action: 'getServices'}
  122 + },
  123 + sorters: [
  124 + {property: 'nb_results', direction: 'DESC'},
  125 + {property: 'short_name', direction: 'ASC'}
  126 + ],
  127 + listeners: {
  128 + // beforeload: function(s, operation) { console.log(operation); },
  129 + load: function (store, records, successful) {
  130 + if (!successful) {
  131 + Ext.Msg.alert('Error', 'Can not get epntap services from registries.')
  132 + }
  133 + }
  134 + }
  135 +})
136 136
137 /** 137 /**
138 `granulesStore`: An ExtJS Store containing the list of granules of the selected service (on `servicesGrid`), which match 138 `granulesStore`: An ExtJS Store containing the list of granules of the selected service (on `servicesGrid`), which match
@@ -143,7 +143,7 @@ selected. @@ -143,7 +143,7 @@ selected.
143 143
144 - `num`: the line number, according to the order of the query response and the current page (see `currentPageLb`); 144 - `num`: the line number, according to the order of the query response and the current page (see `currentPageLb`);
145 - `dataproduct_type`: the dataproduct_type EPN-TAP parameter, as defined in 145 - `dataproduct_type`: the dataproduct_type EPN-TAP parameter, as defined in
146 - https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters. 146 + https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters.
147 - `target_name`: the target_name EPN-TAP parameter (ibid); 147 - `target_name`: the target_name EPN-TAP parameter (ibid);
148 - `time_min`: the time_min EPN-TAP parameter (ibid); 148 - `time_min`: the time_min EPN-TAP parameter (ibid);
149 - `time_max`: the time_max EPN-TAP parameter (ibid); 149 - `time_max`: the time_max EPN-TAP parameter (ibid);
@@ -156,224 +156,224 @@ selected. @@ -156,224 +156,224 @@ selected.
156 // TODO: Add granules filter (see http://docs.sencha.com/extjs/4.0.7/#!/example/grid-filtering/grid-filter-local.html) 156 // TODO: Add granules filter (see http://docs.sencha.com/extjs/4.0.7/#!/example/grid-filtering/grid-filter-local.html)
157 157
158 Ext.define('GranulesModel', { 158 Ext.define('GranulesModel', {
159 - extend: 'Ext.data.Model'  
160 - // columns are created dynamically  
161 -}); 159 + extend: 'Ext.data.Model'
  160 + // columns are created dynamically
  161 +})
162 162
163 Ext.create('Ext.data.Store', { 163 Ext.create('Ext.data.Store', {
164 - storeId: 'granulesStore',  
165 - model: 'GranulesModel',  
166 - buffered: true,  
167 - autoload: false,  
168 - pageSize: 500,  
169 - leadingBufferZone: 0,  
170 - proxy: {  
171 - type: 'ajax',  
172 - url: 'php/epntap.php',  
173 - reader: { type: 'json', root: 'data'},  
174 - simpleSortMode: true  
175 - },  
176 - listeners: {  
177 - 'beforeprefetch': function(store, operation) {  
178 - var service = Ext.data.StoreManager.lookup('servicesStore').getById(store.selectedService).data;  
179 - store.getProxy().extraParams = {  
180 - 'action': 'getGranules',  
181 - 'url': service['access_url'],  
182 - 'tableName': service['table_name'],  
183 - 'targetNames': Ext.getCmp('epnTapTargetNameCB').rawValue,  
184 - 'productTypes': Ext.getCmp('epnTapProductTypeCB').value.join(';'),  
185 - 'timeMin': Ext.Date.format(Ext.getCmp('epnTapTimeSelector').getStartTime(), 'd/m/Y H:i:s'),  
186 - 'timeMax': Ext.Date.format(Ext.getCmp('epnTapTimeSelector').getStopTime(), 'd/m/Y H:i:s'),  
187 - 'nbRes': service['nb_results']  
188 - };  
189 - },  
190 - 'prefetch': function(store, records, successful, operation) {  
191 - // console.log('(prefetch) operation ' + (successful ? 'success' : 'failed') + ': ', operation);  
192 - // console.log(operation.params);  
193 - // console.log(Ext.decode(operation.response.responseText));  
194 - },  
195 - 'metachange': function(store, meta) {  
196 - if(meta.metaHash != store.metaHash) {  
197 - Ext.getCmp('epnTapGranulesGrid').reconfigure(store, meta.columns);  
198 - store.metaHash = meta.metaHash;  
199 - }  
200 - }  
201 - }  
202 -}); 164 + storeId: 'granulesStore',
  165 + model: 'GranulesModel',
  166 + buffered: true,
  167 + autoload: false,
  168 + pageSize: 500,
  169 + leadingBufferZone: 0,
  170 + proxy: {
  171 + type: 'ajax',
  172 + url: 'php/epntap.php',
  173 + reader: { type: 'json', root: 'data'},
  174 + simpleSortMode: true
  175 + },
  176 + listeners: {
  177 + 'beforeprefetch': function (store, operation) {
  178 + var service = Ext.data.StoreManager.lookup('servicesStore').getById(store.selectedService).data
  179 + store.getProxy().extraParams = {
  180 + 'action': 'getGranules',
  181 + 'url': service['access_url'],
  182 + 'tableName': service['table_name'],
  183 + 'targetNames': Ext.getCmp('epnTapTargetNameCB').rawValue,
  184 + 'productTypes': Ext.getCmp('epnTapProductTypeCB').value.join(';'),
  185 + 'timeMin': Ext.Date.format(Ext.getCmp('epnTapTimeSelector').getStartTime(), 'd/m/Y H:i:s'),
  186 + 'timeMax': Ext.Date.format(Ext.getCmp('epnTapTimeSelector').getStopTime(), 'd/m/Y H:i:s'),
  187 + 'nbRes': service['nb_results']
  188 + }
  189 + },
  190 + 'prefetch': function (store, records, successful, operation) {
  191 + // console.log('(prefetch) operation ' + (successful ? 'success' : 'failed') + ': ', operation);
  192 + // console.log(operation.params);
  193 + // console.log(Ext.decode(operation.response.responseText));
  194 + },
  195 + 'metachange': function (store, meta) {
  196 + if (meta.metaHash != store.metaHash) {
  197 + Ext.getCmp('epnTapGranulesGrid').reconfigure(store, meta.columns)
  198 + store.metaHash = meta.metaHash
  199 + }
  200 + }
  201 + }
  202 +})
203 203
204 /** 204 /**
205 Error are not displayed here, use try/catch each time it's necessary. 205 Error are not displayed here, use try/catch each time it's necessary.
206 */ 206 */
207 Ext.define('App.util.Format', { 207 Ext.define('App.util.Format', {
208 - override: 'Ext.util.Format',  
209 -  
210 - // Utils  
211 -  
212 - 'prettify': function(data) {  
213 - return data.charAt(0).toUpperCase() + data.replace(/_/g, ' ').substr(1).toLowerCase();  
214 - },  
215 - 'url': function(data) {  
216 - var url_pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol  
217 - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ // domain name  
218 - '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address  
219 - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path  
220 - '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string  
221 - '(\\#[-a-z\\d_]*)?$','i'); // fragment locator  
222 - return url_pattern.test(data) ? data : null;  
223 - },  
224 - 'cell': function(content, tooltip, tooltipTitle) {  
225 - var ttAttr = "";  
226 - if(tooltip !== '') {  
227 - var ttTitle = tooltipTitle ? " data-qtitle='" + tooltipTitle + "'" : "";  
228 - ttAttr = ttTitle + "' data-qtip='" + (tooltip ? tooltip : (content ? content : 'No value.')) + "'";  
229 - }  
230 - return "<div class=epntap_cell " + ttAttr + ">" + (content ? content : '-') + "</div>";  
231 - },  
232 -  
233 - // Services grid  
234 -  
235 - 'serviceTooltip': function(data) {  
236 - for (var key in data) {  
237 - if(typeof data[key] == 'string' && data[key] != '') {  
238 - data[key] = data[key].replace(/'/g, '&#39;').replace(/"/g, '&#34;');  
239 - }  
240 - }  
241 - var infoColor = data.nb_results == -2 ? 'IndianRed' : 'green';  
242 - var info = data.info.length > 0 ? '<p style="color:' + infoColor + '">' + data.info + '</p>' : '';  
243 -  
244 - var colums = ['res_title', 'ivoid', 'access_url', 'table_name', 'content_type', 'creator_seq', 'content_level', 'reference_url', 'created', 'updated'];  
245 - var details = '';  
246 - for (var key in colums) {  
247 - if(data[colums[key]] !== '') {  
248 - var val = colums[key] === 'content_level' ? data[colums[key]].replace(/#/g, ', ') : data[colums[key]];  
249 - details += '<li><b>' + Ext.util.Format.prettify(colums[key]) + '</b>: ' + val + '</li>';  
250 - }  
251 - }  
252 - return info + '<ul>' + details + '</ul>';  
253 - },  
254 - 'service.text': function(data, metadata, record) {  
255 - return Ext.util.Format.cell(data, Ext.util.Format.serviceTooltip(record.data), data);  
256 - },  
257 - 'service.number': function(data, metadata, record) {  
258 - value = '' + data;  
259 - if(data < 0) {  
260 - value = '-';  
261 - } else if(data >= 1000*1000) {  
262 - value = (data/(1000*1000)).toPrecision(3) + 'm';  
263 - } else if(data >= 1000) {  
264 - value = (data/1000).toPrecision(3) + 'k';  
265 - }  
266 - return Ext.util.Format.cell(value, Ext.util.Format.serviceTooltip(record.data), record.data.short_name);  
267 - },  
268 -  
269 - // Granules grid  
270 -  
271 - 'granule.text': function(data, metadata, record) {  
272 - return Ext.util.Format.cell(data);  
273 - },  
274 - 'granule.link': function(data, metadata, record) {  
275 - var icon_b64 = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSIxMDI0IiB3aWR0aD0iNzY4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik02NDAgNzY4SDEyOFYyNTcuOTA1OTk5OTk5OTk5OTVMMjU2IDI1NlYxMjhIMHY3NjhoNzY4VjU3Nkg2NDBWNzY4ek0zODQgMTI4bDEyOCAxMjhMMzIwIDQ0OGwxMjggMTI4IDE5Mi0xOTIgMTI4IDEyOFYxMjhIMzg0eiIvPjwvc3ZnPg==';  
276 - var icon = '<img style="width:15px;" alt="link" src="' + icon_b64 + '">';  
277 - url = Ext.util.Format.url(data);  
278 - if(url) {  
279 - txt = '<a style="font-size:150%" target="_blank" href="' + url + '">' + icon + '</a>';  
280 - }  
281 - return Ext.util.Format.cell(txt, url);  
282 - },  
283 - 'granule.img': function(data, metadata, record) {  
284 - img_url = Ext.util.Format.url(data);  
285 - if(img_url) {  
286 - icon = '<img style="max-width:100%; max-height:100%" alt="-" src="' + img_url + '">';  
287 - img = '<img style="max-width:200px; max-height:200px" src="' + img_url + '">';  
288 - }  
289 - return Ext.util.Format.cell(icon, img);  
290 - },  
291 - 'granule.type': function(data, metadata, record) {  
292 - var productTypeDict = Ext.data.StoreManager.lookup('productTypesStore').data.map;  
293 - return Ext.util.Format.cell(productTypeDict[data].data.name);  
294 - },  
295 - 'granule.size': function(data, metadata, record) {  
296 - var size = parseInt(data);  
297 - if (isNaN(size)) {  
298 - } else if (size >= 1024*1024) {  
299 - txt = (size/(1024*1024)).toPrecision(3) + 'Go';  
300 - } else if (size >= 1024) {  
301 - txt = (size/1024).toPrecision(3) + 'Mo';  
302 - } else {  
303 - txt = size + 'Ko';  
304 - }  
305 - return Ext.util.Format.cell(txt);  
306 - },  
307 - 'granule.proc_lvl': function(data, metadata, record) {  
308 - var levels = {1: 'Raw', 2: 'Edited', 3: 'Calibrated', 4: 'Resampled', 5: 'Derived', 6: 'Ancillary'};  
309 - return Ext.util.Format.cell((data in levels) ? levels[data] : '<em>' + data + '</em>');  
310 - },  
311 - 'granule.date': function(data, metadata, record) {  
312 - if(isNaN(data)) {  
313 - return '';  
314 - }  
315 - var f = Number(data) + 1401 + Math.floor((Math.floor((4 * Number(data) + 274277) / 146097) * 3) / 4) - 38;  
316 - var e = 4 * f + 3;  
317 - var g = Math.floor((e % 1461) / 4);  
318 - var h = 5 * g + 2;  
319 - var D = Math.floor((h % 153) / 5) + 1;  
320 - var M = ((Math.floor(h / 153) + 2) % 12) + 1;  
321 - var Y = Math.floor(e / 1461) - 4716 + Math.floor((12 + 2 - M) / 12);  
322 - var date = new Date(Y, M-1, D);  
323 - return Ext.util.Format.cell(Ext.Date.format(date, 'Y/m/d'), Ext.Date.format(date, 'F j, Y, g:i a'));  
324 - },  
325 - 'granule.format': function(data, metadata, record) {  
326 - var mimetypeDict = {  
327 - 'application/fits': 'fits',  
328 - 'application/x-pds': 'pds',  
329 - 'image/x-pds': 'pds',  
330 - 'application/gml+xml': 'gml',  
331 - 'application/json': 'json',  
332 - 'application/octet-stream': 'bin, idl, envi or matlab',  
333 - 'application/pdf': 'pdf',  
334 - 'application/postscript': 'ps',  
335 - 'application/vnd.geo+json': 'geojson',  
336 - 'application/vnd.google-earth.kml+xml': 'kml',  
337 - 'application/vnd.google-earth.kmz': 'kmz',  
338 - 'application/vnd.ms-excel': 'xls',  
339 - 'application/x-asdm': 'asdm',  
340 - 'application/x-cdf': 'cdf',  
341 - 'application/x-cdf-istp': 'cdf',  
342 - 'application/x-cdf-pds4': 'cdf',  
343 - 'application/x-cef1': 'cef1',  
344 - 'application/x-cef2': 'cef2',  
345 - 'application/x-directory': 'dir',  
346 - 'application/x-fits-bintable': 'bintable',  
347 - 'application/x-fits-euro3d': 'euro3d',  
348 - 'application/x-fits-mef': 'mef',  
349 - 'application/x-geotiff': 'geotiff',  
350 - 'application/x-hdf': 'hdf',  
351 - 'application/x-netcdf': 'nc',  
352 - 'application/x-netcdf4': 'nc',  
353 - 'application/x-tar': 'tar',  
354 - 'application/x-tar-gzip': 'gtar',  
355 - 'application/x-votable+xml': 'votable',  
356 - 'application/x-votable+xml;content=datalink': 'votable',  
357 - 'application/zip': 'zip',  
358 - 'image/fits': 'fits',  
359 - 'image/gif': 'gif',  
360 - 'image/jpeg': 'jpeg',  
361 - 'image/png': 'png',  
362 - 'image/tiff': 'tiff',  
363 - 'image/x-fits-gzip': 'fits',  
364 - 'image/x-fits-hcompress': 'fits',  
365 - 'text/csv': 'csv',  
366 - 'text/html': 'html',  
367 - 'text/plain': 'txt',  
368 - 'text/tab-separated-values': 'tsv',  
369 - 'text/xml': 'xml',  
370 - 'video/mpeg': 'mpeg',  
371 - 'video/quicktime': 'mov',  
372 - 'video/x-msvideo': 'avi'  
373 - };  
374 - return Ext.util.Format.cell((data in mimetypeDict) ? '<p>' + mimetypeDict[data] + '</p>' : '<em>' + data + '</em>');  
375 - }  
376 -}); 208 + override: 'Ext.util.Format',
  209 +
  210 + // Utils
  211 +
  212 + 'prettify': function (data) {
  213 + return data.charAt(0).toUpperCase() + data.replace(/_/g, ' ').substr(1).toLowerCase()
  214 + },
  215 + 'url': function (data) {
  216 + var url_pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
  217 + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name
  218 + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
  219 + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
  220 + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
  221 + '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator
  222 + return url_pattern.test(data) ? data : null
  223 + },
  224 + 'cell': function (content, tooltip, tooltipTitle) {
  225 + var ttAttr = ''
  226 + if (tooltip !== '') {
  227 + var ttTitle = tooltipTitle ? " data-qtitle='" + tooltipTitle + "'" : ''
  228 + ttAttr = ttTitle + "' data-qtip='" + (tooltip || (content || 'No value.')) + "'"
  229 + }
  230 + return '<div class=epntap_cell ' + ttAttr + '>' + (content || '-') + '</div>'
  231 + },
  232 +
  233 + // Services grid
  234 +
  235 + 'serviceTooltip': function (data) {
  236 + for (var key in data) {
  237 + if (typeof data[key] === 'string' && data[key] != '') {
  238 + data[key] = data[key].replace(/'/g, '&#39;').replace(/"/g, '&#34;')
  239 + }
  240 + }
  241 + var infoColor = data.nb_results == -2 ? 'IndianRed' : 'green'
  242 + var info = data.info.length > 0 ? '<p style="color:' + infoColor + '">' + data.info + '</p>' : ''
  243 +
  244 + var colums = ['res_title', 'ivoid', 'access_url', 'table_name', 'content_type', 'creator_seq', 'content_level', 'reference_url', 'created', 'updated']
  245 + var details = ''
  246 + for (var key in colums) {
  247 + if (data[colums[key]] !== '') {
  248 + var val = colums[key] === 'content_level' ? data[colums[key]].replace(/#/g, ', ') : data[colums[key]]
  249 + details += '<li><b>' + Ext.util.Format.prettify(colums[key]) + '</b>: ' + val + '</li>'
  250 + }
  251 + }
  252 + return info + '<ul>' + details + '</ul>'
  253 + },
  254 + 'service.text': function (data, metadata, record) {
  255 + return Ext.util.Format.cell(data, Ext.util.Format.serviceTooltip(record.data), data)
  256 + },
  257 + 'service.number': function (data, metadata, record) {
  258 + value = '' + data
  259 + if (data < 0) {
  260 + value = '-'
  261 + } else if (data >= 1000 * 1000) {
  262 + value = (data / (1000 * 1000)).toPrecision(3) + 'm'
  263 + } else if (data >= 1000) {
  264 + value = (data / 1000).toPrecision(3) + 'k'
  265 + }
  266 + return Ext.util.Format.cell(value, Ext.util.Format.serviceTooltip(record.data), record.data.short_name)
  267 + },
  268 +
  269 + // Granules grid
  270 +
  271 + 'granule.text': function (data, metadata, record) {
  272 + return Ext.util.Format.cell(data)
  273 + },
  274 + 'granule.link': function (data, metadata, record) {
  275 + var icon_b64 = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSIxMDI0IiB3aWR0aD0iNzY4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik02NDAgNzY4SDEyOFYyNTcuOTA1OTk5OTk5OTk5OTVMMjU2IDI1NlYxMjhIMHY3NjhoNzY4VjU3Nkg2NDBWNzY4ek0zODQgMTI4bDEyOCAxMjhMMzIwIDQ0OGwxMjggMTI4IDE5Mi0xOTIgMTI4IDEyOFYxMjhIMzg0eiIvPjwvc3ZnPg=='
  276 + var icon = '<img style="width:15px;" alt="link" src="' + icon_b64 + '">'
  277 + url = Ext.util.Format.url(data)
  278 + if (url) {
  279 + txt = '<a style="font-size:150%" target="_blank" href="' + url + '">' + icon + '</a>'
  280 + }
  281 + return Ext.util.Format.cell(txt, url)
  282 + },
  283 + 'granule.img': function (data, metadata, record) {
  284 + img_url = Ext.util.Format.url(data)
  285 + if (img_url) {
  286 + icon = '<img style="max-width:100%; max-height:100%" alt="-" src="' + img_url + '">'
  287 + img = '<img style="max-width:200px; max-height:200px" src="' + img_url + '">'
  288 + }
  289 + return Ext.util.Format.cell(icon, img)
  290 + },
  291 + 'granule.type': function (data, metadata, record) {
  292 + var productTypeDict = Ext.data.StoreManager.lookup('productTypesStore').data.map
  293 + return Ext.util.Format.cell(productTypeDict[data].data.name)
  294 + },
  295 + 'granule.size': function (data, metadata, record) {
  296 + var size = parseInt(data)
  297 + if (isNaN(size)) {
  298 + } else if (size >= 1024 * 1024) {
  299 + txt = (size / (1024 * 1024)).toPrecision(3) + 'Go'
  300 + } else if (size >= 1024) {
  301 + txt = (size / 1024).toPrecision(3) + 'Mo'
  302 + } else {
  303 + txt = size + 'Ko'
  304 + }
  305 + return Ext.util.Format.cell(txt)
  306 + },
  307 + 'granule.proc_lvl': function (data, metadata, record) {
  308 + var levels = {1: 'Raw', 2: 'Edited', 3: 'Calibrated', 4: 'Resampled', 5: 'Derived', 6: 'Ancillary'}
  309 + return Ext.util.Format.cell((data in levels) ? levels[data] : '<em>' + data + '</em>')
  310 + },
  311 + 'granule.date': function (data, metadata, record) {
  312 + if (isNaN(data)) {
  313 + return ''
  314 + }
  315 + var f = Number(data) + 1401 + Math.floor((Math.floor((4 * Number(data) + 274277) / 146097) * 3) / 4) - 38
  316 + var e = 4 * f + 3
  317 + var g = Math.floor((e % 1461) / 4)
  318 + var h = 5 * g + 2
  319 + var D = Math.floor((h % 153) / 5) + 1
  320 + var M = ((Math.floor(h / 153) + 2) % 12) + 1
  321 + var Y = Math.floor(e / 1461) - 4716 + Math.floor((12 + 2 - M) / 12)
  322 + var date = new Date(Y, M - 1, D)
  323 + return Ext.util.Format.cell(Ext.Date.format(date, 'Y/m/d'), Ext.Date.format(date, 'F j, Y, g:i a'))
  324 + },
  325 + 'granule.format': function (data, metadata, record) {
  326 + var mimetypeDict = {
  327 + 'application/fits': 'fits',
  328 + 'application/x-pds': 'pds',
  329 + 'image/x-pds': 'pds',
  330 + 'application/gml+xml': 'gml',
  331 + 'application/json': 'json',
  332 + 'application/octet-stream': 'bin, idl, envi or matlab',
  333 + 'application/pdf': 'pdf',
  334 + 'application/postscript': 'ps',
  335 + 'application/vnd.geo+json': 'geojson',
  336 + 'application/vnd.google-earth.kml+xml': 'kml',
  337 + 'application/vnd.google-earth.kmz': 'kmz',
  338 + 'application/vnd.ms-excel': 'xls',
  339 + 'application/x-asdm': 'asdm',
  340 + 'application/x-cdf': 'cdf',
  341 + 'application/x-cdf-istp': 'cdf',
  342 + 'application/x-cdf-pds4': 'cdf',
  343 + 'application/x-cef1': 'cef1',
  344 + 'application/x-cef2': 'cef2',
  345 + 'application/x-directory': 'dir',
  346 + 'application/x-fits-bintable': 'bintable',
  347 + 'application/x-fits-euro3d': 'euro3d',
  348 + 'application/x-fits-mef': 'mef',
  349 + 'application/x-geotiff': 'geotiff',
  350 + 'application/x-hdf': 'hdf',
  351 + 'application/x-netcdf': 'nc',
  352 + 'application/x-netcdf4': 'nc',
  353 + 'application/x-tar': 'tar',
  354 + 'application/x-tar-gzip': 'gtar',
  355 + 'application/x-votable+xml': 'votable',
  356 + 'application/x-votable+xml;content=datalink': 'votable',
  357 + 'application/zip': 'zip',
  358 + 'image/fits': 'fits',
  359 + 'image/gif': 'gif',
  360 + 'image/jpeg': 'jpeg',
  361 + 'image/png': 'png',
  362 + 'image/tiff': 'tiff',
  363 + 'image/x-fits-gzip': 'fits',
  364 + 'image/x-fits-hcompress': 'fits',
  365 + 'text/csv': 'csv',
  366 + 'text/html': 'html',
  367 + 'text/plain': 'txt',
  368 + 'text/tab-separated-values': 'tsv',
  369 + 'text/xml': 'xml',
  370 + 'video/mpeg': 'mpeg',
  371 + 'video/quicktime': 'mov',
  372 + 'video/x-msvideo': 'avi'
  373 + }
  374 + return Ext.util.Format.cell((data in mimetypeDict) ? '<p>' + mimetypeDict[data] + '</p>' : '<em>' + data + '</em>')
  375 + }
  376 +})
377 377
378 /** 378 /**
379 `EpnTapUI`: The view of the AMDA EPN-TAP module, allowing the user to query and display granules information from 379 `EpnTapUI`: The view of the AMDA EPN-TAP module, allowing the user to query and display granules information from
@@ -382,298 +382,298 @@ EPN-TAP services. @@ -382,298 +382,298 @@ EPN-TAP services.
382 Note: The controller part of this module is defined in `js/app/controller/EpnTapModule`. 382 Note: The controller part of this module is defined in `js/app/controller/EpnTapModule`.
383 */ 383 */
384 Ext.define('amdaUI.EpnTapUI', { 384 Ext.define('amdaUI.EpnTapUI', {
385 - extend: 'Ext.panel.Panel',  
386 - alias: 'widget.panelEpnTap',  
387 - requires: ['amdaUI.IntervalUI'],  
388 -  
389 - /**  
390 - Method constructor, which basically call the `init()` method to create the EpnTap panel.  
391 - */  
392 - constructor: function(config) {  
393 - this.init(config);  
394 - this.callParent(arguments);  
395 - },  
396 -  
397 - /**  
398 - Create all the EpnTapPanel UI elements, and apply the AMDA module `config` (which includes the created items).  
399 -  
400 - When the panel is correctly rendered, the panel triggers `EpnTapModule.onWindowLoaded()`.  
401 -  
402 - Note: All the UI elements creation are defined as functions in this init method and not as methods in order to make  
403 - them private (ie. to avoid `EpnTapUI.createServicesGrid();`, which doesn't make sense).  
404 - */  
405 - init: function(config) {  
406 - var myConf = {  
407 - id: 'epntapTab',  
408 - title: 'EPN-TAP',  
409 - items: [{  
410 - xtype: 'container',  
411 - layout: { type: 'vbox', pack: 'start', align: 'stretch'},  
412 - items: [  
413 - this.createServiceFilterPanel(),  
414 - this.createGridsPanel()  
415 - ]  
416 - }]  
417 - };  
418 - Ext.apply(this, Ext.apply(arguments, myConf));  
419 - },  
420 -  
421 - /***************************  
422 - *** Service filter panel ***  
423 - ***************************/  
424 -  
425 - /**  
426 - Create `epnTapServiceFilterPanel`, an ExtJS Panel containing two containers:  
427 - - the left container, containing the combo boxes (for product type, target class and target name)  
428 - and the navigation panel;  
429 - - the right container, containing the time selector.  
430 - */  
431 - createServiceFilterPanel: function() {  
432 - return {  
433 - xtype: 'form',  
434 - id: 'epnTapServiceFilterPanel',  
435 - layout: { type: 'hbox', pack: 'start', align: 'stretch' },  
436 - region: 'north',  
437 - defaults: { margin: '5 0 5 5'},  
438 - items: [{ // Left part  
439 - xtype : 'container',  
440 - flex: 1,  
441 - items: [  
442 - this.createTargetNameCB(),  
443 - this.createProductTypeCB()  
444 - ]  
445 - }, { // Middle part  
446 - xtype : 'container',  
447 - flex: 1,  
448 - items: [  
449 - this.createTimeSelector()  
450 - ]  
451 - }, { // Right part  
452 - xtype : 'container',  
453 - items: [  
454 - this.createSendButton()  
455 - ]  
456 -  
457 - }]  
458 - };  
459 - },  
460 -  
461 - /**  
462 - Create `epnTapTargetNameCB`, an ExtJS ComboBox, containing a list of target names corresponding to the selected  
463 - target class, as defined in `targetNamesStore`, which is initilized by `EpnTapModule`.  
464 -  
465 - The selection of a target name triggers `EpnTapModule.onTargetNameCBChanged()`, which basically updates  
466 - `granulesGrid`.  
467 - */  
468 - createTargetNameCB: function() {  
469 - return {  
470 - xtype: 'combobox',  
471 - id: 'epnTapTargetNameCB',  
472 - fieldLabel: 'Target name',  
473 - emptyText: 'Earth, Saturn, 67P, ...',  
474 - tooltip: 'Start to type a text, then select a target name (required). Several values are allowed, separated by a semicolon.',  
475 - store: Ext.data.StoreManager.lookup('targetNamesStore'),  
476 - queryMode: 'remote',  
477 - queryParam: 'input',  
478 - displayField: 'text',  
479 - valueField: 'id',  
480 - margin: '15 0 5 0',  
481 - labelWidth: 71,  
482 - minWidth: 20,  
483 - minChars: 2,  
484 - hideTrigger: true,  
485 - listConfig: {  
486 - getInnerTpl: function() {  
487 - return '<div data-qtitle="{name}" data-qtip="<p>type: {type}</p><p>parent: {parent}</p><p>aliases:</p><ul>{aliases}</ul>">{name}</div>';  
488 - }  
489 - },  
490 - listeners: {  
491 - render: function(cb) {  
492 - new Ext.ToolTip({ target: cb.getEl(), html: '<div style="width:200px">' + cb.tooltip + '</div>'});  
493 - }  
494 - }  
495 - };  
496 - },  
497 -  
498 - /**  
499 - Create `epnTapProductTypeCB`, an ExtJS ComboBox, containing a list of product types as defined in  
500 - `epnTapProductTypesStore`, which is initilized by `EpnTapModule`.  
501 -  
502 - The selection of a produt type triggers `EpnTapModule.onProductTypeCBChanged()`, which basically update  
503 - `epnTapGranulesGrid`.  
504 - */  
505 - createProductTypeCB: function() {  
506 - return {  
507 - xtype: 'combobox',  
508 - id: 'epnTapProductTypeCB',  
509 - fieldLabel: 'Product type',  
510 - emptyText: 'Image, Time series, ...',  
511 - tooltip: 'Select one or several data product types (required).',  
512 - store: Ext.data.StoreManager.lookup('productTypesStore'),  
513 - queryMode: 'local',  
514 - valueField: 'id',  
515 - multiSelect: true,  
516 - displayField: 'name',  
517 - labelWidth: 71,  
518 - editable: false,  
519 - listConfig: {  
520 - getInnerTpl: function() {  
521 - return '<div data-qtitle="{name}" data-qwidth=200 data-qtip="<p>{desc}</p>">{name}</div>';  
522 - }  
523 - },  
524 - listeners: {  
525 - change: function(cb, records) {  
526 - var val = cb.value[cb.value.length - 1];  
527 - if(val === 'all') {  
528 - cb.select(cb.store.getRange().slice(2));  
529 - } else if (val === 'clear') {  
530 - cb.reset();  
531 - }  
532 - },  
533 - render: function(cb) {  
534 - new Ext.ToolTip({ target: cb.getEl(), html: '<div style="width:200px">' + cb.tooltip + '</div>'});  
535 - }  
536 - }  
537 - };  
538 - },  
539 -  
540 - /**  
541 - Create `epnTapTimeSelector`, an IntervalUI object, allowing the user to select a time interval (by filling two  
542 - dates and/or a duration).  
543 -  
544 - See `js/app/views/IntervalUI.js` for more information about this component.  
545 - */  
546 - createTimeSelector: function() {  
547 - return {  
548 - xtype: 'intervalSelector',  
549 - id: 'epnTapTimeSelector'  
550 - };  
551 - },  
552 -  
553 - /***********************  
554 - *** Navigation panel ***  
555 - ***********************/  
556 -  
557 - /**  
558 - The button used to send the query.  
559 - */  
560 - createSendButton: function() {  
561 - return {  
562 - xtype: 'button',  
563 - id: 'epnTapGetBtn',  
564 - text: 'Get services',  
565 - disabled: true,  
566 - width: 140,  
567 - height: 50,  
568 - margin: 10  
569 - }  
570 - },  
571 -  
572 - /************  
573 - *** Grids ***  
574 - ************/  
575 -  
576 - /**  
577 - Create `epnTapGridsPanel`, an ExtJS Panel, containing `epnTapServicesGrid` and `epnTapGranulesGrid`.  
578 -  
579 - After the rendering of the grids, it triggers `epnTapModule.onWindowLoaded()`, which basically fill  
580 - `epnTapServicesGrid` for the first time.  
581 - */  
582 - createGridsPanel: function() {  
583 - return {  
584 - xtype: 'panel',  
585 - id: 'epnTapGridsPanel',  
586 - layout: 'fit',  
587 - flex: 1,  
588 - items: [{  
589 - xtype: 'container',  
590 - layout: { type: 'hbox', pack: 'start', align: 'stretch'},  
591 - items: [  
592 - this.createServicesGrid(),  
593 - this.createGranulesGrid()  
594 - ]  
595 - }]  
596 - };  
597 - },  
598 -  
599 - /**  
600 - Create `epnTapServicesGrid`, an ExtJS grid containing the EPN-TAP services matching with the filter form  
601 - (`serviceFilterPanel`).  
602 -  
603 - For each service, this grid displays:  
604 - - the service name;  
605 - - the number of granules matching with the filter.  
606 -  
607 - Other informations are available through an ExtJS Tooltip, on each row:  
608 - - short name;  
609 - - title;  
610 - - access URL.  
611 -  
612 - A click on a service triggers `EpnTapModule.onServiceSelected()`, which basically fills `GranulesGrid` by the  
613 - service granules.  
614 - */  
615 - createServicesGrid: function() {  
616 - return {  
617 - xtype: 'grid',  
618 - cls: 'epntap_grid',  
619 - id: 'epnTapServicesGrid',  
620 - title: 'Services',  
621 - store: Ext.data.StoreManager.lookup('servicesStore'),  
622 - flex: 1,  
623 - columns: [  
624 - {text: 'Name', dataIndex: 'short_name', flex: 1, renderer: 'service.text'},  
625 - {text: 'Nb res.', dataIndex: 'nb_results', width: 50, renderer: 'service.number'}  
626 - ],  
627 - viewConfig: {  
628 - getRowClass: function(record, index) {  
629 - var nb_res = record.get('nb_results');  
630 - if(nb_res == 0 || nb_res == -1) {  
631 - return 'disabled_row';  
632 - } else if (nb_res == -2) {  
633 - return 'error_row';  
634 - }  
635 - }  
636 - }  
637 - };  
638 - },  
639 -  
640 - /**  
641 - Create `epnTapGranulesGrid`, an ExtJS grid containing the granules of the selected service in  
642 - `epnTapServicesGrid`.  
643 -  
644 - For each granule, this grid displays:  
645 - - the row number;  
646 - - the dataproduct type;  
647 - - the target name;  
648 - - the min and max times;  
649 - - the format;  
650 - - the UID (granule identifier);  
651 - - the estimated size;  
652 - - the URL;  
653 - - the thumbnail.  
654 -  
655 - Each of these information are displayed in a specific rendering to improve user experience.  
656 - For more information about these parameters, see https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters.  
657 -  
658 - Other informations are available through an ExtJS Tooltip on each row:  
659 - - currently only the granule thumbnail, in full size.  
660 -  
661 - A click on a granule triggers `EpnTapModule.onGranuleSelected()`.  
662 - */  
663 - createGranulesGrid: function() {  
664 - return {  
665 - xtype: 'grid',  
666 - cls: 'epntap_grid',  
667 - id: 'epnTapGranulesGrid',  
668 - title: 'Granules',  
669 - store: Ext.data.StoreManager.lookup('granulesStore'),  
670 - flex: 4,  
671 - plugins: {  
672 - ptype: 'bufferedrenderer',  
673 - trailingBufferZone: 20,  
674 - leadingBufferZone: 50  
675 - },  
676 - columns: []  
677 - };  
678 - }  
679 -}); 385 + extend: 'Ext.panel.Panel',
  386 + alias: 'widget.panelEpnTap',
  387 + requires: ['amdaUI.IntervalUI'],
  388 +
  389 + /**
  390 + Method constructor, which basically call the `init()` method to create the EpnTap panel.
  391 + */
  392 + constructor: function (config) {
  393 + this.init(config)
  394 + this.callParent(arguments)
  395 + },
  396 +
  397 + /**
  398 + Create all the EpnTapPanel UI elements, and apply the AMDA module `config` (which includes the created items).
  399 +
  400 + When the panel is correctly rendered, the panel triggers `EpnTapModule.onWindowLoaded()`.
  401 +
  402 + Note: All the UI elements creation are defined as functions in this init method and not as methods in order to make
  403 + them private (ie. to avoid `EpnTapUI.createServicesGrid();`, which doesn't make sense).
  404 + */
  405 + init: function (config) {
  406 + var myConf = {
  407 + id: 'epntapTab',
  408 + title: 'EPN-TAP',
  409 + items: [{
  410 + xtype: 'container',
  411 + layout: { type: 'vbox', pack: 'start', align: 'stretch'},
  412 + items: [
  413 + this.createServiceFilterPanel(),
  414 + this.createGridsPanel()
  415 + ]
  416 + }]
  417 + }
  418 + Ext.apply(this, Ext.apply(arguments, myConf))
  419 + },
  420 +
  421 + /***************************
  422 + *** Service filter panel ***
  423 + ***************************/
  424 +
  425 + /**
  426 + Create `epnTapServiceFilterPanel`, an ExtJS Panel containing two containers:
  427 + - the left container, containing the combo boxes (for product type, target class and target name)
  428 + and the navigation panel;
  429 + - the right container, containing the time selector.
  430 + */
  431 + createServiceFilterPanel: function () {
  432 + return {
  433 + xtype: 'form',
  434 + id: 'epnTapServiceFilterPanel',
  435 + layout: { type: 'hbox', pack: 'start', align: 'stretch' },
  436 + region: 'north',
  437 + defaults: { margin: '5 0 5 5'},
  438 + items: [{ // Left part
  439 + xtype: 'container',
  440 + flex: 1,
  441 + items: [
  442 + this.createTargetNameCB(),
  443 + this.createProductTypeCB()
  444 + ]
  445 + }, { // Middle part
  446 + xtype: 'container',
  447 + flex: 1,
  448 + items: [
  449 + this.createTimeSelector()
  450 + ]
  451 + }, { // Right part
  452 + xtype: 'container',
  453 + items: [
  454 + this.createSendButton()
  455 + ]
  456 +
  457 + }]
  458 + }
  459 + },
  460 +
  461 + /**
  462 + Create `epnTapTargetNameCB`, an ExtJS ComboBox, containing a list of target names corresponding to the selected
  463 + target class, as defined in `targetNamesStore`, which is initilized by `EpnTapModule`.
  464 +
  465 + The selection of a target name triggers `EpnTapModule.onTargetNameCBChanged()`, which basically updates
  466 + `granulesGrid`.
  467 + */
  468 + createTargetNameCB: function () {
  469 + return {
  470 + xtype: 'combobox',
  471 + id: 'epnTapTargetNameCB',
  472 + fieldLabel: 'Target name',
  473 + emptyText: 'Earth, Saturn, 67P, ...',
  474 + tooltip: 'Start to type a text, then select a target name (required). Several values are allowed, separated by a semicolon.',
  475 + store: Ext.data.StoreManager.lookup('targetNamesStore'),
  476 + queryMode: 'remote',
  477 + queryParam: 'input',
  478 + displayField: 'text',
  479 + valueField: 'id',
  480 + margin: '15 0 5 0',
  481 + labelWidth: 71,
  482 + minWidth: 20,
  483 + minChars: 2,
  484 + hideTrigger: true,
  485 + listConfig: {
  486 + getInnerTpl: function () {
  487 + return '<div data-qtitle="{name}" data-qtip="<p>type: {type}</p><p>parent: {parent}</p><p>aliases:</p><ul>{aliases}</ul>">{name}</div>'
  488 + }
  489 + },
  490 + listeners: {
  491 + render: function (cb) {
  492 + new Ext.ToolTip({ target: cb.getEl(), html: '<div style="width:200px">' + cb.tooltip + '</div>'})
  493 + }
  494 + }
  495 + }
  496 + },
  497 +
  498 + /**
  499 + Create `epnTapProductTypeCB`, an ExtJS ComboBox, containing a list of product types as defined in
  500 + `epnTapProductTypesStore`, which is initilized by `EpnTapModule`.
  501 +
  502 + The selection of a produt type triggers `EpnTapModule.onProductTypeCBChanged()`, which basically update
  503 + `epnTapGranulesGrid`.
  504 + */
  505 + createProductTypeCB: function () {
  506 + return {
  507 + xtype: 'combobox',
  508 + id: 'epnTapProductTypeCB',
  509 + fieldLabel: 'Product type',
  510 + emptyText: 'Image, Time series, ...',
  511 + tooltip: 'Select one or several data product types (required).',
  512 + store: Ext.data.StoreManager.lookup('productTypesStore'),
  513 + queryMode: 'local',
  514 + valueField: 'id',
  515 + multiSelect: true,
  516 + displayField: 'name',
  517 + labelWidth: 71,
  518 + editable: false,
  519 + listConfig: {
  520 + getInnerTpl: function () {
  521 + return '<div data-qtitle="{name}" data-qwidth=200 data-qtip="<p>{desc}</p>">{name}</div>'
  522 + }
  523 + },
  524 + listeners: {
  525 + change: function (cb, records) {
  526 + var val = cb.value[cb.value.length - 1]
  527 + if (val === 'all') {
  528 + cb.select(cb.store.getRange().slice(2))
  529 + } else if (val === 'clear') {
  530 + cb.reset()
  531 + }
  532 + },
  533 + render: function (cb) {
  534 + new Ext.ToolTip({ target: cb.getEl(), html: '<div style="width:200px">' + cb.tooltip + '</div>'})
  535 + }
  536 + }
  537 + }
  538 + },
  539 +
  540 + /**
  541 + Create `epnTapTimeSelector`, an IntervalUI object, allowing the user to select a time interval (by filling two
  542 + dates and/or a duration).
  543 +
  544 + See `js/app/views/IntervalUI.js` for more information about this component.
  545 + */
  546 + createTimeSelector: function () {
  547 + return {
  548 + xtype: 'intervalSelector',
  549 + id: 'epnTapTimeSelector'
  550 + }
  551 + },
  552 +
  553 + /***********************
  554 + *** Navigation panel ***
  555 + ***********************/
  556 +
  557 + /**
  558 + The button used to send the query.
  559 + */
  560 + createSendButton: function () {
  561 + return {
  562 + xtype: 'button',
  563 + id: 'epnTapGetBtn',
  564 + text: 'Get services',
  565 + disabled: true,
  566 + width: 140,
  567 + height: 50,
  568 + margin: 10
  569 + }
  570 + },
  571 +
  572 + /************
  573 + *** Grids ***
  574 + ************/
  575 +
  576 + /**
  577 + Create `epnTapGridsPanel`, an ExtJS Panel, containing `epnTapServicesGrid` and `epnTapGranulesGrid`.
  578 +
  579 + After the rendering of the grids, it triggers `epnTapModule.onWindowLoaded()`, which basically fill
  580 + `epnTapServicesGrid` for the first time.
  581 + */
  582 + createGridsPanel: function () {
  583 + return {
  584 + xtype: 'panel',
  585 + id: 'epnTapGridsPanel',
  586 + layout: 'fit',
  587 + flex: 1,
  588 + items: [{
  589 + xtype: 'container',
  590 + layout: { type: 'hbox', pack: 'start', align: 'stretch'},
  591 + items: [
  592 + this.createServicesGrid(),
  593 + this.createGranulesGrid()
  594 + ]
  595 + }]
  596 + }
  597 + },
  598 +
  599 + /**
  600 + Create `epnTapServicesGrid`, an ExtJS grid containing the EPN-TAP services matching with the filter form
  601 + (`serviceFilterPanel`).
  602 +
  603 + For each service, this grid displays:
  604 + - the service name;
  605 + - the number of granules matching with the filter.
  606 +
  607 + Other informations are available through an ExtJS Tooltip, on each row:
  608 + - short name;
  609 + - title;
  610 + - access URL.
  611 +
  612 + A click on a service triggers `EpnTapModule.onServiceSelected()`, which basically fills `GranulesGrid` by the
  613 + service granules.
  614 + */
  615 + createServicesGrid: function () {
  616 + return {
  617 + xtype: 'grid',
  618 + cls: 'epntap_grid',
  619 + id: 'epnTapServicesGrid',
  620 + title: 'Services',
  621 + store: Ext.data.StoreManager.lookup('servicesStore'),
  622 + flex: 1,
  623 + columns: [
  624 + {text: 'Name', dataIndex: 'short_name', flex: 1, renderer: 'service.text'},
  625 + {text: 'Nb res.', dataIndex: 'nb_results', width: 50, renderer: 'service.number'}
  626 + ],
  627 + viewConfig: {
  628 + getRowClass: function (record, index) {
  629 + var nb_res = record.get('nb_results')
  630 + if (nb_res == 0 || nb_res == -1) {
  631 + return 'disabled_row'
  632 + } else if (nb_res == -2) {
  633 + return 'error_row'
  634 + }
  635 + }
  636 + }
  637 + }
  638 + },
  639 +
  640 + /**
  641 + Create `epnTapGranulesGrid`, an ExtJS grid containing the granules of the selected service in
  642 + `epnTapServicesGrid`.
  643 +
  644 + For each granule, this grid displays:
  645 + - the row number;
  646 + - the dataproduct type;
  647 + - the target name;
  648 + - the min and max times;
  649 + - the format;
  650 + - the UID (granule identifier);
  651 + - the estimated size;
  652 + - the URL;
  653 + - the thumbnail.
  654 +
  655 + Each of these information are displayed in a specific rendering to improve user experience.
  656 + For more information about these parameters, see https://voparis-confluence.obspm.fr/display/VES/EPN-TAP+V2.0+parameters.
  657 +
  658 + Other informations are available through an ExtJS Tooltip on each row:
  659 + - currently only the granule thumbnail, in full size.
  660 +
  661 + A click on a granule triggers `EpnTapModule.onGranuleSelected()`.
  662 + */
  663 + createGranulesGrid: function () {
  664 + return {
  665 + xtype: 'grid',
  666 + cls: 'epntap_grid',
  667 + id: 'epnTapGranulesGrid',
  668 + title: 'Granules',
  669 + store: Ext.data.StoreManager.lookup('granulesStore'),
  670 + flex: 4,
  671 + plugins: {
  672 + ptype: 'bufferedrenderer',
  673 + trailingBufferZone: 20,
  674 + leadingBufferZone: 50
  675 + },
  676 + columns: []
  677 + }
  678 + }
  679 +})