aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2016-12-27 20:26:21 +1300
committerGitHub <noreply@github.com>2016-12-27 20:26:21 +1300
commitecdf23611e30707f1bc5f89420f9409acaa56652 (patch)
tree2bf13878f536d0b63e2b4645b57c264a171fb078 /web
parent082f7926b846f876613f27f21779b594e79ea0c7 (diff)
parent747c16cadc803fdf0e4de0dc331c84e29dd56e9c (diff)
downloadetbsa-traccar-web-ecdf23611e30707f1bc5f89420f9409acaa56652.tar.gz
etbsa-traccar-web-ecdf23611e30707f1bc5f89420f9409acaa56652.tar.bz2
etbsa-traccar-web-ecdf23611e30707f1bc5f89420f9409acaa56652.zip
Merge pull request #371 from Abyss777/charts
Charts implementation
Diffstat (limited to 'web')
-rw-r--r--web/app/Application.js17
-rw-r--r--web/app/Style.js7
-rw-r--r--web/app/model/Position.js13
-rw-r--r--web/app/store/DistanceUnits.js13
-rw-r--r--web/app/store/ReportChartTypes.js30
-rw-r--r--web/app/store/ReportTypes.js3
-rw-r--r--web/app/store/SpeedUnits.js13
-rw-r--r--web/app/view/MapMarkerController.js20
-rw-r--r--web/app/view/Report.js50
-rw-r--r--web/app/view/ReportConfigController.js1
-rw-r--r--web/app/view/ReportConfigDialog.js11
-rw-r--r--web/app/view/ReportController.js100
-rw-r--r--web/l10n/en.json3
-rw-r--r--web/load.js6
-rw-r--r--web/simple/index.html4
15 files changed, 237 insertions, 54 deletions
diff --git a/web/app/Application.js b/web/app/Application.js
index daa25b8..82caf1e 100644
--- a/web/app/Application.js
+++ b/web/app/Application.js
@@ -70,6 +70,7 @@ Ext.define('Traccar.Application', {
'ReportSummary',
'ReportTypes',
'ReportEventTypes',
+ 'ReportChartTypes',
'Statistics',
'DeviceImages',
'Calendars',
@@ -134,6 +135,22 @@ Ext.define('Traccar.Application', {
}
},
+ getReportColor: function (deviceId) {
+ var index, reportColor, device = Ext.getStore('Devices').getById(deviceId);
+ if (device) {
+ reportColor = device.get('attributes')['web.reportColor'];
+ }
+ if (reportColor) {
+ return reportColor;
+ } else {
+ index = 0;
+ if (deviceId !== undefined) {
+ index = deviceId % Traccar.Style.mapRouteColor.length;
+ }
+ return Traccar.Style.mapRouteColor[index];
+ }
+ },
+
showError: function (response) {
if (Ext.isString(response)) {
Ext.Msg.alert(Strings.errorTitle, response);
diff --git a/web/app/Style.js b/web/app/Style.js
index c5fce9b..4a77f56 100644
--- a/web/app/Style.js
+++ b/web/app/Style.js
@@ -78,5 +78,10 @@ Ext.define('Traccar.Style', {
coordinatePrecision: 6,
numberPrecision: 2,
- reportTagfieldWidth: 375
+ reportTagfieldWidth: 375,
+ reportGridStyle: 'borderTop: 1px solid lightgray',
+
+ chartPadding: '20 40 10 10',
+ chartMarkerRadius: 3,
+ chartMarkerHighlightScaling: 1.5
});
diff --git a/web/app/model/Position.js b/web/app/model/Position.js
index b2b12ee..362ca58 100644
--- a/web/app/model/Position.js
+++ b/web/app/model/Position.js
@@ -56,6 +56,12 @@ Ext.define('Traccar.model.Position', {
name: 'speed',
type: 'float'
}, {
+ name: 'speedConverted',
+ type: 'float',
+ calculate: function (data) {
+ return Ext.getStore('SpeedUnits').convertValue(data.speed, Traccar.app.getPreference('speedUnit'));
+ }
+ }, {
name: 'course',
type: 'float'
}, {
@@ -63,5 +69,12 @@ Ext.define('Traccar.model.Position', {
type: 'string'
}, {
name: 'attributes'
+ }, {
+ name: 'distanceConverted',
+ type: 'float',
+ calculate: function (data) {
+ return Ext.getStore('DistanceUnits').convertValue(data.attributes.distance,
+ Traccar.app.getPreference('distanceUnit'));
+ }
}]
});
diff --git a/web/app/store/DistanceUnits.js b/web/app/store/DistanceUnits.js
index cc9f1e9..0dcddbe 100644
--- a/web/app/store/DistanceUnits.js
+++ b/web/app/store/DistanceUnits.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,12 +33,21 @@ Ext.define('Traccar.store.DistanceUnits', {
factor: 0.000539957
}],
+ convertValue: function (value, unit) {
+ var model;
+ if (!unit) {
+ unit = 'km';
+ }
+ model = this.findRecord('key', unit);
+ return value * model.get('factor');
+ },
+
formatValue: function (value, unit) {
var model;
if (!unit) {
unit = 'km';
}
model = this.findRecord('key', unit);
- return (value * model.get('factor')).toFixed(2) + ' ' + model.get('name');
+ return this.convertValue(value, unit).toFixed(2) + ' ' + model.get('name');
}
});
diff --git a/web/app/store/ReportChartTypes.js b/web/app/store/ReportChartTypes.js
new file mode 100644
index 0000000..7a63fd0
--- /dev/null
+++ b/web/app/store/ReportChartTypes.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.store.ReportChartTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'speedConverted',
+ name: Strings.positionSpeed
+ }, {
+ key: 'distanceConverted',
+ name: Strings.positionDistance
+ }]
+});
diff --git a/web/app/store/ReportTypes.js b/web/app/store/ReportTypes.js
index 18b0c74..3305a10 100644
--- a/web/app/store/ReportTypes.js
+++ b/web/app/store/ReportTypes.js
@@ -31,5 +31,8 @@ Ext.define('Traccar.store.ReportTypes', {
}, {
key: 'summary',
name: Strings.reportSummary
+ }, {
+ key: 'charts',
+ name: Strings.reportCharts
}]
});
diff --git a/web/app/store/SpeedUnits.js b/web/app/store/SpeedUnits.js
index e29a42c..a14ab22 100644
--- a/web/app/store/SpeedUnits.js
+++ b/web/app/store/SpeedUnits.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,12 +33,21 @@ Ext.define('Traccar.store.SpeedUnits', {
factor: 1.15078
}],
+ convertValue: function (value, unit) {
+ var model;
+ if (!unit) {
+ unit = 'kn';
+ }
+ model = this.findRecord('key', unit);
+ return value * model.get('factor');
+ },
+
formatValue: function (value, unit) {
var model;
if (!unit) {
unit = 'kn';
}
model = this.findRecord('key', unit);
- return (value * model.get('factor')).toFixed(1) + ' ' + model.get('name');
+ return this.convertValue(value, unit).toFixed(1) + ' ' + model.get('name');
}
});
diff --git a/web/app/view/MapMarkerController.js b/web/app/view/MapMarkerController.js
index 510e11b..f8f0e43 100644
--- a/web/app/view/MapMarkerController.js
+++ b/web/app/view/MapMarkerController.js
@@ -276,26 +276,10 @@ Ext.define('Traccar.view.MapMarkerController', {
}
},
- getReportColor: function (deviceId) {
- var index, reportColor, device = Ext.getStore('Devices').getById(deviceId);
- if (device) {
- reportColor = device.get('attributes')['web.reportColor'];
- }
- if (reportColor) {
- return reportColor;
- } else {
- index = 0;
- if (deviceId !== undefined) {
- index = deviceId % Traccar.Style.mapRouteColor.length;
- }
- return Traccar.Style.mapRouteColor[index];
- }
- },
-
getRouteStyle: function (deviceId) {
return new ol.style.Style({
stroke: new ol.style.Stroke({
- color: this.getReportColor(deviceId),
+ color: Traccar.app.getReportColor(deviceId),
width: Traccar.Style.mapRouteWidth
})
});
@@ -325,7 +309,7 @@ Ext.define('Traccar.view.MapMarkerController', {
},
getReportMarker: function (deviceId, angle) {
- return this.getMarkerStyle(false, this.getReportColor(deviceId), angle, 'arrow');
+ return this.getMarkerStyle(false, Traccar.app.getReportColor(deviceId), angle, 'arrow');
},
resizeMarker: function (style, zoom) {
diff --git a/web/app/view/Report.js b/web/app/view/Report.js
index 5851c4c..339761e 100644
--- a/web/app/view/Report.js
+++ b/web/app/view/Report.js
@@ -16,7 +16,7 @@
*/
Ext.define('Traccar.view.Report', {
- extend: 'Ext.grid.Panel',
+ extend: 'Ext.panel.Panel',
xtype: 'reportView',
requires: [
@@ -67,17 +67,43 @@ Ext.define('Traccar.view.Report', {
}]
},
- listeners: {
- selectionchange: 'onSelectionChange'
- },
-
- forceFit: true,
+ layout: 'card',
- columns: {
- defaults: {
- minWidth: Traccar.Style.columnWidthNormal
+ items: [{
+ xtype: 'grid',
+ itemId: 'grid',
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+ forceFit: true,
+ columns: {
+ defaults: {
+ minWidth: Traccar.Style.columnWidthNormal
+ },
+ items: [
+ ]
+ },
+ style: Traccar.Style.reportGridStyle
+ }, {
+ xtype: 'cartesian',
+ itemId: 'chart',
+ plugins: {
+ ptype: 'chartitemevents',
+ moveEvents: true
+ },
+ store: 'ReportRoute',
+ axes: [{
+ title: Strings.reportCharts,
+ type: 'numeric',
+ position: 'left'
+ }, {
+ type: 'time',
+ position: 'bottom',
+ fields: ['fixTime']
+ }],
+ listeners: {
+ itemclick: 'onChartMarkerClick'
},
- items: [
- ]
- }
+ insetPadding: Traccar.Style.chartPadding
+ }]
});
diff --git a/web/app/view/ReportConfigController.js b/web/app/view/ReportConfigController.js
index 0ae7c0a..f76a082 100644
--- a/web/app/view/ReportConfigController.js
+++ b/web/app/view/ReportConfigController.js
@@ -57,6 +57,7 @@ Ext.define('Traccar.view.ReportConfigController', {
} else if (eventType.length === this.lookupReference('eventTypeField').getStore().getCount() - 1) {
eventType = [Traccar.store.ReportEventTypes.allEvents];
}
+ this.getView().callingPanel.chartType = this.lookupReference('chartTypeField').getValue();
this.getView().callingPanel.eventType = eventType;
this.getView().callingPanel.fromDate = this.lookupReference('fromDateField').getValue();
this.getView().callingPanel.fromTime = this.lookupReference('fromTimeField').getValue();
diff --git a/web/app/view/ReportConfigDialog.js b/web/app/view/ReportConfigDialog.js
index 39e6ae1..4afa929 100644
--- a/web/app/view/ReportConfigDialog.js
+++ b/web/app/view/ReportConfigDialog.js
@@ -56,6 +56,17 @@ Ext.define('Traccar.view.ReportConfigDialog', {
displayField: 'name',
queryMode: 'local'
}, {
+ fieldLabel: Strings.reportChartTypes,
+ xtype: 'combobox',
+ width: Traccar.Style.reportTagfieldWidth,
+ reference: 'chartTypeField',
+ store: 'ReportChartTypes',
+ hidden: true,
+ value: 'speedConverted',
+ valueField: 'key',
+ displayField: 'name',
+ queryMode: 'local'
+ }, {
xtype: 'fieldcontainer',
layout: 'hbox',
items: [{
diff --git a/web/app/view/ReportController.js b/web/app/view/ReportController.js
index 1f3f3a2..65eb698 100644
--- a/web/app/view/ReportController.js
+++ b/web/app/view/ReportController.js
@@ -43,6 +43,9 @@ Ext.define('Traccar.view.ReportController', {
'#ReportEvents': {
add: 'loadEvents',
load: 'loadEvents'
+ },
+ '#ReportRoute': {
+ load: 'loadRoute'
}
}
}
@@ -52,9 +55,18 @@ Ext.define('Traccar.view.ReportController', {
Traccar.app.showReports(false);
},
+ getGrid: function () {
+ return this.getView().getComponent('grid');
+ },
+
+ getChart: function () {
+ return this.getView().getComponent('chart');
+ },
+
onConfigureClick: function () {
var dialog = Ext.create('Traccar.view.ReportConfigDialog');
dialog.lookupReference('eventTypeField').setHidden(this.lookupReference('reportTypeField').getValue() !== 'events');
+ dialog.lookupReference('chartTypeField').setHidden(this.lookupReference('reportTypeField').getValue() !== 'charts');
dialog.callingPanel = this;
dialog.lookupReference('deviceField').setValue(this.deviceId);
dialog.lookupReference('groupField').setValue(this.groupId);
@@ -63,6 +75,9 @@ Ext.define('Traccar.view.ReportController', {
} else {
dialog.lookupReference('eventTypeField').setValue([Traccar.store.ReportEventTypes.allEvents]);
}
+ if (this.chartType !== undefined) {
+ dialog.lookupReference('chartTypeField').setValue(this.chartType);
+ }
if (this.fromDate !== undefined) {
dialog.lookupReference('fromDateField').setValue(this.fromDate);
}
@@ -85,7 +100,7 @@ Ext.define('Traccar.view.ReportController', {
time = this.fromDate && this.fromTime && this.toDate && this.toTime;
disabled = !reportType || !devices || !time;
this.lookupReference('showButton').setDisabled(disabled);
- this.lookupReference('exportButton').setDisabled(disabled);
+ this.lookupReference('exportButton').setDisabled(reportType === 'charts' || disabled);
},
onReportClick: function (button) {
@@ -103,7 +118,12 @@ Ext.define('Traccar.view.ReportController', {
this.toTime.getHours(), this.toTime.getMinutes(), this.toTime.getSeconds(), this.toTime.getMilliseconds());
if (button.reference === 'showButton') {
- store = this.getView().getStore();
+ if (reportType === 'charts') {
+ store = this.getChart().getStore();
+ this.getChart().setSeries([]);
+ } else {
+ store = this.getGrid().getStore();
+ }
store.load({
params: {
deviceId: this.deviceId,
@@ -114,7 +134,7 @@ Ext.define('Traccar.view.ReportController', {
}
});
} else if (button.reference === 'exportButton') {
- url = this.getView().getStore().getProxy().url;
+ url = this.getGrid().getStore().getProxy().url;
this.downloadFile(url, {
deviceId: this.deviceId,
groupId: this.groupId,
@@ -132,10 +152,13 @@ Ext.define('Traccar.view.ReportController', {
},
clearReport: function (reportType) {
- this.getView().getStore().removeAll();
+ this.getGrid().getStore().removeAll();
if (reportType === 'trips' || reportType === 'events') {
Ext.getStore('ReportRoute').removeAll();
}
+ if (reportType === 'charts') {
+ this.getChart().getStore().removeAll();
+ }
},
onSelectionChange: function (selected) {
@@ -154,7 +177,7 @@ Ext.define('Traccar.view.ReportController', {
selectDevice: function (device) {
if (device) {
- this.getView().getSelectionModel().deselectAll();
+ this.getGrid().getSelectionModel().deselectAll();
}
},
@@ -162,12 +185,12 @@ Ext.define('Traccar.view.ReportController', {
var positionEvent, reportType = this.lookupReference('reportTypeField').getValue();
if (object instanceof Traccar.model.Position) {
if (reportType === 'route') {
- this.getView().getSelectionModel().select([object], false, true);
- this.getView().getView().focusRow(object);
+ this.getGrid().getSelectionModel().select([object], false, true);
+ this.getGrid().getView().focusRow(object);
} else if (reportType === 'events') {
- positionEvent = this.getView().getStore().findRecord('positionId', object.get('id'), 0, false, true, true);
- this.getView().getSelectionModel().select([positionEvent], false, true);
- this.getView().getView().focusRow(positionEvent);
+ positionEvent = this.getGrid().getStore().findRecord('positionId', object.get('id'), 0, false, true, true);
+ this.getGrid().getSelectionModel().select([positionEvent], false, true);
+ this.getGrid().getView().focusRow(positionEvent);
}
}
},
@@ -223,6 +246,45 @@ Ext.define('Traccar.view.ReportController', {
}
},
+ loadRoute: function (store) {
+ var i, deviceIds, chartSeries, deviceStore;
+ if (this.lookupReference('reportTypeField').getValue() === 'charts') {
+ this.getChart().getAxes()[0].setTitle(
+ Ext.getStore('ReportChartTypes').findRecord('key', this.chartType).get('name'));
+ chartSeries = [];
+ deviceIds = store.collect('deviceId');
+ for (i = 0; i < deviceIds.length; i++) {
+ deviceStore = new Ext.create('Ext.data.ChainedStore', {
+ source: 'ReportRoute',
+ filters: [{
+ property: 'deviceId',
+ value : deviceIds[i]
+ }]
+ });
+ chartSeries.push({
+ type: 'line',
+ store: deviceStore,
+ yField: this.chartType,
+ xField: 'fixTime',
+ highlightCfg: {
+ scaling: Traccar.Style.chartMarkerHighlightScaling
+ },
+ colors: [Traccar.app.getReportColor(deviceIds[i])],
+ marker: {
+ type: 'circle',
+ radius: Traccar.Style.chartMarkerRadius,
+ fill: Traccar.app.getReportColor(deviceIds[i])
+ }
+ });
+ }
+ this.getChart().setSeries(chartSeries);
+ }
+ },
+
+ onChartMarkerClick: function (chart, item) {
+ this.fireEvent('selectreport', item.record, true);
+ },
+
showSingleEvent: function (eventId) {
this.lookupReference('reportTypeField').setValue('events');
Ext.getStore('Events').load({
@@ -239,8 +301,8 @@ Ext.define('Traccar.view.ReportController', {
this.getView().expand();
}
}
- this.getView().getSelectionModel().select([records[0]], false, true);
- this.getView().getView().focusRow(records[0]);
+ this.getGrid().getSelectionModel().select([records[0]], false, true);
+ this.getGrid().getView().focusRow(records[0]);
}
}
}
@@ -289,13 +351,19 @@ Ext.define('Traccar.view.ReportController', {
}
if (newValue === 'route') {
- this.getView().reconfigure('ReportRoute', this.routeColumns);
+ this.getGrid().reconfigure('ReportRoute', this.routeColumns);
+ this.getView().getLayout().setActiveItem('grid');
} else if (newValue === 'events') {
- this.getView().reconfigure('ReportEvents', this.eventsColumns);
+ this.getGrid().reconfigure('ReportEvents', this.eventsColumns);
+ this.getView().getLayout().setActiveItem('grid');
} else if (newValue === 'summary') {
- this.getView().reconfigure('ReportSummary', this.summaryColumns);
+ this.getGrid().reconfigure('ReportSummary', this.summaryColumns);
+ this.getView().getLayout().setActiveItem('grid');
} else if (newValue === 'trips') {
- this.getView().reconfigure('ReportTrips', this.tripsColumns);
+ this.getGrid().reconfigure('ReportTrips', this.tripsColumns);
+ this.getView().getLayout().setActiveItem('grid');
+ } else if (newValue === 'charts') {
+ this.getView().getLayout().setActiveItem('chart');
}
this.updateButtons();
diff --git a/web/l10n/en.json b/web/l10n/en.json
index 109482b..5ac1328 100644
--- a/web/l10n/en.json
+++ b/web/l10n/en.json
@@ -104,6 +104,7 @@
"positionCourse": "Course",
"positionAddress": "Address",
"positionProtocol": "Protocol",
+ "positionDistance": "Distance",
"serverTitle": "Server Settings",
"serverZoom": "Zoom",
"serverRegistration": "Registration",
@@ -184,8 +185,10 @@
"reportEvents": "Events",
"reportTrips": "Trips",
"reportSummary": "Summary",
+ "reportCharts": "Charts",
"reportConfigure": "Configure",
"reportEventTypes": "Event Types",
+ "reportChartTypes": "Chart Types",
"reportExport": "Export",
"reportDeviceName": "Device Name",
"reportAverageSpeed": "Average Speed",
diff --git a/web/load.js b/web/load.js
index 7aee735..ecf6f23 100644
--- a/web/load.js
+++ b/web/load.js
@@ -117,18 +117,22 @@
extjsVersion = '6.2.0';
fontAwesomeVersion = '4.7.0';
- olVersion = '3.20.0';
+ olVersion = '3.20.1';
if (debugMode) {
addScriptFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/ext-all-debug.js');
+ addScriptFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/packages/charts/classic/charts-debug.js');
} else {
addScriptFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/ext-all.js');
+ addScriptFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/packages/charts/classic/charts.js');
}
addScriptFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/classic/locale/locale-' + locale.languages[locale.language].code + '.js');
addStyleFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/classic/theme-triton/resources/theme-triton-all.css');
addScriptFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/classic/theme-triton/theme-triton.js');
+ addStyleFile('//cdnjs.cloudflare.com/ajax/libs/extjs/' + extjsVersion + '/packages/charts/classic/triton/resources/charts-all.css');
+
addStyleFile('//cdnjs.cloudflare.com/ajax/libs/font-awesome/' + fontAwesomeVersion + '/css/font-awesome.min.css');
addStyleFile('//cdnjs.cloudflare.com/ajax/libs/ol3/' + olVersion + '/ol.css');
diff --git a/web/simple/index.html b/web/simple/index.html
index d52e518..0130067 100644
--- a/web/simple/index.html
+++ b/web/simple/index.html
@@ -4,11 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Traccar</title>
-<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.0/ol.css" type="text/css">
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.1/ol.css" type="text/css">
</head>
<body style="margin: 0; padding: 0;">
<div id="map" style="width: 100%; height: 100%; position:fixed;"></div>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.0/ol.js" type="text/javascript"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.1/ol.js" type="text/javascript"></script>
<script id="loadScript" src="app.js"></script>
</body>
</html>