├── 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 |
3 | 4 | 5 | 6 | 7 |
-------------------------------------------------------------------------------- /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 | '
', 8 | drawing, 9 | '
', 10 | '
'].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 | '
', 10 | '

', 11 | '

', 12 | '
', 13 | '
0
', 14 | '
100
', 15 | '
', 16 | '', 17 | '
', 18 | '
', 19 | '
', 20 | '
', 21 | '

', 22 | '
', 23 | '
', 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 | '
', 10 | '

', 11 | drawing, 12 | '
', 13 | '

: ', i18n.t('Cel'), '

', 14 | '

', i18n.t('Request time'), ':

', 15 | '

', i18n.t('Result time'), ':

', 16 | '
', 17 | '
', 18 | '
', 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 | '
', 9 | '

', 10 | '

', i18n.t('Loading...'), '

', 11 | '
', 12 | '
', 13 | '
', 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 | '
', 13 | `
${i18n.t('Loading...')}
`, 14 | '
', 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 += ``; 52 | }); 53 | html += ''; 54 | Object.keys(table).forEach((p) => { 55 | html += ``; 56 | Object.keys(features).forEach((f) => { 57 | f = features[f]; 58 | if (table[p][f]) { 59 | 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 | '
', 10 | '

', 11 | drawing, 12 | '
', 13 | '', 14 | '

:
', i18n.t('deg'), '

', 15 | '

', i18n.t('Request time'), ':

', 16 | '

', i18n.t('Result time'), ':

', 17 | '
', 18 | '
', 19 | '
'].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 | '
', 30 | '

', 31 | // eslint-disable-next-line no-plusplus 32 | '
${features[c]}
${p}'; 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 += '
', 33 | '
', 34 | '
', 35 | '', 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 | '
', 8 | '

', 9 | '
', 10 | '
', 11 | '
', 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 += ''; 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 += ``; 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 += ``; 51 | Object.keys(sortedNames).forEach((j) => { 52 | html += ``; 53 | }); 54 | html += ''; 55 | }); 56 | html += '
Result Time${name} (${uom})
${ld.display(new Date(parseInt(time, 10)))}${values[sortedNames[j]]}
'; 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 |
47 |

Sirena

48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
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 | '
', 24 | '

', 25 | '
', 26 | '
', 27 | '
', 28 | '
', 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 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 31 | 34 | 37 | 40 | 43 | 44 | 47 | 51 | 55 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/assets/img/s_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 31 | 34 | 37 | 40 | 43 | 44 | 47 | 51 | 55 | 59 | 60 | 61 | 62 | 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 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |

02-SIRENA: Balisa moll nou Contradic

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |

03-ADOSSAT: BOCANA NORD

79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/assets/img/gridtile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 66 | 69 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /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 |
35 | 37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |

Widget Configuration Form

47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |

Widget View

59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |

Take Away

67 |
68 |
69 | 74 |
75 |
76 |

77 |                                 
78 |
79 |

80 |                                 
81 | 84 |
85 |
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('