From 856df4053fe6b2e8b9b0baf4914515d2be96af19 Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 15 Mar 2021 14:04:56 +0100 Subject: [PATCH] [recline]: Delete old files and code --- .gitignore | 6 - .travis.yml | 4 - CONTRIBUTING.md | 43 - LICENSE.txt | 20 - README.md | 148 - _config.yml | 6 - _includes/backend-list.html | 9 - _includes/data.js | 9 - _includes/example-backends-csv-disk.js | 22 - _includes/example-backends-dataproxy.js | 17 - _includes/example-backends-gdocs.js | 21 - _includes/example-backends-online-csv.js | 21 - _includes/recline-deps.html | 88 - _includes/tutorial-basics-ex-1.js | 29 - _includes/tutorial-basics-ex-2.js | 19 - _includes/tutorial-basics-ex-events.js | 13 - _includes/tutorial-basics-ex-fields-2.js | 10 - _includes/tutorial-basics-ex-fields.js | 20 - _includes/tutorial-maps-customize.js | 17 - _includes/tutorial-maps-infobox.js | 14 - _includes/tutorial-views-timeline.js | 15 - _includes/views-list.html | 7 - _layouts/container.html | 8 - _layouts/default.html | 128 - css-site/images/header-screen.png | Bin 116262 -> 0 bytes css-site/images/icons/white.png | Bin 1432 -> 0 bytes css-site/images/zigzags.png | Bin 1944 -> 0 bytes css-site/pygments.css | 61 - css-site/style.css | 251 - css/flot.css | 26 - css/grid.css | 221 - css/map.css | 28 - css/multiview.css | 330 - css/slickgrid.css | 188 - css/timeline.css | 3 - demo/index.html | 8 - demos/data/sample.csv | 4 - demos/images/calendar.gif | Bin 1035 -> 0 bytes demos/index.html | 94 - demos/multiview/app.js | 117 - demos/multiview/index.html | 33 - demos/search/demo.search.app.js | 270 - demos/search/index.html | 84 - dist/recline.css | 796 -- dist/recline.dataset.js | 896 -- dist/recline.dataset.min.js | 1 - dist/recline.js | 4455 ------- dist/recline.min.js | 3 - docs/backends.markdown | 148 - docs/extensions.md | 64 - docs/index.html | 69 - docs/models.markdown | 325 - docs/src/backend.csv.html | 240 - docs/src/backend.dataproxy.html | 271 - docs/src/backend.elasticsearch.html | 242 - docs/src/backend.gdocs.html | 137 - docs/src/backend.memory.html | 631 - docs/src/demo.search.app.html | 236 - docs/src/docco.css | 518 - docs/src/ecma-fixes.html | 215 - docs/src/model.html | 1375 --- docs/src/public/fonts/aller-bold.eot | Bin 29804 -> 0 bytes docs/src/public/fonts/aller-bold.ttf | Bin 66836 -> 0 bytes docs/src/public/fonts/aller-bold.woff | Bin 33244 -> 0 bytes docs/src/public/fonts/aller-light.eot | Bin 29509 -> 0 bytes docs/src/public/fonts/aller-light.ttf | Bin 68620 -> 0 bytes docs/src/public/fonts/aller-light.woff | Bin 33124 -> 0 bytes docs/src/public/fonts/roboto-black.eot | Bin 20702 -> 0 bytes docs/src/public/fonts/roboto-black.ttf | Bin 44828 -> 0 bytes docs/src/public/fonts/roboto-black.woff | Bin 24536 -> 0 bytes docs/src/public/stylesheets/normalize.css | 375 - docs/src/view.flot.html | 865 -- docs/src/view.graph.html | 141 - docs/src/view.grid.html | 606 - docs/src/view.map.html | 1331 -- docs/src/view.multiview.html | 1048 -- docs/src/view.slickgrid.html | 1114 -- docs/src/view.timeline.html | 446 - docs/src/widget.facetviewer.html | 259 - docs/src/widget.fields.html | 299 - docs/src/widget.filtereditor.html | 330 - docs/src/widget.pager.html | 223 - docs/src/widget.queryeditor.html | 184 - docs/src/widget.valuefilter.html | 264 - docs/tutorial-backends.markdown | 205 - docs/tutorial-basics-events.markdown | 55 - docs/tutorial-basics-query.markdown | 70 - docs/tutorial-basics.markdown | 152 - docs/tutorial-grids.markdown | 52 - docs/tutorial-maps.markdown | 131 - docs/tutorial-multiview.markdown | 233 - docs/tutorial-timelines.markdown | 50 - docs/tutorial-views.markdown | 316 - docs/tutorials.html | 67 - docs/views.markdown | 198 - download.markdown | 112 - favicon.ico | Bin 15086 -> 0 bytes images/drag-handle.png | Bin 1130 -> 0 bytes images/logo.png | Bin 45836 -> 0 bytes images/screenshot-1.jpg | Bin 84532 -> 0 bytes index.html | 54 - make | 55 - package.json | 46 - src/backend.dataproxy.js | 76 - src/backend.memory.js | 245 - src/ecma-fixes.js | 67 - src/i18n/en.js | 14 - src/i18n/pl.js | 69 - src/model.js | 651 - src/view.flot.js | 520 - src/view.graph.js | 4 - src/view.grid.js | 273 - src/view.map.js | 687 -- src/view.multiview.js | 568 - src/view.slickgrid.js | 602 - src/view.timeline.js | 186 - src/widget.facetviewer.js | 88 - src/widget.fields.js | 96 - src/widget.filtereditor.js | 183 - src/widget.pager.js | 74 - src/widget.queryeditor.js | 48 - src/widget.valuefilter.js | 115 - test/backend.dataproxy.test.js | 110 - test/backend.memory.test.js | 387 - test/base.js | 45 - test/built.html | 54 - test/index.html | 102 - test/model.test.js | 480 - test/qunit/qunit-assert-html.js | 377 - test/qunit/qunit.css | 244 - test/qunit/qunit.js | 2152 ---- test/qunit/runner.js | 139 - test/sinon-qunit/1.0.0/sinon-qunit.js | 62 - test/sinon/1.7.1/sinon.js | 4299 ------- test/util.test.js | 22 - test/view.flot.test.js | 94 - test/view.grid.test.js | 41 - test/view.i18n.test.js | 134 - test/view.map.test.js | 283 - test/view.multiview.test.js | 107 - test/view.slickgrid.test.js | 280 - test/view.timeline.test.js | 85 - test/widget.filtereditor.test.js | 115 - test/widget.pager.test.js | 129 - test/widget.valuefilter.test.js | 68 - vendor/backbone/1.0.0/backbone.js | 1571 --- vendor/bootstrap/2.3.2/bootstrap.js | 2280 ---- .../2.3.2/css/bootstrap-responsive.css | 1109 -- vendor/bootstrap/2.3.2/css/bootstrap.css | 6167 ---------- .../2.3.2/img/glyphicons-halflings-white.png | Bin 8777 -> 0 bytes .../2.3.2/img/glyphicons-halflings.png | Bin 12799 -> 0 bytes .../bootstrap/3.2.0/css/bootstrap-theme.css | 442 - vendor/bootstrap/3.2.0/css/bootstrap.css | 6203 ---------- .../fonts/glyphicons-halflings-regular.eot | Bin 20335 -> 0 bytes .../fonts/glyphicons-halflings-regular.svg | 229 - .../fonts/glyphicons-halflings-regular.ttf | Bin 41280 -> 0 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 23320 -> 0 bytes vendor/bootstrap/3.2.0/js/bootstrap.js | 2114 ---- vendor/common-intl-wmustache.js | 126 - vendor/flot/excanvas.js | 1428 --- vendor/flot/excanvas.min.js | 1 - vendor/flot/jquery.flot.js | 3061 ----- vendor/flot/jquery.flot.time.js | 431 - vendor/jquery/1.7.1/jquery.js | 9266 -------------- vendor/json/json2.js | 486 - .../MarkerCluster.Default.css | 60 - .../leaflet.markercluster/MarkerCluster.css | 6 - .../leaflet.markercluster-src.js | 2163 ---- .../leaflet.markercluster.js | 6 - vendor/leaflet/0.7.3/images/layers-2x.png | Bin 2898 -> 0 bytes vendor/leaflet/0.7.3/images/layers.png | Bin 1502 -> 0 bytes .../leaflet/0.7.3/images/marker-icon-2x.png | Bin 4033 -> 0 bytes vendor/leaflet/0.7.3/images/marker-icon.png | Bin 1747 -> 0 bytes vendor/leaflet/0.7.3/images/marker-shadow.png | Bin 797 -> 0 bytes vendor/leaflet/0.7.3/leaflet-src.js | 9180 -------------- vendor/leaflet/0.7.3/leaflet.css | 478 - vendor/leaflet/0.7.3/leaflet.js | 9 - vendor/moment/2.0.0/moment.js | 1400 --- vendor/mustache/0.5.0-dev/mustache.js | 536 - vendor/showdown/20120615/showdown.js | 1341 --- vendor/slickgrid/2.2/MIT-LICENSE.txt | 20 - vendor/slickgrid/2.2/README.md | 25 - .../2.2/controls/slick.columnpicker.css | 31 - .../2.2/controls/slick.columnpicker.js | 152 - vendor/slickgrid/2.2/controls/slick.pager.css | 41 - vendor/slickgrid/2.2/controls/slick.pager.js | 154 - .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 180 -> 0 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 178 -> 0 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 120 -> 0 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 105 -> 0 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 111 -> 0 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 110 -> 0 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 119 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 101 -> 0 bytes .../images/ui-icons_222222_256x240.png | Bin 4369 -> 0 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 4369 -> 0 bytes .../images/ui-icons_454545_256x240.png | Bin 4369 -> 0 bytes .../images/ui-icons_888888_256x240.png | Bin 4369 -> 0 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 4369 -> 0 bytes .../smoothness/jquery-ui-1.8.16.custom.css | 409 - vendor/slickgrid/2.2/images/actions.gif | Bin 170 -> 0 bytes .../2.2/images/ajax-loader-small.gif | Bin 1849 -> 0 bytes vendor/slickgrid/2.2/images/arrow_redo.png | Bin 572 -> 0 bytes .../2.2/images/arrow_right_peppermint.png | Bin 128 -> 0 bytes .../2.2/images/arrow_right_spearmint.png | Bin 128 -> 0 bytes vendor/slickgrid/2.2/images/arrow_undo.png | Bin 578 -> 0 bytes vendor/slickgrid/2.2/images/bullet_blue.png | Bin 241 -> 0 bytes vendor/slickgrid/2.2/images/bullet_star.png | Bin 279 -> 0 bytes .../2.2/images/bullet_toggle_minus.png | Bin 154 -> 0 bytes .../2.2/images/bullet_toggle_plus.png | Bin 156 -> 0 bytes vendor/slickgrid/2.2/images/calendar.gif | Bin 1035 -> 0 bytes vendor/slickgrid/2.2/images/collapse.gif | Bin 846 -> 0 bytes .../slickgrid/2.2/images/comment_yellow.gif | Bin 257 -> 0 bytes vendor/slickgrid/2.2/images/down.gif | Bin 59 -> 0 bytes vendor/slickgrid/2.2/images/drag-handle.png | Bin 1130 -> 0 bytes .../slickgrid/2.2/images/editor-helper-bg.gif | Bin 1164 -> 0 bytes vendor/slickgrid/2.2/images/expand.gif | Bin 851 -> 0 bytes vendor/slickgrid/2.2/images/header-bg.gif | Bin 872 -> 0 bytes .../2.2/images/header-columns-bg.gif | Bin 836 -> 0 bytes .../2.2/images/header-columns-over-bg.gif | Bin 823 -> 0 bytes vendor/slickgrid/2.2/images/help.png | Bin 345 -> 0 bytes vendor/slickgrid/2.2/images/info.gif | Bin 80 -> 0 bytes vendor/slickgrid/2.2/images/listview.gif | Bin 2380 -> 0 bytes vendor/slickgrid/2.2/images/pencil.gif | Bin 914 -> 0 bytes vendor/slickgrid/2.2/images/row-over-bg.gif | Bin 823 -> 0 bytes vendor/slickgrid/2.2/images/sort-asc.gif | Bin 830 -> 0 bytes vendor/slickgrid/2.2/images/sort-asc.png | Bin 105 -> 0 bytes vendor/slickgrid/2.2/images/sort-desc.gif | Bin 833 -> 0 bytes vendor/slickgrid/2.2/images/sort-desc.png | Bin 107 -> 0 bytes vendor/slickgrid/2.2/images/stripes.png | Bin 1125 -> 0 bytes vendor/slickgrid/2.2/images/tag_red.png | Bin 537 -> 0 bytes vendor/slickgrid/2.2/images/tick.png | Bin 484 -> 0 bytes vendor/slickgrid/2.2/images/user_identity.gif | Bin 905 -> 0 bytes .../2.2/images/user_identity_plus.gif | Bin 546 -> 0 bytes vendor/slickgrid/2.2/jquery-1.7.min.js | 4 - .../2.2/jquery-ui-1.8.16.custom.min.js | 611 - vendor/slickgrid/2.2/jquery.event.drag-2.2.js | 402 - vendor/slickgrid/2.2/jquery.event.drop-2.2.js | 302 - .../2.2/plugins/slick.autotooltips.js | 83 - .../2.2/plugins/slick.cellcopymanager.js | 86 - .../2.2/plugins/slick.cellrangedecorator.js | 66 - .../2.2/plugins/slick.cellrangeselector.js | 113 - .../2.2/plugins/slick.cellselectionmodel.js | 154 - .../2.2/plugins/slick.checkboxselectcolumn.js | 153 - .../2.2/plugins/slick.headerbuttons.css | 39 - .../2.2/plugins/slick.headerbuttons.js | 177 - .../2.2/plugins/slick.headermenu.css | 59 - .../slickgrid/2.2/plugins/slick.headermenu.js | 275 - .../2.2/plugins/slick.rowmovemanager.js | 138 - .../2.2/plugins/slick.rowselectionmodel.js | 187 - vendor/slickgrid/2.2/slick-default-theme.css | 118 - vendor/slickgrid/2.2/slick.core.js | 467 - vendor/slickgrid/2.2/slick.dataview.js | 1126 -- vendor/slickgrid/2.2/slick.editors.js | 512 - vendor/slickgrid/2.2/slick.formatters.js | 59 - vendor/slickgrid/2.2/slick.grid.css | 157 - vendor/slickgrid/2.2/slick.grid.js | 3422 ------ .../2.2/slick.groupitemmetadataprovider.js | 158 - vendor/slickgrid/2.2/slick.remotemodel.js | 173 - vendor/timeline/LICENSE | 365 - vendor/timeline/README | 1 - vendor/timeline/css/loading.gif | Bin 6909 -> 0 bytes vendor/timeline/css/timeline.css | 284 - vendor/timeline/css/timeline.png | Bin 19872 -> 0 bytes vendor/timeline/css/timeline@2x.png | Bin 49364 -> 0 bytes vendor/timeline/js/timeline.js | 10015 ---------------- .../0.4.0/underscore.deferred.js | 445 - vendor/underscore/1.4.4/underscore.js | 1227 -- 268 files changed, 108993 deletions(-) delete mode 100755 .gitignore delete mode 100644 .travis.yml delete mode 100644 CONTRIBUTING.md delete mode 100755 LICENSE.txt delete mode 100755 README.md delete mode 100644 _config.yml delete mode 100644 _includes/backend-list.html delete mode 100644 _includes/data.js delete mode 100644 _includes/example-backends-csv-disk.js delete mode 100644 _includes/example-backends-dataproxy.js delete mode 100644 _includes/example-backends-gdocs.js delete mode 100644 _includes/example-backends-online-csv.js delete mode 100644 _includes/recline-deps.html delete mode 100644 _includes/tutorial-basics-ex-1.js delete mode 100644 _includes/tutorial-basics-ex-2.js delete mode 100644 _includes/tutorial-basics-ex-events.js delete mode 100644 _includes/tutorial-basics-ex-fields-2.js delete mode 100644 _includes/tutorial-basics-ex-fields.js delete mode 100644 _includes/tutorial-maps-customize.js delete mode 100644 _includes/tutorial-maps-infobox.js delete mode 100644 _includes/tutorial-views-timeline.js delete mode 100644 _includes/views-list.html delete mode 100644 _layouts/container.html delete mode 100644 _layouts/default.html delete mode 100644 css-site/images/header-screen.png delete mode 100644 css-site/images/icons/white.png delete mode 100644 css-site/images/zigzags.png delete mode 100644 css-site/pygments.css delete mode 100644 css-site/style.css delete mode 100644 css/flot.css delete mode 100644 css/grid.css delete mode 100644 css/map.css delete mode 100644 css/multiview.css delete mode 100644 css/slickgrid.css delete mode 100644 css/timeline.css delete mode 100644 demo/index.html delete mode 100644 demos/data/sample.csv delete mode 100755 demos/images/calendar.gif delete mode 100644 demos/index.html delete mode 100755 demos/multiview/app.js delete mode 100644 demos/multiview/index.html delete mode 100644 demos/search/demo.search.app.js delete mode 100644 demos/search/index.html delete mode 100644 dist/recline.css delete mode 100644 dist/recline.dataset.js delete mode 100644 dist/recline.dataset.min.js delete mode 100644 dist/recline.js delete mode 100644 dist/recline.min.js delete mode 100644 docs/backends.markdown delete mode 100644 docs/extensions.md delete mode 100644 docs/index.html delete mode 100644 docs/models.markdown delete mode 100644 docs/src/backend.csv.html delete mode 100644 docs/src/backend.dataproxy.html delete mode 100644 docs/src/backend.elasticsearch.html delete mode 100644 docs/src/backend.gdocs.html delete mode 100644 docs/src/backend.memory.html delete mode 100644 docs/src/demo.search.app.html delete mode 100644 docs/src/docco.css delete mode 100644 docs/src/ecma-fixes.html delete mode 100644 docs/src/model.html delete mode 100644 docs/src/public/fonts/aller-bold.eot delete mode 100644 docs/src/public/fonts/aller-bold.ttf delete mode 100644 docs/src/public/fonts/aller-bold.woff delete mode 100644 docs/src/public/fonts/aller-light.eot delete mode 100644 docs/src/public/fonts/aller-light.ttf delete mode 100644 docs/src/public/fonts/aller-light.woff delete mode 100755 docs/src/public/fonts/roboto-black.eot delete mode 100755 docs/src/public/fonts/roboto-black.ttf delete mode 100755 docs/src/public/fonts/roboto-black.woff delete mode 100644 docs/src/public/stylesheets/normalize.css delete mode 100644 docs/src/view.flot.html delete mode 100644 docs/src/view.graph.html delete mode 100644 docs/src/view.grid.html delete mode 100644 docs/src/view.map.html delete mode 100644 docs/src/view.multiview.html delete mode 100644 docs/src/view.slickgrid.html delete mode 100644 docs/src/view.timeline.html delete mode 100644 docs/src/widget.facetviewer.html delete mode 100644 docs/src/widget.fields.html delete mode 100644 docs/src/widget.filtereditor.html delete mode 100644 docs/src/widget.pager.html delete mode 100644 docs/src/widget.queryeditor.html delete mode 100644 docs/src/widget.valuefilter.html delete mode 100644 docs/tutorial-backends.markdown delete mode 100644 docs/tutorial-basics-events.markdown delete mode 100644 docs/tutorial-basics-query.markdown delete mode 100644 docs/tutorial-basics.markdown delete mode 100644 docs/tutorial-grids.markdown delete mode 100644 docs/tutorial-maps.markdown delete mode 100644 docs/tutorial-multiview.markdown delete mode 100644 docs/tutorial-timelines.markdown delete mode 100644 docs/tutorial-views.markdown delete mode 100644 docs/tutorials.html delete mode 100644 docs/views.markdown delete mode 100644 download.markdown delete mode 100644 favicon.ico delete mode 100644 images/drag-handle.png delete mode 100644 images/logo.png delete mode 100644 images/screenshot-1.jpg delete mode 100644 index.html delete mode 100755 make delete mode 100644 package.json delete mode 100644 src/backend.dataproxy.js delete mode 100644 src/backend.memory.js delete mode 100644 src/ecma-fixes.js delete mode 100644 src/i18n/en.js delete mode 100644 src/i18n/pl.js delete mode 100644 src/model.js delete mode 100644 src/view.flot.js delete mode 100644 src/view.graph.js delete mode 100644 src/view.grid.js delete mode 100644 src/view.map.js delete mode 100644 src/view.multiview.js delete mode 100644 src/view.slickgrid.js delete mode 100644 src/view.timeline.js delete mode 100644 src/widget.facetviewer.js delete mode 100644 src/widget.fields.js delete mode 100644 src/widget.filtereditor.js delete mode 100644 src/widget.pager.js delete mode 100644 src/widget.queryeditor.js delete mode 100644 src/widget.valuefilter.js delete mode 100644 test/backend.dataproxy.test.js delete mode 100644 test/backend.memory.test.js delete mode 100644 test/base.js delete mode 100644 test/built.html delete mode 100644 test/index.html delete mode 100644 test/model.test.js delete mode 100644 test/qunit/qunit-assert-html.js delete mode 100644 test/qunit/qunit.css delete mode 100644 test/qunit/qunit.js delete mode 100644 test/qunit/runner.js delete mode 100644 test/sinon-qunit/1.0.0/sinon-qunit.js delete mode 100644 test/sinon/1.7.1/sinon.js delete mode 100644 test/util.test.js delete mode 100644 test/view.flot.test.js delete mode 100644 test/view.grid.test.js delete mode 100644 test/view.i18n.test.js delete mode 100644 test/view.map.test.js delete mode 100644 test/view.multiview.test.js delete mode 100644 test/view.slickgrid.test.js delete mode 100644 test/view.timeline.test.js delete mode 100644 test/widget.filtereditor.test.js delete mode 100644 test/widget.pager.test.js delete mode 100644 test/widget.valuefilter.test.js delete mode 100644 vendor/backbone/1.0.0/backbone.js delete mode 100644 vendor/bootstrap/2.3.2/bootstrap.js delete mode 100644 vendor/bootstrap/2.3.2/css/bootstrap-responsive.css delete mode 100644 vendor/bootstrap/2.3.2/css/bootstrap.css delete mode 100644 vendor/bootstrap/2.3.2/img/glyphicons-halflings-white.png delete mode 100644 vendor/bootstrap/2.3.2/img/glyphicons-halflings.png delete mode 100644 vendor/bootstrap/3.2.0/css/bootstrap-theme.css delete mode 100644 vendor/bootstrap/3.2.0/css/bootstrap.css delete mode 100644 vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.eot delete mode 100644 vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.svg delete mode 100644 vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.ttf delete mode 100644 vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.woff delete mode 100644 vendor/bootstrap/3.2.0/js/bootstrap.js delete mode 100644 vendor/common-intl-wmustache.js delete mode 100644 vendor/flot/excanvas.js delete mode 100644 vendor/flot/excanvas.min.js delete mode 100644 vendor/flot/jquery.flot.js delete mode 100644 vendor/flot/jquery.flot.time.js delete mode 100644 vendor/jquery/1.7.1/jquery.js delete mode 100644 vendor/json/json2.js delete mode 100644 vendor/leaflet.markercluster/MarkerCluster.Default.css delete mode 100644 vendor/leaflet.markercluster/MarkerCluster.css delete mode 100644 vendor/leaflet.markercluster/leaflet.markercluster-src.js delete mode 100644 vendor/leaflet.markercluster/leaflet.markercluster.js delete mode 100644 vendor/leaflet/0.7.3/images/layers-2x.png delete mode 100644 vendor/leaflet/0.7.3/images/layers.png delete mode 100644 vendor/leaflet/0.7.3/images/marker-icon-2x.png delete mode 100644 vendor/leaflet/0.7.3/images/marker-icon.png delete mode 100644 vendor/leaflet/0.7.3/images/marker-shadow.png delete mode 100644 vendor/leaflet/0.7.3/leaflet-src.js delete mode 100644 vendor/leaflet/0.7.3/leaflet.css delete mode 100644 vendor/leaflet/0.7.3/leaflet.js delete mode 100644 vendor/moment/2.0.0/moment.js delete mode 100644 vendor/mustache/0.5.0-dev/mustache.js delete mode 100644 vendor/showdown/20120615/showdown.js delete mode 100644 vendor/slickgrid/2.2/MIT-LICENSE.txt delete mode 100644 vendor/slickgrid/2.2/README.md delete mode 100644 vendor/slickgrid/2.2/controls/slick.columnpicker.css delete mode 100644 vendor/slickgrid/2.2/controls/slick.columnpicker.js delete mode 100644 vendor/slickgrid/2.2/controls/slick.pager.css delete mode 100644 vendor/slickgrid/2.2/controls/slick.pager.js delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-icons_222222_256x240.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-icons_2e83ff_256x240.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-icons_454545_256x240.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-icons_888888_256x240.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/images/ui-icons_cd0a0a_256x240.png delete mode 100644 vendor/slickgrid/2.2/css/smoothness/jquery-ui-1.8.16.custom.css delete mode 100644 vendor/slickgrid/2.2/images/actions.gif delete mode 100644 vendor/slickgrid/2.2/images/ajax-loader-small.gif delete mode 100644 vendor/slickgrid/2.2/images/arrow_redo.png delete mode 100644 vendor/slickgrid/2.2/images/arrow_right_peppermint.png delete mode 100644 vendor/slickgrid/2.2/images/arrow_right_spearmint.png delete mode 100644 vendor/slickgrid/2.2/images/arrow_undo.png delete mode 100644 vendor/slickgrid/2.2/images/bullet_blue.png delete mode 100644 vendor/slickgrid/2.2/images/bullet_star.png delete mode 100644 vendor/slickgrid/2.2/images/bullet_toggle_minus.png delete mode 100644 vendor/slickgrid/2.2/images/bullet_toggle_plus.png delete mode 100644 vendor/slickgrid/2.2/images/calendar.gif delete mode 100644 vendor/slickgrid/2.2/images/collapse.gif delete mode 100644 vendor/slickgrid/2.2/images/comment_yellow.gif delete mode 100644 vendor/slickgrid/2.2/images/down.gif delete mode 100644 vendor/slickgrid/2.2/images/drag-handle.png delete mode 100644 vendor/slickgrid/2.2/images/editor-helper-bg.gif delete mode 100644 vendor/slickgrid/2.2/images/expand.gif delete mode 100644 vendor/slickgrid/2.2/images/header-bg.gif delete mode 100644 vendor/slickgrid/2.2/images/header-columns-bg.gif delete mode 100644 vendor/slickgrid/2.2/images/header-columns-over-bg.gif delete mode 100644 vendor/slickgrid/2.2/images/help.png delete mode 100644 vendor/slickgrid/2.2/images/info.gif delete mode 100644 vendor/slickgrid/2.2/images/listview.gif delete mode 100644 vendor/slickgrid/2.2/images/pencil.gif delete mode 100644 vendor/slickgrid/2.2/images/row-over-bg.gif delete mode 100644 vendor/slickgrid/2.2/images/sort-asc.gif delete mode 100644 vendor/slickgrid/2.2/images/sort-asc.png delete mode 100644 vendor/slickgrid/2.2/images/sort-desc.gif delete mode 100644 vendor/slickgrid/2.2/images/sort-desc.png delete mode 100644 vendor/slickgrid/2.2/images/stripes.png delete mode 100644 vendor/slickgrid/2.2/images/tag_red.png delete mode 100644 vendor/slickgrid/2.2/images/tick.png delete mode 100644 vendor/slickgrid/2.2/images/user_identity.gif delete mode 100644 vendor/slickgrid/2.2/images/user_identity_plus.gif delete mode 100644 vendor/slickgrid/2.2/jquery-1.7.min.js delete mode 100644 vendor/slickgrid/2.2/jquery-ui-1.8.16.custom.min.js delete mode 100644 vendor/slickgrid/2.2/jquery.event.drag-2.2.js delete mode 100644 vendor/slickgrid/2.2/jquery.event.drop-2.2.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.autotooltips.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.cellcopymanager.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.cellrangedecorator.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.cellrangeselector.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.cellselectionmodel.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.checkboxselectcolumn.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.headerbuttons.css delete mode 100644 vendor/slickgrid/2.2/plugins/slick.headerbuttons.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.headermenu.css delete mode 100644 vendor/slickgrid/2.2/plugins/slick.headermenu.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.rowmovemanager.js delete mode 100644 vendor/slickgrid/2.2/plugins/slick.rowselectionmodel.js delete mode 100644 vendor/slickgrid/2.2/slick-default-theme.css delete mode 100644 vendor/slickgrid/2.2/slick.core.js delete mode 100644 vendor/slickgrid/2.2/slick.dataview.js delete mode 100644 vendor/slickgrid/2.2/slick.editors.js delete mode 100644 vendor/slickgrid/2.2/slick.formatters.js delete mode 100644 vendor/slickgrid/2.2/slick.grid.css delete mode 100644 vendor/slickgrid/2.2/slick.grid.js delete mode 100644 vendor/slickgrid/2.2/slick.groupitemmetadataprovider.js delete mode 100644 vendor/slickgrid/2.2/slick.remotemodel.js delete mode 100644 vendor/timeline/LICENSE delete mode 100644 vendor/timeline/README delete mode 100644 vendor/timeline/css/loading.gif delete mode 100644 vendor/timeline/css/timeline.css delete mode 100644 vendor/timeline/css/timeline.png delete mode 100644 vendor/timeline/css/timeline@2x.png delete mode 100644 vendor/timeline/js/timeline.js delete mode 100644 vendor/underscore.deferred/0.4.0/underscore.deferred.js delete mode 100644 vendor/underscore/1.4.4/underscore.js diff --git a/.gitignore b/.gitignore deleted file mode 100755 index fb371404..00000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.DS_Store -sandbox/* -.*.swp -.*.swo -_site/* -node_modules/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 227a3756..00000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: node_js -node_js: - - "0.10" -script: phantomjs test/qunit/runner.js test/index.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 97df3fc4..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributing to Recline - -We welcome patches and pull requests. There are a few guidelines. - -## General - -* Please run the tests :-) (see below) -* Please do **not** build the dist files (e.g. dist/recline.js) when submitting - patches. dist files will get built automatically and if they are part of a - patch or pull request it makes them harder to review and more likely to - conflict. -* If possible have an issue to which the commits can relate. You can reference - an issue in the commits by just including #{issue-number} somewhere in the - commit message). Note if no issue exists suggest creating one. - -## For larger changes - -* Cleanup your code and affected code parts -* Run the tests from `/test/index.html` in different browsers (at least Chrome and FF) -* Update the documentation and tutorials where necessary -* Update `/_includes/recline-deps.html` if you change required files (e.g. leaflet libraries) -* Try to build the demos in `/demos/` with jekyll and then check out the `/demos/multiview/` which utilizes most aspects of Recline - -You will also probably want to take a quick look at outline of the architecture which can be found in the [documentation online](http://okfnlabs.org/recline). - -## Running tests - -Run the tests by opening `test/index.html` in your browser. - -## Demos and Documentation - -Note that the demos and documentation utilize the [jekyll templating -system][jekyll] and to use them *locally* you will need to build them using -jekyll. Once installed, all you need to do from the command line is run jekyll: - - jekyll serve - -or if you're actively developing and want auto-reloading: - - jekyll serve --watch - -[jekyll]: https://github.com/mojombo/jekyll - diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100755 index a0993c64..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2011-2014 Max Ogden, Rufus Pollock and Contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100755 index aa062840..00000000 --- a/README.md +++ /dev/null @@ -1,148 +0,0 @@ -[![Build Status](https://travis-ci.org/datopian/recline.png)](https://travis-ci.org/datopian/recline) - -A simple but powerful library for building data applications in pure Javascript and HTML. - -

Recline Website - including Overview, Documentation, Demos etc

- -## Features - -* Open-source (and heavy reuser of existing open-source libraries) -* Pure javascript (no Flash) and designed for integration -- so it is easy to - embed in other sites and applications -* View and edit your data in clean grid interface -* Bulk update/clean your data using an easy scripting UI -* Visualize your data -* And more ... see - -## Contributing - -See CONTRIBUTING.md. - -### Contributors - -* [Rufus Pollock](http://rufuspollock.org/) -* [Max Ogden](http://maxogden.com/) -* [John Glover](https://github.com/johnglover) -* [James Casbon](http://casbon.me/) -* [AdriĆ  Mercader](http://amercader.net/) -* [Dominik Moritz](https://github.com/domoritz) -* [Friedrich Lindenberg](http://pudo.org/) -* [Alioune Dia](https://github.com/aliounedia) -* [kielni](https://github.com/kielni) -* And [many more](https://github.com/okfn/recline/graphs/contributors) - -## Changelog - - -### v0.7 - Summer 2014 (tbc) - -[v0.7 milestone](https://github.com/okfn/recline/issues?milestone=7) - -Possible breaking changes - -* Support for row/add/delete/Reorder for recline slickGrid check `_includes/recline-deps.html` for slcikGrid plugins required #396 -* Upgraded timelinejs lib - #316 -* Removed csv backend (as now in separate repo) #444 - -### v0.6 - Summer 2013 - -[v0.6 milestone](https://github.com/okfn/recline/issues?milestone=5) (more than 40 issues) - -Possible breaking changes - -* Many backends moved to their own repositories #314 -* Upgarde to Backbone v1.0 #351 -* Updated Leaflet to latest version 0.4.4 #220 -* Added marker clustering in map view to handle a large number of markers (and allowed it to disabled) -* Dataset.restore method removed (not used internally except from Multiview.restore) -* Views no longer call render in initialize but must be called client code -* Backend.Memory.Store attribute for holding 'records' renamed to `records` from `data` -* Option to use underscore.deferred vendor library and not use jQuery (jQuery no longer required if just using recline.dataset.js) -* View.el is now the raw DOM element. If you want a jQuery-wrapped version, use view.$el. #350 -* Pager widget now takes Dataset object rather than QueryState object #386 - -### v0.5 - July 5th 2012 (first public release) - -[40 closed issues](https://github.com/okfn/recline/issues?milestone=2&page=1&state=closed) - -Lots of breaking changes to the API from v0.4 (should be very few going forwards) including: - -* State only stores backend (name) and dataset url (in url field) rather than entire dataset object -* Backends heavily reorganized -* Rename Document -> Record -* Rename DataExplorer view to MultiView -* ... - -### v0.4 - April 26th 2012 - -[23 closed issues](https://github.com/okfn/recline/issues?milestone=2&page=1&state=closed) including: - -* Map view using Leaflet - #69, #64, #89, #97 -* Term filter support - #66 -* Faceting support- #62 -* Tidy up CSS and JS - #81 and #78 -* Manage and serialize view and dataset state (plus support for embed and permalinks) - #88, #67 -* Graph view improvements e.g. handle date types correctly - #75 -* Write support for ES backend - #61 -* Remove JQuery-UI dependency in favour of bootstrap modal - #46 -* Improved CSV import support - #92 - -### v0.3 - March 31st 2012 - -[16 closed issues](https://github.com/okfn/recline/issues?milestone=1&state=closed) including: - -* ElasticSearch (and hence DataHub/CKAN) backend - #54 -* Loading of local CSV files - #36 -* Fully worked out Data Query support - #34, #49, #53, #57 -* New Field model object for richer field information - #25 -* Upgrade to Bootstrap v2.0 - #55 -* Recline Data Explorer app improvements e.g. #39 (import menu) -* Graph improvements - #58 (more graph types, graph interaction) - -### v0.2 - Feb 24th 2012 - -[17 closed issues](https://github.com/okfn/recline/issues?milestone=3&state=closed) including: - -* Major refactor of backend and model relationship - #35 and #43 -* Support Google Docs Spreadsheets as a Backend - #15 -* Support for online CSV and Excel files via DataProxy backend - #31 -* Data Explorer is customizable re loaded views - #42 -* Start of documentation - #33 -* Views in separate files - #41 -* Better error reporting from backends on JSONP errors - #30 -* Sorting and show/hide of columns in data grid - #23, #29 -* Support for pagination - #27 -* Split backends into separate files to make them easier to maintain and reuse separately #50 - -### v0.1 - Jan 28th 2012 - -* Core models and structure including Dataset and Document -* Memory and webstore backends -* Grid, Graph and Data Explorer views -* Bootstrap-based theme - #22 - -## Copyright and License - -Copyright 2011 Max Ogden and Rufus Pollock. - -Licensed under the MIT license: - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - diff --git a/_config.yml b/_config.yml deleted file mode 100644 index d6aefa8a..00000000 --- a/_config.yml +++ /dev/null @@ -1,6 +0,0 @@ -highlighter: pygments -markdown: kramdown - -title: Recline Data Explorer and Library - -exclude: [] diff --git a/_includes/backend-list.html b/_includes/backend-list.html deleted file mode 100644 index 6fcbf737..00000000 --- a/_includes/backend-list.html +++ /dev/null @@ -1,9 +0,0 @@ -* gdocs: Google Docs (Spreadsheet) -* csv: CSV files -* xlsx: Excel files -* solr: SOLR (partial) -* elasticsearch: ElasticSearch -* dataproxy: DataProxy (CSV and XLS on the Web) -* ckan: CKAN – support for CKAN datastore -* couchdb: CouchDB -* memory: Memory (local data) diff --git a/_includes/data.js b/_includes/data.js deleted file mode 100644 index 57065512..00000000 --- a/_includes/data.js +++ /dev/null @@ -1,9 +0,0 @@ -var data = [ - {id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', geo: {lat:52.56, lon:13.40} }, - {id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', geo: {lat:54.97, lon:-1.60}}, - {id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', geo: {lat:40.00, lon:-75.5}}, - {id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', geo: {lat:57.27, lon:-6.20}}, - {id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', geo: {lat:51.58, lon:0}}, - {id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', geo: {lat:51.04, lon:7.9}} -]; - diff --git a/_includes/example-backends-csv-disk.js b/_includes/example-backends-csv-disk.js deleted file mode 100644 index 537340b3..00000000 --- a/_includes/example-backends-csv-disk.js +++ /dev/null @@ -1,22 +0,0 @@ -// the file input -var $file = $('.my-file-input')[0]; - -// listen for the file to be submitted -$($file).change(function(e) { - // create the dataset in the usual way but specifying file attribute - var dataset = new recline.Model.Dataset({ - file: $file.files[0], - backend: 'csv' - }); - - // now load - note that this is again async (HTML5 File API is async) - // dataset.fetch().done(function() { console.log('here'); }); - dataset.fetch(); - - // For demonstrations purposes display the data in a grid - var grid = new recline.View.Grid({ - model: dataset - }); - $('#my-csv-disk').append(grid.el); -}); - diff --git a/_includes/example-backends-dataproxy.js b/_includes/example-backends-dataproxy.js deleted file mode 100644 index 9d4aea3c..00000000 --- a/_includes/example-backends-dataproxy.js +++ /dev/null @@ -1,17 +0,0 @@ -var dataset = new recline.Model.Dataset({ - url: 'http://data.london.gov.uk/datafiles/transport/tfl_passengers.csv', - // optional rows parameter specifies how many rows to retrieve - default is a 1000 - // rows: 5000 - backend: 'dataproxy' -}); - -// async again as we fetch via AJAX behind the scenes -// once data is fetched it will be stored in a local MemoryStore so further querying will not involve the DataProxy -dataset.fetch(); - -// For demonstrations purposes display the data in a grid -var grid = new recline.View.Grid({ - model: dataset -}); -$('#my-dataproxy').append(grid.el); - diff --git a/_includes/example-backends-gdocs.js b/_includes/example-backends-gdocs.js deleted file mode 100644 index 5ef56069..00000000 --- a/_includes/example-backends-gdocs.js +++ /dev/null @@ -1,21 +0,0 @@ -// Create a dataset with a Google Docs backend and a url to the Google Doc -var dataset = new recline.Model.Dataset({ - url: 'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdGZPaUZsMjBxeGhfOWRlWm85MmV0UUE#gid=0', - backend: 'gdocs' -}); - -// Optional - display the results in a grid -// Note how we can set this up before any data has arrived -// Views will listen for query completion and update themselves automatically -var grid = new recline.View.Grid({ - model: dataset -}); -$('#my-gdocs').append(grid.el); - -// Now do the query to the backend to load data -dataset.fetch().done(function(dataset) { - if (console) { - console.log(dataset.records); - } -}); - diff --git a/_includes/example-backends-online-csv.js b/_includes/example-backends-online-csv.js deleted file mode 100644 index a03fc362..00000000 --- a/_includes/example-backends-online-csv.js +++ /dev/null @@ -1,21 +0,0 @@ -// Create the dataset in the usual way -// Note the additional options you can specify for parsing the CSV file -var dataset = new recline.Model.Dataset({ - url: '{{page.root}}demos/data/sample.csv', - backend: 'csv', - // delimiter: ',', - // quotechar: '"', - // encoding: 'utf8' -}); - -// remember this is async so if you want to do something you need to call it in done method e.g. -// dataset.fetch.done(function(dataset) { console.log(dataset.recordCount)}); -dataset.fetch(); - -// show the data for illustrations sake -var grid = new recline.View.SlickGrid({ - model: dataset, - el: $('#my-online-csv') -}); -grid.visible = true; - diff --git a/_includes/recline-deps.html b/_includes/recline-deps.html deleted file mode 100644 index fb77d16b..00000000 --- a/_includes/recline-deps.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/tutorial-basics-ex-1.js b/_includes/tutorial-basics-ex-1.js deleted file mode 100644 index 2ca52f8f..00000000 --- a/_includes/tutorial-basics-ex-1.js +++ /dev/null @@ -1,29 +0,0 @@ -// (for convenience) assume availability of jquery -// must have div with class="ex-1" -var $el = $('.ex-1'); - -// total number of records resulting from latest query -$el.append('Total found: ' + dataset.recordCount + '
'); -$el.append('Total returned: ' + dataset.records.length); - -$el.append('
'); - -// get 2nd record in list (note collection indexes off 0!) -// this is an instance of a Record object -var record = dataset.records.at(1); - -// if records have an id you can get by id too ... -// var record = dataset.records.get(record-id); - -// To get record attribute we use 'get' -var recdate = record.get('date'); - -$el.append('Date is: ' + recdate); -$el.append('
'); - -// We can also convert the Record back to simple JS object -var simple = record.toJSON(); - -$el.append('

Record as simple object

'); -$el.append('
' + JSON.stringify(simple, null, 2) + '
'); - diff --git a/_includes/tutorial-basics-ex-2.js b/_includes/tutorial-basics-ex-2.js deleted file mode 100644 index 7e22739d..00000000 --- a/_includes/tutorial-basics-ex-2.js +++ /dev/null @@ -1,19 +0,0 @@ -// must have a div with class="ex-1" -var $el = $('.ex-2'); - -// query with text 'UK' - this will attempt to match any field for UK -// Also limit the query to return a maximum of 2 results using the size attribute - -// query function has asynchronous behaviour and returns a promise object -// (even for the case of in memory data where querying in fact happens synchronously) -// On completion the display function will be called -dataset.query({q: 'UK', size: 2}).done(function() { - $('.ex-2').append('Total found: ' + dataset.recordCount); - $('.ex-2').append(' Total returned: ' + dataset.records.length); - $('.ex-2').append( - $('
').html(
-      JSON.stringify(dataset.records.toJSON(), null, 2)
-    )
-  );
-});
-
diff --git a/_includes/tutorial-basics-ex-events.js b/_includes/tutorial-basics-ex-events.js
deleted file mode 100644
index 0f50bf15..00000000
--- a/_includes/tutorial-basics-ex-events.js
+++ /dev/null
@@ -1,13 +0,0 @@
-function onChange() {
-  $('.ex-events').append('Queried: ' + dataset.queryState.get('q') + '. Records matching: ' + dataset.recordCount);
-  $('.ex-events').append('
'); -} - -dataset.records.bind('reset', onChange); - -dataset.query({q: 'DE'}); -dataset.query({q: 'UK'}); -dataset.query({q: 'US'}); - -dataset.unbind('reset', onChange); - diff --git a/_includes/tutorial-basics-ex-fields-2.js b/_includes/tutorial-basics-ex-fields-2.js deleted file mode 100644 index ed4127c5..00000000 --- a/_includes/tutorial-basics-ex-fields-2.js +++ /dev/null @@ -1,10 +0,0 @@ -var $el = $('.ex-fields-2'); - -dataset.fields.models[6] = new recline.Model.Field({ - id: 'geo', - label: 'Location', - type: 'geo_point' -}); -var rec = dataset.records.at(0); -$el.append(record.summary()); - diff --git a/_includes/tutorial-basics-ex-fields.js b/_includes/tutorial-basics-ex-fields.js deleted file mode 100644 index d6cc38eb..00000000 --- a/_includes/tutorial-basics-ex-fields.js +++ /dev/null @@ -1,20 +0,0 @@ -var $el = $('.ex-fields'); - -// Now list the Fields of this Dataset (these will have be automatically extracted from the data) -$el.append('Fields: '); -// Dataset.fields is a Backbone collection of Fields (i.e. record attributes) -dataset.fields.each(function(field) { - $el.append(field.id + ' || '); -}); - -$el.append('
'); - -// Show all field info -var json = dataset.fields.toJSON(); -$el.append( - $('
')
-    .append(
-      JSON.stringify(json, null, 2)
-    )
-);
-
diff --git a/_includes/tutorial-maps-customize.js b/_includes/tutorial-maps-customize.js
deleted file mode 100644
index 3433e3a7..00000000
--- a/_includes/tutorial-maps-customize.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var $el = $('#map-customize');
-var view = new recline.View.Map({
-  el: $el,
-  model: dataset
-});
-
-view.geoJsonLayerOptions.pointToLayer = function(feature, latlng) {
-  // Look up Record so we can use it to customize size of marker
-  // note that 'this' is specially bound for us to parent view + that feature
-  // stores record cid
-  var record = this.model.records.getByCid(feature.properties.cid);
-  var marker = new L.CircleMarker(latlng, { radius: record.get('x') * 3 });
-  marker.bindPopup(feature.properties.popupContent);
-  return marker;
-}
-view.render();
-
diff --git a/_includes/tutorial-maps-infobox.js b/_includes/tutorial-maps-infobox.js
deleted file mode 100644
index 1baf7729..00000000
--- a/_includes/tutorial-maps-infobox.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// this element will need to exist!
-var $el = $('#map-infobox');
-var view = new recline.View.Map({
-  el: $el,
-  model: dataset
-});
-// record is the recline.Model.Record object
-view.infobox = function(record) {
-  var html = '

' + record.get('country') + ' – ' + record.get('date') + '

'; - html += 'id: ' + record.get('id'); - return html; -} -view.render(); - diff --git a/_includes/tutorial-views-timeline.js b/_includes/tutorial-views-timeline.js deleted file mode 100644 index cfb91cc7..00000000 --- a/_includes/tutorial-views-timeline.js +++ /dev/null @@ -1,15 +0,0 @@ -var $el = $('#mytimeline'); -var timeline = new recline.View.Timeline({ - model: dataset -}); -$el.append(timeline.el); -// set the headline/title for each record with x column -timeline.convertRecord = function(record, fields) { - var out = this._convertRecord(record); - if (out) { - out.headline = record.get('x').toString(); - } - return out; -} -timeline.render(); - diff --git a/_includes/views-list.html b/_includes/views-list.html deleted file mode 100644 index 9cb68d3b..00000000 --- a/_includes/views-list.html +++ /dev/null @@ -1,7 +0,0 @@ -* Grid (simple) -* Grid (SlickGrid) -* Map -* Choropleth Map -* Graph -* Timeline -* Multiview (combines views) \ No newline at end of file diff --git a/_layouts/container.html b/_layouts/container.html deleted file mode 100644 index 4abf7ae8..00000000 --- a/_layouts/container.html +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: default ---- - -
- {{content}} -
- diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index 830a8cad..00000000 --- a/_layouts/default.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - {{ page.title }} - Recline Data Explorer and Library - - - - - - - {% if page.recline-deps %} - {% include recline-deps.html %} - {% endif %} - - - - - - - - - - {{content}} - - - - - - - diff --git a/css-site/images/header-screen.png b/css-site/images/header-screen.png deleted file mode 100644 index 0aeb8ca16fe91cbcadfc5c573cadd0551e4804b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116262 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
8#CM(G4>Y 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%0D-IQ;be1nfIpand!=PKTbSz2#HgAX3o7js;l0 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 X)>p{OI5xc>5Bw_?@+{;7sjl=-l)?Xo diff --git a/css-site/pygments.css b/css-site/pygments.css deleted file mode 100644 index 122b4294..00000000 --- a/css-site/pygments.css +++ /dev/null @@ -1,61 +0,0 @@ -.hll { background-color: #ffffcc } -.c { color: #408080; font-style: italic } /* Comment */ -.err { border: 1px solid #FF0000 } /* Error */ -.k { color: #008000; font-weight: bold } /* Keyword */ -.o { color: #666666 } /* Operator */ -.cm { color: #408080; font-style: italic } /* Comment.Multiline */ -.cp { color: #BC7A00 } /* Comment.Preproc */ -.c1 { color: #408080; font-style: italic } /* Comment.Single */ -.cs { color: #408080; font-style: italic } /* Comment.Special */ -.gd { color: #A00000 } /* Generic.Deleted */ -.ge { font-style: italic } /* Generic.Emph */ -.gr { color: #FF0000 } /* Generic.Error */ -.gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.gi { color: #00A000 } /* Generic.Inserted */ -.go { color: #808080 } /* Generic.Output */ -.gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.gs { font-weight: bold } /* Generic.Strong */ -.gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.gt { color: #0040D0 } /* Generic.Traceback */ -.kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.kp { color: #008000 } /* Keyword.Pseudo */ -.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.kt { color: #B00040 } /* Keyword.Type */ -.m { color: #666666 } /* Literal.Number */ -.s { color: #BA2121 } /* Literal.String */ -.na { color: #7D9029 } /* Name.Attribute */ -.nb { color: #008000 } /* Name.Builtin */ -.nc { color: #0000FF; font-weight: bold } /* Name.Class */ -.no { color: #880000 } /* Name.Constant */ -.nd { color: #AA22FF } /* Name.Decorator */ -.ni { color: #999999; font-weight: bold } /* Name.Entity */ -.ne { color: #D2413A; font-weight: bold } /* Name.Exception */ -.nf { color: #0000FF } /* Name.Function */ -.nl { color: #A0A000 } /* Name.Label */ -.nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -.nt { color: #008000; font-weight: bold } /* Name.Tag */ -.nv { color: #19177C } /* Name.Variable */ -.ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -.w { color: #bbbbbb } /* Text.Whitespace */ -.mf { color: #666666 } /* Literal.Number.Float */ -.mh { color: #666666 } /* Literal.Number.Hex */ -.mi { color: #666666 } /* Literal.Number.Integer */ -.mo { color: #666666 } /* Literal.Number.Oct */ -.sb { color: #BA2121 } /* Literal.String.Backtick */ -.sc { color: #BA2121 } /* Literal.String.Char */ -.sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.s2 { color: #BA2121 } /* Literal.String.Double */ -.se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ -.sh { color: #BA2121 } /* Literal.String.Heredoc */ -.si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ -.sx { color: #008000 } /* Literal.String.Other */ -.sr { color: #BB6688 } /* Literal.String.Regex */ -.s1 { color: #BA2121 } /* Literal.String.Single */ -.ss { color: #19177C } /* Literal.String.Symbol */ -.bp { color: #008000 } /* Name.Builtin.Pseudo */ -.vc { color: #19177C } /* Name.Variable.Class */ -.vg { color: #19177C } /* Name.Variable.Global */ -.vi { color: #19177C } /* Name.Variable.Instance */ -.il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/css-site/style.css b/css-site/style.css deleted file mode 100644 index f0a9e030..00000000 --- a/css-site/style.css +++ /dev/null @@ -1,251 +0,0 @@ -/* -Theme Name: Recline -Description: Layout and styling for reclinejs.com -Author: Sam Smith -Author URI: http://www.mintcanary.com/ -*/ - -/* -------------------------------------------------- - Table of Contents ------------------------------------------------------ -:: General Styles -:: Layout -:: -:: -:: -*/ - -/* --------------------------------------------------- - General Styles ---------------------------------------------------- */ - -@import url(http://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700); - -body, p { - font-family: 'PT Sans',Helvetica,Arial,sans-serif; - font-size: 15px; -} - -h1, h2, h3, h4, h5, h6 { - font-family:'PT Sans',Helvetica, Arial, sans-serif; - font-weight: bold; -} - -h2 { - font-size: 24px; -} - -h3 { - font-size: 20px; -} - -h4 { - font-size: 18px; -} - -a { - color: #c7231d; -} -a:hover { - color: #bc130e; -} - -/* --------------------------------------------------- - Layout ---------------------------------------------------- */ - -.navbar-header{ - -} - -.navbar-header .logo-icon { - height: 34px; -} - -.navbar-brand { - font-family:'PT Sans', Helvetica, Arial, sans-serif; - font-style:italic; - font-size:18px; - font-weight:400; - letter-spacing:-1px; - text-shadow: none !important; - color: #FFF !important; - margin-top: -6px; -} - -.navbar-nav > li > a { - padding: 15px 10px; - font-size: 13px; - text-shadow: none !important; - color: #999 !important; -} - -.navbar-nav > li > a:focus, -.navbar-nav > li > a:hover { - color: #FFF !important; -} - -.navbar .divider-vertical { - height: 50px; - margin: 0 9px; - border-right: 1px solid #ffffff; -} - -.navbar-inverse .divider-vertical { - border-right-color: #222222; -} - -@media (max-width: 767px) { - .navbar-collapse .nav > .divider-vertical { - display: none; - } -} - -.navbar { - height:50px; - background: #303030; /* Old browsers */ - background: -moz-linear-gradient(top, #303030 0%, #2d2d2d 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#303030), color-stop(100%,#2d2d2d)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #303030 0%,#2d2d2d 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #303030 0%,#2d2d2d 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(top, #303030 0%,#2d2d2d 100%); /* IE10+ */ - background: linear-gradient(top, #303030 0%,#2d2d2d 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#303030', endColorstr='#2d2d2d',GradientType=0 ); /* IE6-9 */ - -webkit-box-shadow:none; - -moz-box-shadow: none; - box-shadow: none; -} - -.icon-white{ - color: #FFF; - margin-right: 1px; -} - -a.btn, -button.btn { - white-space: normal !important; -} - -body { - padding-top: 60px; -} - -section { - padding-top:20px; -} - -.home-page.page-header { - margin-top:-10px; - background: #2d2d2d; /* Old browsers */ - background: -moz-linear-gradient(top, #2d2d2d 0%, #040404 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#2d2d2d), color-stop(100%,#040404)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #2d2d2d 0%,#040404 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #2d2d2d 0%,#040404 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(top, #2d2d2d 0%,#040404 100%); /* IE10+ */ - background: linear-gradient(top, #2d2d2d 0%,#040404 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2d2d2d', endColorstr='#040404',GradientType=0 ); /* IE6-9 */ - padding:0px; - margin-bottom:0px; - border:none; - padding: 60px; - padding-bottom: 200px; - /* hide crocodile top to footer on front page */ - margin-bottom: -30px; -} -.home-page.page-header p { - color:#FFF; -} -.home-page.page-header a { -} -.home-page.page-header .container { - background-image: url(images/header-screen.png); - background-repeat: no-repeat; - background-position: -3px 0px; -} -.home-page.page-header .inner { - padding:0px 0px 0px 40px; - font-size:16px; - line-height: 23px; - width: 400px; -} - -.home-page.page-header:after { - margin-top:-14px; -} - -.home-page.page-header .links { - margin-top: 30px; - margin-bottom: 0; -} - -.home-page.page-header .links a { - margin-left: 25px; -} - -.home-page.page-header .links a:first-child { - margin-left: 0px; -} - -/* --------------------------------------------------- - Footer ---------------------------------------------------- */ - -.recline-footer { - background-color:#040404; - color:#CCC; - margin-top: 30px; -} -.recline-footer:before { - content: " "; - height:14px; - display:block; - background-image: url(images/zigzags.png); - background-repeat: repeat-x; - background-position: center -100px; - margin-top:-34px; -} -.recline-footer:after { - display:none; -} -.recline-footer .row { - margin-top:15px; - margin-bottom:15px; -} -.recline-footer a { - color:#CCC; -} -.recline-footer a.btn { - color: #333333; -} - -.tutorials .well { - height: 60px; -} - -.tutorials:last-child { - margin-bottom: 200px; -} - -/** Code / Pre **/ - -.container pre { - padding: 10px 15px; - border: 1px solid #ccc; - background: white; - color: #444; - - box-shadow: 0 0 15px #ddd; - -moz-box-shadow: 0 0 15px #ddd; - -webkit-box-shadow: 0 0 15px #ddd; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.doc-ex-rendered { - margin-bottom: 20px; -} - -#my-online-csv { - height: 100px; -} diff --git a/css/flot.css b/css/flot.css deleted file mode 100644 index 57adc9f0..00000000 --- a/css/flot.css +++ /dev/null @@ -1,26 +0,0 @@ -.recline-flot .graph { - height: 500px; - overflow: hidden; -} - -.recline-flot .legend table { - width: auto; - margin-bottom: 0; -} - -.recline-flot .legend td { - padding: 5px; - line-height: 13px; -} - -.recline-flot .graph .alert { - width: 450px; -} - -#recline-flot-tooltip { - position: absolute; - background-color: #FEE; - color: #000000; - opacity: 0.8; - border: 1px solid #fdd; -} diff --git a/css/grid.css b/css/grid.css deleted file mode 100644 index 05d6f619..00000000 --- a/css/grid.css +++ /dev/null @@ -1,221 +0,0 @@ -/********************************************************** - * (Data) Grid - *********************************************************/ - -table.recline-grid { - table-layout: fixed; - width: 100%; -} - -.recline-grid .btn-group .dropdown-toggle { - padding: 1px 3px; - line-height: auto; -} - -.recline-grid td, .recline-grid th { - border-left: 1px solid #ccc; - padding: 3px 4px; - text-align: left; - word-wrap: break-word; - white-space: normal; -} - -.recline-grid tbody tr { - vertical-align: top; - border-bottom: solid 1px #ccc; -} - -.recline-grid tbody tr:last-child { - border-bottom: 1px solid #ccc; -} - -.recline-grid tbody td:last-child { - border-right: 1px solid #ccc; -} - -/* direct borrowing from twitter buttons */ -.recline-grid th { - background-color: #e6e6e6; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); - background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - color: #333; - border: 1px solid #ccc; - border-bottom-color: #bbb; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -webkit-transition: 0.1s linear all; - -moz-transition: 0.1s linear all; - -ms-transition: 0.1s linear all; - -o-transition: 0.1s linear all; - transition: 0.1s linear all; -} - -/********************************************************** - * Fixed Header - http://www.imaputz.com/cssStuff/bigFourVersion.html - *********************************************************/ - -div.table-container { - overflow: auto; -} - -/* Reset overflow value to hidden for all non-IE browsers. */ -html>body div.table-container { - overflow: hidden; -} - -thead.fixed-header tr { - overflow-x: hidden; -} - -/* set table header to a fixed position. WinIE 6.x only */ -/* In WinIE 6.x, any element with a position property set to relative and is a child of */ -/* an element that has an overflow property set, the relative value translates into fixed. */ -/* Ex: parent element DIV with a class of table-container has an overflow property set to auto */ -thead.fixed-header tr { - position: relative -} - -/* set THEAD element to have block level attributes. All other non-IE browsers */ -/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */ -html>body thead.fixed-header tr { - display: block -} - -/* define the table content to be scrollable */ -/* set TBODY element to have block level attributes. All other non-IE browsers */ -/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */ -/* induced side effect is that child TDs no longer accept width: auto */ -tbody.scroll-content { - display: block; - max-height: 500px; - overflow: auto; -} - -/********************************************************** - * Data Table Menus - *********************************************************/ - -.column-header-menu, a.root-header-menu { - float: right; -} - -div.data-table-cell-content { - line-height: 1.2; - color: #222; - position: relative; -} - -div.data-table-cell-content-numeric { - text-align: right; -} - -a.data-table-cell-edit { - position: absolute; - top: 0; - right: 0; - display: block; - width: 25px; - height: 16px; - text-decoration: none; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAQCAYAAABUWyyMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTNEODNFMjVEMDA5MTFERjk5NzhEQzZDRDUwRkEzMUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OTNEODNFMjZEMDA5MTFERjk5NzhEQzZDRDUwRkEzMUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5M0Q4M0UyM0QwMDkxMURGOTk3OERDNkNENTBGQTMxQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5M0Q4M0UyNEQwMDkxMURGOTk3OERDNkNENTBGQTMxQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpApVmcAAAKVSURBVHjaYvz//z/DcAAsIGLaxjf2QKoLiM2oaPYpIC7L8hc5COI4F9+hmR17e1UOskAFVgCxBJUDyQxqriQ97GCCCpBkATCUGQxVOTHY7KyMDFoKHMhKJXCwCQJgKDNEOAlisHk5mRh8LPgw7GChNEg2HfvI8OnbPzDbwYAH6BkmhmsPflA12EtnPmN4/vY3mF0YKgb2zJYTn5CV/EfxiKgAC4O7CS8DHzczw5PXvxl2nv7E8PP3fxTx87e/o1hipMrFcOfZT6BmVgYZUTawmKUWN8Pxa1+xOkpNhp2hLk6CQVKYleHcrW8MTYteMHz+/g9FfMW+9yh6Ih0FGA5e/MLAAIx5IzUusFiqtzDD7K1vYUpcmZA1+FnxM7z++AccyqBkAgphmMPY2ZiAHvsMFkcGMkAPgMQ+ff3L8PPXPzD++O0vztDtypBmuPXkJziUebiYwSEMcxiID/IYiEYJLKDjQWLP3v1h+AI0G4RhMQQF5UzoDvr5CxIDoJhQlmKHy527/Y3hLjDkD4BCBgsAxSAoiYEwrqQFchAoWcBiAOQge30euBwoJkAh37/6FVb9oBgEeQCE0ZKWC8488vj1LzCmJTgLTKZn0ZIquYAJOURBsQDKB68//GHgYEWkOlBMgPICKIYcoCGIC4BiFRSDuEIUFBtSQizg5AWKHRgAxQSoZALFECy54QKgZAbLKxgegZVAfFxM4LyiLMUGTPeQ0ujAhS9gz7mb8sI9iisWQclSG7UIRgFlM56CM3R3uhTY0bC0DkpOtx//AGd4mEdxxSIoWaIVwQyMoCYKsGanWTsFWM8wQmt2mtkBqmdgMfKCRna8wMGmth2MMI/EAfEdKltwB2ouAz3sYBwurV8mhmECAAIMAEe3EkMWh/DvAAAAAElFTkSuQmCC); - background-repeat: no-repeat; - visibility: hidden; -} - -a.data-table-cell-edit:hover { - background-position: -25px 0px; -} - -.recline-grid td:hover .data-table-cell-edit { - visibility: visible; -} - -div.data-table-cell-content-numeric > a.data-table-cell-edit { - left: 0px; - right: auto; -} - -.data-table-value-nonstring { - color: #282; -} - -.data-table-error { - color: red; -} - -.data-table-cell-editor-editor { - overflow: hidden; - display: block; - width: 98%; - height: 3em; - font-family: monospace; - margin: 3px 0; -} - -.data-table-cell-copypaste-editor { - overflow: hidden; - display: block; - width: 98%; - height: 10em; - font-family: monospace; - margin: 3px 0; -} - -.data-table-cell-editor-action { - float: left; - vertical-align: bottom; - text-align: center; -} - -.data-table-cell-editor-key { - font-size: 0.8em; - color: #999; -} - -/********************************************************** - * Read-only mode - *********************************************************/ - -.recline-read-only .recline-grid .write-op, -.recline-read-only .recline-grid a.data-table-cell-edit -{ - display: none; -} - -.recline-read-only a.row-header-menu { - display: none; -} - -/************************* - * WCAG 2.0 - *************************/ - -.wcag_hide { - position:absolute; - top:0; - left:-10000px; - width:1px; - height:1px; -} - -.wcag_hide2 { - clip: rect(1px, 1px, 1px, 1px); - display: block; - position: absolute; -} - -.wcag_show_on_focus { - font-size: 0 !important; -} - -.wcag_show_on_focus:focus { - font-size: 1em !important; -} diff --git a/css/map.css b/css/map.css deleted file mode 100644 index f1f2da29..00000000 --- a/css/map.css +++ /dev/null @@ -1,28 +0,0 @@ -.recline-map .map { - height: 500px; -} - -/********************************************************** - * Editor - *********************************************************/ - -.recline-map .editor { - float: right; - width: 200px; - padding-left: 0px; - margin-left: 10px; -} - -.recline-map .editor form { - padding-left: 4px; -} - -.recline-map .editor select { - width: 100%; -} - -.recline-map .editor .editor-options { - margin-top: 10px; - border-top: 1px solid gray; - padding: 5px 0; -} diff --git a/css/multiview.css b/css/multiview.css deleted file mode 100644 index 4b359842..00000000 --- a/css/multiview.css +++ /dev/null @@ -1,330 +0,0 @@ -.recline-data-explorer .data-view-container { - display: block; -} - -.recline-data-explorer .data-view-sidebar { - float: right; - margin-left: 8px; - width: 220px; -} - -.recline-data-explorer .header .navigation { - margin-bottom: 8px; -} - -.recline-data-explorer .header .navigation, -.recline-data-explorer .header .pagination, -.recline-data-explorer .header .pagination form -{ - display: inline; -} - -.recline-data-explorer .header .navigation { - float: left; -} - -.recline-data-explorer .header .menu-right { - float: right; - margin-left: 5px; - padding-left: 5px; -} - -.recline-results-info { - line-height: 35px; - margin-left: 20px; - float: left; -} - -.recline-data-explorer .data-view-sidebar > div { - margin-top: 5px; - margin-bottom: 10px; -} - -.recline-data-explorer .radio, -.recline-data-explorer .checkbox { - padding-left: 20px; -} - -.recline-data-explorer .editor-update-map { - margin: 30px 0px 20px 0px; -} - -.recline-data-explorer label { - font-weight: normal; -} - -/********************************************************** - * Query Editor - *********************************************************/ - -.recline-query-editor { - float: right; - height: 35px; - padding-right: 5px; - margin-right: 5px; - border-right: solid 2px #ddd; -} - -.header .input-prepend { - margin-bottom: auto; -} - -.header .add-on { - float: left; -} - -/* needed for Chrome but not FF */ -.header .add-on { - margin-left: -27px; -} - -/* needed for FF but not chrome */ -.header .input-prepend { - vertical-align: top; -} - -.recline-query-editor form button { - vertical-align: top; -} - -/* label for screen reader */ -.recline-query-editor .form-inline label { - position: absolute; - top:0; - left:-9999px -} - -/********************************************************** - * Pager - *********************************************************/ - -.recline-pager { - float: left; - margin: auto; - display: block; - margin-left: 20px; -} - -.recline-pager .pagination li { - display: inline-block; -} - -.recline-pager .pagination label { - display:none; -} - -.recline-pager .pagination input { - width: 40px; - height: 25px; - padding: 2px 4px; - margin: 0; - margin-top: -2px; - - border: 1px solid #cccccc; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - transition: border linear 0.2s, box-shadow linear 0.2s; - border-radius: 4px; - - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -webkit-border-radius: 4px; - - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-border-radius: 4px; - - -o-transition: border linear 0.2s, box-shadow linear 0.2s; -} - -.recline-pager .pagination a { - float: none; - margin-left: -5px; - color: #555; -} - -.recline-pager .pagination .page-range { - height: 34px; - padding: 5px 8px; - margin-left: -5px; - border: 1px solid #ddd; -} - -.recline-pager .pagination .page-range a { - padding: 0px 12px; - border: none; -} - -.recline-pager .pagination .page-range a:hover { - background-color: #ffffff; -} - -.recline-pager .pagination > li:first-child > a { - border-bottom-left-radius: 4px; - border-top-left-radius: 4px; - border-bottom-right-radius: 0px; - border-top-right-radius: 0px; - height: 34px; -} - -.recline-pager .pagination > li:last-child > a { - border-bottom-right-radius: 4px; - border-top-right-radius: 4px; - border-bottom-left-radius: 0px; - border-top-left-radius: 0px; - height: 34px; -} - -/********************************************************** - * Filter Editor - *********************************************************/ - -.recline-filter-editor { - padding: 8px; - display: none; -} - -.recline-filter-editor .filters { - margin: 20px 0px; -} - -.recline-filter-editor h3 { - margin-top: 4px; -} - -.recline-filter-editor .filter { - margin-top: 20px; -} - -.recline-filter-editor .filter .form-group { - margin-bottom: 0px; -} - -.recline-filter-editor .filter input, -.recline-filter-editor .filter label { - margin: 0px; -} - -.recline-filter-editor .js-edit button { - margin: 25px 0px 0px 0px; -} - -.recline-filter-editor .filter-term a { - font-size: 18px; -} - -.recline-filter-editor input, -.recline-filter-editor select -{ - width: 175px; -} - -.recline-filter-editor input { - margin-top: 0.5em; - margin-bottom: 10px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - border: 1px solid #cccccc; -} - -.recline-filter-editor label { - font-weight: normal; - display: block; -} - -.recline-filter-editor legend { - margin-bottom: 5px; -} - -.recline-filter-editor .add-filter { - margin-top: 1em; - margin-bottom: 2em; -} - -.recline-filter-editor .update-filter { - margin-top: 1em; -} - -/********************************************************** - * Fields Widget - *********************************************************/ - -.recline-fields-view { - display: none; -} - -.recline-fields-view .fields-list { - padding: 0; -} - -.recline-fields-view .panel { - background-color: #f5f5f5; - border: 1px solid #e5e5e5; -} - -.recline-fields-view .panel-group h3 { - padding-left: 10px; -} - -.recline-fields-view .fields-list .panel-heading { - padding: 2px 5px; - margin: 1px 0px 1px 5px; -} - -.recline-fields-view .panel a, -.recline-fields-view .panel h4 { - display: inline; -} - -.recline-fields-view .panel a { - padding: 0; -} - -.recline-fields-view .panel h4 { - word-wrap: break-word -} - -.recline-fields-view .clear { - clear: both; -} - -.recline-fields-view .facet-items { - list-style-type: none; - margin-left: 0; -} - -.recline-fields-view .facet-item .term { - font-weight: bold; -} - -.recline-fields-view .facet-item .count { -} - -/********************************************************** - * Notifications - *********************************************************/ - -.recline-data-explorer .notification-loader { - width: 18px; - margin-left: 5px; - background-image: url(data:image/gif;base64,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%3D%3D); - display: inline-block; -} - -.recline-data-explorer .alert-loader { - position: absolute; - width: 200px; - left: 50%; - margin-left: -100px; - z-index: 10000; - padding: 40px 0px 40px 0px; - margin-top: -10px; - text-align: center; - font-size: 16px; - font-weight: bold; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - border-radius: 0px; - border-top: none; -} - diff --git a/css/slickgrid.css b/css/slickgrid.css deleted file mode 100644 index f70a38f1..00000000 --- a/css/slickgrid.css +++ /dev/null @@ -1,188 +0,0 @@ -/* -IMPORTANT: -In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes. -No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS -classes should alter those! -*/ - -.recline-slickgrid .slick-header-columns .slick-header-column { - background-color: #e6e6e6; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); - background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - color: #333; - font-weight: bold; - border-right: 1px solid #ccc; - border-top: 1px solid #ccc; - border-bottom: 1px solid #bbb; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.recline-slickgrid .slick-header-column:hover, .slick-header-column-active { -} - -.recline-slickgrid .slick-header-column.ui-state-default { - height: 26px; -} - -.recline-slickgrid .slick-headerrow { - background: #fafafa; -} - -.recline-slickgrid .slick-headerrow-column { - background: #fafafa; - border-bottom: 0; - height: 100%; -} - -.recline-slickgrid .slick-row.ui-state-active { - background: #F5F7D7; -} - -.recline-slickgrid .slick-row { - position: absolute; - background: white; - border: 0px; - line-height: 20px; -} - -.recline-slickgrid .slick-row.selected { - z-index: 10; - background: #DFE8F6; -} - -.recline-slickgrid .slick-cell { - padding-left: 4px; - padding-right: 4px; -} - -.recline-slickgrid .slick-group { - border-bottom: 2px solid silver; -} - -.recline-slickgrid .slick-group-toggle { - width: 9px; - height: 9px; - margin-right: 5px; -} - -.recline-slickgrid .slick-group-toggle.expanded { - background: url(../images/collapse.gif) no-repeat center center; -} - -.recline-slickgrid .slick-group-toggle.collapsed { - background: url(../images/expand.gif) no-repeat center center; -} - -.recline-slickgrid .slick-group-totals { - color: gray; - background: white; -} - -.recline-slickgrid .slick-cell.selected { - background-color: beige; -} - -.recline-slickgrid .slick-cell.active { - border-color: gray; - border-style: solid; -} - -.recline-slickgrid .slick-sortable-placeholder { - background: silver !important; -} - -.recline-slickgrid .slick-row[row$="1"], .slick-row[row$="3"], .slick-row[row$="5"], .slick-row[row$="7"], .slick-row[row$="9"] { - background: #fafafa; -} - -.recline-slickgrid .slick-row.ui-state-active { - background: #F5F7D7; -} - -.recline-slickgrid .slick-row.loading { - opacity: 0.5; - filter: alpha(opacity = 50); -} - -.recline-slickgrid .slick-cell.invalid { - border-color: red; -} - -.recline-slickgrid .slick-row .slick-cell:first-child, -.recline-slickgrid .slick-header { - border-left: 1px solid #ccc; -} - -/* add one pixel extra as added one pixel to left border of header */ -.recline-slickgrid .slick-row .slick-cell { - margin-right: -1px; -} - -/* Slick grid context menu (not part of the recline-slickgrid div) */ -.slick-contextmenu { - border-radius: 5px -} - -.slick-contextmenu li { - clear: both; - height: 24px; - cursor: pointer; -} - -.slick-contextmenu .divider { - cursor: default; -} - -.slick-contextmenu > li:hover { - background-color: #0088cc; -} - -.slick-contextmenu .divider:hover { - background-color: #E5E5E5; -} - -.slick-contextmenu li:hover > label { - color: white; -} - -.slick-contextmenu input { - float: left; - margin-left: 15px; - margin-top: 5px; -} - -.slick-contextmenu label { - float: left; - margin-right: 15px; - margin-left: 5px; - margin-top: 3px; - color: #555; - cursor: pointer; -} - -.recline-slickgrid .recline-row-delete { - font-size: 12px; - padding: 3px; - width: 29px; - height: 18px; - line-height: 13px; -} - -.recline-cell-reorder { - font-size: 12px; - padding: 1px; - width: 31px; - height: 14px; - line-height: 13px; - cursor: move; - background: url("../images/drag-handle.png") no-repeat center center; -} diff --git a/css/timeline.css b/css/timeline.css deleted file mode 100644 index 0036d16f..00000000 --- a/css/timeline.css +++ /dev/null @@ -1,3 +0,0 @@ -.recline-timeline { - position: relative; -} diff --git a/demo/index.html b/demo/index.html deleted file mode 100644 index a5dec198..00000000 --- a/demo/index.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/demos/data/sample.csv b/demos/data/sample.csv deleted file mode 100644 index f3049990..00000000 --- a/demos/data/sample.csv +++ /dev/null @@ -1,4 +0,0 @@ -Date,Name,Price_Paid,Pricing_Mechanism,Description,Transaction_Type,FDIC_Number,OTS_Number,Type_of_Institution,Total_Assets_Q4_2008,Regulator,City,State,Stock_Symbol,Program,Warrant_Strike_Price,Warrant_Received,Stock_Price_as_of_close_20090604,In_Out_of_Money_as_of_close_20090604,Subsidy_Rate_Estimate_percentage,Subsidy_Rate_Estimate_Date,Subsidy_Rate_Estimate_Source -2009-05-29,AMERICAN PREMIER BANCORP,1800000.00,Par,Preferred Stock w/ Exercised Warrants,Purchase,3175600,,holding company,81126000.00,,Arcadia,CA,,CPP,,,,,,, -2009-05-29,"C.S. BANCSHARES, INC.",24990000.00,Par,Preferred Stock w/ Exercised Warrants,Purchase,1138786,,holding company,197348000.00,,Chillicothe,MO,,CPP,,,,,,, -2009-05-29,CB HOLDING CORP.,4114000.00,Par,Preferred Stock w/ Exercised Warrants,Purchase,3184901,,holding company,164478000.00,,Aledo,IL,,CPP,,,,,,, diff --git a/demos/images/calendar.gif b/demos/images/calendar.gif deleted file mode 100755 index 90fd2e17fe247d3252977a0fb5b9e15aa0a513fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1035 zcmZ?wbhEHb6krfw_|5)@S9f&o11MQ?xO!cw>Iaru@>)1r=M0E4P+ZZ7mII zIULeKgDU;7-PP>>t`%1~&E2Rss zmoK_ev+zRIlAE$=f(b?AOC-O$p6(*{}&|uUzqrRQPTg#DgT!MQR@E+qCNbq%HSb);wrm_poFAqn?eAdp14k-|}?A)@PHqKcBko;gs#qr|o(M^svF)$&Z_>+Z^f#E-c4#;{?o?zg3!SJ6`#$&^S1A0Mo zOd^|}cuZ7utZDF2x^QZKw}f%r8-))i=S!QW?1-3n$c0l#`Co>>jVJxRayDfzPBbdB z@d(x%aio0O$kr>VAGIN%@zD`^9;T^(W-@hfEzoLM$MA{iMIURU+pmy=lUPqr(`R8} zQ7}$#a!{(>!NA06dGg`n cVfH2m1!<$04Gs?(n;)OBm5A{(P++hI0EaJh%K!iX diff --git a/demos/index.html b/demos/index.html deleted file mode 100644 index 0857a876..00000000 --- a/demos/index.html +++ /dev/null @@ -1,94 +0,0 @@ ---- -layout: container -title: Demos -root: ../ ---- - - - - - -
- -
-
-
-

Data Explorer

-

The Data Explorer is a full - application for exploring and working with data in your browser. It - utilizes almost every feature of ReclineJS and is the app that ReclineJS - was originally developed to build.

-
-
-
- -
-
- -
- -
-
-
-

Timeliner

-

Create timelines quickly and easily using the Timeliner App created with ReclineJS (source code)

-
-
-
- -
-
- -
- -
-
-
-

Crime Maps

-

See Recline's geo filter and map view capabilities put to good use - exploring crime's in San Francisco. This example shows you the Recline - Data Explorer pre-configured to show thefts near 9th and Mission.

-
-
-
- -
-
- -
- -
-
-
-

Search Demo

-

See how easy it is to build a responsive AJAX-based search interface - to a search backend. It includes full-text search, faceting and ability - to easily customize the display of results.

-
-
-
- -
-
- -
- diff --git a/demos/multiview/app.js b/demos/multiview/app.js deleted file mode 100755 index e905ae92..00000000 --- a/demos/multiview/app.js +++ /dev/null @@ -1,117 +0,0 @@ -jQuery(function($) { - window.multiView = null; - window.explorerDiv = $('.data-explorer-here'); - - // create the demo dataset - var dataset = createDemoDataset(); - // now create the multiview - // this is rather more elaborate than the minimum as we configure the - // MultiView in various ways (see function below) - window.multiview = createMultiView(dataset); - - // last, we'll demonstrate binding to changes in the dataset - // this will print out a summary of each change onto the page in the - // changelog section - dataset.records.bind('all', function(name, obj) { - var $info = $('
'); - $info.html(name + ': ' + JSON.stringify(obj.toJSON())); - $('.changelog').append($info); - $('.changelog').show(); - }); -}); - -// create standard demo dataset -function createDemoDataset() { - var dataset = new recline.Model.Dataset({ - records: [ - {id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', title: 'first', lat:52.56, lon:13.40}, - {id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', title: 'second', lat:54.97, lon:-1.60}, - {id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', title: 'third', lat:40.00, lon:-75.5}, - {id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', title: 'fourth', lat:57.27, lon:-6.20}, - {id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', title: 'fifth', lat:51.58, lon:0}, - {id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', title: 'sixth', lat:51.04, lon:7.9} - ], - // let's be really explicit about fields - // Plus take opportunity to set date to be a date field and set some labels - fields: [ - {id: 'id'}, - {id: 'date', type: 'date'}, - {id: 'x', type: 'number'}, - {id: 'y', type: 'number'}, - {id: 'z', type: 'number'}, - {id: 'country', 'label': 'Country'}, - {id: 'title', 'label': 'Title'}, - {id: 'lat'}, - {id: 'lon'} - ] - }); - return dataset; -} - -// make MultivView -// -// creation / initialization in a function so we can call it again and again -var createMultiView = function(dataset, state) { - // remove existing multiview if present - var reload = false; - if (window.multiView) { - window.multiView.remove(); - window.multiView = null; - reload = true; - } - - var $el = $('
'); - $el.appendTo(window.explorerDiv); - - // customize the subviews for the MultiView - var fmt = I18nMessages('recline', recline.View.translations); - var views = [ - { - id: 'grid', - label: fmt.t('Grid'), - view: new recline.View.SlickGrid({ - model: dataset, - state: { - gridOptions: { - editable: true, - // Enable support for row add - enabledAddRow: true, - // Enable support for row delete - enabledDelRow: true, - // Enable support for row ReOrder - enableReOrderRow:true, - autoEdit: false, - enableCellNavigation: true - }, - columnsEditor: [ - { column: 'date', editor: Slick.Editors.Date }, - { column: 'title', editor: Slick.Editors.Text } - ] - } - }) - }, - { - id: 'graph', - label: fmt.t('Graph'), - view: new recline.View.Graph({ - model: dataset - }) - }, - { - id: 'map', - label: fmt.t('Map'), - view: new recline.View.Map({ - model: dataset - }) - } - ]; - - var multiView = new recline.View.MultiView({ - model: dataset, - el: $el, - state: state, - views: views - }); - return multiView; -} - diff --git a/demos/multiview/index.html b/demos/multiview/index.html deleted file mode 100644 index 4028fa3a..00000000 --- a/demos/multiview/index.html +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: container -title: Demos - Multiview -recline-deps: true -root: ../../ ---- - - - -
-

Changes

-
- -
- -
- - - diff --git a/demos/search/demo.search.app.js b/demos/search/demo.search.app.js deleted file mode 100644 index 9621ffbf..00000000 --- a/demos/search/demo.search.app.js +++ /dev/null @@ -1,270 +0,0 @@ -// (c) Open Knowledge Foundation 2012. Dedicated to the public domain. Please -// use and reuse freely - you don't even need to credit (though a link back to -// ReclineJS.com is always appreciated)! - - -// ## Our main loop - on document ready -jQuery(function($) { - var $el = $('.search-here'); - - // ### Overview - // - // We have a slightly more complex setup than is needed to allow for using - // this demo with different backends - // - // There are 2 alternatives: more complex and a simpler one - // - // If you just want to see how this work skip to the simple case ... - - // ### 1. More complex - use data from a backend configured in query string - - // Check for config from url query string - var config = recline.View.parseQueryString(decodeURIComponent(window.location.search)); - if (config.backend) { - // If we had it hand off to our more complex example setup - setupMoreComplexExample(config); - return; - } - - // ### 2. The simple example case - // - // We will just set up from some example local data (at the bottom of thile file) - - // #### Create our Recline Dataset from sample local data - var dataset = new recline.Model.Dataset({ - records: sampleData - }); - - // #### Custom template - // - // Create a custom template for rendering the records - var template = ' \ -
\ -

\ - {{title}} by {{Author}} \ -

\ -

{{description}}

\ -

${{price}}

\ -
\ - '; - - // #### Set up the search View (using custom template) - var searchView = new SearchView({ - el: $el, - model: dataset, - template: template - }); - searchView.render(); - - // #### Optional - we configure the initial query a bit and set up facets - dataset.queryState.set({ - size: 10 - }, - {silent: true} - ); - dataset.queryState.addFacet('Author'); - - // #### Finally - now do the first query - // - // After this point the Search View will take over handling queries! - dataset.query(); -}); - - -// ## Simple Search View -// -// This is a simple bespoke Backbone view for the Search. It Pulls together -// various Recline UI components and the central Dataset and Query (state) -// object -// -// It also provides simple support for customization e.g. of template for list of results -// -// var view = new SearchView({ -// el: $('some-element'), -// model: dataset -// // EITHER a mustache template (passed a JSON version of recline.Model.Record -// // OR a function which receives a record in JSON form and returns html -// template: mustache-template-or-function -// }); -var SearchView = Backbone.View.extend({ - initialize: function(options) { - this.el = $(this.el); - _.bindAll(this, 'render'); - this.recordTemplate = options.template; - // Every time we do a search the recline.Dataset.records Backbone - // collection will get reset. We want to re-render each time! - this.model.records.bind('reset', this.render); - this.templateResults = options.template; - }, - - // overall template for this view - template: ' \ -
\ -
\ -
\ -

records found

\ -
\ - \ -
\ - {{{results}}} \ -
\ -
\ -
\ - ', - - // render the view - render: function() { - var results = ''; - if (_.isFunction(this.templateResults)) { - var results = _.map(this.model.records.toJSON(), this.templateResults).join('\n'); - } else { - // templateResults is just for one result ... - var tmpl = '{{#records}}' + this.templateResults + '{{/records}}'; - var results = Mustache.render(tmpl, { - records: this.model.records.toJSON() - }); - } - var html = Mustache.render(this.template, { - results: results - }); - this.el.html(html); - - // Set the total records found info - this.el.find('.total span').text(this.model.recordCount); - - // ### Now setup all the extra mini-widgets - // - // Facets, Pager, QueryEditor etc - - var view = new recline.View.FacetViewer({ - model: this.model - }); - view.render(); - this.el.find('.sidebar').append(view.el); - - var pager = new recline.View.Pager({ - model: this.model - }); - this.el.find('.pager-here').append(pager.el); - - var queryEditor = new recline.View.QueryEditor({ - model: this.model.queryState - }); - this.el.find('.query-here').append(queryEditor.el); - } -}); - -// -------------------------------------------------------- -// ## Custom code very specific to this demo - -// e.g. to provide custom templates for the google doc and openspending examples - - -// ### Handle case where we get data from a specific backend -// -// Includes providing custom templates -function setupMoreComplexExample(config) { - var $el = $('.search-here'); - var dataset = new recline.Model.Dataset(config); - // async as may be fetching remote - dataset.fetch().done(function() { - var template = templates[dataset.get('url')] || templates['generic']; - var searchView = new SearchView({ - el: $el, - model: dataset, - template: template - }); - searchView.render(); - - dataset.queryState.set({ - size: 5 - }, - {silent: true} - ); - if (dataset.get('url') in templates) { - // for gdocs example - dataset.queryState.addFacet('cause'); - } - dataset.query(); - }); -}; - -var templates = { - // generic template function - 'generic': function(record) { - var template = '
\ -
    \ - {{#data}} \ -
  • {{key}}: {{value}}
  • \ - {{/data}} \ -
\ -
\ - '; - var data = _.map(_.keys(record), function(key) { - return { key: key, value: record[key] }; - }); - return Mustache.render(template, { - data: data - }); - }, - 'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdExXSTl2Y01xZEszOTBFZjVzcGtzVVE': function(record) { - var template = '
\ -

\ - {{record.incidentsite}} – {{record.datereported}} – {{record.estimatedspillvolumebbl}} barrels \ -

\ -
    \ - {{#data}} \ -
  • {{key}}: {{value}}
  • \ - {{/data}} \ -
\ -
\ - '; - var data = []; - _.each(_.keys(record), function(key) { - data.push({ key: key, value: record[key] }); - }); - return Mustache.render(template, { - record: record, - data: data - }); - } -} - -var sampleData = [ - { - title: 'War and Peace', - description: 'The epic tale of love, war and history', - Author: 'Tolstoy', - price: 7.99 - }, - { - title: 'Anna Karenina', - description: 'How things go wrong in love and ultimately lead to suicide. This is why you should not have affairs, girls!', - Author: 'Tolstoy', - price: 8.50 - }, - { - title: "Fathers and Sons", - description: "Another 19th century Russian novel", - Author: "Turgenev", - price: 11 - } -]; - -var formatAmount = function (num) { - var billion = 1000000000; - var million = 1000000; - var thousand = 1000; - var numabs = Math.abs(num); - if (numabs > billion) { - return (num / billion).toFixed(0) + 'bn'; - } - if (numabs > (million / 2)) { - return (num / million).toFixed(0) + 'm'; - } - if (numabs > thousand) { - return (num / thousand).toFixed(0) + 'k'; - } else { - return num.toFixed(0); - } -}; diff --git a/demos/search/index.html b/demos/search/index.html deleted file mode 100644 index b9cadf5f..00000000 --- a/demos/search/index.html +++ /dev/null @@ -1,84 +0,0 @@ ---- -layout: container -title: Demos - Search -recline-deps: true -root: ../../ ---- - - - - - -
-

This demo shows how Recline can be used to build a search app. It includes faceting as well as search. You can find the source javascript here (plus prettified version of source for readability) – please feel free to reuse!

-

The default setup uses local example data but you can also connect directly to any other backend supported by Recline, for example SOLR, ElasticSearch or even a google docs spreadsheet. Here's an example running against a GDocs spreadsheet (Oil spills in the Niger Delta).

-
- -
- -
- -
- - - diff --git a/dist/recline.css b/dist/recline.css deleted file mode 100644 index f1e976fd..00000000 --- a/dist/recline.css +++ /dev/null @@ -1,796 +0,0 @@ -.recline-flot .graph { - height: 500px; - overflow: hidden; -} - -.recline-flot .legend table { - width: auto; - margin-bottom: 0; -} - -.recline-flot .legend td { - padding: 5px; - line-height: 13px; -} - -.recline-flot .graph .alert { - width: 450px; -} - -#recline-flot-tooltip { - position: absolute; - background-color: #FEE; - color: #000000; - opacity: 0.8; - border: 1px solid #fdd; -} -/********************************************************** - * (Data) Grid - *********************************************************/ - -table.recline-grid { - table-layout: fixed; - width: 100%; -} - -.recline-grid .btn-group .dropdown-toggle { - padding: 1px 3px; - line-height: auto; -} - -.recline-grid td, .recline-grid th { - border-left: 1px solid #ccc; - padding: 3px 4px; - text-align: left; - word-wrap: break-word; - white-space: normal; -} - -.recline-grid tbody tr { - vertical-align: top; - border-bottom: solid 1px #ccc; -} - -.recline-grid tbody tr:last-child { - border-bottom: 1px solid #ccc; -} - -.recline-grid tbody td:last-child { - border-right: 1px solid #ccc; -} - -/* direct borrowing from twitter buttons */ -.recline-grid th { - background-color: #e6e6e6; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); - background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - color: #333; - border: 1px solid #ccc; - border-bottom-color: #bbb; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -webkit-transition: 0.1s linear all; - -moz-transition: 0.1s linear all; - -ms-transition: 0.1s linear all; - -o-transition: 0.1s linear all; - transition: 0.1s linear all; -} - -/********************************************************** - * Fixed Header - http://www.imaputz.com/cssStuff/bigFourVersion.html - *********************************************************/ - -div.table-container { - overflow: auto; -} - -/* Reset overflow value to hidden for all non-IE browsers. */ -html>body div.table-container { - overflow: hidden; -} - -thead.fixed-header tr { - overflow-x: hidden; -} - -/* set table header to a fixed position. WinIE 6.x only */ -/* In WinIE 6.x, any element with a position property set to relative and is a child of */ -/* an element that has an overflow property set, the relative value translates into fixed. */ -/* Ex: parent element DIV with a class of table-container has an overflow property set to auto */ -thead.fixed-header tr { - position: relative -} - -/* set THEAD element to have block level attributes. All other non-IE browsers */ -/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */ -html>body thead.fixed-header tr { - display: block -} - -/* define the table content to be scrollable */ -/* set TBODY element to have block level attributes. All other non-IE browsers */ -/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */ -/* induced side effect is that child TDs no longer accept width: auto */ -tbody.scroll-content { - display: block; - max-height: 500px; - overflow: auto; -} - -/********************************************************** - * Data Table Menus - *********************************************************/ - -.column-header-menu, a.root-header-menu { - float: right; -} - -div.data-table-cell-content { - line-height: 1.2; - color: #222; - position: relative; -} - -div.data-table-cell-content-numeric { - text-align: right; -} - -a.data-table-cell-edit { - position: absolute; - top: 0; - right: 0; - display: block; - width: 25px; - height: 16px; - text-decoration: none; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAQCAYAAABUWyyMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTNEODNFMjVEMDA5MTFERjk5NzhEQzZDRDUwRkEzMUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OTNEODNFMjZEMDA5MTFERjk5NzhEQzZDRDUwRkEzMUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5M0Q4M0UyM0QwMDkxMURGOTk3OERDNkNENTBGQTMxQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5M0Q4M0UyNEQwMDkxMURGOTk3OERDNkNENTBGQTMxQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpApVmcAAAKVSURBVHjaYvz//z/DcAAsIGLaxjf2QKoLiM2oaPYpIC7L8hc5COI4F9+hmR17e1UOskAFVgCxBJUDyQxqriQ97GCCCpBkATCUGQxVOTHY7KyMDFoKHMhKJXCwCQJgKDNEOAlisHk5mRh8LPgw7GChNEg2HfvI8OnbPzDbwYAH6BkmhmsPflA12EtnPmN4/vY3mF0YKgb2zJYTn5CV/EfxiKgAC4O7CS8DHzczw5PXvxl2nv7E8PP3fxTx87e/o1hipMrFcOfZT6BmVgYZUTawmKUWN8Pxa1+xOkpNhp2hLk6CQVKYleHcrW8MTYteMHz+/g9FfMW+9yh6Ih0FGA5e/MLAAIx5IzUusFiqtzDD7K1vYUpcmZA1+FnxM7z++AccyqBkAgphmMPY2ZiAHvsMFkcGMkAPgMQ+ff3L8PPXPzD++O0vztDtypBmuPXkJziUebiYwSEMcxiID/IYiEYJLKDjQWLP3v1h+AI0G4RhMQQF5UzoDvr5CxIDoJhQlmKHy527/Y3hLjDkD4BCBgsAxSAoiYEwrqQFchAoWcBiAOQge30euBwoJkAh37/6FVb9oBgEeQCE0ZKWC8488vj1LzCmJTgLTKZn0ZIquYAJOURBsQDKB68//GHgYEWkOlBMgPICKIYcoCGIC4BiFRSDuEIUFBtSQizg5AWKHRgAxQSoZALFECy54QKgZAbLKxgegZVAfFxM4LyiLMUGTPeQ0ujAhS9gz7mb8sI9iisWQclSG7UIRgFlM56CM3R3uhTY0bC0DkpOtx//AGd4mEdxxSIoWaIVwQyMoCYKsGanWTsFWM8wQmt2mtkBqmdgMfKCRna8wMGmth2MMI/EAfEdKltwB2ouAz3sYBwurV8mhmECAAIMAEe3EkMWh/DvAAAAAElFTkSuQmCC); - background-repeat: no-repeat; - visibility: hidden; -} - -a.data-table-cell-edit:hover { - background-position: -25px 0px; -} - -.recline-grid td:hover .data-table-cell-edit { - visibility: visible; -} - -div.data-table-cell-content-numeric > a.data-table-cell-edit { - left: 0px; - right: auto; -} - -.data-table-value-nonstring { - color: #282; -} - -.data-table-error { - color: red; -} - -.data-table-cell-editor-editor { - overflow: hidden; - display: block; - width: 98%; - height: 3em; - font-family: monospace; - margin: 3px 0; -} - -.data-table-cell-copypaste-editor { - overflow: hidden; - display: block; - width: 98%; - height: 10em; - font-family: monospace; - margin: 3px 0; -} - -.data-table-cell-editor-action { - float: left; - vertical-align: bottom; - text-align: center; -} - -.data-table-cell-editor-key { - font-size: 0.8em; - color: #999; -} - -/********************************************************** - * Read-only mode - *********************************************************/ - -.recline-read-only .recline-grid .write-op, -.recline-read-only .recline-grid a.data-table-cell-edit -{ - display: none; -} - -.recline-read-only a.row-header-menu { - display: none; -} - -/************************* - * WCAG 2.0 - *************************/ - -.wcag_hide { - position:absolute; - top:0; - left:-10000px; - width:1px; - height:1px; -} - -.wcag_hide2 { - clip: rect(1px, 1px, 1px, 1px); - display: block; - position: absolute; -} - -.wcag_show_on_focus { - font-size: 0 !important; -} - -.wcag_show_on_focus:focus { - font-size: 1em !important; -} -.recline-map .map { - height: 500px; -} - -/********************************************************** - * Editor - *********************************************************/ - -.recline-map .editor { - float: right; - width: 200px; - padding-left: 0px; - margin-left: 10px; -} - -.recline-map .editor form { - padding-left: 4px; -} - -.recline-map .editor select { - width: 100%; -} - -.recline-map .editor .editor-options { - margin-top: 10px; - border-top: 1px solid gray; - padding: 5px 0; -} -.recline-data-explorer .data-view-container { - display: block; -} - -.recline-data-explorer .data-view-sidebar { - float: right; - margin-left: 8px; - width: 220px; -} - -.recline-data-explorer .header .navigation { - margin-bottom: 8px; -} - -.recline-data-explorer .header .navigation, -.recline-data-explorer .header .pagination, -.recline-data-explorer .header .pagination form -{ - display: inline; -} - -.recline-data-explorer .header .navigation { - float: left; -} - -.recline-data-explorer .header .menu-right { - float: right; - margin-left: 5px; - padding-left: 5px; -} - -.recline-results-info { - line-height: 35px; - margin-left: 20px; - float: left; -} - -.recline-data-explorer .data-view-sidebar > div { - margin-top: 5px; - margin-bottom: 10px; -} - -.recline-data-explorer .radio, -.recline-data-explorer .checkbox { - padding-left: 20px; -} - -.recline-data-explorer .editor-update-map { - margin: 30px 0px 20px 0px; -} - -.recline-data-explorer label { - font-weight: normal; -} - -/********************************************************** - * Query Editor - *********************************************************/ - -.recline-query-editor { - float: right; - height: 35px; - padding-right: 5px; - margin-right: 5px; - border-right: solid 2px #ddd; -} - -.header .input-prepend { - margin-bottom: auto; -} - -.header .add-on { - float: left; -} - -/* needed for Chrome but not FF */ -.header .add-on { - margin-left: -27px; -} - -/* needed for FF but not chrome */ -.header .input-prepend { - vertical-align: top; -} - -.recline-query-editor form button { - vertical-align: top; -} - -/* label for screen reader */ -.recline-query-editor .form-inline label { - position: absolute; - top:0; - left:-9999px -} - -/********************************************************** - * Pager - *********************************************************/ - -.recline-pager { - float: left; - margin: auto; - display: block; - margin-left: 20px; -} - -.recline-pager .pagination li { - display: inline-block; -} - -.recline-pager .pagination label { - display:none; -} - -.recline-pager .pagination input { - width: 40px; - height: 25px; - padding: 2px 4px; - margin: 0; - margin-top: -2px; - - border: 1px solid #cccccc; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - transition: border linear 0.2s, box-shadow linear 0.2s; - border-radius: 4px; - - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -webkit-border-radius: 4px; - - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-border-radius: 4px; - - -o-transition: border linear 0.2s, box-shadow linear 0.2s; -} - -.recline-pager .pagination a { - float: none; - margin-left: -5px; - color: #555; -} - -.recline-pager .pagination .page-range { - height: 34px; - padding: 5px 8px; - margin-left: -5px; - border: 1px solid #ddd; -} - -.recline-pager .pagination .page-range a { - padding: 0px 12px; - border: none; -} - -.recline-pager .pagination .page-range a:hover { - background-color: #ffffff; -} - -.recline-pager .pagination > li:first-child > a { - border-bottom-left-radius: 4px; - border-top-left-radius: 4px; - border-bottom-right-radius: 0px; - border-top-right-radius: 0px; - height: 34px; -} - -.recline-pager .pagination > li:last-child > a { - border-bottom-right-radius: 4px; - border-top-right-radius: 4px; - border-bottom-left-radius: 0px; - border-top-left-radius: 0px; - height: 34px; -} - -/********************************************************** - * Filter Editor - *********************************************************/ - -.recline-filter-editor { - padding: 8px; - display: none; -} - -.recline-filter-editor .filters { - margin: 20px 0px; -} - -.recline-filter-editor h3 { - margin-top: 4px; -} - -.recline-filter-editor .filter { - margin-top: 20px; -} - -.recline-filter-editor .filter .form-group { - margin-bottom: 0px; -} - -.recline-filter-editor .filter input, -.recline-filter-editor .filter label { - margin: 0px; -} - -.recline-filter-editor .js-edit button { - margin: 25px 0px 0px 0px; -} - -.recline-filter-editor .filter-term a { - font-size: 18px; -} - -.recline-filter-editor input, -.recline-filter-editor select -{ - width: 175px; -} - -.recline-filter-editor input { - margin-top: 0.5em; - margin-bottom: 10px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - border: 1px solid #cccccc; -} - -.recline-filter-editor label { - font-weight: normal; - display: block; -} - -.recline-filter-editor legend { - margin-bottom: 5px; -} - -.recline-filter-editor .add-filter { - margin-top: 1em; - margin-bottom: 2em; -} - -.recline-filter-editor .update-filter { - margin-top: 1em; -} - -/********************************************************** - * Fields Widget - *********************************************************/ - -.recline-fields-view { - display: none; -} - -.recline-fields-view .fields-list { - padding: 0; -} - -.recline-fields-view .panel { - background-color: #f5f5f5; - border: 1px solid #e5e5e5; -} - -.recline-fields-view .panel-group h3 { - padding-left: 10px; -} - -.recline-fields-view .fields-list .panel-heading { - padding: 2px 5px; - margin: 1px 0px 1px 5px; -} - -.recline-fields-view .panel a, -.recline-fields-view .panel h4 { - display: inline; -} - -.recline-fields-view .panel a { - padding: 0; -} - -.recline-fields-view .panel h4 { - word-wrap: break-word -} - -.recline-fields-view .clear { - clear: both; -} - -.recline-fields-view .facet-items { - list-style-type: none; - margin-left: 0; -} - -.recline-fields-view .facet-item .term { - font-weight: bold; -} - -.recline-fields-view .facet-item .count { -} - -/********************************************************** - * Notifications - *********************************************************/ - -.recline-data-explorer .notification-loader { - width: 18px; - margin-left: 5px; - background-image: url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA%3D%3D); - display: inline-block; -} - -.recline-data-explorer .alert-loader { - position: absolute; - width: 200px; - left: 50%; - margin-left: -100px; - z-index: 10000; - padding: 40px 0px 40px 0px; - margin-top: -10px; - text-align: center; - font-size: 16px; - font-weight: bold; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - border-radius: 0px; - border-top: none; -} - -/* -IMPORTANT: -In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes. -No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS -classes should alter those! -*/ - -.recline-slickgrid .slick-header-columns .slick-header-column { - background-color: #e6e6e6; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); - background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - color: #333; - font-weight: bold; - border-right: 1px solid #ccc; - border-top: 1px solid #ccc; - border-bottom: 1px solid #bbb; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.recline-slickgrid .slick-header-column:hover, .slick-header-column-active { -} - -.recline-slickgrid .slick-header-column.ui-state-default { - height: 26px; -} - -.recline-slickgrid .slick-headerrow { - background: #fafafa; -} - -.recline-slickgrid .slick-headerrow-column { - background: #fafafa; - border-bottom: 0; - height: 100%; -} - -.recline-slickgrid .slick-row.ui-state-active { - background: #F5F7D7; -} - -.recline-slickgrid .slick-row { - position: absolute; - background: white; - border: 0px; - line-height: 20px; -} - -.recline-slickgrid .slick-row.selected { - z-index: 10; - background: #DFE8F6; -} - -.recline-slickgrid .slick-cell { - padding-left: 4px; - padding-right: 4px; -} - -.recline-slickgrid .slick-group { - border-bottom: 2px solid silver; -} - -.recline-slickgrid .slick-group-toggle { - width: 9px; - height: 9px; - margin-right: 5px; -} - -.recline-slickgrid .slick-group-toggle.expanded { - background: url(../images/collapse.gif) no-repeat center center; -} - -.recline-slickgrid .slick-group-toggle.collapsed { - background: url(../images/expand.gif) no-repeat center center; -} - -.recline-slickgrid .slick-group-totals { - color: gray; - background: white; -} - -.recline-slickgrid .slick-cell.selected { - background-color: beige; -} - -.recline-slickgrid .slick-cell.active { - border-color: gray; - border-style: solid; -} - -.recline-slickgrid .slick-sortable-placeholder { - background: silver !important; -} - -.recline-slickgrid .slick-row[row$="1"], .slick-row[row$="3"], .slick-row[row$="5"], .slick-row[row$="7"], .slick-row[row$="9"] { - background: #fafafa; -} - -.recline-slickgrid .slick-row.ui-state-active { - background: #F5F7D7; -} - -.recline-slickgrid .slick-row.loading { - opacity: 0.5; - filter: alpha(opacity = 50); -} - -.recline-slickgrid .slick-cell.invalid { - border-color: red; -} - -.recline-slickgrid .slick-row .slick-cell:first-child, -.recline-slickgrid .slick-header { - border-left: 1px solid #ccc; -} - -/* add one pixel extra as added one pixel to left border of header */ -.recline-slickgrid .slick-row .slick-cell { - margin-right: -1px; -} - -/* Slick grid context menu (not part of the recline-slickgrid div) */ -.slick-contextmenu { - border-radius: 5px -} - -.slick-contextmenu li { - clear: both; - height: 24px; - cursor: pointer; -} - -.slick-contextmenu .divider { - cursor: default; -} - -.slick-contextmenu > li:hover { - background-color: #0088cc; -} - -.slick-contextmenu .divider:hover { - background-color: #E5E5E5; -} - -.slick-contextmenu li:hover > label { - color: white; -} - -.slick-contextmenu input { - float: left; - margin-left: 15px; - margin-top: 5px; -} - -.slick-contextmenu label { - float: left; - margin-right: 15px; - margin-left: 5px; - margin-top: 3px; - color: #555; - cursor: pointer; -} - -.recline-slickgrid .recline-row-delete { - font-size: 12px; - padding: 3px; - width: 29px; - height: 18px; - line-height: 13px; -} - -.recline-cell-reorder { - font-size: 12px; - padding: 1px; - width: 31px; - height: 14px; - line-height: 13px; - cursor: move; - background: url("../images/drag-handle.png") no-repeat center center; -} -.recline-timeline { - position: relative; -} diff --git a/dist/recline.dataset.js b/dist/recline.dataset.js deleted file mode 100644 index 96a5ac53..00000000 --- a/dist/recline.dataset.js +++ /dev/null @@ -1,896 +0,0 @@ -// # Recline Backbone Models -this.recline = this.recline || {}; -this.recline.Model = this.recline.Model || {}; - -(function(my) { - "use strict"; - -// use either jQuery or Underscore Deferred depending on what is available -var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - -// ## Dataset -my.Dataset = Backbone.Model.extend({ - constructor: function Dataset() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // ### initialize - initialize: function() { - var self = this; - _.bindAll(this, 'query'); - this.backend = null; - if (this.get('backend')) { - this.backend = this._backendFromString(this.get('backend')); - } else { // try to guess backend ... - if (this.get('records')) { - this.backend = recline.Backend.Memory; - } - } - this.fields = new my.FieldList(); - this.records = new my.RecordList(); - this._changes = { - deletes: [], - updates: [], - creates: [] - }; - this.facets = new my.FacetList(); - this.recordCount = null; - this.queryState = new my.Query(); - this.queryState.bind('change facet:add', function () { - self.query(); // We want to call query() without any arguments. - }); - // store is what we query and save against - // store will either be the backend or be a memory store if Backend fetch - // tells us to use memory store - this._store = this.backend; - - // if backend has a handleQueryResultFunction, use that - this._handleResult = (this.backend != null && _.has(this.backend, 'handleQueryResult')) ? - this.backend.handleQueryResult : this._handleQueryResult; - if (this.backend == recline.Backend.Memory) { - this.fetch(); - } - }, - - sync: function(method, model, options) { - return this.backend.sync(method, model, options); - }, - - // ### fetch - // - // Retrieve dataset and (some) records from the backend. - fetch: function() { - var self = this; - var dfd = new Deferred(); - - if (this.backend !== recline.Backend.Memory) { - this.backend.fetch(this.toJSON()) - .done(handleResults) - .fail(function(args) { - dfd.reject(args); - }); - } else { - // special case where we have been given data directly - handleResults({ - records: this.get('records'), - fields: this.get('fields'), - useMemoryStore: true - }); - } - - function handleResults(results) { - // if explicitly given the fields - // (e.g. var dataset = new Dataset({fields: fields, ...}) - // use that field info over anything we get back by parsing the data - // (results.fields) - var fields = self.get('fields') || results.fields; - - var out = self._normalizeRecordsAndFields(results.records, fields); - if (results.useMemoryStore) { - self._store = new recline.Backend.Memory.Store(out.records, out.fields); - } - - self.set(results.metadata); - self.fields.reset(out.fields); - self.query() - .done(function() { - dfd.resolve(self); - }) - .fail(function(args) { - dfd.reject(args); - }); - } - - return dfd.promise(); - }, - - // ### _normalizeRecordsAndFields - // - // Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects - // - // e.g. fields = ['a', 'b', 'c'] and records = [ [1,2,3] ] => - // fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}] - _normalizeRecordsAndFields: function(records, fields) { - // if no fields get them from records - if (!fields && records && records.length > 0) { - // records is array then fields is first row of records ... - if (records[0] instanceof Array) { - fields = records[0]; - records = records.slice(1); - } else { - fields = _.map(_.keys(records[0]), function(key) { - return {id: key}; - }); - } - } - - // fields is an array of strings (i.e. list of field headings/ids) - if (fields && fields.length > 0 && (fields[0] === null || typeof(fields[0]) != 'object')) { - // Rename duplicate fieldIds as each field name needs to be - // unique. - var seen = {}; - fields = _.map(fields, function(field, index) { - if (field === null) { - field = ''; - } else { - field = field.toString(); - } - // cannot use trim as not supported by IE7 - var fieldId = field.replace(/^\s+|\s+$/g, ''); - if (fieldId === '') { - fieldId = '_noname_'; - field = fieldId; - } - while (fieldId in seen) { - seen[field] += 1; - fieldId = field + seen[field]; - } - if (!(field in seen)) { - seen[field] = 0; - } - // TODO: decide whether to keep original name as label ... - // return { id: fieldId, label: field || fieldId } - return { id: fieldId }; - }); - } - // records is provided as arrays so need to zip together with fields - // NB: this requires you to have fields to match arrays - if (records && records.length > 0 && records[0] instanceof Array) { - records = _.map(records, function(doc) { - var tmp = {}; - _.each(fields, function(field, idx) { - tmp[field.id] = doc[idx]; - }); - return tmp; - }); - } - return { - fields: fields, - records: records - }; - }, - - save: function() { - var self = this; - // TODO: need to reset the changes ... - return this._store.save(this._changes, this.toJSON()); - }, - - // ### query - // - // AJAX method with promise API to get records from the backend. - // - // It will query based on current query state (given by this.queryState) - // updated by queryObj (if provided). - // - // Resulting RecordList are used to reset this.records and are - // also returned. - query: function(queryObj) { - var self = this; - var dfd = new Deferred(); - this.trigger('query:start'); - - if (queryObj) { - var attributes = queryObj; - if (queryObj instanceof my.Query) { - attributes = queryObj.toJSON(); - } - this.queryState.set(attributes, {silent: true}); - } - var actualQuery = this.queryState.toJSON(); - - this._store.query(actualQuery, this.toJSON()) - .done(function(queryResult) { - self._handleResult(queryResult); - self.trigger('query:done'); - dfd.resolve(self.records); - }) - .fail(function(args) { - self.trigger('query:fail', args); - dfd.reject(args); - }); - return dfd.promise(); - }, - - _handleQueryResult: function(queryResult) { - var self = this; - self.recordCount = queryResult.total; - var docs = _.map(queryResult.hits, function(hit) { - var _doc = new my.Record(hit); - _doc.fields = self.fields; - _doc.bind('change', function(doc) { - self._changes.updates.push(doc.toJSON()); - }); - _doc.bind('destroy', function(doc) { - self._changes.deletes.push(doc.toJSON()); - }); - return _doc; - }); - self.records.reset(docs); - if (queryResult.facets) { - var facets = _.map(queryResult.facets, function(facetResult, facetId) { - facetResult.id = facetId; - return new my.Facet(facetResult); - }); - self.facets.reset(facets); - } - }, - - toTemplateJSON: function() { - var data = this.toJSON(); - data.recordCount = this.recordCount; - data.fields = this.fields.toJSON(); - return data; - }, - - // ### getFieldsSummary - // - // Get a summary for each field in the form of a `Facet`. - // - // @return null as this is async function. Provides deferred/promise interface. - getFieldsSummary: function() { - var self = this; - var query = new my.Query(); - query.set({size: 0}); - this.fields.each(function(field) { - query.addFacet(field.id); - }); - var dfd = new Deferred(); - this._store.query(query.toJSON(), this.toJSON()).done(function(queryResult) { - if (queryResult.facets) { - _.each(queryResult.facets, function(facetResult, facetId) { - facetResult.id = facetId; - var facet = new my.Facet(facetResult); - // TODO: probably want replace rather than reset (i.e. just replace the facet with this id) - self.fields.get(facetId).facets.reset(facet); - }); - } - dfd.resolve(queryResult); - }); - return dfd.promise(); - }, - - // Deprecated (as of v0.5) - use record.summary() - recordSummary: function(record) { - return record.summary(); - }, - - // ### _backendFromString(backendString) - // - // Look up a backend module from a backend string (look in recline.Backend) - _backendFromString: function(backendString) { - var backend = null; - if (recline && recline.Backend) { - _.each(_.keys(recline.Backend), function(name) { - if (name.toLowerCase() === backendString.toLowerCase()) { - backend = recline.Backend[name]; - } - }); - } - return backend; - } -}); - - -// ## A Record -// -// A single record (or row) in the dataset -my.Record = Backbone.Model.extend({ - constructor: function Record() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // ### initialize - // - // Create a Record - // - // You usually will not do this directly but will have records created by - // Dataset e.g. in query method - // - // Certain methods require presence of a fields attribute (identical to that on Dataset) - initialize: function() { - _.bindAll(this, 'getFieldValue'); - }, - - // ### getFieldValue - // - // For the provided Field get the corresponding rendered computed data value - // for this record. - // - // NB: if field is undefined a default '' value will be returned - getFieldValue: function(field) { - var val = this.getFieldValueUnrendered(field); - if (field && !_.isUndefined(field.renderer)) { - val = field.renderer(val, field, this.toJSON()); - } - return val; - }, - - // ### getFieldValueUnrendered - // - // For the provided Field get the corresponding computed data value - // for this record. - // - // NB: if field is undefined a default '' value will be returned - getFieldValueUnrendered: function(field) { - if (!field) { - return ''; - } - var val = this.get(field.id); - if (field.deriver) { - val = field.deriver(val, field, this); - } - return val; - }, - - // ### summary - // - // Get a simple html summary of this record in form of key/value list - summary: function(record) { - var self = this; - var html = '
'; - this.fields.each(function(field) { - if (field.id != 'id') { - html += '
' + field.get('label') + ': ' + self.getFieldValue(field) + '
'; - } - }); - html += '
'; - return html; - }, - - // Override Backbone save, fetch and destroy so they do nothing - // Instead, Dataset object that created this Record should take care of - // handling these changes (discovery will occur via event notifications) - // WARNING: these will not persist *unless* you call save on Dataset - fetch: function() {}, - save: function() {}, - destroy: function() { this.trigger('destroy', this); } -}); - - -// ## A Backbone collection of Records -my.RecordList = Backbone.Collection.extend({ - constructor: function RecordList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Record -}); - - -// ## A Field (aka Column) on a Dataset -my.Field = Backbone.Model.extend({ - constructor: function Field() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - // ### defaults - define default values - defaults: { - label: null, - type: 'string', - format: null, - is_derived: false - }, - // ### initialize - // - // @param {Object} data: standard Backbone model attributes - // - // @param {Object} options: renderer and/or deriver functions. - initialize: function(data, options) { - // if a hash not passed in the first argument throw error - if ('0' in data) { - throw new Error('Looks like you did not pass a proper hash with id to Field constructor'); - } - if (this.attributes.label === null) { - this.set({label: this.id}); - } - if (this.attributes.type.toLowerCase() in this._typeMap) { - this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()]; - } - if (options) { - this.renderer = options.renderer; - this.deriver = options.deriver; - } - if (!this.renderer) { - this.renderer = this.defaultRenderers[this.get('type')]; - } - this.facets = new my.FacetList(); - }, - _typeMap: { - 'text': 'string', - 'double': 'number', - 'float': 'number', - 'numeric': 'number', - 'int': 'integer', - 'datetime': 'date-time', - 'bool': 'boolean', - 'timestamp': 'date-time', - 'json': 'object' - }, - defaultRenderers: { - object: function(val, field, doc) { - return JSON.stringify(val); - }, - geo_point: function(val, field, doc) { - return JSON.stringify(val); - }, - 'number': function(val, field, doc) { - var format = field.get('format'); - if (format === 'percentage') { - return val + '%'; - } - return val; - }, - 'string': function(val, field, doc) { - var format = field.get('format'); - if (format === 'markdown') { - if (typeof Showdown !== 'undefined') { - var showdown = new Showdown.converter(); - out = showdown.makeHtml(val); - return out; - } else { - return val; - } - } else if (format == 'plain') { - return val; - } else { - // as this is the default and default type is string may get things - // here that are not actually strings - if (val && typeof val === 'string') { - val = val.replace(/(https?:\/\/[^ ]+)/g, '$1'); - } - return val; - } - } - } -}); - -my.FieldList = Backbone.Collection.extend({ - constructor: function FieldList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Field -}); - -// ## Query -my.Query = Backbone.Model.extend({ - constructor: function Query() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - defaults: function() { - return { - size: 100, - from: 0, - q: '', - facets: {}, - filters: [] - }; - }, - _filterTemplates: { - term: { - type: 'term', - // TODO do we need this attribute here? - field: '', - term: '' - }, - range: { - type: 'range', - from: '', - to: '' - }, - geo_distance: { - type: 'geo_distance', - distance: 10, - unit: 'km', - point: { - lon: 0, - lat: 0 - } - } - }, - // ### addFilter(filter) - // - // Add a new filter specified by the filter hash and append to the list of filters - // - // @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates - addFilter: function(filter) { - // crude deep copy - var ourfilter = JSON.parse(JSON.stringify(filter)); - // not fully specified so use template and over-write - if (_.keys(filter).length <= 3) { - ourfilter = _.defaults(ourfilter, this._filterTemplates[filter.type]); - } - var filters = this.get('filters'); - filters.push(ourfilter); - this.trigger('change:filters:new-blank'); - }, - replaceFilter: function(filter) { - // delete filter on the same field, then add - var filters = this.get('filters'); - var idx = -1; - _.each(this.get('filters'), function(f, key, list) { - if (filter.field == f.field) { - idx = key; - } - }); - // trigger just one event (change:filters:new-blank) instead of one for remove and - // one for add - if (idx >= 0) { - filters.splice(idx, 1); - this.set({filters: filters}); - } - this.addFilter(filter); - }, - updateFilter: function(index, value) { - }, - // ### removeFilter - // - // Remove a filter from filters at index filterIndex - removeFilter: function(filterIndex) { - var filters = this.get('filters'); - filters.splice(filterIndex, 1); - this.set({filters: filters}); - this.trigger('change'); - }, - // ### addFacet - // - // Add a Facet to this query - // - // See - addFacet: function(fieldId, size, silent) { - var facets = this.get('facets'); - // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) - if (_.contains(_.keys(facets), fieldId)) { - return; - } - facets[fieldId] = { - terms: { field: fieldId } - }; - if (!_.isUndefined(size)) { - facets[fieldId].terms.size = size; - } - this.set({facets: facets}, {silent: true}); - if (!silent) { - this.trigger('facet:add', this); - } - }, - addHistogramFacet: function(fieldId) { - var facets = this.get('facets'); - facets[fieldId] = { - date_histogram: { - field: fieldId, - interval: 'day' - } - }; - this.set({facets: facets}, {silent: true}); - this.trigger('facet:add', this); - }, - removeFacet: function(fieldId) { - var facets = this.get('facets'); - // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) - if (!_.contains(_.keys(facets), fieldId)) { - return; - } - delete facets[fieldId]; - this.set({facets: facets}, {silent: true}); - this.trigger('facet:remove', this); - }, - clearFacets: function() { - var facets = this.get('facets'); - _.each(_.keys(facets), function(fieldId) { - delete facets[fieldId]; - }); - this.trigger('facet:remove', this); - }, - // trigger a facet add; use this to trigger a single event after adding - // multiple facets - refreshFacets: function() { - this.trigger('facet:add', this); - } - -}); - - -// ## A Facet (Result) -my.Facet = Backbone.Model.extend({ - constructor: function Facet() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - defaults: function() { - return { - _type: 'terms', - total: 0, - other: 0, - missing: 0, - terms: [] - }; - } -}); - -// ## A Collection/List of Facets -my.FacetList = Backbone.Collection.extend({ - constructor: function FacetList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Facet -}); - -// ## Object State -// -// Convenience Backbone model for storing (configuration) state of objects like Views. -my.ObjectState = Backbone.Model.extend({ -}); - - -// ## Backbone.sync -// -// Override Backbone.sync to hand off to sync function in relevant backend -// Backbone.sync = function(method, model, options) { -// return model.backend.sync(method, model, options); -// }; - -}(this.recline.Model)); - -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.Memory = this.recline.Backend.Memory || {}; - -(function(my) { - "use strict"; - my.__type__ = 'memory'; - - // private data - use either jQuery or Underscore Deferred depending on what is available - var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - - // ## Data Wrapper - // - // Turn a simple array of JS objects into a mini data-store with - // functionality like querying, faceting, updating (by ID) and deleting (by - // ID). - // - // @param records list of hashes for each record/row in the data ({key: - // value, key: value}) - // @param fields (optional) list of field hashes (each hash defining a field - // as per recline.Model.Field). If fields not specified they will be taken - // from the data. - my.Store = function(records, fields) { - var self = this; - this.records = records; - // backwards compatability (in v0.5 records was named data) - this.data = this.records; - if (fields) { - this.fields = fields; - } else { - if (records) { - this.fields = _.map(records[0], function(value, key) { - return {id: key, type: 'string'}; - }); - } - } - - this.update = function(doc) { - _.each(self.records, function(internalDoc, idx) { - if(doc.id === internalDoc.id) { - self.records[idx] = doc; - } - }); - }; - - this.remove = function(doc) { - var newdocs = _.reject(self.records, function(internalDoc) { - return (doc.id === internalDoc.id); - }); - this.records = newdocs; - }; - - this.save = function(changes, dataset) { - var self = this; - var dfd = new Deferred(); - // TODO _.each(changes.creates) { ... } - _.each(changes.updates, function(record) { - self.update(record); - }); - _.each(changes.deletes, function(record) { - self.remove(record); - }); - dfd.resolve(); - return dfd.promise(); - }, - - this.query = function(queryObj) { - var dfd = new Deferred(); - var numRows = queryObj.size || this.records.length; - var start = queryObj.from || 0; - var results = this.records; - - results = this._applyFilters(results, queryObj); - results = this._applyFreeTextQuery(results, queryObj); - - // TODO: this is not complete sorting! - // What's wrong is we sort on the *last* entry in the sort list if there are multiple sort criteria - _.each(queryObj.sort, function(sortObj) { - var fieldName = sortObj.field; - results = _.sortBy(results, function(doc) { - var _out = doc[fieldName]; - return _out; - }); - if (sortObj.order == 'desc') { - results.reverse(); - } - }); - var facets = this.computeFacets(results, queryObj); - var out = { - total: results.length, - hits: results.slice(start, start+numRows), - facets: facets - }; - dfd.resolve(out); - return dfd.promise(); - }; - - // in place filtering - this._applyFilters = function(results, queryObj) { - var filters = queryObj.filters; - // register filters - var filterFunctions = { - term : term, - terms : terms, - range : range, - geo_distance : geo_distance - }; - var dataParsers = { - integer: function (e) { return parseFloat(e, 10); }, - 'float': function (e) { return parseFloat(e, 10); }, - number: function (e) { return parseFloat(e, 10); }, - string : function (e) { return e.toString(); }, - date : function (e) { return moment(e).valueOf(); }, - datetime : function (e) { return new Date(e).valueOf(); } - }; - var keyedFields = {}; - _.each(self.fields, function(field) { - keyedFields[field.id] = field; - }); - function getDataParser(filter) { - var fieldType = keyedFields[filter.field].type || 'string'; - return dataParsers[fieldType]; - } - - // filter records - return _.filter(results, function (record) { - var passes = _.map(filters, function (filter) { - return filterFunctions[filter.type](record, filter); - }); - - // return only these records that pass all filters - return _.all(passes, _.identity); - }); - - // filters definitions - function term(record, filter) { - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var term = parse(filter.term); - - return (value === term); - } - - function terms(record, filter) { - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var terms = parse(filter.terms).split(","); - - return (_.indexOf(terms, value) >= 0); - } - - function range(record, filter) { - var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === ''); - var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === ''); - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var from = parse(fromnull ? '' : filter.from); - var to = parse(tonull ? '' : filter.to); - - // if at least one end of range is set do not allow '' to get through - // note that for strings '' <= {any-character} e.g. '' <= 'a' - if ((!fromnull || !tonull) && value === '') { - return false; - } - return ((fromnull || value >= from) && (tonull || value <= to)); - } - - function geo_distance() { - // TODO code here - } - }; - - // we OR across fields but AND across terms in query string - this._applyFreeTextQuery = function(results, queryObj) { - if (queryObj.q) { - var terms = queryObj.q.split(' '); - var patterns=_.map(terms, function(term) { - return new RegExp(term.toLowerCase()); - }); - results = _.filter(results, function(rawdoc) { - var matches = true; - _.each(patterns, function(pattern) { - var foundmatch = false; - _.each(self.fields, function(field) { - var value = rawdoc[field.id]; - if ((value !== null) && (value !== undefined)) { - value = value.toString(); - } else { - // value can be null (apparently in some cases) - value = ''; - } - // TODO regexes? - foundmatch = foundmatch || (pattern.test(value.toLowerCase())); - // TODO: early out (once we are true should break to spare unnecessary testing) - // if (foundmatch) return true; - }); - matches = matches && foundmatch; - // TODO: early out (once false should break to spare unnecessary testing) - // if (!matches) return false; - }); - return matches; - }); - } - return results; - }; - - this.computeFacets = function(records, queryObj) { - var facetResults = {}; - if (!queryObj.facets) { - return facetResults; - } - _.each(queryObj.facets, function(query, facetId) { - // TODO: remove dependency on recline.Model - facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON(); - facetResults[facetId].termsall = {}; - }); - // faceting - _.each(records, function(doc) { - _.each(queryObj.facets, function(query, facetId) { - var fieldId = query.terms.field; - var val = doc[fieldId]; - var tmp = facetResults[facetId]; - if (val) { - tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1; - } else { - tmp.missing = tmp.missing + 1; - } - }); - }); - _.each(queryObj.facets, function(query, facetId) { - var tmp = facetResults[facetId]; - var terms = _.map(tmp.termsall, function(count, term) { - return { term: term, count: count }; - }); - tmp.terms = _.sortBy(terms, function(item) { - // want descending order - return -item.count; - }); - tmp.terms = tmp.terms.slice(0, 10); - }); - return facetResults; - }; - }; - -}(this.recline.Backend.Memory)); diff --git a/dist/recline.dataset.min.js b/dist/recline.dataset.min.js deleted file mode 100644 index 8ca94447..00000000 --- a/dist/recline.dataset.min.js +++ /dev/null @@ -1 +0,0 @@ -this.recline=this.recline||{};this.recline.Model=this.recline.Model||{};(function(my){"use strict";var Deferred=typeof jQuery!=="undefined"&&jQuery.Deferred||_.Deferred;my.Dataset=Backbone.Model.extend({constructor:function Dataset(){Backbone.Model.prototype.constructor.apply(this,arguments)},initialize:function(){var self=this;_.bindAll(this,"query");this.backend=null;if(this.get("backend")){this.backend=this._backendFromString(this.get("backend"))}else{if(this.get("records")){this.backend=recline.Backend.Memory}}this.fields=new my.FieldList;this.records=new my.RecordList;this._changes={deletes:[],updates:[],creates:[]};this.facets=new my.FacetList;this.recordCount=null;this.queryState=new my.Query;this.queryState.bind("change facet:add",function(){self.query()});this._store=this.backend;this._handleResult=this.backend!=null&&_.has(this.backend,"handleQueryResult")?this.backend.handleQueryResult:this._handleQueryResult;if(this.backend==recline.Backend.Memory){this.fetch()}},sync:function(method,model,options){return this.backend.sync(method,model,options)},fetch:function(){var self=this;var dfd=new Deferred;if(this.backend!==recline.Backend.Memory){this.backend.fetch(this.toJSON()).done(handleResults).fail(function(args){dfd.reject(args)})}else{handleResults({records:this.get("records"),fields:this.get("fields"),useMemoryStore:true})}function handleResults(results){var fields=self.get("fields")||results.fields;var out=self._normalizeRecordsAndFields(results.records,fields);if(results.useMemoryStore){self._store=new recline.Backend.Memory.Store(out.records,out.fields)}self.set(results.metadata);self.fields.reset(out.fields);self.query().done(function(){dfd.resolve(self)}).fail(function(args){dfd.reject(args)})}return dfd.promise()},_normalizeRecordsAndFields:function(records,fields){if(!fields&&records&&records.length>0){if(records[0]instanceof Array){fields=records[0];records=records.slice(1)}else{fields=_.map(_.keys(records[0]),function(key){return{id:key}})}}if(fields&&fields.length>0&&(fields[0]===null||typeof fields[0]!="object")){var seen={};fields=_.map(fields,function(field,index){if(field===null){field=""}else{field=field.toString()}var fieldId=field.replace(/^\s+|\s+$/g,"");if(fieldId===""){fieldId="_noname_";field=fieldId}while(fieldId in seen){seen[field]+=1;fieldId=field+seen[field]}if(!(field in seen)){seen[field]=0}return{id:fieldId}})}if(records&&records.length>0&&records[0]instanceof Array){records=_.map(records,function(doc){var tmp={};_.each(fields,function(field,idx){tmp[field.id]=doc[idx]});return tmp})}return{fields:fields,records:records}},save:function(){var self=this;return this._store.save(this._changes,this.toJSON())},query:function(queryObj){var self=this;var dfd=new Deferred;this.trigger("query:start");if(queryObj){var attributes=queryObj;if(queryObj instanceof my.Query){attributes=queryObj.toJSON()}this.queryState.set(attributes,{silent:true})}var actualQuery=this.queryState.toJSON();this._store.query(actualQuery,this.toJSON()).done(function(queryResult){self._handleResult(queryResult);self.trigger("query:done");dfd.resolve(self.records)}).fail(function(args){self.trigger("query:fail",args);dfd.reject(args)});return dfd.promise()},_handleQueryResult:function(queryResult){var self=this;self.recordCount=queryResult.total;var docs=_.map(queryResult.hits,function(hit){var _doc=new my.Record(hit);_doc.fields=self.fields;_doc.bind("change",function(doc){self._changes.updates.push(doc.toJSON())});_doc.bind("destroy",function(doc){self._changes.deletes.push(doc.toJSON())});return _doc});self.records.reset(docs);if(queryResult.facets){var facets=_.map(queryResult.facets,function(facetResult,facetId){facetResult.id=facetId;return new my.Facet(facetResult)});self.facets.reset(facets)}},toTemplateJSON:function(){var data=this.toJSON();data.recordCount=this.recordCount;data.fields=this.fields.toJSON();return data},getFieldsSummary:function(){var self=this;var query=new my.Query;query.set({size:0});this.fields.each(function(field){query.addFacet(field.id)});var dfd=new Deferred;this._store.query(query.toJSON(),this.toJSON()).done(function(queryResult){if(queryResult.facets){_.each(queryResult.facets,function(facetResult,facetId){facetResult.id=facetId;var facet=new my.Facet(facetResult);self.fields.get(facetId).facets.reset(facet)})}dfd.resolve(queryResult)});return dfd.promise()},recordSummary:function(record){return record.summary()},_backendFromString:function(backendString){var backend=null;if(recline&&recline.Backend){_.each(_.keys(recline.Backend),function(name){if(name.toLowerCase()===backendString.toLowerCase()){backend=recline.Backend[name]}})}return backend}});my.Record=Backbone.Model.extend({constructor:function Record(){Backbone.Model.prototype.constructor.apply(this,arguments)},initialize:function(){_.bindAll(this,"getFieldValue")},getFieldValue:function(field){var val=this.getFieldValueUnrendered(field);if(field&&!_.isUndefined(field.renderer)){val=field.renderer(val,field,this.toJSON())}return val},getFieldValueUnrendered:function(field){if(!field){return""}var val=this.get(field.id);if(field.deriver){val=field.deriver(val,field,this)}return val},summary:function(record){var self=this;var html='
';this.fields.each(function(field){if(field.id!="id"){html+='
'+field.get("label")+": "+self.getFieldValue(field)+"
"}});html+="
";return html},fetch:function(){},save:function(){},destroy:function(){this.trigger("destroy",this)}});my.RecordList=Backbone.Collection.extend({constructor:function RecordList(){Backbone.Collection.prototype.constructor.apply(this,arguments)},model:my.Record});my.Field=Backbone.Model.extend({constructor:function Field(){Backbone.Model.prototype.constructor.apply(this,arguments)},defaults:{label:null,type:"string",format:null,is_derived:false},initialize:function(data,options){if("0"in data){throw new Error("Looks like you did not pass a proper hash with id to Field constructor")}if(this.attributes.label===null){this.set({label:this.id})}if(this.attributes.type.toLowerCase()in this._typeMap){this.attributes.type=this._typeMap[this.attributes.type.toLowerCase()]}if(options){this.renderer=options.renderer;this.deriver=options.deriver}if(!this.renderer){this.renderer=this.defaultRenderers[this.get("type")]}this.facets=new my.FacetList},_typeMap:{text:"string","double":"number","float":"number",numeric:"number","int":"integer",datetime:"date-time",bool:"boolean",timestamp:"date-time",json:"object"},defaultRenderers:{object:function(val,field,doc){return JSON.stringify(val)},geo_point:function(val,field,doc){return JSON.stringify(val)},number:function(val,field,doc){var format=field.get("format");if(format==="percentage"){return val+"%"}return val},string:function(val,field,doc){var format=field.get("format");if(format==="markdown"){if(typeof Showdown!=="undefined"){var showdown=new Showdown.converter;out=showdown.makeHtml(val);return out}else{return val}}else if(format=="plain"){return val}else{if(val&&typeof val==="string"){val=val.replace(/(https?:\/\/[^ ]+)/g,'$1')}return val}}}});my.FieldList=Backbone.Collection.extend({constructor:function FieldList(){Backbone.Collection.prototype.constructor.apply(this,arguments)},model:my.Field});my.Query=Backbone.Model.extend({constructor:function Query(){Backbone.Model.prototype.constructor.apply(this,arguments)},defaults:function(){return{size:100,from:0,q:"",facets:{},filters:[]}},_filterTemplates:{term:{type:"term",field:"",term:""},range:{type:"range",from:"",to:""},geo_distance:{type:"geo_distance",distance:10,unit:"km",point:{lon:0,lat:0}}},addFilter:function(filter){var ourfilter=JSON.parse(JSON.stringify(filter));if(_.keys(filter).length<=3){ourfilter=_.defaults(ourfilter,this._filterTemplates[filter.type])}var filters=this.get("filters");filters.push(ourfilter);this.trigger("change:filters:new-blank")},replaceFilter:function(filter){var filters=this.get("filters");var idx=-1;_.each(this.get("filters"),function(f,key,list){if(filter.field==f.field){idx=key}});if(idx>=0){filters.splice(idx,1);this.set({filters:filters})}this.addFilter(filter)},updateFilter:function(index,value){},removeFilter:function(filterIndex){var filters=this.get("filters");filters.splice(filterIndex,1);this.set({filters:filters});this.trigger("change")},addFacet:function(fieldId,size,silent){var facets=this.get("facets");if(_.contains(_.keys(facets),fieldId)){return}facets[fieldId]={terms:{field:fieldId}};if(!_.isUndefined(size)){facets[fieldId].terms.size=size}this.set({facets:facets},{silent:true});if(!silent){this.trigger("facet:add",this)}},addHistogramFacet:function(fieldId){var facets=this.get("facets");facets[fieldId]={date_histogram:{field:fieldId,interval:"day"}};this.set({facets:facets},{silent:true});this.trigger("facet:add",this)},removeFacet:function(fieldId){var facets=this.get("facets");if(!_.contains(_.keys(facets),fieldId)){return}delete facets[fieldId];this.set({facets:facets},{silent:true});this.trigger("facet:remove",this)},clearFacets:function(){var facets=this.get("facets");_.each(_.keys(facets),function(fieldId){delete facets[fieldId]});this.trigger("facet:remove",this)},refreshFacets:function(){this.trigger("facet:add",this)}});my.Facet=Backbone.Model.extend({constructor:function Facet(){Backbone.Model.prototype.constructor.apply(this,arguments)},defaults:function(){return{_type:"terms",total:0,other:0,missing:0,terms:[]}}});my.FacetList=Backbone.Collection.extend({constructor:function FacetList(){Backbone.Collection.prototype.constructor.apply(this,arguments)},model:my.Facet});my.ObjectState=Backbone.Model.extend({})})(this.recline.Model);this.recline=this.recline||{};this.recline.Backend=this.recline.Backend||{};this.recline.Backend.Memory=this.recline.Backend.Memory||{};(function(my){"use strict";my.__type__="memory";var Deferred=typeof jQuery!=="undefined"&&jQuery.Deferred||_.Deferred;my.Store=function(records,fields){var self=this;this.records=records;this.data=this.records;if(fields){this.fields=fields}else{if(records){this.fields=_.map(records[0],function(value,key){return{id:key,type:"string"}})}}this.update=function(doc){_.each(self.records,function(internalDoc,idx){if(doc.id===internalDoc.id){self.records[idx]=doc}})};this.remove=function(doc){var newdocs=_.reject(self.records,function(internalDoc){return doc.id===internalDoc.id});this.records=newdocs};this.save=function(changes,dataset){var self=this;var dfd=new Deferred;_.each(changes.updates,function(record){self.update(record)});_.each(changes.deletes,function(record){self.remove(record)});dfd.resolve();return dfd.promise()},this.query=function(queryObj){var dfd=new Deferred;var numRows=queryObj.size||this.records.length;var start=queryObj.from||0;var results=this.records;results=this._applyFilters(results,queryObj);results=this._applyFreeTextQuery(results,queryObj);_.each(queryObj.sort,function(sortObj){var fieldName=sortObj.field;results=_.sortBy(results,function(doc){var _out=doc[fieldName];return _out});if(sortObj.order=="desc"){results.reverse()}});var facets=this.computeFacets(results,queryObj);var out={total:results.length,hits:results.slice(start,start+numRows),facets:facets};dfd.resolve(out);return dfd.promise()};this._applyFilters=function(results,queryObj){var filters=queryObj.filters;var filterFunctions={term:term,terms:terms,range:range,geo_distance:geo_distance};var dataParsers={integer:function(e){return parseFloat(e,10)},"float":function(e){return parseFloat(e,10)},number:function(e){return parseFloat(e,10)},string:function(e){return e.toString()},date:function(e){return moment(e).valueOf()},datetime:function(e){return new Date(e).valueOf()}};var keyedFields={};_.each(self.fields,function(field){keyedFields[field.id]=field});function getDataParser(filter){var fieldType=keyedFields[filter.field].type||"string";return dataParsers[fieldType]}return _.filter(results,function(record){var passes=_.map(filters,function(filter){return filterFunctions[filter.type](record,filter)});return _.all(passes,_.identity)});function term(record,filter){var parse=getDataParser(filter);var value=parse(record[filter.field]);var term=parse(filter.term);return value===term}function terms(record,filter){var parse=getDataParser(filter);var value=parse(record[filter.field]);var terms=parse(filter.terms).split(",");return _.indexOf(terms,value)>=0}function range(record,filter){var fromnull=_.isUndefined(filter.from)||filter.from===null||filter.from==="";var tonull=_.isUndefined(filter.to)||filter.to===null||filter.to==="";var parse=getDataParser(filter);var value=parse(record[filter.field]);var from=parse(fromnull?"":filter.from);var to=parse(tonull?"":filter.to);if((!fromnull||!tonull)&&value===""){return false}return(fromnull||value>=from)&&(tonull||value<=to)}function geo_distance(){}};this._applyFreeTextQuery=function(results,queryObj){if(queryObj.q){var terms=queryObj.q.split(" ");var patterns=_.map(terms,function(term){return new RegExp(term.toLowerCase())});results=_.filter(results,function(rawdoc){var matches=true;_.each(patterns,function(pattern){var foundmatch=false;_.each(self.fields,function(field){var value=rawdoc[field.id];if(value!==null&&value!==undefined){value=value.toString()}else{value=""}foundmatch=foundmatch||pattern.test(value.toLowerCase())});matches=matches&&foundmatch});return matches})}return results};this.computeFacets=function(records,queryObj){var facetResults={};if(!queryObj.facets){return facetResults}_.each(queryObj.facets,function(query,facetId){facetResults[facetId]=new recline.Model.Facet({id:facetId}).toJSON();facetResults[facetId].termsall={}});_.each(records,function(doc){_.each(queryObj.facets,function(query,facetId){var fieldId=query.terms.field;var val=doc[fieldId];var tmp=facetResults[facetId];if(val){tmp.termsall[val]=tmp.termsall[val]?tmp.termsall[val]+1:1}else{tmp.missing=tmp.missing+1}})});_.each(queryObj.facets,function(query,facetId){var tmp=facetResults[facetId];var terms=_.map(tmp.termsall,function(count,term){return{term:term,count:count}});tmp.terms=_.sortBy(terms,function(item){return-item.count});tmp.terms=tmp.terms.slice(0,10)});return facetResults}}})(this.recline.Backend.Memory); \ No newline at end of file diff --git a/dist/recline.js b/dist/recline.js deleted file mode 100644 index 7a873345..00000000 --- a/dist/recline.js +++ /dev/null @@ -1,4455 +0,0 @@ -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; - -(function(my) { - "use strict"; - my.__type__ = 'dataproxy'; - // URL for the dataproxy - my.dataproxy_url = '//jsonpdataproxy.appspot.com'; - // Timeout for dataproxy (after this time if no response we error) - // Needed because use JSONP so do not receive e.g. 500 errors - my.timeout = 5000; - - - // use either jQuery or Underscore Deferred depending on what is available - var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - - // ## load - // - // Load data from a URL via the [DataProxy](http://github.com/okfn/dataproxy). - // - // Returns array of field names and array of arrays for records - my.fetch = function(dataset) { - var data = { - url: dataset.url, - 'max-results': dataset.size || dataset.rows || 1000, - type: dataset.format || '' - }; - var jqxhr = jQuery.ajax({ - url: my.dataproxy_url, - data: data, - dataType: 'jsonp' - }); - var dfd = new Deferred(); - _wrapInTimeout(jqxhr).done(function(results) { - if (results.error) { - dfd.reject(results.error); - } - - dfd.resolve({ - records: results.data, - fields: results.fields, - useMemoryStore: true - }); - }) - .fail(function(args) { - dfd.reject(args); - }); - return dfd.promise(); - }; - - // ## _wrapInTimeout - // - // Convenience method providing a crude way to catch backend errors on JSONP calls. - // Many of backends use JSONP and so will not get error messages and this is - // a crude way to catch those errors. - var _wrapInTimeout = function(ourFunction) { - var dfd = new Deferred(); - var timer = setTimeout(function() { - dfd.reject({ - message: 'Request Error: Backend did not respond after ' + (my.timeout / 1000) + ' seconds' - }); - }, my.timeout); - ourFunction.done(function(args) { - clearTimeout(timer); - dfd.resolve(args); - }) - .fail(function(args) { - clearTimeout(timer); - dfd.reject(args); - }) - ; - return dfd.promise(); - }; - -}(this.recline.Backend.DataProxy)); -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.Memory = this.recline.Backend.Memory || {}; - -(function(my) { - "use strict"; - my.__type__ = 'memory'; - - // private data - use either jQuery or Underscore Deferred depending on what is available - var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - - // ## Data Wrapper - // - // Turn a simple array of JS objects into a mini data-store with - // functionality like querying, faceting, updating (by ID) and deleting (by - // ID). - // - // @param records list of hashes for each record/row in the data ({key: - // value, key: value}) - // @param fields (optional) list of field hashes (each hash defining a field - // as per recline.Model.Field). If fields not specified they will be taken - // from the data. - my.Store = function(records, fields) { - var self = this; - this.records = records; - // backwards compatability (in v0.5 records was named data) - this.data = this.records; - if (fields) { - this.fields = fields; - } else { - if (records) { - this.fields = _.map(records[0], function(value, key) { - return {id: key, type: 'string'}; - }); - } - } - - this.update = function(doc) { - _.each(self.records, function(internalDoc, idx) { - if(doc.id === internalDoc.id) { - self.records[idx] = doc; - } - }); - }; - - this.remove = function(doc) { - var newdocs = _.reject(self.records, function(internalDoc) { - return (doc.id === internalDoc.id); - }); - this.records = newdocs; - }; - - this.save = function(changes, dataset) { - var self = this; - var dfd = new Deferred(); - // TODO _.each(changes.creates) { ... } - _.each(changes.updates, function(record) { - self.update(record); - }); - _.each(changes.deletes, function(record) { - self.remove(record); - }); - dfd.resolve(); - return dfd.promise(); - }, - - this.query = function(queryObj) { - var dfd = new Deferred(); - var numRows = queryObj.size || this.records.length; - var start = queryObj.from || 0; - var results = this.records; - - results = this._applyFilters(results, queryObj); - results = this._applyFreeTextQuery(results, queryObj); - - // TODO: this is not complete sorting! - // What's wrong is we sort on the *last* entry in the sort list if there are multiple sort criteria - _.each(queryObj.sort, function(sortObj) { - var fieldName = sortObj.field; - results = _.sortBy(results, function(doc) { - var _out = doc[fieldName]; - return _out; - }); - if (sortObj.order == 'desc') { - results.reverse(); - } - }); - var facets = this.computeFacets(results, queryObj); - var out = { - total: results.length, - hits: results.slice(start, start+numRows), - facets: facets - }; - dfd.resolve(out); - return dfd.promise(); - }; - - // in place filtering - this._applyFilters = function(results, queryObj) { - var filters = queryObj.filters; - // register filters - var filterFunctions = { - term : term, - terms : terms, - range : range, - geo_distance : geo_distance - }; - var dataParsers = { - integer: function (e) { return parseFloat(e, 10); }, - 'float': function (e) { return parseFloat(e, 10); }, - number: function (e) { return parseFloat(e, 10); }, - string : function (e) { return e.toString(); }, - date : function (e) { return moment(e).valueOf(); }, - datetime : function (e) { return new Date(e).valueOf(); } - }; - var keyedFields = {}; - _.each(self.fields, function(field) { - keyedFields[field.id] = field; - }); - function getDataParser(filter) { - var fieldType = keyedFields[filter.field].type || 'string'; - return dataParsers[fieldType]; - } - - // filter records - return _.filter(results, function (record) { - var passes = _.map(filters, function (filter) { - return filterFunctions[filter.type](record, filter); - }); - - // return only these records that pass all filters - return _.all(passes, _.identity); - }); - - // filters definitions - function term(record, filter) { - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var term = parse(filter.term); - - return (value === term); - } - - function terms(record, filter) { - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var terms = parse(filter.terms).split(","); - - return (_.indexOf(terms, value) >= 0); - } - - function range(record, filter) { - var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === ''); - var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === ''); - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var from = parse(fromnull ? '' : filter.from); - var to = parse(tonull ? '' : filter.to); - - // if at least one end of range is set do not allow '' to get through - // note that for strings '' <= {any-character} e.g. '' <= 'a' - if ((!fromnull || !tonull) && value === '') { - return false; - } - return ((fromnull || value >= from) && (tonull || value <= to)); - } - - function geo_distance() { - // TODO code here - } - }; - - // we OR across fields but AND across terms in query string - this._applyFreeTextQuery = function(results, queryObj) { - if (queryObj.q) { - var terms = queryObj.q.split(' '); - var patterns=_.map(terms, function(term) { - return new RegExp(term.toLowerCase()); - }); - results = _.filter(results, function(rawdoc) { - var matches = true; - _.each(patterns, function(pattern) { - var foundmatch = false; - _.each(self.fields, function(field) { - var value = rawdoc[field.id]; - if ((value !== null) && (value !== undefined)) { - value = value.toString(); - } else { - // value can be null (apparently in some cases) - value = ''; - } - // TODO regexes? - foundmatch = foundmatch || (pattern.test(value.toLowerCase())); - // TODO: early out (once we are true should break to spare unnecessary testing) - // if (foundmatch) return true; - }); - matches = matches && foundmatch; - // TODO: early out (once false should break to spare unnecessary testing) - // if (!matches) return false; - }); - return matches; - }); - } - return results; - }; - - this.computeFacets = function(records, queryObj) { - var facetResults = {}; - if (!queryObj.facets) { - return facetResults; - } - _.each(queryObj.facets, function(query, facetId) { - // TODO: remove dependency on recline.Model - facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON(); - facetResults[facetId].termsall = {}; - }); - // faceting - _.each(records, function(doc) { - _.each(queryObj.facets, function(query, facetId) { - var fieldId = query.terms.field; - var val = doc[fieldId]; - var tmp = facetResults[facetId]; - if (val) { - tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1; - } else { - tmp.missing = tmp.missing + 1; - } - }); - }); - _.each(queryObj.facets, function(query, facetId) { - var tmp = facetResults[facetId]; - var terms = _.map(tmp.termsall, function(count, term) { - return { term: term, count: count }; - }); - tmp.terms = _.sortBy(terms, function(item) { - // want descending order - return -item.count; - }); - tmp.terms = tmp.terms.slice(0, 10); - }); - return facetResults; - }; - }; - -}(this.recline.Backend.Memory)); -// This file adds in full array method support in browsers that don't support it -// see: http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc - -// Add ECMA262-5 Array methods if not supported natively -if (!('indexOf' in Array.prototype)) { - Array.prototype.indexOf= function(find, i /*opt*/) { - if (i===undefined) i= 0; - if (i<0) i+= this.length; - if (i<0) i= 0; - for (var n= this.length; ithis.length-1) i= this.length-1; - for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */ - if (i in this && this[i]===find) - return i; - return -1; - }; -} -if (!('forEach' in Array.prototype)) { - Array.prototype.forEach= function(action, that /*opt*/) { - for (var i= 0, n= this.length; iDataset -my.Dataset = Backbone.Model.extend({ - constructor: function Dataset() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // ### initialize - initialize: function() { - var self = this; - _.bindAll(this, 'query'); - this.backend = null; - if (this.get('backend')) { - this.backend = this._backendFromString(this.get('backend')); - } else { // try to guess backend ... - if (this.get('records')) { - this.backend = recline.Backend.Memory; - } - } - this.fields = new my.FieldList(); - this.records = new my.RecordList(); - this._changes = { - deletes: [], - updates: [], - creates: [] - }; - this.facets = new my.FacetList(); - this.recordCount = null; - this.queryState = new my.Query(); - this.queryState.bind('change facet:add', function () { - self.query(); // We want to call query() without any arguments. - }); - // store is what we query and save against - // store will either be the backend or be a memory store if Backend fetch - // tells us to use memory store - this._store = this.backend; - - // if backend has a handleQueryResultFunction, use that - this._handleResult = (this.backend != null && _.has(this.backend, 'handleQueryResult')) ? - this.backend.handleQueryResult : this._handleQueryResult; - if (this.backend == recline.Backend.Memory) { - this.fetch(); - } - }, - - sync: function(method, model, options) { - return this.backend.sync(method, model, options); - }, - - // ### fetch - // - // Retrieve dataset and (some) records from the backend. - fetch: function() { - var self = this; - var dfd = new Deferred(); - - if (this.backend !== recline.Backend.Memory) { - this.backend.fetch(this.toJSON()) - .done(handleResults) - .fail(function(args) { - dfd.reject(args); - }); - } else { - // special case where we have been given data directly - handleResults({ - records: this.get('records'), - fields: this.get('fields'), - useMemoryStore: true - }); - } - - function handleResults(results) { - // if explicitly given the fields - // (e.g. var dataset = new Dataset({fields: fields, ...}) - // use that field info over anything we get back by parsing the data - // (results.fields) - var fields = self.get('fields') || results.fields; - - var out = self._normalizeRecordsAndFields(results.records, fields); - if (results.useMemoryStore) { - self._store = new recline.Backend.Memory.Store(out.records, out.fields); - } - - self.set(results.metadata); - self.fields.reset(out.fields); - self.query() - .done(function() { - dfd.resolve(self); - }) - .fail(function(args) { - dfd.reject(args); - }); - } - - return dfd.promise(); - }, - - // ### _normalizeRecordsAndFields - // - // Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects - // - // e.g. fields = ['a', 'b', 'c'] and records = [ [1,2,3] ] => - // fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}] - _normalizeRecordsAndFields: function(records, fields) { - // if no fields get them from records - if (!fields && records && records.length > 0) { - // records is array then fields is first row of records ... - if (records[0] instanceof Array) { - fields = records[0]; - records = records.slice(1); - } else { - fields = _.map(_.keys(records[0]), function(key) { - return {id: key}; - }); - } - } - - // fields is an array of strings (i.e. list of field headings/ids) - if (fields && fields.length > 0 && (fields[0] === null || typeof(fields[0]) != 'object')) { - // Rename duplicate fieldIds as each field name needs to be - // unique. - var seen = {}; - fields = _.map(fields, function(field, index) { - if (field === null) { - field = ''; - } else { - field = field.toString(); - } - // cannot use trim as not supported by IE7 - var fieldId = field.replace(/^\s+|\s+$/g, ''); - if (fieldId === '') { - fieldId = '_noname_'; - field = fieldId; - } - while (fieldId in seen) { - seen[field] += 1; - fieldId = field + seen[field]; - } - if (!(field in seen)) { - seen[field] = 0; - } - // TODO: decide whether to keep original name as label ... - // return { id: fieldId, label: field || fieldId } - return { id: fieldId }; - }); - } - // records is provided as arrays so need to zip together with fields - // NB: this requires you to have fields to match arrays - if (records && records.length > 0 && records[0] instanceof Array) { - records = _.map(records, function(doc) { - var tmp = {}; - _.each(fields, function(field, idx) { - tmp[field.id] = doc[idx]; - }); - return tmp; - }); - } - return { - fields: fields, - records: records - }; - }, - - save: function() { - var self = this; - // TODO: need to reset the changes ... - return this._store.save(this._changes, this.toJSON()); - }, - - // ### query - // - // AJAX method with promise API to get records from the backend. - // - // It will query based on current query state (given by this.queryState) - // updated by queryObj (if provided). - // - // Resulting RecordList are used to reset this.records and are - // also returned. - query: function(queryObj) { - var self = this; - var dfd = new Deferred(); - this.trigger('query:start'); - - if (queryObj) { - var attributes = queryObj; - if (queryObj instanceof my.Query) { - attributes = queryObj.toJSON(); - } - this.queryState.set(attributes, {silent: true}); - } - var actualQuery = this.queryState.toJSON(); - - this._store.query(actualQuery, this.toJSON()) - .done(function(queryResult) { - self._handleResult(queryResult); - self.trigger('query:done'); - dfd.resolve(self.records); - }) - .fail(function(args) { - self.trigger('query:fail', args); - dfd.reject(args); - }); - return dfd.promise(); - }, - - _handleQueryResult: function(queryResult) { - var self = this; - self.recordCount = queryResult.total; - var docs = _.map(queryResult.hits, function(hit) { - var _doc = new my.Record(hit); - _doc.fields = self.fields; - _doc.bind('change', function(doc) { - self._changes.updates.push(doc.toJSON()); - }); - _doc.bind('destroy', function(doc) { - self._changes.deletes.push(doc.toJSON()); - }); - return _doc; - }); - self.records.reset(docs); - if (queryResult.facets) { - var facets = _.map(queryResult.facets, function(facetResult, facetId) { - facetResult.id = facetId; - return new my.Facet(facetResult); - }); - self.facets.reset(facets); - } - }, - - toTemplateJSON: function() { - var data = this.toJSON(); - data.recordCount = this.recordCount; - data.fields = this.fields.toJSON(); - return data; - }, - - // ### getFieldsSummary - // - // Get a summary for each field in the form of a `Facet`. - // - // @return null as this is async function. Provides deferred/promise interface. - getFieldsSummary: function() { - var self = this; - var query = new my.Query(); - query.set({size: 0}); - this.fields.each(function(field) { - query.addFacet(field.id); - }); - var dfd = new Deferred(); - this._store.query(query.toJSON(), this.toJSON()).done(function(queryResult) { - if (queryResult.facets) { - _.each(queryResult.facets, function(facetResult, facetId) { - facetResult.id = facetId; - var facet = new my.Facet(facetResult); - // TODO: probably want replace rather than reset (i.e. just replace the facet with this id) - self.fields.get(facetId).facets.reset(facet); - }); - } - dfd.resolve(queryResult); - }); - return dfd.promise(); - }, - - // Deprecated (as of v0.5) - use record.summary() - recordSummary: function(record) { - return record.summary(); - }, - - // ### _backendFromString(backendString) - // - // Look up a backend module from a backend string (look in recline.Backend) - _backendFromString: function(backendString) { - var backend = null; - if (recline && recline.Backend) { - _.each(_.keys(recline.Backend), function(name) { - if (name.toLowerCase() === backendString.toLowerCase()) { - backend = recline.Backend[name]; - } - }); - } - return backend; - } -}); - - -// ## A Record -// -// A single record (or row) in the dataset -my.Record = Backbone.Model.extend({ - constructor: function Record() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // ### initialize - // - // Create a Record - // - // You usually will not do this directly but will have records created by - // Dataset e.g. in query method - // - // Certain methods require presence of a fields attribute (identical to that on Dataset) - initialize: function() { - _.bindAll(this, 'getFieldValue'); - }, - - // ### getFieldValue - // - // For the provided Field get the corresponding rendered computed data value - // for this record. - // - // NB: if field is undefined a default '' value will be returned - getFieldValue: function(field) { - var val = this.getFieldValueUnrendered(field); - if (field && !_.isUndefined(field.renderer)) { - val = field.renderer(val, field, this.toJSON()); - } - return val; - }, - - // ### getFieldValueUnrendered - // - // For the provided Field get the corresponding computed data value - // for this record. - // - // NB: if field is undefined a default '' value will be returned - getFieldValueUnrendered: function(field) { - if (!field) { - return ''; - } - var val = this.get(field.id); - if (field.deriver) { - val = field.deriver(val, field, this); - } - return val; - }, - - // ### summary - // - // Get a simple html summary of this record in form of key/value list - summary: function(record) { - var self = this; - var html = '
'; - this.fields.each(function(field) { - if (field.id != 'id') { - html += '
' + field.get('label') + ': ' + self.getFieldValue(field) + '
'; - } - }); - html += '
'; - return html; - }, - - // Override Backbone save, fetch and destroy so they do nothing - // Instead, Dataset object that created this Record should take care of - // handling these changes (discovery will occur via event notifications) - // WARNING: these will not persist *unless* you call save on Dataset - fetch: function() {}, - save: function() {}, - destroy: function() { this.trigger('destroy', this); } -}); - - -// ## A Backbone collection of Records -my.RecordList = Backbone.Collection.extend({ - constructor: function RecordList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Record -}); - - -// ## A Field (aka Column) on a Dataset -my.Field = Backbone.Model.extend({ - constructor: function Field() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - // ### defaults - define default values - defaults: { - label: null, - type: 'string', - format: null, - is_derived: false - }, - // ### initialize - // - // @param {Object} data: standard Backbone model attributes - // - // @param {Object} options: renderer and/or deriver functions. - initialize: function(data, options) { - // if a hash not passed in the first argument throw error - if ('0' in data) { - throw new Error('Looks like you did not pass a proper hash with id to Field constructor'); - } - if (this.attributes.label === null) { - this.set({label: this.id}); - } - if (this.attributes.type.toLowerCase() in this._typeMap) { - this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()]; - } - if (options) { - this.renderer = options.renderer; - this.deriver = options.deriver; - } - if (!this.renderer) { - this.renderer = this.defaultRenderers[this.get('type')]; - } - this.facets = new my.FacetList(); - }, - _typeMap: { - 'text': 'string', - 'double': 'number', - 'float': 'number', - 'numeric': 'number', - 'int': 'integer', - 'datetime': 'date-time', - 'bool': 'boolean', - 'timestamp': 'date-time', - 'json': 'object' - }, - defaultRenderers: { - object: function(val, field, doc) { - return JSON.stringify(val); - }, - geo_point: function(val, field, doc) { - return JSON.stringify(val); - }, - 'number': function(val, field, doc) { - var format = field.get('format'); - if (format === 'percentage') { - return val + '%'; - } - return val; - }, - 'string': function(val, field, doc) { - var format = field.get('format'); - if (format === 'markdown') { - if (typeof Showdown !== 'undefined') { - var showdown = new Showdown.converter(); - out = showdown.makeHtml(val); - return out; - } else { - return val; - } - } else if (format == 'plain') { - return val; - } else { - // as this is the default and default type is string may get things - // here that are not actually strings - if (val && typeof val === 'string') { - val = val.replace(/(https?:\/\/[^ ]+)/g, '$1'); - } - return val; - } - } - } -}); - -my.FieldList = Backbone.Collection.extend({ - constructor: function FieldList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Field -}); - -// ## Query -my.Query = Backbone.Model.extend({ - constructor: function Query() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - defaults: function() { - return { - size: 100, - from: 0, - q: '', - facets: {}, - filters: [] - }; - }, - _filterTemplates: { - term: { - type: 'term', - // TODO do we need this attribute here? - field: '', - term: '' - }, - range: { - type: 'range', - from: '', - to: '' - }, - geo_distance: { - type: 'geo_distance', - distance: 10, - unit: 'km', - point: { - lon: 0, - lat: 0 - } - } - }, - // ### addFilter(filter) - // - // Add a new filter specified by the filter hash and append to the list of filters - // - // @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates - addFilter: function(filter) { - // crude deep copy - var ourfilter = JSON.parse(JSON.stringify(filter)); - // not fully specified so use template and over-write - if (_.keys(filter).length <= 3) { - ourfilter = _.defaults(ourfilter, this._filterTemplates[filter.type]); - } - var filters = this.get('filters'); - filters.push(ourfilter); - this.trigger('change:filters:new-blank'); - }, - replaceFilter: function(filter) { - // delete filter on the same field, then add - var filters = this.get('filters'); - var idx = -1; - _.each(this.get('filters'), function(f, key, list) { - if (filter.field == f.field) { - idx = key; - } - }); - // trigger just one event (change:filters:new-blank) instead of one for remove and - // one for add - if (idx >= 0) { - filters.splice(idx, 1); - this.set({filters: filters}); - } - this.addFilter(filter); - }, - updateFilter: function(index, value) { - }, - // ### removeFilter - // - // Remove a filter from filters at index filterIndex - removeFilter: function(filterIndex) { - var filters = this.get('filters'); - filters.splice(filterIndex, 1); - this.set({filters: filters}); - this.trigger('change'); - }, - // ### addFacet - // - // Add a Facet to this query - // - // See - addFacet: function(fieldId, size, silent) { - var facets = this.get('facets'); - // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) - if (_.contains(_.keys(facets), fieldId)) { - return; - } - facets[fieldId] = { - terms: { field: fieldId } - }; - if (!_.isUndefined(size)) { - facets[fieldId].terms.size = size; - } - this.set({facets: facets}, {silent: true}); - if (!silent) { - this.trigger('facet:add', this); - } - }, - addHistogramFacet: function(fieldId) { - var facets = this.get('facets'); - facets[fieldId] = { - date_histogram: { - field: fieldId, - interval: 'day' - } - }; - this.set({facets: facets}, {silent: true}); - this.trigger('facet:add', this); - }, - removeFacet: function(fieldId) { - var facets = this.get('facets'); - // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) - if (!_.contains(_.keys(facets), fieldId)) { - return; - } - delete facets[fieldId]; - this.set({facets: facets}, {silent: true}); - this.trigger('facet:remove', this); - }, - clearFacets: function() { - var facets = this.get('facets'); - _.each(_.keys(facets), function(fieldId) { - delete facets[fieldId]; - }); - this.trigger('facet:remove', this); - }, - // trigger a facet add; use this to trigger a single event after adding - // multiple facets - refreshFacets: function() { - this.trigger('facet:add', this); - } - -}); - - -// ## A Facet (Result) -my.Facet = Backbone.Model.extend({ - constructor: function Facet() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - defaults: function() { - return { - _type: 'terms', - total: 0, - other: 0, - missing: 0, - terms: [] - }; - } -}); - -// ## A Collection/List of Facets -my.FacetList = Backbone.Collection.extend({ - constructor: function FacetList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Facet -}); - -// ## Object State -// -// Convenience Backbone model for storing (configuration) state of objects like Views. -my.ObjectState = Backbone.Model.extend({ -}); - - -// ## Backbone.sync -// -// Override Backbone.sync to hand off to sync function in relevant backend -// Backbone.sync = function(method, model, options) { -// return model.backend.sync(method, model, options); -// }; - -}(this.recline.Model)); - -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## Graph view for a Dataset using Flot graphing library. -// -// Initialization arguments (in a hash in first parameter): -// -// * model: recline.Model.Dataset -// * state: (optional) configuration hash of form: -// -// { -// group: {column name for x-axis}, -// series: [{column name for series A}, {column name series B}, ... ], -// // options are: lines, points, lines-and-points, bars, columns -// graphType: 'lines', -// graphOptions: {custom [flot options]} -// } -// -// NB: should *not* provide an el argument to the view but must let the view -// generate the element itself (you can then append view.el to the DOM. -my.Flot = Backbone.View.extend({ - template: ' \ -
\ -
\ -
\ -

Hey there!

\ -

There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.

\ -

Please tell us by using the menu on the right and a graph will automatically appear.

\ -
\ -
\ -
\ -', - - initialize: function(options) { - var self = this; - this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; - - _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel'); - this.needToRedraw = false; - this.listenTo(this.model, 'change', this.render); - this.listenTo(this.model.fields, 'reset add', this.render); - this.listenTo(this.model.records, 'reset add', this.redraw); - var stateData = _.extend({ - group: null, - // so that at least one series chooser box shows up - series: [], - graphType: 'lines-and-points' - }, - options.state - ); - this.state = new recline.Model.ObjectState(stateData); - this.previousTooltipPoint = {x: null, y: null}; - this.editor = new my.FlotControls({ - model: this.model, - state: this.state.toJSON() - }); - this.listenTo(this.editor.state, 'change', function() { - self.state.set(self.editor.state.toJSON()); - self.redraw(); - }); - this.elSidebar = this.editor.$el; - }, - - render: function() { - var self = this; - var tmplData = this.model.toTemplateJSON(); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - this.$graph = this.$el.find('.panel.graph'); - this.$graph.on("plothover", this._toolTip); - return this; - }, - - remove: function () { - this.editor.remove(); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - redraw: function() { - // There are issues generating a Flot graph if either: - // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with - // Uncaught Invalid dimensions for plot, width = 0, height = 0 - // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' - var areWeVisible = !jQuery.expr.filters.hidden(this.el); - if ((!areWeVisible || this.model.records.length === 0)) { - this.needToRedraw = true; - return; - } - - // check we have something to plot - if (this.state.get('group') && this.state.get('series')) { - var series = this.createSeries(); - var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length); - this.plot = $.plot(this.$graph, series, options); - } - }, - - show: function() { - // because we cannot redraw when hidden we may need to when becoming visible - if (this.needToRedraw) { - this.redraw(); - } - }, - - // infoboxes on mouse hover on points/bars etc - _toolTip: function (event, pos, item) { - if (item) { - if (this.previousTooltipPoint.x !== item.dataIndex || - this.previousTooltipPoint.y !== item.seriesIndex) { - this.previousTooltipPoint.x = item.dataIndex; - this.previousTooltipPoint.y = item.seriesIndex; - $("#recline-flot-tooltip").remove(); - - var x = item.datapoint[0].toFixed(2), - y = item.datapoint[1].toFixed(2); - - if (this.state.attributes.graphType === 'bars') { - x = item.datapoint[1].toFixed(2), - y = item.datapoint[0].toFixed(2); - } - - var template = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>'); - var content = template({ - group: this.state.attributes.group, - x: this._xaxisLabel(x), - series: item.series.label, - y: y - }); - - // use a different tooltip location offset for bar charts - var xLocation, yLocation; - if (this.state.attributes.graphType === 'bars') { - xLocation = item.pageX + 15; - yLocation = item.pageY - 10; - } else if (this.state.attributes.graphType === 'columns') { - xLocation = item.pageX + 15; - yLocation = item.pageY; - } else { - xLocation = item.pageX + 10; - yLocation = item.pageY - 20; - } - - $('
' + content + '
').css({ - top: yLocation, - left: xLocation - }).appendTo("body").fadeIn(200); - } - } else { - $("#recline-flot-tooltip").remove(); - this.previousTooltipPoint.x = null; - this.previousTooltipPoint.y = null; - } - }, - - _xaxisLabel: function (x) { - if (this._groupFieldIsDateTime()) { - // oddly x comes through as milliseconds *string* (rather than int - // or float) so we have to reparse - x = new Date(parseFloat(x)).toLocaleDateString(); - } else if (this.xvaluesAreIndex) { - x = parseInt(x, 10); - // HACK: deal with bar graph style cases where x-axis items were strings - // In this case x at this point is the index of the item in the list of - // records not its actual x-axis value - x = this.model.records.models[x].get(this.state.attributes.group); - } - - return x; - }, - - // ### getGraphOptions - // - // Get options for Flot Graph - // - // needs to be function as can depend on state - // - // @param typeId graphType id (lines, lines-and-points etc) - // @param numPoints the number of points that will be plotted - getGraphOptions: function(typeId, numPoints) { - var self = this; - var groupFieldIsDateTime = self._groupFieldIsDateTime(); - var xaxis = {}; - - if (!groupFieldIsDateTime) { - xaxis.tickFormatter = function (x) { - // convert x to a string and make sure that it is not too long or the - // tick labels will overlap - // TODO: find a more accurate way of calculating the size of tick labels - var label = self._xaxisLabel(x) || ""; - - if (typeof label !== 'string') { - label = label.toString(); - } - if (self.state.attributes.graphType !== 'bars' && label.length > 10) { - label = label.slice(0, 10) + "..."; - } - - return label; - }; - } - - // for labels case we only want ticks at the label intervals - // HACK: however we also get this case with Date fields. In that case we - // could have a lot of values and so we limit to max 15 (we assume) - if (this.xvaluesAreIndex) { - var numTicks = Math.min(this.model.records.length, 15); - var increment = this.model.records.length / numTicks; - var ticks = []; - for (var i=0; i \ -
\ -
\ -
\ - \ -
\ - \ -
\ -
\ -
\ - \ -
\ - \ -
\ -
\ -
\ -
\ -
\ -
\ - \ -
\ - \ -
\ -
\ -', - templateSeriesEditor: ' \ -
\ -
\ - \ -
\ - \ -
\ -
\ -
\ - ', - events: { - 'change form select': 'onEditorSubmit', - 'click .editor-add': '_onAddSeries', - 'click .action-remove-series': 'removeSeries' - }, - - initialize: function(options) { - var self = this; - _.bindAll(this, 'render'); - this.listenTo(this.model.fields, 'reset add', this.render); - this.state = new recline.Model.ObjectState(options.state); - this.render(); - }, - - render: function() { - var self = this; - var tmplData = this.model.toTemplateJSON(); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - - // set up editor from state - if (this.state.get('graphType')) { - this._selectOption('.editor-type', this.state.get('graphType')); - } - if (this.state.get('group')) { - this._selectOption('.editor-group', this.state.get('group')); - } - // ensure at least one series box shows up - var tmpSeries = [""]; - if (this.state.get('series').length > 0) { - tmpSeries = this.state.get('series'); - } - _.each(tmpSeries, function(series, idx) { - self.addSeries(idx); - self._selectOption('.editor-series.js-series-' + idx, series); - }); - return this; - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = this.$el.find(id + ' select > option'); - if (options) { - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - }, - - onEditorSubmit: function(e) { - var select = this.$el.find('.editor-group select'); - var $editor = this; - var $series = this.$el.find('.editor-series select'); - var series = $series.map(function () { - return $(this).val(); - }); - var updatedState = { - series: $.makeArray(series), - group: this.$el.find('.editor-group select').val(), - graphType: this.$el.find('.editor-type select').val() - }; - this.state.set(updatedState); - }, - - // Public: Adds a new empty series select box to the editor. - // - // @param [int] idx index of this series in the list of series - // - // Returns itself. - addSeries: function (idx) { - var data = _.extend({ - seriesIndex: idx, - seriesName: String.fromCharCode(idx + 64 + 1) - }, this.model.toTemplateJSON()); - - var htmls = Mustache.render(this.templateSeriesEditor, data); - this.$el.find('.editor-series-group').append(htmls); - return this; - }, - - _onAddSeries: function(e) { - e.preventDefault(); - this.addSeries(this.state.get('series').length); - }, - - // Public: Removes a series list item from the editor. - // - // Also updates the labels of the remaining series elements. - removeSeries: function (e) { - e.preventDefault(); - var $el = $(e.target); - $el.parent().parent().remove(); - this.onEditorSubmit(); - } -}); - -})(jQuery, recline.View); -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; -this.recline.View.Graph = this.recline.View.Flot; -this.recline.View.GraphControls = this.recline.View.FlotControls; -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## (Data) Grid Dataset View -// -// Provides a tabular view on a Dataset. -// -// Initialize it with a `recline.Model.Dataset`. -my.Grid = Backbone.View.extend({ - tagName: "div", - className: "recline-grid-container", - - initialize: function(modelEtc) { - var self = this; - _.bindAll(this, 'render', 'onHorizontalScroll'); - this.listenTo(this.model.records, 'add reset remove', this.render); - this.tempState = {}; - var state = _.extend({ - hiddenFields: [] - }, modelEtc.state - ); - this.state = new recline.Model.ObjectState(state); - }, - - events: { - // does not work here so done at end of render function - // 'scroll .recline-grid tbody': 'onHorizontalScroll' - }, - - // ====================================================== - // Column and row menus - - setColumnSort: function(order) { - var sort = [{}]; - sort[0][this.tempState.currentColumn] = {order: order}; - this.model.query({sort: sort}); - }, - - hideColumn: function() { - var hiddenFields = this.state.get('hiddenFields'); - hiddenFields.push(this.tempState.currentColumn); - this.state.set({hiddenFields: hiddenFields}); - // change event not being triggered (because it is an array?) so trigger manually - this.state.trigger('change'); - this.render(); - }, - - showColumn: function(e) { - var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column')); - this.state.set({hiddenFields: hiddenFields}); - this.render(); - }, - - onHorizontalScroll: function(e) { - var currentScroll = $(e.target).scrollLeft(); - this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll); - }, - - // ====================================================== - // #### Templating - template: ' \ -
\ - \ - \ - \ - {{#fields}} \ - \ - {{/fields}} \ - \ - \ - \ - \ -
\ - {{label}} \ -
\ -
\ - ', - - toTemplateJSON: function() { - var self = this; - var modelData = this.model.toJSON(); - modelData.notEmpty = ( this.fields.length > 0 ); - // TODO: move this sort of thing into a toTemplateJSON method on Dataset? - modelData.fields = this.fields.map(function(field) { - return field.toJSON(); - }); - // last header width = scroll bar - border (2px) */ - modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2; - return modelData; - }, - render: function() { - var self = this; - this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) { - return _.indexOf(self.state.get('hiddenFields'), field.id) == -1; - })); - - this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions - var numFields = this.fields.length; - // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar) - var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; - var width = parseInt(Math.max(50, fullWidth / numFields), 10); - // if columns extend outside viewport then remainder is 0 - var remainder = Math.max(fullWidth - numFields * width,0); - this.fields.each(function(field, idx) { - // add the remainder to the first field width so we make up full col - if (idx === 0) { - field.set({width: width+remainder}); - } else { - field.set({width: width}); - } - }); - var htmls = Mustache.render(this.template, this.toTemplateJSON()); - this.$el.html(htmls); - this.model.records.forEach(function(doc) { - var tr = $(''); - self.$el.find('tbody').append(tr); - var newView = new my.GridRow({ - model: doc, - el: tr, - fields: self.fields - }); - newView.render(); - }); - // hide extra header col if no scrollbar to avoid unsightly overhang - var $tbody = this.$el.find('tbody')[0]; - if ($tbody.scrollHeight <= $tbody.offsetHeight) { - this.$el.find('th.last-header').hide(); - } - this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); - this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); - return this; - }, - - // ### _scrollbarSize - // - // Measure width of a vertical scrollbar and height of a horizontal scrollbar. - // - // @return: { width: pixelWidth, height: pixelHeight } - _scrollbarSize: function() { - var $c = $("
").appendTo("body"); - var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight }; - $c.remove(); - return dim; - } -}); - -// ## GridRow View for rendering an individual record. -// -// Since we want this to update in place it is up to creator to provider the element to attach to. -// -// In addition you *must* pass in a FieldList in the constructor options. This should be list of fields for the Grid. -// -// Example: -// -//
-// var row = new GridRow({
-//   model: dataset-record,
-//     el: dom-element,
-//     fields: mydatasets.fields // a FieldList object
-//   });
-// 
-my.GridRow = Backbone.View.extend({ - initialize: function(initData) { - _.bindAll(this, 'render'); - this._fields = initData.fields; - this.listenTo(this.model, 'change', this.render); - }, - - template: ' \ - {{#cells}} \ - \ -
\ -   \ -
{{{value}}}
\ -
\ - \ - {{/cells}} \ - ', - events: { - 'click .data-table-cell-edit': 'onEditClick', - 'click .data-table-cell-editor .okButton': 'onEditorOK', - 'click .data-table-cell-editor .cancelButton': 'onEditorCancel' - }, - - toTemplateJSON: function() { - var self = this; - var doc = this.model; - var cellData = this._fields.map(function(field) { - return { - field: field.id, - width: field.get('width'), - value: doc.getFieldValue(field) - }; - }); - return { id: this.id, cells: cellData }; - }, - - render: function() { - this.$el.attr('data-id', this.model.id); - var html = Mustache.render(this.template, this.toTemplateJSON()); - this.$el.html(html); - return this; - }, - - // =================== - // Cell Editor methods - - cellEditorTemplate: ' \ - \ - ', - - onEditClick: function(e) { - var editing = this.$el.find('.data-table-cell-editor-editor'); - if (editing.length > 0) { - editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); - } - $(e.target).addClass("hidden"); - var cell = $(e.target).siblings('.data-table-cell-value'); - cell.data("previousContents", cell.text()); - var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()}); - cell.html(templated); - }, - - onEditorOK: function(e) { - var self = this; - var cell = $(e.target); - var rowId = cell.parents('tr').attr('data-id'); - var field = cell.parents('td').attr('data-field'); - var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val(); - var newData = {}; - newData[field] = newValue; - this.model.set(newData); - this.trigger('recline:flash', {message: "Updating row...", loader: true}); - this.model.save().then(function(response) { - this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'}); - }) - .fail(function() { - this.trigger('recline:flash', { - message: 'Error saving row', - category: 'error', - persist: true - }); - }); - }, - - onEditorCancel: function(e) { - var cell = $(e.target).parents('.data-table-cell-value'); - cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden"); - } -}); - -})(jQuery, recline.View); -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## Map view for a Dataset using Leaflet mapping library. -// -// This view allows to plot gereferenced records on a map. The location -// information can be provided in 2 ways: -// -// 1. Via a single field. This field must be either a geo_point or -// [GeoJSON](http://geojson.org) object -// 2. Via two fields with latitude and longitude coordinates. -// -// Which fields in the data these correspond to can be configured via the state -// (and are guessed if no info is provided). -// -// Initialization arguments are as standard for Dataset Views. State object may -// have the following (optional) configuration options: -// -//
-//   {
-//     // geomField if specified will be used in preference to lat/lon
-//     geomField: {id of field containing geometry in the dataset}
-//     lonField: {id of field containing longitude in the dataset}
-//     latField: {id of field containing latitude in the dataset}
-//     autoZoom: true,
-//     // use cluster support
-//     // cluster: true = always on
-//     // cluster: false = always off
-//     cluster: false
-//   }
-// 
-// -// Useful attributes to know about (if e.g. customizing) -// -// * map: the Leaflet map (L.Map) -// * features: Leaflet GeoJSON layer containing all the features (L.GeoJSON) -my.Map = Backbone.View.extend({ - template: ' \ -
\ -
\ -
\ -', - - // These are the default (case-insensitive) names of field that are used if found. - // If not found, the user will need to define the fields via the editor. - latitudeFieldNames: ['lat','latitude'], - longitudeFieldNames: ['lon','longitude'], - geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location', 'geo', 'lonlat'], - - initialize: function(options) { - var self = this; - this.options = options; - this.visible = this.$el.is(':visible'); - this.mapReady = false; - // this will be the Leaflet L.Map object (setup below) - this.map = null; - - var stateData = _.extend({ - geomField: null, - lonField: null, - latField: null, - autoZoom: true, - cluster: false - }, - options.state - ); - this.state = new recline.Model.ObjectState(stateData); - - this._clusterOptions = { - zoomToBoundsOnClick: true, - //disableClusteringAtZoom: 10, - maxClusterRadius: 80, - singleMarkerMode: false, - skipDuplicateAddTesting: true, - animateAddingMarkers: false - }; - - // Listen to changes in the fields - this.listenTo(this.model.fields, 'change', function() { - self._setupGeometryField(); - self.render(); - }); - - // Listen to changes in the records - this.listenTo(this.model.records, 'add', function(doc){self.redraw('add',doc);}); - this.listenTo(this.model.records, 'change', function(doc){ - self.redraw('remove',doc); - self.redraw('add',doc); - }); - this.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);}); - this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');}); - - this.menu = new my.MapMenu({ - model: this.model, - state: this.state.toJSON() - }); - this.listenTo(this.menu.state, 'change', function() { - self.state.set(self.menu.state.toJSON()); - self.redraw(); - }); - this.listenTo(this.state, 'change', function() { - self.redraw(); - }); - this.elSidebar = this.menu.$el; - }, - - // ## Customization Functions - // - // The following methods are designed for overriding in order to customize - // behaviour - - // ### infobox - // - // Function to create infoboxes used in popups. The default behaviour is very simple and just lists all attributes. - // - // Users should override this function to customize behaviour i.e. - // - // view = new View({...}); - // view.infobox = function(record) { - // ... - // } - infobox: function(record) { - var html = ''; - for (var key in record.attributes){ - if (!(this.state.get('geomField') && key == this.state.get('geomField'))){ - html += '
' + key + ': '+ record.attributes[key] + '
'; - } - } - return html; - }, - - // Options to use for the [Leaflet GeoJSON layer](http://leaflet.cloudmade.com/reference.html#geojson) - // See also - // - // e.g. - // - // pointToLayer: function(feature, latLng) - // onEachFeature: function(feature, layer) - // - // See defaults for examples - geoJsonLayerOptions: { - // pointToLayer function to use when creating points - // - // Default behaviour shown here is to create a marker using the - // popupContent set on the feature properties (created via infobox function - // during feature generation) - // - // NB: inside pointToLayer `this` will be set to point to this map view - // instance (which allows e.g. this.markers to work in this default case) - pointToLayer: function (feature, latlng) { - var marker = new L.Marker(latlng); - marker.bindPopup(feature.properties.popupContent); - // this is for cluster case - this.markers.addLayer(marker); - return marker; - }, - // onEachFeature default which adds popup in - onEachFeature: function(feature, layer) { - if (feature.properties && feature.properties.popupContent) { - layer.bindPopup(feature.properties.popupContent); - } - } - }, - - // END: Customization section - // ---- - - // ### Public: Adds the necessary elements to the page. - // - // Also sets up the editor fields and the map if necessary. - render: function() { - var self = this; - var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - this.$el.html(htmls); - this.$map = this.$el.find('.panel.map'); - this.redraw(); - return this; - }, - - // ### Public: Redraws the features on the map according to the action provided - // - // Actions can be: - // - // * reset: Clear all features - // * add: Add one or n features (records) - // * remove: Remove one or n features (records) - // * refresh: Clear existing features and add all current records - redraw: function(action, doc){ - var self = this; - action = action || 'refresh'; - // try to set things up if not already - if (!self._geomReady()){ - self._setupGeometryField(); - } - if (!self.mapReady){ - self._setupMap(); - } - - if (this._geomReady() && this.mapReady){ - // removing ad re-adding the layer enables faster bulk loading - this.map.removeLayer(this.features); - this.map.removeLayer(this.markers); - - var countBefore = 0; - this.features.eachLayer(function(){countBefore++;}); - - if (action == 'refresh' || action == 'reset') { - this.features.clearLayers(); - // recreate cluster group because of issues with clearLayer - this.map.removeLayer(this.markers); - this.markers = new L.MarkerClusterGroup(this._clusterOptions); - this._add(this.model.records.models); - } else if (action == 'add' && doc){ - this._add(doc); - } else if (action == 'remove' && doc){ - this._remove(doc); - } - - // this must come before zooming! - // if not: errors when using e.g. circle markers like - // "Cannot call method 'project' of undefined" - if (this.state.get('cluster')) { - this.map.addLayer(this.markers); - } else { - this.map.addLayer(this.features); - } - - if (this.state.get('autoZoom')){ - if (this.visible){ - this._zoomToFeatures(); - } else { - this._zoomPending = true; - } - } - } - }, - - show: function() { - // If the div was hidden, Leaflet needs to recalculate some sizes - // to display properly - if (this.map){ - this.map.invalidateSize(); - if (this._zoomPending && this.state.get('autoZoom')) { - this._zoomToFeatures(); - this._zoomPending = false; - } - } - this.visible = true; - }, - - hide: function() { - this.visible = false; - }, - - _geomReady: function() { - return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); - }, - - // Private: Add one or n features to the map - // - // For each record passed, a GeoJSON geometry will be extracted and added - // to the features layer. If an exception is thrown, the process will be - // stopped and an error notification shown. - // - // Each feature will have a popup associated with all the record fields. - // - _add: function(docs){ - var self = this; - - if (!(docs instanceof Array)) docs = [docs]; - - var count = 0; - var wrongSoFar = 0; - _.every(docs, function(doc){ - count += 1; - var feature = self._getGeometryFromRecord(doc); - if (typeof feature === 'undefined' || feature === null){ - // Empty field - return true; - } else if (feature instanceof Object){ - feature.properties = { - popupContent: self.infobox(doc), - // Add a reference to the model id, which will allow us to - // link this Leaflet layer to a Recline doc - cid: doc.cid - }; - - try { - self.features.addData(feature); - } catch (except) { - wrongSoFar += 1; - var msg = 'Wrong geometry value'; - if (except.message) msg += ' (' + except.message + ')'; - if (wrongSoFar <= 10) { - self.trigger('recline:flash', {message: msg, category:'error'}); - } - } - } else { - wrongSoFar += 1; - if (wrongSoFar <= 10) { - self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'}); - } - } - return true; - }); - }, - - // Private: Remove one or n features from the map - // - _remove: function(docs){ - - var self = this; - - if (!(docs instanceof Array)) docs = [docs]; - - _.each(docs,function(doc){ - for (var key in self.features._layers){ - if (self.features._layers[key].feature.geometry.properties.cid == doc.cid){ - self.features.removeLayer(self.features._layers[key]); - } - } - }); - - }, - - // Private: convert DMS coordinates to decimal - // - // north and east are positive, south and west are negative - // - _parseCoordinateString: function(coord){ - if (typeof(coord) != 'string') { - return(parseFloat(coord)); - } - var dms = coord.split(/[^-?\.\d\w]+/); - var deg = 0; var m = 0; - var toDeg = [1, 60, 3600]; // conversion factors for Deg, min, sec - var i; - for (i = 0; i < dms.length; ++i) { - if (isNaN(parseFloat(dms[i]))) { - continue; - } - deg += parseFloat(dms[i]) / toDeg[m]; - m += 1; - } - if (coord.match(/[SW]/)) { - deg = -1*deg; - } - return(deg); - }, - - // Private: Return a GeoJSON geomtry extracted from the record fields - // - _getGeometryFromRecord: function(doc){ - if (this.state.get('geomField')){ - var value = doc.get(this.state.get('geomField')); - if (typeof(value) === 'string'){ - // We *may* have a GeoJSON string representation - try { - value = $.parseJSON(value); - } catch(e) {} - } - if (typeof(value) === 'string') { - value = value.replace('(', '').replace(')', ''); - var parts = value.split(','); - var lat = this._parseCoordinateString(parts[0]); - var lon = this._parseCoordinateString(parts[1]); - - if (!isNaN(lon) && !isNaN(parseFloat(lat))) { - return { - "type": "Point", - "coordinates": [lon, lat] - }; - } else { - return null; - } - } else if (value && _.isArray(value)) { - // [ lon, lat ] - return { - "type": "Point", - "coordinates": [value[0], value[1]] - }; - } else if (value && value.lat) { - // of form { lat: ..., lon: ...} - return { - "type": "Point", - "coordinates": [value.lon || value.lng, value.lat] - }; - } - // We o/w assume that contents of the field are a valid GeoJSON object - return value; - } else if (this.state.get('lonField') && this.state.get('latField')){ - // We'll create a GeoJSON like point object from the two lat/lon fields - var lon = doc.get(this.state.get('lonField')); - var lat = doc.get(this.state.get('latField')); - lon = this._parseCoordinateString(lon); - lat = this._parseCoordinateString(lat); - - if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) { - return { - type: 'Point', - coordinates: [lon,lat] - }; - } - } - return null; - }, - - // Private: Check if there is a field with GeoJSON geometries or alternatively, - // two fields with lat/lon values. - // - // If not found, the user can define them via the UI form. - _setupGeometryField: function(){ - // should not overwrite if we have already set this (e.g. explicitly via state) - if (!this._geomReady()) { - this.state.set({ - geomField: this._checkField(this.geometryFieldNames), - latField: this._checkField(this.latitudeFieldNames), - lonField: this._checkField(this.longitudeFieldNames) - }); - this.menu.state.set(this.state.toJSON()); - } - }, - - // Private: Check if a field in the current model exists in the provided - // list of names. - // - // - _checkField: function(fieldNames){ - var field; - var modelFieldNames = this.model.fields.pluck('id'); - for (var i = 0; i < fieldNames.length; i++){ - for (var j = 0; j < modelFieldNames.length; j++){ - if (modelFieldNames[j].toLowerCase() == fieldNames[i].toLowerCase()) - return modelFieldNames[j]; - } - } - return null; - }, - - // Private: Zoom to map to current features extent if any, or to the full - // extent if none. - // - _zoomToFeatures: function(){ - var bounds = this.features.getBounds(); - if (bounds && bounds.getNorthEast() && bounds.getSouthWest()){ - this.map.fitBounds(bounds); - } else { - this.map.setView([0, 0], 2); - } - }, - - // Private: Sets up the Leaflet map control and the features layer. - // - // The map uses a base layer from [Stamen](http://maps.stamen.com) based - // on [OpenStreetMap data](http://openstreetmap.org) by default, but it can - // be configured passing the `mapTilesURL` and `mapTilesAttribution` options - // (`mapTilesSubdomains` is also supported), eg: - // - // view = new recline.View.Map({ - // model: dataset, - // mapTilesURL: '//{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/{z}/{x}/{y}.png?access_token=pk.XXXX', - // mapTilesAttribution: '© MapBox etc..', - // mapTilesSubdomains: 'ab' - // }) - // - // - _setupMap: function(){ - var self = this; - this.map = new L.Map(this.$map.get(0)); - var mapUrl = this.options.mapTilesURL || 'https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png'; - var attribution = this.options.mapTilesAttribution ||'Map tiles by Stamen Design (CC BY 3.0). Data by OpenStreetMap (CC BY SA)'; - var subdomains = this.options.mapTilesSubdomains || 'abc'; - - var bg = new L.TileLayer(mapUrl, {maxZoom: 19, attribution: attribution, subdomains: subdomains}); - this.map.addLayer(bg); - - this.markers = new L.MarkerClusterGroup(this._clusterOptions); - - // rebind this (as needed in e.g. default case above) - this.geoJsonLayerOptions.pointToLayer = _.bind( - this.geoJsonLayerOptions.pointToLayer, - this); - this.features = new L.GeoJSON(null, this.geoJsonLayerOptions); - - this.map.setView([0, 0], 2); - - this.mapReady = true; - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = $('.' + id + ' > select > option'); - if (options){ - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - } -}); - -my.MapMenu = Backbone.View.extend({ - className: 'editor', - - template: ' \ -
\ -
\ -
\ - \ - \ -
\ -
\ - \ -
\ - \ -
\ - \ -
\ - \ -
\ -
\ - \ -
\ -
\ - \ -
\ -
\ - \ - \ -
\ - \ -
\ - ', - - // Define here events for UI elements - events: { - 'click .editor-update-map': 'onEditorSubmit', - 'change .editor-field-type': 'onFieldTypeChange', - 'click #editor-auto-zoom': 'onAutoZoomChange', - 'click #editor-cluster': 'onClusteringChange' - }, - - initialize: function(options) { - var self = this; - _.bindAll(this, 'render'); - this.listenTo(this.model.fields, 'change', this.render); - this.state = new recline.Model.ObjectState(options.state); - this.listenTo(this.state, 'change', this.render); - this.render(); - }, - - // ### Public: Adds the necessary elements to the page. - // - // Also sets up the editor fields and the map if necessary. - render: function() { - var self = this; - var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - this.$el.html(htmls); - - if (this._geomReady() && this.model.fields.length){ - if (this.state.get('geomField')){ - this._selectOption('editor-geom-field',this.state.get('geomField')); - this.$el.find('#editor-field-type-geom').attr('checked','checked').change(); - } else{ - this._selectOption('editor-lon-field',this.state.get('lonField')); - this._selectOption('editor-lat-field',this.state.get('latField')); - this.$el.find('#editor-field-type-latlon').attr('checked','checked').change(); - } - } - if (this.state.get('autoZoom')) { - this.$el.find('#editor-auto-zoom').attr('checked', 'checked'); - } else { - this.$el.find('#editor-auto-zoom').removeAttr('checked'); - } - if (this.state.get('cluster')) { - this.$el.find('#editor-cluster').attr('checked', 'checked'); - } else { - this.$el.find('#editor-cluster').removeAttr('checked'); - } - return this; - }, - - _geomReady: function() { - return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); - }, - - // ## UI Event handlers - // - - // Public: Update map with user options - // - // Right now the only configurable option is what field(s) contains the - // location information. - // - onEditorSubmit: function(e){ - e.preventDefault(); - if (this.$el.find('#editor-field-type-geom').attr('checked')){ - this.state.set({ - geomField: this.$el.find('.editor-geom-field > select > option:selected').val(), - lonField: null, - latField: null - }); - } else { - this.state.set({ - geomField: null, - lonField: this.$el.find('.editor-lon-field > select > option:selected').val(), - latField: this.$el.find('.editor-lat-field > select > option:selected').val() - }); - } - return false; - }, - - // Public: Shows the relevant select lists depending on the location field - // type selected. - // - onFieldTypeChange: function(e){ - if (e.target.value == 'geom'){ - this.$el.find('.editor-field-type-geom').show(); - this.$el.find('.editor-field-type-latlon').hide(); - } else { - this.$el.find('.editor-field-type-geom').hide(); - this.$el.find('.editor-field-type-latlon').show(); - } - }, - - onAutoZoomChange: function(e){ - this.state.set({autoZoom: !this.state.get('autoZoom')}); - }, - - onClusteringChange: function(e){ - this.state.set({cluster: !this.state.get('cluster')}); - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = this.$el.find('.' + id + ' > select > option'); - if (options){ - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - } -}); - -})(jQuery, recline.View); -/*jshint multistr:true */ - -// Standard JS module setup -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## MultiView -// -// Manage multiple views together along with query editor etc. Usage: -// -//
-// var myExplorer = new recline.View.MultiView({
-//   model: {{recline.Model.Dataset instance}}
-//   el: {{an existing dom element}}
-//   views: {{dataset views}}
-//   state: {{state configuration -- see below}}
-// });
-// 
-// -// ### Parameters -// -// **model**: (required) recline.model.Dataset instance. -// -// **el**: (required) DOM element to bind to. NB: the element already -// being in the DOM is important for rendering of some subviews (e.g. -// Graph). -// -// **views**: (optional) the dataset views (Grid, Graph etc) for -// MultiView to show. This is an array of view hashes. If not provided -// initialize with (recline.View.)Grid, Graph, and Map views (with obvious id -// and labels!). -// -//
-// var views = [
-//   {
-//     id: 'grid', // used for routing
-//     label: 'Grid', // used for view switcher
-//     view: new recline.View.Grid({
-//       model: dataset
-//     })
-//   },
-//   {
-//     id: 'graph',
-//     label: 'Graph',
-//     view: new recline.View.Graph({
-//       model: dataset
-//     })
-//   }
-// ];
-// 
-// -// **sidebarViews**: (optional) the sidebar views (Filters, Fields) for -// MultiView to show. This is an array of view hashes. If not provided -// initialize with (recline.View.)FilterEditor and Fields views (with obvious -// id and labels!). -// -//
-// var sidebarViews = [
-//   {
-//     id: 'filterEditor', // used for routing
-//     label: 'Filters', // used for view switcher
-//     view: new recline.View.FilterEditor({
-//       model: dataset
-//     })
-//   },
-//   {
-//     id: 'fieldsView',
-//     label: 'Fields',
-//     view: new recline.View.Fields({
-//       model: dataset
-//     })
-//   }
-// ];
-// 
-// -// **state**: standard state config for this view. This state is slightly -// special as it includes config of many of the subviews. -// -//
-// var state = {
-//     query: {dataset query state - see dataset.queryState object}
-//     'view-{id1}': {view-state for this view}
-//     'view-{id2}': {view-state for }
-//     ...
-//     // Explorer
-//     currentView: id of current view (defaults to first view if not specified)
-//     readOnly: (default: false) run in read-only mode
-// }
-// 
-// -// Note that at present we do *not* serialize information about the actual set -// of views in use -- e.g. those specified by the views argument -- but instead -// expect either that the default views are fine or that the client to have -// initialized the MultiView with the relevant views themselves. -my.MultiView = Backbone.View.extend({ - template: ' \ -
\ -
\ - \ -
\ - \ -
\ - {{recordCount}} records\ -
\ - \ -
\ -
\ -
\ -
\ -
\ - ', - events: { - 'click .menu-right button': '_onMenuClick', - 'click .navigation button': '_onSwitchView' - }, - - initialize: function(options) { - var self = this; - this._setupState(options.state); - - // Hash of 'page' views (i.e. those for whole page) keyed by page name - if (options.views) { - this.pageViews = options.views; - } else { - this.pageViews = [{ - id: 'grid', - label: 'Grid', - view: new my.SlickGrid({ - model: this.model, - state: this.state.get('view-grid') - }) - }, { - id: 'graph', - label: 'Graph', - view: new my.Graph({ - model: this.model, - state: this.state.get('view-graph') - }) - }, { - id: 'map', - label: 'Map', - view: new my.Map({ - model: this.model, - state: this.state.get('view-map') - }) - }, { - id: 'timeline', - label: 'Timeline', - view: new my.Timeline({ - model: this.model, - state: this.state.get('view-timeline') - }) - }]; - } - // Hashes of sidebar elements - if(options.sidebarViews) { - this.sidebarViews = options.sidebarViews; - } else { - this.sidebarViews = [{ - id: 'filterEditor', - label: 'Filters', - view: new my.FilterEditor({ - model: this.model - }) - }, { - id: 'fieldsView', - label: 'Fields', - view: new my.Fields({ - model: this.model - }) - }]; - } - // these must be called after pageViews are created - this.render(); - this._bindStateChanges(); - this._bindFlashNotifications(); - // now do updates based on state (need to come after render) - if (this.state.get('readOnly')) { - this.setReadOnly(); - } - if (this.state.get('currentView')) { - this.updateNav(this.state.get('currentView')); - } else { - this.updateNav(this.pageViews[0].id); - } - this._showHideSidebar(); - - this.listenTo(this.model, 'query:start', function() { - self.notify({loader: true, persist: true}); - }); - this.listenTo(this.model, 'query:done', function() { - self.clearNotifications(); - self.$el.find('.doc-count').text(self.model.recordCount || 'Unknown'); - }); - this.listenTo(this.model, 'query:fail', function(error) { - self.clearNotifications(); - var msg = ''; - if (typeof(error) == 'string') { - msg = error; - } else if (typeof(error) == 'object') { - if (error.title) { - msg = error.title + ': '; - } - if (error.message) { - msg += error.message; - } - } else { - msg = 'There was an error querying the backend'; - } - self.notify({message: msg, category: 'error', persist: true}); - }); - - // retrieve basic data like fields etc - // note this.model and dataset returned are the same - // TODO: set query state ...? - this.model.queryState.set(self.state.get('query'), {silent: true}); - }, - - setReadOnly: function() { - this.$el.addClass('recline-read-only'); - }, - - render: function() { - var tmplData = this.model.toTemplateJSON(); - tmplData.views = this.pageViews; - tmplData.sidebarViews = this.sidebarViews; - var template = Mustache.render(this.template, tmplData); - this.$el.html(template); - - // now create and append other views - var $dataViewContainer = this.$el.find('.data-view-container'); - var $dataSidebar = this.$el.find('.data-view-sidebar'); - - // the main views - _.each(this.pageViews, function(view, pageName) { - view.view.render(); - if (view.view.redraw) { - view.view.redraw(); - } - $dataViewContainer.append(view.view.el); - if (view.view.elSidebar) { - $dataSidebar.append(view.view.elSidebar); - } - }); - - _.each(this.sidebarViews, function(view) { - this['$'+view.id] = view.view.$el; - $dataSidebar.append(view.view.el); - }, this); - - this.pager = new recline.View.Pager({ - model: this.model - }); - this.$el.find('.recline-results-info').after(this.pager.el); - - this.queryEditor = new recline.View.QueryEditor({ - model: this.model.queryState - }); - this.$el.find('.query-editor-here').append(this.queryEditor.el); - - }, - - remove: function () { - _.each(this.pageViews, function (view) { - view.view.remove(); - }); - _.each(this.sidebarViews, function (view) { - view.view.remove(); - }); - this.pager.remove(); - this.queryEditor.remove(); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - // hide the sidebar if empty - _showHideSidebar: function() { - var $dataSidebar = this.$el.find('.data-view-sidebar'); - var visibleChildren = $dataSidebar.children().filter(function() { - return $(this).css("display") != "none"; - }).length; - - if (visibleChildren > 0) { - $dataSidebar.show(); - } else { - $dataSidebar.hide(); - } - }, - - updateNav: function(pageName) { - this.$el.find('.navigation button').removeClass('active'); - var $el = this.$el.find('.navigation button[data-view="' + pageName + '"]'); - $el.addClass('active'); - - // add/remove sidebars and hide inactive views - _.each(this.pageViews, function(view, idx) { - if (view.id === pageName) { - view.view.$el.show(); - if (view.view.elSidebar) { - view.view.elSidebar.show(); - } - } else { - view.view.$el.hide(); - if (view.view.elSidebar) { - view.view.elSidebar.hide(); - } - if (view.view.hide) { - view.view.hide(); - } - } - }); - - this._showHideSidebar(); - - // call view.view.show after sidebar visibility has been determined so - // that views can correctly calculate their maximum width - _.each(this.pageViews, function(view, idx) { - if (view.id === pageName) { - if (view.view.show) { - view.view.show(); - } - } - }); - }, - - _onMenuClick: function(e) { - e.preventDefault(); - var action = $(e.target).attr('data-action'); - this['$'+action].toggle(); - this._showHideSidebar(); - }, - - _onSwitchView: function(e) { - e.preventDefault(); - var viewName = $(e.target).attr('data-view'); - this.updateNav(viewName); - this.state.set({currentView: viewName}); - }, - - // create a state object for this view and do the job of - // - // a) initializing it from both data passed in and other sources (e.g. hash url) - // - // b) ensure the state object is updated in responese to changes in subviews, query etc. - _setupState: function(initialState) { - var self = this; - // get data from the query string / hash url plus some defaults - var qs = my.parseHashQueryString(); - var query = qs.reclineQuery; - query = query ? JSON.parse(query) : self.model.queryState.toJSON(); - // backwards compatability (now named view-graph but was named graph) - var graphState = qs['view-graph'] || qs.graph; - graphState = graphState ? JSON.parse(graphState) : {}; - - // now get default data + hash url plus initial state and initial our state object with it - var stateData = _.extend({ - query: query, - 'view-graph': graphState, - backend: this.model.backend.__type__, - url: this.model.get('url'), - dataset: this.model.toJSON(), - currentView: null, - readOnly: false - }, - initialState); - this.state = new recline.Model.ObjectState(stateData); - }, - - _bindStateChanges: function() { - var self = this; - // finally ensure we update our state object when state of sub-object changes so that state is always up to date - this.listenTo(this.model.queryState, 'change', function() { - self.state.set({query: self.model.queryState.toJSON()}); - }); - _.each(this.pageViews, function(pageView) { - if (pageView.view.state && pageView.view.state.bind) { - var update = {}; - update['view-' + pageView.id] = pageView.view.state.toJSON(); - self.state.set(update); - self.listenTo(pageView.view.state, 'change', function() { - var update = {}; - update['view-' + pageView.id] = pageView.view.state.toJSON(); - // had problems where change not being triggered for e.g. grid view so let's do it explicitly - self.state.set(update, {silent: true}); - self.state.trigger('change'); - }); - } - }); - }, - - _bindFlashNotifications: function() { - var self = this; - _.each(this.pageViews, function(pageView) { - self.listenTo(pageView.view, 'recline:flash', function(flash) { - self.notify(flash); - }); - }); - }, - - // ### notify - // - // Create a notification (a div.alert in div.alert-messsages) using provided - // flash object. Flash attributes (all are optional): - // - // * message: message to show. - // * category: warning (default), success, error - // * persist: if true alert is persistent, o/w hidden after 3s (default = false) - // * loader: if true show loading spinner - notify: function(flash) { - var tmplData = _.extend({ - message: 'Loading', - category: 'warning', - loader: false - }, - flash - ); - var _template; - if (tmplData.loader) { - _template = ' \ -
\ - {{message}} \ -   \ -
'; - } else { - _template = ' \ -
Ɨ \ - {{message}} \ -
'; - } - var _templated = $(Mustache.render(_template, tmplData)); - _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages')); - if (!flash.persist) { - setTimeout(function() { - $(_templated).fadeOut(1000, function() { - $(this).remove(); - }); - }, 1000); - } - }, - - // ### clearNotifications - // - // Clear all existing notifications - clearNotifications: function() { - var $notifications = $('.recline-data-explorer .alert-messages .alert'); - $notifications.fadeOut(1500, function() { - $(this).remove(); - }); - } -}); - -// ### MultiView.restore -// -// Restore a MultiView instance from a serialized state including the associated dataset -// -// This inverts the state serialization process in Multiview -my.MultiView.restore = function(state) { - // hack-y - restoring a memory dataset does not mean much ... (but useful for testing!) - var datasetInfo; - if (state.backend === 'memory') { - datasetInfo = { - backend: 'memory', - records: [{stub: 'this is a stub dataset because we do not restore memory datasets'}] - }; - } else { - datasetInfo = _.extend({ - url: state.url, - backend: state.backend - }, - state.dataset - ); - } - var dataset = new recline.Model.Dataset(datasetInfo); - var explorer = new my.MultiView({ - model: dataset, - state: state - }); - return explorer; -}; - -// ## Miscellaneous Utilities -var urlPathRegex = /^([^?]+)(\?.*)?/; - -// Parse the Hash section of a URL into path and query string -my.parseHashUrl = function(hashUrl) { - var parsed = urlPathRegex.exec(hashUrl); - if (parsed === null) { - return {}; - } else { - return { - path: parsed[1], - query: parsed[2] || '' - }; - } -}; - -// Parse a URL query string (?xyz=abc...) into a dictionary. -my.parseQueryString = function(q) { - if (!q) { - return {}; - } - var urlParams = {}, - e, d = function (s) { - return unescape(s.replace(/\+/g, " ")); - }, - r = /([^&=]+)=?([^&]*)/g; - - if (q && q.length && q[0] === '?') { - q = q.slice(1); - } - while (e = r.exec(q)) { - // TODO: have values be array as query string allow repetition of keys - urlParams[d(e[1])] = d(e[2]); - } - return urlParams; -}; - -// Parse the query string out of the URL hash -my.parseHashQueryString = function() { - var q = my.parseHashUrl(window.location.hash).query; - return my.parseQueryString(q); -}; - -// Compse a Query String -my.composeQueryString = function(queryParams) { - var queryString = '?'; - var items = []; - $.each(queryParams, function(key, value) { - if (typeof(value) === 'object') { - value = JSON.stringify(value); - } - items.push(key + '=' + encodeURIComponent(value)); - }); - queryString += items.join('&'); - return queryString; -}; - -my.getNewHashForQueryString = function(queryParams) { - var queryPart = my.composeQueryString(queryParams); - if (window.location.hash) { - // slice(1) to remove # at start - return window.location.hash.split('?')[0].slice(1) + queryPart; - } else { - return queryPart; - } -}; - -my.setHashQueryString = function(queryParams) { - window.location.hash = my.getNewHashForQueryString(queryParams); -}; - -})(jQuery, recline.View); - -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; - -// ## SlickGrid Dataset View -// -// Provides a tabular view on a Dataset, based on SlickGrid. -// -// https://github.com/mleibman/SlickGrid -// -// Initialize it with a `recline.Model.Dataset`. -// -// Additional options to drive SlickGrid grid can be given through state. -// The following keys allow for customization: -// * gridOptions: to add options at grid level -// * columnsEditor: to add editor for editable columns -// -// For example: -// var grid = new recline.View.SlickGrid({ -// model: dataset, -// el: $el, -// state: { -// gridOptions: { -// editable: true, -// enableAddRow: true -// // Enable support for row delete -// enabledDelRow: true, -// // Enable support for row Reorder -// enableReOrderRow:true, -// ... -// }, -// columnsEditor: [ -// {column: 'date', editor: Slick.Editors.Date }, -// {column: 'title', editor: Slick.Editors.Text} -// ] -// } -// }); -//// NB: you need an explicit height on the element for slickgrid to work -my.SlickGrid = Backbone.View.extend({ - initialize: function(modelEtc) { - var self = this; - this.$el.addClass('recline-slickgrid'); - - // Template for row delete menu , change it if you don't love - this.templates = { - "deleterow" : '' - }; - - _.bindAll(this, 'render', 'onRecordChanged'); - this.listenTo(this.model.records, 'add remove reset', this.render); - this.listenTo(this.model.records, 'change', this.onRecordChanged); - var state = _.extend({ - hiddenColumns: [], - columnsOrder: [], - columnsSort: {}, - columnsWidth: [], - columnsEditor: [], - options: {}, - fitColumns: false - }, modelEtc.state - - ); - this.state = new recline.Model.ObjectState(state); - this._slickHandler = new Slick.EventHandler(); - - //add menu for new row , check if enableAddRow is set to true or not set - if(this.state.get("gridOptions") - && this.state.get("gridOptions").enabledAddRow != undefined - && this.state.get("gridOptions").enabledAddRow == true ){ - this.editor = new my.GridControl() - this.elSidebar = this.editor.$el - this.listenTo(this.editor.state, 'change', function(){ - this.model.records.add(new recline.Model.Record()) - }); - } - }, - - onRecordChanged: function(record) { - // Ignore if the grid is not yet drawn - if (!this.grid) { - return; - } - // Let's find the row corresponding to the index - var row_index = this.grid.getData().getModelRow( record ); - this.grid.invalidateRow(row_index); - this.grid.getData().updateItem(record, row_index); - this.grid.render(); - }, - - render: function() { - var self = this; - var options = _.extend({ - enableCellNavigation: true, - enableColumnReorder: true, - explicitInitialization: true, - syncColumnCellResize: true, - forceFitColumns: this.state.get('fitColumns') - }, self.state.get('gridOptions')); - - // We need all columns, even the hidden ones, to show on the column picker - var columns = []; - - // custom formatter as default one escapes html - // plus this way we distinguish between rendering/formatting and computed value (so e.g. sort still works ...) - // row = row index, cell = cell index, value = value, columnDef = column definition, dataContext = full row values - var formatter = function(row, cell, value, columnDef, dataContext) { - if(columnDef.id == "del"){ - return self.templates.deleterow - } - var field = self.model.fields.get(columnDef.id); - if (field.renderer) { - return field.renderer(value, field, dataContext); - } else { - return value - } - }; - - // we need to be sure that user is entering a valid input , for exemple if - // field is date type and field.format ='YY-MM-DD', we should be sure that - // user enter a correct value - var validator = function(field) { - return function(value){ - if (field.type == "date" && isNaN(Date.parse(value))){ - return { - valid: false, - msg: "A date is required, check field field-date-format" - }; - } else { - return {valid: true, msg :null } - } - } - }; - - // Add column for row reorder support - if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow == true) { - columns.push({ - id: "#", - name: "", - width: 22, - behavior: "selectAndMove", - selectable: false, - resizable: false, - cssClass: "recline-cell-reorder" - }) - } - // Add column for row delete support - if (this.state.get("gridOptions") && this.state.get("gridOptions").enabledDelRow == true) { - columns.push({ - id: 'del', - name: '', - field: 'del', - sortable: true, - width: 38, - formatter: formatter, - validator:validator - }) - } - - function sanitizeFieldName(name) { - return $('
').text(name).html(); - } - - _.each(this.model.fields.toJSON(),function(field){ - var column = { - id: field.id, - name: sanitizeFieldName(field.label), - field: field.id, - sortable: true, - minWidth: 80, - formatter: formatter, - validator:validator(field) - }; - var widthInfo = _.find(self.state.get('columnsWidth'),function(c){return c.column === field.id;}); - if (widthInfo){ - column.width = widthInfo.width; - } - var editInfo = _.find(self.state.get('columnsEditor'),function(c){return c.column === field.id;}); - if (editInfo){ - column.editor = editInfo.editor; - } else { - // guess editor type - var typeToEditorMap = { - 'string': Slick.Editors.LongText, - 'integer': Slick.Editors.IntegerEditor, - 'number': Slick.Editors.Text, - // TODO: need a way to ensure we format date in the right way - // Plus what if dates are in distant past or future ... (?) - // 'date': Slick.Editors.DateEditor, - 'date': Slick.Editors.Text, - 'boolean': Slick.Editors.YesNoSelectEditor - // TODO: (?) percent ... - }; - if (field.type in typeToEditorMap) { - column.editor = typeToEditorMap[field.type] - } else { - column.editor = Slick.Editors.LongText; - } - } - columns.push(column); - }); - // Restrict the visible columns - var visibleColumns = _.filter(columns, function(column) { - return _.indexOf(self.state.get('hiddenColumns'), column.id) === -1; - }); - // Order them if there is ordering info on the state - if (this.state.get('columnsOrder') && this.state.get('columnsOrder').length > 0) { - visibleColumns = visibleColumns.sort(function(a,b){ - return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; - }); - columns = columns.sort(function(a,b){ - return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; - }); - } - - // Move hidden columns to the end, so they appear at the bottom of the - // column picker - var tempHiddenColumns = []; - for (var i = columns.length -1; i >= 0; i--){ - if (_.indexOf(_.pluck(visibleColumns,'id'),columns[i].id) === -1){ - tempHiddenColumns.push(columns.splice(i,1)[0]); - } - } - columns = columns.concat(tempHiddenColumns); - - // Transform a model object into a row - function toRow(m) { - var row = {}; - self.model.fields.each(function(field) { - var render = ""; - //when adding row from slickgrid the field value is undefined - if(!_.isUndefined(m.getFieldValueUnrendered(field))){ - render =m.getFieldValueUnrendered(field) - } - row[field.id] = render - }); - return row; - } - - function RowSet() { - var models = []; - var rows = []; - - this.push = function(model, row) { - models.push(model); - rows.push(row); - }; - - this.getLength = function() {return rows.length; }; - this.getItem = function(index) {return rows[index];}; - this.getItemMetadata = function(index) {return {};}; - this.getModel = function(index) {return models[index];}; - this.getModelRow = function(m) {return _.indexOf(models, m);}; - this.updateItem = function(m,i) { - rows[i] = toRow(m); - models[i] = m; - }; - } - - var data = new RowSet(); - - this.model.records.each(function(doc){ - data.push(doc, toRow(doc)); - }); - - this.grid = new Slick.Grid(this.el, data, visibleColumns, options); - // Column sorting - var sortInfo = this.model.queryState.get('sort'); - if (sortInfo){ - var column = sortInfo[0].field; - var sortAsc = sortInfo[0].order !== 'desc'; - this.grid.setSortColumn(column, sortAsc); - } - - if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) { - this._setupRowReordering(); - } - - this._slickHandler.subscribe(this.grid.onSort, function(e, args){ - var order = (args.sortAsc) ? 'asc':'desc'; - var sort = [{ - field: args.sortCol.field, - order: order - }]; - self.model.query({sort: sort}); - }); - - this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){ - self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')}); - }); - - this.grid.onColumnsResized.subscribe(function(e, args){ - var columns = args.grid.getColumns(); - var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth; - var columnsWidth = []; - _.each(columns,function(column){ - if (column.width != defaultColumnWidth){ - columnsWidth.push({column:column.id,width:column.width}); - } - }); - self.state.set({columnsWidth:columnsWidth}); - }); - - this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) { - // We need to change the model associated value - var grid = args.grid; - var model = data.getModel(args.row); - var field = grid.getColumns()[args.cell].id; - var v = {}; - v[field] = args.item[field]; - model.set(v); - }); - this._slickHandler.subscribe(this.grid.onClick,function(e, args){ - //try catch , because this fail in qunit , but no - //error on browser. - try{e.preventDefault()}catch(e){} - - // The cell of grid that handle row delete is The first cell (0) if - // The grid ReOrder is not present ie enableReOrderRow == false - // else it is The the second cell (1) , because The 0 is now cell - // that handle row Reoder. - var cell =0 - if(self.state.get("gridOptions") - && self.state.get("gridOptions").enableReOrderRow != undefined - && self.state.get("gridOptions").enableReOrderRow == true ){ - cell =1 - } - if (args.cell == cell && self.state.get("gridOptions").enabledDelRow == true){ - // We need to delete the associated model - var model = data.getModel(args.row); - model.destroy() - } - }) ; - var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid, - _.extend(options,{state:this.state})); - if (self.visible){ - self.grid.init(); - self.rendered = true; - } else { - // Defer rendering until the view is visible - self.rendered = false; - } - return this; - }, - - // Row reordering support based on - // https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example9-row-reordering.html - _setupRowReordering: function() { - var self = this; - self.grid.setSelectionModel(new Slick.RowSelectionModel()); - - var moveRowsPlugin = new Slick.RowMoveManager({ - cancelEditOnDrag: true - }); - - moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, data) { - for (var i = 0; i < data.rows.length; i++) { - // no point in moving before or after itself - if (data.rows[i] == data.insertBefore || data.rows[i] == data.insertBefore - 1) { - e.stopPropagation(); - return false; - } - } - return true; - }); - - moveRowsPlugin.onMoveRows.subscribe(function (e, args) { - var extractedRows = [], left, right; - var rows = args.rows; - var insertBefore = args.insertBefore; - - var data = self.model.records.toJSON() - left = data.slice(0, insertBefore); - right= data.slice(insertBefore, data.length); - - rows.sort(function(a,b) { return a-b; }); - - for (var i = 0; i < rows.length; i++) { - extractedRows.push(data[rows[i]]); - } - - rows.reverse(); - - for (var i = 0; i < rows.length; i++) { - var row = rows[i]; - if (row < insertBefore) { - left.splice(row, 1); - } else { - right.splice(row - insertBefore, 1); - } - } - - data = left.concat(extractedRows.concat(right)); - var selectedRows = []; - for (var i = 0; i < rows.length; i++) - selectedRows.push(left.length + i); - - self.model.records.reset(data) - - }); - //register The plugin to handle row Reorder - if(this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) { - self.grid.registerPlugin(moveRowsPlugin); - } - }, - - remove: function () { - this._slickHandler.unsubscribeAll(); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - show: function() { - // If the div is hidden, SlickGrid will calculate wrongly some - // sizes so we must render it explicitly when the view is visible - if (!this.rendered){ - if (!this.grid){ - this.render(); - } - this.grid.init(); - this.rendered = true; - } - this.visible = true; - }, - - hide: function() { - this.visible = false; - } -}); - -// Add new grid Control to display a new row add menu bouton -// It display a simple side-bar menu ,for user to add new -// row to grid -my.GridControl= Backbone.View.extend({ - className: "recline-row-add", - // Template for row edit menu , change it if you don't love - template: '

', - - initialize: function(options){ - var self = this; - _.bindAll(this, 'render'); - this.state = new recline.Model.ObjectState(); - this.render(); - }, - - render: function() { - var self = this; - this.$el.html(this.template) - }, - - events : { - "click .recline-row-add" : "addNewRow" - }, - - addNewRow : function(e){ - e.preventDefault() - this.state.trigger("change") - } -}); - -})(jQuery, recline.View); - -/* -* Context menu for the column picker, adapted from -* http://mleibman.github.com/SlickGrid/examples/example-grouping -* -*/ -(function ($) { - function SlickColumnPicker(columns, grid, options) { - var $menu; - var columnCheckboxes; - - var defaults = { - fadeSpeed:250 - }; - - function init() { - grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); - options = $.extend({}, defaults, options); - - $menu = $('
',templateSeriesEditor:'
',events:{"change form select":"onEditorSubmit","click .editor-add":"_onAddSeries","click .action-remove-series":"removeSeries"},initialize:function(options){var self=this;_.bindAll(this,"render");this.listenTo(this.model.fields,"reset add",this.render);this.state=new recline.Model.ObjectState(options.state);this.render()},render:function(){var self=this;var tmplData=this.model.toTemplateJSON();var htmls=Mustache.render(this.template,tmplData);this.$el.html(htmls);if(this.state.get("graphType")){this._selectOption(".editor-type",this.state.get("graphType"))}if(this.state.get("group")){this._selectOption(".editor-group",this.state.get("group"))}var tmpSeries=[""];if(this.state.get("series").length>0){tmpSeries=this.state.get("series")}_.each(tmpSeries,function(series,idx){self.addSeries(idx);self._selectOption(".editor-series.js-series-"+idx,series)});return this},_selectOption:function(id,value){var options=this.$el.find(id+" select > option");if(options){options.each(function(opt){if(this.value==value){$(this).attr("selected","selected");return false}})}},onEditorSubmit:function(e){var select=this.$el.find(".editor-group select");var $editor=this;var $series=this.$el.find(".editor-series select");var series=$series.map(function(){return $(this).val()});var updatedState={series:$.makeArray(series),group:this.$el.find(".editor-group select").val(),graphType:this.$el.find(".editor-type select").val()};this.state.set(updatedState)},addSeries:function(idx){var data=_.extend({seriesIndex:idx,seriesName:String.fromCharCode(idx+64+1)},this.model.toTemplateJSON());var htmls=Mustache.render(this.templateSeriesEditor,data);this.$el.find(".editor-series-group").append(htmls);return this},_onAddSeries:function(e){e.preventDefault();this.addSeries(this.state.get("series").length)},removeSeries:function(e){e.preventDefault();var $el=$(e.target);$el.parent().parent().remove();this.onEditorSubmit()}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};this.recline.View.Graph=this.recline.View.Flot;this.recline.View.GraphControls=this.recline.View.FlotControls;this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.Grid=Backbone.View.extend({tagName:"div",className:"recline-grid-container",initialize:function(modelEtc){var self=this;_.bindAll(this,"render","onHorizontalScroll");this.listenTo(this.model.records,"add reset remove",this.render);this.tempState={};var state=_.extend({hiddenFields:[]},modelEtc.state);this.state=new recline.Model.ObjectState(state)},events:{},setColumnSort:function(order){var sort=[{}];sort[0][this.tempState.currentColumn]={order:order};this.model.query({sort:sort})},hideColumn:function(){var hiddenFields=this.state.get("hiddenFields");hiddenFields.push(this.tempState.currentColumn);this.state.set({hiddenFields:hiddenFields});this.state.trigger("change");this.render()},showColumn:function(e){var hiddenFields=_.without(this.state.get("hiddenFields"),$(e.target).data("column"));this.state.set({hiddenFields:hiddenFields});this.render()},onHorizontalScroll:function(e){var currentScroll=$(e.target).scrollLeft();this.$el.find(".recline-grid thead tr").scrollLeft(currentScroll)},template:'
{{#fields}} {{/fields}}
{{label}}
',toTemplateJSON:function(){var self=this;var modelData=this.model.toJSON();modelData.notEmpty=this.fields.length>0;modelData.fields=this.fields.map(function(field){return field.toJSON()});modelData.lastHeaderWidth=this.scrollbarDimensions.width-2;return modelData},render:function(){var self=this;this.fields=new recline.Model.FieldList(this.model.fields.filter(function(field){return _.indexOf(self.state.get("hiddenFields"),field.id)==-1}));this.scrollbarDimensions=this.scrollbarDimensions||this._scrollbarSize();var numFields=this.fields.length;var fullWidth=self.$el.width()-20-10*numFields-this.scrollbarDimensions.width;var width=parseInt(Math.max(50,fullWidth/numFields),10);var remainder=Math.max(fullWidth-numFields*width,0);this.fields.each(function(field,idx){if(idx===0){field.set({width:width+remainder})}else{field.set({width:width})}});var htmls=Mustache.render(this.template,this.toTemplateJSON());this.$el.html(htmls);this.model.records.forEach(function(doc){var tr=$("");self.$el.find("tbody").append(tr);var newView=new my.GridRow({model:doc,el:tr,fields:self.fields});newView.render()});var $tbody=this.$el.find("tbody")[0];if($tbody.scrollHeight<=$tbody.offsetHeight){this.$el.find("th.last-header").hide()}this.$el.find(".recline-grid").toggleClass("no-hidden",self.state.get("hiddenFields").length===0);this.$el.find(".recline-grid tbody").scroll(this.onHorizontalScroll);return this},_scrollbarSize:function(){var $c=$("
").appendTo("body");var dim={width:$c.width()-$c[0].clientWidth+1,height:$c.height()-$c[0].clientHeight};$c.remove();return dim}});my.GridRow=Backbone.View.extend({initialize:function(initData){_.bindAll(this,"render");this._fields=initData.fields;this.listenTo(this.model,"change",this.render)},template:' {{#cells}}
 
{{{value}}}
{{/cells}} ',events:{"click .data-table-cell-edit":"onEditClick","click .data-table-cell-editor .okButton":"onEditorOK","click .data-table-cell-editor .cancelButton":"onEditorCancel"},toTemplateJSON:function(){var self=this;var doc=this.model;var cellData=this._fields.map(function(field){return{field:field.id,width:field.get("width"),value:doc.getFieldValue(field)}});return{id:this.id,cells:cellData}},render:function(){this.$el.attr("data-id",this.model.id); -var html=Mustache.render(this.template,this.toTemplateJSON());this.$el.html(html);return this},cellEditorTemplate:' ',onEditClick:function(e){var editing=this.$el.find(".data-table-cell-editor-editor");if(editing.length>0){editing.parents(".data-table-cell-value").html(editing.text()).siblings(".data-table-cell-edit").removeClass("hidden")}$(e.target).addClass("hidden");var cell=$(e.target).siblings(".data-table-cell-value");cell.data("previousContents",cell.text());var templated=Mustache.render(this.cellEditorTemplate,{value:cell.text()});cell.html(templated)},onEditorOK:function(e){var self=this;var cell=$(e.target);var rowId=cell.parents("tr").attr("data-id");var field=cell.parents("td").attr("data-field");var newValue=cell.parents(".data-table-cell-editor").find(".data-table-cell-editor-editor").val();var newData={};newData[field]=newValue;this.model.set(newData);this.trigger("recline:flash",{message:"Updating row...",loader:true});this.model.save().then(function(response){this.trigger("recline:flash",{message:"Row updated successfully",category:"success"})}).fail(function(){this.trigger("recline:flash",{message:"Error saving row",category:"error",persist:true})})},onEditorCancel:function(e){var cell=$(e.target).parents(".data-table-cell-value");cell.html(cell.data("previousContents")).siblings(".data-table-cell-edit").removeClass("hidden")}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.Map=Backbone.View.extend({template:'
',latitudeFieldNames:["lat","latitude"],longitudeFieldNames:["lon","longitude"],geometryFieldNames:["geojson","geom","the_geom","geometry","spatial","location","geo","lonlat"],initialize:function(options){var self=this;this.visible=this.$el.is(":visible");this.mapReady=false;this.map=null;var stateData=_.extend({geomField:null,lonField:null,latField:null,autoZoom:true,cluster:false},options.state);this.state=new recline.Model.ObjectState(stateData);this._clusterOptions={zoomToBoundsOnClick:true,maxClusterRadius:80,singleMarkerMode:false,skipDuplicateAddTesting:true,animateAddingMarkers:false};this.listenTo(this.model.fields,"change",function(){self._setupGeometryField();self.render()});this.listenTo(this.model.records,"add",function(doc){self.redraw("add",doc)});this.listenTo(this.model.records,"change",function(doc){self.redraw("remove",doc);self.redraw("add",doc)});this.listenTo(this.model.records,"remove",function(doc){self.redraw("remove",doc)});this.listenTo(this.model.records,"reset",function(){self.redraw("reset")});this.menu=new my.MapMenu({model:this.model,state:this.state.toJSON()});this.listenTo(this.menu.state,"change",function(){self.state.set(self.menu.state.toJSON());self.redraw()});this.listenTo(this.state,"change",function(){self.redraw()});this.elSidebar=this.menu.$el},infobox:function(record){var html="";for(var key in record.attributes){if(!(this.state.get("geomField")&&key==this.state.get("geomField"))){html+="
"+key+": "+record.attributes[key]+"
"}}return html},geoJsonLayerOptions:{pointToLayer:function(feature,latlng){var marker=new L.Marker(latlng);marker.bindPopup(feature.properties.popupContent);this.markers.addLayer(marker);return marker},onEachFeature:function(feature,layer){if(feature.properties&&feature.properties.popupContent){layer.bindPopup(feature.properties.popupContent)}}},render:function(){var self=this;var htmls=Mustache.render(this.template,this.model.toTemplateJSON());this.$el.html(htmls);this.$map=this.$el.find(".panel.map");this.redraw();return this},redraw:function(action,doc){var self=this;action=action||"refresh";if(!self._geomReady()){self._setupGeometryField()}if(!self.mapReady){self._setupMap()}if(this._geomReady()&&this.mapReady){this.map.removeLayer(this.features);this.map.removeLayer(this.markers);var countBefore=0;this.features.eachLayer(function(){countBefore++});if(action=="refresh"||action=="reset"){this.features.clearLayers();this.map.removeLayer(this.markers);this.markers=new L.MarkerClusterGroup(this._clusterOptions);this._add(this.model.records.models)}else if(action=="add"&&doc){this._add(doc)}else if(action=="remove"&&doc){this._remove(doc)}if(this.state.get("cluster")){this.map.addLayer(this.markers)}else{this.map.addLayer(this.features)}if(this.state.get("autoZoom")){if(this.visible){this._zoomToFeatures()}else{this._zoomPending=true}}}},show:function(){if(this.map){this.map.invalidateSize();if(this._zoomPending&&this.state.get("autoZoom")){this._zoomToFeatures();this._zoomPending=false}}this.visible=true},hide:function(){this.visible=false},_geomReady:function(){return Boolean(this.state.get("geomField")||this.state.get("latField")&&this.state.get("lonField"))},_add:function(docs){var self=this;if(!(docs instanceof Array))docs=[docs];var count=0;var wrongSoFar=0;_.every(docs,function(doc){count+=1;var feature=self._getGeometryFromRecord(doc);if(typeof feature==="undefined"||feature===null){return true}else if(feature instanceof Object){feature.properties={popupContent:self.infobox(doc),cid:doc.cid};try{self.features.addData(feature)}catch(except){wrongSoFar+=1;var msg="Wrong geometry value";if(except.message)msg+=" ("+except.message+")";if(wrongSoFar<=10){self.trigger("recline:flash",{message:msg,category:"error"})}}}else{wrongSoFar+=1;if(wrongSoFar<=10){self.trigger("recline:flash",{message:"Wrong geometry value",category:"error"})}}return true})},_remove:function(docs){var self=this;if(!(docs instanceof Array))docs=[docs];_.each(docs,function(doc){for(var key in self.features._layers){if(self.features._layers[key].feature.geometry.properties.cid==doc.cid){self.features.removeLayer(self.features._layers[key])}}})},_parseCoordinateString:function(coord){if(typeof coord!="string"){return parseFloat(coord)}var dms=coord.split(/[^-?\.\d\w]+/);var deg=0;var m=0;var toDeg=[1,60,3600];var i;for(i=0;i select > option");if(options){options.each(function(opt){if(this.value==value){$(this).attr("selected","selected");return false}})}}});my.MapMenu=Backbone.View.extend({className:"editor",template:'
',events:{"click .editor-update-map":"onEditorSubmit","change .editor-field-type":"onFieldTypeChange","click #editor-auto-zoom":"onAutoZoomChange","click #editor-cluster":"onClusteringChange"},initialize:function(options){var self=this;_.bindAll(this,"render");this.listenTo(this.model.fields,"change",this.render);this.state=new recline.Model.ObjectState(options.state);this.listenTo(this.state,"change",this.render);this.render()},render:function(){var self=this;var htmls=Mustache.render(this.template,this.model.toTemplateJSON());this.$el.html(htmls);if(this._geomReady()&&this.model.fields.length){if(this.state.get("geomField")){this._selectOption("editor-geom-field",this.state.get("geomField"));this.$el.find("#editor-field-type-geom").attr("checked","checked").change()}else{this._selectOption("editor-lon-field",this.state.get("lonField"));this._selectOption("editor-lat-field",this.state.get("latField"));this.$el.find("#editor-field-type-latlon").attr("checked","checked").change()}}if(this.state.get("autoZoom")){this.$el.find("#editor-auto-zoom").attr("checked","checked")}else{this.$el.find("#editor-auto-zoom").removeAttr("checked")}if(this.state.get("cluster")){this.$el.find("#editor-cluster").attr("checked","checked")}else{this.$el.find("#editor-cluster").removeAttr("checked")}return this},_geomReady:function(){return Boolean(this.state.get("geomField")||this.state.get("latField")&&this.state.get("lonField"))},onEditorSubmit:function(e){e.preventDefault();if(this.$el.find("#editor-field-type-geom").attr("checked")){this.state.set({geomField:this.$el.find(".editor-geom-field > select > option:selected").val(),lonField:null,latField:null})}else{this.state.set({geomField:null,lonField:this.$el.find(".editor-lon-field > select > option:selected").val(),latField:this.$el.find(".editor-lat-field > select > option:selected").val()})}return false},onFieldTypeChange:function(e){if(e.target.value=="geom"){this.$el.find(".editor-field-type-geom").show();this.$el.find(".editor-field-type-latlon").hide()}else{this.$el.find(".editor-field-type-geom").hide();this.$el.find(".editor-field-type-latlon").show()}},onAutoZoomChange:function(e){this.state.set({autoZoom:!this.state.get("autoZoom")})},onClusteringChange:function(e){this.state.set({cluster:!this.state.get("cluster")})},_selectOption:function(id,value){var options=this.$el.find("."+id+" > select > option");if(options){options.each(function(opt){if(this.value==value){$(this).attr("selected","selected");return false}})}}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.MultiView=Backbone.View.extend({template:'
{{recordCount}} records
',events:{"click .menu-right button":"_onMenuClick","click .navigation button":"_onSwitchView"},initialize:function(options){var self=this;this._setupState(options.state);if(options.views){this.pageViews=options.views}else{this.pageViews=[{id:"grid",label:"Grid",view:new my.SlickGrid({model:this.model,state:this.state.get("view-grid")})},{id:"graph",label:"Graph",view:new my.Graph({model:this.model,state:this.state.get("view-graph")})},{id:"map",label:"Map",view:new my.Map({model:this.model,state:this.state.get("view-map")})},{id:"timeline",label:"Timeline",view:new my.Timeline({model:this.model,state:this.state.get("view-timeline")})}]}if(options.sidebarViews){this.sidebarViews=options.sidebarViews}else{this.sidebarViews=[{id:"filterEditor",label:"Filters",view:new my.FilterEditor({model:this.model})},{id:"fieldsView",label:"Fields",view:new my.Fields({model:this.model})}]}this.render();this._bindStateChanges();this._bindFlashNotifications();if(this.state.get("readOnly")){this.setReadOnly()}if(this.state.get("currentView")){this.updateNav(this.state.get("currentView"))}else{this.updateNav(this.pageViews[0].id)}this._showHideSidebar();this.listenTo(this.model,"query:start",function(){self.notify({loader:true,persist:true})});this.listenTo(this.model,"query:done",function(){self.clearNotifications();self.$el.find(".doc-count").text(self.model.recordCount||"Unknown")});this.listenTo(this.model,"query:fail",function(error){self.clearNotifications();var msg="";if(typeof error=="string"){msg=error}else if(typeof error=="object"){if(error.title){msg=error.title+": "}if(error.message){msg+=error.message}}else{msg="There was an error querying the backend"}self.notify({message:msg,category:"error",persist:true})});this.model.queryState.set(self.state.get("query"),{silent:true})},setReadOnly:function(){this.$el.addClass("recline-read-only")},render:function(){var tmplData=this.model.toTemplateJSON();tmplData.views=this.pageViews;tmplData.sidebarViews=this.sidebarViews;var template=Mustache.render(this.template,tmplData);this.$el.html(template);var $dataViewContainer=this.$el.find(".data-view-container");var $dataSidebar=this.$el.find(".data-view-sidebar");_.each(this.pageViews,function(view,pageName){view.view.render();if(view.view.redraw){view.view.redraw()}$dataViewContainer.append(view.view.el);if(view.view.elSidebar){$dataSidebar.append(view.view.elSidebar)}});_.each(this.sidebarViews,function(view){this["$"+view.id]=view.view.$el;$dataSidebar.append(view.view.el)},this);this.pager=new recline.View.Pager({model:this.model});this.$el.find(".recline-results-info").after(this.pager.el);this.queryEditor=new recline.View.QueryEditor({model:this.model.queryState});this.$el.find(".query-editor-here").append(this.queryEditor.el)},remove:function(){_.each(this.pageViews,function(view){view.view.remove()});_.each(this.sidebarViews,function(view){view.view.remove()});this.pager.remove();this.queryEditor.remove();Backbone.View.prototype.remove.apply(this,arguments)},_showHideSidebar:function(){var $dataSidebar=this.$el.find(".data-view-sidebar");var visibleChildren=$dataSidebar.children().filter(function(){return $(this).css("display")!="none"}).length;if(visibleChildren>0){$dataSidebar.show()}else{$dataSidebar.hide()}},updateNav:function(pageName){this.$el.find(".navigation button").removeClass("active");var $el=this.$el.find('.navigation button[data-view="'+pageName+'"]');$el.addClass("active");_.each(this.pageViews,function(view,idx){if(view.id===pageName){view.view.$el.show();if(view.view.elSidebar){view.view.elSidebar.show()}}else{view.view.$el.hide();if(view.view.elSidebar){view.view.elSidebar.hide()}if(view.view.hide){view.view.hide()}}});this._showHideSidebar();_.each(this.pageViews,function(view,idx){if(view.id===pageName){if(view.view.show){view.view.show()}}})},_onMenuClick:function(e){e.preventDefault();var action=$(e.target).attr("data-action");this["$"+action].toggle();this._showHideSidebar()},_onSwitchView:function(e){e.preventDefault();var viewName=$(e.target).attr("data-view");this.updateNav(viewName);this.state.set({currentView:viewName})},_setupState:function(initialState){var self=this;var qs=my.parseHashQueryString();var query=qs.reclineQuery;query=query?JSON.parse(query):self.model.queryState.toJSON();var graphState=qs["view-graph"]||qs.graph;graphState=graphState?JSON.parse(graphState):{};var stateData=_.extend({query:query,"view-graph":graphState,backend:this.model.backend.__type__,url:this.model.get("url"),dataset:this.model.toJSON(),currentView:null,readOnly:false},initialState);this.state=new recline.Model.ObjectState(stateData)},_bindStateChanges:function(){var self=this;this.listenTo(this.model.queryState,"change",function(){self.state.set({query:self.model.queryState.toJSON()})});_.each(this.pageViews,function(pageView){if(pageView.view.state&&pageView.view.state.bind){var update={};update["view-"+pageView.id]=pageView.view.state.toJSON();self.state.set(update);self.listenTo(pageView.view.state,"change",function(){var update={};update["view-"+pageView.id]=pageView.view.state.toJSON();self.state.set(update,{silent:true});self.state.trigger("change")})}})},_bindFlashNotifications:function(){var self=this;_.each(this.pageViews,function(pageView){self.listenTo(pageView.view,"recline:flash",function(flash){self.notify(flash)})})},notify:function(flash){var tmplData=_.extend({message:"Loading",category:"warning",loader:false},flash);var _template;if(tmplData.loader){_template='
{{message}}  
'}else{_template='
Ɨ {{message}}
'}var _templated=$(Mustache.render(_template,tmplData));_templated=$(_templated).appendTo($(".recline-data-explorer .alert-messages"));if(!flash.persist){setTimeout(function(){$(_templated).fadeOut(1e3,function(){$(this).remove()})},1e3)}},clearNotifications:function(){var $notifications=$(".recline-data-explorer .alert-messages .alert");$notifications.fadeOut(1500,function(){$(this).remove()})}});my.MultiView.restore=function(state){var datasetInfo;if(state.backend==="memory"){datasetInfo={backend:"memory",records:[{stub:"this is a stub dataset because we do not restore memory datasets"}]}}else{datasetInfo=_.extend({url:state.url,backend:state.backend},state.dataset)}var dataset=new recline.Model.Dataset(datasetInfo);var explorer=new my.MultiView({model:dataset,state:state});return explorer};var urlPathRegex=/^([^?]+)(\?.*)?/;my.parseHashUrl=function(hashUrl){var parsed=urlPathRegex.exec(hashUrl);if(parsed===null){return{}}else{return{path:parsed[1],query:parsed[2]||""}}};my.parseQueryString=function(q){if(!q){return{}}var urlParams={},e,d=function(s){return unescape(s.replace(/\+/g," "))},r=/([^&=]+)=?([^&]*)/g;if(q&&q.length&&q[0]==="?"){q=q.slice(1)}while(e=r.exec(q)){urlParams[d(e[1])]=d(e[2])}return urlParams};my.parseHashQueryString=function(){var q=my.parseHashUrl(window.location.hash).query;return my.parseQueryString(q)};my.composeQueryString=function(queryParams){var queryString="?";var items=[];$.each(queryParams,function(key,value){if(typeof value==="object"){value=JSON.stringify(value)}items.push(key+"="+encodeURIComponent(value))});queryString+=items.join("&");return queryString};my.getNewHashForQueryString=function(queryParams){var queryPart=my.composeQueryString(queryParams);if(window.location.hash){return window.location.hash.split("?")[0].slice(1)+queryPart}else{return queryPart}};my.setHashQueryString=function(queryParams){window.location.hash=my.getNewHashForQueryString(queryParams)}})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.SlickGrid=Backbone.View.extend({initialize:function(modelEtc){var self=this;this.$el.addClass("recline-slickgrid");this.templates={deleterow:''};_.bindAll(this,"render","onRecordChanged");this.listenTo(this.model.records,"add remove reset",this.render);this.listenTo(this.model.records,"change",this.onRecordChanged);var state=_.extend({hiddenColumns:[],columnsOrder:[],columnsSort:{},columnsWidth:[],columnsEditor:[],options:{},fitColumns:false},modelEtc.state);this.state=new recline.Model.ObjectState(state);this._slickHandler=new Slick.EventHandler;if(this.state.get("gridOptions")&&this.state.get("gridOptions").enabledAddRow!=undefined&&this.state.get("gridOptions").enabledAddRow==true){this.editor=new my.GridControl;this.elSidebar=this.editor.$el;this.listenTo(this.editor.state,"change",function(){this.model.records.add(new recline.Model.Record)})}},onRecordChanged:function(record){if(!this.grid){return}var row_index=this.grid.getData().getModelRow(record);this.grid.invalidateRow(row_index);this.grid.getData().updateItem(record,row_index);this.grid.render()},render:function(){var self=this;var options=_.extend({enableCellNavigation:true,enableColumnReorder:true,explicitInitialization:true,syncColumnCellResize:true,forceFitColumns:this.state.get("fitColumns")},self.state.get("gridOptions"));var columns=[];var formatter=function(row,cell,value,columnDef,dataContext){if(columnDef.id=="del"){return self.templates.deleterow}var field=self.model.fields.get(columnDef.id);if(field.renderer){return field.renderer(value,field,dataContext)}else{return value}};var validator=function(field){return function(value){if(field.type=="date"&&isNaN(Date.parse(value))){return{valid:false,msg:"A date is required, check field field-date-format"}}else{return{valid:true,msg:null}}}};if(this.state.get("gridOptions")&&this.state.get("gridOptions").enableReOrderRow==true){columns.push({id:"#",name:"",width:22,behavior:"selectAndMove",selectable:false,resizable:false,cssClass:"recline-cell-reorder"})}if(this.state.get("gridOptions")&&this.state.get("gridOptions").enabledDelRow==true){columns.push({id:"del",name:"",field:"del",sortable:true,width:38,formatter:formatter,validator:validator})}function sanitizeFieldName(name){var sanitized;try{sanitized=$(name).text()}catch(e){sanitized=""}return name!==sanitized&&sanitized!==""?sanitized:name}_.each(this.model.fields.toJSON(),function(field){var column={id:field.id,name:sanitizeFieldName(field.label),field:field.id,sortable:true,minWidth:80,formatter:formatter,validator:validator(field)};var widthInfo=_.find(self.state.get("columnsWidth"),function(c){return c.column===field.id});if(widthInfo){column.width=widthInfo.width}var editInfo=_.find(self.state.get("columnsEditor"),function(c){return c.column===field.id});if(editInfo){column.editor=editInfo.editor}else{var typeToEditorMap={string:Slick.Editors.LongText,integer:Slick.Editors.IntegerEditor,number:Slick.Editors.Text,date:Slick.Editors.Text,boolean:Slick.Editors.YesNoSelectEditor};if(field.type in typeToEditorMap){column.editor=typeToEditorMap[field.type]}else{column.editor=Slick.Editors.LongText}}columns.push(column)});var visibleColumns=_.filter(columns,function(column){return _.indexOf(self.state.get("hiddenColumns"),column.id)===-1});if(this.state.get("columnsOrder")&&this.state.get("columnsOrder").length>0){visibleColumns=visibleColumns.sort(function(a,b){return _.indexOf(self.state.get("columnsOrder"),a.id)>_.indexOf(self.state.get("columnsOrder"),b.id)?1:-1});columns=columns.sort(function(a,b){return _.indexOf(self.state.get("columnsOrder"),a.id)>_.indexOf(self.state.get("columnsOrder"),b.id)?1:-1})}var tempHiddenColumns=[];for(var i=columns.length-1;i>=0;i--){if(_.indexOf(_.pluck(visibleColumns,"id"),columns[i].id)===-1){tempHiddenColumns.push(columns.splice(i,1)[0])}}columns=columns.concat(tempHiddenColumns);function toRow(m){var row={};self.model.fields.each(function(field){var render="";if(!_.isUndefined(m.getFieldValueUnrendered(field))){render=m.getFieldValueUnrendered(field)}row[field.id]=render});return row}function RowSet(){var models=[];var rows=[];this.push=function(model,row){models.push(model);rows.push(row)};this.getLength=function(){return rows.length};this.getItem=function(index){return rows[index]};this.getItemMetadata=function(index){return{}};this.getModel=function(index){return models[index]};this.getModelRow=function(m){return _.indexOf(models,m)};this.updateItem=function(m,i){rows[i]=toRow(m);models[i]=m}}var data=new RowSet;this.model.records.each(function(doc){data.push(doc,toRow(doc))});this.grid=new Slick.Grid(this.el,data,visibleColumns,options);var sortInfo=this.model.queryState.get("sort");if(sortInfo){var column=sortInfo[0].field;var sortAsc=sortInfo[0].order!=="desc";this.grid.setSortColumn(column,sortAsc)}if(this.state.get("gridOptions")&&this.state.get("gridOptions").enableReOrderRow){this._setupRowReordering()}this._slickHandler.subscribe(this.grid.onSort,function(e,args){var order=args.sortAsc?"asc":"desc";var sort=[{field:args.sortCol.field,order:order}];self.model.query({sort:sort})});this._slickHandler.subscribe(this.grid.onColumnsReordered,function(e,args){self.state.set({columnsOrder:_.pluck(self.grid.getColumns(),"id")})});this.grid.onColumnsResized.subscribe(function(e,args){var columns=args.grid.getColumns();var defaultColumnWidth=args.grid.getOptions().defaultColumnWidth;var columnsWidth=[];_.each(columns,function(column){if(column.width!=defaultColumnWidth){columnsWidth.push({column:column.id,width:column.width})}});self.state.set({columnsWidth:columnsWidth})});this._slickHandler.subscribe(this.grid.onCellChange,function(e,args){var grid=args.grid;var model=data.getModel(args.row);var field=grid.getColumns()[args.cell].id;var v={};v[field]=args.item[field];model.set(v)});this._slickHandler.subscribe(this.grid.onClick,function(e,args){try{e.preventDefault()}catch(e){}var cell=0;if(self.state.get("gridOptions")&&self.state.get("gridOptions").enableReOrderRow!=undefined&&self.state.get("gridOptions").enableReOrderRow==true){cell=1}if(args.cell==cell&&self.state.get("gridOptions").enabledDelRow==true){var model=data.getModel(args.row);model.destroy()}});var columnpicker=new Slick.Controls.ColumnPicker(columns,this.grid,_.extend(options,{state:this.state}));if(self.visible){self.grid.init();self.rendered=true}else{self.rendered=false}return this},_setupRowReordering:function(){var self=this;self.grid.setSelectionModel(new Slick.RowSelectionModel);var moveRowsPlugin=new Slick.RowMoveManager({cancelEditOnDrag:true});moveRowsPlugin.onBeforeMoveRows.subscribe(function(e,data){for(var i=0;i',initialize:function(options){var self=this;_.bindAll(this,"render");this.state=new recline.Model.ObjectState;this.render()},render:function(){var self=this;this.$el.html(this.template)},events:{"click .recline-row-add":"addNewRow"},addNewRow:function(e){e.preventDefault();this.state.trigger("change")}})})(jQuery,recline.View);(function($){function SlickColumnPicker(columns,grid,options){var $menu;var columnCheckboxes;var defaults={fadeSpeed:250};function init(){grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu);options=$.extend({},defaults,options);$menu=$('
',events:{"click .js-facet-filter":"onFacetFilter"},initialize:function(model){_.bindAll(this,"render");this.listenTo(this.model.facets,"all",this.render);this.listenTo(this.model.fields,"all",this.render);this.render()},render:function(){var tmplData={fields:this.model.fields.toJSON()};tmplData.facets=_.map(this.model.facets.toJSON(),function(facet){if(facet._type==="date_histogram"){facet.entries=_.map(facet.entries,function(entry){entry.term=new Date(entry.time).toDateString();return entry})}return facet});var templated=Mustache.render(this.template,tmplData);this.$el.html(templated);if(this.model.facets.length>0){this.$el.show()}else{this.$el.hide()}},onHide:function(e){e.preventDefault();this.$el.hide()},onFacetFilter:function(e){e.preventDefault();var $target=$(e.target);var fieldId=$target.closest(".facet-summary").attr("data-facet");var value=$target.attr("data-value");this.model.queryState.addFilter({type:"term",field:fieldId,term:value});this.model.query()}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.Fields=Backbone.View.extend({className:"recline-fields-view",template:'

Fields +

{{#fields}}

{{label}} {{type}} »

{{#facets}}
    {{#terms}}
  • {{term}} [{{count}}]
  • {{/terms}}
{{/facets}}
{{/fields}}
',initialize:function(model){var self=this;_.bindAll(this,"render");this.listenTo(this.model.fields,"reset",function(action){self.model.fields.each(function(field){field.facets.unbind("all",self.render);field.facets.bind("all",self.render)});self.model.getFieldsSummary();self.render()});this.$el.find(".collapse").collapse();this.render()},render:function(){var self=this;var tmplData={fields:[]};this.model.fields.each(function(field){var out=field.toJSON();out.facets=field.facets.toJSON();tmplData.fields.push(out)});var templated=Mustache.render(this.template,tmplData);this.$el.html(templated)}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.FilterEditor=Backbone.View.extend({className:"recline-filter-editor well",template:'

Filters

Add filter
{{#filters}} {{{filterRender}}} {{/filters}} {{#filters.length}} {{/filters.length}}
',filterTemplates:{term:'
{{field}} {{type}} ×
',range:'
{{field}} {{type}} ×
',geo_distance:'
{{field}} {{type}} ×
'},events:{"click .js-remove-filter":"onRemoveFilter","click .js-add-filter":"onAddFilterShow","submit form.js-edit":"onTermFiltersUpdate","submit form.js-add":"onAddFilter"},initialize:function(){_.bindAll(this,"render");this.listenTo(this.model.fields,"all",this.render);this.listenTo(this.model.queryState,"change change:filters:new-blank",this.render);this.render()},render:function(){var self=this;var tmplData=$.extend(true,{},this.model.queryState.toJSON());tmplData.filters=_.map(tmplData.filters,function(filter,idx){filter.id=idx;return filter});tmplData.fields=this.model.fields.toJSON();tmplData.filterRender=function(){return Mustache.render(self.filterTemplates[this.type],this)};var out=Mustache.render(this.template,tmplData);this.$el.html(out)},onAddFilterShow:function(e){e.preventDefault();var $target=$(e.target);$target.hide();this.$el.find("form.js-add").show()},onAddFilter:function(e){e.preventDefault();var $target=$(e.target);$target.hide();var filterType=$target.find("select.filterType").val();var field=$target.find("select.fields").val();this.model.queryState.addFilter({type:filterType,field:field})},onRemoveFilter:function(e){e.preventDefault();var $target=$(e.target);var filterId=$target.attr("data-filter-id");this.model.queryState.removeFilter(filterId)},onTermFiltersUpdate:function(e){var self=this;e.preventDefault();var filters=self.model.queryState.get("filters");var $form=$(e.target);_.each($form.find("input"),function(input){var $input=$(input);var filterType=$input.attr("data-filter-type");var fieldId=$input.attr("data-filter-field");var filterIndex=parseInt($input.attr("data-filter-id"),10);var name=$input.attr("name");var value=$input.val();switch(filterType){case"term":filters[filterIndex].term=value;break;case"range":filters[filterIndex][name]=value;break;case"geo_distance":if(name==="distance"){filters[filterIndex].distance=parseFloat(value)}else{filters[filterIndex].point[name]=parseFloat(value)}break}});self.model.queryState.set({filters:filters,from:0});self.model.queryState.trigger("change")}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.Pager=Backbone.View.extend({className:"recline-pager",template:' ',events:{"click .action-pagination-update":"onPaginationUpdate","change input":"onFormSubmit"},initialize:function(){_.bindAll(this,"render");this.listenTo(this.model.queryState,"change",this.render);this.render()},onFormSubmit:function(e){e.preventDefault();var formFrom=parseInt(this.$el.find('input[name="from"]').val())-1;var formTo=parseInt(this.$el.find('input[name="to"]').val())-1;var maxRecord=this.model.recordCount-1;if(this.model.queryState.get("from")!=formFrom){this.model.queryState.set({from:Math.min(maxRecord,Math.max(formFrom,0))})}else if(this.model.queryState.get("to")!=formTo){var to=Math.min(maxRecord,Math.max(formTo,0));this.model.queryState.set({size:Math.min(maxRecord+1,Math.max(to-formFrom+1,1))})}},onPaginationUpdate:function(e){e.preventDefault();var $el=$(e.target);var newFrom=0;var currFrom=this.model.queryState.get("from");var size=this.model.queryState.get("size");var updateQuery=false;if($el.parent().hasClass("prev")){newFrom=Math.max(currFrom-Math.max(0,size),0);updateQuery=newFrom!=currFrom}else{newFrom=Math.max(currFrom+size,0);updateQuery=newFrom
',events:{"submit form":"onFormSubmit"},initialize:function(){_.bindAll(this,"render");this.listenTo(this.model,"change",this.render);this.render()},onFormSubmit:function(e){e.preventDefault();var query=this.$el.find(".search-query").val();this.model.set({q:query})},render:function(){var tmplData=this.model.toJSON();var templated=Mustache.render(this.template,tmplData);this.$el.html(templated)}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.ValueFilter=Backbone.View.extend({className:"recline-filter-editor well",template:'

Filters

{{#filters}} {{{filterRender}}} {{/filters}} {{#filters.length}} {{/filters.length}}
',filterTemplates:{term:'
{{field}} ×
'},events:{"click .js-remove-filter":"onRemoveFilter","click .js-add-filter":"onAddFilterShow","submit form.js-edit":"onTermFiltersUpdate","submit form.js-add":"onAddFilter"},initialize:function(){_.bindAll(this,"render");this.listenTo(this.model.fields,"all",this.render);this.listenTo(this.model.queryState,"change change:filters:new-blank",this.render);this.render()},render:function(){var self=this;var tmplData=$.extend(true,{},this.model.queryState.toJSON());tmplData.filters=_.map(tmplData.filters,function(filter,idx){filter.id=idx;return filter});tmplData.fields=this.model.fields.toJSON();tmplData.filterRender=function(){return Mustache.render(self.filterTemplates.term,this)};var out=Mustache.render(this.template,tmplData);this.$el.html(out)},updateFilter:function(input){var self=this;var filters=self.model.queryState.get("filters");var $input=$(input);var filterIndex=parseInt($input.attr("data-filter-id"),10);var value=$input.val();filters[filterIndex].term=value},onAddFilterShow:function(e){e.preventDefault();var $target=$(e.target);$target.hide();this.$el.find("form.js-add").show()},onAddFilter:function(e){e.preventDefault();var $target=$(e.target);$target.hide();var field=$target.find("select.fields").val();this.model.queryState.addFilter({type:"term",field:field})},onRemoveFilter:function(e){e.preventDefault();var $target=$(e.target);var filterId=$target.attr("data-filter-id");this.model.queryState.removeFilter(filterId)},onTermFiltersUpdate:function(e){var self=this;e.preventDefault();var filters=self.model.queryState.get("filters");var $form=$(e.target);_.each($form.find("input"),function(input){self.updateFilter(input)});self.model.queryState.set({filters:filters,from:0});self.model.queryState.trigger("change")}})})(jQuery,recline.View); \ No newline at end of file diff --git a/docs/backends.markdown b/docs/backends.markdown deleted file mode 100644 index 09adb183..00000000 --- a/docs/backends.markdown +++ /dev/null @@ -1,148 +0,0 @@ ---- -layout: container -title: Backends -root: ../ ---- - - - -Backends connect Dataset and Documents to data from a specific 'Backend' data -source. They provide methods for loading and saving Datasets and individuals -Documents as well as for bulk loading via a query API and doing bulk transforms -on the backend. - -
Looking for quickstart tutorial rather than reference documentation? See the Backends Tutorial.
- -Backends come in 2 flavours: - -* Loader backends - only implement fetch method. The data is then cached in a - Memory.Store on the Dataset and interacted with there. This is best for - sources which just allow you to load data or where you want to load the data - once and work with it locally. -* Store backends - these support fetch, query and, if write-enabled, save. - These are suitable where the backend contains a lot of data (infeasible to - load locally - for examples a million rows) or where the backend has - capabilities you want to take advantage of. - -Examples of the 2 types of backends are provided by the Google docs backend (a -"Loader" backend) and the ElasticSearch backend (a Store backend). - -# Available Backends - -You can find a list of the available Backends along with examples of how to use -them in the [Backends Tutorial](tutorial-backends.html). - -Note that it's easy to write your own backend - you just need to implement the -Recline Backend API described below. - -# Backend API - -Backend modules must implement the following API: - -{% highlight javascript %} -__type__: 'name-of-backend' // e.g. elasticsearch - -// Initial load of dataset including initial set of records -fetch: function(dataset) - -// Query the backend for records returning them in bulk. -// This method will be used by the Dataset.query method to search the backend -// for records, retrieving the results in bulk. -query: function(queryObj, dataset) - -// Save changes to the backend -save: function(changes, dataset) -{% endhighlight %} - -Details of each function below. Note that: - -* Each backend function takes a dataset object. This is not a Dataset object - but is simple JS object representation resulting from calling - Dataset.toJSON(). - - It is required because the Dataset attributes contain details of specific - backend (e.g. url for ElasticSearch etc). - -* Each function returns a promise API object - that is something conforming to - the jquery promise API and, in particular, having a done and fail function. - -### fetch: function(dataset) - -On success, promise callback must return an object with the following structure: - -{% highlight javascript %} -{ - // (optional) Set of record data - // Either an array of arrays *or* an array of objects corresponding to initial set of records for this object - // May not provided if data only returned by query - records: [...] - - // (optional) Set of field data - // Either an array of string or an array of objects corresponding to Field specification (see `Field` above) - fields: { ... } // as per recline.Model.Field - - // (optional) metadata fields to set on the Dataset object - metadata: { title: ..., id: ... etc } - - // boolean indicating whether to use a local memory store for managing this dataset - useMemoryStore: -} -{% endhighlight %} - -### query: function(queryObj, dataset) - -`queryObj`: JS object following Query specification above. - -#### Callbacks - -On success must return a 'QueryResult' object which has the following structure: - -{% highlight javascript %} -{ - // total number of results (can be null) - total: ... - - // one entry for each result record - hits: [ - { - // JS object that can be used to initialize a Record object - } - ], - - // (optional) - facets: { - // facet results (as per ) - } -} -{% endhighlight %} - -The QueryResult is partially modelled on ElasticSearch - see this issue for more -details. - -### save: function(changes, dataset) - -
The save function is still being revised and -its API and arguments are subject to change
- -`changes`: an object with the following structure: - -{% highlight javascript %} -{ - creates: [ record.toJSON(), record.toJSON(), ... ] - updates: [ ... ] - deletes: [ ... ] -} -{% endhighlight %} - -Each key has an array of records (as simple JS objects resulting from a call to -Record.toJSON()) that are in that state. - -The backend should take appropriate actions for each case. - - diff --git a/docs/extensions.md b/docs/extensions.md deleted file mode 100644 index 1626c434..00000000 --- a/docs/extensions.md +++ /dev/null @@ -1,64 +0,0 @@ -# Extensions - -Extensions are Views, Backends or other pieces of code that build on Recline but which do not ship as part of the core distribution. Here we have: - -* Instructions on creating an extension -* A list of existing extensions - * **If you have created an extension please add it to the list below** - -## Creating an Extension - -At its minimum its just a single JS file. However, we suggest a bit more structure: - -* Put the extension in its own repo named e.g. recline.view.{viewname} or recline.backend.{backendname} -* Including (essential): - * README.md - * the extension code itself name view.{name}.js or backend.{name}.js (or recline.view.{name}.js etc) -* Optional - * [optional but recommended] a test for the extension - * [optional] one or more demo html files showing your extension in action - -You may want to include recline as a submodule for testing purposes if you need some of its code - - git submodule add git://github.com/okfn/recline.git - -Or you can just pull the latest recline.js directly from: http://okfnlabs.org/recline/dist/recline.js - ----- - -## Views - -#### Line graphs in nvd3 - -Line graphs in [nvd3](http://nvd3.org/) - -* https://github.com/Moviri/recline/blob/master/src/extensions/views/view.nvd3.graph.js -* https://github.com/NuCivic/recline.view.nvd3.js - -#### Kartograph - -Integration with [Kartograph](http://kartograph.org/) - -* https://github.com/Moviri/recline/blob/master/src/extensions/views/view.kartograph.js - -#### Map View using Ordnance Survey - -[http://oslabs.github.com/recline-view-osmap/](http://oslabs.github.com/recline-view-osmap/) - -A new map view using the Ordnance Survey OpenSpaces tile service. It supports both the free and paid for (pro) stacks. More info and docs are found on the github project page. - ----- - -## Backends - -#### Google Docs - -This is the official Google Docs backend for Recline (part of the Recline distribution in v0.5) - -https://github.com/okfn/recline.backend.gdocs - -#### CouchDB - -CouchDB backend for Recline. - -https://github.com/okfn/recline.backend.couchdb diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index b9c50bd6..00000000 --- a/docs/index.html +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: default -title: Library - Home -root: ../ ---- - -
- - -

Building on Backbone, Recline - supplies components and structure to data-heavy applications by providing a - set of models (Dataset, Record/Row, Field) and views (Grid, Map, Graph - etc).

- - -

Concepts and Structure

- -

The Recline Library consists of 3 parts: Models, Backends and Views

- -
-
-
-

Models

-

Models help you structure your work with data by providing some standard objects such as Dataset and Record – a Dataset being a collection of Records. More »

-
-
-
-
-

Backends

-

Backends connect your Models to data sources (and stores) – for example Google Docs spreadsheets, local CSV files, the DataHub, ElasticSearch etc. More »

-
-
-
-
-

Views

-

Views are user interface components for displaying, editing or interacting with the data. For example, maps, graphs, data grids or a query editor. More »

-
-
-
- - - -

Source Docs (via Docco)

-
-
-

Models

- -
- -
- -
- diff --git a/docs/models.markdown b/docs/models.markdown deleted file mode 100644 index 0b5702b4..00000000 --- a/docs/models.markdown +++ /dev/null @@ -1,325 +0,0 @@ ---- -layout: container -title: Models -root: ../ ---- - - - -Models help you structure your work with data by providing several objects and -functions. The key ones are Dataset and Record -- a Dataset being a collection -of Records. Additionally, there is a a Field object for describing the columns -of a Dataset, a Query object for describing queries, and a Facet object for -holding summary information about a Field (or multiple Fields). - -All the models are Backbone models, that is they extend Backbone.Model. Note, -however, that they do not 'sync' (load/save) like normal Backbone models. - - -

Dataset

- -A Dataset is *the* central object in Recline. Standard usage is: - -{% highlight javascript %} -var dataset = new recline.model.Dataset({ - // general metadata e.g. - id: ... - title: ... - // information about data source e.g. - url: http://url.to.my.data.endpoint/ - // backend string or object - backend: a string identifying the backend we are using - see below -}); - -// initialize dataset with data from the backend. -dataset.fetch(); - -// we will now have the following (and more) set up - see below for details -dataset.fields // collection of Fields (columns) for this Dataset -dataset.records // collection of Records resulting from latest query -dataset.docCount // total number of Records in the last query -{% endhighlight %} - -### Key Attributes - -* records: a collection of `Record`s currently loaded for viewing - (updated by calling query method) - note that this need not - be all the records in the dataset (for example, you may have connected to a - source where the complete dataset contains a million records but you have - only loaded a 1000 records) -* fields: (aka columns) is a Backbone collectoin of `Field`s listing all the - fields on this Dataset (this can be set explicitly, or, will be set by - Dataset.fetch() -* docCount: total number of records in this dataset -* backend: the Backend (instance) for this Dataset. (NB: this is a the backend - attribute on the object itself not the backend in the Backbone attributes - i.e. the result of dataset.get('backend'). The latter is a string identifying - the backend. -* queryState: a `Query` object which stores current queryState. queryState may - be edited by other components (e.g. a query editor view) changes will trigger - a Dataset query. -* facets: a collection of `Facet`s - -### Querying - -{% highlight javascript %} -dataset.query(queryObj) -{% endhighlight %} - -`queryObj` is an object following the query -specification below. - - -

Record (aka Row)

- -A Record is a single entry or row in a dataset. A Record needs little more than -what is provided by the standard Backbone Model object. In general, you will -never create a Record directly -- they will get created for you by Datasets -from query results. - -

Field (aka Column)

- -A Field should have the following attributes as standard: - -{% highlight javascript %} -var field = new Field({ - // a unique identifer for this field- usually this should match the key in the records hash - id: 'my-field-id' - - // (optional: defaults to id) the visible label used for this field - label: 'My Field Name', - - // (optional: defaults to string) the type of the data in this field. - // For list of type names see below - type: 'string', - - // (optional - defaults to null) used to indicate how the data should be - // formatted. See below. - format: null, - - // (default: false) attribute indicating this field has no backend data but - // is just derived from other fields (see below). - is_derived: false -{% endhighlight %} - -#### Types - -The type attribute is a string indicating the type of this field. - -Types are -based on the [type set of json-schmea][types-1] with a few minor additions and -modifications (cf other type lists include those in [Elasticsearch](es-types)). - -The type list is as follows (brackets indicate -possible aliases for specific types - these types will be recognized and -normalized to the default type name for that type): - -* **string (text)**: a string -* **number (double, float, numeric)**: a number including floating point numbers. -* **integer (int)**: an integer. -* **date**: a date. The preferred format is YYYY-MM-DD. -* **time**: a time without a date -* **date-time (datetime, timestamp)**: a date-time. It is recommended this be in ISO 8601 - format of YYYY-MM- DDThh:mm:ssZ in UTC time. -* **boolean (bool)** -* **binary**: base64 representation of binary data. -* **geo_point**: as per - . - That is a field (in these examples named location) that has one of the - following structures: - - location: { - lon: ... - lat: ... - } - - location: [lon,lat] - - location: "lat, lng" - - As bonus there is also support for (beyond the ES style geo_point): - - // geonames style - location: { - lng: ... - lat: ... - } - // found on the web - location: "(lat, lon)" - -* **geojson**: as per -* **array**: an array -* **object (json)**: an object -* **any**: value of field may be any type - -
NB: types are not validated so you can set the type to -whatever value you like (it does not have to be in the above list). However, -using types outside of the specified list may limit functionality.
- -[types-1]: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1 -[es-types]: http://www.elasticsearch.org/guide/reference/mapping/ - -#### Rendering, types and formats - -One can customize the rendering of fields in the user interface and elsewhere -by setting a renderer function on the field. You do this by setting a field -attribute: - -{% highlight javascript %} -myfield.renderer = myRenderFunction; -{% endhighlight %} - -Your renderer function should have the following signature: - - function(value, field, record) - -Where the arguments passed in are as follows: - -* `value`: the value of the cell (record value for this field) -* `field`: corresponding `Field` object -* `record`: is the `Record` object (as simple JS object) - -Note that implementing functions can ignore arguments (e.g. function(value) -would be a valid formatter function). - -To guide the behaviour of renderers we have type and format information. -Example types and formats are: - -* type=date, format=yyyy-mm-dd -* type=float, format=percentage -* type=string, format=markdown (render as markdown if Showdown available) - -Default renderers are provided - see the source for details, but a few examples -are: - -* type = string - * no format provided: pass through but convert http:// to hyperlinks - * format = plain: do no processing on the source text - * format = markdown: process as markdown (if Showdown library available) -* type = float - - * format = percentage: format as a percentage - -#### Derived fields - -Some fields may be 'dervied' from other fields. This allows you to define an -entirely new value for data in this field. This provides support for a) -'derived/computed' fields: i.e. fields whose data are functions of the data in -other fields b) transforming the value of this field prior to rendering. - -To use derived fields set a `deriver` function on the Field. This function will -be used to derive/compute the value of data in this field as a function of this -field's value (if any) and the current record. It's signature and behaviour is -the same as for renderer. - - -

Query

- -Query instances encapsulate a query to the backend (see query method on backend). Useful both -for creating queries and for storing and manipulating query state - -e.g. from a query editor). - - -

Query Structure and format

- -Query structure should follow that of [ElasticSearch query -language](http://www.elasticsearch.org/guide/reference/api/search/). - -**NB: It is up to specific backends how to implement and support this query -structure. Different backends might choose to implement things differently -or not support certain features. Please check your backend for details.** - -Query object has the following key attributes: - - * size (=limit): number of results to return - * from (=offset): offset into result set - http://www.elasticsearch.org/guide/reference/api/search/from-size.html - * sort: sort order - see below - * query: Query in ES Query DSL - * filter: See filters and Filtered Query - * fields: set of fields to return - http://www.elasticsearch.org/guide/reference/api/search/fields.html - * facets: specification of facets - see http://www.elasticsearch.org/guide/reference/api/search/facets/ - -Additions: - -* q: either straight text or a hash will map directly onto a [query_string - query](http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html) - in backend - - * Of course this can be re-interpreted by different backends. E.g. some may - just pass this straight through e.g. for an SQL backend this could be the - full SQL query - -* filters: array of ElasticSearch filters. These will be and-ed together for - execution. - -#### Sort - -Sort structure is inspired by but with some standardization. - -Sort structure must be as follows: - - "sort" : [ - { field: "post_date", "order" : "desc"}, - { field: "user" }, - { "name" : "desc" }, - { "age" : "desc" }, - {"_score": null} - ] - -If order is omitted it is assumed to be "desc" except in the case of _score. -_score is a special case which is used for match score if that is supported by -the backend. - -#### Examples - -
-{
-   q: 'quick brown fox',
-   filters: [
-     { term: { 'owner': 'jones' } }
-   ]
-}
-
- - -

Facet – Summary information (e.g. values and counts) about a field obtained by a 'faceting' or 'group by' method -

- -Structure of a facet follows that of Facet results in ElasticSearch, see: - - -Specifically the object structure of a facet looks like (there is one -addition compared to ElasticSearch: the "id" field which corresponds to the -key used to specify this facet in the facet query): - -{% highlight javascript %} -{ - id: "id-of-facet", - // type of this facet (terms, range, histogram etc) - _type : "terms", - // total number of tokens in the facet - total: 5, - // @property {number} number of records which have no value for the field - missing : 0, - // number of facet values not included in the returned facets - other: 0, - // term object ({term: , count: ...}) - terms: [ { - "term" : "foo", - "count" : 2 - }, { - "term" : "bar", - "count" : 2 - }, { - "term" : "baz", - "count" : 1 - } - ] -} -{% endhighlight %} - diff --git a/docs/src/backend.csv.html b/docs/src/backend.csv.html deleted file mode 100644 index bfc6461a..00000000 --- a/docs/src/backend.csv.html +++ /dev/null @@ -1,240 +0,0 @@ - backend.csv.js

backend.csv.js

this.recline = this.recline || {};
-this.recline.Backend = this.recline.Backend || {};
-this.recline.Backend.CSV = this.recline.Backend.CSV || {};

Note that provision of jQuery is optional (it is only needed if you use fetch on a remote file)

(function(my) {
-  "use strict";
-  my.__type__ = 'csv';

use either jQuery or Underscore Deferred depending on what is available

  var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred;

fetch

- -

fetch supports 3 options depending on the attribute provided on the dataset argument

- -
    -
  1. dataset.file: file is an HTML5 file object. This is opened and parsed with the CSV parser.
  2. -
  3. dataset.data: data is a string in CSV format. This is passed directly to the CSV parser
  4. -
  5. dataset.url: a url to an online CSV file that is ajax accessible (note this usually requires either local or on a server that is CORS enabled). The file is then loaded using jQuery.ajax and parsed using the CSV parser (NB: this requires jQuery)
  6. -
- -

All options generates similar data and use the memory store outcome, that is they return something like:

- -
-{
-  records: [ [...], [...], ... ],
-  metadata: { may be some metadata e.g. file name }
-  useMemoryStore: true
-}
-
  my.fetch = function(dataset) {
-    var dfd = new Deferred();
-    if (dataset.file) {
-      var reader = new FileReader();
-      var encoding = dataset.encoding || 'UTF-8';
-      reader.onload = function(e) {
-        var out = my.extractFields(my.parseCSV(e.target.result, dataset), dataset);
-        out.useMemoryStore = true;
-        out.metadata = {
-          filename: dataset.file.name
-        }
-        dfd.resolve(out);
-      };
-      reader.onerror = function (e) {
-        alert('Failed to load file. Code: ' + e.target.error.code);
-      };
-      reader.readAsText(dataset.file, encoding);
-    } else if (dataset.data) {
-      var out = my.extractFields(my.parseCSV(dataset.data, dataset), dataset);
-      out.useMemoryStore = true;
-      dfd.resolve(out);
-    } else if (dataset.url) {
-      jQuery.get(dataset.url).done(function(data) {
-        var out = my.extractFields(my.parseCSV(data, dataset), dataset);
-        out.useMemoryStore = true;
-        dfd.resolve(out);
-      });
-    }
-    return dfd.promise();
-  };

Convert array of rows in { records: [ ...] , fields: [ ... ] } -@param {Boolean} noHeaderRow If true assume that first row is not a header (i.e. list of fields but is data.

  my.extractFields = function(rows, noFields) {
-    if (noFields.noHeaderRow !== true && rows.length > 0) {
-      return {
-        fields: rows[0],
-        records: rows.slice(1)
-      }
-    } else {
-      return {
-        records: rows
-      }
-    }
-  };

parseCSV

- -

Converts a Comma Separated Values string into an array of arrays. -Each line in the CSV becomes an array.

- -

Empty fields are converted to nulls and non-quoted numbers are converted to integers or floats.

- -

@return The CSV parsed as an array -@type Array

- -

@param {String} s The string to convert -@param {Object} options Options for loading CSV including - @param {Boolean} [trim=false] If set to True leading and trailing - whitespace is stripped off of each non-quoted field as it is imported - @param {String} [delimiter=','] A one-character string used to separate - fields. It defaults to ',' - @param {String} [quotechar='"'] A one-character string used to quote - fields containing special characters, such as the delimiter or - quotechar, or which contain new-line characters. It defaults to '"'

- -

@param {Integer} skipInitialRows A integer number of rows to skip (default 0)

- -

Heavily based on uselesscode's JS CSV parser (MIT Licensed): -http://www.uselesscode.org/javascript/csv/

  my.parseCSV= function(s, options) {

Get rid of any trailing \n

    s = chomp(s);
-
-    var options = options || {};
-    var trm = (options.trim === false) ? false : true;
-    var delimiter = options.delimiter || ',';
-    var quotechar = options.quotechar || '"';
-
-    var cur = '', // The character we are currently processing.
-      inQuote = false,
-      fieldQuoted = false,
-      field = '', // Buffer for building up the current field
-      row = [],
-      out = [],
-      i,
-      processField;
-
-    processField = function (field) {
-      if (fieldQuoted !== true) {

If field is empty set to null

        if (field === '') {
-          field = null;

If the field was not quoted and we are trimming fields, trim it

        } else if (trm === true) {
-          field = trim(field);
-        }

Convert unquoted numbers to their appropriate types

        if (rxIsInt.test(field)) {
-          field = parseInt(field, 10);
-        } else if (rxIsFloat.test(field)) {
-          field = parseFloat(field, 10);
-        }
-      }
-      return field;
-    };
-
-    for (i = 0; i < s.length; i += 1) {
-      cur = s.charAt(i);

If we are at a EOF or EOR

      if (inQuote === false && (cur === delimiter || cur === "\n")) {
-        field = processField(field);

Add the current field to the current row

        row.push(field);

If this is EOR append row to output and flush row

        if (cur === "\n") {
-          out.push(row);
-          row = [];
-        }

Flush the field buffer

        field = '';
-        fieldQuoted = false;
-      } else {

If it's not a quotechar, add it to the field buffer

        if (cur !== quotechar) {
-          field += cur;
-        } else {
-          if (!inQuote) {

We are not in a quote, start a quote

            inQuote = true;
-            fieldQuoted = true;
-          } else {

Next char is quotechar, this is an escaped quotechar

            if (s.charAt(i + 1) === quotechar) {
-              field += quotechar;

Skip the next char

              i += 1;
-            } else {

It's not escaping, so end quote

              inQuote = false;
-            }
-          }
-        }
-      }
-    }

Add the last field

    field = processField(field);
-    row.push(field);
-    out.push(row);

Expose the ability to discard initial rows

    if (options.skipInitialRows) out = out.slice(options.skipInitialRows);
-
-    return out;
-  };

serializeCSV

- -

Convert an Object or a simple array of arrays into a Comma -Separated Values string.

- -

Nulls are converted to empty fields and integers or floats are converted to non-quoted numbers.

- -

@return The array serialized as a CSV -@type String

- -

@param {Object or Array} dataToSerialize The Object or array of arrays to convert. Object structure must be as follows:

- -
{
-  fields: [ {id: .., ...}, {id: ..., 
-  records: [ { record }, { record }, ... ]
-  ... // more attributes we do not care about
-}
-
- -

@param {object} options Options for serializing the CSV file including - delimiter and quotechar (see parseCSV options parameter above for - details on these).

- -

Heavily based on uselesscode's JS CSV serializer (MIT Licensed): -http://www.uselesscode.org/javascript/csv/

  my.serializeCSV= function(dataToSerialize, options) {
-    var a = null;
-    if (dataToSerialize instanceof Array) {
-      a = dataToSerialize;
-    } else {
-      a = [];
-      var fieldNames = _.pluck(dataToSerialize.fields, 'id');
-      a.push(fieldNames);
-      _.each(dataToSerialize.records, function(record, index) {
-        var tmp = _.map(fieldNames, function(fn) {
-          return record[fn];
-        });
-        a.push(tmp);
-      });
-    }
-    var options = options || {};
-    var delimiter = options.delimiter || ',';
-    var quotechar = options.quotechar || '"';
-
-    var cur = '', // The character we are currently processing.
-      field = '', // Buffer for building up the current field
-      row = '',
-      out = '',
-      i,
-      j,
-      processField;
-
-    processField = function (field) {
-      if (field === null) {

If field is null set to empty string

        field = '';
-      } else if (typeof field === "string" && rxNeedsQuoting.test(field)) {

Convert string to delimited string

        field = quotechar + field + quotechar;
-      } else if (typeof field === "number") {

Convert number to string

        field = field.toString(10);
-      }
-
-      return field;
-    };
-
-    for (i = 0; i < a.length; i += 1) {
-      cur = a[i];
-
-      for (j = 0; j < cur.length; j += 1) {
-        field = processField(cur[j]);

If this is EOR append row to output and flush row

        if (j === (cur.length - 1)) {
-          row += field;
-          out += row + "\n";
-          row = '';
-        } else {

Add the current field to the current row

          row += field + delimiter;
-        }

Flush the field buffer

        field = '';
-      }
-    }
-
-    return out;
-  };
-
-  var rxIsInt = /^\d+$/,
-    rxIsFloat = /^\d*\.\d+$|^\d+\.\d*$/,

If a string has leading or trailing space, -contains a comma double quote or a newline -it needs to be quoted in CSV output

    rxNeedsQuoting = /^\s|\s$|,|"|\n/,
-    trim = (function () {

Fx 3.1 has a native trim function, it's about 10x faster, use it if it exists

      if (String.prototype.trim) {
-        return function (s) {
-          return s.trim();
-        };
-      } else {
-        return function (s) {
-          return s.replace(/^\s*/, '').replace(/\s*$/, '');
-        };
-      }
-    }());
-
-  function chomp(s) {
-    if (s.charAt(s.length - 1) !== "\n") {

Does not end with \n, just return string

      return s;
-    } else {

Remove the \n

      return s.substring(0, s.length - 1);
-    }
-  }
-
-
-}(this.recline.Backend.CSV));
-
-
\ No newline at end of file diff --git a/docs/src/backend.dataproxy.html b/docs/src/backend.dataproxy.html deleted file mode 100644 index f954bf0b..00000000 --- a/docs/src/backend.dataproxy.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - backend.dataproxy.js - - - - - -
-
- - - -
    - -
  • -
    -

    backend.dataproxy.js

    -
    -
  • - - - -
  • -
    - -
    - -
    - -
    - -
    this.recline = this.recline || {};
    -this.recline.Backend = this.recline.Backend || {};
    -this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
    -
    -(function(my) {
    -  "use strict";
    -  my.__type__ = 'dataproxy';
    - -
  • - - -
  • -
    - -
    - -
    -

    URL for the dataproxy

    - -
    - -
      my.dataproxy_url = '//jsonpdataproxy.appspot.com';
    - -
  • - - -
  • -
    - -
    - -
    -

    Timeout for dataproxy (after this time if no response we error) -Needed because use JSONP so do not receive e.g. 500 errors

    - -
    - -
      my.timeout = 5000;
    - -
  • - - -
  • -
    - -
    - -
    -

    use either jQuery or Underscore Deferred depending on what is available

    - -
    - -
      var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred;
    - -
  • - - -
  • -
    - -
    - -
    -

    load

    -

    Load data from a URL via the DataProxy.

    -

    Returns array of field names and array of arrays for records

    - -
    - -
      my.fetch = function(dataset) {
    -    var data = {
    -      url: dataset.url,
    -      'max-results':  dataset.size || dataset.rows || 1000,
    -      type: dataset.format || ''
    -    };
    -    var jqxhr = jQuery.ajax({
    -      url: my.dataproxy_url,
    -      data: data,
    -      dataType: 'jsonp'
    -    });
    -    var dfd = new Deferred();
    -    _wrapInTimeout(jqxhr).done(function(results) {
    -      if (results.error) {
    -        dfd.reject(results.error);
    -      }
    -
    -      dfd.resolve({
    -        records: results.data,
    -        fields: results.fields,
    -        useMemoryStore: true
    -      });
    -    })
    -    .fail(function(args) {
    -      dfd.reject(args);
    -    });
    -    return dfd.promise();
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    _wrapInTimeout

    -

    Convenience method providing a crude way to catch backend errors on JSONP calls. -Many of backends use JSONP and so will not get error messages and this is -a crude way to catch those errors.

    - -
    - -
      var _wrapInTimeout = function(ourFunction) {
    -    var dfd = new Deferred();
    -    var timer = setTimeout(function() {
    -      dfd.reject({
    -        message: 'Request Error: Backend did not respond after ' + (my.timeout / 1000) + ' seconds'
    -      });
    -    }, my.timeout);
    -    ourFunction.done(function(args) {
    -        clearTimeout(timer);
    -        dfd.resolve(args);
    -      })
    -      .fail(function(args) {
    -        clearTimeout(timer);
    -        dfd.reject(args);
    -      })
    -      ;
    -    return dfd.promise();
    -  };
    -
    -}(this.recline.Backend.DataProxy));
    - -
  • - -
-
- - diff --git a/docs/src/backend.elasticsearch.html b/docs/src/backend.elasticsearch.html deleted file mode 100644 index 61487458..00000000 --- a/docs/src/backend.elasticsearch.html +++ /dev/null @@ -1,242 +0,0 @@ - backend.elasticsearch.js

backend.elasticsearch.js

this.recline = this.recline || {};
-this.recline.Backend = this.recline.Backend || {};
-this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
-
-(function($, my) {
-  my.__type__ = 'elasticsearch';

use either jQuery or Underscore Deferred depending on what is available

  var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;

ElasticSearch Wrapper

- -

A simple JS wrapper around an ElasticSearch endpoints.

- -

@param {String} endpoint: url for ElasticSearch type/table, e.g. for ES running -on http://localhost:9200 with index twitter and type tweet it would be:

- -
http://localhost:9200/twitter/tweet
- -

@param {Object} options: set of options such as:

- -
    -
  • headers - {dict of headers to add to each request}
  • -
  • dataType: dataType for AJAx requests e.g. set to jsonp to make jsonp requests (default is json requests)
  • -
  my.Wrapper = function(endpoint, options) { 
-    var self = this;
-    this.endpoint = endpoint;
-    this.options = _.extend({
-        dataType: 'json'
-      },
-      options);

mapping

- -

Get ES mapping for this type/table

- -

@return promise compatible deferred object.

    this.mapping = function() {
-      var schemaUrl = self.endpoint + '/_mapping';
-      var jqxhr = makeRequest({
-        url: schemaUrl,
-        dataType: this.options.dataType
-      });
-      return jqxhr;
-    };

get

- -

Get record corresponding to specified id

- -

@return promise compatible deferred object.

    this.get = function(id) {
-      var base = this.endpoint + '/' + id;
-      return makeRequest({
-        url: base,
-        dataType: 'json'
-      });
-    };

upsert

- -

create / update a record to ElasticSearch backend

- -

@param {Object} doc an object to insert to the index. -@return deferred supporting promise API

    this.upsert = function(doc) {
-      var data = JSON.stringify(doc);
-      url = this.endpoint;
-      if (doc.id) {
-        url += '/' + doc.id;
-      }
-      return makeRequest({
-        url: url,
-        type: 'POST',
-        data: data,
-        dataType: 'json'
-      });
-    };

delete

- -

Delete a record from the ElasticSearch backend.

- -

@param {Object} id id of object to delete -@return deferred supporting promise API

    this.remove = function(id) {
-      url = this.endpoint;
-      url += '/' + id;
-      return makeRequest({
-        url: url,
-        type: 'DELETE',
-        dataType: 'json'
-      });
-    };
-
-    this._normalizeQuery = function(queryObj) {
-      var self = this;
-      var queryInfo = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj);
-      var out = {
-        constant_score: {
-          query: {}
-        }
-      };
-      if (!queryInfo.q) {
-        out.constant_score.query = {
-          match_all: {}
-        };
-      } else {
-        out.constant_score.query = {
-          query_string: {
-            query: queryInfo.q
-          }
-        };
-      }
-      if (queryInfo.filters && queryInfo.filters.length) {
-        out.constant_score.filter = {
-          and: []
-        };
-        _.each(queryInfo.filters, function(filter) {
-          out.constant_score.filter.and.push(self._convertFilter(filter));
-        });
-      }
-      return out;
-    },

convert from Recline sort structure to ES form -http://www.elasticsearch.org/guide/reference/api/search/sort.html

    this._normalizeSort = function(sort) {
-      var out = _.map(sort, function(sortObj) {
-        var _tmp = {};
-        var _tmp2 = _.clone(sortObj);
-        delete _tmp2['field'];
-        _tmp[sortObj.field] = _tmp2;
-        return _tmp;
-      });
-      return out;
-    },
-
-    this._convertFilter = function(filter) {
-      var out = {};
-      out[filter.type] = {}
-      if (filter.type === 'term') {
-        out.term[filter.field] = filter.term.toLowerCase();
-      } else if (filter.type === 'geo_distance') {
-        out.geo_distance[filter.field] = filter.point;
-        out.geo_distance.distance = filter.distance;
-        out.geo_distance.unit = filter.unit;
-      }
-      return out;
-    },

query

- -

@return deferred supporting promise API

    this.query = function(queryObj) {
-      var esQuery = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj);
-      esQuery.query = this._normalizeQuery(queryObj);
-      delete esQuery.q;
-      delete esQuery.filters;
-      if (esQuery.sort && esQuery.sort.length > 0) {
-        esQuery.sort = this._normalizeSort(esQuery.sort);
-      }
-      var data = {source: JSON.stringify(esQuery)};
-      var url = this.endpoint + '/_search';
-      var jqxhr = makeRequest({
-        url: url,
-        data: data,
-        dataType: this.options.dataType
-      });
-      return jqxhr;
-    }
-  };

Recline Connectors

- -

Requires URL of ElasticSearch endpoint to be specified on the dataset -via the url attribute.

ES options which are passed through to options on Wrapper (see Wrapper for details)

  my.esOptions = {};

fetch

  my.fetch = function(dataset) {
-    var es = new my.Wrapper(dataset.url, my.esOptions);
-    var dfd = new Deferred();
-    es.mapping().done(function(schema) {
-
-      if (!schema){
-        dfd.reject({'message':'Elastic Search did not return a mapping'});
-        return;
-      }

only one top level key in ES = the type so we can ignore it

      var key = _.keys(schema)[0];
-      var fieldData = _.map(schema[key].properties, function(dict, fieldName) {
-        dict.id = fieldName;
-        return dict;
-      });
-      dfd.resolve({
-        fields: fieldData
-      });
-    })
-    .fail(function(arguments) {
-      dfd.reject(arguments);
-    });
-    return dfd.promise();
-  };

save

  my.save = function(changes, dataset) {
-    var es = new my.Wrapper(dataset.url, my.esOptions);
-    if (changes.creates.length + changes.updates.length + changes.deletes.length > 1) {
-      var dfd = new Deferred();
-      msg = 'Saving more than one item at a time not yet supported';
-      alert(msg);
-      dfd.reject(msg);
-      return dfd.promise();
-    }
-    if (changes.creates.length > 0) {
-      return es.upsert(changes.creates[0]);
-    }
-    else if (changes.updates.length >0) {
-      return es.upsert(changes.updates[0]);
-    } else if (changes.deletes.length > 0) {
-      return es.remove(changes.deletes[0].id);
-    }
-  };

query

  my.query = function(queryObj, dataset) {
-    var dfd = new Deferred();
-    var es = new my.Wrapper(dataset.url, my.esOptions);
-    var jqxhr = es.query(queryObj);
-    jqxhr.done(function(results) {
-      var out = {
-        total: results.hits.total
-      };
-      out.hits = _.map(results.hits.hits, function(hit) {
-        if (!('id' in hit._source) && hit._id) {
-          hit._source.id = hit._id;
-        }
-        return hit._source;
-      });
-      if (results.facets) {
-        out.facets = results.facets;
-      }
-      dfd.resolve(out);
-    }).fail(function(errorObj) {
-      var out = {
-        title: 'Failed: ' + errorObj.status + ' code',
-        message: errorObj.responseText
-      };
-      dfd.reject(out);
-    });
-    return dfd.promise();
-  };

makeRequest

- -

Just $.ajax but in any headers in the 'headers' attribute of this -Backend instance. Example:

- -
-var jqxhr = this._makeRequest({
-  url: the-url
-});
-
var makeRequest = function(data, headers) {
-  var extras = {};
-  if (headers) {
-    extras = {
-      beforeSend: function(req) {
-        _.each(headers, function(value, key) {
-          req.setRequestHeader(key, value);
-        });
-      }
-    };
-  }
-  var data = _.extend(extras, data);
-  return $.ajax(data);
-};
-
-}(jQuery, this.recline.Backend.ElasticSearch));
-
-
\ No newline at end of file diff --git a/docs/src/backend.gdocs.html b/docs/src/backend.gdocs.html deleted file mode 100644 index 7d07578d..00000000 --- a/docs/src/backend.gdocs.html +++ /dev/null @@ -1,137 +0,0 @@ - backend.gdocs.js

backend.gdocs.js

this.recline = this.recline || {};
-this.recline.Backend = this.recline.Backend || {};
-this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
-
-(function(my) {
-  my.__type__ = 'gdocs';

use either jQuery or Underscore Deferred depending on what is available

  var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;

Google spreadsheet backend

- -

Fetch data from a Google Docs spreadsheet.

- -

Dataset must have a url attribute pointing to the Gdocs or its JSON feed e.g.

- -
-var dataset = new recline.Model.Dataset({
-    url: 'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdGlQVDJnbjZRSU1tUUJWOUZXRG53VkE#gid=0'
-  },
-  'gdocs'
-);
-
-var dataset = new recline.Model.Dataset({
-    url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json'
-  },
-  'gdocs'
-);
-
- -

@return object with two attributes

- -
    -
  • fields: array of Field objects
  • -
  • records: array of objects for each row
  • -
  my.fetch = function(dataset) {
-    var dfd  = new Deferred(); 
-    var urls = my.getGDocsAPIUrls(dataset.url);

TODO cover it with tests -get the spreadsheet title

    (function () {
-      var titleDfd = new Deferred();
-
-      jQuery.getJSON(urls.spreadsheet, function (d) {
-          titleDfd.resolve({
-              spreadsheetTitle: d.feed.title.$t
-          });
-      });
-
-      return titleDfd.promise();
-    }()).then(function (response) {

get the actual worksheet data

      jQuery.getJSON(urls.worksheet, function(d) {
-        var result = my.parseData(d);
-        var fields = _.map(result.fields, function(fieldId) {
-          return {id: fieldId};
-        });
-
-        dfd.resolve({
-          metadata: {
-              title: response.spreadsheetTitle +" :: "+ result.worksheetTitle,
-              spreadsheetTitle: response.spreadsheetTitle,
-              worksheetTitle  : result.worksheetTitle
-          },
-          records       : result.records,
-          fields        : fields,
-          useMemoryStore: true
-        });
-      });
-    });
-
-    return dfd.promise();
-  };

parseData

- -

Parse data from Google Docs API into a reasonable form

- -

:options: (optional) optional argument dictionary: -columnsToUse: list of columns to use (specified by field names) -colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion). -:return: tabular data object (hash with keys: field and data).

- -

Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.

  my.parseData = function(gdocsSpreadsheet, options) {
-    var options  = options || {};
-    var colTypes = options.colTypes || {};
-    var results = {
-      fields : [],
-      records: []
-    };
-    var entries = gdocsSpreadsheet.feed.entry || [];
-    var key;
-    var colName;

percentage values (e.g. 23.3%)

    var rep = /^([\d\.\-]+)\%$/;
-
-    for(key in entries[0]) {

it's barely possible it has inherited keys starting with 'gsx$'

      if(/^gsx/.test(key)) {
-        colName = key.substr(4);
-        results.fields.push(colName);
-      }
-    }

converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])

    results.records = _.map(entries, function(entry) {
-      var row = {};
-
-      _.each(results.fields, function(col) {
-        var _keyname = 'gsx$' + col;
-        var value = entry[_keyname].$t;
-        var num;
- 

TODO cover this part of code with test -TODO use the regexp only once -if labelled as % and value contains %, convert

        if(colTypes[col] === 'percent' && rep.test(value)) {
-          num   = rep.exec(value)[1];
-          value = parseFloat(num) / 100;
-        }
-
-        row[col] = value;
-      });
-
-      return row;
-    });
-
-    results.worksheetTitle = gdocsSpreadsheet.feed.title.$t;
-    return results;
-  };

Convenience function to get GDocs JSON API Url from standard URL

  my.getGDocsAPIUrls = function(url) {

https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=YYY

    var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+)[^#]*(#gid=([\d]+).*)?/;
-    var matches = url.match(regex);
-    var key;
-    var worksheet;
-    var urls;
-    
-    if(!!matches) {
-        key = matches[1];

the gid in url is 0-based and feed url is 1-based

        worksheet = parseInt(matches[3]) + 1;
-        if (isNaN(worksheet)) {
-          worksheet = 1;
-        }
-        urls = {
-          worksheet  : 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json',
-          spreadsheet: 'https://spreadsheets.google.com/feeds/worksheets/'+ key +'/public/basic?alt=json'
-        }
-    }
-    else {

we assume that it's one of the feeds urls

        key = url.split('/')[5];

by default then, take first worksheet

        worksheet = 1;
-        urls = {
-          worksheet  : 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json',
-          spreadsheet: 'https://spreadsheets.google.com/feeds/worksheets/'+ key +'/public/basic?alt=json'
-        }            
-    }
-
-    return urls;
-  };
-}(this.recline.Backend.GDocs));
-
-
\ No newline at end of file diff --git a/docs/src/backend.memory.html b/docs/src/backend.memory.html deleted file mode 100644 index 256c00c8..00000000 --- a/docs/src/backend.memory.html +++ /dev/null @@ -1,631 +0,0 @@ - - - - - backend.memory.js - - - - - -
-
- - - -
    - -
  • -
    -

    backend.memory.js

    -
    -
  • - - - -
  • -
    - -
    - -
    - -
    - -
    this.recline = this.recline || {};
    -this.recline.Backend = this.recline.Backend || {};
    -this.recline.Backend.Memory = this.recline.Backend.Memory || {};
    -
    -(function(my) {
    -  "use strict";
    -  my.__type__ = 'memory';
    - -
  • - - -
  • -
    - -
    - -
    -

    private data - use either jQuery or Underscore Deferred depending on what is available

    - -
    - -
      var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred;
    - -
  • - - -
  • -
    - -
    - -
    -

    Data Wrapper

    -

    Turn a simple array of JS objects into a mini data-store with -functionality like querying, faceting, updating (by ID) and deleting (by -ID).

    -

    @param records list of hashes for each record/row in the data ({key: -value, key: value}) -@param fields (optional) list of field hashes (each hash defining a field -as per recline.Model.Field). If fields not specified they will be taken -from the data.

    - -
    - -
      my.Store = function(records, fields) {
    -    var self = this;
    -    this.records = records;
    - -
  • - - -
  • -
    - -
    - -
    -

    backwards compatability (in v0.5 records was named data)

    - -
    - -
        this.data = this.records;
    -    if (fields) {
    -      this.fields = fields;
    -    } else {
    -      if (records) {
    -        this.fields = _.map(records[0], function(value, key) {
    -          return {id: key, type: 'string'};
    -        });
    -      }
    -    }
    -
    -    this.update = function(doc) {
    -      _.each(self.records, function(internalDoc, idx) {
    -        if(doc.id === internalDoc.id) {
    -          self.records[idx] = doc;
    -        }
    -      });
    -    };
    -
    -    this.remove = function(doc) {
    -      var newdocs = _.reject(self.records, function(internalDoc) {
    -        return (doc.id === internalDoc.id);
    -      });
    -      this.records = newdocs;
    -    };
    -
    -    this.save = function(changes, dataset) {
    -      var self = this;
    -      var dfd = new Deferred();
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO _.each(changes.creates) { … }

    - -
    - -
          _.each(changes.updates, function(record) {
    -        self.update(record);
    -      });
    -      _.each(changes.deletes, function(record) {
    -        self.remove(record);
    -      });
    -      dfd.resolve();
    -      return dfd.promise();
    -    },
    -
    -    this.query = function(queryObj) {
    -      var dfd = new Deferred();
    -      var numRows = queryObj.size || this.records.length;
    -      var start = queryObj.from || 0;
    -      var results = this.records;
    -      
    -      results = this._applyFilters(results, queryObj);
    -      results = this._applyFreeTextQuery(results, queryObj);
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: this is not complete sorting! -What’s wrong is we sort on the last entry in the sort list if there are multiple sort criteria

    - -
    - -
          _.each(queryObj.sort, function(sortObj) {
    -        var fieldName = sortObj.field;
    -        results = _.sortBy(results, function(doc) {
    -          var _out = doc[fieldName];
    -          return _out;
    -        });
    -        if (sortObj.order == 'desc') {
    -          results.reverse();
    -        }
    -      });
    -      var facets = this.computeFacets(results, queryObj);
    -      var out = {
    -        total: results.length,
    -        hits: results.slice(start, start+numRows),
    -        facets: facets
    -      };
    -      dfd.resolve(out);
    -      return dfd.promise();
    -    };
    - -
  • - - -
  • -
    - -
    - -
    -

    in place filtering

    - -
    - -
        this._applyFilters = function(results, queryObj) {
    -      var filters = queryObj.filters;
    - -
  • - - -
  • -
    - -
    - -
    -

    register filters

    - -
    - -
          var filterFunctions = {
    -        term         : term,
    -        terms        : terms,
    -        range        : range,
    -        geo_distance : geo_distance
    -      };
    -      var dataParsers = {
    -        integer: function (e) { return parseFloat(e, 10); },
    -        'float': function (e) { return parseFloat(e, 10); },
    -        number: function (e) { return parseFloat(e, 10); },
    -        string : function (e) { return e.toString(); },
    -        date   : function (e) { return moment(e).valueOf(); },
    -        datetime   : function (e) { return new Date(e).valueOf(); }
    -      };
    -      var keyedFields = {};
    -      _.each(self.fields, function(field) {
    -        keyedFields[field.id] = field;
    -      });
    -      function getDataParser(filter) {
    -        var fieldType = keyedFields[filter.field].type || 'string';
    -        return dataParsers[fieldType];
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    filter records

    - -
    - -
          return _.filter(results, function (record) {
    -        var passes = _.map(filters, function (filter) {
    -          return filterFunctions[filter.type](record, filter);
    -        });
    - -
  • - - -
  • -
    - -
    - -
    -

    return only these records that pass all filters

    - -
    - -
            return _.all(passes, _.identity);
    -      });
    - -
  • - - -
  • -
    - -
    - -
    -

    filters definitions

    - -
    - -
          function term(record, filter) {
    -        var parse = getDataParser(filter);
    -        var value = parse(record[filter.field]);
    -        var term  = parse(filter.term);
    -
    -        return (value === term);
    -      }
    -
    -      function terms(record, filter) {
    -        var parse = getDataParser(filter);
    -        var value = parse(record[filter.field]);
    -        var terms  = parse(filter.terms).split(",");
    -
    -        return (_.indexOf(terms, value) >= 0);
    -      }
    -
    -      function range(record, filter) {
    -        var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === '');
    -        var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === '');
    -        var parse = getDataParser(filter);
    -        var value = parse(record[filter.field]);
    -        var from = parse(fromnull ? '' : filter.from);
    -        var to  = parse(tonull ? '' : filter.to);
    - -
  • - - -
  • -
    - -
    - -
    -

    if at least one end of range is set do not allow ā€˜ā€™ to get through -note that for strings ā€˜ā€™ <= {any-character} e.g. ā€˜ā€™ <= ā€˜a’

    - -
    - -
            if ((!fromnull || !tonull) && value === '') {
    -          return false;
    -        }
    -        return ((fromnull || value >= from) && (tonull || value <= to));
    -      }
    -
    -      function geo_distance() {
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO code here

    - -
    - -
          }
    -    };
    - -
  • - - -
  • -
    - -
    - -
    -

    we OR across fields but AND across terms in query string

    - -
    - -
        this._applyFreeTextQuery = function(results, queryObj) {
    -      if (queryObj.q) {
    -        var terms = queryObj.q.split(' ');
    -        var patterns=_.map(terms, function(term) {
    -          return new RegExp(term.toLowerCase());
    -        });
    -        results = _.filter(results, function(rawdoc) {
    -          var matches = true;
    -          _.each(patterns, function(pattern) {
    -            var foundmatch = false;
    -            _.each(self.fields, function(field) {
    -              var value = rawdoc[field.id];
    -              if ((value !== null) && (value !== undefined)) { 
    -                value = value.toString();
    -              } else {
    - -
  • - - -
  • -
    - -
    - -
    -

    value can be null (apparently in some cases)

    - -
    - -
                    value = '';
    -              }
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO regexes?

    - -
    - -
                  foundmatch = foundmatch || (pattern.test(value.toLowerCase()));
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: early out (once we are true should break to spare unnecessary testing) -if (foundmatch) return true;

    - -
    - -
                });
    -            matches = matches && foundmatch;
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: early out (once false should break to spare unnecessary testing) -if (!matches) return false;

    - -
    - -
              });
    -          return matches;
    -        });
    -      }
    -      return results;
    -    };
    -
    -    this.computeFacets = function(records, queryObj) {
    -      var facetResults = {};
    -      if (!queryObj.facets) {
    -        return facetResults;
    -      }
    -      _.each(queryObj.facets, function(query, facetId) {
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: remove dependency on recline.Model

    - -
    - -
            facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON();
    -        facetResults[facetId].termsall = {};
    -      });
    - -
  • - - -
  • -
    - -
    - -
    -

    faceting

    - -
    - -
          _.each(records, function(doc) {
    -        _.each(queryObj.facets, function(query, facetId) {
    -          var fieldId = query.terms.field;
    -          var val = doc[fieldId];
    -          var tmp = facetResults[facetId];
    -          if (val) {
    -            tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1;
    -          } else {
    -            tmp.missing = tmp.missing + 1;
    -          }
    -        });
    -      });
    -      _.each(queryObj.facets, function(query, facetId) {
    -        var tmp = facetResults[facetId];
    -        var terms = _.map(tmp.termsall, function(count, term) {
    -          return { term: term, count: count };
    -        });
    -        tmp.terms = _.sortBy(terms, function(item) {
    - -
  • - - -
  • -
    - -
    - -
    -

    want descending order

    - -
    - -
              return -item.count;
    -        });
    -        tmp.terms = tmp.terms.slice(0, 10);
    -      });
    -      return facetResults;
    -    };
    -  };
    -
    -}(this.recline.Backend.Memory));
    - -
  • - -
-
- - diff --git a/docs/src/demo.search.app.html b/docs/src/demo.search.app.html deleted file mode 100644 index e97a180f..00000000 --- a/docs/src/demo.search.app.html +++ /dev/null @@ -1,236 +0,0 @@ - demo.search.app.js

demo.search.app.js

(c) Open Knowledge Foundation 2012. Dedicated to the public domain. Please -use and reuse freely - you don't even need to credit (though a link back to -ReclineJS.com is always appreciated)!

Our main loop - on document ready

jQuery(function($) {
-  var $el = $('.search-here');

Overview

- -

We have a slightly more complex setup than is needed to allow for using -this demo with different backends

- -

There are 2 alternatives: more complex and a simpler one

- -

If you just want to see how this work skip to the simple case ...

1. More complex - use data from a backend configured in query string

Check for config from url query string

  var config = recline.View.parseQueryString(decodeURIComponent(window.location.search));
-  if (config.backend) {

If we had it hand off to our more complex example setup

    setupMoreComplexExample(config);
-    return;
-  }

2. The simple example case

- -

We will just set up from some example local data (at the bottom of thile file)

Create our Recline Dataset from sample local data

  var dataset = new recline.Model.Dataset({
-    records: sampleData
-  });

Custom template

- -

Create a custom template for rendering the records

  var template = ' \
-    <div class="record"> \
-      <h3> \
-        {{title}} <em>by {{Author}}</em> \
-      </h3> \
-      <p>{{description}}</p> \
-      <p><code>${{price}}</code></p> \
-    </div> \
-  ';

Set up the search View (using custom template)

  var searchView = new SearchView({
-    el: $el,
-    model: dataset,
-    template: template 
-  });
-  searchView.render();

Optional - we configure the initial query a bit and set up facets

  dataset.queryState.set({
-      size: 10
-    },
-    {silent: true}
-  );
-  dataset.queryState.addFacet('Author');

Finally - now do the first query

- -

After this point the Search View will take over handling queries!

  dataset.query();
-});

Simple Search View

- -

This is a simple bespoke Backbone view for the Search. It Pulls together -various Recline UI components and the central Dataset and Query (state) -object

- -

It also provides simple support for customization e.g. of template for list of results

- -
 var view = new SearchView({
-   el: $('some-element'),
-   model: dataset
-   // EITHER a mustache template (passed a JSON version of recline.Model.Record
-   // OR a function which receives a record in JSON form and returns html
-   template: mustache-template-or-function
- });
-
var SearchView = Backbone.View.extend({
-  initialize: function(options) {
-    this.el = $(this.el);
-    _.bindAll(this, 'render');
-    this.recordTemplate = options.template;

Every time we do a search the recline.Dataset.records Backbone -collection will get reset. We want to re-render each time!

    this.model.records.bind('reset', this.render);
-    this.templateResults = options.template;
-  },

overall template for this view

  template: ' \
-    <div class="controls"> \
-      <div class="query-here"></div> \
-    </div> \
-    <div class="total"><h2><span></span> records found</h2></div> \
-    <div class="body"> \
-      <div class="sidebar"></div> \
-      <div class="results"> \
-        {{{results}}} \
-      </div> \
-    </div> \
-    <div class="pager-here"></div> \
-  ',
- 

render the view

  render: function() {
-    var results = '';
-    if (_.isFunction(this.templateResults)) {
-      var results = _.map(this.model.records.toJSON(), this.templateResults).join('\n');
-    } else {

templateResults is just for one result ...

      var tmpl = '{{#records}}' + this.templateResults + '{{/records}}'; 
-      var results = Mustache.render(tmpl, {
-        records: this.model.records.toJSON()
-      });
-    }
-    var html = Mustache.render(this.template, {
-      results: results
-    });
-    this.el.html(html);

Set the total records found info

    this.el.find('.total span').text(this.model.recordCount);

Now setup all the extra mini-widgets

- -

Facets, Pager, QueryEditor etc

    var view = new recline.View.FacetViewer({
-      model: this.model
-    });
-    view.render();
-    this.el.find('.sidebar').append(view.el);
-
-    var pager = new recline.View.Pager({
-      model: this.model.queryState
-    });
-    this.el.find('.pager-here').append(pager.el);
-
-    var queryEditor = new recline.View.QueryEditor({
-      model: this.model.queryState
-    });
-    this.el.find('.query-here').append(queryEditor.el);
-  }
-});

- -

Custom code very specific to this demo

e.g. to provide custom templates for the google doc and openspending examples

Handle case where we get data from a specific backend

- -

Includes providing custom templates

function setupMoreComplexExample(config) {
-  var $el = $('.search-here');
-  var dataset = new recline.Model.Dataset(config);

async as may be fetching remote

  dataset.fetch().done(function() {
-    var template = templates[dataset.get('url')] || templates['generic'];
-    var searchView = new SearchView({
-      el: $el,
-      model: dataset,
-      template: template 
-    });
-    searchView.render();
-
-    dataset.queryState.set({
-        size: 5
-      },
-      {silent: true}
-    );
-    if (dataset.get('url') in templates) {

for gdocs example

      dataset.queryState.addFacet('cause');
-    }
-    dataset.query();
-  });
-};
-
-var templates = {

generic template function

  'generic': function(record) {
-    var template = '<div class="record"> \
-      <ul> \
-       {{#data}} \
-       <li>{{key}}: {{value}}</li> \
-       {{/data}} \
-     </ul> \
-    </div> \
-    ';
-    var data = _.map(_.keys(record), function(key) {
-      return { key: key, value: record[key] };
-    });
-    return Mustache.render(template, {
-      data: data
-    });
-  },
-  'http://openspending.org/api/search': function(record) {
-    record['time'] = record['time.label_facet']
-    var template = '<div class="record"> \
-      <h3> \
-        <a href="http://openspending.org/{{record.dataset}}/entries/{{record.id}}">{{record.dataset}} {{record.time}}</a> \
-        &ndash; <img src="http://openspending.org/static/img/icons/cd_16x16.png" /> {{amount_formatted}} \
-      </h3> \
-      <ul> \
-       {{#data}} \
-         <li>{{key}}: {{value}}</li> \
-       {{/data}} \
-       </ul> \
-    </div> \
-    ';
-    var data = [];
-    _.each(_.keys(record), function(key) {
-      if (key !='_id' && key != 'id') {
-        data.push({ key: key, value: record[key] });
-      }
-    });
-    return Mustache.render(template, {
-      record: record,
-      amount_formatted: formatAmount(record['amount']),
-      data: data
-    });
-  },
-  'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdExXSTl2Y01xZEszOTBFZjVzcGtzVVE': function(record) {
-    var template = '<div class="record"> \
-      <h3> \
-        {{record.incidentsite}} &ndash; {{record.datereported}} &ndash; {{record.estimatedspillvolumebbl}} barrels \
-      </h3> \
-      <ul> \
-       {{#data}} \
-         <li>{{key}}: {{value}}</li> \
-       {{/data}} \
-       </ul> \
-    </div> \
-    ';
-    var data = [];
-    _.each(_.keys(record), function(key) {
-      data.push({ key: key, value: record[key] });
-    });
-    return Mustache.render(template, {
-      record: record,
-      data: data
-    });
-  }
-}
-
-var sampleData = [
-  {
-    title: 'War and Peace',
-    description: 'The epic tale of love, war and history',
-    Author: 'Tolstoy',
-    price: 7.99
-  },
-  {
-    title: 'Anna Karenina',
-    description: 'How things go wrong in love and ultimately lead to suicide. This is why you should not have affairs, girls!',
-    Author: 'Tolstoy',
-    price: 8.50
-  },
-  {
-    title: "Fathers and Sons",
-    description: "Another 19th century Russian novel",
-    Author: "Turgenev",
-    price: 11
-  }
-];
-
-var formatAmount = function (num) {
-  var billion = 1000000000;
-  var million = 1000000;
-  var thousand = 1000;
-  var numabs = Math.abs(num);
-  if (numabs > billion) {
-    return (num / billion).toFixed(0) + 'bn';
-  }
-  if (numabs > (million / 2)) {
-    return (num / million).toFixed(0) + 'm';
-  }
-  if (numabs > thousand) {
-    return (num / thousand).toFixed(0) + 'k';
-  } else {
-    return num.toFixed(0);
-  }
-};
-
-
\ No newline at end of file diff --git a/docs/src/docco.css b/docs/src/docco.css deleted file mode 100644 index b60f6fa3..00000000 --- a/docs/src/docco.css +++ /dev/null @@ -1,518 +0,0 @@ -/*--------------------- Typography ----------------------------*/ - -@font-face { - font-family: 'aller-light'; - src: url('public/fonts/aller-light.eot'); - src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), - url('public/fonts/aller-light.woff') format('woff'), - url('public/fonts/aller-light.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'aller-bold'; - src: url('public/fonts/aller-bold.eot'); - src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), - url('public/fonts/aller-bold.woff') format('woff'), - url('public/fonts/aller-bold.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'roboto-black'; - src: url('public/fonts/roboto-black.eot'); - src: url('public/fonts/roboto-black.eot?#iefix') format('embedded-opentype'), - url('public/fonts/roboto-black.woff') format('woff'), - url('public/fonts/roboto-black.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -/*--------------------- Layout ----------------------------*/ -html { height: 100%; } -body { - font-family: "aller-light"; - font-size: 14px; - line-height: 18px; - color: #30404f; - margin: 0; padding: 0; - height:100%; -} -#container { min-height: 100%; } - -a { - color: #000; -} - -b, strong { - font-weight: normal; - font-family: "aller-bold"; -} - -p { - margin: 15px 0 0px; -} - .annotation ul, .annotation ol { - margin: 25px 0; - } - .annotation ul li, .annotation ol li { - font-size: 14px; - line-height: 18px; - margin: 10px 0; - } - -h1, h2, h3, h4, h5, h6 { - color: #112233; - line-height: 1em; - font-weight: normal; - font-family: "roboto-black"; - text-transform: uppercase; - margin: 30px 0 15px 0; -} - -h1 { - margin-top: 40px; -} -h2 { - font-size: 1.26em; -} - -hr { - border: 0; - background: 1px #ddd; - height: 1px; - margin: 20px 0; -} - -pre, tt, code { - font-size: 12px; line-height: 16px; - font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; - margin: 0; padding: 0; -} - .annotation pre { - display: block; - margin: 0; - padding: 7px 10px; - background: #fcfcfc; - -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); - -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); - box-shadow: inset 0 0 10px rgba(0,0,0,0.1); - overflow-x: auto; - } - .annotation pre code { - border: 0; - padding: 0; - background: transparent; - } - - -blockquote { - border-left: 5px solid #ccc; - margin: 0; - padding: 1px 0 1px 1em; -} - .sections blockquote p { - font-family: Menlo, Consolas, Monaco, monospace; - font-size: 12px; line-height: 16px; - color: #999; - margin: 10px 0 0; - white-space: pre-wrap; - } - -ul.sections { - list-style: none; - padding:0 0 5px 0;; - margin:0; -} - -/* - Force border-box so that % widths fit the parent - container without overlap because of margin/padding. - - More Info : http://www.quirksmode.org/css/box.html -*/ -ul.sections > li > div { - -moz-box-sizing: border-box; /* firefox */ - -ms-box-sizing: border-box; /* ie */ - -webkit-box-sizing: border-box; /* webkit */ - -khtml-box-sizing: border-box; /* konqueror */ - box-sizing: border-box; /* css3 */ -} - - -/*---------------------- Jump Page -----------------------------*/ -#jump_to, #jump_page { - margin: 0; - background: white; - -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; - -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; - font: 16px Arial; - cursor: pointer; - text-align: right; - list-style: none; -} - -#jump_to a { - text-decoration: none; -} - -#jump_to a.large { - display: none; -} -#jump_to a.small { - font-size: 22px; - font-weight: bold; - color: #676767; -} - -#jump_to, #jump_wrapper { - position: fixed; - right: 0; top: 0; - padding: 10px 15px; - margin:0; -} - -#jump_wrapper { - display: none; - padding:0; -} - -#jump_to:hover #jump_wrapper { - display: block; -} - -#jump_page_wrapper{ - position: fixed; - right: 0; - top: 0; - bottom: 0; -} - -#jump_page { - padding: 5px 0 3px; - margin: 0 0 25px 25px; - max-height: 100%; - overflow: auto; -} - -#jump_page .source { - display: block; - padding: 15px; - text-decoration: none; - border-top: 1px solid #eee; -} - -#jump_page .source:hover { - background: #f5f5ff; -} - -#jump_page .source:first-child { -} - -/*---------------------- Low resolutions (> 320px) ---------------------*/ -@media only screen and (min-width: 320px) { - .pilwrap { display: none; } - - ul.sections > li > div { - display: block; - padding:5px 10px 0 10px; - } - - ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { - padding-left: 30px; - } - - ul.sections > li > div.content { - overflow-x:auto; - -webkit-box-shadow: inset 0 0 5px #e5e5ee; - box-shadow: inset 0 0 5px #e5e5ee; - border: 1px solid #dedede; - margin:5px 10px 5px 10px; - padding-bottom: 5px; - } - - ul.sections > li > div.annotation pre { - margin: 7px 0 7px; - padding-left: 15px; - } - - ul.sections > li > div.annotation p tt, .annotation code { - background: #f8f8ff; - border: 1px solid #dedede; - font-size: 12px; - padding: 0 0.2em; - } -} - -/*---------------------- (> 481px) ---------------------*/ -@media only screen and (min-width: 481px) { - #container { - position: relative; - } - body { - background-color: #F5F5FF; - font-size: 15px; - line-height: 21px; - } - pre, tt, code { - line-height: 18px; - } - p, ul, ol { - margin: 0 0 15px; - } - - - #jump_to { - padding: 5px 10px; - } - #jump_wrapper { - padding: 0; - } - #jump_to, #jump_page { - font: 10px Arial; - text-transform: uppercase; - } - #jump_page .source { - padding: 5px 10px; - } - #jump_to a.large { - display: inline-block; - } - #jump_to a.small { - display: none; - } - - - - #background { - position: absolute; - top: 0; bottom: 0; - width: 350px; - background: #fff; - border-right: 1px solid #e5e5ee; - z-index: -1; - } - - ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { - padding-left: 40px; - } - - ul.sections > li { - white-space: nowrap; - } - - ul.sections > li > div { - display: inline-block; - } - - ul.sections > li > div.annotation { - max-width: 350px; - min-width: 350px; - min-height: 5px; - padding: 13px; - overflow-x: hidden; - white-space: normal; - vertical-align: top; - text-align: left; - } - ul.sections > li > div.annotation pre { - margin: 15px 0 15px; - padding-left: 15px; - } - - ul.sections > li > div.content { - padding: 13px; - vertical-align: top; - border: none; - -webkit-box-shadow: none; - box-shadow: none; - } - - .pilwrap { - position: relative; - display: inline; - } - - .pilcrow { - font: 12px Arial; - text-decoration: none; - color: #454545; - position: absolute; - top: 3px; left: -20px; - padding: 1px 2px; - opacity: 0; - -webkit-transition: opacity 0.2s linear; - } - .for-h1 .pilcrow { - top: 47px; - } - .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { - top: 35px; - } - - ul.sections > li > div.annotation:hover .pilcrow { - opacity: 1; - } -} - -/*---------------------- (> 1025px) ---------------------*/ -@media only screen and (min-width: 1025px) { - - body { - font-size: 16px; - line-height: 24px; - } - - #background { - width: 525px; - } - ul.sections > li > div.annotation { - max-width: 525px; - min-width: 525px; - padding: 10px 25px 1px 50px; - } - ul.sections > li > div.content { - padding: 9px 15px 16px 25px; - } -} - -/*---------------------- Syntax Highlighting -----------------------------*/ - -td.linenos { background-color: #f0f0f0; padding-right: 10px; } -span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } -/* - -github.com style (c) Vasily Polovnyov - -*/ - -pre code { - display: block; padding: 0.5em; - color: #000; - background: #f8f8ff -} - -pre .hljs-comment, -pre .hljs-template_comment, -pre .hljs-diff .hljs-header, -pre .hljs-javadoc { - color: #408080; - font-style: italic -} - -pre .hljs-keyword, -pre .hljs-assignment, -pre .hljs-literal, -pre .hljs-css .hljs-rule .hljs-keyword, -pre .hljs-winutils, -pre .hljs-javascript .hljs-title, -pre .hljs-lisp .hljs-title, -pre .hljs-subst { - color: #954121; - /*font-weight: bold*/ -} - -pre .hljs-number, -pre .hljs-hexcolor { - color: #40a070 -} - -pre .hljs-string, -pre .hljs-tag .hljs-value, -pre .hljs-phpdoc, -pre .hljs-tex .hljs-formula { - color: #219161; -} - -pre .hljs-title, -pre .hljs-id { - color: #19469D; -} -pre .hljs-params { - color: #00F; -} - -pre .hljs-javascript .hljs-title, -pre .hljs-lisp .hljs-title, -pre .hljs-subst { - font-weight: normal -} - -pre .hljs-class .hljs-title, -pre .hljs-haskell .hljs-label, -pre .hljs-tex .hljs-command { - color: #458; - font-weight: bold -} - -pre .hljs-tag, -pre .hljs-tag .hljs-title, -pre .hljs-rules .hljs-property, -pre .hljs-django .hljs-tag .hljs-keyword { - color: #000080; - font-weight: normal -} - -pre .hljs-attribute, -pre .hljs-variable, -pre .hljs-instancevar, -pre .hljs-lisp .hljs-body { - color: #008080 -} - -pre .hljs-regexp { - color: #B68 -} - -pre .hljs-class { - color: #458; - font-weight: bold -} - -pre .hljs-symbol, -pre .hljs-ruby .hljs-symbol .hljs-string, -pre .hljs-ruby .hljs-symbol .hljs-keyword, -pre .hljs-ruby .hljs-symbol .hljs-keymethods, -pre .hljs-lisp .hljs-keyword, -pre .hljs-tex .hljs-special, -pre .hljs-input_number { - color: #990073 -} - -pre .hljs-builtin, -pre .hljs-constructor, -pre .hljs-built_in, -pre .hljs-lisp .hljs-title { - color: #0086b3 -} - -pre .hljs-preprocessor, -pre .hljs-pi, -pre .hljs-doctype, -pre .hljs-shebang, -pre .hljs-cdata { - color: #999; - font-weight: bold -} - -pre .hljs-deletion { - background: #fdd -} - -pre .hljs-addition { - background: #dfd -} - -pre .hljs-diff .hljs-change { - background: #0086b3 -} - -pre .hljs-chunk { - color: #aaa -} - -pre .hljs-tex .hljs-formula { - opacity: 0.5; -} diff --git a/docs/src/ecma-fixes.html b/docs/src/ecma-fixes.html deleted file mode 100644 index 7839ba34..00000000 --- a/docs/src/ecma-fixes.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - ecma-fixes.js - - - - - -
-
- - - -
    - -
  • -
    -

    ecma-fixes.js

    -
    -
  • - - - -
  • -
    - -
    - -
    -

    This file adds in full array method support in browsers that don’t support it -see: http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Add ECMA262-5 Array methods if not supported natively

    - -
    - -
    if (!('indexOf' in Array.prototype)) {
    -    Array.prototype.indexOf= function(find, i /*opt*/) {
    -        if (i===undefined) i= 0;
    -        if (i<0) i+= this.length;
    -        if (i<0) i= 0;
    -        for (var n= this.length; i<n; i++)
    -            if (i in this && this[i]===find)
    -                return i;
    -        return -1;
    -    };
    -}
    -if (!('lastIndexOf' in Array.prototype)) {
    -    Array.prototype.lastIndexOf= function(find, i /*opt*/) {
    -        if (i===undefined) i= this.length-1;
    -        if (i<0) i+= this.length;
    -        if (i>this.length-1) i= this.length-1;
    -        for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */
    -            if (i in this && this[i]===find)
    -                return i;
    -        return -1;
    -    };
    -}
    -if (!('forEach' in Array.prototype)) {
    -    Array.prototype.forEach= function(action, that /*opt*/) {
    -        for (var i= 0, n= this.length; i<n; i++)
    -            if (i in this)
    -                action.call(that, this[i], i, this);
    -    };
    -}
    -if (!('map' in Array.prototype)) {
    -    Array.prototype.map= function(mapper, that /*opt*/) {
    -        var other= new Array(this.length);
    -        for (var i= 0, n= this.length; i<n; i++)
    -            if (i in this)
    -                other[i]= mapper.call(that, this[i], i, this);
    -        return other;
    -    };
    -}
    -if (!('filter' in Array.prototype)) {
    -    Array.prototype.filter= function(filter, that /*opt*/) {
    -        var other= [], v;
    -        for (var i=0, n= this.length; i<n; i++)
    -            if (i in this && filter.call(that, v= this[i], i, this))
    -                other.push(v);
    -        return other;
    -    };
    -}
    -if (!('every' in Array.prototype)) {
    -    Array.prototype.every= function(tester, that /*opt*/) {
    -        for (var i= 0, n= this.length; i<n; i++)
    -            if (i in this && !tester.call(that, this[i], i, this))
    -                return false;
    -        return true;
    -    };
    -}
    -if (!('some' in Array.prototype)) {
    -    Array.prototype.some= function(tester, that /*opt*/) {
    -        for (var i= 0, n= this.length; i<n; i++)
    -            if (i in this && tester.call(that, this[i], i, this))
    -                return true;
    -        return false;
    -    };
    -}
    - -
  • - -
-
- - diff --git a/docs/src/model.html b/docs/src/model.html deleted file mode 100644 index d35b2ceb..00000000 --- a/docs/src/model.html +++ /dev/null @@ -1,1375 +0,0 @@ - - - - - Recline Backbone Models - - - - - -
-
- - - -
    - - - -
  • -
    - -
    - -
    -

    Recline Backbone Models

    - -
    - -
    this.recline = this.recline || {};
    -this.recline.Model = this.recline.Model || {};
    -
    -(function(my) {
    -  "use strict";
    - -
  • - - -
  • -
    - -
    - -
    -

    use either jQuery or Underscore Deferred depending on what is available

    - -
    - -
    var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred;
    - -
  • - - -
  • -
    - -
    - -
    -

    Dataset

    - -
    - -
    my.Dataset = Backbone.Model.extend({
    -  constructor: function Dataset() {
    -    Backbone.Model.prototype.constructor.apply(this, arguments);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    initialize

    - -
    - -
      initialize: function() {
    -    var self = this;
    -    _.bindAll(this, 'query');
    -    this.backend = null;
    -    if (this.get('backend')) {
    -      this.backend = this._backendFromString(this.get('backend'));
    -    } else { // try to guess backend ...
    -      if (this.get('records')) {
    -        this.backend = recline.Backend.Memory;
    -      }
    -    }
    -    this.fields = new my.FieldList();
    -    this.records = new my.RecordList();
    -    this._changes = {
    -      deletes: [],
    -      updates: [],
    -      creates: []
    -    };
    -    this.facets = new my.FacetList();
    -    this.recordCount = null;
    -    this.queryState = new my.Query();
    -    this.queryState.bind('change facet:add', function () {
    -      self.query(); // We want to call query() without any arguments.
    -    });
    - -
  • - - -
  • -
    - -
    - -
    -

    store is what we query and save against -store will either be the backend or be a memory store if Backend fetch -tells us to use memory store

    - -
    - -
        this._store = this.backend;
    - -
  • - - -
  • -
    - -
    - -
    -

    if backend has a handleQueryResultFunction, use that

    - -
    - -
        this._handleResult = (this.backend != null && _.has(this.backend, 'handleQueryResult')) ? 
    -      this.backend.handleQueryResult : this._handleQueryResult;
    -    if (this.backend == recline.Backend.Memory) {
    -      this.fetch();
    -    }
    -  },
    -
    -  sync: function(method, model, options) {
    -    return this.backend.sync(method, model, options);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    fetch

    -

    Retrieve dataset and (some) records from the backend.

    - -
    - -
      fetch: function() {
    -    var self = this;
    -    var dfd = new Deferred();
    -
    -    if (this.backend !== recline.Backend.Memory) {
    -      this.backend.fetch(this.toJSON())
    -        .done(handleResults)
    -        .fail(function(args) {
    -          dfd.reject(args);
    -        });
    -    } else {
    - -
  • - - -
  • -
    - -
    - -
    -

    special case where we have been given data directly

    - -
    - -
          handleResults({
    -        records: this.get('records'),
    -        fields: this.get('fields'),
    -        useMemoryStore: true
    -      });
    -    }
    -
    -    function handleResults(results) {
    - -
  • - - -
  • -
    - -
    - -
    -

    if explicitly given the fields -(e.g. var dataset = new Dataset({fields: fields, …}) -use that field info over anything we get back by parsing the data -(results.fields)

    - -
    - -
          var fields = self.get('fields') || results.fields;
    -
    -      var out = self._normalizeRecordsAndFields(results.records, fields);
    -      if (results.useMemoryStore) {
    -        self._store = new recline.Backend.Memory.Store(out.records, out.fields);
    -      }
    -
    -      self.set(results.metadata);
    -      self.fields.reset(out.fields);
    -      self.query()
    -        .done(function() {
    -          dfd.resolve(self);
    -        })
    -        .fail(function(args) {
    -          dfd.reject(args);
    -        });
    -    }
    -
    -    return dfd.promise();
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    _normalizeRecordsAndFields

    -

    Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects

    -

    e.g. fields = [ā€˜a’, ā€˜b’, ā€˜c’] and records = [ [1,2,3] ] => -fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}]

    - -
    - -
      _normalizeRecordsAndFields: function(records, fields) {
    - -
  • - - -
  • -
    - -
    - -
    -

    if no fields get them from records

    - -
    - -
        if (!fields && records && records.length > 0) {
    - -
  • - - -
  • -
    - -
    - -
    -

    records is array then fields is first row of records …

    - -
    - -
          if (records[0] instanceof Array) {
    -        fields = records[0];
    -        records = records.slice(1);
    -      } else {
    -        fields = _.map(_.keys(records[0]), function(key) {
    -          return {id: key};
    -        });
    -      }
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    fields is an array of strings (i.e. list of field headings/ids)

    - -
    - -
        if (fields && fields.length > 0 && (fields[0] === null || typeof(fields[0]) != 'object')) {
    - -
  • - - -
  • -
    - -
    - -
    -

    Rename duplicate fieldIds as each field name needs to be -unique.

    - -
    - -
          var seen = {};
    -      fields = _.map(fields, function(field, index) {
    -        if (field === null) {
    -          field = '';
    -        } else {
    -          field = field.toString();
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    cannot use trim as not supported by IE7

    - -
    - -
            var fieldId = field.replace(/^\s+|\s+$/g, '');
    -        if (fieldId === '') {
    -          fieldId = '_noname_';
    -          field = fieldId;
    -        }
    -        while (fieldId in seen) {
    -          seen[field] += 1;
    -          fieldId = field + seen[field];
    -        }
    -        if (!(field in seen)) {
    -          seen[field] = 0;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: decide whether to keep original name as label … -return { id: fieldId, label: field || fieldId }

    - -
    - -
            return { id: fieldId };
    -      });
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    records is provided as arrays so need to zip together with fields -NB: this requires you to have fields to match arrays

    - -
    - -
        if (records && records.length > 0 && records[0] instanceof Array) {
    -      records = _.map(records, function(doc) {
    -        var tmp = {};
    -        _.each(fields, function(field, idx) {
    -          tmp[field.id] = doc[idx];
    -        });
    -        return tmp;
    -      });
    -    }
    -    return {
    -      fields: fields,
    -      records: records
    -    };
    -  },
    -
    -  save: function() {
    -    var self = this;
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: need to reset the changes …

    - -
    - -
        return this._store.save(this._changes, this.toJSON());
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    query

    -

    AJAX method with promise API to get records from the backend.

    -

    It will query based on current query state (given by this.queryState) -updated by queryObj (if provided).

    -

    Resulting RecordList are used to reset this.records and are -also returned.

    - -
    - -
      query: function(queryObj) {
    -    var self = this;
    -    var dfd = new Deferred();
    -    this.trigger('query:start');
    -
    -    if (queryObj) {
    -      var attributes = queryObj;
    -      if (queryObj instanceof my.Query) {
    -        attributes = queryObj.toJSON();
    -      }
    -      this.queryState.set(attributes, {silent: true});
    -    }
    -    var actualQuery = this.queryState.toJSON();
    -
    -    this._store.query(actualQuery, this.toJSON())
    -      .done(function(queryResult) {
    -        self._handleResult(queryResult);
    -        self.trigger('query:done');
    -        dfd.resolve(self.records);
    -      })
    -      .fail(function(args) {
    -        self.trigger('query:fail', args);
    -        dfd.reject(args);
    -      });
    -    return dfd.promise();
    -  },
    -
    -  _handleQueryResult: function(queryResult) {
    -    var self = this;
    -    self.recordCount = queryResult.total;
    -    var docs = _.map(queryResult.hits, function(hit) {
    -      var _doc = new my.Record(hit);
    -      _doc.fields = self.fields;
    -      _doc.bind('change', function(doc) {
    -        self._changes.updates.push(doc.toJSON());
    -      });
    -      _doc.bind('destroy', function(doc) {
    -        self._changes.deletes.push(doc.toJSON());
    -      });
    -      return _doc;
    -    });
    -    self.records.reset(docs);
    -    if (queryResult.facets) {
    -      var facets = _.map(queryResult.facets, function(facetResult, facetId) {
    -        facetResult.id = facetId;
    -        return new my.Facet(facetResult);
    -      });
    -      self.facets.reset(facets);
    -    }
    -  },
    -
    -  toTemplateJSON: function() {
    -    var data = this.toJSON();
    -    data.recordCount = this.recordCount;
    -    data.fields = this.fields.toJSON();
    -    return data;
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    getFieldsSummary

    -

    Get a summary for each field in the form of a Facet.

    -

    @return null as this is async function. Provides deferred/promise interface.

    - -
    - -
      getFieldsSummary: function() {
    -    var self = this;
    -    var query = new my.Query();
    -    query.set({size: 0});
    -    this.fields.each(function(field) {
    -      query.addFacet(field.id);
    -    });
    -    var dfd = new Deferred();
    -    this._store.query(query.toJSON(), this.toJSON()).done(function(queryResult) {
    -      if (queryResult.facets) {
    -        _.each(queryResult.facets, function(facetResult, facetId) {
    -          facetResult.id = facetId;
    -          var facet = new my.Facet(facetResult);
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO: probably want replace rather than reset (i.e. just replace the facet with this id)

    - -
    - -
              self.fields.get(facetId).facets.reset(facet);
    -        });
    -      }
    -      dfd.resolve(queryResult);
    -    });
    -    return dfd.promise();
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Deprecated (as of v0.5) - use record.summary()

    - -
    - -
      recordSummary: function(record) {
    -    return record.summary();
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    _backendFromString(backendString)

    -

    Look up a backend module from a backend string (look in recline.Backend)

    - -
    - -
      _backendFromString: function(backendString) {
    -    var backend = null;
    -    if (recline && recline.Backend) {
    -      _.each(_.keys(recline.Backend), function(name) {
    -        if (name.toLowerCase() === backendString.toLowerCase()) {
    -          backend = recline.Backend[name];
    -        }
    -      });
    -    }
    -    return backend;
    -  }
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    A Record

    -

    A single record (or row) in the dataset

    - -
    - -
    my.Record = Backbone.Model.extend({
    -  constructor: function Record() {
    -    Backbone.Model.prototype.constructor.apply(this, arguments);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    initialize

    -

    Create a Record

    -

    You usually will not do this directly but will have records created by -Dataset e.g. in query method

    -

    Certain methods require presence of a fields attribute (identical to that on Dataset)

    - -
    - -
      initialize: function() {
    -    _.bindAll(this, 'getFieldValue');
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    getFieldValue

    -

    For the provided Field get the corresponding rendered computed data value -for this record.

    -

    NB: if field is undefined a default ā€˜ā€™ value will be returned

    - -
    - -
      getFieldValue: function(field) {
    -    var val = this.getFieldValueUnrendered(field);
    -    if (field && !_.isUndefined(field.renderer)) {
    -      val = field.renderer(val, field, this.toJSON());
    -    }
    -    return val;
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    getFieldValueUnrendered

    -

    For the provided Field get the corresponding computed data value -for this record.

    -

    NB: if field is undefined a default ā€˜ā€™ value will be returned

    - -
    - -
      getFieldValueUnrendered: function(field) {
    -    if (!field) {
    -      return '';
    -    }
    -    var val = this.get(field.id);
    -    if (field.deriver) {
    -      val = field.deriver(val, field, this);
    -    }
    -    return val;
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    summary

    -

    Get a simple html summary of this record in form of key/value list

    - -
    - -
      summary: function(record) {
    -    var self = this;
    -    var html = '<div class="recline-record-summary">';
    -    this.fields.each(function(field) { 
    -      if (field.id != 'id') {
    -        html += '<div class="' + field.id + '"><strong>' + field.get('label') + '</strong>: ' + self.getFieldValue(field) + '</div>';
    -      }
    -    });
    -    html += '</div>';
    -    return html;
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Override Backbone save, fetch and destroy so they do nothing -Instead, Dataset object that created this Record should take care of -handling these changes (discovery will occur via event notifications) -WARNING: these will not persist unless you call save on Dataset

    - -
    - -
      fetch: function() {},
    -  save: function() {},
    -  destroy: function() { this.trigger('destroy', this); }
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    A Backbone collection of Records

    - -
    - -
    my.RecordList = Backbone.Collection.extend({
    -  constructor: function RecordList() {
    -    Backbone.Collection.prototype.constructor.apply(this, arguments);
    -  },
    -  model: my.Record
    -});
    - -
  • - - -
  • - - -
    my.Field = Backbone.Model.extend({
    -  constructor: function Field() {
    -    Backbone.Model.prototype.constructor.apply(this, arguments);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    defaults - define default values

    - -
    - -
      defaults: {
    -    label: null,
    -    type: 'string',
    -    format: null,
    -    is_derived: false
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    initialize

    -

    @param {Object} data: standard Backbone model attributes

    -

    @param {Object} options: renderer and/or deriver functions.

    - -
    - -
      initialize: function(data, options) {
    - -
  • - - -
  • -
    - -
    - -
    -

    if a hash not passed in the first argument throw error

    - -
    - -
        if ('0' in data) {
    -      throw new Error('Looks like you did not pass a proper hash with id to Field constructor');
    -    }
    -    if (this.attributes.label === null) {
    -      this.set({label: this.id});
    -    }
    -    if (this.attributes.type.toLowerCase() in this._typeMap) {
    -      this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()];
    -    }
    -    if (options) {
    -      this.renderer = options.renderer;
    -      this.deriver = options.deriver;
    -    }
    -    if (!this.renderer) {
    -      this.renderer = this.defaultRenderers[this.get('type')];
    -    }
    -    this.facets = new my.FacetList();
    -  },
    -  _typeMap: {
    -    'text': 'string',
    -    'double': 'number',
    -    'float': 'number',
    -    'numeric': 'number',
    -    'int': 'integer',
    -    'datetime': 'date-time',
    -    'bool': 'boolean',
    -    'timestamp': 'date-time',
    -    'json': 'object'
    -  },
    -  defaultRenderers: {
    -    object: function(val, field, doc) {
    -      return JSON.stringify(val);
    -    },
    -    geo_point: function(val, field, doc) {
    -      return JSON.stringify(val);
    -    },
    -    'number': function(val, field, doc) {
    -      var format = field.get('format'); 
    -      if (format === 'percentage') {
    -        return val + '%';
    -      }
    -      return val;
    -    },
    -    'string': function(val, field, doc) {
    -      var format = field.get('format');
    -      if (format === 'markdown') {
    -        if (typeof Showdown !== 'undefined') {
    -          var showdown = new Showdown.converter();
    -          out = showdown.makeHtml(val);
    -          return out;
    -        } else {
    -          return val;
    -        }
    -      } else if (format == 'plain') {
    -        return val;
    -      } else {
    - -
  • - - -
  • -
    - -
    - -
    -

    as this is the default and default type is string may get things -here that are not actually strings

    - -
    - -
            if (val && typeof val === 'string') {
    -          val = val.replace(/(https?:\/\/[^ ]+)/g, '<a href="$1">$1</a>');
    -        }
    -        return val;
    -      }
    -    }
    -  }
    -});
    -
    -my.FieldList = Backbone.Collection.extend({
    -  constructor: function FieldList() {
    -    Backbone.Collection.prototype.constructor.apply(this, arguments);
    -  },
    -  model: my.Field
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    Query

    - -
    - -
    my.Query = Backbone.Model.extend({
    -  constructor: function Query() {
    -    Backbone.Model.prototype.constructor.apply(this, arguments);
    -  },
    -  defaults: function() {
    -    return {
    -      size: 100,
    -      from: 0,
    -      q: '',
    -      facets: {},
    -      filters: []
    -    };
    -  },
    -  _filterTemplates: {
    -    term: {
    -      type: 'term',
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO do we need this attribute here?

    - -
    - -
          field: '',
    -      term: ''
    -    },
    -    range: {
    -      type: 'range',
    -      from: '',
    -      to: ''
    -    },
    -    geo_distance: {
    -      type: 'geo_distance',
    -      distance: 10,
    -      unit: 'km',
    -      point: {
    -        lon: 0,
    -        lat: 0
    -      }
    -    }
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    addFilter(filter)

    -

    Add a new filter specified by the filter hash and append to the list of filters

    -

    @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates

    - -
    - -
      addFilter: function(filter) {
    - -
  • - - -
  • -
    - -
    - -
    -

    crude deep copy

    - -
    - -
        var ourfilter = JSON.parse(JSON.stringify(filter));
    - -
  • - - -
  • -
    - -
    - -
    -

    not fully specified so use template and over-write

    - -
    - -
        if (_.keys(filter).length <= 3) {
    -      ourfilter = _.defaults(ourfilter, this._filterTemplates[filter.type]);
    -    }
    -    var filters = this.get('filters');
    -    filters.push(ourfilter);
    -    this.trigger('change:filters:new-blank');
    -  },
    -  replaceFilter: function(filter) {
    - -
  • - - -
  • -
    - -
    - -
    -

    delete filter on the same field, then add

    - -
    - -
        var filters = this.get('filters');
    -    var idx = -1;
    -    _.each(this.get('filters'), function(f, key, list) {
    -      if (filter.field == f.field) {
    -        idx = key;
    -      }
    -    });
    - -
  • - - -
  • -
    - -
    - -
    -

    trigger just one event (change:filters:new-blank) instead of one for remove and -one for add

    - -
    - -
        if (idx >= 0) {
    -      filters.splice(idx, 1);
    -      this.set({filters: filters});
    -    }
    -    this.addFilter(filter);
    -  },
    -  updateFilter: function(index, value) {
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    removeFilter

    -

    Remove a filter from filters at index filterIndex

    - -
    - -
      removeFilter: function(filterIndex) {
    -    var filters = this.get('filters');
    -    filters.splice(filterIndex, 1);
    -    this.set({filters: filters});
    -    this.trigger('change');
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    addFacet

    -

    Add a Facet to this query

    -

    See http://www.elasticsearch.org/guide/reference/api/search/facets/

    - -
    - -
      addFacet: function(fieldId, size, silent) {
    -    var facets = this.get('facets');
    - -
  • - - -
  • -
    - -
    - -
    -

    Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field)

    - -
    - -
        if (_.contains(_.keys(facets), fieldId)) {
    -      return;
    -    }
    -    facets[fieldId] = {
    -      terms: { field: fieldId }
    -    };
    -    if (!_.isUndefined(size)) {
    -      facets[fieldId].terms.size = size;
    -    }
    -    this.set({facets: facets}, {silent: true});
    -    if (!silent) {
    -      this.trigger('facet:add', this);
    -    }
    -  },
    -  addHistogramFacet: function(fieldId) {
    -    var facets = this.get('facets');
    -    facets[fieldId] = {
    -      date_histogram: {
    -        field: fieldId,
    -        interval: 'day'
    -      }
    -    };
    -    this.set({facets: facets}, {silent: true});
    -    this.trigger('facet:add', this);
    -  },
    -  removeFacet: function(fieldId) {
    -    var facets = this.get('facets');
    - -
  • - - -
  • -
    - -
    - -
    -

    Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field)

    - -
    - -
        if (!_.contains(_.keys(facets), fieldId)) {
    -      return;
    -    }
    -    delete facets[fieldId];
    -    this.set({facets: facets}, {silent: true});
    -    this.trigger('facet:remove', this);
    -  },
    -  clearFacets: function() {
    -    var facets = this.get('facets');
    -    _.each(_.keys(facets), function(fieldId) {
    -      delete facets[fieldId];
    -    });
    -    this.trigger('facet:remove', this);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    trigger a facet add; use this to trigger a single event after adding -multiple facets

    - -
    - -
      refreshFacets: function() {
    -    this.trigger('facet:add', this);
    -  }
    -
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    A Facet (Result)

    - -
    - -
    my.Facet = Backbone.Model.extend({
    -  constructor: function Facet() {
    -    Backbone.Model.prototype.constructor.apply(this, arguments);
    -  },
    -  defaults: function() {
    -    return {
    -      _type: 'terms',
    -      total: 0,
    -      other: 0,
    -      missing: 0,
    -      terms: []
    -    };
    -  }
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    A Collection/List of Facets

    - -
    - -
    my.FacetList = Backbone.Collection.extend({
    -  constructor: function FacetList() {
    -    Backbone.Collection.prototype.constructor.apply(this, arguments);
    -  },
    -  model: my.Facet
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    Object State

    -

    Convenience Backbone model for storing (configuration) state of objects like Views.

    - -
    - -
    my.ObjectState = Backbone.Model.extend({
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    Backbone.sync

    -

    Override Backbone.sync to hand off to sync function in relevant backend -Backbone.sync = function(method, model, options) { - return model.backend.sync(method, model, options); -};

    - -
    - -
    -}(this.recline.Model));
    - -
  • - -
-
- - diff --git a/docs/src/public/fonts/aller-bold.eot b/docs/src/public/fonts/aller-bold.eot deleted file mode 100644 index 1b32532a8e40e483069482c3650c3ef22ef16bdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29804 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 F>VRGqwvGS* diff --git a/docs/src/public/fonts/aller-bold.ttf b/docs/src/public/fonts/aller-bold.ttf deleted file mode 100644 index dc4cc9c27a59ab14fbb7444c6ab35c5b33a034f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66836 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%0AAIIsziQtyhPht7#*LdNqb{-ye3^( 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ⅆA*wV|2E?UgaO-298Xthc^!eoT=hLnT61q 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 diff --git a/docs/src/public/fonts/aller-bold.woff b/docs/src/public/fonts/aller-bold.woff deleted file mode 100644 index fa16fd0aba81582de121d833c7bc50e8f1b1b981..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33244 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

--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 q6yXA@F-u0Z~1!1poleYR}~W diff --git a/docs/src/public/fonts/aller-light.eot b/docs/src/public/fonts/aller-light.eot deleted file mode 100644 index 40bd654b5fe63501ae64d5673b5aa45051a69e65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29509 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�y6>nTFX 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 diff --git a/docs/src/public/fonts/roboto-black.eot b/docs/src/public/fonts/roboto-black.eot deleted file mode 100755 index 571ed49125910cd6b6b5a9259134dd2e8d37766e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20702 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
~sO?6glRGZi8n^KyFR=i1erVaJp>l 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

  • t|30`uv~79Ft#-pi(_^vDdzXOR=ok|9cSAxaBMRW}~B z08t8W4i$yvLtJ*lYbqFeV^nk+`LmGG9gIs9T5rB1Xv;(a3?yfi(G>tJ2gK49o{@Zv zOq=Z50bQcn2%e3m-~!FQgHS|AniDWhKJ@zQEmw#sOPRvm1zQ(J5C7QoFA*2YkHnim z9bE^7It81?8~8n%*PC7f79X#ro+F0e2llIUhw1f>@us%xs!Ti}qg@=UQ@m-Ffra|7 zyk2=hAlvc2#?__Jt+@6Rrh^^hP2$>kqQc*dH_dNFl^opRQv)H6BU4FCKn~rAdpv1+ z3|cnK5d940$?~?yz#Az|l?!)U5aZr&hE;x5Za}~-GheJOe@NT zx{F>eGMZT%rJ2-j3eUkpC5a`!vH~xtwsoU=F9~u^6y`*_X$b#eF1P+P{NgetaistK zF}G(9@-3cv&#;$9-@fcT{fXcH&|l?s=Pg{a)_c~7)vNh?U+|c`Y}3HAd#qYL(VUPT zb0l$S+2YlcR_Jf+?57{nZ|5m*&A;gFdwf~lE~OZo=uHA!!3o;CFdU;oCmxPvZEeSc zPUKoDJngAA)lKSZ4p|2vJqWK(4NHjhD5%pYC%VaKLf4X@IhCw!w6qOoP^~K95pLF_ zp`)Ke&97EdjVGbNMPk%3wzXohcNMPc+~wrQP8$9RT~AE@cIrGZOY}d1uc3>W6{bx^ ztpA&sRfldU?VJQu0YXmT4lPlxHz;`8NgAUlPm^$}Y2O~Db=;q0+-cCGBe6CStD

    z&Mp6atg6GfBY0;FS-8(SjJvIsOO(MVPTJ8$QU&)Q-;M$A;~4h|@hKYje;J=rgxA;V z5NSK)h3qhaPUd!(z`v-K2l-C|fBC(zr1G{cL3kdZ6(c6KynRfH=JCIZNonMcF``n5 zX_=AGn_KXW&T*-Z6FsR-+~u4v>qI2UCz-k8YDX(Aj<)GTq8RJ=8s=}rq87A|MG*u} z5sRWQw+Pe4Ls+90rejb_^$=g_5R6h{T7yv?=iO?zM&?PTYX>A<%ds&Pr=y2c>BRh@ z+MsDa&Z{)NN=oH*^%yLk7B!6Hj?ZXTEk-GcXZI@`x@nDMG+>3a(JEYZ0BIkPj?{ zpq)f4f=_qFs{FX9s$^3w956+M>a?>$HPs4u(7JP&m;{8D|9azu9vK52SIQyz!3Hz??4n z?J`ul4_QVRP?`yu@z zaHgwOIcnr6vKgW8ELWk{%#9e?dKPq$SB1J|qF~I6 zo66>vmCx<5X|u>9j^d+YquL%y6CHym*hby6-E5I}nynArO}toUBjMAe9(CD>6joCP zu?GO4EI@cW&`CsinW*Oo9=b;b;0b;Y!2szoTa0t*8k?gJ% zV`ky@xxz#X<7S5rwTADpQ^UjDh^t^Ecm956)4+ZYodLhp%~H%+vra?j+997`y!5d_ z10P%d2ihUl8Oq7rN$9W5eS?P$8*T{E{=NIuws%qu=-YQd`zDF=B5o7lwh=3jL#&*% zu`^DHmQ!I~4A49QUJ^w8jRxzDaTY5A!Qp3*;Bmr@@!|xOF;R?XrG-UPt$2{BD6`b; z)GD32C5tB`b_}jl`X*s?$DZu_1sb>S%&znh_={&Iqd&(@$j`B&CW%-FdUHsWupd=Q z{s^OcSs41^B(pWGl0x|^A{RJYro1elV>%Argj!MR;S1Z;jOW`DKev%>3SN&)fAkzC zcEiv$iXNdz59~537ye&w*8&|?afbi7d++XMvq?7Z2jmSQ&n3xb$%7>(8d7lBBrPT1CJ|EmE}BW2*-(qDL)IijeI$ zbMGdbK&q!br+d%rKQs5v+?oHMJ9qBP{NF!P&!lzK$XNcT&j=Xp%@_XwHu6ST`Tg(D z?UkJJ`?@N$=19(qUS@*6EN8~BB0g@lSDq$0hxExeNKU2yJJW1Xrud{u)?H@J=*PLu zT->MM#}+d5e-+hYwE<5%$9)Buj`*Mowdul>d-gkYmtSTJ2cLW$Jg_34H8;+OL63Mi zpOI#j!*x~%Gxs3zqL+>i_VDMIQ8MgNv@rXP$~1Of;D#>Q!2<3>;s8uKb2HqSzd-$@ z7e%@JsZz8my-lm6fEU7GeW;#pMc8lX_g71cal5zH9L^b#p~7c%gBG z-+LnN{n~%N_fUPfjy2fauh|Ia_trS&Hi#jIYVt=lMjfuR@v((&BIjMdF9S6;qTZfV zX@KQ{hhx@Ty*CF}8@@L}{Cj1Ldq?I6+ky=?@t^Ft)blRfne!}R@QZ(4U{Uy2<=4qh zNq5U}=y~vs1TAM7s!D*UQwig{5y5h!usom{O z*BUG=aibaBNR|3|CidT;4RQJqoBarz{jh3nldGT$HFVf_pogU@p);Kbo9hO(<){V} zU{ILnCms@XhrB8Rnx$nF7z>KUs2cfXjLZjW|NV-{YB(Rp^T}6rva7Gt#YR45z(l=_ z_k9K*`%dB&2Do}3-{H?;q=2Y4$%IupjDR`9(Ky^d77>xQUIh^xEje?tJN~$n!htuhvIGxa39>N501T4w=c``#pv*B54d^3xY4}+zz-H?= z3WM%_N>FGnD#UTMyivlB&09dI5O!&RE~XdV92SCaY?_953J`67q`HiZ-O<=t)*qxU z9dqy!4qT71EFb9cm*63Pa|j>)@g|Un(P}a~ONJ0~9yxcB4uPO1n#I*L-~bbEuN@f7*M-*botd@y}C z{5?ml;Cn-1aZh|OMe&V*9AFtn_Igdi2+obL@x=7-c-b<^ps!@64xh;|DKAMBFkfPoU&qb!%$_62waKlUlvnN7l;+)E`PU(Y8Zo zKYgHBbNpYb$FakQApg0*@eJ?|Y!ui(*VDNU@LxJ8>N6Vj|Ek-Id#>a4F?5Og@SP;D zsx|7(%GR*Y4+Tf3VNZ<^S6^sU2HHAbD~*E|H$Wm`001khVmHb66CTHoi@_s~-Z3?| zFU!iG`>YHo##pns*UAB3u!#bDqk3(SG2e7z=*`B!xOXDZv)tHvl7uNs!nSdA z4}fq1kPGY~!~x(3(PJ|ykuz_M)^I)ZZXTHU^OjCGUDes%$ z;;uN3FA8#8qj2o^!i}vmD?C^9McB;Lc zc9xTK(s|&e;Kv44C)uf8+C~}jewrxvP>u36-KsPKa>hX!(w}Ipbeb}C4^xa>L0M8P z73dzNM*zXHReHHwSJmTpk=hKU-Rg>&s{fU~jDQ)3gb+O%A0X z19o`h?QBqzsZE(cvy>vl?|^$0;cBW?_EDSEK-=VfG)>7u{5)#YU%)xUD{E=CQUdxk z)hU&r0aPX5jr_Jzvl8v`7{-xF8ApLioaY)0os~$X)S_GF`3f{p*G&_2ThV1dk2J-U zC2s^aQG}p2c`7-jPw^t#NH)0nQVz9B6FfWQMRYs&>p#J@8)<=dp8IXmMZ`r>nsgSg zkBw9-$6_S)28Dwva;)cD&{FA9N|pTS8PGH(2pGNKzqE{Y$OGWZ30kIfP`P}LLL@W& zSP7@w_{+m%GxBG-{a zdJwokg@`Mo)w-vsgQr&>rv=JORIb09O65p|Ip1&K44q`*vJi6Mve2B=;2D4{Hh~_1 zdt9ld=pi|9S@<~c(5`HST(~TJ9hb#7!eE0L8+2l zpg)KEseBymbyyBuR>Q7~rplW{xK_x6%S6Zpvbc*DahVuS&_aWqW{CFTM{9L|rdHi+ z{LO%&utp_}i`mC;PSw%ZpqugV0D_3i!t0bRv{!#0ZD3uL3}eSolA)Z#+4!p${s^gQP{Dk9t&tYbFoX(Zk8F(S6KInam*-~ZMY&mAR5}X-aAG|%d zC-_wGr4UO~mV;nKtW1fh)Fs=f}>T#cpzis?yvDLAzxXic(@#grh_=E9h;x8u*B%Ds1 zkmyQ$C8;E-Dd}+1xnxsvadJ~~U-GF5^Cz60*q0KOGCyT^%9%-dle$wwQdgx>+RU^) zY3I|c($}Q#OFx|vmf^}cn^}?hL{>=FZmY?fY3-XVO|F^zOtv|DX7(dFrkw8Fgj~1n zHrtiF!}b>YS5vB{?9bQdZ_YoM|3N`uL3P2ag(Zc13NIJc7kx0bdg=xTITkqjr-e=1 zJnfz0yy87KCEV0=)76rMlG-tjrV>}l=8`=nhfCfsrP7qr#?qeBkDL}~HICKJZfAE{ zSlQMx_w@18x0L6VLtTn^-+m6O$O6u%CJ{pv&LoUUnGD5ZDuz@2468Q9 znqPvZ#(d4M!)#om=9e+s(5?BorQH`azaD8n)BFZ1K@rqE{m9HpG(WW4S+(XjQwqCX z^9N8p>(u;#RKxzJ`7LCYrf7bQ+9jvv51}Z@o6j+dki2DkowB9%+BL^1P!A9Uqa88T^h4C49JrWs@t*}5 z@jQLy?kja)UJl3Kg3>Pm@4PLI%PAXk+8v@+72<3H%5Q_?@+-nOVh;Xuad_LBU2vlb z<>sx5w>RE8d5*&^crJF3|K^VrVXuK&nn~csjUSf!0(Bepf*&s~XyP zGoj}^3n&Y-=pb$X2sLPia=6P!8$?j57! z^cJ0@H|S0JJ7o7qdYk?LOz36w6+K9&=@h+-mi0M(0c-3l!SiOc^%h9u7m&#vXj}Zb z?0}rsptYT&wfII~2Yi6LuxI!Pv^6$jAJ!%ya-OFzX#g{tz4U83L+>#^D2n;Rh;;xA zYy>e2UR+8F}^db<`dB4EhS0)7Y>6SaT|0l#=H;E#am0WDxgz&}h4 z0IWm1doW@HQY9?BSFFMPEeHk|4F!V|$_EH)a5NMXhzo);hk_yef`E;l;R`}>LCEZ( X3&Ic#E%XNK++i8SYTo{GJ<-1bF1c8o diff --git a/docs/src/public/fonts/roboto-black.woff b/docs/src/public/fonts/roboto-black.woff deleted file mode 100755 index 642e5b60f7c662ab5bcf67cfc1089f50bfdd7b00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24536 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‚ZNmb*+jwao2 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 E0ADU%yZ`_I diff --git a/docs/src/public/stylesheets/normalize.css b/docs/src/public/stylesheets/normalize.css deleted file mode 100644 index 73abb76f..00000000 --- a/docs/src/public/stylesheets/normalize.css +++ /dev/null @@ -1,375 +0,0 @@ -/*! normalize.css v2.0.1 | MIT License | git.io/normalize */ - -/* ========================================================================== - HTML5 display definitions - ========================================================================== */ - -/* - * Corrects `block` display not defined in IE 8/9. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -nav, -section, -summary { - display: block; -} - -/* - * Corrects `inline-block` display not defined in IE 8/9. - */ - -audio, -canvas, -video { - display: inline-block; -} - -/* - * Prevents modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/* - * Addresses styling for `hidden` attribute not present in IE 8/9. - */ - -[hidden] { - display: none; -} - -/* ========================================================================== - Base - ========================================================================== */ - -/* - * 1. Sets default font family to sans-serif. - * 2. Prevents iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ - -ms-text-size-adjust: 100%; /* 2 */ -} - -/* - * Removes default margin. - */ - -body { - margin: 0; -} - -/* ========================================================================== - Links - ========================================================================== */ - -/* - * Addresses `outline` inconsistency between Chrome and other browsers. - */ - -a:focus { - outline: thin dotted; -} - -/* - * Improves readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* ========================================================================== - Typography - ========================================================================== */ - -/* - * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, - * Safari 5, and Chrome. - */ - -h1 { - font-size: 2em; -} - -/* - * Addresses styling not present in IE 8/9, Safari 5, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/* - * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/* - * Addresses styling not present in Safari 5 and Chrome. - */ - -dfn { - font-style: italic; -} - -/* - * Addresses styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - - -/* - * Corrects font family set oddly in Safari 5 and Chrome. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, serif; - font-size: 1em; -} - -/* - * Improves readability of pre-formatted text in all browsers. - */ - -pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; -} - -/* - * Sets consistent quote types. - */ - -q { - quotes: "\201C" "\201D" "\2018" "\2019"; -} - -/* - * Addresses inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/* - * Prevents `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* ========================================================================== - Embedded content - ========================================================================== */ - -/* - * Removes border when inside `a` element in IE 8/9. - */ - -img { - border: 0; -} - -/* - * Corrects overflow displayed oddly in IE 9. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* ========================================================================== - Figures - ========================================================================== */ - -/* - * Addresses margin not present in IE 8/9 and Safari 5. - */ - -figure { - margin: 0; -} - -/* ========================================================================== - Forms - ========================================================================== */ - -/* - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/* - * 1. Corrects color not being inherited in IE 8/9. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/* - * 1. Corrects font family not being inherited in all browsers. - * 2. Corrects font size not being inherited in all browsers. - * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome - */ - -button, -input, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 2 */ - margin: 0; /* 3 */ -} - -/* - * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -button, -input { - line-height: normal; -} - -/* - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Corrects inability to style clickable `input` types in iOS. - * 3. Improves usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/* - * Re-set default cursor for disabled elements. - */ - -button[disabled], -input[disabled] { - cursor: default; -} - -/* - * 1. Addresses box sizing set to `content-box` in IE 8/9. - * 2. Removes excess padding in IE 8/9. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/* - * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. - * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; /* 2 */ - box-sizing: content-box; -} - -/* - * Removes inner padding and search cancel button in Safari 5 and Chrome - * on OS X. - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/* - * Removes inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/* - * 1. Removes default vertical scrollbar in IE 8/9. - * 2. Improves readability and alignment in all browsers. - */ - -textarea { - overflow: auto; /* 1 */ - vertical-align: top; /* 2 */ -} - -/* ========================================================================== - Tables - ========================================================================== */ - -/* - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} \ No newline at end of file diff --git a/docs/src/view.flot.html b/docs/src/view.flot.html deleted file mode 100644 index a8b69629..00000000 --- a/docs/src/view.flot.html +++ /dev/null @@ -1,865 +0,0 @@ - - - - - view.flot.js - - - - - -

    -
    - -
    - -
      - -
    • -
      -

      view.flot.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      Graph view for a Dataset using Flot graphing library.

      -

      Initialization arguments (in a hash in first parameter):

      -
        -
      • model: recline.Model.Dataset
      • -
      • state: (optional) configuration hash of form:

        -
         {
        -   group: {column name for x-axis},
        -   series: [{column name for series A}, {column name series B}, ... ],
        -   // options are: lines, points, lines-and-points, bars, columns
        -   graphType: 'lines',
        -   graphOptions: {custom [flot options]}
        - }
        -
      • -
      -

      NB: should not provide an el argument to the view but must let the view -generate the element itself (you can then append view.el to the DOM.

      - -
      - -
      my.Flot = Backbone.View.extend({
      -  template: ' \
      -    <div class="recline-flot"> \
      -      <div class="panel graph" style="display: block;"> \
      -        <div class="js-temp-notice alert alert-warning alert-block"> \
      -          <h3 class="alert-heading">Hey there!</h3> \
      -          <p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
      -          <p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
      -        </div> \
      -      </div> \
      -    </div> \
      -',
      -
      -  initialize: function(options) {
      -    var self = this;
      -    this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
      -
      -    _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel');
      -    this.needToRedraw = false;
      -    this.listenTo(this.model, 'change', this.render);
      -    this.listenTo(this.model.fields, 'reset add', this.render);
      -    this.listenTo(this.model.records, 'reset add', this.redraw);
      -    var stateData = _.extend({
      -        group: null,
      - -
    • - - -
    • -
      - -
      - -
      -

      so that at least one series chooser box shows up

      - -
      - -
              series: [],
      -        graphType: 'lines-and-points'
      -      },
      -      options.state
      -    );
      -    this.state = new recline.Model.ObjectState(stateData);
      -    this.previousTooltipPoint = {x: null, y: null};
      -    this.editor = new my.FlotControls({
      -      model: this.model,
      -      state: this.state.toJSON()
      -    });
      -    this.listenTo(this.editor.state, 'change', function() {
      -      self.state.set(self.editor.state.toJSON());
      -      self.redraw();
      -    });
      -    this.elSidebar = this.editor.$el;
      -  },
      -
      -  render: function() {
      -    var self = this;
      -    var tmplData = this.model.toTemplateJSON();
      -    var htmls = Mustache.render(this.template, tmplData);
      -    this.$el.html(htmls);
      -    this.$graph = this.$el.find('.panel.graph');
      -    this.$graph.on("plothover", this._toolTip);
      -    return this;
      -  },
      -
      -  remove: function () {
      -    this.editor.remove();
      -    Backbone.View.prototype.remove.apply(this, arguments);
      -  },
      -
      -  redraw: function() {
      - -
    • - - -
    • -
      - -
      - -
      -

      There are issues generating a Flot graph if either:

      -
        -
      • The relevant div that graph attaches to his hidden at the moment of creating the plot — Flot will complain with -Uncaught Invalid dimensions for plot, width = 0, height = 0
      • -
      • There is no data for the plot — either same error or may have issues later with errors like ā€˜non-existent node-value’
      • -
      - -
      - -
          var areWeVisible = !jQuery.expr.filters.hidden(this.el);
      -    if ((!areWeVisible || this.model.records.length === 0)) {
      -      this.needToRedraw = true;
      -      return;
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      check we have something to plot

      - -
      - -
          if (this.state.get('group') && this.state.get('series')) {
      -      var series = this.createSeries();
      -      var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length);
      -      this.plot = $.plot(this.$graph, series, options);
      -    }
      -  },
      -
      -  show: function() {
      - -
    • - - -
    • -
      - -
      - -
      -

      because we cannot redraw when hidden we may need to when becoming visible

      - -
      - -
          if (this.needToRedraw) {
      -      this.redraw();
      -    }
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      infoboxes on mouse hover on points/bars etc

      - -
      - -
        _toolTip: function (event, pos, item) {
      -    if (item) {
      -      if (this.previousTooltipPoint.x !== item.dataIndex ||
      -          this.previousTooltipPoint.y !== item.seriesIndex) {
      -        this.previousTooltipPoint.x = item.dataIndex;
      -        this.previousTooltipPoint.y = item.seriesIndex;
      -        $("#recline-flot-tooltip").remove();
      -
      -        var x = item.datapoint[0].toFixed(2),
      -            y = item.datapoint[1].toFixed(2);
      -
      -        if (this.state.attributes.graphType === 'bars') {
      -          x = item.datapoint[1].toFixed(2),
      -          y = item.datapoint[0].toFixed(2);
      -        }
      -
      -        var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
      -          group: this.state.attributes.group,
      -          x: this._xaxisLabel(x),
      -          series: item.series.label,
      -          y: y
      -        });
      - -
    • - - -
    • -
      - -
      - -
      -

      use a different tooltip location offset for bar charts

      - -
      - -
              var xLocation, yLocation;
      -        if (this.state.attributes.graphType === 'bars') {
      -          xLocation = item.pageX + 15;
      -          yLocation = item.pageY - 10;
      -        } else if (this.state.attributes.graphType === 'columns') {
      -          xLocation = item.pageX + 15;
      -          yLocation = item.pageY;
      -        } else {
      -          xLocation = item.pageX + 10;
      -          yLocation = item.pageY - 20;
      -        }
      -
      -        $('<div id="recline-flot-tooltip">' + content + '</div>').css({
      -            top: yLocation,
      -            left: xLocation
      -        }).appendTo("body").fadeIn(200);
      -      }
      -    } else {
      -      $("#recline-flot-tooltip").remove();
      -      this.previousTooltipPoint.x = null;
      -      this.previousTooltipPoint.y = null;
      -    }
      -  },
      -
      -  _xaxisLabel: function (x) {
      -    if (this._groupFieldIsDateTime()) {
      - -
    • - - -
    • -
      - -
      - -
      -

      oddly x comes through as milliseconds string (rather than int -or float) so we have to reparse

      - -
      - -
            x = new Date(parseFloat(x)).toLocaleDateString();
      -    } else if (this.xvaluesAreIndex) {
      -      x = parseInt(x, 10);
      - -
    • - - -
    • -
      - -
      - -
      -

      HACK: deal with bar graph style cases where x-axis items were strings -In this case x at this point is the index of the item in the list of -records not its actual x-axis value

      - -
      - -
            x = this.model.records.models[x].get(this.state.attributes.group);
      -    }
      -
      -    return x;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      getGraphOptions

      -

      Get options for Flot Graph

      -

      needs to be function as can depend on state

      -

      @param typeId graphType id (lines, lines-and-points etc) -@param numPoints the number of points that will be plotted

      - -
      - -
        getGraphOptions: function(typeId, numPoints) {
      -    var self = this;
      -    var groupFieldIsDateTime = self._groupFieldIsDateTime();
      -    var xaxis = {};
      -
      -    if (!groupFieldIsDateTime) {
      -      xaxis.tickFormatter = function (x) {
      - -
    • - - -
    • -
      - -
      - -
      -

      convert x to a string and make sure that it is not too long or the -tick labels will overlap -TODO: find a more accurate way of calculating the size of tick labels

      - -
      - -
              var label = self._xaxisLabel(x) || "";
      -
      -        if (typeof label !== 'string') {
      -          label = label.toString();
      -        }
      -        if (self.state.attributes.graphType !== 'bars' && label.length > 10) {
      -          label = label.slice(0, 10) + "...";
      -        }
      -
      -        return label;
      -      };
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      for labels case we only want ticks at the label intervals -HACK: however we also get this case with Date fields. In that case we -could have a lot of values and so we limit to max 15 (we assume)

      - -
      - -
          if (this.xvaluesAreIndex) {
      -      var numTicks = Math.min(this.model.records.length, 15);
      -      var increment = this.model.records.length / numTicks;
      -      var ticks = [];
      -      for (var i=0; i<numTicks; i++) {
      -        ticks.push(parseInt(i*increment, 10));
      -      }
      -      xaxis.ticks = ticks;
      -    } else if (groupFieldIsDateTime) {
      -      xaxis.mode = 'time';
      -    }
      -
      -    var yaxis = {};
      -    yaxis.autoscale = true;
      -    yaxis.autoscaleMargin = 0.02;
      -
      -    var legend = {};
      -    legend.position = 'ne';
      -
      -    var grid = {};
      -    grid.hoverable = true;
      -    grid.clickable = true;
      -    grid.borderColor = "#aaaaaa";
      -    grid.borderWidth = 1;
      -
      -    var optionsPerGraphType = {
      -      lines: {
      -        legend: legend,
      -        colors: this.graphColors,
      -        lines: { show: true },
      -        xaxis: xaxis,
      -        yaxis: yaxis,
      -        grid: grid
      -      },
      -      points: {
      -        legend: legend,
      -        colors: this.graphColors,
      -        points: { show: true, hitRadius: 5 },
      -        xaxis: xaxis,
      -        yaxis: yaxis,
      -        grid: grid
      -      },
      -      'lines-and-points': {
      -        legend: legend,
      -        colors: this.graphColors,
      -        points: { show: true, hitRadius: 5 },
      -        lines: { show: true },
      -        xaxis: xaxis,
      -        yaxis: yaxis,
      -        grid: grid
      -      },
      -      bars: {
      -        legend: legend,
      -        colors: this.graphColors,
      -        lines: { show: false },
      -        xaxis: yaxis,
      -        yaxis: xaxis,
      -        grid: grid,
      -        bars: {
      -          show: true,
      -          horizontal: true,
      -          shadowSize: 0,
      -          align: 'center',
      -          barWidth: 0.8
      -        }
      -      },
      -      columns: {
      -        legend: legend,
      -        colors: this.graphColors,
      -        lines: { show: false },
      -        xaxis: xaxis,
      -        yaxis: yaxis,
      -        grid: grid,
      -        bars: {
      -          show: true,
      -          horizontal: false,
      -          shadowSize: 0,
      -          align: 'center',
      -          barWidth: 0.8
      -        }
      -      }
      -    };
      -
      -    if (self.state.get('graphOptions')) {
      -      return _.extend(optionsPerGraphType[typeId],
      -                      self.state.get('graphOptions'));
      -    } else {
      -      return optionsPerGraphType[typeId];
      -    }
      -  },
      -
      -  _groupFieldIsDateTime: function() {
      -    var xfield = this.model.fields.get(this.state.attributes.group);
      -    var xtype = xfield.get('type');
      -    var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype  === 'time');
      -    return isDateTime;
      -  },
      -
      -  createSeries: function() {
      -    var self = this;
      -    self.xvaluesAreIndex = false;
      -    var series = [];
      -    var xfield = self.model.fields.get(self.state.attributes.group);
      -    var isDateTime = self._groupFieldIsDateTime();
      -
      -    _.each(this.state.attributes.series, function(field) {
      -      var points = [];
      -      var fieldLabel = self.model.fields.get(field).get('label');
      -
      -        if (isDateTime){
      -            var cast = function(x){
      -                var _date = moment(String(x));
      -                if (_date.isValid()) {
      -                    x = _date.toDate().getTime();
      -                }
      -                return x
      -            }
      -        } else {
      -            var raw = _.map(self.model.records.models,
      -                            function(doc, index){
      -                                return doc.getFieldValueUnrendered(xfield)
      -                            });
      -
      -            if (_.all(raw, function(x){ return !isNaN(parseFloat(x)) })){
      -                var cast = function(x){ return parseFloat(x) }
      -            } else {
      -                self.xvaluesAreIndex = true
      -            }
      -        }
      -
      -      _.each(self.model.records.models, function(doc, index) {
      -        if(self.xvaluesAreIndex){
      -            var x = index;
      -        }else{
      -            var x = cast(doc.getFieldValueUnrendered(xfield));
      -        }
      -
      -        var yfield = self.model.fields.get(field);
      -        var y = parseFloat(doc.getFieldValueUnrendered(yfield));
      -
      -        if (self.state.attributes.graphType == 'bars') {
      -          points.push([y, x]);
      -        } else {
      -          points.push([x, y]);
      -        }
      -      });
      -      series.push({
      -        data: points,
      -        label: fieldLabel,
      -        hoverable: true
      -      });
      -    });
      -    return series;
      -  }
      -});
      -
      -my.FlotControls = Backbone.View.extend({
      -  className: "editor",
      -  template: ' \
      -  <div class="editor"> \
      -    <form class="form-stacked"> \
      -      <div class="clearfix"> \
      -        <div class="form-group"> \
      -          <label>Graph Type</label> \
      -          <div class="input editor-type"> \
      -            <select class="form-control"> \
      -              <option value="lines-and-points">Lines and Points</option> \
      -              <option value="lines">Lines</option> \
      -              <option value="points">Points</option> \
      -              <option value="bars">Bars</option> \
      -              <option value="columns">Columns</option> \
      -            </select> \
      -          </div> \
      -        </div> \
      -        <div class="form-group"> \
      -          <label>Group Column (Axis 1)</label> \
      -          <div class="input editor-group"> \
      -            <select class="form-control"> \
      -              <option value="">Please choose ...</option> \
      -                {{#fields}} \
      -              <option value="{{id}}">{{label}}</option> \
      -                {{/fields}} \
      -            </select> \
      -          </div> \
      -        </div> \
      -        <div class="editor-series-group"> \
      -        </div> \
      -      </div> \
      -      <div class="editor-buttons"> \
      -        <button class="btn btn-default editor-add">Add Series</button> \
      -      </div> \
      -      <div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
      -        <button class="editor-save">Save</button> \
      -        <input type="hidden" class="editor-id" value="chart-1" /> \
      -      </div> \
      -    </form> \
      -  </div> \
      -',
      -  templateSeriesEditor: ' \
      -    <div class="editor-series js-series-{{seriesIndex}}"> \
      -      <div class="form-group"> \
      -        <label>Series <span>{{seriesName}} (Axis 2)</span> \
      -          [<a href="#remove" class="action-remove-series">Remove</a>] \
      -        </label> \
      -        <div class="input"> \
      -          <select class="form-control"> \
      -          {{#fields}} \
      -          <option value="{{id}}">{{label}}</option> \
      -          {{/fields}} \
      -          </select> \
      -        </div> \
      -      </div> \
      -    </div> \
      -  ',
      -  events: {
      -    'change form select': 'onEditorSubmit',
      -    'click .editor-add': '_onAddSeries',
      -    'click .action-remove-series': 'removeSeries'
      -  },
      -
      -  initialize: function(options) {
      -    var self = this;
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model.fields, 'reset add', this.render);
      -    this.state = new recline.Model.ObjectState(options.state);
      -    this.render();
      -  },
      -
      -  render: function() {
      -    var self = this;
      -    var tmplData = this.model.toTemplateJSON();
      -    var htmls = Mustache.render(this.template, tmplData);
      -    this.$el.html(htmls);
      - -
    • - - -
    • -
      - -
      - -
      -

      set up editor from state

      - -
      - -
          if (this.state.get('graphType')) {
      -      this._selectOption('.editor-type', this.state.get('graphType'));
      -    }
      -    if (this.state.get('group')) {
      -      this._selectOption('.editor-group', this.state.get('group'));
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      ensure at least one series box shows up

      - -
      - -
          var tmpSeries = [""];
      -    if (this.state.get('series').length > 0) {
      -      tmpSeries = this.state.get('series');
      -    }
      -    _.each(tmpSeries, function(series, idx) {
      -      self.addSeries(idx);
      -      self._selectOption('.editor-series.js-series-' + idx, series);
      -    });
      -    return this;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Helper function to select an option from a select list

      - -
      - -
        _selectOption: function(id,value){
      -    var options = this.$el.find(id + ' select > option');
      -    if (options) {
      -      options.each(function(opt){
      -        if (this.value == value) {
      -          $(this).attr('selected','selected');
      -          return false;
      -        }
      -      });
      -    }
      -  },
      -
      -  onEditorSubmit: function(e) {
      -    var select = this.$el.find('.editor-group select');
      -    var $editor = this;
      -    var $series = this.$el.find('.editor-series select');
      -    var series = $series.map(function () {
      -      return $(this).val();
      -    });
      -    var updatedState = {
      -      series: $.makeArray(series),
      -      group: this.$el.find('.editor-group select').val(),
      -      graphType: this.$el.find('.editor-type select').val()
      -    };
      -    this.state.set(updatedState);
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Adds a new empty series select box to the editor.

      -

      @param [int] idx index of this series in the list of series

      -

      Returns itself.

      - -
      - -
        addSeries: function (idx) {
      -    var data = _.extend({
      -      seriesIndex: idx,
      -      seriesName: String.fromCharCode(idx + 64 + 1)
      -    }, this.model.toTemplateJSON());
      -
      -    var htmls = Mustache.render(this.templateSeriesEditor, data);
      -    this.$el.find('.editor-series-group').append(htmls);
      -    return this;
      -  },
      -
      -  _onAddSeries: function(e) {
      -    e.preventDefault();
      -    this.addSeries(this.state.get('series').length);
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Removes a series list item from the editor.

      -

      Also updates the labels of the remaining series elements.

      - -
      - -
        removeSeries: function (e) {
      -    e.preventDefault();
      -    var $el = $(e.target);
      -    $el.parent().parent().remove();
      -    this.onEditorSubmit();
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/view.graph.html b/docs/src/view.graph.html deleted file mode 100644 index a6cbb03d..00000000 --- a/docs/src/view.graph.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - view.graph.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      view.graph.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -this.recline.View.Graph = this.recline.View.Flot;
      -this.recline.View.GraphControls = this.recline.View.FlotControls;
      - -
    • - -
    -
    - - diff --git a/docs/src/view.grid.html b/docs/src/view.grid.html deleted file mode 100644 index 09a91eca..00000000 --- a/docs/src/view.grid.html +++ /dev/null @@ -1,606 +0,0 @@ - - - - - view.grid.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      view.grid.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      (Data) Grid Dataset View

      -

      Provides a tabular view on a Dataset.

      -

      Initialize it with a recline.Model.Dataset.

      - -
      - -
      my.Grid = Backbone.View.extend({
      -  tagName:  "div",
      -  className: "recline-grid-container",
      -
      -  initialize: function(modelEtc) {
      -    var self = this;
      -    _.bindAll(this, 'render', 'onHorizontalScroll');
      -    this.listenTo(this.model.records, 'add reset remove', this.render);
      -    this.tempState = {};
      -    var state = _.extend({
      -        hiddenFields: []
      -      }, modelEtc.state
      -    ); 
      -    this.state = new recline.Model.ObjectState(state);
      -  },
      -
      -  events: {
      - -
    • - - -
    • -
      - -
      - -
      -

      does not work here so done at end of render function -ā€˜scroll .recline-grid tbody’: ā€˜onHorizontalScroll’

      - -
      - -
        },
      - -
    • - - -
    • -
      - -
      - -
      -

      ======================================================

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Column and row menus

      - -
      - -
      -  setColumnSort: function(order) {
      -    var sort = [{}];
      -    sort[0][this.tempState.currentColumn] = {order: order};
      -    this.model.query({sort: sort});
      -  },
      -  
      -  hideColumn: function() {
      -    var hiddenFields = this.state.get('hiddenFields');
      -    hiddenFields.push(this.tempState.currentColumn);
      -    this.state.set({hiddenFields: hiddenFields});
      - -
    • - - -
    • -
      - -
      - -
      -

      change event not being triggered (because it is an array?) so trigger manually

      - -
      - -
          this.state.trigger('change');
      -    this.render();
      -  },
      -  
      -  showColumn: function(e) {
      -    var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column'));
      -    this.state.set({hiddenFields: hiddenFields});
      -    this.render();
      -  },
      -
      -  onHorizontalScroll: function(e) {
      -    var currentScroll = $(e.target).scrollLeft();
      -    this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll);
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      ======================================================

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Templating

      - -
      - -
        template: ' \
      -    <div class="table-container"> \
      -    <table class="recline-grid table-striped table-condensed" cellspacing="0"> \
      -      <thead class="fixed-header"> \
      -        <tr> \
      -          {{#fields}} \
      -            <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;" title="{{label}}"> \
      -              <span class="column-header-name">{{label}}</span> \
      -            </th> \
      -          {{/fields}} \
      -          <th class="last-header" style="width: {{lastHeaderWidth}}px; max-width: {{lastHeaderWidth}}px; min-width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
      -        </tr> \
      -      </thead> \
      -      <tbody class="scroll-content"></tbody> \
      -    </table> \
      -    </div> \
      -  ',
      -
      -  toTemplateJSON: function() {
      -    var self = this; 
      -    var modelData = this.model.toJSON();
      -    modelData.notEmpty = ( this.fields.length > 0 );
      - -
    • - - -
    • -
      - -
      - -
      -

      TODO: move this sort of thing into a toTemplateJSON method on Dataset?

      - -
      - -
          modelData.fields = this.fields.map(function(field) {
      -      return field.toJSON();
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      last header width = scroll bar - border (2px) */

      - -
      - -
          modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2;
      -    return modelData;
      -  },
      -  render: function() {
      -    var self = this;
      -    this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) {
      -      return _.indexOf(self.state.get('hiddenFields'), field.id) == -1;
      -    }));
      -
      -    this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions
      -    var numFields = this.fields.length;
      - -
    • - - -
    • -
      - -
      - -
      -

      compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar)

      - -
      - -
          var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width;
      -    var width = parseInt(Math.max(50, fullWidth / numFields), 10);
      - -
    • - - -
    • -
      - -
      - -
      -

      if columns extend outside viewport then remainder is 0

      - -
      - -
          var remainder = Math.max(fullWidth - numFields * width,0);
      -    this.fields.each(function(field, idx) {
      - -
    • - - -
    • -
      - -
      - -
      -

      add the remainder to the first field width so we make up full col

      - -
      - -
            if (idx === 0) {
      -        field.set({width: width+remainder});
      -      } else {
      -        field.set({width: width});
      -      }
      -    });
      -    var htmls = Mustache.render(this.template, this.toTemplateJSON());
      -    this.$el.html(htmls);
      -    this.model.records.forEach(function(doc) {
      -      var tr = $('<tr />');
      -      self.$el.find('tbody').append(tr);
      -      var newView = new my.GridRow({
      -          model: doc,
      -          el: tr,
      -          fields: self.fields
      -        });
      -      newView.render();
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      hide extra header col if no scrollbar to avoid unsightly overhang

      - -
      - -
          var $tbody = this.$el.find('tbody')[0];
      -    if ($tbody.scrollHeight <= $tbody.offsetHeight) {
      -      this.$el.find('th.last-header').hide();
      -    }
      -    this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0));
      -    this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll);
      -    return this;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      _scrollbarSize

      -

      Measure width of a vertical scrollbar and height of a horizontal scrollbar.

      -

      @return: { width: pixelWidth, height: pixelHeight }

      - -
      - -
        _scrollbarSize: function() {
      -    var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
      -    var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
      -    $c.remove();
      -    return dim;
      -  }
      -});
      - -
    • - - -
    • -
      - -
      - -
      -

      GridRow View for rendering an individual record.

      -

      Since we want this to update in place it is up to creator to provider the element to attach to.

      -

      In addition you must pass in a FieldList in the constructor options. This should be list of fields for the Grid.

      -

      Example:

      -
      -var row = new GridRow({
      -  model: dataset-record,
      -    el: dom-element,
      -    fields: mydatasets.fields // a FieldList object
      -  });
      -
      - -
      - -
      my.GridRow = Backbone.View.extend({
      -  initialize: function(initData) {
      -    _.bindAll(this, 'render');
      -    this._fields = initData.fields;
      -    this.listenTo(this.model, 'change', this.render);
      -  },
      -
      -  template: ' \
      -      {{#cells}} \
      -      <td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;"> \
      -        <div class="data-table-cell-content"> \
      -          <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
      -          <div class="data-table-cell-value">{{{value}}}</div> \
      -        </div> \
      -      </td> \
      -      {{/cells}} \
      -    ',
      -  events: {
      -    'click .data-table-cell-edit': 'onEditClick',
      -    'click .data-table-cell-editor .okButton': 'onEditorOK',
      -    'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
      -  },
      -  
      -  toTemplateJSON: function() {
      -    var self = this;
      -    var doc = this.model;
      -    var cellData = this._fields.map(function(field) {
      -      return {
      -        field: field.id,
      -        width: field.get('width'),
      -        value: doc.getFieldValue(field)
      -      };
      -    });
      -    return { id: this.id, cells: cellData };
      -  },
      -
      -  render: function() {
      -    this.$el.attr('data-id', this.model.id);
      -    var html = Mustache.render(this.template, this.toTemplateJSON());
      -    this.$el.html(html);
      -    return this;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      ===================

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Cell Editor methods

      - -
      - -
      -  cellEditorTemplate: ' \
      -    <div class="menu-container data-table-cell-editor"> \
      -      <textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
      -      <div id="data-table-cell-editor-actions"> \
      -        <div class="data-table-cell-editor-action"> \
      -          <button class="okButton btn primary">Update</button> \
      -          <button class="cancelButton btn danger">Cancel</button> \
      -        </div> \
      -      </div> \
      -    </div> \
      -  ',
      -
      -  onEditClick: function(e) {
      -    var editing = this.$el.find('.data-table-cell-editor-editor');
      -    if (editing.length > 0) {
      -      editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden");
      -    }
      -    $(e.target).addClass("hidden");
      -    var cell = $(e.target).siblings('.data-table-cell-value');
      -    cell.data("previousContents", cell.text());
      -    var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()});
      -    cell.html(templated);
      -  },
      -
      -  onEditorOK: function(e) {
      -    var self = this;
      -    var cell = $(e.target);
      -    var rowId = cell.parents('tr').attr('data-id');
      -    var field = cell.parents('td').attr('data-field');
      -    var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
      -    var newData = {};
      -    newData[field] = newValue;
      -    this.model.set(newData);
      -    this.trigger('recline:flash', {message: "Updating row...", loader: true});
      -    this.model.save().then(function(response) {
      -        this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'});
      -      })
      -      .fail(function() {
      -        this.trigger('recline:flash', {
      -          message: 'Error saving row',
      -          category: 'error',
      -          persist: true
      -        });
      -      });
      -  },
      -
      -  onEditorCancel: function(e) {
      -    var cell = $(e.target).parents('.data-table-cell-value');
      -    cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/view.map.html b/docs/src/view.map.html deleted file mode 100644 index 3bd2ebdd..00000000 --- a/docs/src/view.map.html +++ /dev/null @@ -1,1331 +0,0 @@ - - - - - view.map.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      view.map.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      Map view for a Dataset using Leaflet mapping library.

      -

      This view allows to plot gereferenced records on a map. The location -information can be provided in 2 ways:

      -
        -
      1. Via a single field. This field must be either a geo_point or -GeoJSON object
      2. -
      3. Via two fields with latitude and longitude coordinates.
      4. -
      -

      Which fields in the data these correspond to can be configured via the state -(and are guessed if no info is provided).

      -

      Initialization arguments are as standard for Dataset Views. State object may -have the following (optional) configuration options:

      -
      -  {
      -    // geomField if specified will be used in preference to lat/lon
      -    geomField: {id of field containing geometry in the dataset}
      -    lonField: {id of field containing longitude in the dataset}
      -    latField: {id of field containing latitude in the dataset}
      -    autoZoom: true,
      -    // use cluster support
      -    // cluster: true = always on
      -    // cluster: false = always off
      -    cluster: false
      -  }
      -
      - -

      Useful attributes to know about (if e.g. customizing)

      -
        -
      • map: the Leaflet map (L.Map)
      • -
      • features: Leaflet GeoJSON layer containing all the features (L.GeoJSON)
      • -
      - -
      - -
      my.Map = Backbone.View.extend({
      -  template: ' \
      -    <div class="recline-map"> \
      -      <div class="panel map"></div> \
      -    </div> \
      -',
      - -
    • - - -
    • -
      - -
      - -
      -

      These are the default (case-insensitive) names of field that are used if found. -If not found, the user will need to define the fields via the editor.

      - -
      - -
        latitudeFieldNames: ['lat','latitude'],
      -  longitudeFieldNames: ['lon','longitude'],
      -  geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location', 'geo', 'lonlat'],
      -
      -  initialize: function(options) {
      -    var self = this;
      -    this.visible = this.$el.is(':visible');
      -    this.mapReady = false;
      - -
    • - - -
    • -
      - -
      - -
      -

      this will be the Leaflet L.Map object (setup below)

      - -
      - -
          this.map = null;
      -
      -    var stateData = _.extend({
      -        geomField: null,
      -        lonField: null,
      -        latField: null,
      -        autoZoom: true,
      -        cluster: false
      -      },
      -      options.state
      -    );
      -    this.state = new recline.Model.ObjectState(stateData);
      -
      -    this._clusterOptions = {
      -      zoomToBoundsOnClick: true,
      - -
    • - - -
    • -
      - -
      - -
      -

      disableClusteringAtZoom: 10,

      - -
      - -
            maxClusterRadius: 80,
      -      singleMarkerMode: false,
      -      skipDuplicateAddTesting: true,
      -      animateAddingMarkers: false
      -    };
      - -
    • - - -
    • -
      - -
      - -
      -

      Listen to changes in the fields

      - -
      - -
          this.listenTo(this.model.fields, 'change', function() {
      -      self._setupGeometryField();
      -      self.render();
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      Listen to changes in the records

      - -
      - -
          this.listenTo(this.model.records, 'add', function(doc){self.redraw('add',doc);});
      -    this.listenTo(this.model.records, 'change', function(doc){
      -        self.redraw('remove',doc);
      -        self.redraw('add',doc);
      -    });
      -    this.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);});
      -    this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');});
      -
      -    this.menu = new my.MapMenu({
      -      model: this.model,
      -      state: this.state.toJSON()
      -    });
      -    this.listenTo(this.menu.state, 'change', function() {
      -      self.state.set(self.menu.state.toJSON());
      -      self.redraw();
      -    });
      -    this.listenTo(this.state, 'change', function() {
      -      self.redraw();
      -    });
      -    this.elSidebar = this.menu.$el;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Customization Functions

      -

      The following methods are designed for overriding in order to customize -behaviour

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      infobox

      -

      Function to create infoboxes used in popups. The default behaviour is very simple and just lists all attributes.

      -

      Users should override this function to customize behaviour i.e.

      -
      view = new View({...});
      -view.infobox = function(record) {
      -  ...
      -}
      -
      -
      - -
        infobox: function(record) {
      -    var html = '';
      -    for (var key in record.attributes){
      -      if (!(this.state.get('geomField') && key == this.state.get('geomField'))){
      -        html += '<div><strong>' + key + '</strong>: '+ record.attributes[key] + '</div>';
      -      }
      -    }
      -    return html;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Options to use for the Leaflet GeoJSON layer -See also http://leaflet.cloudmade.com/examples/geojson.html

      -

      e.g.

      -
      pointToLayer: function(feature, latLng)
      -onEachFeature: function(feature, layer)
      -

      See defaults for examples

      - -
      - -
        geoJsonLayerOptions: {
      - -
    • - - -
    • -
      - -
      - -
      -

      pointToLayer function to use when creating points

      -

      Default behaviour shown here is to create a marker using the -popupContent set on the feature properties (created via infobox function -during feature generation)

      -

      NB: inside pointToLayer this will be set to point to this map view -instance (which allows e.g. this.markers to work in this default case)

      - -
      - -
          pointToLayer: function (feature, latlng) {
      -      var marker = new L.Marker(latlng);
      -      marker.bindPopup(feature.properties.popupContent);
      - -
    • - - -
    • -
      - -
      - -
      -

      this is for cluster case

      - -
      - -
            this.markers.addLayer(marker);
      -      return marker;
      -    },
      - -
    • - - -
    • -
      - -
      - -
      -

      onEachFeature default which adds popup in

      - -
      - -
          onEachFeature: function(feature, layer) {
      -      if (feature.properties && feature.properties.popupContent) {
      -        layer.bindPopup(feature.properties.popupContent);
      -      }
      -    }
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      END: Customization section

      - -
      - -
    • - - -
    • -
      - -
      - -
      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Adds the necessary elements to the page.

      -

      Also sets up the editor fields and the map if necessary.

      - -
      - -
        render: function() {
      -    var self = this;
      -    var htmls = Mustache.render(this.template, this.model.toTemplateJSON());
      -    this.$el.html(htmls);
      -    this.$map = this.$el.find('.panel.map');
      -    this.redraw();
      -    return this;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Redraws the features on the map according to the action provided

      -

      Actions can be:

      -
        -
      • reset: Clear all features
      • -
      • add: Add one or n features (records)
      • -
      • remove: Remove one or n features (records)
      • -
      • refresh: Clear existing features and add all current records
      • -
      - -
      - -
        redraw: function(action, doc){
      -    var self = this;
      -    action = action || 'refresh';
      - -
    • - - -
    • -
      - -
      - -
      -

      try to set things up if not already

      - -
      - -
          if (!self._geomReady()){
      -      self._setupGeometryField();
      -    }
      -    if (!self.mapReady){
      -      self._setupMap();
      -    }
      -
      -    if (this._geomReady() && this.mapReady){
      - -
    • - - -
    • -
      - -
      - -
      -

      removing ad re-adding the layer enables faster bulk loading

      - -
      - -
            this.map.removeLayer(this.features);
      -      this.map.removeLayer(this.markers);
      -
      -      var countBefore = 0;
      -      this.features.eachLayer(function(){countBefore++;});
      -
      -      if (action == 'refresh' || action == 'reset') {
      -        this.features.clearLayers();
      - -
    • - - -
    • -
      - -
      - -
      -

      recreate cluster group because of issues with clearLayer

      - -
      - -
              this.map.removeLayer(this.markers);
      -        this.markers = new L.MarkerClusterGroup(this._clusterOptions);
      -        this._add(this.model.records.models);
      -      } else if (action == 'add' && doc){
      -        this._add(doc);
      -      } else if (action == 'remove' && doc){
      -        this._remove(doc);
      -      }
      - -
    • - - -
    • -
      - -
      - -
      -

      this must come before zooming! -if not: errors when using e.g. circle markers like -ā€œCannot call method ā€˜project’ of undefinedā€

      - -
      - -
            if (this.state.get('cluster')) {
      -        this.map.addLayer(this.markers);
      -      } else {
      -        this.map.addLayer(this.features);
      -      }
      -
      -      if (this.state.get('autoZoom')){
      -        if (this.visible){
      -          this._zoomToFeatures();
      -        } else {
      -          this._zoomPending = true;
      -        }
      -      }
      -    }
      -  },
      -
      -  show: function() {
      - -
    • - - -
    • -
      - -
      - -
      -

      If the div was hidden, Leaflet needs to recalculate some sizes -to display properly

      - -
      - -
          if (this.map){
      -      this.map.invalidateSize();
      -      if (this._zoomPending && this.state.get('autoZoom')) {
      -        this._zoomToFeatures();
      -        this._zoomPending = false;
      -      }
      -    }
      -    this.visible = true;
      -  },
      -
      -  hide: function() {
      -    this.visible = false;
      -  },
      -
      -  _geomReady: function() {
      -    return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Add one or n features to the map

      -

      For each record passed, a GeoJSON geometry will be extracted and added -to the features layer. If an exception is thrown, the process will be -stopped and an error notification shown.

      -

      Each feature will have a popup associated with all the record fields.

      - -
      - -
        _add: function(docs){
      -    var self = this;
      -
      -    if (!(docs instanceof Array)) docs = [docs];
      -
      -    var count = 0;
      -    var wrongSoFar = 0;
      -    _.every(docs, function(doc){
      -      count += 1;
      -      var feature = self._getGeometryFromRecord(doc);
      -      if (typeof feature === 'undefined' || feature === null){
      - -
    • - - -
    • -
      - -
      - -
      -

      Empty field

      - -
      - -
              return true;
      -      } else if (feature instanceof Object){
      -        feature.properties = {
      -          popupContent: self.infobox(doc),
      - -
    • - - -
    • -
      - -
      - -
      -

      Add a reference to the model id, which will allow us to -link this Leaflet layer to a Recline doc

      - -
      - -
                cid: doc.cid
      -        };
      -
      -        try {
      -          self.features.addData(feature);
      -        } catch (except) {
      -          wrongSoFar += 1;
      -          var msg = 'Wrong geometry value';
      -          if (except.message) msg += ' (' + except.message + ')';
      -          if (wrongSoFar <= 10) {
      -            self.trigger('recline:flash', {message: msg, category:'error'});
      -          }
      -        }
      -      } else {
      -        wrongSoFar += 1;
      -        if (wrongSoFar <= 10) {
      -          self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'});
      -        }
      -      }
      -      return true;
      -    });
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Remove one or n features from the map

      - -
      - -
        _remove: function(docs){
      -
      -    var self = this;
      -
      -    if (!(docs instanceof Array)) docs = [docs];
      -
      -    _.each(docs,function(doc){
      -      for (var key in self.features._layers){
      -        if (self.features._layers[key].feature.geometry.properties.cid == doc.cid){
      -          self.features.removeLayer(self.features._layers[key]);
      -        }
      -      }
      -    });
      -
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: convert DMS coordinates to decimal

      -

      north and east are positive, south and west are negative

      - -
      - -
        _parseCoordinateString: function(coord){
      -    if (typeof(coord) != 'string') {
      -      return(parseFloat(coord));
      -    }
      -    var dms = coord.split(/[^-?\.\d\w]+/);
      -    var deg = 0; var m = 0;
      -    var toDeg = [1, 60, 3600]; // conversion factors for Deg, min, sec
      -    var i;
      -    for (i = 0; i < dms.length; ++i) {
      -        if (isNaN(parseFloat(dms[i]))) {
      -          continue;
      -        }
      -        deg += parseFloat(dms[i]) / toDeg[m];
      -        m += 1;
      -    }
      -    if (coord.match(/[SW]/)) {
      -          deg = -1*deg;
      -    }
      -    return(deg);
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Return a GeoJSON geomtry extracted from the record fields

      - -
      - -
        _getGeometryFromRecord: function(doc){
      -    if (this.state.get('geomField')){
      -      var value = doc.get(this.state.get('geomField'));
      -      if (typeof(value) === 'string'){
      - -
    • - - -
    • -
      - -
      - -
      -

      We may have a GeoJSON string representation

      - -
      - -
              try {
      -          value = $.parseJSON(value);
      -        } catch(e) {}
      -      }
      -      if (typeof(value) === 'string') {
      -        value = value.replace('(', '').replace(')', '');
      -        var parts = value.split(',');
      -        var lat = this._parseCoordinateString(parts[0]);
      -        var lon = this._parseCoordinateString(parts[1]);
      -
      -        if (!isNaN(lon) && !isNaN(parseFloat(lat))) {
      -          return {
      -            "type": "Point",
      -            "coordinates": [lon, lat]
      -          };
      -        } else {
      -          return null;
      -        }
      -      } else if (value && _.isArray(value)) {
      - -
    • - - -
    • -
      - -
      - -
      -

      [ lon, lat ]

      - -
      - -
              return {
      -          "type": "Point",
      -          "coordinates": [value[0], value[1]]
      -        };
      -      } else if (value && value.lat) {
      - -
    • - - -
    • -
      - -
      - -
      -

      of form { lat: …, lon: …}

      - -
      - -
              return {
      -          "type": "Point",
      -          "coordinates": [value.lon || value.lng, value.lat]
      -        };
      -      }
      - -
    • - - -
    • -
      - -
      - -
      -

      We o/w assume that contents of the field are a valid GeoJSON object

      - -
      - -
            return value;
      -    } else if (this.state.get('lonField') && this.state.get('latField')){
      - -
    • - - -
    • -
      - -
      - -
      -

      We’ll create a GeoJSON like point object from the two lat/lon fields

      - -
      - -
            var lon = doc.get(this.state.get('lonField'));
      -      var lat = doc.get(this.state.get('latField'));
      -      lon = this._parseCoordinateString(lon);
      -      lat = this._parseCoordinateString(lat);
      -
      -      if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) {
      -        return {
      -          type: 'Point',
      -          coordinates: [lon,lat]
      -        };
      -      }
      -    }
      -    return null;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Check if there is a field with GeoJSON geometries or alternatively, -two fields with lat/lon values.

      -

      If not found, the user can define them via the UI form.

      - -
      - -
        _setupGeometryField: function(){
      - -
    • - - -
    • -
      - -
      - -
      -

      should not overwrite if we have already set this (e.g. explicitly via state)

      - -
      - -
          if (!this._geomReady()) {
      -      this.state.set({
      -        geomField: this._checkField(this.geometryFieldNames),
      -        latField: this._checkField(this.latitudeFieldNames),
      -        lonField: this._checkField(this.longitudeFieldNames)
      -      });
      -      this.menu.state.set(this.state.toJSON());
      -    }
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Check if a field in the current model exists in the provided -list of names.

      - -
      - -
        _checkField: function(fieldNames){
      -    var field;
      -    var modelFieldNames = this.model.fields.pluck('id');
      -    for (var i = 0; i < fieldNames.length; i++){
      -      for (var j = 0; j < modelFieldNames.length; j++){
      -        if (modelFieldNames[j].toLowerCase() == fieldNames[i].toLowerCase())
      -          return modelFieldNames[j];
      -      }
      -    }
      -    return null;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Zoom to map to current features extent if any, or to the full -extent if none.

      - -
      - -
        _zoomToFeatures: function(){
      -    var bounds = this.features.getBounds();
      -    if (bounds && bounds.getNorthEast() && bounds.getSouthWest()){
      -      this.map.fitBounds(bounds);
      -    } else {
      -      this.map.setView([0, 0], 2);
      -    }
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Sets up the Leaflet map control and the features layer.

      -

      The map uses a base layer from OpenStreetMap based -on OpenStreetMap data.

      - -
      - -
        _setupMap: function(){
      -    var self = this;
      -    this.map = new L.Map(this.$map.get(0));
      -
      -    var mapUrl = "http://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png";
      -    var osmAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">';
      -    var bg = new L.TileLayer(mapUrl, {maxZoom: 18, attribution: osmAttribution ,subdomains: '1234'});
      -    this.map.addLayer(bg);
      -
      -    this.markers = new L.MarkerClusterGroup(this._clusterOptions);
      - -
    • - - -
    • -
      - -
      - -
      -

      rebind this (as needed in e.g. default case above)

      - -
      - -
          this.geoJsonLayerOptions.pointToLayer =  _.bind(
      -        this.geoJsonLayerOptions.pointToLayer,
      -        this);
      -    this.features = new L.GeoJSON(null, this.geoJsonLayerOptions);
      -
      -    this.map.setView([0, 0], 2);
      -
      -    this.mapReady = true;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Helper function to select an option from a select list

      - -
      - -
        _selectOption: function(id,value){
      -    var options = $('.' + id + ' > select > option');
      -    if (options){
      -      options.each(function(opt){
      -        if (this.value == value) {
      -          $(this).attr('selected','selected');
      -          return false;
      -        }
      -      });
      -    }
      -  }
      -});
      -
      -my.MapMenu = Backbone.View.extend({
      -  className: 'editor',
      -
      -  template: ' \
      -    <form class="form-stacked"> \
      -      <div class="clearfix"> \
      -        <div class="editor-field-type"> \
      -            <label class="radio"> \
      -              <input type="radio" id="editor-field-type-latlon" name="editor-field-type" value="latlon" checked="checked"/> \
      -              Latitude / Longitude fields</label> \
      -            <label class="radio"> \
      -              <input type="radio" id="editor-field-type-geom" name="editor-field-type" value="geom" /> \
      -              GeoJSON field</label> \
      -        </div> \
      -        <div class="editor-field-type-latlon"> \
      -          <label>Latitude field</label> \
      -          <div class="input editor-lat-field"> \
      -            <select class="form-control"> \
      -            <option value=""></option> \
      -            {{#fields}} \
      -            <option value="{{id}}">{{label}}</option> \
      -            {{/fields}} \
      -            </select> \
      -          </div> \
      -          <label>Longitude field</label> \
      -          <div class="input editor-lon-field"> \
      -            <select class="form-control"> \
      -            <option value=""></option> \
      -            {{#fields}} \
      -            <option value="{{id}}">{{label}}</option> \
      -            {{/fields}} \
      -            </select> \
      -          </div> \
      -        </div> \
      -        <div class="editor-field-type-geom" style="display:none"> \
      -          <label>Geometry field (GeoJSON)</label> \
      -          <div class="input editor-geom-field"> \
      -            <select class="form-control"> \
      -            <option value=""></option> \
      -            {{#fields}} \
      -            <option value="{{id}}">{{label}}</option> \
      -            {{/fields}} \
      -            </select> \
      -          </div> \
      -        </div> \
      -      </div> \
      -      <div class="editor-buttons"> \
      -        <button class="btn btn-default editor-update-map">Update</button> \
      -      </div> \
      -      <div class="editor-options" > \
      -        <label class="checkbox"> \
      -          <input type="checkbox" id="editor-auto-zoom" value="autozoom" checked="checked" /> \
      -          Auto zoom to features</label> \
      -        <label class="checkbox"> \
      -          <input type="checkbox" id="editor-cluster" value="cluster"/> \
      -          Cluster markers</label> \
      -      </div> \
      -      <input type="hidden" class="editor-id" value="map-1" /> \
      -    </form> \
      -  ',
      - -
    • - - -
    • -
      - -
      - -
      -

      Define here events for UI elements

      - -
      - -
        events: {
      -    'click .editor-update-map': 'onEditorSubmit',
      -    'change .editor-field-type': 'onFieldTypeChange',
      -    'click #editor-auto-zoom': 'onAutoZoomChange',
      -    'click #editor-cluster': 'onClusteringChange'
      -  },
      -
      -  initialize: function(options) {
      -    var self = this;
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model.fields, 'change', this.render);
      -    this.state = new recline.Model.ObjectState(options.state);
      -    this.listenTo(this.state, 'change', this.render);
      -    this.render();
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Adds the necessary elements to the page.

      -

      Also sets up the editor fields and the map if necessary.

      - -
      - -
        render: function() {
      -    var self = this;
      -    var htmls = Mustache.render(this.template, this.model.toTemplateJSON());
      -    this.$el.html(htmls);
      -
      -    if (this._geomReady() && this.model.fields.length){
      -      if (this.state.get('geomField')){
      -        this._selectOption('editor-geom-field',this.state.get('geomField'));
      -        this.$el.find('#editor-field-type-geom').attr('checked','checked').change();
      -      } else{
      -        this._selectOption('editor-lon-field',this.state.get('lonField'));
      -        this._selectOption('editor-lat-field',this.state.get('latField'));
      -        this.$el.find('#editor-field-type-latlon').attr('checked','checked').change();
      -      }
      -    }
      -    if (this.state.get('autoZoom')) {
      -      this.$el.find('#editor-auto-zoom').attr('checked', 'checked');
      -    } else {
      -      this.$el.find('#editor-auto-zoom').removeAttr('checked');
      -    }
      -    if (this.state.get('cluster')) {
      -      this.$el.find('#editor-cluster').attr('checked', 'checked');
      -    } else {
      -      this.$el.find('#editor-cluster').removeAttr('checked');
      -    }
      -    return this;
      -  },
      -
      -  _geomReady: function() {
      -    return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      UI Event handlers

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Update map with user options

      -

      Right now the only configurable option is what field(s) contains the -location information.

      - -
      - -
        onEditorSubmit: function(e){
      -    e.preventDefault();
      -    if (this.$el.find('#editor-field-type-geom').attr('checked')){
      -      this.state.set({
      -        geomField: this.$el.find('.editor-geom-field > select > option:selected').val(),
      -        lonField: null,
      -        latField: null
      -      });
      -    } else {
      -      this.state.set({
      -        geomField: null,
      -        lonField: this.$el.find('.editor-lon-field > select > option:selected').val(),
      -        latField: this.$el.find('.editor-lat-field > select > option:selected').val()
      -      });
      -    }
      -    return false;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Public: Shows the relevant select lists depending on the location field -type selected.

      - -
      - -
        onFieldTypeChange: function(e){
      -    if (e.target.value == 'geom'){
      -        this.$el.find('.editor-field-type-geom').show();
      -        this.$el.find('.editor-field-type-latlon').hide();
      -    } else {
      -        this.$el.find('.editor-field-type-geom').hide();
      -        this.$el.find('.editor-field-type-latlon').show();
      -    }
      -  },
      -
      -  onAutoZoomChange: function(e){
      -    this.state.set({autoZoom: !this.state.get('autoZoom')});
      -  },
      -
      -  onClusteringChange: function(e){
      -    this.state.set({cluster: !this.state.get('cluster')});
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Private: Helper function to select an option from a select list

      - -
      - -
        _selectOption: function(id,value){
      -    var options = this.$el.find('.' + id + ' > select > option');
      -    if (options){
      -      options.each(function(opt){
      -        if (this.value == value) {
      -          $(this).attr('selected','selected');
      -          return false;
      -        }
      -      });
      -    }
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/view.multiview.html b/docs/src/view.multiview.html deleted file mode 100644 index 070be0b1..00000000 --- a/docs/src/view.multiview.html +++ /dev/null @@ -1,1048 +0,0 @@ - - - - - view.multiview.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      view.multiview.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      - -
    • - - -
    • -
      - -
      - -
      -

      Standard JS module setup

      - -
      - -
      this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      MultiView

      -

      Manage multiple views together along with query editor etc. Usage:

      -
      -var myExplorer = new recline.View.MultiView({
      -  model: {{recline.Model.Dataset instance}}
      -  el: {{an existing dom element}}
      -  views: {{dataset views}}
      -  state: {{state configuration -- see below}}
      -});
      -
      - -

      Parameters

      -

      model: (required) recline.model.Dataset instance.

      -

      el: (required) DOM element to bind to. NB: the element already -being in the DOM is important for rendering of some subviews (e.g. -Graph).

      -

      views: (optional) the dataset views (Grid, Graph etc) for -MultiView to show. This is an array of view hashes. If not provided -initialize with (recline.View.)Grid, Graph, and Map views (with obvious id -and labels!).

      -
      -var views = [
      -  {
      -    id: 'grid', // used for routing
      -    label: 'Grid', // used for view switcher
      -    view: new recline.View.Grid({
      -      model: dataset
      -    })
      -  },
      -  {
      -    id: 'graph',
      -    label: 'Graph',
      -    view: new recline.View.Graph({
      -      model: dataset
      -    })
      -  }
      -];
      -
      - -

      sidebarViews: (optional) the sidebar views (Filters, Fields) for -MultiView to show. This is an array of view hashes. If not provided -initialize with (recline.View.)FilterEditor and Fields views (with obvious -id and labels!).

      -
      -var sidebarViews = [
      -  {
      -    id: 'filterEditor', // used for routing
      -    label: 'Filters', // used for view switcher
      -    view: new recline.View.FilterEditor({
      -      model: dataset
      -    })
      -  },
      -  {
      -    id: 'fieldsView',
      -    label: 'Fields',
      -    view: new recline.View.Fields({
      -      model: dataset
      -    })
      -  }
      -];
      -
      - -

      state: standard state config for this view. This state is slightly - special as it includes config of many of the subviews.

      -
      -var state = {
      -    query: {dataset query state - see dataset.queryState object}
      -    'view-{id1}': {view-state for this view}
      -    'view-{id2}': {view-state for }
      -    ...
      -    // Explorer
      -    currentView: id of current view (defaults to first view if not specified)
      -    readOnly: (default: false) run in read-only mode
      -}
      -
      - -

      Note that at present we do not serialize information about the actual set -of views in use — e.g. those specified by the views argument — but instead -expect either that the default views are fine or that the client to have -initialized the MultiView with the relevant views themselves.

      - -
      - -
      my.MultiView = Backbone.View.extend({
      -  template: ' \
      -  <div class="recline-data-explorer"> \
      -    <div class="alert-messages"></div> \
      -    \
      -    <div class="header clearfix"> \
      -      <div class="navigation"> \
      -        <div class="btn-group" data-toggle="buttons-radio"> \
      -        {{#views}} \
      -        <button href="#{{id}}" data-view="{{id}}" class="btn btn-default">{{label}}</button> \
      -        {{/views}} \
      -        </div> \
      -      </div> \
      -      <div class="recline-results-info"> \
      -        <span class="doc-count">{{recordCount}}</span> records\
      -      </div> \
      -      <div class="menu-right"> \
      -        <div class="btn-group" data-toggle="buttons-checkbox"> \
      -          {{#sidebarViews}} \
      -          <button href="#" data-action="{{id}}" class="btn btn-default">{{label}}</button> \
      -          {{/sidebarViews}} \
      -        </div> \
      -      </div> \
      -      <div class="query-editor-here" style="display:inline;"></div> \
      -    </div> \
      -    <div class="data-view-sidebar"></div> \
      -    <div class="data-view-container"></div> \
      -  </div> \
      -  ',
      -  events: {
      -    'click .menu-right button': '_onMenuClick',
      -    'click .navigation button': '_onSwitchView'
      -  },
      -
      -  initialize: function(options) {
      -    var self = this;
      -    this._setupState(options.state);
      - -
    • - - -
    • -
      - -
      - -
      -

      Hash of ā€˜page’ views (i.e. those for whole page) keyed by page name

      - -
      - -
          if (options.views) {
      -      this.pageViews = options.views;
      -    } else {
      -      this.pageViews = [{
      -        id: 'grid',
      -        label: 'Grid',
      -        view: new my.SlickGrid({
      -          model: this.model,
      -          state: this.state.get('view-grid')
      -        })
      -      }, {
      -        id: 'graph',
      -        label: 'Graph',
      -        view: new my.Graph({
      -          model: this.model,
      -          state: this.state.get('view-graph')
      -        })
      -      }, {
      -        id: 'map',
      -        label: 'Map',
      -        view: new my.Map({
      -          model: this.model,
      -          state: this.state.get('view-map')
      -        })
      -      }, {
      -        id: 'timeline',
      -        label: 'Timeline',
      -        view: new my.Timeline({
      -          model: this.model,
      -          state: this.state.get('view-timeline')
      -        })
      -      }];
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      Hashes of sidebar elements

      - -
      - -
          if(options.sidebarViews) {
      -      this.sidebarViews = options.sidebarViews;
      -    } else {
      -      this.sidebarViews = [{
      -        id: 'filterEditor',
      -        label: 'Filters',
      -        view: new my.FilterEditor({
      -          model: this.model
      -        })
      -      }, {
      -        id: 'fieldsView',
      -        label: 'Fields',
      -        view: new my.Fields({
      -          model: this.model
      -        })
      -      }];
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      these must be called after pageViews are created

      - -
      - -
          this.render();
      -    this._bindStateChanges();
      -    this._bindFlashNotifications();
      - -
    • - - -
    • -
      - -
      - -
      -

      now do updates based on state (need to come after render)

      - -
      - -
          if (this.state.get('readOnly')) {
      -      this.setReadOnly();
      -    }
      -    if (this.state.get('currentView')) {
      -      this.updateNav(this.state.get('currentView'));
      -    } else {
      -      this.updateNav(this.pageViews[0].id);
      -    }
      -    this._showHideSidebar();
      -
      -    this.listenTo(this.model, 'query:start', function() {
      -      self.notify({loader: true, persist: true});
      -    });
      -    this.listenTo(this.model, 'query:done', function() {
      -      self.clearNotifications();
      -      self.$el.find('.doc-count').text(self.model.recordCount || 'Unknown');
      -    });
      -    this.listenTo(this.model, 'query:fail', function(error) {
      -      self.clearNotifications();
      -      var msg = '';
      -      if (typeof(error) == 'string') {
      -        msg = error;
      -      } else if (typeof(error) == 'object') {
      -        if (error.title) {
      -          msg = error.title + ': ';
      -        }
      -        if (error.message) {
      -          msg += error.message;
      -        }
      -      } else {
      -        msg = 'There was an error querying the backend';
      -      }
      -      self.notify({message: msg, category: 'error', persist: true});
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      retrieve basic data like fields etc -note this.model and dataset returned are the same -TODO: set query state …?

      - -
      - -
          this.model.queryState.set(self.state.get('query'), {silent: true});
      -  },
      -
      -  setReadOnly: function() {
      -    this.$el.addClass('recline-read-only');
      -  },
      -
      -  render: function() {
      -    var tmplData = this.model.toTemplateJSON();
      -    tmplData.views = this.pageViews;
      -    tmplData.sidebarViews = this.sidebarViews;
      -    var template = Mustache.render(this.template, tmplData);
      -    this.$el.html(template);
      - -
    • - - -
    • -
      - -
      - -
      -

      now create and append other views

      - -
      - -
          var $dataViewContainer = this.$el.find('.data-view-container');
      -    var $dataSidebar = this.$el.find('.data-view-sidebar');
      - -
    • - - -
    • -
      - -
      - -
      -

      the main views

      - -
      - -
          _.each(this.pageViews, function(view, pageName) {
      -      view.view.render();
      -      if (view.view.redraw) {
      -        view.view.redraw();
      -      }
      -      $dataViewContainer.append(view.view.el);
      -      if (view.view.elSidebar) {
      -        $dataSidebar.append(view.view.elSidebar);
      -      }
      -    });
      -
      -    _.each(this.sidebarViews, function(view) {
      -      this['$'+view.id] = view.view.$el;
      -      $dataSidebar.append(view.view.el);
      -    }, this);
      -
      -    this.pager = new recline.View.Pager({
      -      model: this.model
      -    });
      -    this.$el.find('.recline-results-info').after(this.pager.el);
      -
      -    this.queryEditor = new recline.View.QueryEditor({
      -      model: this.model.queryState
      -    });
      -    this.$el.find('.query-editor-here').append(this.queryEditor.el);
      -
      -  },
      -
      -  remove: function () {
      -    _.each(this.pageViews, function (view) {
      -      view.view.remove();
      -    });
      -    _.each(this.sidebarViews, function (view) {
      -      view.view.remove();
      -    });
      -    this.pager.remove();
      -    this.queryEditor.remove();
      -    Backbone.View.prototype.remove.apply(this, arguments);
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      hide the sidebar if empty

      - -
      - -
        _showHideSidebar: function() {
      -    var $dataSidebar = this.$el.find('.data-view-sidebar');
      -    var visibleChildren = $dataSidebar.children().filter(function() {
      -      return $(this).css("display") != "none";
      -    }).length;
      -
      -    if (visibleChildren > 0) {
      -      $dataSidebar.show();
      -    } else {
      -      $dataSidebar.hide();
      -    }
      -  },
      -
      -  updateNav: function(pageName) {
      -    this.$el.find('.navigation button').removeClass('active');
      -    var $el = this.$el.find('.navigation button[data-view="' + pageName + '"]');
      -    $el.addClass('active');
      - -
    • - - -
    • -
      - -
      - -
      -

      add/remove sidebars and hide inactive views

      - -
      - -
          _.each(this.pageViews, function(view, idx) {
      -      if (view.id === pageName) {
      -        view.view.$el.show();
      -        if (view.view.elSidebar) {
      -          view.view.elSidebar.show();
      -        }
      -      } else {
      -        view.view.$el.hide();
      -        if (view.view.elSidebar) {
      -          view.view.elSidebar.hide();
      -        }
      -        if (view.view.hide) {
      -          view.view.hide();
      -        }
      -      }
      -    });
      -
      -    this._showHideSidebar();
      - -
    • - - -
    • -
      - -
      - -
      -

      call view.view.show after sidebar visibility has been determined so -that views can correctly calculate their maximum width

      - -
      - -
          _.each(this.pageViews, function(view, idx) {
      -      if (view.id === pageName) {
      -        if (view.view.show) {
      -          view.view.show();
      -        }
      -      }
      -    });
      -  },
      -
      -  _onMenuClick: function(e) {
      -    e.preventDefault();
      -    var action = $(e.target).attr('data-action');
      -    this['$'+action].toggle();
      -    this._showHideSidebar();
      -  },
      -
      -  _onSwitchView: function(e) {
      -    e.preventDefault();
      -    var viewName = $(e.target).attr('data-view');
      -    this.updateNav(viewName);
      -    this.state.set({currentView: viewName});
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      create a state object for this view and do the job of

      -

      a) initializing it from both data passed in and other sources (e.g. hash url)

      -

      b) ensure the state object is updated in responese to changes in subviews, query etc.

      - -
      - -
        _setupState: function(initialState) {
      -    var self = this;
      - -
    • - - -
    • -
      - -
      - -
      -

      get data from the query string / hash url plus some defaults

      - -
      - -
          var qs = my.parseHashQueryString();
      -    var query = qs.reclineQuery;
      -    query = query ? JSON.parse(query) : self.model.queryState.toJSON();
      - -
    • - - -
    • -
      - -
      - -
      -

      backwards compatability (now named view-graph but was named graph)

      - -
      - -
          var graphState = qs['view-graph'] || qs.graph;
      -    graphState = graphState ? JSON.parse(graphState) : {};
      - -
    • - - -
    • -
      - -
      - -
      -

      now get default data + hash url plus initial state and initial our state object with it

      - -
      - -
          var stateData = _.extend({
      -        query: query,
      -        'view-graph': graphState,
      -        backend: this.model.backend.__type__,
      -        url: this.model.get('url'),
      -        dataset: this.model.toJSON(),
      -        currentView: null,
      -        readOnly: false
      -      },
      -      initialState);
      -    this.state = new recline.Model.ObjectState(stateData);
      -  },
      -
      -  _bindStateChanges: function() {
      -    var self = this;
      - -
    • - - -
    • -
      - -
      - -
      -

      finally ensure we update our state object when state of sub-object changes so that state is always up to date

      - -
      - -
          this.listenTo(this.model.queryState, 'change', function() {
      -      self.state.set({query: self.model.queryState.toJSON()});
      -    });
      -    _.each(this.pageViews, function(pageView) {
      -      if (pageView.view.state && pageView.view.state.bind) {
      -        var update = {};
      -        update['view-' + pageView.id] = pageView.view.state.toJSON();
      -        self.state.set(update);
      -        self.listenTo(pageView.view.state, 'change', function() {
      -          var update = {};
      -          update['view-' + pageView.id] = pageView.view.state.toJSON();
      - -
    • - - -
    • -
      - -
      - -
      -

      had problems where change not being triggered for e.g. grid view so let’s do it explicitly

      - -
      - -
                self.state.set(update, {silent: true});
      -          self.state.trigger('change');
      -        });
      -      }
      -    });
      -  },
      -
      -  _bindFlashNotifications: function() {
      -    var self = this;
      -    _.each(this.pageViews, function(pageView) {
      -      self.listenTo(pageView.view, 'recline:flash', function(flash) {
      -        self.notify(flash);
      -      });
      -    });
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      notify

      -

      Create a notification (a div.alert in div.alert-messsages) using provided -flash object. Flash attributes (all are optional):

      -
        -
      • message: message to show.
      • -
      • category: warning (default), success, error
      • -
      • persist: if true alert is persistent, o/w hidden after 3s (default = false)
      • -
      • loader: if true show loading spinner
      • -
      - -
      - -
        notify: function(flash) {
      -    var tmplData = _.extend({
      -      message: 'Loading',
      -      category: 'warning',
      -      loader: false
      -      },
      -      flash
      -    );
      -    var _template;
      -    if (tmplData.loader) {
      -      _template = ' \
      -        <div class="alert alert-info alert-loader"> \
      -          {{message}} \
      -          <span class="notification-loader">&nbsp;</span> \
      -        </div>';
      -    } else {
      -      _template = ' \
      -        <div class="alert alert-{{category}} fade in" data-alert="alert"><a class="close" data-dismiss="alert" href="#">Ɨ</a> \
      -          {{message}} \
      -        </div>';
      -    }
      -    var _templated = $(Mustache.render(_template, tmplData));
      -    _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages'));
      -    if (!flash.persist) {
      -      setTimeout(function() {
      -        $(_templated).fadeOut(1000, function() {
      -          $(this).remove();
      -        });
      -      }, 1000);
      -    }
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      clearNotifications

      -

      Clear all existing notifications

      - -
      - -
        clearNotifications: function() {
      -    var $notifications = $('.recline-data-explorer .alert-messages .alert');
      -    $notifications.fadeOut(1500, function() {
      -      $(this).remove();
      -    });
      -  }
      -});
      - -
    • - - -
    • -
      - -
      - -
      -

      MultiView.restore

      -

      Restore a MultiView instance from a serialized state including the associated dataset

      -

      This inverts the state serialization process in Multiview

      - -
      - -
      my.MultiView.restore = function(state) {
      - -
    • - - -
    • -
      - -
      - -
      -

      hack-y - restoring a memory dataset does not mean much … (but useful for testing!)

      - -
      - -
        var datasetInfo;
      -  if (state.backend === 'memory') {
      -    datasetInfo = {
      -      backend: 'memory',
      -      records: [{stub: 'this is a stub dataset because we do not restore memory datasets'}]
      -    };
      -  } else {
      -    datasetInfo = _.extend({
      -        url: state.url,
      -        backend: state.backend
      -      },
      -      state.dataset
      -    );
      -  }
      -  var dataset = new recline.Model.Dataset(datasetInfo);
      -  var explorer = new my.MultiView({
      -    model: dataset,
      -    state: state
      -  });
      -  return explorer;
      -};
      - -
    • - - -
    • -
      - -
      - -
      -

      Miscellaneous Utilities

      - -
      - -
      var urlPathRegex = /^([^?]+)(\?.*)?/;
      - -
    • - - -
    • -
      - -
      - -
      -

      Parse the Hash section of a URL into path and query string

      - -
      - -
      my.parseHashUrl = function(hashUrl) {
      -  var parsed = urlPathRegex.exec(hashUrl);
      -  if (parsed === null) {
      -    return {};
      -  } else {
      -    return {
      -      path: parsed[1],
      -      query: parsed[2] || ''
      -    };
      -  }
      -};
      - -
    • - - -
    • -
      - -
      - -
      -

      Parse a URL query string (?xyz=abc…) into a dictionary.

      - -
      - -
      my.parseQueryString = function(q) {
      -  if (!q) {
      -    return {};
      -  }
      -  var urlParams = {},
      -    e, d = function (s) {
      -      return unescape(s.replace(/\+/g, " "));
      -    },
      -    r = /([^&=]+)=?([^&]*)/g;
      -
      -  if (q && q.length && q[0] === '?') {
      -    q = q.slice(1);
      -  }
      -  while (e = r.exec(q)) {
      - -
    • - - -
    • -
      - -
      - -
      -

      TODO: have values be array as query string allow repetition of keys

      - -
      - -
          urlParams[d(e[1])] = d(e[2]);
      -  }
      -  return urlParams;
      -};
      - -
    • - - -
    • -
      - -
      - -
      -

      Parse the query string out of the URL hash

      - -
      - -
      my.parseHashQueryString = function() {
      -  var q = my.parseHashUrl(window.location.hash).query;
      -  return my.parseQueryString(q);
      -};
      - -
    • - - -
    • -
      - -
      - -
      -

      Compse a Query String

      - -
      - -
      my.composeQueryString = function(queryParams) {
      -  var queryString = '?';
      -  var items = [];
      -  $.each(queryParams, function(key, value) {
      -    if (typeof(value) === 'object') {
      -      value = JSON.stringify(value);
      -    }
      -    items.push(key + '=' + encodeURIComponent(value));
      -  });
      -  queryString += items.join('&');
      -  return queryString;
      -};
      -
      -my.getNewHashForQueryString = function(queryParams) {
      -  var queryPart = my.composeQueryString(queryParams);
      -  if (window.location.hash) {
      - -
    • - - -
    • -
      - -
      - -
      -

      slice(1) to remove # at start

      - -
      - -
          return window.location.hash.split('?')[0].slice(1) + queryPart;
      -  } else {
      -    return queryPart;
      -  }
      -};
      -
      -my.setHashQueryString = function(queryParams) {
      -  window.location.hash = my.getNewHashForQueryString(queryParams);
      -};
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/view.slickgrid.html b/docs/src/view.slickgrid.html deleted file mode 100644 index 80bb6006..00000000 --- a/docs/src/view.slickgrid.html +++ /dev/null @@ -1,1114 +0,0 @@ - - - - - view.slickgrid.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      view.slickgrid.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      SlickGrid Dataset View

      -

      Provides a tabular view on a Dataset, based on SlickGrid.

      -

      https://github.com/mleibman/SlickGrid

      -

      Initialize it with a recline.Model.Dataset.

      -

      Additional options to drive SlickGrid grid can be given through state. -The following keys allow for customization:

      -
        -
      • gridOptions: to add options at grid level
      • -
      • columnsEditor: to add editor for editable columns
      • -
      -

      For example: - var grid = new recline.View.SlickGrid({ - model: dataset, - el: $el, - state: { - gridOptions: { - editable: true, - enableAddRow: true - // Enable support for row delete - enabledDelRow: true, - // Enable support for row Reorder - enableReOrderRow:true, - … - }, - columnsEditor: [ - {column: ā€˜date’, editor: Slick.Editors.Date }, - {column: ā€˜title’, editor: Slick.Editors.Text} - ] - } - }); -// NB: you need an explicit height on the element for slickgrid to work

      - -
      - -
      my.SlickGrid = Backbone.View.extend({
      -  initialize: function(modelEtc) {
      -    var self = this;
      -    this.$el.addClass('recline-slickgrid');
      - -
    • - - -
    • -
      - -
      - -
      -

      Template for row delete menu , change it if you don’t love

      - -
      - -
          this.templates = {
      -      "deleterow" : '<button href="#" class="recline-row-delete btn btn-default" title="Delete row">X</button>'
      -    };
      -
      -    _.bindAll(this, 'render', 'onRecordChanged');
      -    this.listenTo(this.model.records, 'add remove reset', this.render);
      -    this.listenTo(this.model.records, 'change', this.onRecordChanged);
      -    var state = _.extend({
      -        hiddenColumns: [],
      -        columnsOrder: [],
      -        columnsSort: {},
      -        columnsWidth: [],
      -        columnsEditor: [],
      -        options: {},
      -        fitColumns: false
      -      }, modelEtc.state
      -
      -    );
      -    this.state = new recline.Model.ObjectState(state);
      -    this._slickHandler = new Slick.EventHandler();
      - -
    • - - -
    • -
      - -
      - -
      -

      add menu for new row , check if enableAddRow is set to true or not set

      - -
      - -
          if(this.state.get("gridOptions") 
      -  && this.state.get("gridOptions").enabledAddRow != undefined 
      -      && this.state.get("gridOptions").enabledAddRow == true ){
      -      this.editor    =  new  my.GridControl()
      -      this.elSidebar =  this.editor.$el
      -  this.listenTo(this.editor.state, 'change', function(){   
      -    this.model.records.add(new recline.Model.Record())
      -      });
      -    }
      -  },
      -
      -  onRecordChanged: function(record) {
      - -
    • - - -
    • -
      - -
      - -
      -

      Ignore if the grid is not yet drawn

      - -
      - -
          if (!this.grid) {
      -      return;
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      Let’s find the row corresponding to the index

      - -
      - -
          var row_index = this.grid.getData().getModelRow( record );
      -    this.grid.invalidateRow(row_index);
      -    this.grid.getData().updateItem(record, row_index);
      -    this.grid.render();
      -  },
      -
      -  render: function() {
      -    var self = this;
      -    var options = _.extend({
      -      enableCellNavigation: true,
      -      enableColumnReorder: true,
      -      explicitInitialization: true,
      -      syncColumnCellResize: true,
      -      forceFitColumns: this.state.get('fitColumns')
      -    }, self.state.get('gridOptions'));
      - -
    • - - -
    • -
      - -
      - -
      -

      We need all columns, even the hidden ones, to show on the column picker

      - -
      - -
          var columns = [];
      - -
    • - - -
    • -
      - -
      - -
      -

      custom formatter as default one escapes html -plus this way we distinguish between rendering/formatting and computed value (so e.g. sort still works …) -row = row index, cell = cell index, value = value, columnDef = column definition, dataContext = full row values

      - -
      - -
          var formatter = function(row, cell, value, columnDef, dataContext) {
      -      if(columnDef.id == "del"){
      -        return self.templates.deleterow 
      -      }
      -      var field = self.model.fields.get(columnDef.id);
      -      if (field.renderer) {
      -        return  field.renderer(value, field, dataContext);
      -      } else {
      -        return  value 
      -      }
      -    };
      - -
    • - - -
    • -
      - -
      - -
      -

      we need to be sure that user is entering a valid input , for exemple if -field is date type and field.format =’YY-MM-DD’, we should be sure that -user enter a correct value

      - -
      - -
          var validator = function(field) {
      -      return function(value){
      -        if (field.type == "date" && isNaN(Date.parse(value))){
      -          return {
      -            valid: false,
      -            msg: "A date is required, check field field-date-format"
      -          };
      -        } else {
      -          return {valid: true, msg :null } 
      -        }
      -      }
      -    };
      - -
    • - - -
    • -
      - -
      - -
      -

      Add column for row reorder support

      - -
      - -
          if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow == true) {
      -      columns.push({
      -        id: "#",
      -        name: "",
      -        width: 22,
      -        behavior: "selectAndMove",
      -        selectable: false,
      -        resizable: false,
      -        cssClass: "recline-cell-reorder"
      -      })
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      Add column for row delete support

      - -
      - -
          if (this.state.get("gridOptions") && this.state.get("gridOptions").enabledDelRow == true) {
      -      columns.push({
      -        id: 'del',
      -        name: '',
      -        field: 'del',
      -        sortable: true,
      -        width: 38,
      -        formatter: formatter,
      -        validator:validator
      -      })
      -    }
      -
      -    function sanitizeFieldName(name) {
      -      var sanitized = $(name).text();
      -      return (name !== sanitized && sanitized !== '') ? sanitized : name;
      -    }
      -
      -    _.each(this.model.fields.toJSON(),function(field){
      -      var column = {
      -        id: field.id,
      -        name: sanitizeFieldName(field.label),
      -        field: field.id,
      -        sortable: true,
      -        minWidth: 80,
      -        formatter: formatter,
      -        validator:validator(field)
      -      };
      -      var widthInfo = _.find(self.state.get('columnsWidth'),function(c){return c.column === field.id;});
      -      if (widthInfo){
      -        column.width = widthInfo.width;
      -      }
      -      var editInfo = _.find(self.state.get('columnsEditor'),function(c){return c.column === field.id;});
      -      if (editInfo){
      -        column.editor = editInfo.editor;
      -      } else {
      - -
    • - - -
    • -
      - -
      - -
      -

      guess editor type

      - -
      - -
              var typeToEditorMap = {
      -          'string': Slick.Editors.LongText,
      -          'integer': Slick.Editors.IntegerEditor,
      -          'number': Slick.Editors.Text,
      - -
    • - - -
    • -
      - -
      - -
      -

      TODO: need a way to ensure we format date in the right way -Plus what if dates are in distant past or future … (?) -ā€˜date’: Slick.Editors.DateEditor,

      - -
      - -
                'date': Slick.Editors.Text,
      -          'boolean': Slick.Editors.YesNoSelectEditor
      - -
    • - - -
    • -
      - -
      - -
      -

      TODO: (?) percent …

      - -
      - -
              };
      -        if (field.type in typeToEditorMap) {
      -          column.editor = typeToEditorMap[field.type]
      -        } else {
      -          column.editor = Slick.Editors.LongText;
      -        }
      -      }
      -      columns.push(column);
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      Restrict the visible columns

      - -
      - -
          var visibleColumns = _.filter(columns, function(column) {
      -      return _.indexOf(self.state.get('hiddenColumns'), column.id) === -1;
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      Order them if there is ordering info on the state

      - -
      - -
          if (this.state.get('columnsOrder') && this.state.get('columnsOrder').length > 0) {
      -      visibleColumns = visibleColumns.sort(function(a,b){
      -        return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1;
      -      });
      -      columns = columns.sort(function(a,b){
      -        return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1;
      -      });
      -    }
      - -
    • - - -
    • -
      - -
      - -
      -

      Move hidden columns to the end, so they appear at the bottom of the -column picker

      - -
      - -
          var tempHiddenColumns = [];
      -    for (var i = columns.length -1; i >= 0; i--){
      -      if (_.indexOf(_.pluck(visibleColumns,'id'),columns[i].id) === -1){
      -        tempHiddenColumns.push(columns.splice(i,1)[0]);
      -      }
      -    }
      -    columns = columns.concat(tempHiddenColumns);
      - -
    • - - -
    • -
      - -
      - -
      -

      Transform a model object into a row

      - -
      - -
          function toRow(m) {
      -      var row = {};
      -      self.model.fields.each(function(field) {
      -        var render = "";
      - -
    • - - -
    • -
      - -
      - -
      -

      when adding row from slickgrid the field value is undefined

      - -
      - -
              if(!_.isUndefined(m.getFieldValueUnrendered(field))){
      -           render =m.getFieldValueUnrendered(field)
      -        }
      -        row[field.id] = render
      -      });
      -      return row;
      -    }
      -
      -    function RowSet() {
      -      var models = [];
      -      var rows = [];
      -
      -      this.push = function(model, row) {
      -        models.push(model);
      -        rows.push(row);
      -      };
      -
      -      this.getLength = function() {return rows.length; };
      -      this.getItem = function(index) {return rows[index];};
      -      this.getItemMetadata = function(index) {return {};};
      -      this.getModel = function(index) {return models[index];};
      -      this.getModelRow = function(m) {return _.indexOf(models, m);};
      -      this.updateItem = function(m,i) {
      -        rows[i] = toRow(m);
      -        models[i] = m;
      -      };
      -    }
      -
      -    var data = new RowSet();
      -
      -    this.model.records.each(function(doc){
      -      data.push(doc, toRow(doc));
      -    });
      -
      -    this.grid = new Slick.Grid(this.el, data, visibleColumns, options);
      - -
    • - - -
    • -
      - -
      - -
      -

      Column sorting

      - -
      - -
          var sortInfo = this.model.queryState.get('sort');
      -    if (sortInfo){
      -      var column = sortInfo[0].field;
      -      var sortAsc = sortInfo[0].order !== 'desc';
      -      this.grid.setSortColumn(column, sortAsc);
      -    }
      -
      -    if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) {
      -      this._setupRowReordering();
      -    }
      -    
      -    this._slickHandler.subscribe(this.grid.onSort, function(e, args){
      -      var order = (args.sortAsc) ? 'asc':'desc';
      -      var sort = [{
      -        field: args.sortCol.field,
      -        order: order
      -      }];
      -      self.model.query({sort: sort});
      -    });
      -    
      -    this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){
      -      self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')});
      -    });
      -    
      -    this.grid.onColumnsResized.subscribe(function(e, args){
      -        var columns = args.grid.getColumns();
      -        var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth;
      -        var columnsWidth = [];
      -        _.each(columns,function(column){
      -          if (column.width != defaultColumnWidth){
      -            columnsWidth.push({column:column.id,width:column.width});
      -          }
      -        });
      -        self.state.set({columnsWidth:columnsWidth});
      -    });
      -    
      -    this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) {
      - -
    • - - -
    • -
      - -
      - -
      -

      We need to change the model associated value

      - -
      - -
            var grid = args.grid;
      -      var model = data.getModel(args.row);
      -      var field = grid.getColumns()[args.cell].id;
      -      var v = {};
      -      v[field] = args.item[field];
      -      model.set(v);
      -    });  
      -    this._slickHandler.subscribe(this.grid.onClick,function(e, args){
      - -
    • - - -
    • -
      - -
      - -
      -

      try catch , because this fail in qunit , but no -error on browser.

      - -
      - -
            try{e.preventDefault()}catch(e){}
      - -
    • - - -
    • -
      - -
      - -
      -

      The cell of grid that handle row delete is The first cell (0) if -The grid ReOrder is not present ie enableReOrderRow == false -else it is The the second cell (1) , because The 0 is now cell -that handle row Reoder.

      - -
      - -
            var cell =0
      -      if(self.state.get("gridOptions") 
      -  && self.state.get("gridOptions").enableReOrderRow != undefined 
      -        && self.state.get("gridOptions").enableReOrderRow == true ){
      -        cell =1
      -      }
      -      if (args.cell == cell && self.state.get("gridOptions").enabledDelRow == true){
      - -
    • - - -
    • -
      - -
      - -
      -

      We need to delete the associated model

      - -
      - -
                var model = data.getModel(args.row);
      -          model.destroy()
      -        }
      -    }) ;
      -    var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid,
      -                                                       _.extend(options,{state:this.state}));
      -    if (self.visible){
      -      self.grid.init();
      -      self.rendered = true;
      -    } else {
      - -
    • - - -
    • -
      - -
      - -
      -

      Defer rendering until the view is visible

      - -
      - -
            self.rendered = false;
      -    }
      -    return this;
      -  },
      - -
    • - - -
    • - - -
        _setupRowReordering: function() {
      -    var self = this;
      -    self.grid.setSelectionModel(new Slick.RowSelectionModel());
      -
      -    var moveRowsPlugin = new Slick.RowMoveManager({
      -      cancelEditOnDrag: true
      -    });
      -
      -    moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, data) {
      -      for (var i = 0; i < data.rows.length; i++) {
      - -
    • - - -
    • -
      - -
      - -
      -

      no point in moving before or after itself

      - -
      - -
              if (data.rows[i] == data.insertBefore || data.rows[i] == data.insertBefore - 1) {
      -          e.stopPropagation();
      -          return false;
      -        }
      -      }
      -      return true;
      -    });
      -    
      -    moveRowsPlugin.onMoveRows.subscribe(function (e, args) {
      -      var extractedRows = [], left, right;
      -      var rows = args.rows;
      -      var insertBefore = args.insertBefore;
      -
      -      var data = self.model.records.toJSON()      
      -      left = data.slice(0, insertBefore);
      -      right= data.slice(insertBefore, data.length);
      -      
      -      rows.sort(function(a,b) { return a-b; });
      -
      -      for (var i = 0; i < rows.length; i++) {
      -          extractedRows.push(data[rows[i]]);
      -      }
      -
      -      rows.reverse();
      -
      -      for (var i = 0; i < rows.length; i++) {
      -        var row = rows[i];
      -        if (row < insertBefore) {
      -          left.splice(row, 1);
      -        } else {
      -          right.splice(row - insertBefore, 1);
      -        }
      -      }
      -
      -      data = left.concat(extractedRows.concat(right));
      -      var selectedRows = [];
      -      for (var i = 0; i < rows.length; i++)
      -        selectedRows.push(left.length + i);      
      -
      -      self.model.records.reset(data)
      -      
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      register The plugin to handle row Reorder

      - -
      - -
          if(this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) {
      -      self.grid.registerPlugin(moveRowsPlugin);
      -    }
      -  },
      -
      -  remove: function () {
      -    this._slickHandler.unsubscribeAll();
      -    Backbone.View.prototype.remove.apply(this, arguments);
      -  },
      -
      -  show: function() {
      - -
    • - - -
    • -
      - -
      - -
      -

      If the div is hidden, SlickGrid will calculate wrongly some -sizes so we must render it explicitly when the view is visible

      - -
      - -
          if (!this.rendered){
      -      if (!this.grid){
      -        this.render();
      -      }
      -      this.grid.init();
      -      this.rendered = true;
      -    }
      -    this.visible = true;
      -  },
      -
      -  hide: function() {
      -    this.visible = false;
      -  }
      -});
      - -
    • - - -
    • -
      - -
      - -
      -

      Add new grid Control to display a new row add menu bouton -It display a simple side-bar menu ,for user to add new -row to grid

      - -
      - -
      my.GridControl= Backbone.View.extend({
      -  className: "recline-row-add",
      - -
    • - - -
    • -
      - -
      - -
      -

      Template for row edit menu , change it if you don’t love

      - -
      - -
        template: '<h1><button href="#" class="recline-row-add btn btn-default">Add row</button></h1>',
      -  
      -  initialize: function(options){
      -    var self = this;
      -    _.bindAll(this, 'render');
      -    this.state = new recline.Model.ObjectState();
      -    this.render();
      -  },
      -
      -  render: function() {
      -    var self = this;
      -    this.$el.html(this.template)
      -  },
      -
      -  events : {
      -    "click .recline-row-add" : "addNewRow"
      -  },
      -
      -  addNewRow : function(e){
      -    e.preventDefault()
      -    this.state.trigger("change")
      - }
      -});
      -
      -})(jQuery, recline.View);
      -
      -/*
      -* Context menu for the column picker, adapted from
      -* http://mleibman.github.com/SlickGrid/examples/example-grouping
      -*
      -*/
      -(function ($) {
      -  function SlickColumnPicker(columns, grid, options) {
      -    var $menu;
      -    var columnCheckboxes;
      -
      -    var defaults = {
      -      fadeSpeed:250
      -    };
      -
      -    function init() {
      -      grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu);
      -      options = $.extend({}, defaults, options);
      -
      -      $menu = $('<ul class="dropdown-menu slick-contextmenu" style="display:none;position:absolute;z-index:20;" />').appendTo(document.body);
      -
      -      $menu.bind('mouseleave', function (e) {
      -        $(this).fadeOut(options.fadeSpeed);
      -      });
      -      $menu.bind('click', updateColumn);
      -
      -    }
      -
      -    function handleHeaderContextMenu(e, args) {
      -      e.preventDefault();
      -      $menu.empty();
      -      columnCheckboxes = [];
      -
      -      var $li, $input;
      -      for (var i = 0; i < columns.length; i++) {
      -        $li = $('<li />').appendTo($menu);
      -        $input = $('<input type="checkbox" />').data('column-id', columns[i].id).attr('id','slick-column-vis-'+columns[i].id);
      -        columnCheckboxes.push($input);
      -
      -        if (grid.getColumnIndex(columns[i].id) !== null) {
      -          $input.attr('checked', 'checked');
      -        }
      -        $input.appendTo($li);
      -        $('<label />')
      -            .text(columns[i].name)
      -            .attr('for','slick-column-vis-'+columns[i].id)
      -            .appendTo($li);
      -      }
      -      $('<li/>').addClass('divider').appendTo($menu);
      -      $li = $('<li />').data('option', 'autoresize').appendTo($menu);
      -      $input = $('<input type="checkbox" />').data('option', 'autoresize').attr('id','slick-option-autoresize');
      -      $input.appendTo($li);
      -      $('<label />')
      -          .text('Force fit columns')
      -          .attr('for','slick-option-autoresize')
      -          .appendTo($li);
      -      if (grid.getOptions().forceFitColumns) {
      -        $input.attr('checked', 'checked');
      -      }
      -
      -      $menu.css('top', e.pageY - 10)
      -          .css('left', e.pageX - 10)
      -          .fadeIn(options.fadeSpeed);
      -    }
      -
      -    function updateColumn(e) {
      -      var checkbox;
      -
      -      if ($(e.target).data('option') === 'autoresize') {
      -        var checked;
      -        if ($(e.target).is('li')){
      -            checkbox = $(e.target).find('input').first();
      -            checked = !checkbox.is(':checked');
      -            checkbox.attr('checked',checked);
      -        } else {
      -          checked = e.target.checked;
      -        }
      -
      -        if (checked) {
      -          grid.setOptions({forceFitColumns:true});
      -          grid.autosizeColumns();
      -        } else {
      -          grid.setOptions({forceFitColumns:false});
      -        }
      -        options.state.set({fitColumns:checked});
      -        return;
      -      }
      -
      -      if (($(e.target).is('li') && !$(e.target).hasClass('divider')) ||
      -            $(e.target).is('input')) {
      -        if ($(e.target).is('li')){
      -            checkbox = $(e.target).find('input').first();
      -            checkbox.attr('checked',!checkbox.is(':checked'));
      -        }
      -        var visibleColumns = [];
      -        var hiddenColumnsIds = [];
      -        $.each(columnCheckboxes, function (i, e) {
      -          if ($(this).is(':checked')) {
      -            visibleColumns.push(columns[i]);
      -          } else {
      -            hiddenColumnsIds.push(columns[i].id);
      -          }
      -        });
      -
      -        if (!visibleColumns.length) {
      -          $(e.target).attr('checked', 'checked');
      -          return;
      -        }
      -
      -        grid.setColumns(visibleColumns);
      -        options.state.set({hiddenColumns:hiddenColumnsIds});
      -      }
      -    }
      -    init();
      -  }
      - -
    • - - -
    • -
      - -
      - -
      -

      Slick.Controls.ColumnPicker

      - -
      - -
        $.extend(true, window, {
      -    Slick: {
      -      Controls: {
      -        ColumnPicker: SlickColumnPicker
      -      }
      -    }
      -  });
      -
      -})(jQuery);
      - -
    • - -
    -
    - - diff --git a/docs/src/view.timeline.html b/docs/src/view.timeline.html deleted file mode 100644 index 5a7551f8..00000000 --- a/docs/src/view.timeline.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - view.timeline.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      view.timeline.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      turn off unnecessary logging from VMM Timeline

      - -
      - -
      if (typeof VMM !== 'undefined') {
      -  VMM.debug = false;
      -}
      - -
    • - - -
    • -
      - -
      - -
      -

      Timeline

      -

      Timeline view using http://timeline.verite.co/

      - -
      - -
      my.Timeline = Backbone.View.extend({
      -  template: ' \
      -    <div class="recline-timeline"> \
      -      <div id="vmm-timeline-id"></div> \
      -    </div> \
      -  ',
      - -
    • - - -
    • -
      - -
      - -
      -

      These are the default (case-insensitive) names of field that are used if found. -If not found, the user will need to define these fields on initialization

      - -
      - -
        startFieldNames: ['date','startdate', 'start', 'start-date'],
      -  endFieldNames: ['end','endDate'],
      -  elementId: '#vmm-timeline-id',
      -
      -  initialize: function(options) {
      -    var self = this;
      -    this.timeline = new VMM.Timeline(this.elementId);
      -    this._timelineIsInitialized = false;
      -    this.listenTo(this.model.fields, 'reset', function() {
      -      self._setupTemporalField();
      -    });
      -    this.listenTo(this.model.records, 'all', function() {
      -      self.reloadData();
      -    });
      -    var stateData = _.extend({
      -        startField: null,
      -        endField: null,
      - -
    • - - -
    • -
      - -
      - -
      -

      by default timelinejs (and browsers) will parse ambiguous dates in US format (mm/dd/yyyy) -set to true to interpret dd/dd/dddd as dd/mm/yyyy

      - -
      - -
              nonUSDates: false,
      -        timelineJSOptions: {}
      -      },
      -      options.state
      -    );
      -    this.state = new recline.Model.ObjectState(stateData);
      -    this._setupTemporalField();
      -  },
      -
      -  render: function() {
      -    var tmplData = {};
      -    var htmls = Mustache.render(this.template, tmplData);
      -    this.$el.html(htmls);
      - -
    • - - -
    • -
      - -
      - -
      -

      can only call _initTimeline once view in DOM as Timeline uses $ -internally to look up element

      - -
      - -
          if ($(this.elementId).length > 0) {
      -      this._initTimeline();
      -    }
      -  },
      -
      -  show: function() {
      - -
    • - - -
    • -
      - -
      - -
      -

      only call _initTimeline once view in DOM as Timeline uses $ internally to look up element

      - -
      - -
          if (this._timelineIsInitialized === false) {
      -      this._initTimeline();
      -    }
      -  },
      -
      -  _initTimeline: function() {
      -    var data = this._timelineJSON();
      -    var config = this.state.get("timelineJSOptions");
      -    config.id = this.elementId;
      -    this.timeline.init(config, data);
      -    this._timelineIsInitialized = true
      -  },
      -
      -  reloadData: function() {
      -    if (this._timelineIsInitialized) {
      -      var data = this._timelineJSON();
      -      this.timeline.reload(data);
      -    }
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Convert record to JSON for timeline

      -

      Designed to be overridden in client apps

      - -
      - -
        convertRecord: function(record, fields) {
      -    return this._convertRecord(record, fields);
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      Internal method to generate a Timeline formatted entry

      - -
      - -
        _convertRecord: function(record, fields) {
      -    var start = this._parseDate(record.get(this.state.get('startField')));
      -    var end = this._parseDate(record.get(this.state.get('endField')));
      -    if (start) {
      -      var tlEntry = {
      -        "startDate": start,
      -        "endDate": end,
      -        "headline": String(record.get('title') || ''),
      -        "text": record.get('description') || record.summary(),
      -        "tag": record.get('tags')
      -      };
      -      return tlEntry;
      -    } else {
      -      return null;
      -    }
      -  },
      -
      -  _timelineJSON: function() {
      -    var self = this;
      -    var out = {
      -      'timeline': {
      -        'type': 'default',
      -        'headline': '',
      -        'date': [
      -        ]
      -      }
      -    };
      -    this.model.records.each(function(record) {
      -      var newEntry = self.convertRecord(record, self.fields);
      -      if (newEntry) {
      -        out.timeline.date.push(newEntry); 
      -      }
      -    });
      - -
    • - - -
    • -
      - -
      - -
      -

      if no entries create a placeholder entry to prevent Timeline crashing with error

      - -
      - -
          if (out.timeline.date.length === 0) {
      -      var tlEntry = {
      -        "startDate": '2000,1,1',
      -        "headline": 'No data to show!'
      -      };
      -      out.timeline.date.push(tlEntry);
      -    }
      -    return out;
      -  },
      - -
    • - - -
    • -
      - -
      - -
      -

      convert dates into a format TimelineJS will handle -TimelineJS does not document this at all so combo of read the code + -trial and error -Summary (AFAICt): -Preferred: [-]yyyy[,mm,dd,hh,mm,ss] -Supported: mm/dd/yyyy

      - -
      - -
        _parseDate: function(date) {
      -    if (!date) {
      -      return null;
      -    }
      -    var out = $.trim(date);
      -    out = out.replace(/(\d)th/g, '$1');
      -    out = out.replace(/(\d)st/g, '$1');
      -    out = $.trim(out);
      -    if (out.match(/\d\d\d\d-\d\d-\d\d(T.*)?/)) {
      -      out = out.replace(/-/g, ',').replace('T', ',').replace(':',',');
      -    }
      -    if (out.match(/\d\d-\d\d-\d\d.*/)) {
      -      out = out.replace(/-/g, '/');
      -    }
      -    if (this.state.get('nonUSDates')) {
      -      var parts = out.match(/(\d\d)\/(\d\d)\/(\d\d.*)/);
      -      if (parts) {
      -        out = [parts[2], parts[1], parts[3]].join('/');
      -      }
      -    }
      -    return out;
      -  },
      -
      -  _setupTemporalField: function() {
      -    this.state.set({
      -      startField: this._checkField(this.startFieldNames),
      -      endField: this._checkField(this.endFieldNames)
      -    });
      -  },
      -
      -  _checkField: function(possibleFieldNames) {
      -    var modelFieldNames = this.model.fields.pluck('id');
      -    for (var i = 0; i < possibleFieldNames.length; i++){
      -      for (var j = 0; j < modelFieldNames.length; j++){
      -        if (modelFieldNames[j].toLowerCase() == possibleFieldNames[i].toLowerCase())
      -          return modelFieldNames[j];
      -      }
      -    }
      -    return null;
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/widget.facetviewer.html b/docs/src/widget.facetviewer.html deleted file mode 100644 index d2523dc3..00000000 --- a/docs/src/widget.facetviewer.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - widget.facetviewer.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      widget.facetviewer.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      - -
    • - - -
    • -
      - -
      - -
      -

      FacetViewer

      -

      Widget for displaying facets

      -

      Usage:

      -
       var viewer = new FacetViewer({
      -   model: dataset
      - });
      -
      -
      - -
      my.FacetViewer = Backbone.View.extend({
      -  className: 'recline-facet-viewer', 
      -  template: ' \
      -    <div class="facets"> \
      -      {{#facets}} \
      -      <div class="facet-summary" data-facet="{{id}}"> \
      -        <h3> \
      -          {{id}} \
      -        </h3> \
      -        <ul class="facet-items"> \
      -        {{#terms}} \
      -          <li><a class="facet-choice js-facet-filter" data-value="{{term}}" href="#{{term}}">{{term}} ({{count}})</a></li> \
      -        {{/terms}} \
      -        {{#entries}} \
      -          <li><a class="facet-choice js-facet-filter" data-value="{{time}}">{{term}} ({{count}})</a></li> \
      -        {{/entries}} \
      -        </ul> \
      -      </div> \
      -      {{/facets}} \
      -    </div> \
      -  ',
      -
      -  events: {
      -    'click .js-facet-filter': 'onFacetFilter'
      -  },
      -  initialize: function(model) {
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model.facets, 'all', this.render);
      -    this.listenTo(this.model.fields, 'all', this.render);
      -    this.render();
      -  },
      -  render: function() {
      -    var tmplData = {
      -      fields: this.model.fields.toJSON()
      -    };
      -    tmplData.facets = _.map(this.model.facets.toJSON(), function(facet) {
      -      if (facet._type === 'date_histogram') {
      -        facet.entries = _.map(facet.entries, function(entry) {
      -          entry.term = new Date(entry.time).toDateString();
      -          return entry;
      -        });
      -      }
      -      return facet;
      -    });
      -    var templated = Mustache.render(this.template, tmplData);
      -    this.$el.html(templated);
      - -
    • - - -
    • -
      - -
      - -
      -

      are there actually any facets to show?

      - -
      - -
          if (this.model.facets.length > 0) {
      -      this.$el.show();
      -    } else {
      -      this.$el.hide();
      -    }
      -  },
      -  onHide: function(e) {
      -    e.preventDefault();
      -    this.$el.hide();
      -  },
      -  onFacetFilter: function(e) {
      -    e.preventDefault();
      -    var $target= $(e.target);
      -    var fieldId = $target.closest('.facet-summary').attr('data-facet');
      -    var value = $target.attr('data-value');
      -    this.model.queryState.addFilter({type: 'term', field: fieldId, term: value});
      - -
    • - - -
    • -
      - -
      - -
      -

      have to trigger explicitly for some reason

      - -
      - -
          this.model.query();
      -  }
      -});
      -
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/widget.fields.html b/docs/src/widget.fields.html deleted file mode 100644 index 07b34852..00000000 --- a/docs/src/widget.fields.html +++ /dev/null @@ -1,299 +0,0 @@ - - - - - widget.fields.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      widget.fields.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      - -
    • - - -
    • -
      - -
      - -
      -

      Field Info

      -

      For each field

      -

      Id / Label / type / format

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Editor — to change type (and possibly format) -Editor for show/hide …

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Summaries of fields

      -

      Top values / number empty -If number: max, min average …

      - -
      - -
    • - - -
    • -
      - -
      - -
      -

      Box to boot transform editor …

      - -
      - -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      -  
      -my.Fields = Backbone.View.extend({
      -  className: 'recline-fields-view', 
      -  template: ' \
      -    <div class="panel-group fields-list well"> \
      -    <h3>Fields <a href="#" class="js-show-hide">+</a></h3> \
      -    {{#fields}} \
      -      <div class="panel panel-default field"> \
      -        <div class="panel-heading"> \
      -          <i class="glyphicon glyphicon-file"></i> \
      -          <h4> \
      -            {{label}} \
      -            <small> \
      -              {{type}} \
      -              <a class="accordion-toggle" data-toggle="collapse" href="#collapse{{id}}"> &raquo; </a> \
      -            </small> \
      -          </h4> \
      -        </div> \
      -        <div id="collapse{{id}}" class="panel-collapse collapse"> \
      -          <div class="panel-body"> \
      -            {{#facets}} \
      -            <div class="facet-summary" data-facet="{{id}}"> \
      -              <ul class="facet-items"> \
      -              {{#terms}} \
      -                <li class="facet-item"><span class="term">{{term}}</span> <span class="count">[{{count}}]</span></li> \
      -              {{/terms}} \
      -              </ul> \
      -            </div> \
      -            {{/facets}} \
      -            <div class="clear"></div> \
      -          </div> \
      -        </div> \
      -      </div> \
      -    {{/fields}} \
      -    </div> \
      -  ',
      -
      -  initialize: function(model) {
      -    var self = this;
      -    _.bindAll(this, 'render');
      - -
    • - - -
    • -
      - -
      - -
      -

      TODO: this is quite restrictive in terms of when it is re-run -e.g. a change in type will not trigger a re-run atm. -being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width)

      - -
      - -
          this.listenTo(this.model.fields, 'reset', function(action) {
      -      self.model.fields.each(function(field) {
      -        field.facets.unbind('all', self.render);
      -        field.facets.bind('all', self.render);
      -      });
      - -
    • - - -
    • -
      - -
      - -
      -

      fields can get reset or changed in which case we need to recalculate

      - -
      - -
            self.model.getFieldsSummary();
      -      self.render();
      -    });
      -    this.$el.find('.collapse').collapse();
      -    this.render();
      -  },
      -  render: function() {
      -    var self = this;
      -    var tmplData = {
      -      fields: []
      -    };
      -    this.model.fields.each(function(field) {
      -      var out = field.toJSON();
      -      out.facets = field.facets.toJSON();
      -      tmplData.fields.push(out);
      -    });
      -    var templated = Mustache.render(this.template, tmplData);
      -    this.$el.html(templated);
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/widget.filtereditor.html b/docs/src/widget.filtereditor.html deleted file mode 100644 index a761e3d7..00000000 --- a/docs/src/widget.filtereditor.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - widget.filtereditor.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      widget.filtereditor.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      -
      -my.FilterEditor = Backbone.View.extend({
      -  className: 'recline-filter-editor well', 
      -  template: ' \
      -    <div class="filters"> \
      -      <h3>Filters</h3> \
      -      <a href="#" class="js-add-filter">Add filter</a> \
      -      <form class="form-stacked js-add" style="display: none;"> \
      -        <div class="form-group"> \
      -          <label>Field</label> \
      -          <select class="fields form-control"> \
      -            {{#fields}} \
      -            <option value="{{id}}">{{label}}</option> \
      -            {{/fields}} \
      -          </select> \
      -        </div> \
      -        <div class="form-group"> \
      -          <label>Filter type</label> \
      -          <select class="filterType form-control"> \
      -            <option value="term">Value</option> \
      -            <option value="range">Range</option> \
      -            <option value="geo_distance">Geo distance</option> \
      -          </select> \
      -        </div> \
      -        <button type="submit" class="btn btn-default">Add</button> \
      -      </form> \
      -      <form class="form-stacked js-edit"> \
      -        {{#filters}} \
      -          {{{filterRender}}} \
      -        {{/filters}} \
      -        {{#filters.length}} \
      -        <button type="submit" class="btn btn-default">Update</button> \
      -        {{/filters.length}} \
      -      </form> \
      -    </div> \
      -  ',
      -  filterTemplates: {
      -    term: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          <legend> \
      -            {{field}} <small>{{type}}</small> \
      -            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          </legend> \
      -          <input class="input-sm" type="text" value="{{term}}" name="term" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -        </fieldset> \
      -      </div> \
      -    ',
      -    range: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          <legend> \
      -            {{field}} <small>{{type}}</small> \
      -            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          </legend> \
      -          <div class="form-group"> \
      -            <label class="control-label" for="">From</label> \
      -            <input class="input-sm" type="text" value="{{from}}" name="from" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          </div> \
      -          <div class="form-group"> \
      -            <label class="control-label" for="">To</label> \
      -            <input class="input-sm" type="text" value="{{to}}" name="to" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          </div> \
      -        </fieldset> \
      -      </div> \
      -    ',
      -    geo_distance: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          <legend> \
      -            {{field}} <small>{{type}}</small> \
      -            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          </legend> \
      -          <div class="form-group"> \
      -            <label class="control-label" for="">Longitude</label> \
      -            <input class="input-sm" type="text" value="{{point.lon}}" name="lon" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          </div> \
      -          <div class="form-group"> \
      -            <label class="control-label" for="">Latitude</label> \
      -            <input class="input-sm" type="text" value="{{point.lat}}" name="lat" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          </div> \
      -          <div class="form-group"> \
      -            <label class="control-label" for="">Distance (km)</label> \
      -            <input class="input-sm" type="text" value="{{distance}}" name="distance" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          </div> \
      -        </fieldset> \
      -      </div> \
      -    '
      -  },
      -  events: {
      -    'click .js-remove-filter': 'onRemoveFilter',
      -    'click .js-add-filter': 'onAddFilterShow',
      -    'submit form.js-edit': 'onTermFiltersUpdate',
      -    'submit form.js-add': 'onAddFilter'
      -  },
      -  initialize: function() {
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model.fields, 'all', this.render);
      -    this.listenTo(this.model.queryState, 'change change:filters:new-blank', this.render);
      -    this.render();
      -  },
      -  render: function() {
      -    var self = this;
      -    var tmplData = $.extend(true, {}, this.model.queryState.toJSON());
      - -
    • - - -
    • -
      - -
      - -
      -

      we will use idx in list as there id …

      - -
      - -
          tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
      -      filter.id = idx;
      -      return filter;
      -    });
      -    tmplData.fields = this.model.fields.toJSON();
      -    tmplData.filterRender = function() {
      -      return Mustache.render(self.filterTemplates[this.type], this);
      -    };
      -    var out = Mustache.render(this.template, tmplData);
      -    this.$el.html(out);
      -  },
      -  onAddFilterShow: function(e) {
      -    e.preventDefault();
      -    var $target = $(e.target);
      -    $target.hide();
      -    this.$el.find('form.js-add').show();
      -  },
      -  onAddFilter: function(e) {
      -    e.preventDefault();
      -    var $target = $(e.target);
      -    $target.hide();
      -    var filterType = $target.find('select.filterType').val();
      -    var field      = $target.find('select.fields').val();
      -    this.model.queryState.addFilter({type: filterType, field: field});
      -  },
      -  onRemoveFilter: function(e) {
      -    e.preventDefault();
      -    var $target = $(e.target);
      -    var filterId = $target.attr('data-filter-id');
      -    this.model.queryState.removeFilter(filterId);
      -  },
      -  onTermFiltersUpdate: function(e) {
      -   var self = this;
      -    e.preventDefault();
      -    var filters = self.model.queryState.get('filters');
      -    var $form = $(e.target);
      -    _.each($form.find('input'), function(input) {
      -      var $input = $(input);
      -      var filterType  = $input.attr('data-filter-type');
      -      var fieldId     = $input.attr('data-filter-field');
      -      var filterIndex = parseInt($input.attr('data-filter-id'), 10);
      -      var name        = $input.attr('name');
      -      var value       = $input.val();
      -
      -      switch (filterType) {
      -        case 'term':
      -          filters[filterIndex].term = value;
      -          break;
      -        case 'range':
      -          filters[filterIndex][name] = value;
      -          break;
      -        case 'geo_distance':
      -          if(name === 'distance') {
      -            filters[filterIndex].distance = parseFloat(value);
      -          }
      -          else {
      -            filters[filterIndex].point[name] = parseFloat(value);
      -          }
      -          break;
      -      }
      -    });
      -    self.model.queryState.set({filters: filters, from: 0});
      -    self.model.queryState.trigger('change');
      -  }
      -});
      -
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/widget.pager.html b/docs/src/widget.pager.html deleted file mode 100644 index e223a41f..00000000 --- a/docs/src/widget.pager.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - widget.pager.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      widget.pager.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      -
      -my.Pager = Backbone.View.extend({
      -  className: 'recline-pager', 
      -  template: ' \
      -    <div class="pagination"> \
      -      <ul class="pagination"> \
      -        <li class="prev action-pagination-update"><a href="" class="btn btn-default">&laquo;</a></li> \
      -        <li class="page-range"><a><label for="from">From</label><input id="from" name="from" type="text" value="{{from}}" /> &ndash; <label for="to">To</label><input id="to" name="to" type="text" value="{{to}}" /> </a></li> \
      -        <li class="next action-pagination-update"><a href="" class="btn btn-default">&raquo;</a></li> \
      -      </ul> \
      -    </div> \
      -  ',
      -
      -  events: {
      -    'click .action-pagination-update': 'onPaginationUpdate',
      -    'change input': 'onFormSubmit'
      -  },
      -
      -  initialize: function() {
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model.queryState, 'change', this.render);
      -    this.render();
      -  },
      -  onFormSubmit: function(e) {
      -    e.preventDefault();
      - -
    • - - -
    • -
      - -
      - -
      -

      filter is 0-based; form is 1-based

      - -
      - -
          var formFrom = parseInt(this.$el.find('input[name="from"]').val())-1; 
      -    var formTo = parseInt(this.$el.find('input[name="to"]').val())-1; 
      -    var maxRecord = this.model.recordCount-1;
      -    if (this.model.queryState.get('from') != formFrom) { // changed from; update from
      -      this.model.queryState.set({from: Math.min(maxRecord, Math.max(formFrom, 0))});
      -    } else if (this.model.queryState.get('to') != formTo) { // change to; update size
      -      var to = Math.min(maxRecord, Math.max(formTo, 0));
      -      this.model.queryState.set({size: Math.min(maxRecord+1, Math.max(to-formFrom+1, 1))});
      -    }
      -  },
      -  onPaginationUpdate: function(e) {
      -    e.preventDefault();
      -    var $el = $(e.target);
      -    var newFrom = 0;
      -    var currFrom = this.model.queryState.get('from');
      -    var size = this.model.queryState.get('size');
      -    var updateQuery = false;
      -    if ($el.parent().hasClass('prev')) {
      -      newFrom = Math.max(currFrom - Math.max(0, size), 0);
      -      updateQuery = newFrom != currFrom;
      -    } else {
      -      newFrom = Math.max(currFrom + size, 0);
      -      updateQuery = (newFrom < this.model.recordCount);
      -    }
      -    if (updateQuery) {
      -      this.model.queryState.set({from: newFrom});
      -    }
      -  },
      -  render: function() {
      -    var tmplData = this.model.toJSON();
      -    var from = parseInt(this.model.queryState.get('from'));
      -    tmplData.from = from+1;
      -    tmplData.to = Math.min(from+this.model.queryState.get('size'), this.model.recordCount);
      -    var templated = Mustache.render(this.template, tmplData);
      -    this.$el.html(templated);
      -    return this;
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/widget.queryeditor.html b/docs/src/widget.queryeditor.html deleted file mode 100644 index 3ce92817..00000000 --- a/docs/src/widget.queryeditor.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - widget.queryeditor.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      widget.queryeditor.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      -
      -my.QueryEditor = Backbone.View.extend({
      -  className: 'recline-query-editor', 
      -  template: ' \
      -    <form action="" method="GET" class="form-inline" role="form"> \
      -      <div class="form-group"> \
      -        <div class="input-group text-query"> \
      -          <div class="input-group-addon"> \
      -            <i class="glyphicon glyphicon-search"></i> \
      -          </div> \
      -          <label for="q">Search</label> \
      -          <input class="form-control search-query" type="text" id="q" name="q" value="{{q}}" placeholder="Search data ..."> \
      -        </div> \
      -      </div> \
      -      <button type="submit" class="btn btn-default">Go &raquo;</button> \
      -    </form> \
      -  ',
      -
      -  events: {
      -    'submit form': 'onFormSubmit'
      -  },
      -
      -  initialize: function() {
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model, 'change', this.render);
      -    this.render();
      -  },
      -  onFormSubmit: function(e) {
      -    e.preventDefault();
      -    var query = this.$el.find('.search-query').val();
      -    this.model.set({q: query});
      -  },
      -  render: function() {
      -    var tmplData = this.model.toJSON();
      -    var templated = Mustache.render(this.template, tmplData);
      -    this.$el.html(templated);
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/src/widget.valuefilter.html b/docs/src/widget.valuefilter.html deleted file mode 100644 index 1f72bc0f..00000000 --- a/docs/src/widget.valuefilter.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - - widget.valuefilter.js - - - - - -
    -
    - - - -
      - -
    • -
      -

      widget.valuefilter.js

      -
      -
    • - - - -
    • -
      - -
      - -
      - -
      - -
      /*jshint multistr:true */
      -
      -this.recline = this.recline || {};
      -this.recline.View = this.recline.View || {};
      -
      -(function($, my) {
      -  "use strict";
      -
      -my.ValueFilter = Backbone.View.extend({
      -  className: 'recline-filter-editor well', 
      -  template: ' \
      -    <div class="filters"> \
      -      <h3>Filters</h3> \
      -      <button class="btn js-add-filter add-filter">Add filter</button> \
      -      <form class="form-stacked js-add" style="display: none;"> \
      -        <fieldset> \
      -          <label>Field</label> \
      -          <select class="fields form-control"> \
      -            {{#fields}} \
      -            <option value="{{id}}">{{label}}</option> \
      -            {{/fields}} \
      -          </select> \
      -          <button type="submit" class="btn">Add</button> \
      -        </fieldset> \
      -      </form> \
      -      <form class="form-stacked js-edit"> \
      -        {{#filters}} \
      -          {{{filterRender}}} \
      -        {{/filters}} \
      -        {{#filters.length}} \
      -        <button type="submit" class="btn update-filter">Update</button> \
      -        {{/filters.length}} \
      -      </form> \
      -    </div> \
      -  ',
      -  filterTemplates: {
      -    term: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          {{field}} \
      -          <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          <input type="text" value="{{term}}" name="term" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -        </fieldset> \
      -      </div> \
      -    '
      -  },
      -  events: {
      -    'click .js-remove-filter': 'onRemoveFilter',
      -    'click .js-add-filter': 'onAddFilterShow',
      -    'submit form.js-edit': 'onTermFiltersUpdate',
      -    'submit form.js-add': 'onAddFilter'
      -  },
      -  initialize: function() {
      -    _.bindAll(this, 'render');
      -    this.listenTo(this.model.fields, 'all', this.render);
      -    this.listenTo(this.model.queryState, 'change change:filters:new-blank', this.render);
      -    this.render();
      -  },
      -  render: function() {
      -    var self = this;
      -    var tmplData = $.extend(true, {}, this.model.queryState.toJSON());
      - -
    • - - -
    • -
      - -
      - -
      -

      we will use idx in list as the id …

      - -
      - -
          tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
      -      filter.id = idx;
      -      return filter;
      -    });
      -    tmplData.fields = this.model.fields.toJSON();
      -    tmplData.filterRender = function() {
      -      return Mustache.render(self.filterTemplates.term, this);
      -    };
      -    var out = Mustache.render(this.template, tmplData);
      -    this.$el.html(out);
      -  },
      -  updateFilter: function(input) {
      -    var self = this;
      -    var filters = self.model.queryState.get('filters');
      -    var $input = $(input);
      -    var filterIndex = parseInt($input.attr('data-filter-id'), 10);
      -    var value = $input.val();
      -    filters[filterIndex].term = value;
      -  },
      -  onAddFilterShow: function(e) {
      -    e.preventDefault();
      -    var $target = $(e.target);
      -    $target.hide();
      -    this.$el.find('form.js-add').show();
      -  },
      -  onAddFilter: function(e) {
      -    e.preventDefault();
      -    var $target = $(e.target);
      -    $target.hide();
      -    var field = $target.find('select.fields').val();
      -    this.model.queryState.addFilter({type: 'term', field: field});
      -  },
      -  onRemoveFilter: function(e) {
      -    e.preventDefault();
      -    var $target = $(e.target);
      -    var filterId = $target.attr('data-filter-id');
      -    this.model.queryState.removeFilter(filterId);
      -  },
      -  onTermFiltersUpdate: function(e) {
      -    var self = this;
      -    e.preventDefault();
      -    var filters = self.model.queryState.get('filters');
      -    var $form = $(e.target);
      -    _.each($form.find('input'), function(input) {
      -      self.updateFilter(input);
      -    });
      -    self.model.queryState.set({filters: filters, from: 0});
      -    self.model.queryState.trigger('change');
      -  }
      -});
      -
      -})(jQuery, recline.View);
      - -
    • - -
    -
    - - diff --git a/docs/tutorial-backends.markdown b/docs/tutorial-backends.markdown deleted file mode 100644 index f64abd3d..00000000 --- a/docs/tutorial-backends.markdown +++ /dev/null @@ -1,205 +0,0 @@ ---- -layout: container -title: Loading data from different sources using Backends - Tutorial -recline-deps: true -root: ../ ---- - - - -## Overview - -Backends connect Recline Datasets to data from a specific 'Backend' data -source. - -They provide methods for loading and saving Datasets and individuals -Documents as well as for bulk loading via a query API and doing bulk transforms -on the backend. - -Backends come in 2 flavours: - -* Loader backends - only implement fetch method. The data is then cached in a - Memory.Store on the Dataset and interacted with there. This is best for - sources which just allow you to load data or where you want to load the data - once and work with it locally. -* Store backends - these support fetch, query and, if write-enabled, save. - These are suited to cases where the source datastore contains a lot of data - (infeasible to load locally - for examples a million rows) or where the - backend has, for example, query capabilities you want to take advantage of. - -### Instantiation and Use - -You can use a backend directly e.g. - -{% highlight javascript %} -var backend = recline.Backend.ElasticSearch.fetch({url: ...}); -{% endhighlight %} - -But more usually the backend will be created or loaded for you by Recline and -all you need is provide the identifier for that Backend e.g. - -{% highlight javascript %} -var dataset = recline.Model.Dataset({ - backend: 'backend-identifier' -}); -{% endhighlight %} - -
    -

    Backend identifiers -How do you know the backend identifier for a given Backend? It's just the name -of the 'class' in recline.Backend module (but case-insensitive). E.g. -recline.Backend.ElasticSearch can be identified as 'ElasticSearch' or -'elasticsearch'.

    -
    - -## What Backends are available from Recline? - -{% include backend-list.html %} - -**Backend you'd like to see not available?** It's easy to write your own -– see the Backend reference docs for details -of the required API. - -## Preparing your app - -This is as per the [quickstart](tutorial-views.html) but the set of files is -much more limited if you are just using a Backend. Specifically: - -{% highlight html %} - - - - - - - - - - -{% endhighlight %} - - -## Loading Data from Google Docs - -We will be using the [following Google -Doc](https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdGZPaUZsMjBxeGhfOWRlWm85MmV0UUE#gid=0). -For Recline to be able to access a Google Spreadsheet it **must** have been -'Published to the Web' (enabled via File -> Publish to the Web menu). - -{% highlight javascript %} -// include the Recline backend for Google Docs - -{% include example-backends-gdocs.js %} -{% endhighlight %} - -### Result - -
     
    - - - - - - -## Loading Data from ElasticSearch - -Recline supports ElasticSearch as a full read/write/query backend via the -[ElasticSearch.js library][esjs]. See the library for examples. - -[esjs]: https://github.com/okfn/elasticsearch.js - - -## Loading data from CSV files - -For loading data from CSV files there are 3 cases: - -1. CSV is online but on same domain or supporting CORS (S3 and Google Storage support CORS!) -- we can then load using AJAX (as no problems with same origin policy) -2. CSV is on local disk -- if your browser supports HTML5 File API we can load the CSV file off disk -3. CSV is online but not on same domain -- use DataProxy (see below) - -In all cases we'll need to have loaded the Recline CSV backend (for your own -app you'll probably want this locally): - -{% highlight html %} - -{% endhighlight %} - -### Local online CSV file - -Let's start with first case: loading a "local" online CSV file. We'll be using this [example file]({{page.root}}/demos/data/sample.csv). - -{% highlight javascript %} -{% include example-backends-online-csv.js %} -{% endhighlight %} - -#### Result - -
     
    - - - -### CSV file on disk - -This requires your browser to support the HTML5 file API. Suppose we have a file input like: - - - -Then we can load the file into a Recline Dataset as follows: - -{% highlight javascript %} -{% include example-backends-csv-disk.js %} -{% endhighlight %} - -#### Try it out! - -Try it out by clicking on the file input above, selecting a CSV file and seeing what happens. - -
     
    - - - - -## Loading data from CSV and Excel files online using DataProxy - -The [DataProxy](http://github.com/okfn/dataproxy) is a web-service run by the Open Knowledge Foundation that converts CSV and Excel files to JSON. It has a convenient JSON-p-able API which means we can use it to load data from online CSV and Excel into Recline Datasets. - -Recline ships with a simple DataProxy "backend" that takes care of fetching data from the DataProxy source. - -The main limitation of the DataProxy is that it can only handle Excel files up to a certain size (5mb) and that as we must use JSONP to access it error information can be limited. - -{% highlight javascript %} -{% include example-backends-dataproxy.js %} -{% endhighlight %} - -### Result - -
     
    - - - -### Customizing the timeout - -As we must use JSONP in this backend we have the problem that if DataProxy errors (e.g. 500) this won't be picked up. To deal with this and prevent the case where the request never finishes We have a timeout on the request after which the Backend sends back an error stating that request timed out. - -You can customize the length of this timeout by setting the following constant: - -{% highlight javascript %} -// Customize the timeout (in milliseconds) - default is 5000 -recline.Backend.DataProxy.timeout = 10000; -{% endhighlight %} - diff --git a/docs/tutorial-basics-events.markdown b/docs/tutorial-basics-events.markdown deleted file mode 100644 index 55b40526..00000000 --- a/docs/tutorial-basics-events.markdown +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: container -title: Tutorial - Dataset Basics - Events -recline-deps: true -root: ../ ---- - - - - -## Preparations - -See first Dataset basics tutorial for getting setup and initializing Dataset. - - - -## Listening for Events - -Often you'll want to listen to events on a Dataset and its associated objects. This is easy to do thanks to the use of Backbone model objects which have a [standard set of events](http://backbonejs.org/#FAQ-events). - -Here's an example to illustrate: - -{% highlight javascript %} -{% include tutorial-basics-ex-events.js %} -{% endhighlight %} - -
     
    - - - -Here's a summary of the main objects and their events: - -* Dataset: - - * Standard Backbone events for changes to attributes (note that this will **not** include changes to records) - * *query:start / query:end* called at start and completion of a query - -* Dataset.records: Backbone.Collection of "current" records (i.e. those resulting from latest query) with standard Backbone set of events: *add, reset, remove* - -* Dataset.queryState: queryState is a Query object with standard Backbone Model set of events - -* Dataset.fields: Backbone Collection of Fields. - diff --git a/docs/tutorial-basics-query.markdown b/docs/tutorial-basics-query.markdown deleted file mode 100644 index f44c8522..00000000 --- a/docs/tutorial-basics-query.markdown +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: container -title: Tutorial - Dataset Basics -recline-deps: true -root: ../ ---- - - - -## Preparations - -See first Dataset basics tutorial for getting setup and initializing a Dataset. - - - -## Querying - -The basic thing we want to do with Datasets is query and filter them. This is -very easy to do: - -{% highlight javascript %} -{% include tutorial-basics-ex-2.js %} -{% endhighlight %} - -This results in the following. Note how recordCount is now 3 (the total number -of records matched by the query) but that records only contains 2 records as we -restricted number of returned records using the size attribute. - -
     
    - - - -## Filtering - -A simple unstructured query like the one provided above searches all fieldsfor the value provided. - -Often you want to "filter" results more precisely, for example by specifying a specific value in a specific field. To do this we use "filters". - -{% highlight javascript %} -var query = new recline.Model.Query(); -query.addFilter({type: 'term', field: 2}); -dataset.query(query); -{% endhighlight %} - -## QueryState - -The last run query is stored as a Query -instance in the `queryState` attribute of the Dataset object. Modifying -`queryState` will also resulting in a query being run. This is useful when -building views that want to display or manage the query state (see, for -example, Query Editor or Filter Editor widgets). - -## Full Details of the Query Language - -Full details of the query structure and its options -can be found in the reference documentation. - diff --git a/docs/tutorial-basics.markdown b/docs/tutorial-basics.markdown deleted file mode 100644 index a1e548d9..00000000 --- a/docs/tutorial-basics.markdown +++ /dev/null @@ -1,152 +0,0 @@ ---- -layout: container -title: Tutorial - Dataset Basics -recline-deps: true -root: ../ ---- - - - -## Preparations - -1. [Download ReclineJS]({{page.root}}download.html) and relevant dependencies. - -2. Include the core dependencies for the Dataset model - - {% highlight html %} - - - - -{% endhighlight %} - -## Creating a Dataset - -Here's the example data We are going to work with: - -{% highlight javascript %} -{% include data.js %} -{% endhighlight %} - -In this data we have 6 records / rows. Each record is a javascript object -containing keys and values (values can be 'simple' e.g. a string or float or arrays or hashes - e.g. the geo value here). - -
    In this tutorial we are creating datasets with -"local" JS data. However, Recline has a variety of Backends that make it easy -to create Datasets from a variety of online sources and local sources including -Google Spreadsheets, CSV files, etc. See the Backend tutorial for more.
    - -We can now create a recline Dataset object from this raw data: - -{% highlight javascript %} -var dataset = new recline.Model.Dataset({ - records: data -}); -{% endhighlight %} - - - -## A Dataset and its Records - -Now that we have created a Dataset, we can use it. - -For example, let's display some information about the Dataset and its records using some of the key Dataset attributes: `recordCount` and `records`. - -{% highlight javascript %} -{% include tutorial-basics-ex-1.js %} -{% endhighlight %} - -Here's the output: - -
     
    - - - -### Records - -`Dataset.records` is a Backbone Collection of `Record`s resutling from latest query. This need not (and usually isn't) **all** the records in this Dataset since the latest query need not have matched all records. - -
    -Note that on initialization, a Dataset automatically queries for the first 100 records so this is what will usually be available in th records attribute. If you did want all records loaded into records at the start just requery after fetch has completed: - -{% highlight javascript %} -// for the async case need to put inside of done -dataset.fetch().done(function() { - dataset.query({size: dataset.recordCount}); -}); -{% endhighlight %} -
    - -As a Backbone Collection it supports all the standard Backbone collection functionality including methods like `each` and `filter`: - -{% highlight javascript %} -dataset.records.each(function(record) { - console.log(record.get('x') * 2); -}); - -var filtered = dataset.records.filter(function(record) { - return (record.get('z') > 4 && record.get('z') < 18); -}); - -// get all the values for a given attribute/field/column -var xvalues = dataset.records.pluck('x'); - -// calls toJSON on all records at once -dataset.records.toJSON(); -{% endhighlight %} - -
    Want to know more about Dataset and Records? Check out the reference documentation
    - -## Fields - -In addition to Records, a Dataset has Fields stored in the `fields` attribute. Fields provide information about the fields/columns in the Dataset, for example their id (key name in record), label, type etc. - -The Dataset's fields will be automatically computed from records or provided by the backend but they can also be explicitly provided and configured. Let's take a look at the fields on the dataset at present using the following code: - -{% highlight javascript %} -{% include tutorial-basics-ex-fields.js %} -{% endhighlight %} - -Running this results in the following: - -
     
    - - - -As can be seen all fields have the default type of 'string'. - -As you may have noticed above the last geo attribute of the dataset just rendered as `[object Object]`. This is because the Dataset is treating that field value as a string. Let's now take a look at the Dataset fields in more detail. - -Let's change the geo field to have type geo\_point and see what affect that has on displaying of the dataset (for good measure we'll also set the label): - -{% highlight javascript %} -{% include tutorial-basics-ex-fields-2.js %} -{% endhighlight %} - -
     
    - - - -As can be seen the rendering of the field has changed. This is because the `summary` method uses the Record.getFieldValue function which in turn renders a record field using the Field's renderer function. This function varies depending on the type and can also be customized (see the Field documentation). - diff --git a/docs/tutorial-grids.markdown b/docs/tutorial-grids.markdown deleted file mode 100644 index 20632f5f..00000000 --- a/docs/tutorial-grids.markdown +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: container -title: Grids - Advanced use of grids in Recline - Tutorial -recline-deps: true -root: ../ ---- - - - -### How much can I do with a simple grid view - -### Benefits of SlickGrid -What does Recline give you out of the box, what does SlickGrid have built in and how to use it (reference to stuff below). - -### Preparing your page - -See the instructions in the [basic views tutorial](tutorial-views.html). - -### Creating a Dataset - -Just like in the main tutorial, here's some example data We are going to work with: - -{% highlight javascript %} -{% include data.js %} -var dataset = new recline.Model.Dataset({ - records: data -}); -{% endhighlight %} - - - -### Accessing SlickGrid features -Show how we can customize SlickGrid grid "normally" - i.e. you can get access to underlying grid and tweak it as you want. - -Suggest we demonstrate using this example: https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example-plugin-headermenu.html - -Idea here is: we don't want to mimic all that slickgrid can do through recline interface - just let people do it directly themselves ... - - - diff --git a/docs/tutorial-maps.markdown b/docs/tutorial-maps.markdown deleted file mode 100644 index 60ab0a1d..00000000 --- a/docs/tutorial-maps.markdown +++ /dev/null @@ -1,131 +0,0 @@ ---- -layout: container -title: Maps - Customizing Maps in Recline - Tutorial -recline-deps: true -root: ../ ---- - - - -### Preparing your page - -See the instructions in the [basic views tutorial](tutorial-views.html). - -### Creating a Dataset - -Just like in the main tutorial, here's some example data We are going to work with: - -{% highlight javascript %} -{% include data.js %} -var dataset = new recline.Model.Dataset({ - records: data -}); -{% endhighlight %} - - - -### General Pointers - -Check out the Map reference -(source) docs. In particular this has details of the various state options. - -In addition, remember that Recline's map view is just a relatively lightweight -wrapper around Leaflet. This means that pretty much anything you can do with -Leaflet you can do with Recline's map. Specifically a `recline.View.Map` -instance has the following attributes exposed: - - map: the Leaflet map (L.Map) - features: Leaflet GeoJSON layer (L.GeoJSON) containing all the features - -(Recline converts all records in a dataset that yield geospatial info to a -GeoJSON feature and adds it to the features layer). - -### Customizing the infobox - -The default infobox just shows all of the dataset attributes. Usually you'll -want something a bit nicer. All you need to do is override the infobox -function. For example, in our case let's make a nicer title and only show some -data. - -{% highlight javascript %} -{% include tutorial-maps-infobox.js %} -{% endhighlight %} - -
     
    - - - -### Customizing the marker - -We're going to show how to replace the default marker with a circular marker. -Even more exciting, we'll show how to have the marker size vary with an -attribute of our data. We do the customization by via over-riding the -pointToLayer function: - -{% highlight javascript %} -{% include tutorial-maps-customize.js %} -{% endhighlight %} - -
     
    - - - -### Customing features (which aren't points) - -Leaflet treats points and features differently. To customize features that -aren't point we will need to bind to the feature layers featureparse event. As -the feature layer can get re-rendered you don't do this directly but rather set -the featureparse function on the recline view. For example, for classic popup -behaviour: - -{% highlight javascript %} -view.featureparse = function (e) { - if (e.properties && e.properties.popupContent) { - e.layer.bindPopup(e.properties.popupContent); - } -}; -{% endhighlight %} - - -### Turning on clustering - -You can turn on clustering of markers by setting the cluster option: - - var map = new recline.View.Map({ - model: dataset - state: { - cluster: true; - } - }); - -You could also enable marker clustering only if you have more than a -certain number of markers. Here's an example: - - // var map is recline.View.Map instance - // marker cluster threshold - var threshold = 65; - - // enable clustering if there is a large number of markers - var countAfter = 0; - map.features.eachLayer(function(){countAfter++;}); - if (countAfter > threshold) { - // note the map will auto-redraw when you change state! - map.state.set({cluster: true}); - } - diff --git a/docs/tutorial-multiview.markdown b/docs/tutorial-multiview.markdown deleted file mode 100644 index 68dba87d..00000000 --- a/docs/tutorial-multiview.markdown +++ /dev/null @@ -1,233 +0,0 @@ ---- -layout: container -title: Library - Multiview Tutorial -recline-deps: true -root: ../ ---- - - - -
    -The full source code along with all dependencies for the tutorial can be found at this GitHub repository. Do not try to assemble a working example from the code snippets in this page! See it in action via GitHub Pages. - The code is almost identical to that used for the site demo, with the advantage of the code being separated out of the main recline website. You can also see Multiview in action in many CKAN-based data catalogs (resource views) and in the OKFN Labs DataDeck
    - -### Multiview - -Multiview, as its name suggests, combines multiple [Recline views](views.html) into one visualization. The different views are synced to one dataset. The [technical documentation for Multiview](src/view.multiview.html) details the nuts and bolts. - -When building a Multiview from scratch, it is advised to start by getting each individual view to work satisfactorily before combining into a Multiview, to aid debugging. - -### Preparing your page - -Before writing any code with Recline, you need to do the following preparation steps on your page: - -* [Download ReclineJS]({{page.root}}download.html) (downloading the master, developer code is recommended as this example is based on that and all dependencies should be available) or the all-in-one demo code. -* Include the Multiview CSS as well as the CSS for each view in the head section of your document, as well as any 3rd party CSS for each view e.g.: - {% highlight html %} - - - - - - - - - - - - - - - {% endhighlight %} - -* Include the relevant Javascript files somewhere on the page (preferably before body close tag). You will need to include any necessary Javascript dependencies for each view as well, e.g.: - {% highlight html %} - - - - - - - - - - - - - - - - - - - - - - - - - - {% endhighlight %} - -### Creating a Dataset - -Here's the function to create an example dataset we are going to work with: - -{% highlight javascript %} -function createDemoDataset() { - var dataset = new recline.Model.Dataset({ - records: [ - {id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', sometext: 'first', lat:52.56, lon:13.40}, - {id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', sometext: 'second', lat:54.97, lon:-1.60}, - {id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', sometext: 'third', lat:40.00, lon:-75.5}, - {id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', sometext: 'fourth', lat:57.27, lon:-6.20}, - {id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', sometext: 'fifth', lat:51.58, lon:0}, - {id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', sometext: 'sixth', lat:51.04, lon:7.9} - ], - // let's be really explicit about fields - // Plus take opportunity to set date to be a date field and set some labels - fields: [ - {id: 'id'}, - {id: 'date', type: 'date'}, - {id: 'x', type: 'number'}, - {id: 'y', type: 'number'}, - {id: 'z', type: 'number'}, - {id: 'country', 'label': 'Country'}, - {id: 'sometext', 'label': 'Some text'}, - {id: 'lat'}, - {id: 'lon'} - ] - }); - return dataset; -} -{% endhighlight %} - -In this data we have 6 documents / rows. Each document is a javascript object -containing keys and values (note that all values here are 'simple' but there is -no reason you cannot have objects as values allowing you to nest data. - -### Setting up the Multiview - -To create a Multiview, we first create each view that we want to include, and include these in an array. A function to do everything for SlickGrid, Graph and Map views is shown below: - -{% highlight javascript %} -var createMultiView = function(dataset, state) { - // remove existing multiview if present - var reload = false; - if (window.multiView) { - window.multiView.remove(); - window.multiView = null; - reload = true; - } - - var $el = $('
    '); - $el.appendTo(window.explorerDiv); - - // customize the subviews for the MultiView - var views = [ - { - id: 'grid', - label: 'Grid', - view: new recline.View.SlickGrid({ - model: dataset, - state: { - gridOptions: { - editable: true, - // Enable support for row add - enabledAddRow: true, - // Enable support for row delete - enabledDelRow: true, - // Enable support for row ReOrder - enableReOrderRow:true, - autoEdit: false, - enableCellNavigation: true - }, - columnsEditor: [ - { column: 'date', editor: Slick.Editors.Date }, - { column: 'sometext', editor: Slick.Editors.Text } - ] - } - }) - }, - { - id: 'graph', - label: 'Graph', - view: new recline.View.Graph({ - model: dataset - - }) - }, - { - id: 'map', - label: 'Map', - view: new recline.View.Map({ - model: dataset - }) - } - ]; - - var multiView = new recline.View.MultiView({ - model: dataset, - el: $el, - state: state, - views: views - }); - return multiView; -} -{% endhighlight %} - -To tie it all together: -{% highlight javascript %} -jQuery(function($) { - window.multiView = null; - window.explorerDiv = $('.data-explorer-here'); - - // create the demo dataset - var dataset = createDemoDataset(); - // now create the multiview - // this is rather more elaborate than the minimum as we configure the - // MultiView in various ways (see function below) - window.multiview = createMultiView(dataset); - - // last, we'll demonstrate binding to changes in the dataset - // this will print out a summary of each change onto the page in the - // changelog section - dataset.records.bind('all', function(name, obj) { - var $info = $('
    '); - $info.html(name + ': ' + JSON.stringify(obj.toJSON())); - $('.changelog').append($info); - $('.changelog').show(); - }); -}); -{% endhighlight %} - -The HTML is very simple: -{% highlight html %} -
    - - -
    -

    Changes

    -
    - -
    -
    -
    -{% endhighlight %} diff --git a/docs/tutorial-timelines.markdown b/docs/tutorial-timelines.markdown deleted file mode 100644 index d6222e6c..00000000 --- a/docs/tutorial-timelines.markdown +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: container -title: Timelines - Advanced use of timelines in Recline - Tutorial -recline-deps: true -root: ../ ---- - - - -### Preparing your page - -See the instructions in the [basic views tutorial](tutorial-views.html). - -### Creating a Dataset - -Just like in the main tutorial, here's some example data We are going to work with: - -{% highlight javascript %} -{% include data.js %} -var dataset = new recline.Model.Dataset({ - records: data -}); -{% endhighlight %} - - - -### Underlying library and Recline extensions - - - -### Handling dates - - - -### Customizing the rendering - - - diff --git a/docs/tutorial-views.markdown b/docs/tutorial-views.markdown deleted file mode 100644 index 9e519841..00000000 --- a/docs/tutorial-views.markdown +++ /dev/null @@ -1,316 +0,0 @@ ---- -layout: container -title: Library - Example - Quickstart -recline-deps: true -root: ../ ---- - - - -### Views - -Views display Recline datasets in different ways. This page covers the interesting built-in views. For a full list of views including extensions outside of the Recline.js core, see the [list of currently available views]({{page.root}}docs/views.html#dataset-views-currently-available). - -### Preparing your page - -Before writing any code with Recline, you need to do the following preparation steps on your page: - -* [Download ReclineJS]({{page.root}}download.html) and relevant dependencies. -* Include the relevant CSS in the head section of your document (for view-specific CSS files, see below): - {% highlight html %} - - -{% endhighlight %} - -* Include the relevant Javascript files somewhere on the page (preferably before body close tag; for view-specific Javascript dependencies, see below): - {% highlight html %} - - - - - - - - -{% endhighlight %} - -You're now ready to start working with Recline. - -### Creating a Dataset - -Here's some example data we are going to work with: - -{% highlight javascript %} -{% include data.js %} -{% endhighlight %} - -In this data we have 6 documents / rows. Each document is a javascript object -containing keys and values (note that all values here are 'simple' but there is -no reason you cannot have objects as values allowing you to nest data. - -We can now create a recline Dataset object (and memory backend) from this raw data: - -{% highlight javascript %} -var dataset = new recline.Model.Dataset({ - records: data -}); - -//Depending on the view, it may be important to set the date type -dataset.fields.models[1].attributes.type = 'date'; -{% endhighlight %} - -### Setting up the Grid - -
    -The source code along with all dependencies for the grid part of the tutorial can be found at this GitHub repository. See it in action via GitHub Pages. -Although it's not demonstrated here, you can also use the simpler Grid view without SlickGrid. Source code along with all dependencies for that can be found at this GitHub repository. Demo on GitHub Pages. -
    - -Let's create a data grid view to display the dataset we have just created. We're going to use the SlickGrid-based grid so we need the following CSS and JS dependencies in addition to those above: - -{% highlight html %} - - - - - - - - - - - - - - - - -{% endhighlight %} - -Now, let's create an HTML element to for the Grid: - -{% highlight html %} -
    -{% endhighlight %} - -Now let's set up the Grid: - -{% highlight javascript %} -var $el = $('#mygrid'); -var grid = new recline.View.SlickGrid({ - model: dataset, - el: $el -}); -grid.visible = true; -grid.render(); -{% endhighlight %} - -And hey presto: - -
     
    - - - -### Creating a Graph - -
    -The source code along with all dependencies for the graph part of the tutorial can be found at this GitHub repository. See it in action via GitHub Pages. -
    - -Let's create a graph view to display a line graph for this dataset. - -First, add the additional dependencies for this view. These are the Flot -library and the Recline Flot Graph view: - -{% highlight html %} - - - - - - - - - - - - -{% endhighlight %} - -Next, create a new div for the graph: - -{% highlight html %} -
    -{% endhighlight %} - -Now let's create the graph, we will use the same dataset we had earlier, and we will need to set the view 'state' in order to configure the graph with the column to use for the x-axis ("group") and the columns to use for the series to show ("series"). - -
    -State: The concept of a state is a common feature of Recline views being an object -which stores information about the state and configuration of a given view. You -can read more about it in the general Views -documentation as well as the documentation of individual views such as the -Graph View. -
    - -{% highlight javascript %} -var $el = $('#mygraph'); -var graph = new recline.View.Graph({ - model: dataset, - state: { - graphType: "lines-and-points", - group: "date", - series: ["y", "z"] - } -}); -$el.append(graph.el); -graph.render(); -graph.redraw(); -{% endhighlight %} - -For the axis date formatting to work, it is crucial that the date type is set for that field as shown in the code concerning the dataset above. The result is the following graph: - -
     
    - - - -### Creating a Map - -
    -The source code along with all dependencies for the map part of the tutorial can be found at this GitHub repository. See it in action via GitHub Pages. -
    - -Now, let's create a map of this dataset using the lon/lat information which is -present on these data points. - -First, add the additional dependencies for the map view. These are the Leaflet -library and the Recline Map view: - -{% highlight html %} - - - - - - - - - - - - - -{% endhighlight %} - -Now, create a new div for the map: - -{% highlight html %} -
    -{% endhighlight %} - -Now let's create the map, we will use the existing dataset object created -previously: - -{% highlight javascript %} -var $el = $('#mymap'); -var map = new recline.View.Map({ - model: dataset -}); -$el.append(map.el); -map.render(); -{% endhighlight %} - -
     
    - - - -### Creating a Timeline - -
    -The source code along with all dependencies for the timeline part of the tutorial can be found at this GitHub repository. See it in action via GitHub Pages. -
    - -Now, let's create a timeline for this dataset using the date information which is -present on these data points. - -First, add the additional dependencies for the timeline view. The timeline is built on the excellent Verite Timeline widget so that library is the key one for this view: - -{% highlight html %} - - - - - - - -{% endhighlight %} - -Now, create a new div for the map (must have an explicit height for the timeline to render): - -{% highlight html %} - -
    -{% endhighlight %} - -Now let's create the timeline, we will use the existing dataset object created -previously: - -{% highlight javascript %} -{% include tutorial-views-timeline.js %} -{% endhighlight %} - - -
    -
    - - - diff --git a/docs/tutorials.html b/docs/tutorials.html deleted file mode 100644 index 35dae7df..00000000 --- a/docs/tutorials.html +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: container -title: Tutorials -root: ../ ---- - - - -

    Basics – Using the Dataset and its Friends

    -
    -
    -
    -
    - -
    - - -
    -
    - -

    Backends – Loading and Storing Data from Remote Sources

    -
    - - -

    Views – visualize data

    -
    - diff --git a/docs/views.markdown b/docs/views.markdown deleted file mode 100644 index d29220e5..00000000 --- a/docs/views.markdown +++ /dev/null @@ -1,198 +0,0 @@ ---- -layout: container -title: Library - Views -root: ../ ---- - - - -Recline Views are instances of Backbone Views and they act as 'WUI' (web user -interface) component displaying some model object in the DOM. Like all Backbone -views they have a pointer to a model (or a collection) and have an associated -DOM-style element (usually this element will be bound into the page at some -point). - -
    Looking for quickstart tutorial rather than reference documentation? See the Views Tutorial.
    - - -Views provided by core Recline are crudely divided into two types: - -* Dataset Views: a View intended for displaying a recline.Model.Dataset in some - fashion. Examples are the Grid, Graph and Map views. -* Widget Views: a widget used for displaying some specific (and smaller) aspect - of a dataset or the application. Examples are QueryEditor and FilterEditor - which both provide a way for editing (a part of) a `recline.Model.Query` - associated to a Dataset. - -## Dataset Views currently available -{% include views-list.html %} - -## Dataset View - -These views are just Backbone views with a few additional conventions: - -1. The model passed to the View should always be a recline.Model.Dataset - instance -2. Views should generate their own root element rather than having it passed - in. -3. Views should apply a css class named 'recline-{view-name-lower-cased} to the - root element (and for all CSS for this view to be qualified using this CSS - class) -4. Read-only mode: CSS for this view should respect/utilize a parent - recline-read-only class in order to trigger read-only behaviour (this class - will usually be set on some parent element of the view's root element). -5. State: state (configuration) information for the view should be stored on an - attribute named state that is an instance of a Backbone Model (or, more - speficially, be an instance of `recline.Model.ObjectState`). In addition, a - state attribute may be specified in the Hash passed to a View on - iniitialization and this information should be used to set the initial state - of the view. - - Example of state would be the set of fields being plotted in a graph view. - - More information about State can be found below. - -To summarize some of this, the initialize function for a Dataset View should -look like: - -
    -   initialize: {
    -       model: {a recline.Model.Dataset instance}
    -       // el: {do not specify - instead view should create}
    -       state: {(optional) Object / Hash specifying initial state}
    -       ...
    -   }
    -
    - -Note: Dataset Views in core Recline have a common layout on disk as follows, -where ViewName is the named of View class: - -
    -src/view-{lower-case-ViewName}.js
    -css/{lower-case-ViewName}.css
    -test/view-{lower-case-ViewName}.js
    -
    - -### State - -State information exists in order to support state serialization into the url -or elsewhere and reloading of application from a stored state. - -State is available not only for individual views (as described above) but for -the dataset (e.g. the current query). For an example of pulling together state -from across multiple components see `recline.View.DataExplorer`. - -### Flash Messages / Notifications - -To send 'flash messages' or notifications the convention is that views should -fire an event named `recline:flash` with a payload that is a flash object with -the following attributes (all optional): - -* message: message to show. -* category: warning (default), success, error -* persist: if true alert is persistent, o/w hidden after 3s (default=false) -* loader: if true show a loading message - -Objects or views wishing to bind to flash messages may then subscribe to these -events and take some action such as displaying them to the user. For an example -of such behaviour see the DataExplorer view. - -### Writing your own Views - -See the existing Views. - -## Internationalization - -### Adding translations to templates - -Internationalization is implemented with help of [intl-messageformat](https://www.npmjs.com/package/intl-messageformat) library and supports [ICU Message syntax](http://userguide.icu-project.org/formatparse/messages). - -Translation keys are using Mustache tags prefixed by `t.`. You can specify translated strings in two ways: - -1. Simple text with no variables is rendered using Mustache variable-tag -```javascript -$template = '{{ "{{t.Add_row"}}}}'; // will show "Add row" in defaultLocale (English) -``` - -2. Text with variables or special characters is rendered using Mustache section-tag - -```javascript -$template = '{{ "{{#t.desc"}}}}Add first_row field{{ "{{/t.desc"}}}}'; -// using special chars; will show "Add first_row field" in defaultLocale - -$template = '{{ "{{#t.num_records"}}}}{recordCount, plural, =0 {no records} =1{# record} other {# records}}{{ "{{/t.num_records"}}}}'; -// will show "no records", "1 record" or "x records" in defaultLocale (English) -``` - -When using section-tags in existing templates be sure to remove a bracket from variables inside sections: -```javascript -#template___notranslation = '{{ "{{recordCount"}}}} records'; -#template_withtranslation = '{{ "{{#t.num_records"}}}} {recordCount} records {{ "{{/t.num_records"}}}}'; -``` - - -Then setup Mustache to use translation by injecting tranlation tags in `render` function: - -```javascript -// ============== BEFORE =================== - -my.MultiView = Backbone.View.extend({ - render: function() { - var tmplData = this.model.toTemplateJSON(); - var output = Mustache.render(this.template, tmplData); - ... - } -}); - -// ============== AFTER ==================== - -my.MultiView = Backbone.View.extend({ - render: function() { - var tmplData = this.model.toTemplateJSON(); - tmplData = I18nMessages('recline', recline.View.translations).injectMustache(tmplData); // inject Moustache formatter - var output = Mustache.render(this.template, tmplData); - ... - } -}); -``` - -### Language resolution - -By default the language is detected from the root `lang` attributes - ` and ``. - -If you want to override this functionality then override `I18nMessages.languageResolver` with your implementation. - -```html - - -``` - -Libraries can also ask for specific language: `I18nMessages('recline', recline.View.translations, 'pl')`. Language resolver is not used in that case. - -If you're creating templates using default language other than English (why?) set appropriately appHardcodedLocale: `I18nMessages('recline', recline.View.translations, undefined, 'pl')`. Then missing strings won't be reported in console and underscores in simple translations will be converted to spaces. - -### Adding new language - -Create a copy of `src/i18n/pl.js` and translate all the keys. Long English messages can be found in `en.js`. - -### Overriding defined messages - -If you want to override translations from provided locale do it in a script tag after including recline: - -```html - - -``` \ No newline at end of file diff --git a/download.markdown b/download.markdown deleted file mode 100644 index 049e2e6d..00000000 --- a/download.markdown +++ /dev/null @@ -1,112 +0,0 @@ ---- -layout: container -title: Download ---- - - - -

    Latest Code - Master

    -

    The tutorials on this website will usually be based on the latest (master) codebase. It should also be very stable.

    - - -

    Most Recent Official Release – v0.5

    - - -

    After downloading recline you'll want to use it in your project -- see below or tutorials for details.

    - -### Changelog - -[View Changelog on Github](https://github.com/okfn/recline#changelog) - -### Dependencies - -Recline has dependencies on some third-party libraries. Specifically, recline.dataset.js depends on: - -* [Underscore](http://documentcloud.github.com/underscore/) >= 1.0 -* [Underscore Deferred](https://github.com/wookiehangover/underscore.deferred) v0.4.0 -* [Backbone](http://backbonejs.org/) >= 0.5.1 - -Those backends which utilize jquery's ajax method depend on jQuery: - -* [JQuery](http://jquery.com/) >= 1.6 - -All the views require, in addition to those needed for recline.dataset.js: - -* [JQuery](http://jquery.com/) >= 1.6 -* [Mustache.js](https://github.com/janl/mustache.js/) >= 0.5.0-dev (required for all views) - -Individual views have additional dependencies such as: - -* [JQuery Flot](http://www.flotcharts.org/) >= 0.7 (required for for graph view) -* [Leaflet](http://leaflet.cloudmade.com/) >= 0.4.4 (required for map view) -* [Leaflet.markercluster](https://github.com/danzel/Leaflet.markercluster) as of 2012-09-12 (required for marker clustering) -* [Verite Timeline](https://github.com/VeriteCo/Timeline/) as of 2012-05-02 (required for the timeline view) -* [Bootstrap](http://twitter.github.com/bootstrap/) >= v3 (default option for CSS and UI JS but you can use your own) - -If you grab the full zipball for Recline this will include all of the relevant -dependencies in the vendor directory and you can also find them at in the -[github repo here](https://github.com/okfn/recline/tree/master/vendor). - -### Example - -Here is an example of the page setup for an app using every Recline component: - -{% highlight html %} - - - - - -{% include recline-deps.html %} -{% endhighlight %} - diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index a1f8bfaeabd1af8d6260709345ef9c84d62525e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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�-@qXQ{w7-Nv%%oF8vG4^9m(nD zX3Jy8jyg0p9&*Xd%AT++c*~a1u&4#hgL!G{oMpXw^{Re#b&cQ9u`@3Bd+mK1Ic8$@ zs@372uUVTkZ{v4qb2n|yoV{gh)`!4sj(-=D{>j?lq|er^Px%MOfjL6jy5!H6uZ&yp z^}-$B%$vVu?Se1YtsFUKQf>bcAJD+zQ?9~S!=GhT`!%o}tfPw;FB!id6dkQ?5J5e4 zzk&VkL)>K(@D^M6RM0hy!JGdy&|3mq><*eVIVAD?`3t6+I$-`SE$5{pM`bJYBt@ic z5K3CQ{Jx2|7Gw6^cv9aM1!PYK?>XST740d4jXJjM?AdcNP5!L^)2A<34H;Hgqi<9U zyT^9$p95QBJnXMetbdt45^o z#@HTTPZ2q|rnOY6jt=a~zgTf@xZJ+@s@Sv;G}oYf2;^lS7uX^Mu#Z!rK!fQ# z7@Q9n6$tg%&xrjTv|;PehQElRNdatdnb!&nkGM=*w##8t&VxNL=T21gz7M+cU%T$m0`z0Mu?#XxR-o)I*xcBM<>?_djmJD# zjxhq;h^(M}`HHe7UHJ!1S0$Lh-^IiNzDy=7U}s`m4ckXmPY1TmynU43oOldxmdEKYe-K=U>oSd@qw2tW33#VN zmJ0Iizw?+T|K`)zbOBDr|0V0$b_zola>_=I}&z#FD6{E74ufn)%3>ZNvqQPNVk=ZycN#U-mOZ9;BKtwKp=m5eupO1v%9 zlQ-T;>Ha= zw?t?Izqt$?wx8Vl;0;=i_a*Q$mv6Z9udfT&`3^{Jwu61n*|QYy0X0H7zaekI@X>Ri!Yz%3*cVqn?gtxrGwRk7lBfz&a8H}3i@~*>ShRY*wLd7?`{V` z`v7!-Hy^K3yanZxk6%hlR?f+H-kVmV7&fZ_UQ1Bf@Hm4_UhcRX<2VhrX!sz)2kCJkktU*UE@)s3!p=Wn@RnNGtsaB_ zPqN`}WUB1ld5o7se!QR9MJP`o!iuWK-*=UR?`wQ- z( z(OsgZpXi=`GprZCgKkP#M>Me*<^_hR^N-`O<;Wif*n0v5hMoV|PQhjgYv8_}0>3ru z`iJ6zEr7=a{M3HeF2MJBzTa|Lbv;gFJMPT6OBCC~cg|8O>jP9(4gQQ$#LLR}2UJuZ z99UFbH!L-^c>KnY@Xw}wys)&Tr3L%MJ;9GYBBJ&KZG#=SO9kUh!oLN8uh9d6JFNan zAe_R&%VwzW+H2SCU!W(!8?hmu?@ItB!2Y}(XK9x@*VsTAg`BS754w_ntKY*emo5aK04~fwaZYK^XkAaBs0qf^SF}Z3sEIXwa)^N9^!+(r@ry ztYL{2RFU zb$*-4)3EPlQSkcG&(!tQ;g-;YbM4$pan8iq1n;a5gngEHq)$ diff --git a/images/drag-handle.png b/images/drag-handle.png deleted file mode 100644 index ad7531cf046278081efe4b36e4f32d9e99bcc108..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1130 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$3?vg*uer*=z|0Wf6XFWwPMbFE#*G_5#wZvK zfsqyh&z7{`VPIfnC<*cl{y(w-L-2QYRbc!}db&7Hn@mvv4FO#mmc9hm?C diff --git a/images/logo.png b/images/logo.png deleted file mode 100644 index 423f84eddfb5be89d5b6ddd6ab9f83380fe4234e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45836 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 RG!g;iM@>mfu}&Tu{y)j&fLs6o diff --git a/images/screenshot-1.jpg b/images/screenshot-1.jpg deleted file mode 100644 index eddf1cea1f72a2a35e169dcd2be7444e50f0ab27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84532 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

    6A#>45_eswg=Q%o_nCKFPe$<*G? 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 O{#>!`I|GhynEwKnyY@x^ diff --git a/index.html b/index.html deleted file mode 100644 index aea40126..00000000 --- a/index.html +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: default -title: Home ---- - -

    - diff --git a/make b/make deleted file mode 100755 index a01a23e4..00000000 --- a/make +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -import sys -import shutil -import os - -def cat(): - print("** Combining js files") - cmd = 'ls src/*.js | grep -v couchdb | xargs cat > dist/recline.js' - os.system(cmd) - cmd = 'ls src/**/*.js | grep -v couchdb | xargs cat >> dist/recline.js' - os.system(cmd) - - cmd = 'cat src/model.js src/backend.memory.js > dist/recline.dataset.js' - os.system(cmd) - - print("** Combining css files") - cmd = 'cat css/*.css > dist/recline.css' - os.system(cmd) - -def docs(): - # build docs - print("** Building docs") - - docco_executable = os.environ.get('DOCCO_EXECUTABLE','docco') - if os.path.exists('/tmp/recline-docs'): - shutil.rmtree('/tmp/recline-docs') - os.makedirs('/tmp/recline-docs') - files = '%s/src/*.js' % os.getcwd() - dest = '%s/docs/src' % os.getcwd() - os.system('cd /tmp/recline-docs && %s %s && mv docs/* %s' % (docco_executable,files, dest)) - print("** Docs built ok") - -def minify(): - cmd = 'uglifyjs -o %s %s' % ('dist/recline.min.js', 'dist/recline.js') - os.system(cmd) - - cmd = 'uglifyjs -o %s %s' % ('dist/recline.dataset.min.js', 'dist/recline.dataset.js') - os.system(cmd) - -if __name__ == '__main__': - if not len(sys.argv) > 1: - print 'make cat | docs | minify | all' - sys.exit(1) - action = sys.argv[1] - if action == 'cat': - cat() - elif action == 'docs': - docs() - elif action == 'minify': - minify() - elif action == 'all': - cat() - docs() - minify() - diff --git a/package.json b/package.json deleted file mode 100644 index 7e6a8058..00000000 --- a/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "reclinejs", - "description": "A simple but powerful data library for building data-oriented applications in pure Javascript", - "version": "0.7.0", - "contributors": [ - { - "name": "Rufus Pollock", - "email": "rufus.pollock@okfn.org" - }, - { - "name": "AdriĆ  Mercader", - "email": "adria.mercader@okfn.org" - }, - { - "name": "Max Ogden", - "email": "max@maxogden.com" - }, - { - "name": "Krzysztof Madejski", - "email": "krzysztof.madejski@epf.org.pl" - } - ], - "dependencies": { - "backbone": ">=0.5", - "jquery": ">=1.6", - "mustache": ">=0.5.2", - "underscore": ">=1.0", - "intl-messageformat": "1.3.x" - }, - "homepage": "http://reclinejs.com/", - "keywords": [ - "app", - "data", - "explorer", - "grid", - "library", - "table" - ], - "lib": "src", - "main": "dist/recline.js", - "repository": { - "type": "git", - "url": "http://github.com/okfn/recline.git", - "path": "src" - } -} diff --git a/src/backend.dataproxy.js b/src/backend.dataproxy.js deleted file mode 100644 index 92b5ae7c..00000000 --- a/src/backend.dataproxy.js +++ /dev/null @@ -1,76 +0,0 @@ -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; - -(function(my) { - "use strict"; - my.__type__ = 'dataproxy'; - // URL for the dataproxy - my.dataproxy_url = '//jsonpdataproxy.appspot.com'; - // Timeout for dataproxy (after this time if no response we error) - // Needed because use JSONP so do not receive e.g. 500 errors - my.timeout = 5000; - - - // use either jQuery or Underscore Deferred depending on what is available - var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - - // ## load - // - // Load data from a URL via the [DataProxy](http://github.com/okfn/dataproxy). - // - // Returns array of field names and array of arrays for records - my.fetch = function(dataset) { - var data = { - url: dataset.url, - 'max-results': dataset.size || dataset.rows || 1000, - type: dataset.format || '' - }; - var jqxhr = jQuery.ajax({ - url: my.dataproxy_url, - data: data, - dataType: 'jsonp' - }); - var dfd = new Deferred(); - _wrapInTimeout(jqxhr).done(function(results) { - if (results.error) { - dfd.reject(results.error); - } - - dfd.resolve({ - records: results.data, - fields: results.fields, - useMemoryStore: true - }); - }) - .fail(function(args) { - dfd.reject(args); - }); - return dfd.promise(); - }; - - // ## _wrapInTimeout - // - // Convenience method providing a crude way to catch backend errors on JSONP calls. - // Many of backends use JSONP and so will not get error messages and this is - // a crude way to catch those errors. - var _wrapInTimeout = function(ourFunction) { - var dfd = new Deferred(); - var timer = setTimeout(function() { - dfd.reject({ - message: 'Request Error: Backend did not respond after ' + (my.timeout / 1000) + ' seconds' - }); - }, my.timeout); - ourFunction.done(function(args) { - clearTimeout(timer); - dfd.resolve(args); - }) - .fail(function(args) { - clearTimeout(timer); - dfd.reject(args); - }) - ; - return dfd.promise(); - }; - -}(this.recline.Backend.DataProxy)); diff --git a/src/backend.memory.js b/src/backend.memory.js deleted file mode 100644 index 8cb64ddf..00000000 --- a/src/backend.memory.js +++ /dev/null @@ -1,245 +0,0 @@ -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.Memory = this.recline.Backend.Memory || {}; - -(function(my) { - "use strict"; - my.__type__ = 'memory'; - - // private data - use either jQuery or Underscore Deferred depending on what is available - var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - - // ## Data Wrapper - // - // Turn a simple array of JS objects into a mini data-store with - // functionality like querying, faceting, updating (by ID) and deleting (by - // ID). - // - // @param records list of hashes for each record/row in the data ({key: - // value, key: value}) - // @param fields (optional) list of field hashes (each hash defining a field - // as per recline.Model.Field). If fields not specified they will be taken - // from the data. - my.Store = function(records, fields) { - var self = this; - this.records = records; - // backwards compatability (in v0.5 records was named data) - this.data = this.records; - if (fields) { - this.fields = fields; - } else { - if (records) { - this.fields = _.map(records[0], function(value, key) { - return {id: key, type: 'string'}; - }); - } - } - - this.update = function(doc) { - _.each(self.records, function(internalDoc, idx) { - if(doc.id === internalDoc.id) { - self.records[idx] = doc; - } - }); - }; - - this.remove = function(doc) { - var newdocs = _.reject(self.records, function(internalDoc) { - return (doc.id === internalDoc.id); - }); - this.records = newdocs; - }; - - this.save = function(changes, dataset) { - var self = this; - var dfd = new Deferred(); - // TODO _.each(changes.creates) { ... } - _.each(changes.updates, function(record) { - self.update(record); - }); - _.each(changes.deletes, function(record) { - self.remove(record); - }); - dfd.resolve(); - return dfd.promise(); - }, - - this.query = function(queryObj) { - var dfd = new Deferred(); - var numRows = queryObj.size || this.records.length; - var start = queryObj.from || 0; - var results = this.records; - - results = this._applyFilters(results, queryObj); - results = this._applyFreeTextQuery(results, queryObj); - - // TODO: this is not complete sorting! - // What's wrong is we sort on the *last* entry in the sort list if there are multiple sort criteria - _.each(queryObj.sort, function(sortObj) { - var fieldName = sortObj.field; - results = _.sortBy(results, function(doc) { - var _out = doc[fieldName]; - return _out; - }); - if (sortObj.order == 'desc') { - results.reverse(); - } - }); - var facets = this.computeFacets(results, queryObj); - var out = { - total: results.length, - hits: results.slice(start, start+numRows), - facets: facets - }; - dfd.resolve(out); - return dfd.promise(); - }; - - // in place filtering - this._applyFilters = function(results, queryObj) { - var filters = queryObj.filters; - // register filters - var filterFunctions = { - term : term, - terms : terms, - range : range, - geo_distance : geo_distance - }; - var dataParsers = { - integer: function (e) { return parseFloat(e, 10); }, - 'float': function (e) { return parseFloat(e, 10); }, - number: function (e) { return parseFloat(e, 10); }, - string : function (e) { return e.toString(); }, - date : function (e) { return moment(e).valueOf(); }, - datetime : function (e) { return new Date(e).valueOf(); } - }; - var keyedFields = {}; - _.each(self.fields, function(field) { - keyedFields[field.id] = field; - }); - function getDataParser(filter) { - var fieldType = keyedFields[filter.field].type || 'string'; - return dataParsers[fieldType]; - } - - // filter records - return _.filter(results, function (record) { - var passes = _.map(filters, function (filter) { - return filterFunctions[filter.type](record, filter); - }); - - // return only these records that pass all filters - return _.all(passes, _.identity); - }); - - // filters definitions - function term(record, filter) { - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var term = parse(filter.term); - - return (value === term); - } - - function terms(record, filter) { - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var terms = parse(filter.terms).split(","); - - return (_.indexOf(terms, value) >= 0); - } - - function range(record, filter) { - var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === ''); - var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === ''); - var parse = getDataParser(filter); - var value = parse(record[filter.field]); - var from = parse(fromnull ? '' : filter.from); - var to = parse(tonull ? '' : filter.to); - - // if at least one end of range is set do not allow '' to get through - // note that for strings '' <= {any-character} e.g. '' <= 'a' - if ((!fromnull || !tonull) && value === '') { - return false; - } - return ((fromnull || value >= from) && (tonull || value <= to)); - } - - function geo_distance() { - // TODO code here - } - }; - - // we OR across fields but AND across terms in query string - this._applyFreeTextQuery = function(results, queryObj) { - if (queryObj.q) { - var terms = queryObj.q.split(' '); - var patterns=_.map(terms, function(term) { - return new RegExp(term.toLowerCase()); - }); - results = _.filter(results, function(rawdoc) { - var matches = true; - _.each(patterns, function(pattern) { - var foundmatch = false; - _.each(self.fields, function(field) { - var value = rawdoc[field.id]; - if ((value !== null) && (value !== undefined)) { - value = value.toString(); - } else { - // value can be null (apparently in some cases) - value = ''; - } - // TODO regexes? - foundmatch = foundmatch || (pattern.test(value.toLowerCase())); - // TODO: early out (once we are true should break to spare unnecessary testing) - // if (foundmatch) return true; - }); - matches = matches && foundmatch; - // TODO: early out (once false should break to spare unnecessary testing) - // if (!matches) return false; - }); - return matches; - }); - } - return results; - }; - - this.computeFacets = function(records, queryObj) { - var facetResults = {}; - if (!queryObj.facets) { - return facetResults; - } - _.each(queryObj.facets, function(query, facetId) { - // TODO: remove dependency on recline.Model - facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON(); - facetResults[facetId].termsall = {}; - }); - // faceting - _.each(records, function(doc) { - _.each(queryObj.facets, function(query, facetId) { - var fieldId = query.terms.field; - var val = doc[fieldId]; - var tmp = facetResults[facetId]; - if (val) { - tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1; - } else { - tmp.missing = tmp.missing + 1; - } - }); - }); - _.each(queryObj.facets, function(query, facetId) { - var tmp = facetResults[facetId]; - var terms = _.map(tmp.termsall, function(count, term) { - return { term: term, count: count }; - }); - tmp.terms = _.sortBy(terms, function(item) { - // want descending order - return -item.count; - }); - tmp.terms = tmp.terms.slice(0, 10); - }); - return facetResults; - }; - }; - -}(this.recline.Backend.Memory)); diff --git a/src/ecma-fixes.js b/src/ecma-fixes.js deleted file mode 100644 index de8148be..00000000 --- a/src/ecma-fixes.js +++ /dev/null @@ -1,67 +0,0 @@ -// This file adds in full array method support in browsers that don't support it -// see: http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc - -// Add ECMA262-5 Array methods if not supported natively -if (!('indexOf' in Array.prototype)) { - Array.prototype.indexOf= function(find, i /*opt*/) { - if (i===undefined) i= 0; - if (i<0) i+= this.length; - if (i<0) i= 0; - for (var n= this.length; ithis.length-1) i= this.length-1; - for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */ - if (i in this && this[i]===find) - return i; - return -1; - }; -} -if (!('forEach' in Array.prototype)) { - Array.prototype.forEach= function(action, that /*opt*/) { - for (var i= 0, n= this.length; iWitamy! \ -

    Jakie kolumny powinny zostać narysowane na wykresie?

    \ -

    Wybierz je używając menu po prawej, a wykres pojawi się automatycznie.

    ', - Graph_Type: 'Typ wykresu', - Lines_and_Points: 'Linie z punktami', - Lines: 'Linie', - Points: 'Punkty', - Bars: 'Słupki poziome', - Columns: 'Słupki', - flot_Group_Column: 'Kolumna (Oś X)', - Please_choose: 'Proszę wybrać', - Remove: 'Usuń', - Series: 'Seria', - Axis_2: 'Oś Y', - Add_Series: 'Dodaj serię danych', - Save: 'Zapisz', - - map_mapping: 'Źródło koordynatów', - map_mapping_lat_lon: 'Szerokość i długość geo.', - map_mapping_geojson: 'Jedna kolumna typu GeoJSON', - Latitude_field: 'Kolumna szerokości geo. (WGS84)', - Longitude_field: 'Kolumna długości geo. (WGS84)', - Auto_zoom_to_features: 'Kadruj, aby pokazać wszytkie punkty', - Cluster_markers: 'Łącz pobliskie punkty w grupy', - - num_records: '{recordCount} rekordów' -}; \ No newline at end of file diff --git a/src/model.js b/src/model.js deleted file mode 100644 index 1c383b61..00000000 --- a/src/model.js +++ /dev/null @@ -1,651 +0,0 @@ -// # Recline Backbone Models -this.recline = this.recline || {}; -this.recline.Model = this.recline.Model || {}; - -(function(my) { - "use strict"; - -// use either jQuery or Underscore Deferred depending on what is available -var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; - -// ## Dataset -my.Dataset = Backbone.Model.extend({ - constructor: function Dataset() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // ### initialize - initialize: function() { - var self = this; - _.bindAll(this, 'query'); - this.backend = null; - if (this.get('backend')) { - this.backend = this._backendFromString(this.get('backend')); - } else { // try to guess backend ... - if (this.get('records')) { - this.backend = recline.Backend.Memory; - } - } - this.fields = new my.FieldList(); - this.records = new my.RecordList(); - this._changes = { - deletes: [], - updates: [], - creates: [] - }; - this.facets = new my.FacetList(); - this.recordCount = null; - this.queryState = new my.Query(); - this.queryState.bind('change facet:add', function () { - self.query(); // We want to call query() without any arguments. - }); - // store is what we query and save against - // store will either be the backend or be a memory store if Backend fetch - // tells us to use memory store - this._store = this.backend; - - // if backend has a handleQueryResultFunction, use that - this._handleResult = (this.backend != null && _.has(this.backend, 'handleQueryResult')) ? - this.backend.handleQueryResult : this._handleQueryResult; - if (this.backend == recline.Backend.Memory) { - this.fetch(); - } - }, - - sync: function(method, model, options) { - return this.backend.sync(method, model, options); - }, - - // ### fetch - // - // Retrieve dataset and (some) records from the backend. - fetch: function() { - var self = this; - var dfd = new Deferred(); - - if (this.backend !== recline.Backend.Memory) { - this.backend.fetch(this.toJSON()) - .done(handleResults) - .fail(function(args) { - dfd.reject(args); - }); - } else { - // special case where we have been given data directly - handleResults({ - records: this.get('records'), - fields: this.get('fields'), - useMemoryStore: true - }); - } - - function handleResults(results) { - // if explicitly given the fields - // (e.g. var dataset = new Dataset({fields: fields, ...}) - // use that field info over anything we get back by parsing the data - // (results.fields) - var fields = self.get('fields') || results.fields; - - var out = self._normalizeRecordsAndFields(results.records, fields); - if (results.useMemoryStore) { - self._store = new recline.Backend.Memory.Store(out.records, out.fields); - } - - self.set(results.metadata); - self.fields.reset(out.fields); - self.query() - .done(function() { - dfd.resolve(self); - }) - .fail(function(args) { - dfd.reject(args); - }); - } - - return dfd.promise(); - }, - - // ### _normalizeRecordsAndFields - // - // Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects - // - // e.g. fields = ['a', 'b', 'c'] and records = [ [1,2,3] ] => - // fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}] - _normalizeRecordsAndFields: function(records, fields) { - // if no fields get them from records - if (!fields && records && records.length > 0) { - // records is array then fields is first row of records ... - if (records[0] instanceof Array) { - fields = records[0]; - records = records.slice(1); - } else { - fields = _.map(_.keys(records[0]), function(key) { - return {id: key}; - }); - } - } - - // fields is an array of strings (i.e. list of field headings/ids) - if (fields && fields.length > 0 && (fields[0] === null || typeof(fields[0]) != 'object')) { - // Rename duplicate fieldIds as each field name needs to be - // unique. - var seen = {}; - fields = _.map(fields, function(field, index) { - if (field === null) { - field = ''; - } else { - field = field.toString(); - } - // cannot use trim as not supported by IE7 - var fieldId = field.replace(/^\s+|\s+$/g, ''); - if (fieldId === '') { - fieldId = '_noname_'; - field = fieldId; - } - while (fieldId in seen) { - seen[field] += 1; - fieldId = field + seen[field]; - } - if (!(field in seen)) { - seen[field] = 0; - } - // TODO: decide whether to keep original name as label ... - // return { id: fieldId, label: field || fieldId } - return { id: fieldId }; - }); - } - // records is provided as arrays so need to zip together with fields - // NB: this requires you to have fields to match arrays - if (records && records.length > 0 && records[0] instanceof Array) { - records = _.map(records, function(doc) { - var tmp = {}; - _.each(fields, function(field, idx) { - tmp[field.id] = doc[idx]; - }); - return tmp; - }); - } - return { - fields: fields, - records: records - }; - }, - - save: function() { - var self = this; - // TODO: need to reset the changes ... - return this._store.save(this._changes, this.toJSON()); - }, - - // ### query - // - // AJAX method with promise API to get records from the backend. - // - // It will query based on current query state (given by this.queryState) - // updated by queryObj (if provided). - // - // Resulting RecordList are used to reset this.records and are - // also returned. - query: function(queryObj) { - var self = this; - var dfd = new Deferred(); - this.trigger('query:start'); - - if (queryObj) { - var attributes = queryObj; - if (queryObj instanceof my.Query) { - attributes = queryObj.toJSON(); - } - this.queryState.set(attributes, {silent: true}); - } - var actualQuery = this.queryState.toJSON(); - - this._store.query(actualQuery, this.toJSON()) - .done(function(queryResult) { - self._handleResult(queryResult); - self.trigger('query:done'); - dfd.resolve(self.records); - }) - .fail(function(args) { - self.trigger('query:fail', args); - dfd.reject(args); - }); - return dfd.promise(); - }, - - _handleQueryResult: function(queryResult) { - var self = this; - self.recordCount = queryResult.total; - var docs = _.map(queryResult.hits, function(hit) { - var _doc = new my.Record(hit); - _doc.fields = self.fields; - _doc.bind('change', function(doc) { - self._changes.updates.push(doc.toJSON()); - }); - _doc.bind('destroy', function(doc) { - self._changes.deletes.push(doc.toJSON()); - }); - return _doc; - }); - self.records.reset(docs); - if (queryResult.facets) { - var facets = _.map(queryResult.facets, function(facetResult, facetId) { - facetResult.id = facetId; - return new my.Facet(facetResult); - }); - self.facets.reset(facets); - } - }, - - toTemplateJSON: function() { - var data = this.toJSON(); - data.recordCount = this.recordCount; - data.fields = this.fields.toJSON(); - return data; - }, - - // ### getFieldsSummary - // - // Get a summary for each field in the form of a `Facet`. - // - // @return null as this is async function. Provides deferred/promise interface. - getFieldsSummary: function() { - var self = this; - var query = new my.Query(); - query.set({size: 0}); - this.fields.each(function(field) { - query.addFacet(field.id); - }); - var dfd = new Deferred(); - this._store.query(query.toJSON(), this.toJSON()).done(function(queryResult) { - if (queryResult.facets) { - _.each(queryResult.facets, function(facetResult, facetId) { - facetResult.id = facetId; - var facet = new my.Facet(facetResult); - // TODO: probably want replace rather than reset (i.e. just replace the facet with this id) - self.fields.get(facetId).facets.reset(facet); - }); - } - dfd.resolve(queryResult); - }); - return dfd.promise(); - }, - - // Deprecated (as of v0.5) - use record.summary() - recordSummary: function(record) { - return record.summary(); - }, - - // ### _backendFromString(backendString) - // - // Look up a backend module from a backend string (look in recline.Backend) - _backendFromString: function(backendString) { - var backend = null; - if (recline && recline.Backend) { - _.each(_.keys(recline.Backend), function(name) { - if (name.toLowerCase() === backendString.toLowerCase()) { - backend = recline.Backend[name]; - } - }); - } - return backend; - } -}); - - -// ## A Record -// -// A single record (or row) in the dataset -my.Record = Backbone.Model.extend({ - constructor: function Record() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // ### initialize - // - // Create a Record - // - // You usually will not do this directly but will have records created by - // Dataset e.g. in query method - // - // Certain methods require presence of a fields attribute (identical to that on Dataset) - initialize: function() { - _.bindAll(this, 'getFieldValue'); - }, - - // ### getFieldValue - // - // For the provided Field get the corresponding rendered computed data value - // for this record. - // - // NB: if field is undefined a default '' value will be returned - getFieldValue: function(field) { - var val = this.getFieldValueUnrendered(field); - if (field && !_.isUndefined(field.renderer)) { - val = field.renderer(val, field, this.toJSON()); - } - return val; - }, - - // ### getFieldValueUnrendered - // - // For the provided Field get the corresponding computed data value - // for this record. - // - // NB: if field is undefined a default '' value will be returned - getFieldValueUnrendered: function(field) { - if (!field) { - return ''; - } - var val = this.get(field.id); - if (field.deriver) { - val = field.deriver(val, field, this); - } - return val; - }, - - // ### summary - // - // Get a simple html summary of this record in form of key/value list - summary: function(record) { - var self = this; - var html = '
    '; - this.fields.each(function(field) { - if (field.id != 'id') { - html += '
    ' + field.get('label') + ': ' + self.getFieldValue(field) + '
    '; - } - }); - html += '
    '; - return html; - }, - - // Override Backbone save, fetch and destroy so they do nothing - // Instead, Dataset object that created this Record should take care of - // handling these changes (discovery will occur via event notifications) - // WARNING: these will not persist *unless* you call save on Dataset - fetch: function() {}, - save: function() {}, - destroy: function() { this.trigger('destroy', this); } -}); - - -// ## A Backbone collection of Records -my.RecordList = Backbone.Collection.extend({ - constructor: function RecordList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Record -}); - - -// ## A Field (aka Column) on a Dataset -my.Field = Backbone.Model.extend({ - constructor: function Field() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - // ### defaults - define default values - defaults: { - label: null, - type: 'string', - format: null, - is_derived: false - }, - // ### initialize - // - // @param {Object} data: standard Backbone model attributes - // - // @param {Object} options: renderer and/or deriver functions. - initialize: function(data, options) { - // if a hash not passed in the first argument throw error - if ('0' in data) { - throw new Error('Looks like you did not pass a proper hash with id to Field constructor'); - } - if (this.attributes.label === null) { - this.set({label: this.id}); - } - if (this.attributes.type.toLowerCase() in this._typeMap) { - this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()]; - } - if (options) { - this.renderer = options.renderer; - this.deriver = options.deriver; - } - if (!this.renderer) { - this.renderer = this.defaultRenderers[this.get('type')]; - } - this.facets = new my.FacetList(); - }, - _typeMap: { - 'text': 'string', - 'double': 'number', - 'float': 'number', - 'numeric': 'number', - 'int': 'integer', - 'datetime': 'date-time', - 'bool': 'boolean', - 'timestamp': 'date-time', - 'json': 'object' - }, - defaultRenderers: { - object: function(val, field, doc) { - return JSON.stringify(val); - }, - geo_point: function(val, field, doc) { - return JSON.stringify(val); - }, - 'number': function(val, field, doc) { - var format = field.get('format'); - if (format === 'percentage') { - return val + '%'; - } - return val; - }, - 'string': function(val, field, doc) { - var format = field.get('format'); - if (format === 'markdown') { - if (typeof Showdown !== 'undefined') { - var showdown = new Showdown.converter(); - out = showdown.makeHtml(val); - return out; - } else { - return val; - } - } else if (format == 'plain') { - return val; - } else { - // as this is the default and default type is string may get things - // here that are not actually strings - if (val && typeof val === 'string') { - val = val.replace(/(https?:\/\/[^ ]+)/g, '$1'); - } - return val; - } - } - } -}); - -my.FieldList = Backbone.Collection.extend({ - constructor: function FieldList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Field -}); - -// ## Query -my.Query = Backbone.Model.extend({ - constructor: function Query() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - defaults: function() { - return { - size: 100, - from: 0, - q: '', - facets: {}, - filters: [] - }; - }, - _filterTemplates: { - term: { - type: 'term', - // TODO do we need this attribute here? - field: '', - term: '' - }, - range: { - type: 'range', - from: '', - to: '' - }, - geo_distance: { - type: 'geo_distance', - distance: 10, - unit: 'km', - point: { - lon: 0, - lat: 0 - } - } - }, - // ### addFilter(filter) - // - // Add a new filter specified by the filter hash and append to the list of filters - // - // @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates - addFilter: function(filter) { - // crude deep copy - var ourfilter = JSON.parse(JSON.stringify(filter)); - // not fully specified so use template and over-write - if (_.keys(filter).length <= 3) { - ourfilter = _.defaults(ourfilter, this._filterTemplates[filter.type]); - } - var filters = this.get('filters'); - filters.push(ourfilter); - this.trigger('change:filters:new-blank'); - }, - replaceFilter: function(filter) { - // delete filter on the same field, then add - var filters = this.get('filters'); - var idx = -1; - _.each(this.get('filters'), function(f, key, list) { - if (filter.field == f.field) { - idx = key; - } - }); - // trigger just one event (change:filters:new-blank) instead of one for remove and - // one for add - if (idx >= 0) { - filters.splice(idx, 1); - this.set({filters: filters}); - } - this.addFilter(filter); - }, - updateFilter: function(index, value) { - }, - // ### removeFilter - // - // Remove a filter from filters at index filterIndex - removeFilter: function(filterIndex) { - var filters = this.get('filters'); - filters.splice(filterIndex, 1); - this.set({filters: filters}); - this.trigger('change'); - }, - // ### addFacet - // - // Add a Facet to this query - // - // See - addFacet: function(fieldId, size, silent) { - var facets = this.get('facets'); - // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) - if (_.contains(_.keys(facets), fieldId)) { - return; - } - facets[fieldId] = { - terms: { field: fieldId } - }; - if (!_.isUndefined(size)) { - facets[fieldId].terms.size = size; - } - this.set({facets: facets}, {silent: true}); - if (!silent) { - this.trigger('facet:add', this); - } - }, - addHistogramFacet: function(fieldId) { - var facets = this.get('facets'); - facets[fieldId] = { - date_histogram: { - field: fieldId, - interval: 'day' - } - }; - this.set({facets: facets}, {silent: true}); - this.trigger('facet:add', this); - }, - removeFacet: function(fieldId) { - var facets = this.get('facets'); - // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) - if (!_.contains(_.keys(facets), fieldId)) { - return; - } - delete facets[fieldId]; - this.set({facets: facets}, {silent: true}); - this.trigger('facet:remove', this); - }, - clearFacets: function() { - var facets = this.get('facets'); - _.each(_.keys(facets), function(fieldId) { - delete facets[fieldId]; - }); - this.trigger('facet:remove', this); - }, - // trigger a facet add; use this to trigger a single event after adding - // multiple facets - refreshFacets: function() { - this.trigger('facet:add', this); - } - -}); - - -// ## A Facet (Result) -my.Facet = Backbone.Model.extend({ - constructor: function Facet() { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - defaults: function() { - return { - _type: 'terms', - total: 0, - other: 0, - missing: 0, - terms: [] - }; - } -}); - -// ## A Collection/List of Facets -my.FacetList = Backbone.Collection.extend({ - constructor: function FacetList() { - Backbone.Collection.prototype.constructor.apply(this, arguments); - }, - model: my.Facet -}); - -// ## Object State -// -// Convenience Backbone model for storing (configuration) state of objects like Views. -my.ObjectState = Backbone.Model.extend({ -}); - - -// ## Backbone.sync -// -// Override Backbone.sync to hand off to sync function in relevant backend -// Backbone.sync = function(method, model, options) { -// return model.backend.sync(method, model, options); -// }; - -}(this.recline.Model)); - diff --git a/src/view.flot.js b/src/view.flot.js deleted file mode 100644 index 6d468f80..00000000 --- a/src/view.flot.js +++ /dev/null @@ -1,520 +0,0 @@ -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## Graph view for a Dataset using Flot graphing library. -// -// Initialization arguments (in a hash in first parameter): -// -// * model: recline.Model.Dataset -// * state: (optional) configuration hash of form: -// -// { -// group: {column name for x-axis}, -// series: [{column name for series A}, {column name series B}, ... ], -// // options are: lines, points, lines-and-points, bars, columns -// graphType: 'lines', -// graphOptions: {custom [flot options]} -// } -// -// NB: should *not* provide an el argument to the view but must let the view -// generate the element itself (you can then append view.el to the DOM. -my.Flot = Backbone.View.extend({ - template: ' \ -
    \ -
    \ -
    \ - {{#t.flot_info}}

    Hey there!

    \ -

    There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.

    \ -

    Please tell us by using the menu on the right and a graph will automatically appear.

    {{/t.flot_info}} \ -
    \ -
    \ -
    \ -', - - initialize: function(options) { - var self = this; - this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; - - _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel'); - this.needToRedraw = false; - this.listenTo(this.model, 'change', this.render); - this.listenTo(this.model.fields, 'reset add', this.render); - this.listenTo(this.model.records, 'reset add', this.redraw); - var stateData = _.extend({ - group: null, - // so that at least one series chooser box shows up - series: [], - graphType: 'lines-and-points' - }, - options.state - ); - this.state = new recline.Model.ObjectState(stateData); - this.previousTooltipPoint = {x: null, y: null}; - this.editor = new my.FlotControls({ - model: this.model, - state: this.state.toJSON() - }); - this.listenTo(this.editor.state, 'change', function() { - self.state.set(self.editor.state.toJSON()); - self.redraw(); - }); - this.elSidebar = this.editor.$el; - }, - - render: function() { - var self = this; - var tmplData = I18nMessages('recline', recline.View.translations).injectMustache(this.model.toTemplateJSON()); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - this.$graph = this.$el.find('.panel.graph'); - this.$graph.on("plothover", this._toolTip); - return this; - }, - - remove: function () { - this.editor.remove(); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - redraw: function() { - // There are issues generating a Flot graph if either: - // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with - // Uncaught Invalid dimensions for plot, width = 0, height = 0 - // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' - var areWeVisible = !jQuery.expr.filters.hidden(this.el); - if ((!areWeVisible || this.model.records.length === 0)) { - this.needToRedraw = true; - return; - } - - // check we have something to plot - if (this.state.get('group') && this.state.get('series')) { - var series = this.createSeries(); - var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length); - this.plot = $.plot(this.$graph, series, options); - } - }, - - show: function() { - // because we cannot redraw when hidden we may need to when becoming visible - if (this.needToRedraw) { - this.redraw(); - } - }, - - // infoboxes on mouse hover on points/bars etc - _toolTip: function (event, pos, item) { - if (item) { - if (this.previousTooltipPoint.x !== item.dataIndex || - this.previousTooltipPoint.y !== item.seriesIndex) { - this.previousTooltipPoint.x = item.dataIndex; - this.previousTooltipPoint.y = item.seriesIndex; - $("#recline-flot-tooltip").remove(); - - var x = item.datapoint[0].toFixed(2), - y = item.datapoint[1].toFixed(2); - - if (this.state.attributes.graphType === 'bars') { - x = item.datapoint[1].toFixed(2), - y = item.datapoint[0].toFixed(2); - } - - var template = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>'); - var content = template({ - group: this.state.attributes.group, - x: this._xaxisLabel(x), - series: item.series.label, - y: y - }); - - // use a different tooltip location offset for bar charts - var xLocation, yLocation; - if (this.state.attributes.graphType === 'bars') { - xLocation = item.pageX + 15; - yLocation = item.pageY - 10; - } else if (this.state.attributes.graphType === 'columns') { - xLocation = item.pageX + 15; - yLocation = item.pageY; - } else { - xLocation = item.pageX + 10; - yLocation = item.pageY - 20; - } - - $('
    ' + content + '
    ').css({ - top: yLocation, - left: xLocation - }).appendTo("body").fadeIn(200); - } - } else { - $("#recline-flot-tooltip").remove(); - this.previousTooltipPoint.x = null; - this.previousTooltipPoint.y = null; - } - }, - - _xaxisLabel: function (x) { - if (this._groupFieldIsDateTime()) { - // oddly x comes through as milliseconds *string* (rather than int - // or float) so we have to reparse - x = new Date(parseFloat(x)).toLocaleDateString(); - } else if (this.xvaluesAreIndex) { - x = parseInt(x, 10); - // HACK: deal with bar graph style cases where x-axis items were strings - // In this case x at this point is the index of the item in the list of - // records not its actual x-axis value - x = this.model.records.models[x].get(this.state.attributes.group); - } - - return x; - }, - - // ### getGraphOptions - // - // Get options for Flot Graph - // - // needs to be function as can depend on state - // - // @param typeId graphType id (lines, lines-and-points etc) - // @param numPoints the number of points that will be plotted - getGraphOptions: function(typeId, numPoints) { - var self = this; - var groupFieldIsDateTime = self._groupFieldIsDateTime(); - var xaxis = {}; - - if (!groupFieldIsDateTime) { - xaxis.tickFormatter = function (x) { - // convert x to a string and make sure that it is not too long or the - // tick labels will overlap - // TODO: find a more accurate way of calculating the size of tick labels - var label = self._xaxisLabel(x) || ""; - - if (typeof label !== 'string') { - label = label.toString(); - } - if (self.state.attributes.graphType !== 'bars' && label.length > 10) { - label = label.slice(0, 10) + "..."; - } - - return label; - }; - } - - // for labels case we only want ticks at the label intervals - // HACK: however we also get this case with Date fields. In that case we - // could have a lot of values and so we limit to max 15 (we assume) - if (this.xvaluesAreIndex) { - var numTicks = Math.min(this.model.records.length, 15); - var increment = this.model.records.length / numTicks; - var ticks = []; - for (var i=0; i \ -
    \ -
    \ -
    \ - \ -
    \ - \ -
    \ -
    \ -
    \ - \ -
    \ - \ -
    \ -
    \ -
    \ -
    \ -
    \ -
    \ - \ -
    \ - \ -
    \ -
    \ -', - templateSeriesEditor: ' \ -
    \ -
    \ -
    \ -
    \ -
    \ - ', - events: { - 'change form select': 'onEditorSubmit', - 'click .editor-add': '_onAddSeries', - 'click .action-remove-series': 'removeSeries' - }, - - initialize: function(options) { - var self = this; - _.bindAll(this, 'render'); - this.listenTo(this.model.fields, 'reset add', this.render); - this.state = new recline.Model.ObjectState(options.state); - this.render(); - }, - - render: function() { - var self = this; - var tmplData = this.model.toTemplateJSON(); - tmplData = I18nMessages('recline', recline.View.translations).injectMustache(tmplData); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - - // set up editor from state - if (this.state.get('graphType')) { - this._selectOption('.editor-type', this.state.get('graphType')); - } - if (this.state.get('group')) { - this._selectOption('.editor-group', this.state.get('group')); - } - // ensure at least one series box shows up - var tmpSeries = [""]; - if (this.state.get('series').length > 0) { - tmpSeries = this.state.get('series'); - } - _.each(tmpSeries, function(series, idx) { - self.addSeries(idx); - self._selectOption('.editor-series.js-series-' + idx, series); - }); - return this; - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = this.$el.find(id + ' select > option'); - if (options) { - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - }, - - onEditorSubmit: function(e) { - var select = this.$el.find('.editor-group select'); - var $editor = this; - var $series = this.$el.find('.editor-series select'); - var series = $series.map(function () { - return $(this).val(); - }); - var updatedState = { - series: $.makeArray(series), - group: this.$el.find('.editor-group select').val(), - graphType: this.$el.find('.editor-type select').val() - }; - this.state.set(updatedState); - }, - - // Public: Adds a new empty series select box to the editor. - // - // @param [int] idx index of this series in the list of series - // - // Returns itself. - addSeries: function (idx) { - var data = _.extend({ - seriesIndex: idx, - seriesName: String.fromCharCode(idx + 64 + 1) - }, this.model.toTemplateJSON()); - - data = I18nMessages('recline', recline.View.translations).injectMustache(data); - var htmls = Mustache.render(this.templateSeriesEditor, data); - this.$el.find('.editor-series-group').append(htmls); - return this; - }, - - _onAddSeries: function(e) { - e.preventDefault(); - this.addSeries(this.state.get('series').length); - }, - - // Public: Removes a series list item from the editor. - // - // Also updates the labels of the remaining series elements. - removeSeries: function (e) { - e.preventDefault(); - var $el = $(e.target); - $el.parent().parent().remove(); - this.onEditorSubmit(); - } -}); - -})(jQuery, recline.View); diff --git a/src/view.graph.js b/src/view.graph.js deleted file mode 100644 index fb3d8356..00000000 --- a/src/view.graph.js +++ /dev/null @@ -1,4 +0,0 @@ -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; -this.recline.View.Graph = this.recline.View.Flot; -this.recline.View.GraphControls = this.recline.View.FlotControls; diff --git a/src/view.grid.js b/src/view.grid.js deleted file mode 100644 index d2b0792c..00000000 --- a/src/view.grid.js +++ /dev/null @@ -1,273 +0,0 @@ -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## (Data) Grid Dataset View -// -// Provides a tabular view on a Dataset. -// -// Initialize it with a `recline.Model.Dataset`. -my.Grid = Backbone.View.extend({ - tagName: "div", - className: "recline-grid-container", - - initialize: function(modelEtc) { - var self = this; - _.bindAll(this, 'render', 'onHorizontalScroll'); - this.listenTo(this.model.records, 'add reset remove', this.render); - this.tempState = {}; - var state = _.extend({ - hiddenFields: [] - }, modelEtc.state - ); - this.state = new recline.Model.ObjectState(state); - }, - - events: { - // does not work here so done at end of render function - // 'scroll .recline-grid tbody': 'onHorizontalScroll' - }, - - // ====================================================== - // Column and row menus - - setColumnSort: function(order) { - var sort = [{}]; - sort[0][this.tempState.currentColumn] = {order: order}; - this.model.query({sort: sort}); - }, - - hideColumn: function() { - var hiddenFields = this.state.get('hiddenFields'); - hiddenFields.push(this.tempState.currentColumn); - this.state.set({hiddenFields: hiddenFields}); - // change event not being triggered (because it is an array?) so trigger manually - this.state.trigger('change'); - this.render(); - }, - - showColumn: function(e) { - var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column')); - this.state.set({hiddenFields: hiddenFields}); - this.render(); - }, - - onHorizontalScroll: function(e) { - var currentScroll = $(e.target).scrollLeft(); - this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll); - }, - - // ====================================================== - // #### Templating - template: ' \ -
    \ - \ - \ - \ - {{#fields}} \ - \ - {{/fields}} \ - \ - \ - \ - \ -
    \ - {{label}} \ -
    \ -
    \ - ', - - toTemplateJSON: function() { - var self = this; - var modelData = this.model.toJSON(); - modelData.notEmpty = ( this.fields.length > 0 ); - // TODO: move this sort of thing into a toTemplateJSON method on Dataset? - modelData.fields = this.fields.map(function(field) { - return field.toJSON(); - }); - // last header width = scroll bar - border (2px) */ - modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2; - return modelData; - }, - render: function() { - var self = this; - this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) { - return _.indexOf(self.state.get('hiddenFields'), field.id) == -1; - })); - - this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions - var numFields = this.fields.length; - // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar) - var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; - var width = parseInt(Math.max(50, fullWidth / numFields), 10); - // if columns extend outside viewport then remainder is 0 - var remainder = Math.max(fullWidth - numFields * width,0); - this.fields.each(function(field, idx) { - // add the remainder to the first field width so we make up full col - if (idx === 0) { - field.set({width: width+remainder}); - } else { - field.set({width: width}); - } - }); - var tmplData = this.toTemplateJSON(); - tmplData = I18nMessages('recline', recline.View.translations).injectMustache(tmplData); - - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - this.model.records.forEach(function(doc) { - var tr = $(''); - self.$el.find('tbody').append(tr); - var newView = new my.GridRow({ - model: doc, - el: tr, - fields: self.fields - }); - newView.render(); - }); - // hide extra header col if no scrollbar to avoid unsightly overhang - var $tbody = this.$el.find('tbody')[0]; - if ($tbody.scrollHeight <= $tbody.offsetHeight) { - this.$el.find('th.last-header').hide(); - } - this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); - this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); - return this; - }, - - // ### _scrollbarSize - // - // Measure width of a vertical scrollbar and height of a horizontal scrollbar. - // - // @return: { width: pixelWidth, height: pixelHeight } - _scrollbarSize: function() { - var $c = $("
    ").appendTo("body"); - var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight }; - $c.remove(); - return dim; - } -}); - -// ## GridRow View for rendering an individual record. -// -// Since we want this to update in place it is up to creator to provider the element to attach to. -// -// In addition you *must* pass in a FieldList in the constructor options. This should be list of fields for the Grid. -// -// Example: -// -//
    -// var row = new GridRow({
    -//   model: dataset-record,
    -//     el: dom-element,
    -//     fields: mydatasets.fields // a FieldList object
    -//   });
    -// 
    -my.GridRow = Backbone.View.extend({ - initialize: function(initData) { - _.bindAll(this, 'render'); - this._fields = initData.fields; - this.listenTo(this.model, 'change', this.render); - }, - - template: ' \ - {{#cells}} \ - \ -
    \ -   \ -
    {{{value}}}
    \ -
    \ - \ - {{/cells}} \ - ', - events: { - 'click .data-table-cell-edit': 'onEditClick', - 'click .data-table-cell-editor .okButton': 'onEditorOK', - 'click .data-table-cell-editor .cancelButton': 'onEditorCancel' - }, - - toTemplateJSON: function() { - var self = this; - var doc = this.model; - var cellData = this._fields.map(function(field) { - return { - field: field.id, - width: field.get('width'), - value: doc.getFieldValue(field) - }; - }); - return { id: this.id, cells: cellData }; - }, - - render: function() { - this.$el.attr('data-id', this.model.id); - var tmplData = this.toTemplateJSON(); - tmplData = I18nMessages('recline', recline.View.translations).injectMustache(tmplData); - var html = Mustache.render(this.template, tmplData); - this.$el.html(html); - return this; - }, - - // =================== - // Cell Editor methods - - cellEditorTemplate: ' \ - \ - ', - - onEditClick: function(e) { - var editing = this.$el.find('.data-table-cell-editor-editor'); - if (editing.length > 0) { - editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); - } - $(e.target).addClass("hidden"); - var cell = $(e.target).siblings('.data-table-cell-value'); - cell.data("previousContents", cell.text()); - - var tmplData = I18nMessages('recline', recline.View.translations).injectMustache({value: cell.text()}); - var output = Mustache.render(this.cellEditorTemplate, tmplData); - cell.html(output); - }, - - onEditorOK: function(e) { - var self = this; - var cell = $(e.target); - var rowId = cell.parents('tr').attr('data-id'); - var field = cell.parents('td').attr('data-field'); - var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val(); - var newData = {}; - newData[field] = newValue; - this.model.set(newData); - - var fmt = I18nMessages('recline', recline.View.translations); - this.trigger('recline:flash', {message: fmt.t("Updating_row") + "...", loader: true}); - this.model.save().then(function(response) { - this.trigger('recline:flash', {message: fmt.t("Row_updated_successfully"), category: 'success'}); - }) - .fail(function() { - this.trigger('recline:flash', { - message: fmt.t('Error_saving_row'), - category: 'error', - persist: true - }); - }); - }, - - onEditorCancel: function(e) { - var cell = $(e.target).parents('.data-table-cell-value'); - cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden"); - } -}); - -})(jQuery, recline.View); diff --git a/src/view.map.js b/src/view.map.js deleted file mode 100644 index 75d1576f..00000000 --- a/src/view.map.js +++ /dev/null @@ -1,687 +0,0 @@ -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## Map view for a Dataset using Leaflet mapping library. -// -// This view allows to plot gereferenced records on a map. The location -// information can be provided in 2 ways: -// -// 1. Via a single field. This field must be either a geo_point or -// [GeoJSON](http://geojson.org) object -// 2. Via two fields with latitude and longitude coordinates. -// -// Which fields in the data these correspond to can be configured via the state -// (and are guessed if no info is provided). -// -// Initialization arguments are as standard for Dataset Views. State object may -// have the following (optional) configuration options: -// -//
    -//   {
    -//     // geomField if specified will be used in preference to lat/lon
    -//     geomField: {id of field containing geometry in the dataset}
    -//     lonField: {id of field containing longitude in the dataset}
    -//     latField: {id of field containing latitude in the dataset}
    -//     autoZoom: true,
    -//     // use cluster support
    -//     // cluster: true = always on
    -//     // cluster: false = always off
    -//     cluster: false
    -//   }
    -// 
    -// -// Useful attributes to know about (if e.g. customizing) -// -// * map: the Leaflet map (L.Map) -// * features: Leaflet GeoJSON layer containing all the features (L.GeoJSON) -my.Map = Backbone.View.extend({ - template: ' \ -
    \ -
    \ -
    \ -', - - // These are the default (case-insensitive) names of field that are used if found. - // If not found, the user will need to define the fields via the editor. - latitudeFieldNames: ['lat','latitude'], - longitudeFieldNames: ['lon','longitude'], - geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location', 'geo', 'lonlat'], - - initialize: function(options) { - var self = this; - this.options = options; - this.visible = this.$el.is(':visible'); - this.mapReady = false; - // this will be the Leaflet L.Map object (setup below) - this.map = null; - - var stateData = _.extend({ - geomField: null, - lonField: null, - latField: null, - autoZoom: true, - cluster: false - }, - options.state - ); - this.state = new recline.Model.ObjectState(stateData); - - this._clusterOptions = { - zoomToBoundsOnClick: true, - //disableClusteringAtZoom: 10, - maxClusterRadius: 80, - singleMarkerMode: false, - skipDuplicateAddTesting: true, - animateAddingMarkers: false - }; - - // Listen to changes in the fields - this.listenTo(this.model.fields, 'change', function() { - self._setupGeometryField(); - self.render(); - }); - - // Listen to changes in the records - this.listenTo(this.model.records, 'add', function(doc){self.redraw('add',doc);}); - this.listenTo(this.model.records, 'change', function(doc){ - self.redraw('remove',doc); - self.redraw('add',doc); - }); - this.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);}); - this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');}); - - this.menu = new my.MapMenu({ - model: this.model, - state: this.state.toJSON() - }); - this.listenTo(this.menu.state, 'change', function() { - self.state.set(self.menu.state.toJSON()); - self.redraw(); - }); - this.listenTo(this.state, 'change', function() { - self.redraw(); - }); - this.elSidebar = this.menu.$el; - }, - - // ## Customization Functions - // - // The following methods are designed for overriding in order to customize - // behaviour - - // ### infobox - // - // Function to create infoboxes used in popups. The default behaviour is very simple and just lists all attributes. - // - // Users should override this function to customize behaviour i.e. - // - // view = new View({...}); - // view.infobox = function(record) { - // ... - // } - infobox: function(record) { - var html = ''; - for (var key in record.attributes){ - if (!(this.state.get('geomField') && key == this.state.get('geomField'))){ - html += '
    ' + key + ': '+ record.attributes[key] + '
    '; - } - } - return html; - }, - - // Options to use for the [Leaflet GeoJSON layer](http://leaflet.cloudmade.com/reference.html#geojson) - // See also - // - // e.g. - // - // pointToLayer: function(feature, latLng) - // onEachFeature: function(feature, layer) - // - // See defaults for examples - geoJsonLayerOptions: { - // pointToLayer function to use when creating points - // - // Default behaviour shown here is to create a marker using the - // popupContent set on the feature properties (created via infobox function - // during feature generation) - // - // NB: inside pointToLayer `this` will be set to point to this map view - // instance (which allows e.g. this.markers to work in this default case) - pointToLayer: function (feature, latlng) { - var marker = new L.Marker(latlng); - marker.bindPopup(feature.properties.popupContent); - // this is for cluster case - this.markers.addLayer(marker); - return marker; - }, - // onEachFeature default which adds popup in - onEachFeature: function(feature, layer) { - if (feature.properties && feature.properties.popupContent) { - layer.bindPopup(feature.properties.popupContent); - } - } - }, - - // END: Customization section - // ---- - - // ### Public: Adds the necessary elements to the page. - // - // Also sets up the editor fields and the map if necessary. - render: function() { - var self = this; - var tmplData = I18nMessages('recline', recline.View.translations).injectMustache(this.model.toTemplateJSON()); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - this.$map = this.$el.find('.panel.map'); - this.redraw(); - return this; - }, - - // ### Public: Redraws the features on the map according to the action provided - // - // Actions can be: - // - // * reset: Clear all features - // * add: Add one or n features (records) - // * remove: Remove one or n features (records) - // * refresh: Clear existing features and add all current records - redraw: function(action, doc){ - var self = this; - action = action || 'refresh'; - // try to set things up if not already - if (!self._geomReady()){ - self._setupGeometryField(); - } - if (!self.mapReady){ - self._setupMap(); - } - - if (this._geomReady() && this.mapReady){ - // removing ad re-adding the layer enables faster bulk loading - this.map.removeLayer(this.features); - this.map.removeLayer(this.markers); - - var countBefore = 0; - this.features.eachLayer(function(){countBefore++;}); - - if (action == 'refresh' || action == 'reset') { - this.features.clearLayers(); - // recreate cluster group because of issues with clearLayer - this.map.removeLayer(this.markers); - this.markers = new L.MarkerClusterGroup(this._clusterOptions); - this._add(this.model.records.models); - } else if (action == 'add' && doc){ - this._add(doc); - } else if (action == 'remove' && doc){ - this._remove(doc); - } - - // this must come before zooming! - // if not: errors when using e.g. circle markers like - // "Cannot call method 'project' of undefined" - if (this.state.get('cluster')) { - this.map.addLayer(this.markers); - } else { - this.map.addLayer(this.features); - } - - if (this.state.get('autoZoom')){ - if (this.visible){ - this._zoomToFeatures(); - } else { - this._zoomPending = true; - } - } - } - }, - - show: function() { - // If the div was hidden, Leaflet needs to recalculate some sizes - // to display properly - if (this.map){ - this.map.invalidateSize(); - if (this._zoomPending && this.state.get('autoZoom')) { - this._zoomToFeatures(); - this._zoomPending = false; - } - } - this.visible = true; - }, - - hide: function() { - this.visible = false; - }, - - _geomReady: function() { - return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); - }, - - // Private: Add one or n features to the map - // - // For each record passed, a GeoJSON geometry will be extracted and added - // to the features layer. If an exception is thrown, the process will be - // stopped and an error notification shown. - // - // Each feature will have a popup associated with all the record fields. - // - _add: function(docs){ - var self = this; - - if (!(docs instanceof Array)) docs = [docs]; - - var count = 0; - var wrongSoFar = 0; - _.every(docs, function(doc){ - count += 1; - var feature = self._getGeometryFromRecord(doc); - if (typeof feature === 'undefined' || feature === null){ - // Empty field - return true; - } else if (feature instanceof Object){ - feature.properties = { - popupContent: self.infobox(doc), - // Add a reference to the model id, which will allow us to - // link this Leaflet layer to a Recline doc - cid: doc.cid - }; - - try { - self.features.addData(feature); - } catch (except) { - wrongSoFar += 1; - var msg = 'Wrong geometry value'; - if (except.message) msg += ' (' + except.message + ')'; - if (wrongSoFar <= 10) { - self.trigger('recline:flash', {message: msg, category:'error'}); - } - } - } else { - wrongSoFar += 1; - if (wrongSoFar <= 10) { - self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'}); - } - } - return true; - }); - }, - - // Private: Remove one or n features from the map - // - _remove: function(docs){ - - var self = this; - - if (!(docs instanceof Array)) docs = [docs]; - - _.each(docs,function(doc){ - for (var key in self.features._layers){ - if (self.features._layers[key].feature.geometry.properties.cid == doc.cid){ - self.features.removeLayer(self.features._layers[key]); - } - } - }); - - }, - - // Private: convert DMS coordinates to decimal - // - // north and east are positive, south and west are negative - // - _parseCoordinateString: function(coord){ - if (typeof(coord) != 'string') { - return(parseFloat(coord)); - } - var dms = coord.split(/[^-?\.\d\w]+/); - var deg = 0; var m = 0; - var toDeg = [1, 60, 3600]; // conversion factors for Deg, min, sec - var i; - for (i = 0; i < dms.length; ++i) { - if (isNaN(parseFloat(dms[i]))) { - continue; - } - deg += parseFloat(dms[i]) / toDeg[m]; - m += 1; - } - if (coord.match(/[SW]/)) { - deg = -1*deg; - } - return(deg); - }, - - // Private: Return a GeoJSON geomtry extracted from the record fields - // - _getGeometryFromRecord: function(doc){ - if (this.state.get('geomField')){ - var value = doc.get(this.state.get('geomField')); - if (typeof(value) === 'string'){ - // We *may* have a GeoJSON string representation - try { - value = $.parseJSON(value); - } catch(e) {} - } - if (typeof(value) === 'string') { - value = value.replace('(', '').replace(')', ''); - var parts = value.split(','); - var lat = this._parseCoordinateString(parts[0]); - var lon = this._parseCoordinateString(parts[1]); - - if (!isNaN(lon) && !isNaN(parseFloat(lat))) { - return { - "type": "Point", - "coordinates": [lon, lat] - }; - } else { - return null; - } - } else if (value && _.isArray(value)) { - // [ lon, lat ] - return { - "type": "Point", - "coordinates": [value[0], value[1]] - }; - } else if (value && value.lat) { - // of form { lat: ..., lon: ...} - return { - "type": "Point", - "coordinates": [value.lon || value.lng, value.lat] - }; - } - // We o/w assume that contents of the field are a valid GeoJSON object - return value; - } else if (this.state.get('lonField') && this.state.get('latField')){ - // We'll create a GeoJSON like point object from the two lat/lon fields - var lon = doc.get(this.state.get('lonField')); - var lat = doc.get(this.state.get('latField')); - lon = this._parseCoordinateString(lon); - lat = this._parseCoordinateString(lat); - - if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) { - return { - type: 'Point', - coordinates: [lon,lat] - }; - } - } - return null; - }, - - // Private: Check if there is a field with GeoJSON geometries or alternatively, - // two fields with lat/lon values. - // - // If not found, the user can define them via the UI form. - _setupGeometryField: function(){ - // should not overwrite if we have already set this (e.g. explicitly via state) - if (!this._geomReady()) { - this.state.set({ - geomField: this._checkField(this.geometryFieldNames), - latField: this._checkField(this.latitudeFieldNames), - lonField: this._checkField(this.longitudeFieldNames) - }); - this.menu.state.set(this.state.toJSON()); - } - }, - - // Private: Check if a field in the current model exists in the provided - // list of names. - // - // - _checkField: function(fieldNames){ - var field; - var modelFieldNames = this.model.fields.pluck('id'); - for (var i = 0; i < fieldNames.length; i++){ - for (var j = 0; j < modelFieldNames.length; j++){ - if (modelFieldNames[j].toLowerCase() == fieldNames[i].toLowerCase()) - return modelFieldNames[j]; - } - } - return null; - }, - - // Private: Zoom to map to current features extent if any, or to the full - // extent if none. - // - _zoomToFeatures: function(){ - var bounds = this.features.getBounds(); - if (bounds && bounds.getNorthEast() && bounds.getSouthWest()){ - this.map.fitBounds(bounds); - } else { - this.map.setView([0, 0], 2); - } - }, - - // Private: Sets up the Leaflet map control and the features layer. - // - // The map uses a base layer from [Stamen](http://maps.stamen.com) based - // on [OpenStreetMap data](http://openstreetmap.org) by default, but it can - // be configured passing the `mapTilesURL` and `mapTilesAttribution` options - // (`mapTilesSubdomains` is also supported), eg: - // - // view = new recline.View.Map({ - // model: dataset, - // mapTilesURL: '//{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/{z}/{x}/{y}.png?access_token=pk.XXXX', - // mapTilesAttribution: '© MapBox etc..', - // mapTilesSubdomains: 'ab' - // }) - // - // - _setupMap: function(){ - var self = this; - this.map = new L.Map(this.$map.get(0)); - var mapUrl = this.options.mapTilesURL || 'https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png'; - var attribution = this.options.mapTilesAttribution ||'Map tiles by Stamen Design (CC BY 3.0). Data by OpenStreetMap (CC BY SA)'; - var subdomains = this.options.mapTilesSubdomains || 'abc'; - - var bg = new L.TileLayer(mapUrl, {maxZoom: 19, attribution: attribution, subdomains: subdomains}); - this.map.addLayer(bg); - - this.markers = new L.MarkerClusterGroup(this._clusterOptions); - - // rebind this (as needed in e.g. default case above) - this.geoJsonLayerOptions.pointToLayer = _.bind( - this.geoJsonLayerOptions.pointToLayer, - this); - this.features = new L.GeoJSON(null, this.geoJsonLayerOptions); - - this.map.setView([0, 0], 2); - - this.mapReady = true; - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = $('.' + id + ' > select > option'); - if (options){ - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - } -}); - -my.MapMenu = Backbone.View.extend({ - className: 'editor', - - template: ' \ -
    \ -
    \ -
    \ - {{t.map_mapping}}: \ - \ - \ -
    \ -
    \ - \ -
    \ - \ -
    \ - \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ - \ -
    \ - \ -
    \ - ', - - // Define here events for UI elements - events: { - 'click .editor-update-map': 'onEditorSubmit', - 'change .editor-field-type': 'onFieldTypeChange', - 'click #editor-auto-zoom': 'onAutoZoomChange', - 'click #editor-cluster': 'onClusteringChange' - }, - - initialize: function(options) { - var self = this; - _.bindAll(this, 'render'); - this.listenTo(this.model.fields, 'change', this.render); - this.state = new recline.Model.ObjectState(options.state); - this.listenTo(this.state, 'change', this.render); - this.render(); - }, - - // ### Public: Adds the necessary elements to the page. - // - // Also sets up the editor fields and the map if necessary. - render: function() { - var self = this; - var tmplData = I18nMessages('recline', recline.View.translations).injectMustache(this.model.toTemplateJSON()); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - - if (this._geomReady() && this.model.fields.length){ - if (this.state.get('geomField')){ - this._selectOption('editor-geom-field',this.state.get('geomField')); - this.$el.find('#editor-field-type-geom').attr('checked','checked').change(); - } else{ - this._selectOption('editor-lon-field',this.state.get('lonField')); - this._selectOption('editor-lat-field',this.state.get('latField')); - this.$el.find('#editor-field-type-latlon').attr('checked','checked').change(); - } - } - if (this.state.get('autoZoom')) { - this.$el.find('#editor-auto-zoom').attr('checked', 'checked'); - } else { - this.$el.find('#editor-auto-zoom').removeAttr('checked'); - } - if (this.state.get('cluster')) { - this.$el.find('#editor-cluster').attr('checked', 'checked'); - } else { - this.$el.find('#editor-cluster').removeAttr('checked'); - } - return this; - }, - - _geomReady: function() { - return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); - }, - - // ## UI Event handlers - // - - // Public: Update map with user options - // - // Right now the only configurable option is what field(s) contains the - // location information. - // - onEditorSubmit: function(e){ - e.preventDefault(); - if (this.$el.find('#editor-field-type-geom').attr('checked')){ - this.state.set({ - geomField: this.$el.find('.editor-geom-field > select > option:selected').val(), - lonField: null, - latField: null - }); - } else { - this.state.set({ - geomField: null, - lonField: this.$el.find('.editor-lon-field > select > option:selected').val(), - latField: this.$el.find('.editor-lat-field > select > option:selected').val() - }); - } - return false; - }, - - // Public: Shows the relevant select lists depending on the location field - // type selected. - // - onFieldTypeChange: function(e){ - if (e.target.value == 'geom'){ - this.$el.find('.editor-field-type-geom').show(); - this.$el.find('.editor-field-type-latlon').hide(); - } else { - this.$el.find('.editor-field-type-geom').hide(); - this.$el.find('.editor-field-type-latlon').show(); - } - }, - - onAutoZoomChange: function(e){ - this.state.set({autoZoom: !this.state.get('autoZoom')}); - }, - - onClusteringChange: function(e){ - this.state.set({cluster: !this.state.get('cluster')}); - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = this.$el.find('.' + id + ' > select > option'); - if (options){ - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - } -}); - -})(jQuery, recline.View); diff --git a/src/view.multiview.js b/src/view.multiview.js deleted file mode 100644 index 1a263714..00000000 --- a/src/view.multiview.js +++ /dev/null @@ -1,568 +0,0 @@ -/*jshint multistr:true */ - -// Standard JS module setup -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; -// ## MultiView -// -// Manage multiple views together along with query editor etc. Usage: -// -//
    -// var myExplorer = new recline.View.MultiView({
    -//   model: {{recline.Model.Dataset instance}}
    -//   el: {{an existing dom element}}
    -//   views: {{dataset views}}
    -//   state: {{state configuration -- see below}}
    -// });
    -// 
    -// -// ### Parameters -// -// **model**: (required) recline.model.Dataset instance. -// -// **el**: (required) DOM element to bind to. NB: the element already -// being in the DOM is important for rendering of some subviews (e.g. -// Graph). -// -// **views**: (optional) the dataset views (Grid, Graph etc) for -// MultiView to show. This is an array of view hashes. If not provided -// initialize with (recline.View.)Grid, Graph, and Map views (with obvious id -// and labels!). -// -//
    -// var views = [
    -//   {
    -//     id: 'grid', // used for routing
    -//     label: 'Grid', // used for view switcher
    -//     view: new recline.View.Grid({
    -//       model: dataset
    -//     })
    -//   },
    -//   {
    -//     id: 'graph',
    -//     label: 'Graph',
    -//     view: new recline.View.Graph({
    -//       model: dataset
    -//     })
    -//   }
    -// ];
    -// 
    -// -// **sidebarViews**: (optional) the sidebar views (Filters, Fields) for -// MultiView to show. This is an array of view hashes. If not provided -// initialize with (recline.View.)FilterEditor and Fields views (with obvious -// id and labels!). -// -//
    -// var sidebarViews = [
    -//   {
    -//     id: 'filterEditor', // used for routing
    -//     label: 'Filters', // used for view switcher
    -//     view: new recline.View.FilterEditor({
    -//       model: dataset
    -//     })
    -//   },
    -//   {
    -//     id: 'fieldsView',
    -//     label: 'Fields',
    -//     view: new recline.View.Fields({
    -//       model: dataset
    -//     })
    -//   }
    -// ];
    -// 
    -// -// **state**: standard state config for this view. This state is slightly -// special as it includes config of many of the subviews. -// -//
    -// var state = {
    -//     query: {dataset query state - see dataset.queryState object}
    -//     'view-{id1}': {view-state for this view}
    -//     'view-{id2}': {view-state for }
    -//     ...
    -//     // Explorer
    -//     currentView: id of current view (defaults to first view if not specified)
    -//     readOnly: (default: false) run in read-only mode
    -// }
    -// 
    -// -// Note that at present we do *not* serialize information about the actual set -// of views in use -- e.g. those specified by the views argument -- but instead -// expect either that the default views are fine or that the client to have -// initialized the MultiView with the relevant views themselves. -my.MultiView = Backbone.View.extend({ - template: ' \ -
    \ -
    \ - \ -
    \ - \ -
    \ - {{#t.num_records}}{recordCount} {recordCount, plural, =1{record} other{records}}{{/t.num_records}}\ -
    \ - \ -
    \ -
    \ -
    \ -
    \ -
    \ - ', - events: { - 'click .menu-right button': '_onMenuClick', - 'click .navigation button': '_onSwitchView' - }, - - initialize: function(options) { - var self = this; - this._setupState(options.state); - var fmt = I18nMessages('recline', recline.View.translations); - - // Hash of 'page' views (i.e. those for whole page) keyed by page name - if (options.views) { - this.pageViews = options.views; - } else { - this.pageViews = [{ - id: 'grid', - label: fmt.t('Grid'), - view: new my.SlickGrid({ - model: this.model, - state: this.state.get('view-grid') - }) - }, { - id: 'graph', - label: fmt.t('Graph'), - view: new my.Graph({ - model: this.model, - state: this.state.get('view-graph') - }) - }, { - id: 'map', - label: fmt.t('Map'), - view: new my.Map({ - model: this.model, - state: this.state.get('view-map') - }) - }, { - id: 'timeline', - label: fmt.t('Timeline'), - view: new my.Timeline({ - model: this.model, - state: this.state.get('view-timeline') - }) - }]; - } - // Hashes of sidebar elements - if(options.sidebarViews) { - this.sidebarViews = options.sidebarViews; - } else { - this.sidebarViews = [{ - id: 'filterEditor', - label: fmt.t('Filters'), - view: new my.FilterEditor({ - model: this.model - }) - }, { - id: 'fieldsView', - label: fmt.t('Fields'), - view: new my.Fields({ - model: this.model - }) - }]; - } - // these must be called after pageViews are created - this.render(); - this._bindStateChanges(); - this._bindFlashNotifications(); - // now do updates based on state (need to come after render) - if (this.state.get('readOnly')) { - this.setReadOnly(); - } - if (this.state.get('currentView')) { - this.updateNav(this.state.get('currentView')); - } else { - this.updateNav(this.pageViews[0].id); - } - this._showHideSidebar(); - - this.listenTo(this.model, 'query:start', function() { - self.notify({loader: true, persist: true}); - }); - this.listenTo(this.model, 'query:done', function() { - self.clearNotifications(); - self.$el.find('.doc-count').text(self.model.recordCount || fmt.t('Unknown')); - }); - this.listenTo(this.model, 'query:fail', function(error) { - self.clearNotifications(); - var msg = ''; - if (typeof(error) == 'string') { - msg = error; - } else if (typeof(error) == 'object') { - if (error.title) { - msg = error.title + ': '; - } - if (error.message) { - msg += error.message; - } - } else { - msg = fmt.t('backend_error', {}, 'There was an error querying the backend'); - } - self.notify({message: msg, category: 'error', persist: true}); - }); - - // retrieve basic data like fields etc - // note this.model and dataset returned are the same - // TODO: set query state ...? - this.model.queryState.set(self.state.get('query'), {silent: true}); - }, - - setReadOnly: function() { - this.$el.addClass('recline-read-only'); - }, - - render: function() { - var tmplData = this.model.toTemplateJSON(); - tmplData.views = this.pageViews; - tmplData.sidebarViews = this.sidebarViews; - tmplData = I18nMessages('recline', recline.View.translations).injectMustache(tmplData); - var template = Mustache.render(this.template, tmplData); - this.$el.html(template); - - // now create and append other views - var $dataViewContainer = this.$el.find('.data-view-container'); - var $dataSidebar = this.$el.find('.data-view-sidebar'); - - // the main views - _.each(this.pageViews, function(view, pageName) { - view.view.render(); - if (view.view.redraw) { - view.view.redraw(); - } - $dataViewContainer.append(view.view.el); - if (view.view.elSidebar) { - $dataSidebar.append(view.view.elSidebar); - } - }); - - _.each(this.sidebarViews, function(view) { - this['$'+view.id] = view.view.$el; - $dataSidebar.append(view.view.el); - }, this); - - this.pager = new recline.View.Pager({ - model: this.model - }); - this.$el.find('.recline-results-info').after(this.pager.el); - - this.queryEditor = new recline.View.QueryEditor({ - model: this.model.queryState - }); - this.$el.find('.query-editor-here').append(this.queryEditor.el); - - }, - - remove: function () { - _.each(this.pageViews, function (view) { - view.view.remove(); - }); - _.each(this.sidebarViews, function (view) { - view.view.remove(); - }); - this.pager.remove(); - this.queryEditor.remove(); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - // hide the sidebar if empty - _showHideSidebar: function() { - var $dataSidebar = this.$el.find('.data-view-sidebar'); - var visibleChildren = $dataSidebar.children().filter(function() { - return $(this).css("display") != "none"; - }).length; - - if (visibleChildren > 0) { - $dataSidebar.show(); - } else { - $dataSidebar.hide(); - } - }, - - updateNav: function(pageName) { - this.$el.find('.navigation button').removeClass('active'); - var $el = this.$el.find('.navigation button[data-view="' + pageName + '"]'); - $el.addClass('active'); - - // add/remove sidebars and hide inactive views - _.each(this.pageViews, function(view, idx) { - if (view.id === pageName) { - view.view.$el.show(); - if (view.view.elSidebar) { - view.view.elSidebar.show(); - } - } else { - view.view.$el.hide(); - if (view.view.elSidebar) { - view.view.elSidebar.hide(); - } - if (view.view.hide) { - view.view.hide(); - } - } - }); - - this._showHideSidebar(); - - // call view.view.show after sidebar visibility has been determined so - // that views can correctly calculate their maximum width - _.each(this.pageViews, function(view, idx) { - if (view.id === pageName) { - if (view.view.show) { - view.view.show(); - } - } - }); - }, - - _onMenuClick: function(e) { - e.preventDefault(); - var action = $(e.target).attr('data-action'); - this['$'+action].toggle(); - this._showHideSidebar(); - }, - - _onSwitchView: function(e) { - e.preventDefault(); - var viewName = $(e.target).attr('data-view'); - this.updateNav(viewName); - this.state.set({currentView: viewName}); - }, - - // create a state object for this view and do the job of - // - // a) initializing it from both data passed in and other sources (e.g. hash url) - // - // b) ensure the state object is updated in responese to changes in subviews, query etc. - _setupState: function(initialState) { - var self = this; - // get data from the query string / hash url plus some defaults - var qs = my.parseHashQueryString(); - var query = qs.reclineQuery; - query = query ? JSON.parse(query) : self.model.queryState.toJSON(); - // backwards compatability (now named view-graph but was named graph) - var graphState = qs['view-graph'] || qs.graph; - graphState = graphState ? JSON.parse(graphState) : {}; - - // now get default data + hash url plus initial state and initial our state object with it - var stateData = _.extend({ - query: query, - 'view-graph': graphState, - backend: this.model.backend.__type__, - url: this.model.get('url'), - dataset: this.model.toJSON(), - currentView: null, - readOnly: false - }, - initialState); - this.state = new recline.Model.ObjectState(stateData); - }, - - _bindStateChanges: function() { - var self = this; - // finally ensure we update our state object when state of sub-object changes so that state is always up to date - this.listenTo(this.model.queryState, 'change', function() { - self.state.set({query: self.model.queryState.toJSON()}); - }); - _.each(this.pageViews, function(pageView) { - if (pageView.view.state && pageView.view.state.bind) { - var update = {}; - update['view-' + pageView.id] = pageView.view.state.toJSON(); - self.state.set(update); - self.listenTo(pageView.view.state, 'change', function() { - var update = {}; - update['view-' + pageView.id] = pageView.view.state.toJSON(); - // had problems where change not being triggered for e.g. grid view so let's do it explicitly - self.state.set(update, {silent: true}); - self.state.trigger('change'); - }); - } - }); - }, - - _bindFlashNotifications: function() { - var self = this; - _.each(this.pageViews, function(pageView) { - self.listenTo(pageView.view, 'recline:flash', function(flash) { - self.notify(flash); - }); - }); - }, - - // ### notify - // - // Create a notification (a div.alert in div.alert-messsages) using provided - // flash object. Flash attributes (all are optional): - // - // * message: message to show. - // * category: warning (default), success, error - // * persist: if true alert is persistent, o/w hidden after 3s (default = false) - // * loader: if true show loading spinner - notify: function(flash) { - var tmplData = _.extend({ - message: 'Loading', - category: 'warning', - loader: false - }, - flash - ); - var _template; - if (tmplData.loader) { - _template = ' \ -
    \ - {{message}} \ -   \ -
    '; - } else { - _template = ' \ -
    Ɨ \ - {{message}} \ -
    '; - } - var _templated = $(Mustache.render(_template, tmplData)); - _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages')); - if (!flash.persist) { - setTimeout(function() { - $(_templated).fadeOut(1000, function() { - $(this).remove(); - }); - }, 1000); - } - }, - - // ### clearNotifications - // - // Clear all existing notifications - clearNotifications: function() { - var $notifications = $('.recline-data-explorer .alert-messages .alert'); - $notifications.fadeOut(1500, function() { - $(this).remove(); - }); - } -}); - -// ### MultiView.restore -// -// Restore a MultiView instance from a serialized state including the associated dataset -// -// This inverts the state serialization process in Multiview -my.MultiView.restore = function(state) { - // hack-y - restoring a memory dataset does not mean much ... (but useful for testing!) - var datasetInfo; - if (state.backend === 'memory') { - datasetInfo = { - backend: 'memory', - records: [{stub: 'this is a stub dataset because we do not restore memory datasets'}] - }; - } else { - datasetInfo = _.extend({ - url: state.url, - backend: state.backend - }, - state.dataset - ); - } - var dataset = new recline.Model.Dataset(datasetInfo); - var explorer = new my.MultiView({ - model: dataset, - state: state - }); - return explorer; -}; - -// ## Miscellaneous Utilities -var urlPathRegex = /^([^?]+)(\?.*)?/; - -// Parse the Hash section of a URL into path and query string -my.parseHashUrl = function(hashUrl) { - var parsed = urlPathRegex.exec(hashUrl); - if (parsed === null) { - return {}; - } else { - return { - path: parsed[1], - query: parsed[2] || '' - }; - } -}; - -// Parse a URL query string (?xyz=abc...) into a dictionary. -my.parseQueryString = function(q) { - if (!q) { - return {}; - } - var urlParams = {}, - e, d = function (s) { - return unescape(s.replace(/\+/g, " ")); - }, - r = /([^&=]+)=?([^&]*)/g; - - if (q && q.length && q[0] === '?') { - q = q.slice(1); - } - while (e = r.exec(q)) { - // TODO: have values be array as query string allow repetition of keys - urlParams[d(e[1])] = d(e[2]); - } - return urlParams; -}; - -// Parse the query string out of the URL hash -my.parseHashQueryString = function() { - var q = my.parseHashUrl(window.location.hash).query; - return my.parseQueryString(q); -}; - -// Compse a Query String -my.composeQueryString = function(queryParams) { - var queryString = '?'; - var items = []; - $.each(queryParams, function(key, value) { - if (typeof(value) === 'object') { - value = JSON.stringify(value); - } - items.push(key + '=' + encodeURIComponent(value)); - }); - queryString += items.join('&'); - return queryString; -}; - -my.getNewHashForQueryString = function(queryParams) { - var queryPart = my.composeQueryString(queryParams); - if (window.location.hash) { - // slice(1) to remove # at start - return window.location.hash.split('?')[0].slice(1) + queryPart; - } else { - return queryPart; - } -}; - -my.setHashQueryString = function(queryParams) { - window.location.hash = my.getNewHashForQueryString(queryParams); -}; - -})(jQuery, recline.View); - diff --git a/src/view.slickgrid.js b/src/view.slickgrid.js deleted file mode 100644 index ad6b6bf0..00000000 --- a/src/view.slickgrid.js +++ /dev/null @@ -1,602 +0,0 @@ -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.View = this.recline.View || {}; - -(function($, my) { - "use strict"; - -// ## SlickGrid Dataset View -// -// Provides a tabular view on a Dataset, based on SlickGrid. -// -// https://github.com/mleibman/SlickGrid -// -// Initialize it with a `recline.Model.Dataset`. -// -// Additional options to drive SlickGrid grid can be given through state. -// The following keys allow for customization: -// * gridOptions: to add options at grid level -// * columnsEditor: to add editor for editable columns -// -// For example: -// var grid = new recline.View.SlickGrid({ -// model: dataset, -// el: $el, -// state: { -// gridOptions: { -// editable: true, -// enableAddRow: true -// // Enable support for row delete -// enabledDelRow: true, -// // Enable support for row Reorder -// enableReOrderRow:true, -// ... -// }, -// columnsEditor: [ -// {column: 'date', editor: Slick.Editors.Date }, -// {column: 'title', editor: Slick.Editors.Text} -// ] -// } -// }); -//// NB: you need an explicit height on the element for slickgrid to work -my.SlickGrid = Backbone.View.extend({ - initialize: function(modelEtc) { - var self = this; - this.$el.addClass('recline-slickgrid'); - - // Template for row delete menu , change it if you don't love - this.templates = { - deleterow : '', - reorderrows: '
    {{t.Reorder_row}}
    ' - }; - - _.bindAll(this, 'render', 'onRecordChanged'); - this.listenTo(this.model.records, 'add remove reset', this.render); - this.listenTo(this.model.records, 'change', this.onRecordChanged); - var state = _.extend({ - hiddenColumns: [], - columnsOrder: [], - columnsSort: {}, - columnsWidth: [], - columnsEditor: [], - options: {}, - fitColumns: false - }, modelEtc.state - - ); - this.state = new recline.Model.ObjectState(state); - this._slickHandler = new Slick.EventHandler(); - - //add menu for new row , check if enableAddRow is set to true or not set - if(this.state.get("gridOptions") - && this.state.get("gridOptions").enabledAddRow != undefined - && this.state.get("gridOptions").enabledAddRow == true ){ - this.editor = new my.GridControl() - this.elSidebar = this.editor.$el - this.listenTo(this.editor.state, 'change', function(){ - this.model.records.add(new recline.Model.Record()) - }); - } - }, - - onRecordChanged: function(record) { - // Ignore if the grid is not yet drawn - if (!this.grid) { - return; - } - // Let's find the row corresponding to the index - var row_index = this.grid.getData().getModelRow( record ); - this.grid.invalidateRow(row_index); - this.grid.getData().updateItem(record, row_index); - this.grid.render(); - }, - - render: function() { - var self = this; - var options = _.extend({ - enableCellNavigation: true, - enableColumnReorder: true, - explicitInitialization: true, - syncColumnCellResize: true, - forceFitColumns: this.state.get('fitColumns') - }, self.state.get('gridOptions')); - - // We need all columns, even the hidden ones, to show on the column picker - var columns = []; - var fmt = I18nMessages('recline', recline.View.translations); - - // custom formatter as default one escapes html - // plus this way we distinguish between rendering/formatting and computed value (so e.g. sort still works ...) - // row = row index, cell = cell index, value = value, columnDef = column definition, dataContext = full row values - var formatter = function(row, cell, value, columnDef, dataContext) { - if(columnDef.id == "del"){ - var formatted = Mustache.render(self.templates.deleterow, fmt.injectMustache({})); - return formatted; - } - if(columnDef.id == "#"){ - var formatted = Mustache.render(self.templates.reorderrows, fmt.injectMustache({})); - return formatted; - } - - var field = self.model.fields.get(columnDef.id); - if (field.renderer) { - return field.renderer(value, field, dataContext); - } else { - return value - } - }; - - // we need to be sure that user is entering a valid input , for exemple if - // field is date type and field.format ='YY-MM-DD', we should be sure that - // user enter a correct value - var validator = function(field) { - return function(value){ - if (field.type == "date" && isNaN(Date.parse(value))){ - return { - valid: false, - msg: fmt.t('date_required', {}, "A date is required, check field field-date-format") - }; - } else { - return {valid: true, msg :null } - } - } - }; - - // Add column for row reorder support - if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow == true) { - columns.push({ - id: "#", - name: "", - width: 22, - behavior: "selectAndMove", - selectable: false, - resizable: false, - cssClass: "recline-cell-reorder", - formatter: formatter - }) - } - // Add column for row delete support - if (this.state.get("gridOptions") && this.state.get("gridOptions").enabledDelRow == true) { - columns.push({ - id: 'del', - name: '', - field: 'del', - sortable: true, - width: 38, - formatter: formatter, - validator:validator - }) - } - - function sanitizeFieldName(name) { - return $('
    ').text(name).html(); - } - - _.each(this.model.fields.toJSON(),function(field){ - var column = { - id: field.id, - name: sanitizeFieldName(field.label), - field: field.id, - sortable: true, - minWidth: 80, - formatter: formatter, - validator:validator(field) - }; - var widthInfo = _.find(self.state.get('columnsWidth'),function(c){return c.column === field.id;}); - if (widthInfo){ - column.width = widthInfo.width; - } - var editInfo = _.find(self.state.get('columnsEditor'),function(c){return c.column === field.id;}); - if (editInfo){ - column.editor = editInfo.editor; - } else { - // guess editor type - var typeToEditorMap = { - 'string': Slick.Editors.LongText, - 'integer': Slick.Editors.IntegerEditor, - 'number': Slick.Editors.Text, - // TODO: need a way to ensure we format date in the right way - // Plus what if dates are in distant past or future ... (?) - // 'date': Slick.Editors.DateEditor, - 'date': Slick.Editors.Text, - 'boolean': Slick.Editors.YesNoSelectEditor - // TODO: (?) percent ... - }; - if (field.type in typeToEditorMap) { - column.editor = typeToEditorMap[field.type] - } else { - column.editor = Slick.Editors.LongText; - } - } - columns.push(column); - }); - // Restrict the visible columns - var visibleColumns = _.filter(columns, function(column) { - return _.indexOf(self.state.get('hiddenColumns'), column.id) === -1; - }); - // Order them if there is ordering info on the state - if (this.state.get('columnsOrder') && this.state.get('columnsOrder').length > 0) { - visibleColumns = visibleColumns.sort(function(a,b){ - return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; - }); - columns = columns.sort(function(a,b){ - return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; - }); - } - - // Move hidden columns to the end, so they appear at the bottom of the - // column picker - var tempHiddenColumns = []; - for (var i = columns.length -1; i >= 0; i--){ - if (_.indexOf(_.pluck(visibleColumns,'id'),columns[i].id) === -1){ - tempHiddenColumns.push(columns.splice(i,1)[0]); - } - } - columns = columns.concat(tempHiddenColumns); - - // Transform a model object into a row - function toRow(m) { - var row = {}; - self.model.fields.each(function(field) { - var render = ""; - //when adding row from slickgrid the field value is undefined - if(!_.isUndefined(m.getFieldValueUnrendered(field))){ - render =m.getFieldValueUnrendered(field) - } - row[field.id] = render - }); - return row; - } - - function RowSet() { - var models = []; - var rows = []; - - this.push = function(model, row) { - models.push(model); - rows.push(row); - }; - - this.getLength = function() {return rows.length; }; - this.getItem = function(index) {return rows[index];}; - this.getItemMetadata = function(index) {return {};}; - this.getModel = function(index) {return models[index];}; - this.getModelRow = function(m) {return _.indexOf(models, m);}; - this.updateItem = function(m,i) { - rows[i] = toRow(m); - models[i] = m; - }; - } - - var data = new RowSet(); - - this.model.records.each(function(doc){ - data.push(doc, toRow(doc)); - }); - - this.grid = new Slick.Grid(this.el, data, visibleColumns, options); - // Column sorting - var sortInfo = this.model.queryState.get('sort'); - if (sortInfo){ - var column = sortInfo[0].field; - var sortAsc = sortInfo[0].order !== 'desc'; - this.grid.setSortColumn(column, sortAsc); - } - - if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) { - this._setupRowReordering(); - } - - this._slickHandler.subscribe(this.grid.onSort, function(e, args){ - var order = (args.sortAsc) ? 'asc':'desc'; - var sort = [{ - field: args.sortCol.field, - order: order - }]; - self.model.query({sort: sort}); - }); - - this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){ - self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')}); - }); - - this.grid.onColumnsResized.subscribe(function(e, args){ - var columns = args.grid.getColumns(); - var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth; - var columnsWidth = []; - _.each(columns,function(column){ - if (column.width != defaultColumnWidth){ - columnsWidth.push({column:column.id,width:column.width}); - } - }); - self.state.set({columnsWidth:columnsWidth}); - }); - - this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) { - // We need to change the model associated value - var grid = args.grid; - var model = data.getModel(args.row); - var field = grid.getColumns()[args.cell].id; - var v = {}; - v[field] = args.item[field]; - model.set(v); - }); - this._slickHandler.subscribe(this.grid.onClick,function(e, args){ - //try catch , because this fail in qunit , but no - //error on browser. - try{e.preventDefault()}catch(e){} - - // The cell of grid that handle row delete is The first cell (0) if - // The grid ReOrder is not present ie enableReOrderRow == false - // else it is The the second cell (1) , because The 0 is now cell - // that handle row Reoder. - var cell =0 - if(self.state.get("gridOptions") - && self.state.get("gridOptions").enableReOrderRow != undefined - && self.state.get("gridOptions").enableReOrderRow == true ){ - cell =1 - } - if (args.cell == cell && self.state.get("gridOptions").enabledDelRow == true){ - // We need to delete the associated model - var model = data.getModel(args.row); - model.destroy() - } - }) ; - var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid, - _.extend(options,{state:this.state})); - if (self.visible){ - self.grid.init(); - self.rendered = true; - } else { - // Defer rendering until the view is visible - self.rendered = false; - } - return this; - }, - - // Row reordering support based on - // https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example9-row-reordering.html - _setupRowReordering: function() { - var self = this; - self.grid.setSelectionModel(new Slick.RowSelectionModel()); - - var moveRowsPlugin = new Slick.RowMoveManager({ - cancelEditOnDrag: true - }); - - moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, data) { - for (var i = 0; i < data.rows.length; i++) { - // no point in moving before or after itself - if (data.rows[i] == data.insertBefore || data.rows[i] == data.insertBefore - 1) { - e.stopPropagation(); - return false; - } - } - return true; - }); - - moveRowsPlugin.onMoveRows.subscribe(function (e, args) { - var extractedRows = [], left, right; - var rows = args.rows; - var insertBefore = args.insertBefore; - - var data = self.model.records.toJSON() - left = data.slice(0, insertBefore); - right= data.slice(insertBefore, data.length); - - rows.sort(function(a,b) { return a-b; }); - - for (var i = 0; i < rows.length; i++) { - extractedRows.push(data[rows[i]]); - } - - rows.reverse(); - - for (var i = 0; i < rows.length; i++) { - var row = rows[i]; - if (row < insertBefore) { - left.splice(row, 1); - } else { - right.splice(row - insertBefore, 1); - } - } - - data = left.concat(extractedRows.concat(right)); - var selectedRows = []; - for (var i = 0; i < rows.length; i++) - selectedRows.push(left.length + i); - - self.model.records.reset(data) - - }); - //register The plugin to handle row Reorder - if(this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) { - self.grid.registerPlugin(moveRowsPlugin); - } - }, - - remove: function () { - this._slickHandler.unsubscribeAll(); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - show: function() { - // If the div is hidden, SlickGrid will calculate wrongly some - // sizes so we must render it explicitly when the view is visible - if (!this.rendered){ - if (!this.grid){ - this.render(); - } - this.grid.init(); - this.rendered = true; - } - this.visible = true; - }, - - hide: function() { - this.visible = false; - } -}); - -// Add new grid Control to display a new row add menu bouton -// It display a simple side-bar menu ,for user to add new -// row to grid -my.GridControl= Backbone.View.extend({ - className: "recline-row-add", - // Template for row edit menu , change it if you don't love - template: '

    ', - - initialize: function(options){ - var self = this; - _.bindAll(this, 'render'); - this.state = new recline.Model.ObjectState(); - this.render(); - }, - - render: function() { - var self = this; - - var tmplData = I18nMessages('recline', recline.View.translations).injectMustache({}); - var formatted = Mustache.render(this.template, tmplData); - this.$el.html(formatted); - }, - - events : { - "click .recline-row-add" : "addNewRow" - }, - - addNewRow : function(e){ - e.preventDefault() - this.state.trigger("change") - } -}); - -})(jQuery, recline.View); - -/* -* Context menu for the column picker, adapted from -* http://mleibman.github.com/SlickGrid/examples/example-grouping -* -*/ -(function ($) { - function SlickColumnPicker(columns, grid, options) { - var $menu; - var columnCheckboxes; - - var defaults = { - fadeSpeed:250 - }; - - function init() { - grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); - options = $.extend({}, defaults, options); - - $menu = $('
  • -
    -
    -

    Multiview Demo

    -

    See the integrated multiview in action on a - sample dataset - the multiview incorporates most the main views - (grid, graph, map etc) into one integrated view with search, filtering - and field summaries.

    -
    -
    -
    - -
    -