├── doc
├── .gitignore
├── slides
│ ├── deckjs
│ │ ├── .gitignore
│ │ ├── extensions
│ │ │ ├── hash
│ │ │ │ ├── deck.hash.html
│ │ │ │ ├── deck.hash.css
│ │ │ │ └── deck.hash.js
│ │ │ ├── status
│ │ │ │ ├── deck.status.html
│ │ │ │ ├── deck.status.css
│ │ │ │ └── deck.status.js
│ │ │ ├── navigation
│ │ │ │ ├── deck.navigation.html
│ │ │ │ ├── deck.navigation.css
│ │ │ │ └── deck.navigation.js
│ │ │ ├── goto
│ │ │ │ ├── deck.goto.html
│ │ │ │ ├── deck.goto.css
│ │ │ │ └── deck.goto.js
│ │ │ ├── scale
│ │ │ │ ├── deck.scale.css
│ │ │ │ └── deck.scale.js
│ │ │ └── menu
│ │ │ │ ├── deck.menu.css
│ │ │ │ └── deck.menu.js
│ │ └── themes
│ │ │ ├── style
│ │ │ ├── slides.custom.css
│ │ │ ├── swiss.css
│ │ │ └── neon.css
│ │ │ └── transition
│ │ │ ├── fade.css
│ │ │ ├── horizontal-slide.css
│ │ │ └── vertical-slide.css
│ └── img
│ │ ├── 52n.jpg
│ │ ├── mail.png
│ │ ├── port.jpg
│ │ ├── github.jpeg
│ │ ├── oracle.jpg
│ │ ├── twitter.png
│ │ ├── thinclient.png
│ │ ├── timeseries.png
│ │ ├── sos-js-client.png
│ │ ├── logo_geomatico_256.png
│ │ └── s_logo.svg
├── img
│ ├── panel.png
│ ├── map-custom-base-layer.png
│ ├── map-with-custom-popup.png
│ ├── map-permanent-tooltips.png
│ ├── map-no-features-no-properties.png
│ └── map-some-features-some-properties.png
├── makeall.sh
├── ca
│ ├── index.rst
│ └── contribute.rst
├── en
│ └── index.rst
└── es
│ ├── index.rst
│ └── contribute.rst
├── src
├── assets
│ ├── CNAME
│ ├── config.js
│ ├── favicon.ico
│ ├── fonts
│ │ ├── flaticon.eot
│ │ ├── flaticon.ttf
│ │ ├── flaticon.woff
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ ├── glyphicons-halflings-regular.woff2
│ │ └── flaticon.css
│ ├── font-awesome-4.7.0
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ └── fontawesome-webfont.woff2
│ ├── css
│ │ ├── images
│ │ │ ├── ui-icons_004276_256x240.png
│ │ │ ├── ui-icons_4A4A4A_256x240.png
│ │ │ ├── ui-icons_ffffff_256x240.png
│ │ │ ├── ui-bg_flat_0_333333_40x100.png
│ │ │ ├── ui-bg_flat_65_ffffff_40x100.png
│ │ │ ├── ui-bg_flat_75_ffffff_40x100.png
│ │ │ ├── ui-bg_glass_55_fbf8ee_1x400.png
│ │ │ ├── ui-bg_dots-small_65_a6a6a6_2x2.png
│ │ │ ├── ui-bg_diagonals-thick_75_f3d8d8_40x40.png
│ │ │ ├── ui-bg_highlight-hard_100_eeeeee_1x100.png
│ │ │ ├── ui-bg_highlight-hard_100_f6f6f6_1x100.png
│ │ │ └── ui-bg_highlight-soft_15_4A4A4A_1x100.png
│ │ └── home.css
│ ├── examples
│ │ ├── sos.html
│ │ ├── simple-remote.html
│ │ ├── simple-local.html
│ │ ├── map.html
│ │ ├── meteo.apb.es
│ │ │ ├── DataBrowser.html
│ │ │ ├── index.html
│ │ │ ├── TorreControl.html
│ │ │ └── XMVQA.html
│ │ └── sync-charts.html
│ ├── widget
│ │ └── index.html
│ ├── img
│ │ ├── s_logo.svg
│ │ ├── gridtile.svg
│ │ └── logo.svg
│ ├── wizard
│ │ └── index.html
│ └── index.html
├── js
│ ├── jQuery-globals.js
│ ├── main.js
│ ├── widget
│ │ ├── jqgrid.css
│ │ ├── gauge.js
│ │ ├── progressbar.js
│ │ ├── thermometer.js
│ │ ├── panel.js
│ │ ├── status.js
│ │ ├── compass.js
│ │ ├── jqgrid.js
│ │ ├── table.js
│ │ ├── timechart.js
│ │ ├── progressbar.css
│ │ └── windrose.js
│ ├── locale-date.js
│ ├── widget-common.js
│ ├── pages
│ │ ├── wizard.css
│ │ └── meteo.css
│ ├── i18n.js
│ ├── SensorWidgets.css
│ ├── sos-data-access.js
│ ├── SensorWidget.js
│ └── translations.json
└── test
│ ├── fixtures
│ ├── getFeatureOfInterestRequest.xml
│ ├── describeSensorRequest.xml
│ ├── getCapabilitiesRequest.xml
│ ├── getObservationResponse.json
│ ├── getDataAvailabilityRequest.xml
│ ├── getObservationRequest.xml
│ ├── getDataAvailabilityResponse.json
│ ├── getLatestObservationsRequest.xml
│ ├── getFeatureOfInterestResponse.json
│ ├── getObservationResponse.xml
│ ├── getDataAvailabilityResponse.xml
│ ├── getFeatureOfInterestResponse.xml
│ └── getCapabilitiesResponse.json
│ └── XML.test.js
├── .gitignore
├── .eslintrc
├── LICENSE
├── webpack.config.js
├── karma.config.js
├── package.json
└── README.rst
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | _build/
2 |
--------------------------------------------------------------------------------
/src/assets/CNAME:
--------------------------------------------------------------------------------
1 | sensors.fonts.cat
--------------------------------------------------------------------------------
/doc/slides/deckjs/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .sass-cache/
3 | progress/
--------------------------------------------------------------------------------
/src/assets/config.js:
--------------------------------------------------------------------------------
1 | window.sosUrl = 'https://demo.geomatico.es/52n-sos/service';
2 |
--------------------------------------------------------------------------------
/doc/img/panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/img/panel.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
4 | # IDEA
5 | sensor-widgets.iml
6 | .project
7 | .idea/
8 |
--------------------------------------------------------------------------------
/doc/slides/img/52n.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/52n.jpg
--------------------------------------------------------------------------------
/doc/slides/img/mail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/mail.png
--------------------------------------------------------------------------------
/doc/slides/img/port.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/port.jpg
--------------------------------------------------------------------------------
/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/favicon.ico
--------------------------------------------------------------------------------
/doc/slides/img/github.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/github.jpeg
--------------------------------------------------------------------------------
/doc/slides/img/oracle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/oracle.jpg
--------------------------------------------------------------------------------
/doc/slides/img/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/twitter.png
--------------------------------------------------------------------------------
/doc/slides/img/thinclient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/thinclient.png
--------------------------------------------------------------------------------
/doc/slides/img/timeseries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/timeseries.png
--------------------------------------------------------------------------------
/src/assets/fonts/flaticon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/flaticon.eot
--------------------------------------------------------------------------------
/src/assets/fonts/flaticon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/flaticon.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/flaticon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/flaticon.woff
--------------------------------------------------------------------------------
/src/js/jQuery-globals.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | window.$ = $;
4 | window.jQuery = $;
5 | export default $;
6 |
--------------------------------------------------------------------------------
/doc/img/map-custom-base-layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/img/map-custom-base-layer.png
--------------------------------------------------------------------------------
/doc/img/map-with-custom-popup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/img/map-with-custom-popup.png
--------------------------------------------------------------------------------
/doc/makeall.sh:
--------------------------------------------------------------------------------
1 | for lang in en es ca; do
2 | make -C $lang html
3 | xdg-open $lang/_build/html/index.html
4 | done
5 |
--------------------------------------------------------------------------------
/doc/slides/img/sos-js-client.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/sos-js-client.png
--------------------------------------------------------------------------------
/doc/img/map-permanent-tooltips.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/img/map-permanent-tooltips.png
--------------------------------------------------------------------------------
/doc/slides/img/logo_geomatico_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/slides/img/logo_geomatico_256.png
--------------------------------------------------------------------------------
/doc/img/map-no-features-no-properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/img/map-no-features-no-properties.png
--------------------------------------------------------------------------------
/doc/img/map-some-features-some-properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/doc/img/map-some-features-some-properties.png
--------------------------------------------------------------------------------
/src/assets/font-awesome-4.7.0/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/font-awesome-4.7.0/FontAwesome.otf
--------------------------------------------------------------------------------
/src/assets/css/images/ui-icons_004276_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-icons_004276_256x240.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-icons_4A4A4A_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-icons_4A4A4A_256x240.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/src/assets/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/src/assets/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_flat_0_333333_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_flat_0_333333_40x100.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_flat_65_ffffff_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_flat_65_ffffff_40x100.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_flat_75_ffffff_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_flat_75_ffffff_40x100.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_glass_55_fbf8ee_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_glass_55_fbf8ee_1x400.png
--------------------------------------------------------------------------------
/src/assets/font-awesome-4.7.0/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/font-awesome-4.7.0/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/src/assets/font-awesome-4.7.0/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/font-awesome-4.7.0/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_dots-small_65_a6a6a6_2x2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_dots-small_65_a6a6a6_2x2.png
--------------------------------------------------------------------------------
/src/assets/font-awesome-4.7.0/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/font-awesome-4.7.0/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/assets/font-awesome-4.7.0/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/font-awesome-4.7.0/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/doc/ca/index.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Sensor Widgets
3 | ==============
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | sos
9 | use
10 | widgets
11 | contribute
12 |
--------------------------------------------------------------------------------
/doc/en/index.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Sensor Widgets
3 | ==============
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | sos
9 | use
10 | widgets
11 | contribute
12 |
--------------------------------------------------------------------------------
/doc/es/index.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Sensor Widgets
3 | ==============
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | sos
9 | use
10 | widgets
11 | contribute
12 |
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_diagonals-thick_75_f3d8d8_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_diagonals-thick_75_f3d8d8_40x40.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_highlight-hard_100_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_highlight-hard_100_eeeeee_1x100.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_highlight-hard_100_f6f6f6_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_highlight-hard_100_f6f6f6_1x100.png
--------------------------------------------------------------------------------
/src/assets/css/images/ui-bg_highlight-soft_15_4A4A4A_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oscarfonts/sensor-widgets/HEAD/src/assets/css/images/ui-bg_highlight-soft_15_4A4A4A_1x100.png
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/hash/deck.hash.html:
--------------------------------------------------------------------------------
1 |
2 | #
--------------------------------------------------------------------------------
/doc/ca/contribute.rst:
--------------------------------------------------------------------------------
1 | ==========================
2 | Com contribuir al projecte
3 | ==========================
4 |
5 | La documentació per a desenvolupadors està disponible únicament `en anglès <../../en/latest/ contribute.html>`_.
6 |
--------------------------------------------------------------------------------
/doc/es/contribute.rst:
--------------------------------------------------------------------------------
1 | ===========================
2 | Cómo contribuir al proyecto
3 | ===========================
4 |
5 | La documentación para desarrolladores está disponible únicamente `en inglés <../../en/latest/contribute.html>`_.
6 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "mocha": true
5 | },
6 | "parserOptions": {
7 | "ecmaVersion": 11
8 | },
9 | "extends": ["airbnb-base"],
10 | "rules": {
11 | "no-console": "off"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/status/deck.status.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | /
5 |
6 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/navigation/deck.navigation.html:
--------------------------------------------------------------------------------
1 |
2 | ←
3 | →
--------------------------------------------------------------------------------
/src/test/fixtures/getFeatureOfInterestRequest.xml:
--------------------------------------------------------------------------------
1 |
5 | http://sensors.portdebarcelona.cat/def/weather/procedure
6 |
7 |
8 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/hash/deck.hash.css:
--------------------------------------------------------------------------------
1 | .deck-container .deck-permalink {
2 | display: none;
3 | position: absolute;
4 | z-index: 4;
5 | bottom: 30px;
6 | right: 0;
7 | width: 48px;
8 | text-align: center;
9 | }
10 |
11 | .no-history .deck-container:hover .deck-permalink {
12 | display: block;
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | import SensorWidget from './SensorWidget';
2 | import sos from './SOS';
3 |
4 | // 'Leak' SensorWidget to global scope.
5 | window.SensorWidget = (...args) => {
6 | window.onload = () => SensorWidget.apply(this, args);
7 | };
8 |
9 | // Expose SOS as well.
10 | window.getSOS = (callback) => {
11 | callback(sos);
12 | };
13 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/status/deck.status.css:
--------------------------------------------------------------------------------
1 | .deck-container .deck-status {
2 | position: absolute;
3 | bottom: 10px;
4 | right: 5px;
5 | color: #888;
6 | z-index: 3;
7 | margin: 0;
8 | }
9 |
10 | body.deck-container .deck-status {
11 | position: fixed;
12 | }
13 |
14 | @media print {
15 | .deck-status {
16 | display: none;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/goto/deck.goto.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/js/widget/jqgrid.css:
--------------------------------------------------------------------------------
1 | table.results {
2 | border: 1px solid black;
3 | margin-left: 10px;
4 | margin-top: 10px;
5 | }
6 | table.results td {
7 | background: #DDD;
8 | color: #333;
9 | padding: 3px;
10 | text-align: left;
11 | }
12 | table.results th {
13 | background: #333;
14 | color: #DDD;
15 | padding: 3px;
16 | }
17 | #gbox_grid1 {
18 | margin: auto;
19 | }
--------------------------------------------------------------------------------
/src/test/fixtures/describeSensorRequest.xml:
--------------------------------------------------------------------------------
1 |
5 | http://sensors.portdebarcelona.cat/def/weather/procedure
6 | http://www.opengis.net/sensorML/1.0.1
7 |
8 |
--------------------------------------------------------------------------------
/src/test/fixtures/getCapabilitiesRequest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 | 2.0.0
7 |
8 |
9 | Contents
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/assets/examples/sos.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sensor Widgets Simple Example
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/js/locale-date.js:
--------------------------------------------------------------------------------
1 | import i18n from './i18n';
2 |
3 | const date = {
4 | utc: false,
5 | locale: navigator.language || navigator.browserLanguage,
6 | };
7 |
8 | export default {
9 | display(d) {
10 | if (!d) {
11 | return i18n.t('(no date)');
12 | }
13 | if (date.utc) {
14 | return `${d.toLocaleString(date.locale, {
15 | timeZone: 'UTC',
16 | })} UTC`;
17 | }
18 | return d.toLocaleString(date.locale);
19 | },
20 | locale(l) {
21 | if (l) {
22 | date.locale = l;
23 | }
24 | return date.locale;
25 | },
26 | utc(u) {
27 | if (typeof u !== 'undefined') {
28 | date.utc = u;
29 | }
30 | return date.utc;
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/src/assets/examples/simple-remote.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sensor Widgets Simple Example
4 |
5 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/assets/examples/simple-local.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sensor Widgets Simple Example
4 |
5 |
6 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/fixtures/getObservationResponse.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "procedure" : "http://sensors.portdebarcelona.cat/def/weather/procedure",
4 | "observableProperty" : "http://sensors.portdebarcelona.cat/def/weather/properties#31",
5 | "featureOfInterest" : {
6 | "identifier" : {
7 | "codespace" : "http://www.opengis.net/def/nil/OGC/0/unknown",
8 | "value" : "http://sensors.portdebarcelona.cat/def/weather/features#02"
9 | },
10 | "name" : {
11 | "codespace" : "http://www.opengis.net/def/nil/OGC/0/unknown",
12 | "value" : "Sirena"
13 | }
14 | },
15 | "phenomenonTime" : "2020-10-05T16:06:00.000Z",
16 | "resultTime" : "2020-10-05T16:06:00.000Z",
17 | "result" : {
18 | "uom" : "º",
19 | "value" : 83.0
20 | }
21 | }
22 | ]
23 |
--------------------------------------------------------------------------------
/src/test/fixtures/getDataAvailabilityRequest.xml:
--------------------------------------------------------------------------------
1 |
5 | http://sensors.portdebarcelona.cat/def/weather/procedure
6 | http://sensors.portdebarcelona.cat/def/weather/offerings#10M
7 | http://sensors.portdebarcelona.cat/def/weather/features#01
8 | http://sensors.portdebarcelona.cat/def/weather/properties#30
9 | http://sensors.portdebarcelona.cat/def/weather/properties#30M
10 | http://sensors.portdebarcelona.cat/def/weather/properties#30N
11 |
12 |
--------------------------------------------------------------------------------
/src/assets/css/home.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 70px;
3 | padding-bottom: 30px;
4 | }
5 |
6 | .theme-dropdown .dropdown-menu {
7 | position: static;
8 | display: block;
9 | margin-bottom: 20px;
10 | }
11 |
12 | pre {
13 | overflow: auto;
14 | word-wrap: normal;
15 | white-space: pre;
16 | font-size: 11px;
17 | color: black;
18 | }
19 |
20 | .theme-showcase > p > .btn {
21 | margin: 5px 0;
22 | }
23 |
24 | .anchor {
25 | display: block;
26 | position: relative;
27 | top: -70px;
28 | visibility: hidden;
29 | }
30 |
31 | .widget-container {
32 | height: 450px;
33 | }
34 |
35 | /* Override progressbar label positioning */
36 | .progressbar.widget .min,
37 | .progressbar.widget .max {
38 | top: 5px !important;
39 | }
40 |
41 | .tab-content {
42 | border: 1px solid #ecf0f1;
43 | border-top: none;
44 | border-radius: 0 0 4px 4px;
45 | padding: 15px;
46 | padding-bottom: 0;
47 | }
--------------------------------------------------------------------------------
/src/test/fixtures/getObservationRequest.xml:
--------------------------------------------------------------------------------
1 |
7 | http://sensors.portdebarcelona.cat/def/weather/offerings#1M
8 | http://sensors.portdebarcelona.cat/def/weather/properties#31
9 |
10 |
11 | resultTime
12 |
13 | latest
14 |
15 |
16 |
17 | http://sensors.portdebarcelona.cat/def/weather/features#02
18 |
19 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/goto/deck.goto.css:
--------------------------------------------------------------------------------
1 | .deck-container .goto-form {
2 | position: absolute;
3 | z-index: 3;
4 | bottom: 10px;
5 | left: 50%;
6 | height: 1.75em;
7 | margin: 0 0 0 -9.125em;
8 | line-height: 1.75em;
9 | padding: 0.625em;
10 | display: none;
11 | background: #ccc;
12 | overflow: hidden;
13 | }
14 | .borderradius .deck-container .goto-form {
15 | -webkit-border-radius: 10px;
16 | -moz-border-radius: 10px;
17 | border-radius: 10px;
18 | }
19 | .deck-container .goto-form label {
20 | font-weight: bold;
21 | }
22 | .deck-container .goto-form label, .deck-container .goto-form input {
23 | display: inline-block;
24 | font-family: inherit;
25 | }
26 |
27 | .deck-goto .goto-form {
28 | display: block;
29 | }
30 |
31 | #goto-slide {
32 | width: 8.375em;
33 | margin: 0 0.625em;
34 | height: 1.4375em;
35 | }
36 |
37 | @media print {
38 | .goto-form, #goto-slide {
39 | display: none !important;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/js/widget-common.js:
--------------------------------------------------------------------------------
1 | import ld from './locale-date';
2 |
3 | function loadCSS(url) {
4 | const link = document.createElement('link');
5 | link.setAttribute('rel', 'stylesheet');
6 | link.setAttribute('type', 'text/css');
7 | link.setAttribute('href', url);
8 | if (typeof link !== 'undefined') {
9 | document.getElementsByTagName('head')[0].appendChild(link);
10 | }
11 | }
12 |
13 | export default {
14 | inputs: ['service', 'offering'],
15 | optional_inputs: ['footnote', 'custom_css_url', 'display_utc_times'],
16 |
17 | init(config, el) {
18 | if (config.custom_css_url !== undefined) {
19 | loadCSS(config.custom_css_url);
20 | }
21 | if (config.footnote !== undefined && el.querySelector('.footnote')) {
22 | // eslint-disable-next-line no-param-reassign
23 | el.querySelector('.footnote').innerHTML = config.footnote;
24 | }
25 | if (config.display_utc_times) {
26 | ld.utc(true);
27 | }
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/scale/deck.scale.css:
--------------------------------------------------------------------------------
1 | /* Remove this line if you are embedding deck.js in a page and
2 | using the scale extension. */
3 | .csstransforms {
4 | overflow: hidden;
5 | }
6 |
7 | .csstransforms .deck-container.deck-scale:not(.deck-menu) > .slide {
8 | -webkit-box-sizing: padding-box;
9 | -moz-box-sizing: padding-box;
10 | box-sizing: padding-box;
11 | width: 100%;
12 | padding-bottom: 20px;
13 | }
14 | .csstransforms .deck-container.deck-scale:not(.deck-menu) > .slide > .deck-slide-scaler {
15 | -webkit-transform-origin: 50% 0;
16 | -moz-transform-origin: 50% 0;
17 | -o-transform-origin: 50% 0;
18 | -ms-transform-origin: 50% 0;
19 | transform-origin: 50% 0;
20 | }
21 |
22 | .csstransforms .deck-container.deck-menu .deck-slide-scaler {
23 | -webkit-transform: none !important;
24 | -moz-transform: none !important;
25 | -o-transform: none !important;
26 | -ms-transform: none !important;
27 | transform: none !important;
28 | }
29 |
--------------------------------------------------------------------------------
/src/assets/widget/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Sensor Widgets
8 |
9 |
21 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Oscar Fonts
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/js/pages/wizard.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 70px;
3 | padding-bottom: 30px;
4 | }
5 |
6 | .col-fixed-200 {
7 | padding-left: 15px;
8 | padding-right: 15px;
9 | width: 200px;
10 | float: left;
11 | }
12 |
13 | .left-200 {
14 | margin-left: 200px;
15 | }
16 |
17 | .tab-content {
18 | border: 1px solid #ecf0f1;
19 | border-top: none;
20 | border-radius: 0 0 4px 4px;
21 | padding: 15px;
22 | padding-bottom: 0;
23 | }
24 |
25 | pre {
26 | overflow: auto;
27 | word-wrap: normal;
28 | white-space: pre;
29 | font-size: 11px;
30 | color: black;
31 | }
32 |
33 | .well {
34 | margin-bottom: 0;
35 | }
36 |
37 | #widget-view,
38 | #widget-container,
39 | .map.widget,
40 | .timechart.widget {
41 | height: 100%;
42 | padding: 0;
43 | }
44 |
45 | select[multiple] {
46 | height: 225px !important;
47 | }
48 |
49 | #widget-container {
50 | z-index: 998;
51 | }
52 |
53 | .ui-resizable-handle {
54 | z-index: 999 !important;
55 | }
56 |
57 | .ui-resizable-helper {
58 | border-radius: 4px;
59 | border: 2px dashed #e74c3c;
60 | }
61 |
62 | .calendar-time:after {
63 | content: 'UTC'
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/fixtures/getDataAvailabilityResponse.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "featureOfInterest" : "http://sensors.portdebarcelona.cat/def/weather/features#01",
4 | "procedure" : "http://sensors.portdebarcelona.cat/def/weather/procedure",
5 | "observedProperty" : "http://sensors.portdebarcelona.cat/def/weather/properties#30N",
6 | "phenomenonTime" : [
7 | "2020-08-03T16:10:00.000Z",
8 | "2020-09-28T15:50:00.000Z"
9 | ]
10 | },
11 | {
12 | "featureOfInterest" : "http://sensors.portdebarcelona.cat/def/weather/features#01",
13 | "procedure" : "http://sensors.portdebarcelona.cat/def/weather/procedure",
14 | "observedProperty" : "http://sensors.portdebarcelona.cat/def/weather/properties#30M",
15 | "phenomenonTime" : [
16 | "2020-08-03T16:10:00.000Z",
17 | "2020-09-28T15:50:00.000Z"
18 | ]
19 | },
20 | {
21 | "featureOfInterest" : "http://sensors.portdebarcelona.cat/def/weather/features#01",
22 | "procedure" : "http://sensors.portdebarcelona.cat/def/weather/procedure",
23 | "observedProperty" : "http://sensors.portdebarcelona.cat/def/weather/properties#30",
24 | "phenomenonTime" : [
25 | "2020-08-03T16:10:00.000Z",
26 | "2020-09-28T15:50:00.000Z"
27 | ]
28 | }
29 | ]
30 |
--------------------------------------------------------------------------------
/src/test/fixtures/getLatestObservationsRequest.xml:
--------------------------------------------------------------------------------
1 |
9 | http://sensors.portdebarcelona.cat/def/weather/procedure
10 | http://sensors.portdebarcelona.cat/def/weather/offerings#1M
11 | http://sensors.portdebarcelona.cat/def/weather/properties#33M
12 |
13 |
14 | resultTime
15 |
16 | latest
17 |
18 |
19 |
20 |
21 | http://sensors.portdebarcelona.cat/def/weather/features#02
22 |
23 | http://www.opengis.net/om/2.0
24 |
25 |
--------------------------------------------------------------------------------
/src/test/fixtures/getFeatureOfInterestResponse.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "identifier" : {
4 | "codespace" : "",
5 | "value" : "http://sensors.portdebarcelona.cat/def/weather/features#01"
6 | },
7 | "name" : {
8 | "codespace" : "http://www.opengis.net/def/nil/OGC/0/unknown",
9 | "value" : "Dispensari"
10 | },
11 | "sampledFeature" : "http://planolws.portdebarcelona.cat/geoserver/wfs?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=WS_AGR_20131025:zones_port_area_polygon&SRSNAME=EPSG:4326",
12 | "geometry" : {
13 | "type" : "Point",
14 | "coordinates" : [
15 | 41.38001,
16 | 2.18221
17 | ]
18 | }
19 | },
20 | {
21 | "identifier" : {
22 | "codespace" : "",
23 | "value" : "http://sensors.portdebarcelona.cat/def/weather/features#02"
24 | },
25 | "name" : {
26 | "codespace" : "http://www.opengis.net/def/nil/OGC/0/unknown",
27 | "value" : "Sirena"
28 | },
29 | "sampledFeature" : "http://planolws.portdebarcelona.cat/geoserver/wfs?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=WS_AGR_20131025:zones_port_area_polygon&SRSNAME=EPSG:4326",
30 | "geometry" : {
31 | "type" : "Point",
32 | "coordinates" : [
33 | 41.34121,
34 | 2.16611
35 | ]
36 | }
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/src/assets/fonts/flaticon.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Flaticon";
3 | src: url("flaticon.eot");
4 | src: url("flaticon.eot#iefix") format("embedded-opentype"), url("flaticon.woff") format("woff"), url("flaticon.ttf") format("truetype"), url("flaticon.svg") format("svg");
5 | font-weight: normal;
6 | font-style: normal;
7 | }
8 |
9 | [class^="flaticon-"]:before, [class*=" flaticon-"]:before, [class^="flaticon-"]:after, [class*=" flaticon-"]:after {
10 | font-family: Flaticon;
11 | font-size: 48px;
12 | font-style: normal;
13 | }
14 |
15 | .flaticon-compass:before {
16 | content: "\e000";
17 | }
18 |
19 | .flaticon-windrose:before {
20 | content: "\e000";
21 | }
22 |
23 | .flaticon-flask:before {
24 | content: "\e001";
25 | }
26 |
27 | .flaticon-timechart:before {
28 | content: "\e002";
29 | }
30 |
31 | .flaticon-table:before {
32 | content: "\e003";
33 | }
34 |
35 | .flaticon-status:before {
36 | content: "\e003";
37 | }
38 |
39 | .flaticon-jqgrid:before {
40 | content: "\e003";
41 | }
42 |
43 | .flaticon-panel:before {
44 | content: "\e003";
45 | }
46 |
47 | .flaticon-map:before {
48 | content: "\e004";
49 | }
50 |
51 | .flaticon-progressbar:before {
52 | content: "\e005";
53 | }
54 |
55 | .flaticon-gauge:before {
56 | content: "\e006";
57 | }
58 |
59 | .flaticon-thermometer:before {
60 | content: "\e007";
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/menu/deck.menu.css:
--------------------------------------------------------------------------------
1 | .deck-menu .slide {
2 | background: #eee;
3 | position: relative;
4 | left: 0;
5 | top: 0;
6 | visibility: visible;
7 | cursor: pointer;
8 | }
9 | .no-csstransforms .deck-menu > .slide {
10 | float: left;
11 | width: 22%;
12 | height: 22%;
13 | min-height: 0;
14 | margin: 1%;
15 | font-size: 0.22em;
16 | overflow: hidden;
17 | padding: 0 0.5%;
18 | }
19 | .csstransforms .deck-menu > .slide {
20 | -webkit-transform: scale(0.22) !important;
21 | -moz-transform: scale(0.22) !important;
22 | -o-transform: scale(0.22) !important;
23 | -ms-transform: scale(0.22) !important;
24 | transform: scale(0.22) !important;
25 | -webkit-transform-origin: 0 0;
26 | -moz-transform-origin: 0 0;
27 | -o-transform-origin: 0 0;
28 | -ms-transform-origin: 0 0;
29 | transform-origin: 0 0;
30 | -webkit-box-sizing: border-box;
31 | -moz-box-sizing: border-box;
32 | box-sizing: border-box;
33 | width: 100%;
34 | height: 100%;
35 | overflow: hidden;
36 | padding: 0 48px;
37 | margin: 12px;
38 | }
39 | .deck-menu iframe, .deck-menu img, .deck-menu video {
40 | max-width: 100%;
41 | }
42 | .deck-menu .deck-current, .no-touch .deck-menu .slide:hover {
43 | background: #ddf;
44 | }
45 | .deck-menu.deck-container:hover .deck-prev-link, .deck-menu.deck-container:hover .deck-next-link {
46 | display: none;
47 | }
48 |
--------------------------------------------------------------------------------
/src/js/widget/gauge.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import dataAccess from '../sos-data-access';
3 | import drawing from './gauge.svg';
4 | import common from '../widget-common';
5 |
6 | const template = [
7 | ''].join('');
11 |
12 | export default {
13 | inputs: common.inputs.concat(['feature', 'property', 'refresh_interval']),
14 | optional_inputs: common.optional_inputs,
15 | preferredSizes: [{ w: 300, h: 300 }],
16 |
17 | init(config, el, errorHandler) {
18 | // Render template
19 | el.innerHTML = template;
20 | const arrow = el.querySelector('.arrow');
21 | const title = el.querySelector('.title');
22 | const value = el.querySelector('.value');
23 |
24 | // load widget common features
25 | common.init(config, el);
26 |
27 | // Update view
28 | function redraw(data) {
29 | const measure = data[0];
30 | title.innerHTML = measure.property;
31 | value.innerHTML = `${measure.value} %`;
32 | arrow.setAttribute('transform', `rotate(${2.7 * measure.value}, 365.396, 495)`);
33 | }
34 |
35 | // Setup SOS data access
36 | const data = dataAccess(config, redraw, errorHandler);
37 | const refreshIntervalId = setInterval(data.read, config.refresh_interval * 1000);
38 | data.read();
39 |
40 | return {
41 | destroy() {
42 | clearInterval(refreshIntervalId);
43 | },
44 | };
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const CopyPlugin = require('copy-webpack-plugin');
4 | const WriteFilePlugin = require('write-file-webpack-plugin');
5 |
6 | module.exports = (env) => ({
7 | mode: env.prod ? 'production' : 'development',
8 | entry: env.test
9 | ? ''
10 | : {
11 | SensorWidgets: './src/js/main.js',
12 | home: './src/js/pages/home.js',
13 | wizard: './src/js/pages/wizard.js',
14 | 'meteo.apb.es': './src/js/pages/meteo.apb.es.js',
15 | },
16 | devtool: env.prod ? 'source-map' : 'eval-source-map',
17 | devServer: {
18 | contentBase: './dist',
19 | },
20 | module: {
21 | rules: [
22 | {
23 | test: /\.(js)$/,
24 | exclude: /node_modules/,
25 | use: env.test ? [] : ['eslint-loader'],
26 | },
27 | {
28 | test: /\.css$/i,
29 | use: ['style-loader', 'css-loader'],
30 | },
31 | {
32 | test: /\.(svg|xml)$/i,
33 | use: 'raw-loader',
34 | },
35 | {
36 | test: /\.(png|jpe?g|gif|eot|ttf|woff|woff2)$/i,
37 | loader: 'url-loader',
38 | options: {
39 | limit: 8192,
40 | },
41 | },
42 | ],
43 | },
44 | plugins: env.test ? [] : [
45 | new CopyPlugin({
46 | patterns: [
47 | { from: 'src/assets' },
48 | ],
49 | }),
50 | new WriteFilePlugin(),
51 | ],
52 | output: {
53 | filename: '[name].js',
54 | chunkFilename: '[name].chunk.js',
55 | path: path.resolve(__dirname, 'dist'),
56 | },
57 | });
58 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/navigation/deck.navigation.css:
--------------------------------------------------------------------------------
1 | .deck-container .deck-prev-link, .deck-container .deck-next-link {
2 | display: none;
3 | position: absolute;
4 | z-index: 3;
5 | top: 50%;
6 | width: 32px;
7 | height: 32px;
8 | margin-top: -16px;
9 | font-size: 20px;
10 | font-weight: bold;
11 | line-height: 32px;
12 | vertical-align: middle;
13 | text-align: center;
14 | text-decoration: none;
15 | color: #fff;
16 | background: #888;
17 | }
18 | .borderradius .deck-container .deck-prev-link, .borderradius .deck-container .deck-next-link {
19 | -webkit-border-radius: 16px;
20 | -moz-border-radius: 16px;
21 | border-radius: 16px;
22 | }
23 | .deck-container .deck-prev-link:hover, .deck-container .deck-prev-link:focus, .deck-container .deck-prev-link:active, .deck-container .deck-prev-link:visited, .deck-container .deck-next-link:hover, .deck-container .deck-next-link:focus, .deck-container .deck-next-link:active, .deck-container .deck-next-link:visited {
24 | color: #fff;
25 | }
26 | .deck-container .deck-prev-link {
27 | left: 8px;
28 | }
29 | .deck-container .deck-next-link {
30 | right: 8px;
31 | }
32 | .deck-container:hover .deck-prev-link, .deck-container:hover .deck-next-link {
33 | display: block;
34 | }
35 | .deck-container:hover .deck-prev-link.deck-nav-disabled, .touch .deck-container:hover .deck-prev-link, .deck-container:hover .deck-next-link.deck-nav-disabled, .touch .deck-container:hover .deck-next-link {
36 | display: none;
37 | }
38 |
39 | @media print {
40 | .deck-prev-link, .deck-next-link {
41 | display: none !important;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/karma.config.js:
--------------------------------------------------------------------------------
1 | const webpackConfig = require('./webpack.config')({
2 | test: true,
3 | });
4 |
5 | module.exports = (config) => {
6 | config.set({
7 |
8 | // base path that will be used to resolve all patterns (eg. files, exclude)
9 | basePath: 'src',
10 |
11 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
12 | frameworks: ['mocha'],
13 |
14 | files: [
15 | 'test/**/*.js',
16 | ],
17 |
18 | exclude: [
19 | ],
20 |
21 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
22 | preprocessors: {
23 | 'test/**/*.js': ['webpack'],
24 | },
25 | webpack: webpackConfig,
26 | webpackMiddleware: {
27 | stats: 'errors-warnings',
28 | },
29 |
30 | // possible values: 'dots', 'progress'
31 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
32 | reporters: ['mocha'],
33 |
34 | // web server port
35 | port: 9876,
36 |
37 | // enable / disable colors in the output (reporters and logs)
38 | colors: true,
39 |
40 | // config.LOG_DISABLE config.LOG_ERROR config.LOG_WARN config.LOG_INFO config.LOG_DEBUG
41 | logLevel: config.LOG_INFO,
42 |
43 | // enable / disable watching file and executing tests whenever any file changes
44 | autoWatch: true,
45 |
46 | // start these browsers
47 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
48 | browsers: ['jsdom'],
49 |
50 | // Continuous Integration mode
51 | // if true, Karma captures browsers, runs the tests and exits
52 | singleRun: true,
53 |
54 | // Concurrency level
55 | // how many browser should be started simultaneous
56 | concurrency: Infinity,
57 | });
58 | };
59 |
--------------------------------------------------------------------------------
/src/test/fixtures/getObservationResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 2020-10-05T16:06:00.000Z
9 |
10 |
11 |
12 |
13 |
14 |
15 | 83.0
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/js/i18n.js:
--------------------------------------------------------------------------------
1 | import translations from './translations.json';
2 |
3 | const params = {};
4 | window.location.search.substr(1).split('&').forEach((item) => {
5 | const [key, value] = item.split('=');
6 | params[key] = value;
7 | });
8 |
9 | let activeLang;
10 |
11 | function setLang(lang) {
12 | activeLang = lang;
13 | console.debug(`Language set to ${activeLang}`);
14 | }
15 |
16 | setLang(Object.prototype.hasOwnProperty.call(params, 'lang') ? params.lang : 'en');
17 |
18 | function template(templ, dict) {
19 | let result = templ;
20 | if (dict) {
21 | Object.entries(dict).forEach(([key, value]) => {
22 | result = result.replace(new RegExp(`{${key}}`, 'g'), value);
23 | });
24 | }
25 | return result;
26 | }
27 |
28 | function t(string, values) {
29 | let translated = string;
30 | if (Object.prototype.hasOwnProperty.call(translations, string)
31 | && Object.prototype.hasOwnProperty.call(translations[string], activeLang)) {
32 | translated = translations[string][activeLang];
33 | }
34 | return template(translated, values);
35 | }
36 |
37 | export default {
38 | langs() {
39 | return translations.langs;
40 | },
41 | getLang() {
42 | return activeLang;
43 | },
44 | setLang,
45 | t,
46 | addTranslations(bundle) {
47 | Object.keys(bundle).forEach((key) => {
48 | if (!Object.prototype.hasOwnProperty.call(translations, key)) {
49 | translations[key] = bundle[key];
50 | } else {
51 | console.warn(`Skipping duplicate entry '${key}' in translation bundle.`);
52 | }
53 | });
54 | },
55 | translateDocTree(el) {
56 | const element = el || document;
57 | const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
58 | while (treeWalker.nextNode()) {
59 | const node = treeWalker.currentNode;
60 | if (/\S/.test(node.nodeValue)) { // Not a whitespace-only text node
61 | node.nodeValue = t(node.nodeValue);
62 | }
63 | }
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/themes/style/slides.custom.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | a, a:active, a:visited, a:hover {
4 | color: #444 !important;
5 | text-decoration: none;
6 | }
7 | a:hover {
8 | color: #cfcfcf !important;
9 | text-decoration: none;
10 | }
11 |
12 | .deck-container {
13 | font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
14 | font-size: 1.25em;
15 | }
16 |
17 | .deck-container h1 {
18 | font-size: 3.2em;
19 | }
20 |
21 | .deck-container h1 .subtitle{
22 | font-size: .4em;
23 | font-style: italic;
24 | font-weight: normal;
25 | }
26 |
27 | .deck-container .slide h2 {
28 | font-size: 1.8em;
29 | text-align: left;
30 | color: #933;
31 | }
32 |
33 | .deck-container .slide h3 {
34 | padding-top: 0.5em;
35 | font-size: 1.8em;
36 | color: #933;
37 | }
38 |
39 | .deck-container section {
40 | font-size: 1.2em;
41 | }
42 |
43 | .deck-container ul,
44 | .deck-container ol {
45 | font-size: 1.1em;
46 | }
47 |
48 | .deck-container .slide p {
49 | margin-bottom: 0.5em;
50 | }
51 |
52 | .deck-container span {
53 | font-size: 1.0em;
54 | }
55 |
56 | div.CodeMirror,
57 | .deck-container pre {
58 | font-size: 0.9em;
59 | }
60 |
61 | .slide img.bordered {
62 | border: 1px solid #ccc;
63 | }
64 |
65 | .slide .link {
66 | width: 100%;
67 | text-align: center;
68 | font-size: 3em;
69 | }
70 |
71 | #conclusion div {
72 | font-size: 1.2em;
73 | text-align: center;
74 | margin-bottom: 14px;
75 | }
76 |
77 | div a,
78 | .subtitle {
79 | color: inherit;
80 | text-decoration: none;
81 | padding: 0 0.1em;
82 | background: rgba(200,200,200,0.5);
83 | text-shadow: -1px -1px 2px rgba(100,100,100,0.9);
84 | border-radius: 0.2em;
85 |
86 | -webkit-transition: 0.5s;
87 | -moz-transition: 0.5s;
88 | -ms-transition: 0.5s;
89 | -o-transition: 0.5s;
90 | transition: 0.5s;
91 | }
92 |
93 | div a:hover {
94 | background: rgba(50,50,50,1);
95 | color: #cfcfcf;
96 | text-shadow: -1px -1px 2px rgba(25,25,25,0.5);
97 | }
--------------------------------------------------------------------------------
/doc/slides/deckjs/themes/transition/fade.css:
--------------------------------------------------------------------------------
1 | .csstransitions.csstransforms .deck-container .slide {
2 | -webkit-transition: opacity 500ms ease-in-out 0ms;
3 | -moz-transition: opacity 500ms ease-in-out 0ms;
4 | -ms-transition: opacity 500ms ease-in-out 0ms;
5 | -o-transition: opacity 500ms ease-in-out 0ms;
6 | transition: opacity 500ms ease-in-out 0ms;
7 | }
8 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | -webkit-box-sizing: border-box;
13 | -moz-box-sizing: border-box;
14 | box-sizing: border-box;
15 | width: 100%;
16 | padding: 0 48px;
17 | }
18 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .slide {
19 | position: relative;
20 | left: 0;
21 | top: 0;
22 | }
23 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-next, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-after {
24 | opacity: 0;
25 | }
26 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-current {
27 | opacity: 1;
28 | }
29 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-previous, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-before, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-next, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-after {
30 | opacity: 0;
31 | pointer-events: none;
32 | }
33 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-before .slide, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-previous .slide {
34 | visibility: visible;
35 | }
36 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-child-current {
37 | opacity: 1;
38 | visibility: visible;
39 | pointer-events: auto;
40 | }
41 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-child-current .deck-next, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-child-current .deck-after {
42 | visibility: hidden;
43 | }
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sensor-widgets",
3 | "version": "0.2.0",
4 | "description": "Configurable graphical components for your SOS sensor data.",
5 | "directories": {
6 | "doc": "doc",
7 | "example": "examples"
8 | },
9 | "scripts": {
10 | "test:lint": "eslint src",
11 | "test:karma": "karma start karma.config.js",
12 | "test": "npm run test:lint && npm run test:karma",
13 | "start": "webpack-dev-server --env.dev --open",
14 | "build": "npm run test && webpack --env.prod",
15 | "deploy": "npm run build && gh-pages -d dist"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git://github.com/oscarfonts/sensor-widgets.git"
20 | },
21 | "author": "Oscar Fonts (http://fonts.cat)",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/oscarfonts/sensor-widgets/issues"
25 | },
26 | "devDependencies": {
27 | "chai": "^4.2.0",
28 | "copy-webpack-plugin": "^6.1.0",
29 | "css-loader": "^4.3.0",
30 | "eslint": "^7.2.0",
31 | "eslint-config-airbnb-base": "^14.2.0",
32 | "eslint-loader": "^4.0.2",
33 | "eslint-plugin-import": "^2.22.0",
34 | "file-loader": "^6.1.0",
35 | "gh-pages": "^3.1.0",
36 | "jsdom": "^16.4.0",
37 | "karma": "^5.2.2",
38 | "karma-jsdom-launcher": "^8.0.2",
39 | "karma-mocha": "^2.0.1",
40 | "karma-mocha-reporter": "^2.2.5",
41 | "karma-webpack": "^4.0.2",
42 | "mocha": "^8.1.3",
43 | "raw-loader": "^4.0.1",
44 | "sinon": "^9.0.3",
45 | "style-loader": "^1.2.1",
46 | "url-loader": "^4.1.0",
47 | "webpack": "^4.44.1",
48 | "webpack-cli": "^3.3.12",
49 | "webpack-dev-server": "^3.11.0",
50 | "write-file-webpack-plugin": "^4.5.1"
51 | },
52 | "dependencies": {
53 | "bootstrap": "^3.4.1",
54 | "daterangepicker": "^3.1.0",
55 | "flot": "^4.2.1",
56 | "flot-plugins": "^3.9.1",
57 | "highcharts": "^8.2.0",
58 | "highlightjs": "^9.16.2",
59 | "jqgrid": "^4.6.4",
60 | "jquery": "^3.5.1",
61 | "jquery-ui": "^1.12.1",
62 | "leaflet": "^1.7.1",
63 | "moment": "^2.27.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/assets/examples/map.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Map Widget Example
4 |
5 |
6 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/js/widget/progressbar.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import './progressbar.css';
3 |
4 | import dataAccess from '../sos-data-access';
5 | import ld from '../locale-date';
6 | import common from '../widget-common';
7 |
8 | const template = [
9 | '',
24 | ].join('');
25 |
26 | export default {
27 | inputs: common.inputs.concat(['feature', 'property', 'refresh_interval', 'min_value', 'max_value']),
28 | optional_inputs: common.optional_inputs,
29 | preferredSizes: [{ w: 500, h: 220 }],
30 |
31 | init(config, el, errorHandler) {
32 | // Render template
33 | el.innerHTML = template;
34 | el.querySelector('.min').innerHTML = config.min_value;
35 | el.querySelector('.max').innerHTML = config.max_value;
36 |
37 | // load widget common features
38 | common.init(config, el);
39 |
40 | // Update view
41 | function redraw(data) {
42 | const measure = data[0];
43 | el.querySelector('.date').innerHTML = ld.display(measure.time);
44 | el.querySelector('.value').innerHTML = `${measure.value} ${measure.uom}`;
45 | el.querySelector('.feature').innerHTML = measure.feature;
46 | el.querySelector('.property').innerHTML = measure.property;
47 |
48 | const fullspan = el.querySelector('.background-bar').offsetWidth;
49 | const proportion = (measure.value - config.min_value) / (config.max_value - config.min_value);
50 | const width = fullspan * proportion;
51 |
52 | el.querySelector('.bar').style.width = `${width}px`;
53 | }
54 |
55 | // Setup SOS data access
56 | const data = dataAccess(config, redraw, errorHandler);
57 | const refreshIntervalId = setInterval(data.read, config.refresh_interval * 1000);
58 | data.read();
59 |
60 | return {
61 | destroy() {
62 | clearInterval(refreshIntervalId);
63 | },
64 | };
65 | },
66 | };
67 |
--------------------------------------------------------------------------------
/src/js/widget/thermometer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import i18n from '../i18n';
3 | import dataAccess from '../sos-data-access';
4 | import drawing from './thermometer.svg';
5 | import ld from '../locale-date';
6 | import common from '../widget-common';
7 |
8 | const template = [
9 | '',
19 | ].join('');
20 |
21 | const dy = 3.342574;
22 | const yMax = 206.34359 + 267.40595;
23 | const tMin = -24;
24 |
25 | export default {
26 | inputs: common.inputs.concat(['feature', 'property', 'refresh_interval']),
27 | optional_inputs: common.optional_inputs,
28 | preferredSizes: [{ w: 300, h: 540 }],
29 |
30 | init(config, el, errorHandler) {
31 | // Render template
32 | el.innerHTML = template;
33 | const elem = el.querySelector('.svg-temp');
34 | const clip = (elem.firstElementChild || elem.firstChild);
35 |
36 | // load widget common features
37 | common.init(config, el);
38 |
39 | // Update view
40 | function redraw(data) {
41 | const measure = data[0];
42 | if (measure) {
43 | el.querySelector('.feature').innerHTML = measure.feature;
44 | el.querySelector('.property').innerHTML = measure.property;
45 | el.querySelector('.value').innerHTML = measure.value;
46 | el.querySelector('.request_time').innerHTML = ld.display(new Date());
47 | el.querySelector('.result_time').innerHTML = ld.display(measure.time);
48 |
49 | const h = dy * (measure.value - tMin);
50 | const yMin = yMax - h;
51 | clip.setAttribute('height', h.toString());
52 | clip.setAttribute('y', yMin.toString());
53 | } else {
54 | el.querySelector('.value').innerHTML = i18n.t('(no data)');
55 | }
56 | }
57 |
58 | // Setup SOS data access
59 | const data = dataAccess(config, redraw, errorHandler);
60 | const refreshIntervalId = setInterval(data.read, config.refresh_interval * 1000);
61 | data.read();
62 |
63 | return {
64 | destroy() {
65 | clearInterval(refreshIntervalId);
66 | },
67 | };
68 | },
69 | };
70 |
--------------------------------------------------------------------------------
/src/js/widget/panel.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import i18n from '../i18n';
3 | import dataAccess from '../sos-data-access';
4 | import ld from '../locale-date';
5 | import common from '../widget-common';
6 |
7 | const template = [
8 | '',
14 | ].join('');
15 |
16 | export default {
17 | inputs: common.inputs.concat(['feature', 'properties', 'refresh_interval']),
18 | optional_inputs: ['title'].concat(common.optional_inputs),
19 | preferredSizes: [{ w: 400, h: 400 }],
20 |
21 | init(config, el, errorHandler) {
22 | // Render template
23 | el.innerHTML = template;
24 | const title = el.querySelector('h2');
25 | const subtitle = el.querySelector('h3');
26 | const panel = el.querySelector('dl');
27 |
28 | // load widget common features
29 | common.init(config, el);
30 |
31 | // Update view
32 | function redraw(data) {
33 | if (!data.length) {
34 | title.innerHTML = config.title || '';
35 | subtitle.innerHTML = i18n.t('(no data)');
36 | return;
37 | }
38 |
39 | // Get the most recent measure time as the reference one
40 | const mostRecentTime = new Date(Math.max(...data.map((o) => o.time)));
41 |
42 | // Sort by property
43 | data.sort((a, b) => a.property.localeCompare(b.property));
44 |
45 | title.innerHTML = config.title || `${i18n.t('Last measures from')} ${data[0].feature}`;
46 | subtitle.innerHTML = ld.display(mostRecentTime);
47 | let html = '';
48 | Object.keys(data).forEach((i) => {
49 | const measure = data[i];
50 | html += `${measure.property}`;
51 | if (measure.time.getTime() === mostRecentTime.getTime()) {
52 | html += `${measure.value} ${measure.uom}`;
53 | } else { // Outdated! Display distinctly and with corresponding date
54 | html += `${measure.value} ${measure.uom}* *(${ld.display(measure.time)})`;
55 | }
56 | });
57 | panel.innerHTML = html;
58 | }
59 |
60 | // Setup SOS data access
61 | const data = dataAccess(config, redraw, errorHandler);
62 | const refreshIntervalId = setInterval(data.read, config.refresh_interval * 1000);
63 | data.read();
64 |
65 | return {
66 | destroy() {
67 | clearInterval(refreshIntervalId);
68 | },
69 | };
70 | },
71 | };
72 |
--------------------------------------------------------------------------------
/src/test/fixtures/getDataAvailabilityResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 2020-08-03T16:10:00.000Z
10 | 2020-09-28T15:50:00.000Z
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/js/widget/status.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import moment from 'moment';
3 | import i18n from '../i18n';
4 | import dataAccess from '../sos-data-access';
5 | import common from '../widget-common';
6 |
7 | // TODO moment locale?
8 |
9 | moment.locale(i18n.getLang());
10 |
11 | const template = [
12 | '',
15 | ''].join('');
16 |
17 | export default {
18 | inputs: common.inputs,
19 | optional_inputs: common.optional_inputs,
20 | preferredSizes: [{ w: 800, h: 600 }, { w: 1024, h: 768 }, { w: 1280, h: 1024 }],
21 |
22 | init(config, el, errorHandler) {
23 | // Render template
24 | el.innerHTML = template;
25 |
26 | // load widget common features
27 | common.init(config, el);
28 |
29 | config.features = undefined;
30 | config.properties = undefined;
31 |
32 | // Update view
33 | function redraw(data) {
34 | const table = {};
35 | const features = [];
36 | Object.keys(data).forEach((i) => {
37 | const measure = data[i];
38 | if (!table[measure.property]) {
39 | table[measure.property] = [];
40 | }
41 | table[measure.property][measure.feature] = measure;
42 | if (features.indexOf(measure.feature) === -1) {
43 | features.push(measure.feature);
44 | }
45 | });
46 |
47 | let html = '';
48 | html += '';
49 | html += ' | ';
50 | Object.keys(features).forEach((c) => {
51 | html += `${features[c]} | `;
52 | });
53 | html += '
';
54 | Object.keys(table).forEach((p) => {
55 | html += `| ${p} | `;
56 | Object.keys(features).forEach((f) => {
57 | f = features[f];
58 | if (table[p][f]) {
59 | html += '';
60 | const m = table[p][f];
61 | const cell = ` ${m.value} ${m.uom} `
62 | + `${moment(m.time).fromNow()} `;
63 | html += cell;
64 | } else {
65 | html += ' | ';
66 | html += '(N/A)';
67 | }
68 | html += ' | ';
69 | });
70 | html += '
';
71 | });
72 | el.querySelector('.table-responsive').innerHTML = html;
73 | }
74 |
75 | // Setup SOS data access
76 | const data = dataAccess(config, redraw, errorHandler);
77 | data.read();
78 | },
79 | };
80 |
--------------------------------------------------------------------------------
/src/js/widget/compass.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import i18n from '../i18n';
3 | import dataAccess from '../sos-data-access';
4 | import drawing from './compass.svg';
5 | import ld from '../locale-date';
6 | import common from '../widget-common';
7 |
8 | const template = [
9 | ''].join('');
20 |
21 | export default {
22 | inputs: common.inputs.concat(['feature', 'property', 'refresh_interval']),
23 | optional_inputs: ['title'].concat(common.optional_inputs),
24 | preferredSizes: [{ w: 570, h: 380 }, { w: 280, h: 540 }],
25 |
26 | init(config, el, errorHandler) {
27 | // Render template
28 | el.innerHTML = template;
29 | const arrow = el.querySelector('.arrow');
30 | const shadow = el.querySelector('.shadow');
31 | arrow.style.visibility = 'hidden';
32 | shadow.style.visibility = 'hidden';
33 |
34 | // load widget common features
35 | common.init(config, el);
36 |
37 | if (config.title) {
38 | el.querySelector('.title').innerHTML = config.title;
39 | }
40 |
41 | // Update view
42 | function redraw(data) {
43 | const measure = data[0];
44 | if (measure) {
45 | el.querySelector('.error').style.display = 'none';
46 | el.querySelector('.request_time').innerHTML = ld.display(new Date());
47 | el.querySelector('.result_time').innerHTML = ld.display(measure.time);
48 | el.querySelector('.value').innerHTML = measure.value;
49 | if (!config.title) {
50 | el.querySelector('.title').innerHTML = measure.feature;
51 | }
52 | el.querySelector('.property').innerHTML = measure.property;
53 | arrow.setAttribute('transform', `rotate(${measure.value}, 256, 256)`);
54 | shadow.setAttribute('transform', `translate(5, 5) rotate(${measure.value}, 256, 256)`);
55 | arrow.style.visibility = 'visible';
56 | shadow.style.visibility = 'visible';
57 | }
58 | }
59 |
60 | // Setup SOS data access
61 | const data = dataAccess(config, redraw, errorHandler);
62 | const refreshIntervalId = setInterval(data.read, config.refresh_interval * 1000);
63 | data.read();
64 |
65 | return {
66 | destroy() {
67 | clearInterval(refreshIntervalId);
68 | },
69 | };
70 | },
71 | };
72 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/status/deck.status.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Deck JS - deck.status
3 | Copyright (c) 2011 Caleb Troughton
4 | Dual licensed under the MIT license and GPL license.
5 | https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
6 | https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
7 | */
8 |
9 | /*
10 | This module adds a (current)/(total) style status indicator to the deck.
11 | */
12 | (function($, deck, undefined) {
13 | var $d = $(document),
14 |
15 | updateCurrent = function(e, from, to) {
16 | var opts = $[deck]('getOptions');
17 |
18 | $(opts.selectors.statusCurrent).text(opts.countNested ?
19 | to + 1 :
20 | $[deck]('getSlide', to).data('rootSlide')
21 | );
22 | };
23 |
24 | /*
25 | Extends defaults/options.
26 |
27 | options.selectors.statusCurrent
28 | The element matching this selector displays the current slide number.
29 |
30 | options.selectors.statusTotal
31 | The element matching this selector displays the total number of slides.
32 |
33 | options.countNested
34 | If false, only top level slides will be counted in the current and
35 | total numbers.
36 | */
37 | $.extend(true, $[deck].defaults, {
38 | selectors: {
39 | statusCurrent: '.deck-status-current',
40 | statusTotal: '.deck-status-total'
41 | },
42 |
43 | countNested: true
44 | });
45 |
46 | $d.bind('deck.init', function() {
47 | var opts = $[deck]('getOptions'),
48 | slides = $[deck]('getSlides'),
49 | $current = $[deck]('getSlide'),
50 | ndx;
51 |
52 | // Set total slides once
53 | if (opts.countNested) {
54 | $(opts.selectors.statusTotal).text(slides.length);
55 | }
56 | else {
57 | /* Determine root slides by checking each slide's ancestor tree for
58 | any of the slide classes. */
59 | var rootIndex = 1,
60 | slideTest = $.map([
61 | opts.classes.before,
62 | opts.classes.previous,
63 | opts.classes.current,
64 | opts.classes.next,
65 | opts.classes.after
66 | ], function(el, i) {
67 | return '.' + el;
68 | }).join(', ');
69 |
70 | /* Store the 'real' root slide number for use during slide changes. */
71 | $.each(slides, function(i, $el) {
72 | var $parentSlides = $el.parentsUntil(opts.selectors.container, slideTest);
73 |
74 | $el.data('rootSlide', $parentSlides.length ?
75 | $parentSlides.last().data('rootSlide') :
76 | rootIndex++
77 | );
78 | });
79 |
80 | $(opts.selectors.statusTotal).text(rootIndex - 1);
81 | }
82 |
83 | // Find where we started in the deck and set initial state
84 | $.each(slides, function(i, $el) {
85 | if ($el === $current) {
86 | ndx = i;
87 | return false;
88 | }
89 | });
90 | updateCurrent(null, ndx, ndx);
91 | })
92 | /* Update current slide number with each change event */
93 | .bind('deck.change', updateCurrent);
94 | })(jQuery, 'deck');
95 |
96 |
--------------------------------------------------------------------------------
/src/js/SensorWidgets.css:
--------------------------------------------------------------------------------
1 | body .widget {
2 | font-family: "Source Sans Pro", helvetica, sans-serif;
3 | text-align: center;
4 | margin: 0;
5 | }
6 |
7 | .widget {
8 | height: 100%;
9 | overflow: hidden;
10 | color: #222;
11 | font-family: Helvetica, sans-serif;
12 | }
13 |
14 | .widget h1 {
15 | font-size: 28px;
16 | }
17 |
18 | .widget h2 {
19 | font-size: 18px;
20 | }
21 |
22 | .widget h3 {
23 | font-size: 14px;
24 | }
25 |
26 | .widget .footnote {
27 | margin: 10px;
28 | font-size: 11px;
29 | font-weight: bold;
30 | display: block;
31 | color: #222;
32 | }
33 |
34 | .widget .data {
35 | display: inline-block;
36 | vertical-align: top;
37 | margin: 10px;
38 | max-width: 300px;
39 | }
40 |
41 | .widget.compass svg,
42 | .widget.gauge svg {
43 | max-width: 300px;
44 | }
45 |
46 | .widget.status {
47 | overflow: auto;
48 | }
49 |
50 | .widget.status th {
51 | padding: 4px;
52 | background-color: darkgrey;
53 | }
54 |
55 | .widget.status td {
56 | padding: 4px;
57 | background-color: lightgrey;
58 | }
59 |
60 | .widget.status td.nodata {
61 | background-color: whitesmoke;
62 | color: darkgrey;
63 | font-size: small;
64 | }
65 |
66 | .widget.status .result_time {
67 | font-size: small;
68 | font-style: italic;
69 | }
70 |
71 | .widget.panel dl {
72 | text-align: left;
73 | }
74 |
75 | .widget.panel dd.outdated {
76 | color: #AA1111;
77 | font-style: italic;
78 | }
79 |
80 | .widget.panel dd.outdated span {
81 | font-size: 9px;
82 | display: block;
83 | }
84 |
85 | .widget.map .leaflet-label .widget.panel h2 {
86 | font-size: small;
87 | }
88 |
89 | .widget.map .leaflet-label .widget.panel h3 {
90 | font-size: x-small;
91 | }
92 |
93 | .widget.map .leaflet-label .widget.panel h2,
94 | .widget.map .leaflet-label .widget.panel h3,
95 | .widget.map .leaflet-label .widget.panel dl,
96 | .widget.map .leaflet-label .widget.panel .footnote {
97 | margin: 0;
98 | }
99 |
100 | .widget.map .leaflet-label .widget.panel {
101 | background-color: transparent;
102 | }
103 |
104 | .widget.map .leaflet-label .widget.panel dt {
105 | border-top: 1px solid darkgray;
106 | clear: left;
107 | font-size: x-small;
108 | font-weight: normal;
109 | line-height: normal;
110 | padding-top: 2px;
111 | float: none;
112 | text-align: left;
113 | width: 100%;
114 | }
115 |
116 | .widget.map .leaflet-label .widget.panel dd {
117 | margin: 0;
118 | text-align: right;
119 | font-size: x-small;
120 | line-height: normal;
121 | padding-bottom: 2px;
122 | }
123 |
124 | .widget.table {
125 | overflow: auto;
126 | font-size: 12px;
127 | }
128 |
129 | .widget.windrose {
130 | width: 100%;
131 | }
132 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Sensor Widgets
2 | ==============
3 |
4 | **Configurable graphical components for your SOS sensor data.**
5 |
6 | *100% Javascript. Extensible. MIT licensed.*
7 |
8 | * Demo & samples: http://sensors.fonts.cat/
9 | * Documentation: http://sensor-widgets.readthedocs.org/en/latest/
10 | * Configure your own widget:
11 |
12 | 1. Go to http://sensors.fonts.cat/wizard/
13 | 2. Choose a widget from the left menu.
14 | 3. Configure it instantly with the "Widget Configuration" form.
15 | 4. See the result in the "Widget View" panel.
16 | 4. Embed the widget in any web page grabbing the code snippets from the "Take Away" panel.
17 |
18 |
19 | Building
20 | ========
21 |
22 | * Install node and npm. See instructions:
23 |
24 | * Mac & Windows: http://nodejs.org/download/
25 | * Debian & Ubuntu: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions
26 | * Install grunt-cli. For example: `sudo npm install -g grunt-cli`.
27 | * Get the project's npm deps (such as grunt) running `npm install`.
28 | * Build the project running `grunt build`.
29 | * Run a server with livereload using `grunt` (default task).
30 |
31 |
32 | Updating gh-pages (demo site)
33 | =============================
34 |
35 | Simply use the "publish" grunt task::
36 |
37 | grunt publish
38 |
39 | This will push an optimized version of the library along with examples and documentation.
40 |
41 |
42 | Updating documentation
43 | ======================
44 |
45 | Just push the changes. GitHub will automatically trigger a build on readthedocs.
46 |
47 |
48 | Available widgets
49 | =================
50 |
51 | =========== ====================== ====================== =====================
52 | Name Library & dependencies Represented concept Candidate properties
53 | =========== ====================== ====================== =====================
54 | Compass (SVG + JS) Instant angle Wind direction
55 | Gauge (SVG + JS) Percentage Relative humidity
56 | jqGrid jqGrid Any measurement Any value
57 | Map Leaflet Geographic Features Features of Interest
58 | Panel (HTML + bootstrap-CSS) Instant quantities Any collection
59 | Progressbar (HTML + CSS) Instant quantity Pressure, wind speed,
60 | solar radiation, etc.
61 | Status (HTML + bootstrap-CSS) All measurements All values
62 | Table (HTML + bootstrap-CSS) Any measurement Any value
63 | Thermometer (SVG + JS) Temperature Temperature
64 | Timechart Flot Charts Time series Any numeric quantity
65 | Windrose Highcharts (not free!) Accumulated angular Wind direction stats
66 | =========== ====================== ====================== =====================
67 |
--------------------------------------------------------------------------------
/src/js/pages/meteo.css:
--------------------------------------------------------------------------------
1 | .section {
2 | padding-top: 35px;
3 | }
4 | .compass svg {
5 | max-height: 80%;
6 | max-width: 100%;
7 | }
8 | .widget h1, .widget h2, .widget h3 {
9 | margin: 0;
10 | padding: 3px;
11 | text-align: center;
12 | }
13 | .widget h1 {
14 | font-size: 18px;
15 | }
16 | .widget h2 {
17 | font-size: 22px;
18 | }
19 | .widget.compass h3,
20 | .widget.timechart h3,
21 | .widget.thermometer h3 {
22 | display: none;
23 | font-size: 12px;
24 | }
25 |
26 | .widget.panel dl {
27 | text-align: left;
28 | }
29 |
30 | .widget.panel dd.outdated {
31 | color: #AA1111;
32 | font-style: italic;
33 | }
34 |
35 | .widget.panel dd.outdated span {
36 | font-size: 9px;
37 | display: block;
38 | }
39 |
40 | .sirena .feature,
41 | .sirena .widget .title {
42 | display: none;
43 | }
44 | .sirena .thermometer {
45 | height: 340px;
46 | overflow: hidden;
47 | }
48 | .sirena .timechart {
49 | height: 170px;
50 | }
51 | .sirena .tablex {
52 | font-size: 11px;
53 | }
54 | .sirena .tablex h3 {
55 | display: none;
56 | }
57 | .sirena .tablex .time {
58 | white-space: nowrap;
59 | }
60 | .xmvqa .left .timechart {
61 | height: 160px;
62 | }
63 | .xmvqa .compass h2 {
64 | display: none;
65 | }
66 | .xmvqa [class|='panel'] h2 {
67 | text-align: center;
68 | font-size: 16px;
69 | font-weight: bold;
70 | }
71 | .xmvqa [class|='panel'] h3 {
72 | text-align: center;
73 | font-size: 14px;
74 | margin-top: 0;
75 | }
76 | .xmvqa .right [class|='panel'] dt {
77 | width: 80px;
78 | font-size: 12px;
79 | }
80 | .xmvqa .right [class|='panel'] dd {
81 | margin-left: 90px;
82 | font-size: 12px;
83 | }
84 | .torrecontrol .map {
85 | width: 100%;
86 | height: 180px;
87 | }
88 | .torrecontrol .compass h1, .torrecontrol .compass h2 {
89 | display: none;
90 | }
91 | .torrecontrol [class|='panel'] h2 {
92 | text-align: center;
93 | font-size: 16px;
94 | font-weight: bold;
95 | }
96 | .torrecontrol [class|='panel'] h3 {
97 | text-align: center;
98 | font-size: 14px;
99 | margin-top: 0;
100 | }
101 | .panel {
102 | -webkit-box-shadow: none;
103 | box-shadow: none;
104 | }
105 | .databrowser h2 {
106 | text-align: center;
107 | font-size: 16px;
108 | font-weight: bold;
109 | }
110 | .databrowser h3 {
111 | text-align: center;
112 | font-size: 14px;
113 | margin-top: 0;
114 | }
115 | .table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, .table-condensed>thead>tr>td, .table-condensed>tbody>tr>td, .table-condensed>tfoot>tr>td {
116 | padding: 4px;
117 | }
118 | /* specific CSS for thermometer in sirena */
119 | .thermometer .data {
120 | display: inline-flex;
121 | vertical-align: top;
122 | margin: 5px;
123 | margin-top: 20px;
124 | max-width: 60px;
125 | }
126 | .thermometer h2 {
127 | font-size: 12px;
128 | }
--------------------------------------------------------------------------------
/doc/slides/deckjs/themes/style/swiss.css:
--------------------------------------------------------------------------------
1 | .deck-container {
2 | font-family: "Helvetica Neue", sans-serif;
3 | font-size: 1.75em;
4 | background: #fff;
5 | }
6 | .deck-container .slide {
7 | background: #fff;
8 | }
9 | .deck-container .slide h1 {
10 | color: #000;
11 | }
12 | .deck-container .slide h2 {
13 | color: #c00;
14 | border-bottom-color: #ccc;
15 | }
16 | .deck-container .slide h3 {
17 | color: #888;
18 | }
19 | .deck-container .slide pre {
20 | border-color: #ccc;
21 | }
22 | .deck-container .slide code {
23 | color: #888;
24 | }
25 | .deck-container .slide blockquote {
26 | font-size: 2em;
27 | font-style: italic;
28 | padding: 1em 2em;
29 | color: #000;
30 | border-left: 5px solid #ccc;
31 | }
32 | .deck-container .slide blockquote p {
33 | margin: 0;
34 | }
35 | .deck-container .slide blockquote cite {
36 | font-size: .5em;
37 | font-style: normal;
38 | font-weight: bold;
39 | color: #888;
40 | }
41 | .deck-container .slide ::-moz-selection {
42 | background: #c00;
43 | color: #fff;
44 | }
45 | .deck-container .slide ::selection {
46 | background: #c00;
47 | color: #fff;
48 | }
49 | .deck-container .slide a, .deck-container .slide a:hover, .deck-container .slide a:focus, .deck-container .slide a:active, .deck-container .slide a:visited {
50 | color: #c00;
51 | text-decoration: none;
52 | }
53 | .deck-container .slide a:hover, .deck-container .slide a:focus {
54 | text-decoration: underline;
55 | }
56 | .deck-container > .slide .deck-before, .deck-container > .slide .deck-previous {
57 | opacity: 0.4;
58 | }
59 | .deck-container > .slide .deck-before:not(.deck-child-current) .deck-before, .deck-container > .slide .deck-before:not(.deck-child-current) .deck-previous, .deck-container > .slide .deck-previous:not(.deck-child-current) .deck-before, .deck-container > .slide .deck-previous:not(.deck-child-current) .deck-previous {
60 | opacity: 1;
61 | }
62 | .deck-container > .slide .deck-child-current {
63 | opacity: 1;
64 | }
65 | .deck-container .deck-prev-link, .deck-container .deck-next-link {
66 | background: #ccc;
67 | font-family: serif;
68 | }
69 | .deck-container .deck-prev-link, .deck-container .deck-prev-link:hover, .deck-container .deck-prev-link:focus, .deck-container .deck-prev-link:active, .deck-container .deck-prev-link:visited, .deck-container .deck-next-link, .deck-container .deck-next-link:hover, .deck-container .deck-next-link:focus, .deck-container .deck-next-link:active, .deck-container .deck-next-link:visited {
70 | color: #fff;
71 | }
72 | .deck-container .deck-prev-link:hover, .deck-container .deck-prev-link:focus, .deck-container .deck-next-link:hover, .deck-container .deck-next-link:focus {
73 | background: #c00;
74 | text-decoration: none;
75 | }
76 | .deck-container .deck-status {
77 | font-size: 0.6666em;
78 | }
79 | .deck-container.deck-menu .slide {
80 | background: #eee;
81 | }
82 | .deck-container.deck-menu .deck-current, .no-touch .deck-container.deck-menu .slide:hover {
83 | background: #ddf;
84 | }
85 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/navigation/deck.navigation.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Deck JS - deck.navigation
3 | Copyright (c) 2011 Caleb Troughton
4 | Dual licensed under the MIT license and GPL license.
5 | https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
6 | https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
7 | */
8 |
9 | /*
10 | This module adds clickable previous and next links to the deck.
11 | */
12 | (function($, deck, undefined) {
13 | var $d = $(document),
14 |
15 | /* Updates link hrefs, and disabled states if last/first slide */
16 | updateButtons = function(e, from, to) {
17 | var opts = $[deck]('getOptions'),
18 | last = $[deck]('getSlides').length - 1,
19 | prevSlide = $[deck]('getSlide', to - 1),
20 | nextSlide = $[deck]('getSlide', to + 1),
21 | hrefBase = window.location.href.replace(/#.*/, ''),
22 | prevId = prevSlide ? prevSlide.attr('id') : undefined,
23 | nextId = nextSlide ? nextSlide.attr('id') : undefined;
24 |
25 | $(opts.selectors.previousLink)
26 | .toggleClass(opts.classes.navDisabled, !to)
27 | .attr('href', hrefBase + '#' + (prevId ? prevId : ''));
28 | $(opts.selectors.nextLink)
29 | .toggleClass(opts.classes.navDisabled, to === last)
30 | .attr('href', hrefBase + '#' + (nextId ? nextId : ''));
31 | };
32 |
33 | /*
34 | Extends defaults/options.
35 |
36 | options.classes.navDisabled
37 | This class is added to a navigation link when that action is disabled.
38 | It is added to the previous link when on the first slide, and to the
39 | next link when on the last slide.
40 |
41 | options.selectors.nextLink
42 | The elements that match this selector will move the deck to the next
43 | slide when clicked.
44 |
45 | options.selectors.previousLink
46 | The elements that match this selector will move to deck to the previous
47 | slide when clicked.
48 | */
49 | $.extend(true, $[deck].defaults, {
50 | classes: {
51 | navDisabled: 'deck-nav-disabled'
52 | },
53 |
54 | selectors: {
55 | nextLink: '.deck-next-link',
56 | previousLink: '.deck-prev-link'
57 | }
58 | });
59 |
60 | $d.bind('deck.init', function() {
61 | var opts = $[deck]('getOptions'),
62 | slides = $[deck]('getSlides'),
63 | $current = $[deck]('getSlide'),
64 | ndx;
65 |
66 | // Setup prev/next link events
67 | $(opts.selectors.previousLink)
68 | .unbind('click.decknavigation')
69 | .bind('click.decknavigation', function(e) {
70 | $[deck]('prev');
71 | e.preventDefault();
72 | });
73 |
74 | $(opts.selectors.nextLink)
75 | .unbind('click.decknavigation')
76 | .bind('click.decknavigation', function(e) {
77 | $[deck]('next');
78 | e.preventDefault();
79 | });
80 |
81 | // Find where we started in the deck and set initial states
82 | $.each(slides, function(i, $slide) {
83 | if ($slide === $current) {
84 | ndx = i;
85 | return false;
86 | }
87 | });
88 | updateButtons(null, ndx, ndx);
89 | })
90 | .bind('deck.change', updateButtons);
91 | })(jQuery, 'deck');
92 |
93 |
--------------------------------------------------------------------------------
/src/js/widget/jqgrid.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import $ from 'jquery';
3 | import grid from 'jqgrid/js/jquery.jqGrid.src';
4 | import gridLocaleEn from 'jqgrid/js/i18n/grid.locale-en';
5 | import i18n from '../i18n';
6 | import dataAccess from '../sos-data-access';
7 | import ld from '../locale-date';
8 | import common from '../widget-common';
9 |
10 | import 'jquery-ui';
11 | import '../../assets/css/jquery-ui.css';
12 |
13 | import 'jqgrid/css/ui.jqgrid.css';
14 | import './jqgrid.css';
15 |
16 | grid($);
17 | gridLocaleEn($);
18 |
19 | let count = 0;
20 |
21 | export default {
22 | inputs: common.inputs.concat(['features', 'properties', 'time_start', 'time_end', 'title']),
23 | optional_inputs: common.optional_inputs,
24 | preferredSizes: [{ w: 530, h: 440 }],
25 |
26 | init(config, el, errorHandler) {
27 | // Render template
28 | el.innerHTML = [
29 | '',
36 | ].join('');
37 | el.querySelector('.title').innerHTML = config.title;
38 |
39 | // load widget common features
40 | common.init(config, el);
41 |
42 | function setFullWidth() {
43 | $('.grid').setGridWidth($(window).width() - 2);
44 | }
45 |
46 | function redraw(data) {
47 | // jqGrid table
48 | $(`#grid${count}`).first().jqGrid({
49 | datatype: 'local',
50 | height: 'auto',
51 | width: '100%',
52 | caption: i18n.t('Results'),
53 | data,
54 | pager: `#pager${count}`,
55 | rowNum: 12,
56 | sortname: 'time',
57 | autowidth: true,
58 | colNames: [
59 | i18n.t('Time'),
60 | i18n.t('Feature'),
61 | i18n.t('Property'),
62 | i18n.t('Value'),
63 | i18n.t('Unit'),
64 | ],
65 | colModel: [{
66 | name: 'time',
67 | index: 'time',
68 | width: '160',
69 | formatter(cellvalue) {
70 | return ld.display(cellvalue);
71 | },
72 | }, {
73 | name: 'feature',
74 | index: 'feature',
75 | width: '150',
76 | }, {
77 | name: 'property',
78 | index: 'property',
79 | width: '150',
80 | }, {
81 | name: 'value',
82 | index: 'value',
83 | width: '80',
84 | align: 'right',
85 | }, {
86 | name: 'uom',
87 | index: 'uom',
88 | width: '60',
89 | }],
90 | });
91 |
92 | $(window).bind('resize', setFullWidth);
93 | setFullWidth();
94 | }
95 |
96 | // Setup SOS data access
97 | const data = dataAccess(config, redraw, errorHandler);
98 | data.read();
99 | },
100 | };
101 |
--------------------------------------------------------------------------------
/src/js/widget/table.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import dataAccess from '../sos-data-access';
3 | import ld from '../locale-date';
4 | import common from '../widget-common';
5 |
6 | const template = [
7 | '',
12 | ].join('');
13 |
14 | export default {
15 | inputs: common.inputs.concat(['feature', 'properties', 'time_start', 'time_end', 'title']),
16 | optional_inputs: common.optional_inputs,
17 | preferredSizes: [{ w: 400, h: 400 }],
18 |
19 | init(config, el, errorHandler) {
20 | // Render template
21 | el.innerHTML = template;
22 | el.querySelector('h3').innerHTML = config.title;
23 | const table = el.querySelector('.table-responsive');
24 |
25 | // load widget common features
26 | common.init(config, el);
27 |
28 | // Update view
29 | function createTable(measures, properties) {
30 | let html = '';
31 | html += '';
32 | html += '';
33 | html += '| Result Time | ';
34 |
35 | const sortedNames = Object.keys(properties).sort();
36 | Object.keys(sortedNames).forEach((i) => {
37 | const name = sortedNames[i];
38 | const { uom } = properties[name];
39 | html += `${name} (${uom}) | `;
40 | });
41 | html += '
';
42 | html += '';
43 |
44 | const times = Object.keys(measures);
45 | times.sort().reverse();
46 | Object.keys(times).forEach((i) => {
47 | const time = times[i];
48 | const values = measures[time];
49 | html += '';
50 | html += `| ${ld.display(new Date(parseInt(time, 10)))} | `;
51 | Object.keys(sortedNames).forEach((j) => {
52 | html += `${values[sortedNames[j]]} | `;
53 | });
54 | html += '
';
55 | });
56 | html += '
';
57 | table.innerHTML = html;
58 | }
59 |
60 | function redraw(data) {
61 | // Get tabular data from observations
62 | const measures = {};
63 | const properties = {};
64 | Object.keys(data).forEach((i) => {
65 | const measure = data[i];
66 |
67 | // Add value in a time-indexed "measures" object
68 | const time = measure.time.getTime();
69 | if (!measures[time]) {
70 | measures[time] = {};
71 | }
72 | measures[time][measure.property] = measure.value;
73 |
74 | // Add property to a "properties" object, including uom
75 | if (!properties[measure.property]) {
76 | properties[measure.property] = {
77 | name: measure.property,
78 | uom: measure.uom,
79 | };
80 | }
81 | });
82 |
83 | createTable(measures, properties);
84 | }
85 |
86 | // Setup SOS data access
87 | const data = dataAccess(config, redraw, errorHandler);
88 | data.read();
89 | },
90 | };
91 |
--------------------------------------------------------------------------------
/src/assets/examples/meteo.apb.es/DataBrowser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | meteo.apb.es
11 |
27 |
28 |
29 |
30 |
31 |
61 |
62 |
63 |
Data Browser
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/test/fixtures/getFeatureOfInterestResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | http://sensors.portdebarcelona.cat/def/weather/features#01
6 | Dispensari
7 |
8 |
9 |
10 | 41.38001 2.18221
11 |
12 |
13 |
14 |
15 |
16 |
17 | http://sensors.portdebarcelona.cat/def/weather/features#02
18 | Sirena
19 |
20 |
21 |
22 | 41.34121 2.16611
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/assets/examples/sync-charts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sensor Widgets Simple Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/src/assets/examples/meteo.apb.es/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | meteo.apb.es
11 |
12 |
13 |
14 |
15 |
45 |
46 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/themes/transition/horizontal-slide.css:
--------------------------------------------------------------------------------
1 | .csstransitions.csstransforms {
2 | overflow-x: hidden;
3 | }
4 | .csstransitions.csstransforms .deck-container > .slide {
5 | -webkit-transition: -webkit-transform 500ms ease-in-out;
6 | -moz-transition: -moz-transform 500ms ease-in-out;
7 | -ms-transition: -ms-transform 500ms ease-in-out;
8 | -o-transition: -o-transform 500ms ease-in-out;
9 | transition: transform 500ms ease-in-out;
10 | }
11 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide {
12 | position: absolute;
13 | top: 0;
14 | left: 0;
15 | -webkit-box-sizing: border-box;
16 | -moz-box-sizing: border-box;
17 | box-sizing: border-box;
18 | width: 100%;
19 | padding: 0 48px;
20 | }
21 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .slide {
22 | position: relative;
23 | left: 0;
24 | top: 0;
25 | -webkit-transition: -webkit-transform 500ms ease-in-out, opacity 500ms ease-in-out;
26 | -moz-transition: -moz-transform 500ms ease-in-out, opacity 500ms ease-in-out;
27 | -ms-transition: -ms-transform 500ms ease-in-out, opacity 500ms ease-in-out;
28 | -o-transition: -o-transform 500ms ease-in-out, opacity 500ms ease-in-out;
29 | transition: -webkit-transform 500ms ease-in-out, opacity 500ms ease-in-out;
30 | }
31 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-next, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-after {
32 | visibility: visible;
33 | -webkit-transform: translate3d(200%, 0, 0);
34 | -moz-transform: translate(200%, 0);
35 | -ms-transform: translate(200%, 0);
36 | -o-transform: translate(200%, 0);
37 | transform: translate3d(200%, 0, 0);
38 | }
39 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-previous {
40 | -webkit-transform: translate3d(-200%, 0, 0);
41 | -moz-transform: translate(-200%, 0);
42 | -ms-transform: translate(-200%, 0);
43 | -o-transform: translate(-200%, 0);
44 | transform: translate3d(-200%, 0, 0);
45 | }
46 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-before {
47 | -webkit-transform: translate3d(-400%, 0, 0);
48 | -moz-transform: translate(-400%, 0);
49 | -ms-transform: translate(-400%, 0);
50 | -o-transform: translate(-400%, 0);
51 | transform: translate3d(-400%, 0, 0);
52 | }
53 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-next {
54 | -webkit-transform: translate3d(200%, 0, 0);
55 | -moz-transform: translate(200%, 0);
56 | -ms-transform: translate(200%, 0);
57 | -o-transform: translate(200%, 0);
58 | transform: translate3d(200%, 0, 0);
59 | }
60 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-after {
61 | -webkit-transform: translate3d(400%, 0, 0);
62 | -moz-transform: translate(400%, 0);
63 | -ms-transform: translate(400%, 0);
64 | -o-transform: translate(400%, 0);
65 | transform: translate3d(400%, 0, 0);
66 | }
67 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-before .slide, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-previous .slide {
68 | visibility: visible;
69 | }
70 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-child-current {
71 | -webkit-transform: none;
72 | -moz-transform: none;
73 | -ms-transform: none;
74 | -o-transform: none;
75 | transform: none;
76 | }
77 |
--------------------------------------------------------------------------------
/src/js/widget/timechart.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import dataAccess from '../sos-data-access';
3 | import ld from '../locale-date';
4 | import common from '../widget-common';
5 |
6 | import $ from '../jQuery-globals';
7 |
8 | import 'flot/lib/jquery.mousewheel';
9 | import 'flot/source/jquery.canvaswrapper';
10 | import 'flot/source/jquery.colorhelpers';
11 | import 'flot/source/jquery.flot';
12 | import 'flot/source/jquery.flot.uiConstants';
13 |
14 | import 'flot-plugins/dist/source/misc/jquery.flot.tooltip';
15 |
16 | const flotReq = require.context('../../../node_modules/flot/source/', true, /flot.*\.js$/);
17 | flotReq.keys().forEach(flotReq);
18 |
19 | // TODO readd legend
20 | // TODO readd pan and zoom
21 |
22 | const template = [
23 | '',
29 | ].join('');
30 |
31 | const timechart = {
32 | inputs: common.inputs.concat(['features', 'properties', 'time_start', 'time_end', 'title']),
33 | optional_inputs: common.optional_inputs,
34 | preferredSizes: [{ w: 650, h: 530 }],
35 |
36 | init(config, el, errorHandler) {
37 | // Render template
38 | el.innerHTML = template;
39 | el.querySelector('h3').innerHTML = config.title;
40 | const graph = el.querySelector('.graph');
41 |
42 | // load widget common features
43 | common.init(config, el);
44 |
45 | function redraw(data) {
46 | const series = {};
47 | Object.keys(data).forEach((i) => {
48 | const measure = data[i];
49 | const label = `${measure.property} (${measure.feature})`;
50 | if (!series[label]) {
51 | series[label] = {
52 | data: [],
53 | label,
54 | };
55 | }
56 | series[label].data.push([measure.time.getTime() / 1000, measure.value]);
57 | });
58 |
59 | // Sort data by time, convert to array
60 | const arr = [];
61 | Object.keys(series).forEach((k) => {
62 | series[k].data.sort((a, b) => b[0] - a[0]);
63 | arr.push(series[k]);
64 | });
65 |
66 | const options = {
67 | xaxis: {
68 | mode: 'time',
69 | timezone: ld.utc() ? 'UTC' : 'browser',
70 | },
71 | yaxis: {
72 | zoomRange: false,
73 | panRange: false,
74 | },
75 | grid: {
76 | hoverable: true,
77 | },
78 | legend: {
79 | container: el.querySelector('.legend'),
80 | },
81 | series: {
82 | lines: {
83 | show: true,
84 | },
85 | points: {
86 | show: true,
87 | },
88 | },
89 | tooltip: true,
90 | tooltipOpts: {
91 | content: data.length ? `[%x] %s: %y.2 ${data[0].uom}` : '',
92 | },
93 | zoom: {
94 | interactive: true,
95 | },
96 | pan: {
97 | interactive: true,
98 | },
99 | };
100 |
101 | if (config.colors) {
102 | options.colors = config.colors;
103 | }
104 |
105 | const plot = $.plot(graph, arr, options);
106 |
107 | if (config.callback) {
108 | config.callback(plot, graph);
109 | }
110 | }
111 |
112 | // Setup SOS data access
113 | const data = dataAccess(config, redraw, errorHandler);
114 | data.read();
115 | },
116 | };
117 |
118 | export default timechart;
119 |
--------------------------------------------------------------------------------
/src/js/widget/progressbar.css:
--------------------------------------------------------------------------------
1 | .progressbar.widget .progress {
2 | height: 85px;
3 | width: 90%;
4 | margin: 0 auto;
5 | padding-left: 12px;
6 | padding-right: 12px;
7 | }
8 |
9 | .progressbar.widget .background-bar {
10 | text-align: left;
11 | margin-top: 30px;
12 | background: #e9e5e2;
13 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e1ddd9), to(#e9e5e2));
14 | background-image: -webkit-linear-gradient(top, #e1ddd9, #e9e5e2);
15 | background-image: -moz-linear-gradient(top, #e1ddd9, #e9e5e2);
16 | background-image: -ms-linear-gradient(top, #e1ddd9, #e9e5e2);
17 | background-image: -o-linear-gradient(top, #e1ddd9, #e9e5e2);
18 | background-image: linear-gradient(top, #e1ddd9, #e9e5e2);
19 | height: 20px;
20 | border-radius: 10px;
21 | -moz-box-shadow: 0 1px 0 #bebbb9 inset, 0 1px 0 #fcfcfc;
22 | -webkit-box-shadow: 0 1px 0 #bebbb9 inset, 0 1px 0 #fcfcfc;
23 | box-shadow: 0 1px 0 #bebbb9 inset, 0 1px 0 #fcfcfc;
24 | }
25 | .progressbar.widget .bar {
26 | width: 10px;
27 | height: 18px;
28 | position: absolute;
29 | border-radius: 10px;
30 | -moz-box-shadow: 0 1px 0 #fcfcfc inset, 0 1px 0 #bebbb9;
31 | -webkit-box-shadow: 0 1px 0 #fcfcfc inset, 0 1px 0 #bebbb9;
32 | box-shadow: 0 1px 0 #fcfcfc inset, 0 1px 0 #bebbb9;
33 | }
34 | .progressbar.widget .min {
35 | position: relative;
36 | top: -20px;
37 | float: left;
38 | width: 0;
39 | }
40 | .progressbar.widget .max {
41 | position: relative;
42 | top: -20px;
43 | float: right;
44 | }
45 | .progressbar.widget .value {
46 | position: relative;
47 | top: 25px;
48 | text-align: right;
49 | }
50 | .progressbar.widget .pink {
51 | background-color: #f674a4;
52 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f674a4), to(#e06995));
53 | background-image: -webkit-linear-gradient(top, #f674a4, #e06995);
54 | background-image: -moz-linear-gradient(top, #f674a4, #e06995);
55 | background-image: -ms-linear-gradient(top, #f674a4, #e06995);
56 | background-image: -o-linear-gradient(top, #f674a4, #e06995);
57 | background-image: linear-gradient(top, #f674a4, #e06995);
58 | }
59 | .progressbar.widget .yellow {
60 | background-color: #f0bb4b;
61 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0bb4b), to(#d9aa44));
62 | background-image: -webkit-linear-gradient(top, #f0bb4b, #d9aa44);
63 | background-image: -moz-linear-gradient(top, #f0bb4b, #d9aa44);
64 | background-image: -ms-linear-gradient(top, #f0bb4b, #d9aa44);
65 | background-image: -o-linear-gradient(top, #f0bb4b, #d9aa44);
66 | background-image: linear-gradient(top, #f0bb4b, #d9aa44);
67 | }
68 | .progressbar.widget .green {
69 | background-color: #a1ce5b;
70 | background-image: -webkit-gradient(linear, left top, left bottom, from(#a1ce5b), to(#91ba52));
71 | background-image: -webkit-linear-gradient(top, #a1ce5b, #91ba52);
72 | background-image: -moz-linear-gradient(top, #a1ce5b, #91ba52);
73 | background-image: -ms-linear-gradient(top, #a1ce5b, #91ba52);
74 | background-image: -o-linear-gradient(top, #a1ce5b, #91ba52);
75 | background-image: linear-gradient(top, #a1ce5b, #91ba52);
76 | }
77 | .progressbar.widget .blue {
78 | background-color: #66b3cc;
79 | background-image: -webkit-gradient(linear, left top, left bottom, from(#66b3cc), to(#5da3ba));
80 | background-image: -webkit-linear-gradient(top, #66b3cc, #5da3ba);
81 | background-image: -moz-linear-gradient(top, #66b3cc, #5da3ba);
82 | background-image: -ms-linear-gradient(top, #66b3cc, #5da3ba);
83 | background-image: -o-linear-gradient(top, #66b3cc, #5da3ba);
84 | background-image: linear-gradient(top, #66b3cc, #5da3ba);
85 | }
86 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/themes/transition/vertical-slide.css:
--------------------------------------------------------------------------------
1 | .csstransitions.csstransforms .deck-container {
2 | overflow-y: hidden;
3 | }
4 | .csstransitions.csstransforms .deck-container > .slide {
5 | -webkit-transition: -webkit-transform 500ms ease-in-out;
6 | -moz-transition: -moz-transform 500ms ease-in-out;
7 | -ms-transition: -ms-transform 500ms ease-in-out;
8 | -o-transition: -o-transform 500ms ease-in-out;
9 | transition: transform 500ms ease-in-out;
10 | }
11 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide {
12 | position: absolute;
13 | top: 0;
14 | left: 0;
15 | -webkit-box-sizing: border-box;
16 | -moz-box-sizing: border-box;
17 | box-sizing: border-box;
18 | width: 100%;
19 | padding: 0 48px;
20 | }
21 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .slide {
22 | position: relative;
23 | left: 0;
24 | top: 0;
25 | -webkit-transition: -webkit-transform 500ms ease-in-out, opacity 500ms ease-in-out;
26 | -moz-transition: -moz-transform 500ms ease-in-out, opacity 500ms ease-in-out;
27 | -ms-transition: -ms-transform 500ms ease-in-out, opacity 500ms ease-in-out;
28 | -o-transition: -o-transform 500ms ease-in-out, opacity 500ms ease-in-out;
29 | transition: -webkit-transform 500ms ease-in-out, opacity 500ms ease-in-out;
30 | }
31 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-next, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .slide .deck-after {
32 | visibility: visible;
33 | -webkit-transform: translate3d(0, 1600px, 0);
34 | -moz-transform: translate(0, 1600px);
35 | -ms-transform: translate(0, 1600px);
36 | -o-transform: translate(0, 1600px);
37 | transform: translate3d(0, 1600px, 0);
38 | }
39 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-previous {
40 | -webkit-transform: translate3d(0, -200%, 0);
41 | -moz-transform: translate(0, -200%);
42 | -ms-transform: translate(0, -200%);
43 | -o-transform: translate(0, -200%);
44 | transform: translate3d(0, -200%, 0);
45 | }
46 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-before {
47 | -webkit-transform: translate3d(0, -400%, 0);
48 | -moz-transform: translate(0, -400%);
49 | -ms-transform: translate(0, -400%);
50 | -o-transform: translate(0, -400%);
51 | transform: translate3d(0, -400%, 0);
52 | }
53 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-next {
54 | -webkit-transform: translate3d(0, 200%, 0);
55 | -moz-transform: translate(0, 200%);
56 | -ms-transform: translate(0, 200%);
57 | -o-transform: translate(0, 200%);
58 | transform: translate3d(0, 200%, 0);
59 | }
60 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-after {
61 | -webkit-transform: translate3d(0, 400%, 0);
62 | -moz-transform: translate(0, 400%);
63 | -ms-transform: translate(0, 400%);
64 | -o-transform: translate(0, 400%);
65 | transform: translate3d(0, 400%, 0);
66 | }
67 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-before .slide, .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-previous .slide {
68 | visibility: visible;
69 | }
70 | .csstransitions.csstransforms .deck-container:not(.deck-menu) > .deck-child-current {
71 | -webkit-transform: none;
72 | -moz-transform: none;
73 | -ms-transform: none;
74 | -o-transform: none;
75 | transform: none;
76 | }
77 | .csstransitions.csstransforms .deck-prev-link {
78 | left: auto;
79 | right: 8px;
80 | top: 59px;
81 | -webkit-transform: rotate(90deg);
82 | -moz-transform: rotate(90deg);
83 | -ms-transform: rotate(90deg);
84 | -o-transform: rotate(90deg);
85 | transform: rotate(90deg);
86 | }
87 | .csstransitions.csstransforms .deck-next-link {
88 | top: 99px;
89 | -webkit-transform: rotate(90deg);
90 | -moz-transform: rotate(90deg);
91 | -ms-transform: rotate(90deg);
92 | -o-transform: rotate(90deg);
93 | transform: rotate(90deg);
94 | }
95 |
--------------------------------------------------------------------------------
/doc/slides/img/s_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
--------------------------------------------------------------------------------
/src/assets/img/s_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
--------------------------------------------------------------------------------
/src/assets/examples/meteo.apb.es/TorreControl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | meteo.apb.es
11 |
12 |
13 |
14 |
15 |
45 |
46 |
47 |
Torre Control
48 |
49 |
50 |
51 |
52 |
P4-DIC SUD: Edifici PIF ZAL Prat
53 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
02-SIRENA: Balisa moll nou Contradic
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
03-ADOSSAT: BOCANA NORD
79 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/assets/img/gridtile.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/js/sos-data-access.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary, camelcase */
2 | import SOS, { isArray } from './SOS';
3 |
4 | const propertyNames = {};
5 | const waitingDescribeResponse = {};
6 | const propertyCallbackQueue = {};
7 |
8 | export default (config, redraw, errorHandler) => {
9 | SOS.setUrl(config.service);
10 |
11 | function getPropertyName(procedure, id, callback, context) {
12 | if (!propertyNames[procedure]) {
13 | // Queue callback call
14 | if (!propertyCallbackQueue[procedure]) {
15 | propertyCallbackQueue[procedure] = [];
16 | }
17 |
18 | propertyCallbackQueue[procedure].push({
19 | callback,
20 | id,
21 | context,
22 | });
23 |
24 | if (!waitingDescribeResponse[procedure]) {
25 | waitingDescribeResponse[procedure] = true;
26 | // Trigger a DescribeSensor, cache all property names for this procedure
27 | SOS.describeSensor(procedure, (description) => {
28 | let properties = Object.prototype.hasOwnProperty.call(description, 'ProcessModel')
29 | ? description.ProcessModel.outputs.OutputList.output
30 | : description.System.outputs.OutputList.output;
31 | properties = properties instanceof Array ? properties : [properties];
32 | const types = ['Quantity', 'Count', 'Boolean', 'Category', 'Text', 'ObservableProperty'];
33 |
34 | const names = [];
35 | Object.values(properties).forEach((property) => {
36 | Object.values(types).forEach((type) => {
37 | if (Object.prototype.hasOwnProperty.call(property, type)) {
38 | // eslint-disable-next-line no-param-reassign
39 | property.id = property[type].definition;
40 | }
41 | });
42 | names[property.id] = property.name;
43 | });
44 | propertyNames[procedure] = names;
45 |
46 | // Clear propertyCallbackQueue
47 | while (propertyCallbackQueue[procedure].length) {
48 | const elem = propertyCallbackQueue[procedure].shift();
49 | elem.callback.call(undefined, propertyNames[procedure][elem.id], elem.context);
50 | }
51 | }, errorHandler);
52 | }
53 | } else {
54 | callback(propertyNames[procedure][id], context);
55 | }
56 | }
57 |
58 | function parse(observations) {
59 | if (!observations.length) {
60 | redraw([]);
61 | }
62 |
63 | // Get tabular data from observations
64 | const data = [];
65 |
66 | function addObservation(property, observation) {
67 | const foi = observation.featureOfInterest;
68 | data.push({
69 | time: new Date(observation.resultTime),
70 | value: Object.prototype.hasOwnProperty.call(observation.result, 'value') ? observation.result.value : observation.result,
71 | feature: foi.name ? foi.name.value : (foi.identifier ? foi.identifier.value : foi),
72 | property,
73 | uom: Object.prototype.hasOwnProperty.call(observation.result, 'uom') ? observation.result.uom : '(N/A)',
74 | });
75 | if (data.length === observations.length) {
76 | redraw(data);
77 | }
78 | }
79 | Object.values(observations).forEach((observation) => {
80 | getPropertyName(observation.procedure, observation.observableProperty,
81 | addObservation, observation);
82 | });
83 | }
84 |
85 | function read() {
86 | const {
87 | offering, feature, property, features, properties, time_start, time_end,
88 | } = config;
89 | const getFeatures = feature
90 | ? [feature]
91 | : isArray(features)
92 | ? features
93 | : features
94 | ? JSON.parse(features)
95 | : undefined;
96 | const getProperties = property
97 | ? [property]
98 | : isArray(properties)
99 | ? properties
100 | : properties
101 | ? JSON.parse(properties)
102 | : undefined;
103 | const time = (time_start && time_end) ? [time_start, time_end] : 'latest';
104 | SOS.getObservation(offering, getFeatures, getProperties, time, parse, errorHandler);
105 | }
106 |
107 | return {
108 | read,
109 | };
110 | };
111 |
--------------------------------------------------------------------------------
/src/assets/wizard/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Sensor Widget Wizard
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
33 |
34 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
54 |
55 |
56 |
64 |
65 |
66 |
Take Away
67 |
68 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/src/js/SensorWidget.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | import i18n from './i18n';
3 | import './SensorWidgets.css';
4 |
5 | // eslint-disable-next-line camelcase,no-undef
6 | __webpack_public_path__ = document.currentScript.src.replace(/[^/]*$/, '');
7 |
8 | const instances = {};
9 | // eslint-disable-next-line no-plusplus
10 | const uid = ((i) => () => `SensorWidgetTarget-${++i}`)(0);
11 |
12 | export default function SensorWidget(name, config, renderTo) {
13 | const target = renderTo || document.body;
14 |
15 | function errorHandler(message, url, request) {
16 | let text = '';
17 | if (url) {
18 | text = `[${url}] `;
19 | }
20 | if (request && request.request) {
21 | text += `${request.request}: `;
22 | }
23 | if (message) {
24 | text += message;
25 | }
26 | target.innerHTML = `${text}
`;
27 | }
28 |
29 | function checkConfig(widgetName, widgetInputs, widgetConfig) {
30 | const missing = [];
31 |
32 | Object.values(widgetInputs).forEach((input) => {
33 | if (!Object.prototype.hasOwnProperty.call(widgetConfig, input)) {
34 | missing.push(input);
35 | }
36 | });
37 | if (missing.length) {
38 | errorHandler(i18n.t("The '{name}' widget is missing some mandatory parameters: ", { name: widgetName }) + missing.join(', '));
39 | }
40 | return !missing.length;
41 | }
42 |
43 | if (name && config) {
44 | if (!target.id) target.id = uid();
45 |
46 | if (!config.service) {
47 | config.service = '/52n-sos/sos/json';
48 | }
49 |
50 | import(/* webpackChunkName: "widget-[request]" */ `./widget/${name}.js`)
51 | .then(({ default: widget }) => {
52 | target.innerHTML = '';
53 | if (Object.prototype.hasOwnProperty.call(instances, target.id) && instances[target.id] && Object.prototype.hasOwnProperty.call(instances[target.id], 'destroy')) {
54 | console.debug(`Destroying previous widget on ElementId=${target.id}`);
55 | instances[target.id].destroy();
56 | delete instances[target.id];
57 | }
58 | if (checkConfig(name, widget.inputs, config)) {
59 | console.debug(`Creating new ${name} widget on ElementId=${target.id}`);
60 | instances[target.id] = widget.init(config, target, errorHandler);
61 | }
62 | }).catch((cause) => {
63 | console.error(cause);
64 | errorHandler(i18n.t("Widget '{name}' cannot be found, or there was an error instantiating it", { name }));
65 | });
66 | } else if (!name) {
67 | errorHandler(i18n.t('No widget name specified'));
68 | }
69 | return {
70 | name,
71 | config,
72 | renderTo: target,
73 | inspect(cb) {
74 | import(/* webpackChunkName: "widget-[request]" */ `./widget/${name}.js`)
75 | .then(({ default: widget }) => {
76 | cb.call(this, widget.inputs, widget.optional_inputs, widget.preferredSizes);
77 | });
78 | },
79 | url() {
80 | function relPathToAbs(pathname) {
81 | const output = [];
82 | pathname.replace(/^(\.\.?(\/|$))+/, '')
83 | .replace(/\/(\.(\/|$))+/g, '/')
84 | .replace(/\/\.\.$/, '/../')
85 | .replace(/\/?[^/]*/g, (p) => {
86 | if (p === '/..') {
87 | output.pop();
88 | } else {
89 | output.push(p);
90 | }
91 | });
92 | return output.join('').replace(/^\//, pathname.charAt(0) === '/' ? '/' : '');
93 | }
94 | let url = `${relPathToAbs('../widget/')}?`;
95 | url += `name=${encodeURIComponent(name)}&`;
96 | url += Object.keys(config).map((key) => {
97 | let val = config[key];
98 | if (typeof config[key] === 'object') {
99 | val = JSON.stringify(config[key]);
100 | }
101 | return `${key}=${encodeURIComponent(val)}`;
102 | }).join('&');
103 | url += `&lang=${i18n.getLang()}`;
104 | return url;
105 | },
106 | iframe(w, h) {
107 | w = w || '100%';
108 | h = h || '100%';
109 | return ``;
110 | },
111 | javascript() {
112 | return `SensorWidget('${name}', ${JSON.stringify(config, null, 3)}, document.getElementById('${name}-container'));`;
113 | },
114 | };
115 | }
116 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/themes/style/neon.css:
--------------------------------------------------------------------------------
1 | .deck-container {
2 | font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
3 | font-size: 1.75em;
4 | color: #aaa;
5 | background: #000;
6 | }
7 | .deck-container .slide {
8 | background: #000;
9 | }
10 | .deck-container .slide h1 {
11 | color: #0af;
12 | font-weight: normal;
13 | font-weight: 100;
14 | text-shadow: 0 0 50px #0af, 0 0 3px #fff;
15 | }
16 | .deck-container .slide h2 {
17 | color: #af0;
18 | border-bottom-color: #ccc;
19 | font-weight: normal;
20 | font-weight: 100;
21 | text-shadow: 0 0 15px #af0, 0 0 2px #fff;
22 | border-bottom: 1px solid #333;
23 | }
24 | .deck-container .slide h3 {
25 | color: #fff;
26 | font-weight: normal;
27 | font-weight: 100;
28 | text-shadow: 0 0 10px #fff, 0 0 2px #fff;
29 | }
30 | .deck-container .slide pre {
31 | border-color: #333;
32 | }
33 | .deck-container .slide pre code {
34 | color: #fff;
35 | }
36 | .deck-container .slide code {
37 | color: #f0a;
38 | }
39 | .deck-container .slide blockquote {
40 | font-size: 2em;
41 | padding: 1em 2em;
42 | color: #fff;
43 | border-left: 5px solid #fff;
44 | }
45 | .deck-container .slide blockquote p {
46 | margin: 0;
47 | }
48 | .deck-container .slide blockquote cite {
49 | font-size: .5em;
50 | font-style: normal;
51 | font-weight: normal;
52 | font-weight: 100;
53 | color: #aaa;
54 | text-shadow: 0 0 15px #fff, 0 0 2px #fff;
55 | }
56 | .deck-container .slide ::-moz-selection {
57 | background: #a0f;
58 | }
59 | .deck-container .slide ::selection {
60 | background: #a0f;
61 | }
62 | .deck-container .slide a, .deck-container .slide a:hover, .deck-container .slide a:focus, .deck-container .slide a:active, .deck-container .slide a:visited {
63 | color: #f0a;
64 | text-decoration: none;
65 | }
66 | .deck-container .slide a:hover, .deck-container .slide a:focus {
67 | text-decoration: underline;
68 | }
69 | .deck-container .deck-prev-link, .deck-container .deck-next-link {
70 | background: #f0a;
71 | text-shadow: 0 0 3px #fff;
72 | }
73 | .deck-container .deck-prev-link, .deck-container .deck-prev-link:hover, .deck-container .deck-prev-link:focus, .deck-container .deck-prev-link:active, .deck-container .deck-prev-link:visited, .deck-container .deck-next-link, .deck-container .deck-next-link:hover, .deck-container .deck-next-link:focus, .deck-container .deck-next-link:active, .deck-container .deck-next-link:visited {
74 | color: #fff;
75 | }
76 | .deck-container .deck-prev-link:hover, .deck-container .deck-prev-link:focus, .deck-container .deck-next-link:hover, .deck-container .deck-next-link:focus {
77 | text-decoration: none;
78 | }
79 | .boxshadow .deck-container .deck-prev-link:hover, .boxshadow .deck-container .deck-prev-link:focus, .boxshadow .deck-container .deck-next-link:hover, .boxshadow .deck-container .deck-next-link:focus {
80 | -webkit-box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
81 | -moz-box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
82 | box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
83 | }
84 | .deck-container > .slide .deck-before, .deck-container > .slide .deck-previous {
85 | opacity: 0.4;
86 | }
87 | .deck-container > .slide .deck-before:not(.deck-child-current) .deck-before, .deck-container > .slide .deck-before:not(.deck-child-current) .deck-previous, .deck-container > .slide .deck-previous:not(.deck-child-current) .deck-before, .deck-container > .slide .deck-previous:not(.deck-child-current) .deck-previous {
88 | opacity: 1;
89 | }
90 | .deck-container > .slide .deck-child-current {
91 | opacity: 1;
92 | }
93 | .deck-container .deck-status {
94 | font-size: 0.6666em;
95 | }
96 | .deck-container .goto-form {
97 | background: #000;
98 | border: 1px solid #f0a;
99 | }
100 | .deck-container .goto-form label {
101 | color: #fff;
102 | }
103 | .deck-container.deck-menu .slide {
104 | background: #333;
105 | }
106 | .deck-container.deck-menu .deck-current {
107 | background: #444;
108 | }
109 | .boxshadow .deck-container.deck-menu .deck-current {
110 | background: #000;
111 | -webkit-box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
112 | -moz-box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
113 | box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
114 | }
115 | .no-touch .deck-container.deck-menu .slide:hover {
116 | background: #444;
117 | }
118 | .no-touch.boxshadow .deck-container.deck-menu .slide:hover {
119 | background: #000;
120 | -webkit-box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
121 | -moz-box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
122 | box-shadow: 0 0 20px #f0a, 0 0 5px #fff;
123 | }
124 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/hash/deck.hash.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Deck JS - deck.hash
3 | Copyright (c) 2011 Caleb Troughton
4 | Dual licensed under the MIT license and GPL license.
5 | https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
6 | https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
7 | */
8 |
9 | /*
10 | This module adds deep linking to individual slides, enables internal links
11 | to slides within decks, and updates the address bar with the hash as the user
12 | moves through the deck. A permalink anchor is also updated. Standard themes
13 | hide this link in browsers that support the History API, and show it for
14 | those that do not. Slides that do not have an id are assigned one according to
15 | the hashPrefix option. In addition to the on-slide container state class
16 | kept by core, this module adds an on-slide state class that uses the id of each
17 | slide.
18 | */
19 | (function ($, deck, window, undefined) {
20 | var $d = $(document),
21 | $window = $(window),
22 |
23 | /* Collection of internal fragment links in the deck */
24 | $internals,
25 |
26 | /*
27 | Internal only function. Given a string, extracts the id from the hash,
28 | matches it to the appropriate slide, and navigates there.
29 | */
30 | goByHash = function(str) {
31 | var id = str.substr(str.indexOf("#") + 1),
32 | slides = $[deck]('getSlides');
33 |
34 | $.each(slides, function(i, $el) {
35 | if ($el.attr('id') === id) {
36 | $[deck]('go', i);
37 | return false;
38 | }
39 | });
40 |
41 | // If we don't set these to 0 the container scrolls due to hashchange
42 | $[deck]('getContainer').scrollLeft(0).scrollTop(0);
43 | };
44 |
45 | /*
46 | Extends defaults/options.
47 |
48 | options.selectors.hashLink
49 | The element matching this selector has its href attribute updated to
50 | the hash of the current slide as the user navigates through the deck.
51 |
52 | options.hashPrefix
53 | Every slide that does not have an id is assigned one at initialization.
54 | Assigned ids take the form of hashPrefix + slideIndex, e.g., slide-0,
55 | slide-12, etc.
56 |
57 | options.preventFragmentScroll
58 | When deep linking to a hash of a nested slide, this scrolls the deck
59 | container to the top, undoing the natural browser behavior of scrolling
60 | to the document fragment on load.
61 | */
62 | $.extend(true, $[deck].defaults, {
63 | selectors: {
64 | hashLink: '.deck-permalink'
65 | },
66 |
67 | hashPrefix: 'slide-',
68 | preventFragmentScroll: true
69 | });
70 |
71 |
72 | $d.bind('deck.init', function() {
73 | var opts = $[deck]('getOptions');
74 | $internals = $(),
75 | slides = $[deck]('getSlides');
76 |
77 | $.each(slides, function(i, $el) {
78 | var hash;
79 |
80 | /* Hand out ids to the unfortunate slides born without them */
81 | if (!$el.attr('id') || $el.data('deckAssignedId') === $el.attr('id')) {
82 | $el.attr('id', opts.hashPrefix + i);
83 | $el.data('deckAssignedId', opts.hashPrefix + i);
84 | }
85 |
86 | hash ='#' + $el.attr('id');
87 |
88 | /* Deep link to slides on init */
89 | if (hash === window.location.hash) {
90 | $[deck]('go', i);
91 | }
92 |
93 | /* Add internal links to this slide */
94 | $internals = $internals.add('a[href="' + hash + '"]');
95 | });
96 |
97 | if (!Modernizr.hashchange) {
98 | /* Set up internal links using click for the poor browsers
99 | without a hashchange event. */
100 | $internals.unbind('click.deckhash').bind('click.deckhash', function(e) {
101 | goByHash($(this).attr('href'));
102 | });
103 | }
104 |
105 | /* Set up first id container state class */
106 | if (slides.length) {
107 | $[deck]('getContainer').addClass(opts.classes.onPrefix + $[deck]('getSlide').attr('id'));
108 | };
109 | })
110 | /* Update permalink, address bar, and state class on a slide change */
111 | .bind('deck.change', function(e, from, to) {
112 | var hash = '#' + $[deck]('getSlide', to).attr('id'),
113 | hashPath = window.location.href.replace(/#.*/, '') + hash,
114 | opts = $[deck]('getOptions'),
115 | osp = opts.classes.onPrefix,
116 | $c = $[deck]('getContainer');
117 |
118 | $c.removeClass(osp + $[deck]('getSlide', from).attr('id'));
119 | $c.addClass(osp + $[deck]('getSlide', to).attr('id'));
120 |
121 | $(opts.selectors.hashLink).attr('href', hashPath);
122 | if (Modernizr.history) {
123 | window.history.replaceState({}, "", hashPath);
124 | }
125 | });
126 |
127 | /* Deals with internal links in modern browsers */
128 | $window.bind('hashchange.deckhash', function(e) {
129 | if (e.originalEvent && e.originalEvent.newURL) {
130 | goByHash(e.originalEvent.newURL);
131 | }
132 | else {
133 | goByHash(window.location.hash);
134 | }
135 | })
136 | /* Prevent scrolling on deep links */
137 | .bind('load', function() {
138 | if ($[deck]('getOptions').preventFragmentScroll) {
139 | $[deck]('getContainer').scrollLeft(0).scrollTop(0);
140 | }
141 | });
142 | })(jQuery, 'deck', this);
--------------------------------------------------------------------------------
/src/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Sensor Widgets
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
86 |
87 |
88 |
89 |
90 |

91 |
Sensor Widgets
92 |
Configurable graphical components for your SOS sensor data.
93 |
100% Javascript. Extensible. MIT licensed.
94 |
95 |
96 | Build your own »
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/src/test/XML.test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import XML from '../js/XML';
3 |
4 | import getLatestObservationRequest from './fixtures/getLatestObservationsRequest.xml';
5 |
6 | const removeWhiteSpace = (str) => str.replace(/\s+/g, ' ').replace(/>[\t ]+<').trim();
7 |
8 | describe('XML', () => {
9 | describe('#read()', () => {
10 | it('should read simple XML', () => {
11 | // GIVEN
12 | const givenXml = 'Hola';
13 |
14 | // WHEN
15 | const result = XML.read(givenXml);
16 |
17 | // THEN
18 | expect(result).to.deep.equal({ xml: { test: 'Hola' } });
19 | });
20 |
21 | it('should read XML with namespaces', () => {
22 | // WHEN
23 | const result = XML.read(getLatestObservationRequest);
24 |
25 | // THEN
26 | const expected = {
27 | 'sos:GetObservation': {
28 | '@xmlns:sos': 'http://www.opengis.net/sos/2.0',
29 | '@xmlns:fes': 'http://www.opengis.net/fes/2.0',
30 | '@xmlns:gml': 'http://www.opengis.net/gml/3.2',
31 | '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
32 | '@service': 'SOS',
33 | '@version': '2.0.0',
34 | '@xsi:schemaLocation': 'http://www.opengis.net/sos/2.0 http://schemas.opengis.net/sos/2.0/sos.xsd',
35 | 'sos:procedure': 'http://sensors.portdebarcelona.cat/def/weather/procedure',
36 | 'sos:offering': 'http://sensors.portdebarcelona.cat/def/weather/offerings#1M',
37 | 'sos:observedProperty': 'http://sensors.portdebarcelona.cat/def/weather/properties#33M',
38 | 'sos:temporalFilter': {
39 | 'fes:TEquals': {
40 | 'fes:ValueReference': 'resultTime',
41 | 'gml:TimeInstant': {
42 | '@gml:id': 'ti_1',
43 | 'gml:timePosition': 'latest',
44 | },
45 | },
46 | },
47 | 'sos:featureOfInterest': 'http://sensors.portdebarcelona.cat/def/weather/features#02',
48 | 'sos:responseFormat': 'http://www.opengis.net/om/2.0',
49 | },
50 | };
51 | expect(result).to.deep.equal(expected);
52 | });
53 |
54 | it('should read XML without namespaces', () => {
55 | // WHEN
56 | const result = XML.read(getLatestObservationRequest, true);
57 |
58 | // THEN
59 | const expected = {
60 | GetObservation: {
61 | service: 'SOS',
62 | version: '2.0.0',
63 | schemaLocation: 'http://www.opengis.net/sos/2.0 http://schemas.opengis.net/sos/2.0/sos.xsd',
64 | procedure: 'http://sensors.portdebarcelona.cat/def/weather/procedure',
65 | offering: 'http://sensors.portdebarcelona.cat/def/weather/offerings#1M',
66 | observedProperty: 'http://sensors.portdebarcelona.cat/def/weather/properties#33M',
67 | temporalFilter: {
68 | TEquals: {
69 | ValueReference: 'resultTime',
70 | TimeInstant: {
71 | id: 'ti_1',
72 | timePosition: 'latest',
73 | },
74 | },
75 | },
76 | featureOfInterest: 'http://sensors.portdebarcelona.cat/def/weather/features#02',
77 | responseFormat: 'http://www.opengis.net/om/2.0',
78 | },
79 | };
80 | expect(result).to.deep.equal(expected);
81 | });
82 | });
83 |
84 | describe('#write()', () => {
85 | it('should write simple XML', () => {
86 | // GIVEN
87 | const givenObject = { xml: { test: 'Hola' } };
88 |
89 | // WHEN
90 | const result = XML.write(givenObject);
91 |
92 | // THEN
93 | expect(result).to.deep.equal('Hola');
94 | });
95 |
96 | it('should write XML with namespaces', () => {
97 | // GIVEN
98 | const givenObject = {
99 | 'sos:GetObservation': {
100 | '@xmlns:sos': 'http://www.opengis.net/sos/2.0',
101 | '@xmlns:fes': 'http://www.opengis.net/fes/2.0',
102 | '@xmlns:gml': 'http://www.opengis.net/gml/3.2',
103 | '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
104 | '@service': 'SOS',
105 | '@version': '2.0.0',
106 | '@xsi:schemaLocation': 'http://www.opengis.net/sos/2.0 http://schemas.opengis.net/sos/2.0/sos.xsd',
107 | 'sos:procedure': 'http://sensors.portdebarcelona.cat/def/weather/procedure',
108 | 'sos:offering': 'http://sensors.portdebarcelona.cat/def/weather/offerings#1M',
109 | 'sos:observedProperty': 'http://sensors.portdebarcelona.cat/def/weather/properties#33M',
110 | 'sos:temporalFilter': {
111 | 'fes:TEquals': {
112 | 'fes:ValueReference': 'resultTime',
113 | 'gml:TimeInstant': {
114 | '@gml:id': 'ti_1',
115 | 'gml:timePosition': 'latest',
116 | },
117 | },
118 | },
119 | 'sos:featureOfInterest': 'http://sensors.portdebarcelona.cat/def/weather/features#02',
120 | 'sos:responseFormat': 'http://www.opengis.net/om/2.0',
121 | },
122 | };
123 |
124 | // WHEN
125 | const result = XML.write(givenObject);
126 |
127 | // THEN
128 | const expected = removeWhiteSpace(getLatestObservationRequest);
129 | expect(result).to.equal(expected);
130 | });
131 | });
132 | });
133 |
--------------------------------------------------------------------------------
/doc/slides/deckjs/extensions/goto/deck.goto.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Deck JS - deck.goto
3 | Copyright (c) 2011 Caleb Troughton
4 | Dual licensed under the MIT license and GPL license.
5 | https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
6 | https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
7 | */
8 |
9 | /*
10 | This module adds the necessary methods and key bindings to show and hide a form
11 | for jumping to any slide number/id in the deck (and processes that form
12 | accordingly). The form-showing state is indicated by the presence of a class on
13 | the deck container.
14 | */
15 | (function($, deck, undefined) {
16 | var $d = $(document);
17 |
18 | /*
19 | Extends defaults/options.
20 |
21 | options.classes.goto
22 | This class is added to the deck container when showing the Go To Slide
23 | form.
24 |
25 | options.selectors.gotoDatalist
26 | The element that matches this selector is the datalist element that will
27 | be populated with options for each of the slide ids. In browsers that
28 | support the datalist element, this provides a drop list of slide ids to
29 | aid the user in selecting a slide.
30 |
31 | options.selectors.gotoForm
32 | The element that matches this selector is the form that is submitted
33 | when a user hits enter after typing a slide number/id in the gotoInput
34 | element.
35 |
36 | options.selectors.gotoInput
37 | The element that matches this selector is the text input field for
38 | entering a slide number/id in the Go To Slide form.
39 |
40 | options.keys.goto
41 | The numeric keycode used to show the Go To Slide form.
42 |
43 | options.countNested
44 | If false, only top level slides will be counted when entering a
45 | slide number.
46 | */
47 | $.extend(true, $[deck].defaults, {
48 | classes: {
49 | goto: 'deck-goto'
50 | },
51 |
52 | selectors: {
53 | gotoDatalist: '#goto-datalist',
54 | gotoForm: '.goto-form',
55 | gotoInput: '#goto-slide'
56 | },
57 |
58 | keys: {
59 | goto: 71 // g
60 | },
61 |
62 | countNested: true
63 | });
64 |
65 | /*
66 | jQuery.deck('showGoTo')
67 |
68 | Shows the Go To Slide form by adding the class specified by the goto class
69 | option to the deck container.
70 | */
71 | $[deck]('extend', 'showGoTo', function() {
72 | $[deck]('getContainer').addClass($[deck]('getOptions').classes.goto);
73 | $($[deck]('getOptions').selectors.gotoInput).focus();
74 | });
75 |
76 | /*
77 | jQuery.deck('hideGoTo')
78 |
79 | Hides the Go To Slide form by removing the class specified by the goto class
80 | option from the deck container.
81 | */
82 | $[deck]('extend', 'hideGoTo', function() {
83 | $($[deck]('getOptions').selectors.gotoInput).blur();
84 | $[deck]('getContainer').removeClass($[deck]('getOptions').classes.goto);
85 | });
86 |
87 | /*
88 | jQuery.deck('toggleGoTo')
89 |
90 | Toggles between showing and hiding the Go To Slide form.
91 | */
92 | $[deck]('extend', 'toggleGoTo', function() {
93 | $[deck]($[deck]('getContainer').hasClass($[deck]('getOptions').classes.goto) ? 'hideGoTo' : 'showGoTo');
94 | });
95 |
96 | $d.bind('deck.init', function() {
97 | var opts = $[deck]('getOptions'),
98 | $datalist = $(opts.selectors.gotoDatalist),
99 | slideTest = $.map([
100 | opts.classes.before,
101 | opts.classes.previous,
102 | opts.classes.current,
103 | opts.classes.next,
104 | opts.classes.after
105 | ], function(el, i) {
106 | return '.' + el;
107 | }).join(', '),
108 | rootCounter = 1;
109 |
110 | // Bind key events
111 | $d.unbind('keydown.deckgoto').bind('keydown.deckgoto', function(e) {
112 | var key = $[deck]('getOptions').keys.goto;
113 |
114 | if (e.which === key || $.inArray(e.which, key) > -1) {
115 | e.preventDefault();
116 | $[deck]('toggleGoTo');
117 | }
118 | });
119 |
120 | /* Populate datalist and work out countNested*/
121 | $.each($[deck]('getSlides'), function(i, $slide) {
122 | var id = $slide.attr('id'),
123 | $parentSlides = $slide.parentsUntil(opts.selectors.container, slideTest);
124 |
125 | if (id) {
126 | $datalist.append('