From c1a9bc96969a791f99a959ad451294af4656e16a Mon Sep 17 00:00:00 2001 From: Marcin Warpechowski Date: Wed, 12 Jun 2013 15:41:08 +0200 Subject: [PATCH] define Handsontable as Bower dependency; upgrade Handsontable to 0.9.4 --- CHANGELOG.md | 6 + Gruntfile.js | 4 +- components/handsontable/.gitattributes | 2 + components/handsontable/.gitignore | 5 + components/handsontable/.travis.yml | 9 + components/handsontable/CHANGELOG.md | 582 + components/handsontable/CONTRIBUTING.md | 12 + components/handsontable/Gruntfile.js | 214 + components/handsontable/LICENSE | 22 + components/handsontable/README.md | 102 + components/handsontable/bower.json | 8 + components/handsontable/demo/ajax.html | 183 + .../handsontable/demo/autocomplete.html | 303 + components/handsontable/demo/backbone.html | 221 + .../handsontable/demo/beforeKeyDown.html | 156 + components/handsontable/demo/bootstrap.html | 175 + .../bootstrap/css/bootstrap-responsive.css | 1109 ++ .../css/bootstrap-responsive.min.css | 9 + .../demo/bootstrap/css/bootstrap.css | 6158 ++++++++ .../demo/bootstrap/css/bootstrap.min.css | 9 + .../img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../bootstrap/img/glyphicons-halflings.png | Bin 0 -> 12799 bytes .../demo/bootstrap/js/bootstrap.js | 2276 +++ .../demo/bootstrap/js/bootstrap.min.js | 6 + components/handsontable/demo/buttons.html | 147 + components/handsontable/demo/callbacks.html | 229 + .../handsontable/demo/column_resize.html | 196 + components/handsontable/demo/conditional.html | 178 + components/handsontable/demo/contextmenu.html | 298 + components/handsontable/demo/css/samples.css | 353 + components/handsontable/demo/current.html | 143 + components/handsontable/demo/datasources.html | 423 + components/handsontable/demo/date.html | 162 + components/handsontable/demo/dragdown.html | 126 + components/handsontable/demo/fixed.html | 152 + .../demo/github-buttons/README.md | 154 + .../demo/github-buttons/github-btn.html | 243 + .../handsontable/demo/handsontable.html | 169 + .../handsontable/demo/image/fork-github.png | Bin 0 -> 7791 bytes .../handsontable/demo/image/og-image.png | Bin 0 -> 37366 bytes .../backbone/backbone-relational/LICENSE.txt | 22 + .../backbone-relational.js | 1732 +++ .../handsontable/demo/js/backbone/backbone.js | 1508 ++ .../demo/js/backbone/lodash.underscore.js | 3870 +++++ components/handsontable/demo/js/ga.js | 14 + .../handsontable/demo/js/highlight/LICENSE | 24 + .../handsontable/demo/js/highlight/README.md | 139 + .../demo/js/highlight/highlight.pack.js | 1 + .../demo/js/highlight/styles/github.css | 131 + .../demo/js/highlight/styles/zenburn.css | 116 + components/handsontable/demo/js/json2.min.js | 14 + components/handsontable/demo/js/samples.js | 165 + .../handsontable/demo/json/autocomplete.json | 1 + components/handsontable/demo/json/load.json | 8 + components/handsontable/demo/json/save.json | 3 + components/handsontable/demo/legend.html | 16 + components/handsontable/demo/numeric.html | 161 + components/handsontable/demo/options.html | 146 + components/handsontable/demo/pagination.html | 168 + components/handsontable/demo/php.html | 191 + components/handsontable/demo/php/cars.php | 74 + components/handsontable/demo/php/load.php | 34 + components/handsontable/demo/php/save.php | 91 + components/handsontable/demo/prepopulate.html | 182 + components/handsontable/demo/readonly.html | 204 + components/handsontable/demo/renderers.html | 212 + .../handsontable/demo/renderers_html.html | 255 + components/handsontable/demo/scroll.html | 341 + .../handsontable/demo/scroll_native.html | 141 + components/handsontable/demo/search.html | 150 + components/handsontable/demo/sorting.html | 155 + .../demo/understanding_reference.html | 189 + components/handsontable/demo/validation.html | 161 + .../handsontable/demo/web_component.html | 319 + .../demo/web_component/polymer/polymer.min.js | 5 + .../demo/web_component/x-tile.html | 45 + components/handsontable/dist/README.md | 29 + .../handsontable/dist/jquery.handsontable.css | 360 + .../dist}/jquery.handsontable.full.css | 4 +- .../dist}/jquery.handsontable.full.js | 2484 ++-- .../handsontable/dist/jquery.handsontable.js | 9274 ++++++++++++ components/handsontable/dist_wc/README.md | 14 + .../handsontable/dist_wc/x-handsontable.html | 102 + .../dist_wc/x-handsontable/jquery-2.min.js | 6 + .../jquery.handsontable.full.css | 502 + .../jquery.handsontable.full.js | 11713 ++++++++++++++++ .../dist_wc/x-handsontable/numeral.de-de.js | 32 + .../jquery.handsontable.bootstrap.css | 34 + .../jquery.handsontable.removeRow.css | 23 + .../jquery.handsontable.removeRow.js | 46 + .../handsontable/handsontable.jquery.json | 37 + components/handsontable/index.html | 272 + .../handsontable/lib/bootstrap-typeahead.js | 335 + .../jQuery-contextMenu/jquery.contextMenu.css | 142 + .../jQuery-contextMenu/jquery.contextMenu.js | 1585 +++ .../jQuery-contextMenu/jquery.ui.position.js | 517 + components/handsontable/lib/jquery-2.js | 8755 ++++++++++++ components/handsontable/lib/jquery-2.min.js | 6 + .../smoothness/images/animated-overlay.gif | Bin 0 -> 1738 bytes .../ui-bg_diagonals-thick_18_b81900_40x40.png | Bin 0 -> 418 bytes .../ui-bg_diagonals-thick_20_666666_40x40.png | Bin 0 -> 312 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 212 bytes .../images/ui-bg_flat_10_000000_40x100.png | Bin 0 -> 205 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 208 bytes .../images/ui-bg_glass_100_f6f6f6_1x400.png | Bin 0 -> 262 bytes .../images/ui-bg_glass_100_fdf5ce_1x400.png | Bin 0 -> 348 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 335 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 207 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 262 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 262 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 332 bytes .../ui-bg_gloss-wave_35_f6a828_500x100.png | Bin 0 -> 5815 bytes .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin 0 -> 278 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 280 bytes .../ui-bg_highlight-soft_75_ffe45c_1x100.png | Bin 0 -> 328 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 6922 bytes .../images/ui-icons_228ef1_256x240.png | Bin 0 -> 4549 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4549 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 6992 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 6999 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4549 bytes .../images/ui-icons_ef8c08_256x240.png | Bin 0 -> 4549 bytes .../images/ui-icons_ffd27a_256x240.png | Bin 0 -> 4549 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 6299 bytes .../css/smoothness/jquery-ui.custom.css | 649 + .../css/smoothness/jquery-ui.custom.min.css | 5 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes .../images/ui-bg_glass_75_ffffff_1x400.png | Bin 0 -> 107 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 101 bytes .../ui-bg_inset-soft_95_fef1ec_1x100.png | Bin 0 -> 123 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_f6cf3b_256x240.png | Bin 0 -> 8884 bytes .../css/ui-bootstrap/jquery-ui.custom.css | 1614 +++ .../lib/jquery-ui/js/jquery-ui.custom.js | 2340 +++ .../lib/jquery-ui/js/jquery-ui.custom.min.js | 7 + components/handsontable/lib/jquery.js | 9597 +++++++++++++ components/handsontable/lib/jquery.min.js | 5 + components/handsontable/lib/numeral.de-de.js | 32 + components/handsontable/lib/numeral.js | 518 + components/handsontable/package.json | 21 + .../handsontable/src/3rdparty/copypaste.js | 136 + .../src/3rdparty/jquery.autoresize.js | 253 + .../handsontable/src/3rdparty/sheetclip.js | 87 + .../src/3rdparty/walkontable/README.md | 41 + .../3rdparty/walkontable/css/bootstrap.css | 148 + .../3rdparty/walkontable/css/walkontable.css | 153 + .../src/3rdparty/walkontable/index.html | 40 + .../src/3rdparty/walkontable/index.js | 145 + .../src/3rdparty/walkontable/package.json | 12 + .../walkontable/src/3rdparty/dragdealer.js | 597 + .../src/3rdparty/jquery.browser.js | 48 + .../src/3rdparty/jquery.mousewheel.js | 84 + .../src/3rdparty/walkontable/src/border.js | 226 + .../3rdparty/walkontable/src/cellFilter.js | 47 + .../3rdparty/walkontable/src/cellStrategy.js | 22 + .../walkontable/src/classNameCache.js | 21 + .../3rdparty/walkontable/src/columnFilter.js | 32 + .../walkontable/src/columnStrategy.js | 77 + .../src/3rdparty/walkontable/src/core.js | 100 + .../src/3rdparty/walkontable/src/dom.js | 255 + .../src/3rdparty/walkontable/src/event.js | 100 + .../src/3rdparty/walkontable/src/helpers.js | 10 + .../src/3rdparty/walkontable/src/polyfill.js | 64 + .../src/3rdparty/walkontable/src/rowFilter.js | 14 + .../3rdparty/walkontable/src/rowStrategy.js | 37 + .../src/3rdparty/walkontable/src/scroll.js | 172 + .../src/3rdparty/walkontable/src/scrollbar.js | 246 + .../walkontable/src/scrollbarNative.js | 190 + .../3rdparty/walkontable/src/scrollbars.js | 35 + .../src/3rdparty/walkontable/src/selection.js | 90 + .../src/3rdparty/walkontable/src/settings.js | 126 + .../src/3rdparty/walkontable/src/table.js | 615 + .../src/3rdparty/walkontable/src/viewport.js | 130 + .../src/3rdparty/walkontable/src/wheel.js | 39 + .../walkontable/test/jasmine/SpecHelper.js | 45 + .../walkontable/test/jasmine/SpecRunner.html | 103 + .../test/jasmine/lib/jasmine/MIT.LICENSE | 20 + .../test/jasmine/lib/jasmine/jasmine-html.js | 681 + .../test/jasmine/lib/jasmine/jasmine.css | 82 + .../test/jasmine/lib/jasmine/jasmine.js | 2600 ++++ .../jasmine/lib/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes .../test/jasmine/lib/jquery.min.js | 2 + .../test/jasmine/spec/cellFilter.spec.js | 111 + .../test/jasmine/spec/columnFilter.spec.js | 29 + .../test/jasmine/spec/columnStrategy.spec.js | 108 + .../test/jasmine/spec/core.spec.js | 245 + .../walkontable/test/jasmine/spec/dom.spec.js | 154 + .../test/jasmine/spec/event.spec.js | 424 + .../test/jasmine/spec/helpers.spec.js | 40 + .../test/jasmine/spec/rowStrategy.spec.js | 47 + .../test/jasmine/spec/scroll.spec.js | 426 + .../test/jasmine/spec/scrollbar.spec.js | 433 + .../test/jasmine/spec/scrollbarNative.spec.js | 58 + .../test/jasmine/spec/selection.spec.js | 372 + .../test/jasmine/spec/settings.spec.js | 28 + .../test/jasmine/spec/table.spec.js | 896 ++ .../walkontable/test/performance/basic.html | 20 + .../walkontable/test/performance/basic.js | 110 + .../performance/lib/benchmark.js/benchmark.js | 3919 ++++++ .../performance/lib/benchmark.js/nano.jar | Bin 0 -> 293 bytes .../walkontable/test/performance/run.js | 29 + components/handsontable/src/cellTypes.js | 61 + components/handsontable/src/core.js | 2617 ++++ .../src/css/jquery.handsontable.css | 360 + .../src/editors/autocompleteEditor.js | 194 + .../src/editors/checkboxEditor.js | 50 + .../handsontable/src/editors/dateEditor.js | 99 + .../src/editors/handsontableEditor.js | 120 + .../handsontable/src/editors/textEditor.js | 344 + components/handsontable/src/focusCatcher.js | 16 + components/handsontable/src/helpers.js | 222 + components/handsontable/src/intro.js | 19 + components/handsontable/src/outro.js | 1 + components/handsontable/src/pluginHooks.js | 147 + .../src/plugins/autoColumnSize.js | 150 + .../handsontable/src/plugins/columnSorting.js | 77 + .../handsontable/src/plugins/contextMenu.js | 150 + components/handsontable/src/plugins/legacy.js | 39 + .../src/plugins/manualColumnMove.js | 108 + .../src/plugins/manualColumnResize.js | 121 + .../src/plugins/observeChanges.js | 227 + .../src/renderers/autocompleteRenderer.js | 39 + .../src/renderers/checkboxRenderer.js | 67 + .../src/renderers/numericRenderer.js | 24 + .../src/renderers/textRenderer.js | 25 + components/handsontable/src/selectionPoint.js | 41 + components/handsontable/src/tableView.js | 378 + components/handsontable/src/undoRedo.js | 77 + .../src/wc/x-handsontable-controller.js | 77 + .../handsontable/src/wc/x-handsontable.html | 26 + .../handsontable/test/jasmine/SpecRunner.html | 110 + .../test/jasmine/css/SpecRunner.css | 8 + .../test/jasmine/lib/jasmine-jquery.js | 288 + .../test/jasmine/lib/jasmine/MIT.LICENSE | 20 + .../test/jasmine/lib/jasmine/jasmine-html.js | 681 + .../test/jasmine/lib/jasmine/jasmine.css | 82 + .../test/jasmine/lib/jasmine/jasmine.js | 2600 ++++ .../jasmine/lib/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes .../test/jasmine/spec/ColHeaderSpec.js | 73 + .../test/jasmine/spec/Core_alterSpec.js | 477 + .../jasmine/spec/Core_beforeKeyDownSpec.js | 110 + .../jasmine/spec/Core_beforechangeSpec.js | 119 + .../test/jasmine/spec/Core_copySpec.js | 62 + .../test/jasmine/spec/Core_countSpec.js | 32 + .../test/jasmine/spec/Core_dataSchemaSpec.js | 78 + .../test/jasmine/spec/Core_datachangeSpec.js | 240 + .../jasmine/spec/Core_destroyEditorSpec.js | 56 + .../test/jasmine/spec/Core_destroySpec.js | 63 + .../test/jasmine/spec/Core_getCellMetaSpec.js | 93 + .../jasmine/spec/Core_getColHeaderSpec.js | 66 + .../jasmine/spec/Core_getDataAt___Spec.js | 39 + .../jasmine/spec/Core_getRowHeaderSpec.js | 66 + .../test/jasmine/spec/Core_initSpec.js | 55 + .../jasmine/spec/Core_keepEmptyRowsSpec.js | 208 + .../test/jasmine/spec/Core_loadDataSpec.js | 418 + .../test/jasmine/spec/Core_onKeyDownSpec.js | 84 + .../test/jasmine/spec/Core_pasteSpec.js | 117 + .../spec/Core_populateFromArraySpec.js | 111 + .../test/jasmine/spec/Core_renderSpec.js | 65 + .../test/jasmine/spec/Core_selectionSpec.js | 393 + .../jasmine/spec/Core_setDataAtCellSpec.js | 286 + .../test/jasmine/spec/Core_spliceSpec.js | 88 + .../test/jasmine/spec/Core_updateSpec.js | 59 + .../test/jasmine/spec/Core_viewSpec.js | 146 + .../test/jasmine/spec/FillHandleSpec.js | 98 + .../test/jasmine/spec/RowHeaderSpec.js | 73 + .../test/jasmine/spec/SpecHelper.js | 269 + .../test/jasmine/spec/UndoRedoSpec.js | 39 + .../spec/editors/autocompleteEditorSpec.js | 466 + .../spec/editors/handsontableEditorSpec.js | 125 + .../jasmine/spec/editors/numericEditorSpec.js | 47 + .../jasmine/spec/editors/textEditorSpec.js | 185 + .../jasmine/spec/plugins/PluginHooksSpec.js | 150 + .../spec/plugins/autoColumnSizeSpec.js | 61 + .../jasmine/spec/plugins/columnSortingSpec.js | 42 + .../jasmine/spec/plugins/contextMenuSpec.js | 72 + .../spec/plugins/observeChangesSpec.js | 120 + .../spec/renderers/checkboxRendererSpec.js | 62 + .../spec/renderers/numericRendererSpec.js | 56 + .../spec/renderers/textRendererSpec.js | 67 + .../test/jasmine/spec/settings/editorSpec.js | 79 + .../test/jasmine/spec/settings/heightSpec.js | 118 + .../jasmine/spec/settings/rendererSpec.js | 75 + .../performance/lib/benchmark.js/benchmark.js | 3919 ++++++ .../performance/lib/benchmark.js/lodash.js | 4983 +++++++ .../handsontable/test/performance/render.html | 25 + .../handsontable/test/performance/render.js | 960 ++ .../test/performance/run_phantomjs.js | 43 + .../test/performance/textRenderer.html | 24 + .../test/performance/textRenderer.js | 155 + dist/angular-ui-handsontable.full.css | 6 +- dist/angular-ui-handsontable.full.js | 2486 ++-- dist/angular-ui-handsontable.full.min.css | 2 +- dist/angular-ui-handsontable.full.min.js | 13 +- 302 files changed, 118390 insertions(+), 1665 deletions(-) create mode 100644 components/handsontable/.gitattributes create mode 100644 components/handsontable/.gitignore create mode 100644 components/handsontable/.travis.yml create mode 100644 components/handsontable/CHANGELOG.md create mode 100644 components/handsontable/CONTRIBUTING.md create mode 100644 components/handsontable/Gruntfile.js create mode 100644 components/handsontable/LICENSE create mode 100644 components/handsontable/README.md create mode 100644 components/handsontable/bower.json create mode 100644 components/handsontable/demo/ajax.html create mode 100644 components/handsontable/demo/autocomplete.html create mode 100644 components/handsontable/demo/backbone.html create mode 100644 components/handsontable/demo/beforeKeyDown.html create mode 100644 components/handsontable/demo/bootstrap.html create mode 100644 components/handsontable/demo/bootstrap/css/bootstrap-responsive.css create mode 100644 components/handsontable/demo/bootstrap/css/bootstrap-responsive.min.css create mode 100644 components/handsontable/demo/bootstrap/css/bootstrap.css create mode 100644 components/handsontable/demo/bootstrap/css/bootstrap.min.css create mode 100644 components/handsontable/demo/bootstrap/img/glyphicons-halflings-white.png create mode 100644 components/handsontable/demo/bootstrap/img/glyphicons-halflings.png create mode 100644 components/handsontable/demo/bootstrap/js/bootstrap.js create mode 100644 components/handsontable/demo/bootstrap/js/bootstrap.min.js create mode 100644 components/handsontable/demo/buttons.html create mode 100644 components/handsontable/demo/callbacks.html create mode 100644 components/handsontable/demo/column_resize.html create mode 100644 components/handsontable/demo/conditional.html create mode 100644 components/handsontable/demo/contextmenu.html create mode 100644 components/handsontable/demo/css/samples.css create mode 100644 components/handsontable/demo/current.html create mode 100644 components/handsontable/demo/datasources.html create mode 100644 components/handsontable/demo/date.html create mode 100644 components/handsontable/demo/dragdown.html create mode 100644 components/handsontable/demo/fixed.html create mode 100644 components/handsontable/demo/github-buttons/README.md create mode 100644 components/handsontable/demo/github-buttons/github-btn.html create mode 100644 components/handsontable/demo/handsontable.html create mode 100644 components/handsontable/demo/image/fork-github.png create mode 100644 components/handsontable/demo/image/og-image.png create mode 100644 components/handsontable/demo/js/backbone/backbone-relational/LICENSE.txt create mode 100644 components/handsontable/demo/js/backbone/backbone-relational/backbone-relational.js create mode 100644 components/handsontable/demo/js/backbone/backbone.js create mode 100644 components/handsontable/demo/js/backbone/lodash.underscore.js create mode 100644 components/handsontable/demo/js/ga.js create mode 100644 components/handsontable/demo/js/highlight/LICENSE create mode 100644 components/handsontable/demo/js/highlight/README.md create mode 100644 components/handsontable/demo/js/highlight/highlight.pack.js create mode 100644 components/handsontable/demo/js/highlight/styles/github.css create mode 100644 components/handsontable/demo/js/highlight/styles/zenburn.css create mode 100644 components/handsontable/demo/js/json2.min.js create mode 100644 components/handsontable/demo/js/samples.js create mode 100644 components/handsontable/demo/json/autocomplete.json create mode 100644 components/handsontable/demo/json/load.json create mode 100644 components/handsontable/demo/json/save.json create mode 100644 components/handsontable/demo/legend.html create mode 100644 components/handsontable/demo/numeric.html create mode 100644 components/handsontable/demo/options.html create mode 100644 components/handsontable/demo/pagination.html create mode 100644 components/handsontable/demo/php.html create mode 100644 components/handsontable/demo/php/cars.php create mode 100644 components/handsontable/demo/php/load.php create mode 100644 components/handsontable/demo/php/save.php create mode 100644 components/handsontable/demo/prepopulate.html create mode 100644 components/handsontable/demo/readonly.html create mode 100644 components/handsontable/demo/renderers.html create mode 100644 components/handsontable/demo/renderers_html.html create mode 100644 components/handsontable/demo/scroll.html create mode 100644 components/handsontable/demo/scroll_native.html create mode 100644 components/handsontable/demo/search.html create mode 100644 components/handsontable/demo/sorting.html create mode 100644 components/handsontable/demo/understanding_reference.html create mode 100644 components/handsontable/demo/validation.html create mode 100644 components/handsontable/demo/web_component.html create mode 100644 components/handsontable/demo/web_component/polymer/polymer.min.js create mode 100644 components/handsontable/demo/web_component/x-tile.html create mode 100644 components/handsontable/dist/README.md create mode 100644 components/handsontable/dist/jquery.handsontable.css rename {src/3rdparty/handsontable => components/handsontable/dist}/jquery.handsontable.full.css (99%) rename {src/3rdparty/handsontable => components/handsontable/dist}/jquery.handsontable.full.js (85%) create mode 100644 components/handsontable/dist/jquery.handsontable.js create mode 100644 components/handsontable/dist_wc/README.md create mode 100644 components/handsontable/dist_wc/x-handsontable.html create mode 100644 components/handsontable/dist_wc/x-handsontable/jquery-2.min.js create mode 100644 components/handsontable/dist_wc/x-handsontable/jquery.handsontable.full.css create mode 100644 components/handsontable/dist_wc/x-handsontable/jquery.handsontable.full.js create mode 100644 components/handsontable/dist_wc/x-handsontable/numeral.de-de.js create mode 100644 components/handsontable/extensions/jquery.handsontable.bootstrap.css create mode 100644 components/handsontable/extensions/jquery.handsontable.removeRow.css create mode 100644 components/handsontable/extensions/jquery.handsontable.removeRow.js create mode 100644 components/handsontable/handsontable.jquery.json create mode 100644 components/handsontable/index.html create mode 100644 components/handsontable/lib/bootstrap-typeahead.js create mode 100644 components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.css create mode 100644 components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.js create mode 100644 components/handsontable/lib/jQuery-contextMenu/jquery.ui.position.js create mode 100644 components/handsontable/lib/jquery-2.js create mode 100644 components/handsontable/lib/jquery-2.min.js create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/animated-overlay.gif create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_diagonals-thick_18_b81900_40x40.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_diagonals-thick_20_666666_40x40.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_flat_10_000000_40x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_100_f6f6f6_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_100_fdf5ce_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_gloss-wave_35_f6a828_500x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_222222_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_228ef1_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_2e83ff_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_454545_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_888888_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_cd0a0a_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_ef8c08_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_ffd27a_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/images/ui-icons_ffffff_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/jquery-ui.custom.css create mode 100644 components/handsontable/lib/jquery-ui/css/smoothness/jquery-ui.custom.min.css create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_glass_75_ffffff_1x400.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-bg_inset-soft_95_fef1ec_1x100.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-icons_222222_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-icons_2e83ff_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-icons_454545_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-icons_888888_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-icons_cd0a0a_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/images/ui-icons_f6cf3b_256x240.png create mode 100644 components/handsontable/lib/jquery-ui/css/ui-bootstrap/jquery-ui.custom.css create mode 100644 components/handsontable/lib/jquery-ui/js/jquery-ui.custom.js create mode 100644 components/handsontable/lib/jquery-ui/js/jquery-ui.custom.min.js create mode 100644 components/handsontable/lib/jquery.js create mode 100644 components/handsontable/lib/jquery.min.js create mode 100644 components/handsontable/lib/numeral.de-de.js create mode 100644 components/handsontable/lib/numeral.js create mode 100644 components/handsontable/package.json create mode 100644 components/handsontable/src/3rdparty/copypaste.js create mode 100644 components/handsontable/src/3rdparty/jquery.autoresize.js create mode 100644 components/handsontable/src/3rdparty/sheetclip.js create mode 100644 components/handsontable/src/3rdparty/walkontable/README.md create mode 100644 components/handsontable/src/3rdparty/walkontable/css/bootstrap.css create mode 100644 components/handsontable/src/3rdparty/walkontable/css/walkontable.css create mode 100644 components/handsontable/src/3rdparty/walkontable/index.html create mode 100644 components/handsontable/src/3rdparty/walkontable/index.js create mode 100644 components/handsontable/src/3rdparty/walkontable/package.json create mode 100644 components/handsontable/src/3rdparty/walkontable/src/3rdparty/dragdealer.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/3rdparty/jquery.browser.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/3rdparty/jquery.mousewheel.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/border.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/cellFilter.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/cellStrategy.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/classNameCache.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/columnFilter.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/columnStrategy.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/core.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/dom.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/event.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/helpers.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/polyfill.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/rowFilter.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/rowStrategy.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/scroll.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/scrollbar.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/scrollbarNative.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/scrollbars.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/selection.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/settings.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/table.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/viewport.js create mode 100644 components/handsontable/src/3rdparty/walkontable/src/wheel.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/SpecHelper.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/SpecRunner.html create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/lib/jasmine/MIT.LICENSE create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/lib/jasmine/jasmine-html.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/lib/jasmine/jasmine.css create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/lib/jasmine/jasmine.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/lib/jasmine/jasmine_favicon.png create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/lib/jquery.min.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/cellFilter.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/columnFilter.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/columnStrategy.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/core.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/dom.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/event.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/helpers.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/rowStrategy.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/scroll.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/scrollbar.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/scrollbarNative.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/selection.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/settings.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/jasmine/spec/table.spec.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/performance/basic.html create mode 100644 components/handsontable/src/3rdparty/walkontable/test/performance/basic.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/performance/lib/benchmark.js/benchmark.js create mode 100644 components/handsontable/src/3rdparty/walkontable/test/performance/lib/benchmark.js/nano.jar create mode 100644 components/handsontable/src/3rdparty/walkontable/test/performance/run.js create mode 100644 components/handsontable/src/cellTypes.js create mode 100644 components/handsontable/src/core.js create mode 100644 components/handsontable/src/css/jquery.handsontable.css create mode 100644 components/handsontable/src/editors/autocompleteEditor.js create mode 100644 components/handsontable/src/editors/checkboxEditor.js create mode 100644 components/handsontable/src/editors/dateEditor.js create mode 100644 components/handsontable/src/editors/handsontableEditor.js create mode 100644 components/handsontable/src/editors/textEditor.js create mode 100644 components/handsontable/src/focusCatcher.js create mode 100644 components/handsontable/src/helpers.js create mode 100644 components/handsontable/src/intro.js create mode 100644 components/handsontable/src/outro.js create mode 100644 components/handsontable/src/pluginHooks.js create mode 100644 components/handsontable/src/plugins/autoColumnSize.js create mode 100644 components/handsontable/src/plugins/columnSorting.js create mode 100644 components/handsontable/src/plugins/contextMenu.js create mode 100644 components/handsontable/src/plugins/legacy.js create mode 100644 components/handsontable/src/plugins/manualColumnMove.js create mode 100644 components/handsontable/src/plugins/manualColumnResize.js create mode 100644 components/handsontable/src/plugins/observeChanges.js create mode 100644 components/handsontable/src/renderers/autocompleteRenderer.js create mode 100644 components/handsontable/src/renderers/checkboxRenderer.js create mode 100644 components/handsontable/src/renderers/numericRenderer.js create mode 100644 components/handsontable/src/renderers/textRenderer.js create mode 100644 components/handsontable/src/selectionPoint.js create mode 100644 components/handsontable/src/tableView.js create mode 100644 components/handsontable/src/undoRedo.js create mode 100644 components/handsontable/src/wc/x-handsontable-controller.js create mode 100644 components/handsontable/src/wc/x-handsontable.html create mode 100644 components/handsontable/test/jasmine/SpecRunner.html create mode 100644 components/handsontable/test/jasmine/css/SpecRunner.css create mode 100644 components/handsontable/test/jasmine/lib/jasmine-jquery.js create mode 100644 components/handsontable/test/jasmine/lib/jasmine/MIT.LICENSE create mode 100644 components/handsontable/test/jasmine/lib/jasmine/jasmine-html.js create mode 100644 components/handsontable/test/jasmine/lib/jasmine/jasmine.css create mode 100644 components/handsontable/test/jasmine/lib/jasmine/jasmine.js create mode 100644 components/handsontable/test/jasmine/lib/jasmine/jasmine_favicon.png create mode 100644 components/handsontable/test/jasmine/spec/ColHeaderSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_alterSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_beforeKeyDownSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_beforechangeSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_copySpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_countSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_dataSchemaSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_datachangeSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_destroyEditorSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_destroySpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_getCellMetaSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_getColHeaderSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_getDataAt___Spec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_getRowHeaderSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_initSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_keepEmptyRowsSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_loadDataSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_onKeyDownSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_pasteSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_populateFromArraySpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_renderSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_selectionSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_setDataAtCellSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_spliceSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_updateSpec.js create mode 100644 components/handsontable/test/jasmine/spec/Core_viewSpec.js create mode 100644 components/handsontable/test/jasmine/spec/FillHandleSpec.js create mode 100644 components/handsontable/test/jasmine/spec/RowHeaderSpec.js create mode 100644 components/handsontable/test/jasmine/spec/SpecHelper.js create mode 100644 components/handsontable/test/jasmine/spec/UndoRedoSpec.js create mode 100644 components/handsontable/test/jasmine/spec/editors/autocompleteEditorSpec.js create mode 100644 components/handsontable/test/jasmine/spec/editors/handsontableEditorSpec.js create mode 100644 components/handsontable/test/jasmine/spec/editors/numericEditorSpec.js create mode 100644 components/handsontable/test/jasmine/spec/editors/textEditorSpec.js create mode 100644 components/handsontable/test/jasmine/spec/plugins/PluginHooksSpec.js create mode 100644 components/handsontable/test/jasmine/spec/plugins/autoColumnSizeSpec.js create mode 100644 components/handsontable/test/jasmine/spec/plugins/columnSortingSpec.js create mode 100644 components/handsontable/test/jasmine/spec/plugins/contextMenuSpec.js create mode 100644 components/handsontable/test/jasmine/spec/plugins/observeChangesSpec.js create mode 100644 components/handsontable/test/jasmine/spec/renderers/checkboxRendererSpec.js create mode 100644 components/handsontable/test/jasmine/spec/renderers/numericRendererSpec.js create mode 100644 components/handsontable/test/jasmine/spec/renderers/textRendererSpec.js create mode 100644 components/handsontable/test/jasmine/spec/settings/editorSpec.js create mode 100644 components/handsontable/test/jasmine/spec/settings/heightSpec.js create mode 100644 components/handsontable/test/jasmine/spec/settings/rendererSpec.js create mode 100644 components/handsontable/test/performance/lib/benchmark.js/benchmark.js create mode 100644 components/handsontable/test/performance/lib/benchmark.js/lodash.js create mode 100644 components/handsontable/test/performance/render.html create mode 100644 components/handsontable/test/performance/render.js create mode 100644 components/handsontable/test/performance/run_phantomjs.js create mode 100644 components/handsontable/test/performance/textRenderer.html create mode 100644 components/handsontable/test/performance/textRenderer.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b95df402..080672bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Head + +Other: +- define Handsontable as Bower dependency +- upgrade Handsontable to 0.9.4 + ## 0.3.9 (May 15, 2012) Bugfix: diff --git a/Gruntfile.js b/Gruntfile.js index a97c25d5..a03de6f4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -36,13 +36,13 @@ module.exports = function (grunt) { src: [ 'src/ie-shim.js', 'src/angular-ui-handsontable.js', - 'src/3rdparty/handsontable/jquery.handsontable.full.js' + 'components/handsontable/dist/jquery.handsontable.full.js' ], dest: 'dist/angular-ui-handsontable.full.js' }, full_css: { src: [ - 'src/3rdparty/handsontable/jquery.handsontable.full.css' + 'components/handsontable/dist/jquery.handsontable.full.css' ], dest: 'dist/angular-ui-handsontable.full.css' } diff --git a/components/handsontable/.gitattributes b/components/handsontable/.gitattributes new file mode 100644 index 00000000..0fcd6c27 --- /dev/null +++ b/components/handsontable/.gitattributes @@ -0,0 +1,2 @@ +# Set default behaviour, in case users don't have core.autocrlf set. +* text=crlf \ No newline at end of file diff --git a/components/handsontable/.gitignore b/components/handsontable/.gitignore new file mode 100644 index 00000000..f6963c55 --- /dev/null +++ b/components/handsontable/.gitignore @@ -0,0 +1,5 @@ +node_modules +.idea +_SpecRunner.html +.grunt + diff --git a/components/handsontable/.travis.yml b/components/handsontable/.travis.yml new file mode 100644 index 00000000..057ad4a1 --- /dev/null +++ b/components/handsontable/.travis.yml @@ -0,0 +1,9 @@ +language: node_js + +node_js: + - "0.8" + +before_script: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - "npm install -g grunt-cli" diff --git a/components/handsontable/CHANGELOG.md b/components/handsontable/CHANGELOG.md new file mode 100644 index 00000000..b65a2878 --- /dev/null +++ b/components/handsontable/CHANGELOG.md @@ -0,0 +1,582 @@ +## [0.9.4](https://github.com/warpech/jquery-handsontable/tree/v0.9.4) (Jun 7, 2013) + +Bugfixes: +- should scroll to last row with very high rows respecting fixedRows ([#758](https://github.com/warpech/jquery-handsontable/issues/758)) +- Native Scrollbar was listening to window scroll also after table was removed from DOM +- `numeric` cell type now correctly formats negative values and strings with dot character ([#658](https://github.com/warpech/jquery-handsontable/issues/658)) +- fix for returning false on `beforeChange` callback has no effect ([#749](https://github.com/warpech/jquery-handsontable/issues/749)) +- clicking on partially visible cell scrolled the table but did not select the desired cell + +Other: +- [Numeric](http://handsontable.com/demo/numeric.html) cell type demo now shows default (USD) and custom (EUR) locale for currency formatting + +## [0.9.3](https://github.com/warpech/jquery-handsontable/tree/v0.9.3) (Jun 4, 2013) + +Bugfixes: +- scrolling to the last row/column did not work with rows of big height, or columns of big width ([#645](https://github.com/warpech/jquery-handsontable/issues/645), [#698](https://github.com/warpech/jquery-handsontable/issues/698), [#730](https://github.com/warpech/jquery-handsontable/issues/730)) +- fix `observeChanges` in IE9-10 and Firefox (merge Object.observe shim fixes from https://github.com/Starcounter-Jack/JSON-Patch/pull/6) +- initial render was incomplete with Native Scrollbars on +- flat notation of options `cellProperties.renderer`, `cellProperties.editor` did not work as desired ([#713](https://github.com/warpech/jquery-handsontable/issues/713)) +- cut and paste events did not work in a reliable way in Mac Chrome and Safari + +Other: +- make [Callbacks](http://handsontable.com/demo/callbacks.html) demo faster and more convenient +- change async tests to sync where possible + +## [0.9.2](https://github.com/warpech/jquery-handsontable/tree/v0.9.2) (May 28, 2013) + +Features: +- initial release of Native Scrollbars feature (experimental, don't use yet) +- initial release of `observeChanges` option (experimental). Available for all supported browsers except IE 7-8 (uses Object.observe when possible or a custom shim based on [Starcounter-Jack/JSON-Patch](https://github.com/Starcounter-Jack/JSON-Patch)) + +Bugfixes: +- mouse wheel didn't scroll the window when cursor was over Handsontable ([#383](https://github.com/warpech/jquery-handsontable/issues/383), [#627](https://github.com/warpech/jquery-handsontable/issues/627)) +- methods `countVisibleRows` and `countVisibleCols` were broken since version 0.9.0 ([#711](https://github.com/warpech/jquery-handsontable/issues/711)) +- WalkontableDom.prototype.offset now returns offset relatively to the document also for position: fixed +- fix scrolling on border cases when table height was the size of the container + +Docs: +- new demo page [Options](http://handsontable.com/demo/options.html). Currently features one option but this will improve over time +- added new file [CONTRIBUTING.md](CONTRIBUTING.md). Will be updated with more information soon + +## [0.9.1](https://github.com/warpech/jquery-handsontable/tree/v0.9.1) (May 22, 2013) + +Bugfix: +- `beforeKeyDown` event is now also triggered from text editor handler + +Docs: +- demo page [Callbacks](http://handsontable.com/demo/callbacks.html) now features all callbacks +- new demo page for [beforeKeyDown](http://handsontable.com/demo/beforeKeyDown.html) event + +## [0.9.0](https://github.com/warpech/jquery-handsontable/tree/v0.9.0) (May 21, 2013) + +Features: +- cell renderer and editor may now be declared as strings provided they are aliased in the lookup map ([docs](https://github.com/warpech/jquery-handsontable/wiki/Options#column-options), [demo](http://handsontable.com/demo/conditional.html), [#667](https://github.com/warpech/jquery-handsontable/issues/667)) +- new event hook `beforeKeyDown` ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events)) + +Bugfixes: +- fix demo page buttons.html ([#602](https://github.com/warpech/jquery-handsontable/issues/602)) + +Web Component updates: +- update [Web Component demo](http://handsontable.com/) with tiny dashboard +- update Toolkitchen Toolkit to Polymer (@3aec92c) +- use unmodified Handsontable JS and CSS files inside the component +- include all Web Component dependencies inside HTML Import (you just import Polymer and one HTML file!) +- Web Component now uses jQuery 2.0.0 (bundled in), but in future will not depend on jQuery +- remove jQuery UI datepicker from Web Component version (it won't work, as described here: http://bugs.jquery.com/ticket/13342) + +Other: +- date cell type: upgrade to jQuery UI 1.10.3 +- introduce WalkontableViewport abstraction +- fix problem with row header fixed width (now width is dynamically checked) ([#402](https://github.com/warpech/jquery-handsontable/issues/402), [#475](https://github.com/warpech/jquery-handsontable/issues/475)) + +## [0.9.0-beta2](https://github.com/warpech/jquery-handsontable/tree/v0.9.0-beta2) (May 15, 2013) + +Features: +- consistency: now all `on*` events are renamed to `after*` (see [Events](https://github.com/warpech/jquery-handsontable/wiki/Events) wiki page) +- new paste methods ([docs](https://github.com/warpech/jquery-handsontable/wiki/Options)) +- new methods: + - getDataAtRow ([docs](https://github.com/warpech/jquery-handsontable/wiki/Methods)) + - getDataAtCol ([docs](https://github.com/warpech/jquery-handsontable/wiki/Methods)) + - getDataAtProp ([docs](https://github.com/warpech/jquery-handsontable/wiki/Methods)) + - spliceCol ([docs](https://github.com/warpech/jquery-handsontable/wiki/Methods)) + - spliceRow ([docs](https://github.com/warpech/jquery-handsontable/wiki/Methods)) + +Bugfixes: +- fix problem with clearing `animate` function interval ([#456](https://github.com/warpech/jquery-handsontable/issues/456)) +- fix auto resize column when double clicked on `.manualColumnResizer` ([#594](https://github.com/warpech/jquery-handsontable/issues/594)) +- fix undo/redo event handler (change `datachange.handsontable` event to `afterChange` hook) +- fix inline styles in "Edit in JSFiddle" code generation +- fix horizontal scrolling in IE8 ([#583](https://github.com/warpech/jquery-handsontable/issues/583)) + +Other: + +- new demo pages: [Pagination](http://handsontable.com/demo/pagination.html) and [Search](http://handsontable.com/demo/search.html) + +## [0.9.0-beta1](https://github.com/warpech/jquery-handsontable/tree/v0.9.0-beta1) (May 7, 2013) + +Features: +- improved [Options](https://github.com/warpech/jquery-handsontable/wiki/Options) model. Cell properties now inherit from Column properties constructor, which inherit from Handsontable Constructor. This is based on JavaScript prototypal inheritance, rather than `$.extend` as previously +- new [Events](https://github.com/warpech/jquery-handsontable/wiki/Events) architecture (common for callbacks and plugin hooks) +- new events available: + - afterRender ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#597](https://github.com/warpech/jquery-handsontable/issues/597)) + - afterColumnResize ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#557](https://github.com/warpech/jquery-handsontable/issues/557)) + - afterColumnMove ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#557](https://github.com/warpech/jquery-handsontable/issues/557)) + - afterCreateRow ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#122](https://github.com/warpech/jquery-handsontable/issues/122), [#344](https://github.com/warpech/jquery-handsontable/issues/344)) + - afterCreateCol ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#122](https://github.com/warpech/jquery-handsontable/issues/122)) + - afterRemoveRow ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#69](https://github.com/warpech/jquery-handsontable/issues/69), [#227](https://github.com/warpech/jquery-handsontable/issues/227)) + - afterRemoveCol ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events)) + - beforeAutofill ([docs](https://github.com/warpech/jquery-handsontable/wiki/Events), [#200](https://github.com/warpech/jquery-handsontable/issues/200), [#133](https://github.com/warpech/jquery-handsontable/issues/133), [#36](https://github.com/warpech/jquery-handsontable/issues/36)) +- `setDataAtCell` method now accepts `source` string as second parameter (if first parameter is `changes` array) + +Bugfixes: +- fix problem of rendering an unspecified height table with no rows +- `onBeforeChange` now allows to remove one change from array by assigning it null value (`changes[i] = null`) + +Other: +- move `jquery.handsontable.js` and `jquery.handsontable.css` to `dist/` + +## [0.8.23](https://github.com/warpech/jquery-handsontable/tree/v0.8.23) (May 3, 2013) + +Features: +- Handsontable will not be rendered if the container is not attached to DOM or it's style is `display: none` +- new configuration option `observeDOMVisibility` (default `true`) will attempt to rerender it once it is attached to DOM or style changed to `display: block` + +Bugfixes: +- Backbone Collections throw error in loadData ([#606](https://github.com/warpech/jquery-handsontable/issues/606)) +- auto column size did not consider CSS style of each instance separately +- vertical mousewheel scrolling does not work without horizontal scrollbar ([#541](https://github.com/warpech/jquery-handsontable/issues/541)) + +## [0.8.22](https://github.com/warpech/jquery-handsontable/tree/v0.8.22) (Apr 29, 2013) + +Features: +- first version to support [W3C Web Components](http://handsontable.com/demo/web_component.html)! In this proof-of-concept state, better don't bother to follow that link in browser different than Chrome +- text renderer now adds class name `htDimmed` to read only cells +- `TD.htDimmed` rule added to CSS file + +Bugfix: +- table with set `width` and undefined `height` was shrinking on window resize +- onChange fired twice when changing an autocomplete cell (fixes [#260](https://github.com/warpech/jquery-handsontable/issues/260), [#530](https://github.com/warpech/jquery-handsontable/issues/530), [#531](https://github.com/warpech/jquery-handsontable/issues/531), [#534](https://github.com/warpech/jquery-handsontable/issues/534)) + +## [0.8.21](https://github.com/warpech/jquery-handsontable/tree/v0.8.21) (Apr 24, 2013) + +Feature: +- [Fixed rows & columns](http://handsontable.com/demo/fixed.html) + +Bugfix: +- focusCatcher produced a 1x1 px red pixel in top left corner + +## [0.8.20](https://github.com/warpech/jquery-handsontable/tree/v0.8.20) (Apr 19, 2013) + +Bugfixes: +- source parameter should be `edit` when cell value is changed through editor ([#566](https://github.com/warpech/jquery-handsontable/issues/566)) +- getRowHeaders returns NaN when no argument given - should return array of all row headers ([#568](https://github.com/warpech/jquery-handsontable/issues/568), [#352](https://github.com/warpech/jquery-handsontable/issues/352)) +- getColHeaders returns NaN when no argument given - should return array of all column headers ([#569](https://github.com/warpech/jquery-handsontable/issues/569), [#382](https://github.com/warpech/jquery-handsontable/issues/382)) +- selected area class name was not applied on scrolling (tdCache was not bound to instance) +- fix cell focusing problems ([#549](https://github.com/warpech/jquery-handsontable/issues/549), [#506](https://github.com/warpech/jquery-handsontable/issues/506), [#573](https://github.com/warpech/jquery-handsontable/issues/573)) +- currentRowClassName and currentColClassName not kept while scrolling ([#455](https://github.com/warpech/jquery-handsontable/issues/455)) + +Other: +- refactor row and column header DOM operations to be consistently defined in `tableView.js` +- remove `asyncRendering` mode that was causing more trouble than benefit +- as the result, code is now 100 lines shorter and more stable! + +## [0.8.19](https://github.com/warpech/jquery-handsontable/tree/v0.8.19) (Apr 12, 2013) + +Bugfix: +- table width was not correctly read from container width + +## [0.8.18](https://github.com/warpech/jquery-handsontable/tree/v0.8.18) (Apr 12, 2013) + +Features: +- added "Maximize HOT table" button in first example on [Scroll demo](handsontable.com/demo/scroll.html) page ([#495](https://github.com/warpech/jquery-handsontable/issues/495)) + +Bugfixes: +- clicking on checkbox renderer does not change state ([#543](https://github.com/warpech/jquery-handsontable/issues/543)) +- checkbox readonly is not working ([#555](https://github.com/warpech/jquery-handsontable/issues/555)) + +Other: +- refactored Walkontable code that measures column width strategy (for better performance and stability) +- moved reading DOM settings from tableView.js core.js (new method `parseSettingsFromDOM`) + +## [0.8.17](https://github.com/warpech/jquery-handsontable/tree/v0.8.17) (Mar 31, 2013) + +Features: +- performance: remove jQuery dependency from many places +- performance: in cell renderers, replace `innerHTML` with `createTextNode` +- better integration with Twitter Bootstrap (fixes [#78](https://github.com/warpech/jquery-handsontable/issues/78)) + +Bugfixes: +- empty cell not shown in Firefox when not in standards mode ([#418](https://github.com/warpech/jquery-handsontable/issues/418)). This is questionable as a bug but anyway we fixed it. +- continued fix for #461 +- fix problems when removing rows/columns using context menu ([#523](https://github.com/warpech/jquery-handsontable/pull/523)) + +Other: +- new editor inheritance model ([#516](https://github.com/warpech/jquery-handsontable/pull/516)) +- `src/3rdparty/walkontable.js` has been divided into many source files in `src/3rdparty/walkontable/src/*`. Build process is updated to use Walkontable source files now +- 3 Grunt test tasks are available now: `grunt test`, `grunt test:handsontable`, `grunt test:walkontable` + +## [0.8.16](https://github.com/warpech/jquery-handsontable/tree/v0.8.16) (Mar 26, 2013) + +Features: +- [Handsontable in Handsontable editor](http://handsontable.com/demo/handsontable.html) +- performance and code quality fixes +- `height` and `width` settings can now be functions (that return a number) + +Bugfixes: +- autocomplete menu did not reset `
  • ` margin +- `destroy()` of one Handsontable instance made other instances on the same page unfunctional +- Autocomplete (Bootstrap Typeahead) did not allow empty string as a value ([#254](https://github.com/warpech/jquery-handsontable/issues/254)) +- outsideClickDeselects : false disables all focus in other inputs outside the table ([#408](https://github.com/warpech/jquery-handsontable/issues/408)) +- can't scroll all the way horizontally ([#430](https://github.com/warpech/jquery-handsontable/issues/430)) +- scrollable Handsontable does not work well with Twitter Bootstrap ([#224](https://github.com/warpech/jquery-handsontable/issues/224)) +- weirdly shrinking table when height attribute was not set ([#461](https://github.com/warpech/jquery-handsontable/issues/461)) + +Other: +- trying to load a string as a data source now throws an error + +## [0.8.15](https://github.com/warpech/jquery-handsontable/tree/v0.8.15) (Mar 18, 2013) + +Features: +- new callbacks: `onSelectionEnd` and `onSelectionEndByProp` (see [README.md](https://github.com/warpech/jquery-handsontable) for usage information) +- new demo page [Callbacks](http://handsontable.com/demo/callbacks.html) +- **breaking change:** `getSelected` method output has slightly changed. Was: [`topLeftRow`, `topLeftCol`, `bottomRightRow`, `bottomRightCol`]. Is: [`startRow`, `startCol`, `endRow`, `endCol`]. This gives information about the direction of the selection - now you can tell if the selection started from left to right or otherwise. + +Bugfixes: +- `outsideClickDeselects: false` disables all focus in other inputs outside the table ([#408](https://github.com/warpech/jquery-handsontable/issues/408)) +- copy paste in Mac Safari didn't work ([#470](https://github.com/warpech/jquery-handsontable/issues/470)) +- table jumps back and forth when scrolling ([#432](https://github.com/warpech/jquery-handsontable/issues/432)) +- destroy doesn't unload context menu functionality ([#434](https://github.com/warpech/jquery-handsontable/issues/434)) +- throwed error when Enter key was pressed on autocomplete cell in the last row ([#497](https://github.com/warpech/jquery-handsontable/issues/497)) +- Autocomplete in strict mode allows values other than predefined ([#405](https://github.com/warpech/jquery-handsontable/issues/405)) + +## [0.8.14](https://github.com/warpech/jquery-handsontable/tree/v0.8.14) (Mar 14, 2013) + +Features: +- now, your data source can be a function! See the last section of the [Array, object and function data sources](http://handsontable.com/demo/datasources.html) page for examples (fixes [#471](https://github.com/warpech/jquery-handsontable/pull/471), [#435](https://github.com/warpech/jquery-handsontable/pull/435), [#261](https://github.com/warpech/jquery-handsontable/issues/261)) +- also the column `data` property can be a function as well! Again, see the last section of the [data sources](http://handsontable.com/demo/datasources.html) page for examples +- new methods: `countEmptyRows`, `countEmptyCols`, `isEmptyRow`, `isEmptyCol`. You can define your own functions for `isEmptyRow` and `isEmptyCol` (see [README.md](https://github.com/warpech/jquery-handsontable) for details) + +Bugfix: +- replace reference to non-existent Exception with Error ([#494](https://github.com/warpech/jquery-handsontable/pull/494)) +- textarea copyPaste covering page elements ([#488](https://github.com/warpech/jquery-handsontable/issues/488), [#490](https://github.com/warpech/jquery-handsontable/issues/490)) + +## [0.8.13](https://github.com/warpech/jquery-handsontable/tree/v0.8.13) (Mar 12, 2013) + +This release adds a `min-width: 600px` to the test suite to make sure that 100% of current tests are passing in [Travis CI](https://travis-ci.org/warpech/jquery-handsontable) (PhantomJS) as well as in the desktop browsers. + +There are still many bugs to be fixed, but now it should be much easier to keep the increasing quality of future versions. + +## [0.8.12](https://github.com/warpech/jquery-handsontable/tree/v0.8.12) (Mar 12, 2013) + +This release doesn't really bring any improvements for the end user but is a big step towards test automation. From now on, a push to the `master` branch triggers a [Travis CI](https://travis-ci.org/warpech/jquery-handsontable) build that performs automated testing using [grunt-contrib-jasmine](https://github.com/gruntjs/grunt-contrib-jasmine). Thanks @bollwyvl for your [help](https://github.com/warpech/jquery-handsontable/pull/474)! + +As a side effect, tests are now be runnable using PhantomJS. To run the test suite in PhantomJS, type `grunt test` (first run `npm install` to make sure you have all dependencies installed). PhantomJS runs the test suite much faster than desktop browsers, so it may come handy. + +Bugfixes: +- `destroy` method now clears all pending timeouts + +## [0.8.11](https://github.com/warpech/jquery-handsontable/tree/v0.8.11) (Mar 8, 2013) + +Features: +- this may be a **breaking change** for some applications: Now the third parameter to the `alter` method tells the amount of rows/columns to be inserted/removed. This adds more consistency to the API. ([#368](https://github.com/warpech/jquery-handsontable/issues/368), [67fe67c](https://github.com/warpech/jquery-handsontable/commit/67fe67c3cf6bf4df47c02ea8c7aa6802864e97de)) +- merged pull request [#474](https://github.com/warpech/jquery-handsontable/pull/474) - Travis-CI integration. The tests don't pass yet but don't worry about it yet. It is a work in progress on complete test automation. Making all tests pass on Travis CI headless browser may take few more days or weeks :) + +Bugfix: +- again, this may be a **breaking change** for some applications: Fix very long standing inconsistency. Restored original `setDataAtCell` requirement of the second parameter to be a column number (as described in [README.md](https://github.com/warpech/jquery-handsontable) since always). If you happen to experience an error in `setDataAtCell` after upgrade, change your usage of this method to the new method `setDataAtRowProp` ([#284](https://github.com/warpech/jquery-handsontable/issues/284), [90e1b67](https://github.com/warpech/jquery-handsontable/commit/90e1b6765cf3aa6e0e5c5a9156b9d45da74fa055)) +- new methods `setDataAtRowProp` and `getDataAtRowProp` that set/get data according to the property name in data source. See [README.md](https://github.com/warpech/jquery-handsontable) for clarification ([#284](https://github.com/warpech/jquery-handsontable/issues/284), [90e1b67](https://github.com/warpech/jquery-handsontable/commit/90e1b6765cf3aa6e0e5c5a9156b9d45da74fa055)) +- merged pull request [#266](https://github.com/warpech/jquery-handsontable/pull/266) - fix countCols for arrays with column settings + +## [0.8.10](https://github.com/warpech/jquery-handsontable/tree/v0.8.10) (Mar 7, 2013) + +Bugfix: +- inline cell styles applied by cell renderer were not removed on table scroll ([#353](https://github.com/warpech/jquery-handsontable/issues/353), [#376](https://github.com/warpech/jquery-handsontable/issues/376), [a9b328d](https://github.com/warpech/jquery-handsontable/commit/a9b328d2fcbcb7e7a05f1d2494a1f9062bd17a37)) + +Docs: +- simplify CSS for demo pages (get rid of `bottomSpace` classes) +- refresh the style of jsFiddle links + +## [0.8.9](https://github.com/warpech/jquery-handsontable/tree/v0.8.9) (Mar 6, 2013) + +This is a feature release of a new cell type. For people waiting for some bug fixes, I promise next release will focus on fixing some important bugs! + +Features: +- **Date cell type**. Updated pages [Date](http://handsontable.com/demo/date.html) and [Cell types](http://handsontable.com/demo/renderers.html). Solves issue [#431](https://github.com/warpech/jquery-handsontable/issues/431) +- refactored the `text`, `autocomplete` and `date` cell editors to share as much code as possible + +## [0.8.8](https://github.com/warpech/jquery-handsontable/tree/v0.8.8) (Mar 4, 2013) + +Bugfixes: +- `startRows`/`startCols` option did not work as described in README.md. This parameters should be only taken into account when NO data source is provided ([5c865ba](https://github.com/warpech/jquery-handsontable/commit/5c865ba15dcb7d9f93a47b14aedfc96751bcbb7a)) +- finish editing should move the focus aways from textarea to table cell ([f4f6496](https://github.com/warpech/jquery-handsontable/commit/f4f649600311ba527c19dc2e8f73af2e764c54d6)) + +## [0.8.7](https://github.com/warpech/jquery-handsontable/tree/v0.8.7) (Mar 1, 2013) + +Bugfixes: +- use default cell editor for a cell that has declared only cell renderer ([#453](https://github.com/warpech/jquery-handsontable/issues/453), [a5c61a2](https://github.com/warpech/jquery-handsontable/commit/a5c61a26b9e833abd25afa31aee4199cc6810590)) +- copy/paste did not work on Mac since 0.8.6 ([#348](https://github.com/warpech/jquery-handsontable/issues/348), [463d5c9](https://github.com/warpech/jquery-handsontable/commit/463d5c918b85b2dc74df2669047134c23ae15fcc)) +- global shortcuts (like CTRL+A) should be blocked when cell is being edited ([8363008](https://github.com/warpech/jquery-handsontable/commit/8363008c3756ea869f50b2ad8a213839a50ad807)) +- numeric cell editor did not convert cell data to number type when data source was an object ([b09f6d3](https://github.com/warpech/jquery-handsontable/commit/b09f6d3666d238ac0ae849937152baffa6fb2824)) +- another attempt to solve scrolling to the top of table on any key press if the top is not on the screen ([#348](https://github.com/warpech/jquery-handsontable/issues/348), [d37859b](https://github.com/warpech/jquery-handsontable/commit/d37859b6acdaef0be8b886b33404144c7cf21227)) + +## [0.8.6](https://github.com/warpech/jquery-handsontable/tree/v0.8.6) (Feb 27, 2013) + +Features: +- **Numeric cell type**. Updated pages [Numeric](http://handsontable.com/demo/numeric.html) and [Column sorting](http://handsontable.com/demo/sorting.html). Solves issues [#443](https://github.com/warpech/jquery-handsontable/issues/443), [#397](https://github.com/warpech/jquery-handsontable/issues/397), [#336](https://github.com/warpech/jquery-handsontable/issues/336). Partially solves issue [#121](https://github.com/warpech/jquery-handsontable/issues/121) ([a052013](https://github.com/warpech/jquery-handsontable/commit/a052013049ccf6ca75a7104ddeaaec2f84961f93)) +- autoColumnSize feature is now aware of renderer functions (before this, cells rendered with autocomplete and numeric renderer were too narrow) ([7770f8a](https://github.com/warpech/jquery-handsontable/commit/7770f8a499e5c21891308bdb4d1111bfc79dd2dc)) + +Bugfix: +- scrolls to top of table on any key press if the top is not on the screen ([#348](https://github.com/warpech/jquery-handsontable/issues/348), [290cde9](https://github.com/warpech/jquery-handsontable/commit/290cde93503686f1a89e5533662e3c9ca80d880e), [408f29d](https://github.com/warpech/jquery-handsontable/commit/408f29d6a65e797572adf45873780c3adfc0bf4d)) +- cell editor was using a wrong cell value if column order was manually changed ([#367](https://github.com/warpech/jquery-handsontable/issues/367)) +- cell editor dimensions were not refreshed if table was rerendered during editing +- fix recalculation of container dimensions after window resize ([#400](https://github.com/warpech/jquery-handsontable/issues/400), [#428](https://github.com/warpech/jquery-handsontable/issues/428)) + +Other: +- reimplement cell editing abstraction. Now TextEditor `' + + '' + + '' + + ''); + form.css({ + visibility: 'hidden' + }); + $('body').append(form); + form.submit(); + form.remove(); + }); + + bindDumpButton(); + }); + + function bindDumpButton() { + $('body').on('click', 'button[name=dump]', function () { + var dump = $(this).data('dump'); + var $container = $(dump); + console.log('data of ' + dump, $container.handsontable('getData')); + }); + } +})(jQuery); \ No newline at end of file diff --git a/components/handsontable/demo/json/autocomplete.json b/components/handsontable/demo/json/autocomplete.json new file mode 100644 index 00000000..c5e1bae1 --- /dev/null +++ b/components/handsontable/demo/json/autocomplete.json @@ -0,0 +1 @@ +["Acura","Audi","BMW","Buick","Cadillac","Chevrolet","Chrysler","Citroen","Dodge","Eagle","Ferrari","Ford","General Motors","GMC","Honda","Hummer","Hyundai","Infiniti","Isuzu","Jaguar","Jeep","Kia","Lamborghini","Land Rover","Lexus","Lincoln","Lotus","Mazda","Mercedes-Benz","Mercury","Mitsubishi","Nissan","Oldsmobile","Peugeot","Pontiac","Porsche","Regal","Renault","Saab","Saturn","Seat","Skoda","Subaru","Suzuki","Toyota","Volkswagen","Volvo"] \ No newline at end of file diff --git a/components/handsontable/demo/json/load.json b/components/handsontable/demo/json/load.json new file mode 100644 index 00000000..33c464ac --- /dev/null +++ b/components/handsontable/demo/json/load.json @@ -0,0 +1,8 @@ +{ + "data": [ + ["", "Kia", "Nissan", "Toyota", "Honda"], + ["2008", 10, 11, 12, 13], + ["2009", 20, 11, 14, 13], + ["2010", 30, 15, 12, 13] + ] +} \ No newline at end of file diff --git a/components/handsontable/demo/json/save.json b/components/handsontable/demo/json/save.json new file mode 100644 index 00000000..45c565f8 --- /dev/null +++ b/components/handsontable/demo/json/save.json @@ -0,0 +1,3 @@ +{ + "result": "ok" +} \ No newline at end of file diff --git a/components/handsontable/demo/legend.html b/components/handsontable/demo/legend.html new file mode 100644 index 00000000..a2326c0e --- /dev/null +++ b/components/handsontable/demo/legend.html @@ -0,0 +1,16 @@ + + + + + Legend - Handsontable + + + + + +

    Legend was removed in Handsontable 0.7.0

    + +

    Instead, please use cell renderers. Also see conditional formatting tips

    + + + \ No newline at end of file diff --git a/components/handsontable/demo/numeric.html b/components/handsontable/demo/numeric.html new file mode 100644 index 00000000..85fff286 --- /dev/null +++ b/components/handsontable/demo/numeric.html @@ -0,0 +1,161 @@ + + + + + Numeric cell type - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    + + +

    Numeric cell type

    + +

    By default, Handsontable treats all cell values as string type. This is because <textarea> + returns a string as its value.

    + +

    In many cases you will prefer cell values to be treated as number type. This allows to format + numbers nicely and sort them correctly.

    + +

    To trigger the Numeric cell type, use the option type: 'numeric' in columns array + or + cells function.

    + +

    Numeric cell type uses Numeral.js as the formatting library. Head over + to + their website to learn about the formatting syntax.

    + +

    To use number formatting style valid for your language (i18n), load language definition to Numeral.js. See + "Languages" section in Numeral.js docs for more info.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/options.html b/components/handsontable/demo/options.html new file mode 100644 index 00000000..4215f08f --- /dev/null +++ b/components/handsontable/demo/options.html @@ -0,0 +1,146 @@ + + + + + Options - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    + + +

    Options

    + +

    + This demo lets you play with some table options (currently only pasteMode, but more to come). +

    + +

    + Use the drop-down menu under the table to change Handsontable options live. +

    + +
    + +

    + +

    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/pagination.html b/components/handsontable/demo/pagination.html new file mode 100644 index 00000000..51cf51a5 --- /dev/null +++ b/components/handsontable/demo/pagination.html @@ -0,0 +1,168 @@ + + + + + Pagination - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fork me on GitHub + + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    + + +

    Pagination

    + +
    + + + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/php.html b/components/handsontable/demo/php.html new file mode 100644 index 00000000..da8924b7 --- /dev/null +++ b/components/handsontable/demo/php.html @@ -0,0 +1,191 @@ + + + + + PHP example - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    PHP example

    +
    +
    +
    + +
    +
    +
    +

    This page loads and saves data on server. In this example, client side uses $.ajax. Server side uses + PHP with PDO (SQLite).

    + +

    Please note. This page and the PHP scripts are a work in progress. They are not yet configured on GitHub. + Please run it on your own localhost.

    + +

    + + + +

    + +
    Click "Load" to load data from server
    + +
    + +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/php/cars.php b/components/handsontable/demo/php/cars.php new file mode 100644 index 00000000..324fa78f --- /dev/null +++ b/components/handsontable/demo/php/cars.php @@ -0,0 +1,74 @@ + \ No newline at end of file diff --git a/components/handsontable/demo/php/load.php b/components/handsontable/demo/php/load.php new file mode 100644 index 00000000..1be06d2f --- /dev/null +++ b/components/handsontable/demo/php/load.php @@ -0,0 +1,34 @@ +exec("CREATE TABLE IF NOT EXISTS cars (id INTEGER PRIMARY KEY, manufacturer TEXT, year INTEGER, price INTEGER)"); + + //select all data from the table + $select = $db->prepare('SELECT * FROM cars ORDER BY id ASC LIMIT 100'); + $select->execute(); + + $out = array( + 'cars' => $select->fetchAll(PDO::FETCH_ASSOC) + ); + echo json_encode($out); + + // close the database connection + $db = NULL; +} +catch (PDOException $e) { + print 'Exception : ' . $e->getMessage(); +} +?> \ No newline at end of file diff --git a/components/handsontable/demo/php/save.php b/components/handsontable/demo/php/save.php new file mode 100644 index 00000000..6514500d --- /dev/null +++ b/components/handsontable/demo/php/save.php @@ -0,0 +1,91 @@ +exec("CREATE TABLE IF NOT EXISTS cars (id INTEGER PRIMARY KEY, manufacturer TEXT, year INTEGER, price INTEGER)"); + + $colMap = array( + 0 => 'manufacturer', + 1 => 'year', + 2 => 'price' + ); + + if ($_POST['changes']) { + foreach ($_POST['changes'] as $change) { + $rowId = $change[0] + 1; + $colId = $change[1]; + $newVal = $change[3]; + + if (!isset($colMap[$colId])) { + echo "\n spadam"; + continue; + } + + $select = $db->prepare('SELECT id FROM cars WHERE id=? LIMIT 1'); + $select->execute(array( + $rowId + )); + + if ($row = $select->fetch()) { + $query = $db->prepare('UPDATE cars SET `' . $colMap[$colId] . '` = :newVal WHERE id = :id'); + } else { + $query = $db->prepare('INSERT INTO cars (id, `' . $colMap[$colId] . '`) VALUES(:id, :newVal)'); + } + $query->bindValue(':id', $rowId, PDO::PARAM_INT); + $query->bindValue(':newVal', $newVal, PDO::PARAM_STR); + $query->execute(); + } + } elseif ($_POST['data']) { + $select = $db->prepare('DELETE FROM cars'); + $select->execute(); + + for ($r = 0, $rlen = count($_POST['data']); $r < $rlen; $r++) { + $rowId = $r + 1; + for ($c = 0, $clen = count($_POST['data'][$r]); $c < $clen; $c++) { + if (!isset($colMap[$c])) { + continue; + } + + $newVal = $_POST['data'][$r][$c]; + + $select = $db->prepare('SELECT id FROM cars WHERE id=? LIMIT 1'); + $select->execute(array( + $rowId + )); + + if ($row = $select->fetch()) { + $query = $db->prepare('UPDATE cars SET `' . $colMap[$c] . '` = :newVal WHERE id = :id'); + } else { + $query = $db->prepare('INSERT INTO cars (id, `' . $colMap[$c] . '`) VALUES(:id, :newVal)'); + } + $query->bindValue(':id', $rowId, PDO::PARAM_INT); + $query->bindValue(':newVal', $newVal, PDO::PARAM_STR); + $query->execute(); + } + } + } + + $out = array( + 'result' => 'ok' + ); + echo json_encode($out); + + // close the database connection + $db = NULL; +} +catch (PDOException $e) { + print 'Exception : ' . $e->getMessage(); +} +?> \ No newline at end of file diff --git a/components/handsontable/demo/prepopulate.html b/components/handsontable/demo/prepopulate.html new file mode 100644 index 00000000..22adf8ac --- /dev/null +++ b/components/handsontable/demo/prepopulate.html @@ -0,0 +1,182 @@ + + + + + Pre-populate new rows from template - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    +

    Pre-populate new rows from template

    + +

    Below example shows how cell type renderers can be used to present the template values for empty rows.

    + +

    When any cell in the empty row is edited, the + onChange callback fills the row with the template values.

    + +
    + + + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/readonly.html b/components/handsontable/demo/readonly.html new file mode 100644 index 00000000..72cb6770 --- /dev/null +++ b/components/handsontable/demo/readonly.html @@ -0,0 +1,204 @@ + + + + + Read-only cells - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    Read-only cells

    + +

    This page shows ways to configure columns or cells to be read only:

    + + +
    +
    +
    + +
    +
    +
    + + +

    Read-only columns

    + +

    In many usage cases, you will need to configure a certain column to be read only. This column will be + available for keyboard navigation and CTRL+C. Only editing and pasting data will be disabled.

    + +

    To make a column read-only, declare it in the columns setting. You can also define a special + renderer function that will dim the read-only values.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + + +

    Read-only specific cells

    + +

    This example makes cells that contain the word "Nissan" read only.

    + +

    It forces all cells to be rendered by myReadonlyRenderer, which will decide wheather a cell is + really read only by checking its readOnly property.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/renderers.html b/components/handsontable/demo/renderers.html new file mode 100644 index 00000000..7deb7ed9 --- /dev/null +++ b/components/handsontable/demo/renderers.html @@ -0,0 +1,212 @@ + + + + + Cell types - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    Cell types

    + +

    This page is an introduction to Handsontable cell types:

    + + +
    +
    +
    + +
    +
    +
    + + +

    Preview of built-in and custom cell types

    + +

    The below example shows all built-in cell types (in other words, combinations of cell renderers and + editors) + available in Handsontable:

    + + + +

    The same example also shows the declaration of custom cell renderers, namely yellowRenderer + and + greenRenderer. +

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + + +

    Anatomy of a cell type

    + +

    A cell type is a predefined set of cell properties. Cell type defines what renderer + and editor should be used for a cell. They can also define any different cell property that + will be assumed for each matching cell.

    + +

    For example writing:

    + +
    
    +        columns: [
    +          {
    +            type: 'text'
    +          }
    +        ]
    +        
    + +

    Equals:

    + +
    
    +        columns: [
    +          {
    +            renderer: Handsontable.TextRenderer,
    +            editor: Handsontable.TextEditor
    +          }
    +        ]
    +        
    + + This mapping is defined in file src/cellTypes.js +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/renderers_html.html b/components/handsontable/demo/renderers_html.html new file mode 100644 index 00000000..53063c38 --- /dev/null +++ b/components/handsontable/demo/renderers_html.html @@ -0,0 +1,255 @@ + + + + + Custom HTML in cells and headers - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    Custom HTML in cells and headers

    + +

    On this page:

    + + +
    +
    +
    + +
    +
    +
    + + +

    Rendering custom HTML in cells

    + +

    This example shows how to use custom cell renderers to display HTML content in a cell.

    + +

    This is a very powerful feature. Just remember to escape any HTML code that could be used for XSS + attacks.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + + +

    Rendering custom HTML in header

    + +

    You can also put HTML into row and column headers.

    + +

    If you need to attach events to DOM elements like the checkbox below, just remember to identify the element + by class name, not by id. This is because row and column headers are duplicated in the DOM tree and id + attribute must be unique.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/scroll.html b/components/handsontable/demo/scroll.html new file mode 100644 index 00000000..6a588ab2 --- /dev/null +++ b/components/handsontable/demo/scroll.html @@ -0,0 +1,341 @@ + + + + + Scroll - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    Scrollbars

    + +

    This page shows how to configure Handsontable scrollbars:

    + + +
    +
    +
    + +
    +
    +
    + + +

    Vertical and horizontal scrollbars

    + +

    If you want scrollbars, just set container width, height and overflow: scroll in CSS.

    + +

    + +

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + + +

    Single scrollbar (stretch last column)

    + +

    It is also possible to configure only a single scrollbar. The following example creates one by specifying + only the container height and overflow: auto in CSS.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + + +

    Single scrollbar (stretch all columns)

    + +

    If the table content is not as wide as the container width, the table will be stretched to the container + width. The default horizontal stretch model is to stretch the last column only (by using stretchH: + 'last' option).

    + +

    Other possible stretch modes are all (stretches all columns equally, used in the below example) + and none (not stretching).

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + + +

    Single scrollbar (hybrid mode - default)

    + +

    Sometimes you don't know if horizontal scroll bar will be needed. That's why stretchH: 'hybrid' is + the default column stretching mode

    + +

    When horizontal scrollbar is NOT visible, it acts as stretchH: 'none'. When horizontal scrollbar + is visible, it acts as stretchH: 'last'.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/scroll_native.html b/components/handsontable/demo/scroll_native.html new file mode 100644 index 00000000..7c25d42e --- /dev/null +++ b/components/handsontable/demo/scroll_native.html @@ -0,0 +1,141 @@ + + + + + Native Scrollbar - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    + + +

    Native Scrollbar

    + +

    This demo shows table of 1000 rows. Only visible part is rendered. Native window scrollbar is used to scroll through the table.

    + +

    This feature is considered experimental - not for production use.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/search.html b/components/handsontable/demo/search.html new file mode 100644 index 00000000..c19570da --- /dev/null +++ b/components/handsontable/demo/search.html @@ -0,0 +1,150 @@ + + + + + Search - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fork me on GitHub + + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    + + +

    Search

    + + + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/sorting.html b/components/handsontable/demo/sorting.html new file mode 100644 index 00000000..fb6ff10d --- /dev/null +++ b/components/handsontable/demo/sorting.html @@ -0,0 +1,155 @@ + + + + + Column sorting - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    + + +

    Column sorting

    + +

    To enable this experimental feature, use setting columnSorting: true

    + +

    Click on a column header to sort.

    + +

    Only the table view is sorted. The data source remains in the original order.

    + +
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/understanding_reference.html b/components/handsontable/demo/understanding_reference.html new file mode 100644 index 00000000..54bcbcf2 --- /dev/null +++ b/components/handsontable/demo/understanding_reference.html @@ -0,0 +1,189 @@ + + + + + Understanding binding as reference - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    Understanding binding as reference

    +
    +
    +
    + +
    +
    +
    +

    Handsontable binds to your data source (array or object) by reference. Therefore, all the data entered in + the + grid will alter the original data source.

    + +

    In complex applications, you may have a purpose to change data source programatically (outside of + Handsontable). A value change that was done programatically will not be presented on the screen unless you + refresh the grid on screen using the render method.

    + +
    + +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    +

    But I want to change my data without rendering changes!

    +
    +
    +
    + +
    +
    +
    +

    In case you want to keep separate working copy of data for Handsontable, it is suggested to clone the data + source before you load it to Handsontable.

    + +

    This can easily be done with $.extend method.

    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    +

    In a similar way, you may find it useful to clone data before saving it.

    + +

    That would be useful to make programmatic changes that would be saved to server but kept not invisible to + the + user.

    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/validation.html b/components/handsontable/demo/validation.html new file mode 100644 index 00000000..b0e434dd --- /dev/null +++ b/components/handsontable/demo/validation.html @@ -0,0 +1,161 @@ + + + + + Validation - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    +
    +
    +
    + +
    +
    +
    +

    Validation

    + +

    Use the + onBeforeChange callback to validate changes before they are applied to the table. +

    + +
    +
    onBeforeChange (changes: Array, source: String)
    +
    Callback fired before one or more cells is changed. Its main purpose is to validate the input. Parameters: +
      +
    • + changes is a 2D array containing information about each of the edited cells [ [row, prop, oldVal, newVal], ... ]. +
        +
      • To disregard a single change, set changes[i] to null or remove it from array using changes.splice(i, 1).
      • +
      • To alter a single change, overwrite the desired value to changes[i][3].
      • +
      • To cancel all edit, return false from the callback or set array length to 0 (changes.length = 0).
      +
    • +
    • + source is the name of a source of changes. +
    • +
    +
    +
    + +
    + +

    Callback console: [[row, col, oldValue, newValue], ...]

    + +
    Edit the above grid to see callback
    + +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/web_component.html b/components/handsontable/demo/web_component.html new file mode 100644 index 00000000..f4ebee28 --- /dev/null +++ b/components/handsontable/demo/web_component.html @@ -0,0 +1,319 @@ + + + + + Web Component - Handsontable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fork me on GitHub + +
    +
    + +
    +
    +
    +

    Handsontable

    + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +

    Web Component

    + +

    This page shows our effort to make a Web Component-compatible version of Handsontable.

    + +

    Our Web Component implementation leverages Toolkitchen Toolkit library. Currently, it works correctly + only in Google Chrome. According to the browser compatibility matrix, it should also + be possible to run it in Firefox, IE10, Safari (though right now it produces Assertion failed error + in every browser except Chrome). +

    + +

    All your help (review, comments, pull requests) in this area is very welcome. Please use the GitHub Issues + ticket #603 to share your + thoughts.

    +
    +
    +
    + + + + + + +
    +
    +

    For more examples, head back to the main page.

    + +

    Handsontable © 2012 Marcin Warpechowski and contributors.
    Code and documentation + licensed under the The MIT License.

    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/components/handsontable/demo/web_component/polymer/polymer.min.js b/components/handsontable/demo/web_component/polymer/polymer.min.js new file mode 100644 index 00000000..89a05346 --- /dev/null +++ b/components/handsontable/demo/web_component/polymer/polymer.min.js @@ -0,0 +1,5 @@ +function PointerGestureEvent(e,t){var n=t||{},r=document.createEvent("Event"),i={bubbles:!0,cancelable:!0};return Object.keys(i).forEach(function(e){e in n&&(i[e]=n[e])}),r.initEvent(e,i.bubbles,i.cancelable),Object.keys(n).forEach(function(e){r[e]=t[e]}),r.preventTap=this.preventTap,r}if(window.Platform=window.Platform||{},window.logFlags=window.logFlags||{},function(e){var t=e.flags||{};location.search.slice(1).split("&").forEach(function(e){e=e.split("="),e[0]&&(t[e[0]]=e[1]||!0)}),t.shadow=(t.shadowdom||t.shadow||t.polyfill||!HTMLElement.prototype.webkitCreateShadowRoot)&&"polyfill",e.flags=t}(Platform),"polyfill"===Platform.flags.shadow){var SideTable;"undefined"!=typeof WeakMap&&0>navigator.userAgent.indexOf("Firefox/")?SideTable=WeakMap:function(){var e=Object.defineProperty,t=Object.hasOwnProperty,n=(new Date).getTime()%1e9;SideTable=function(){this.name="__st"+(1e9*Math.random()>>>0)+(n++ +"__")},SideTable.prototype={set:function(t,n){e(t,this.name,{value:n,writable:!0})},get:function(e){return t.call(e,this.name)?e[this.name]:void 0},"delete":function(e){this.set(e,void 0)}}}();var ShadowDOMPolyfill={};(function(e){"use strict";function t(e){if(!e)throw Error("Assertion failed")}function n(e,t){return Object.getOwnPropertyNames(t).forEach(function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}),e}function r(e,t){return Object.getOwnPropertyNames(t).forEach(function(n){switch(n){case"arguments":case"caller":case"length":case"name":case"prototype":case"toString":return}Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}),e}function i(e){var t=e.__proto__||Object.getPrototypeOf(e),n=S.get(t);if(n)return n;var r=i(t),o=h(r);return u(t,o,e),o}function o(e,t){s(e,t,!0)}function a(e,t){s(t,e,!1)}function s(e,t,n){Object.getOwnPropertyNames(e).forEach(function(r){if(!(r in t)){O&&e.__lookupGetter__(r);var i;try{i=Object.getOwnPropertyDescriptor(e,r)}catch(o){i=C}var a,s;if(n&&"function"==typeof i.value)return t[r]=function(){return this.impl[r].apply(this.impl,arguments)},void 0;a=function(){return this.impl[r]},(i.writable||i.set)&&(s=function(e){this.impl[r]=e}),Object.defineProperty(t,r,{get:a,set:s,configurable:i.configurable,enumerable:i.enumerable})}})}function l(e,t,n){var i=e.prototype;u(i,t,n),r(t,e)}function u(e,n,r){var i=n.prototype;t(void 0===S.get(e)),S.set(e,n),o(e,i),r&&a(i,r)}function c(e,t){return S.get(t.prototype)===e}function d(e){var t=Object.getPrototypeOf(e),n=i(t),r=h(n);return u(t,r,e),r}function h(e){function t(t){e.call(this,t)}return t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t}function p(e){return e instanceof P.EventTarget||e instanceof P.Event||e instanceof P.DOMImplementation}function f(e){return e instanceof N||e instanceof _||e instanceof D||e instanceof L}function v(e){if(null===e)return null;t(f(e));var n=M.get(e);if(!n){var r=i(e);n=new r(e),M.set(e,n)}return n}function m(e){return null===e?null:(t(p(e)),e.impl)}function g(e){return e&&p(e)?m(e):e}function b(e){return e&&!p(e)?v(e):e}function y(e,n){null!==n&&(t(f(e)),t(void 0===n||p(n)),M.set(e,n))}function w(e,t,n){Object.defineProperty(e.prototype,t,{get:n,configurable:!0,enumerable:!0})}function E(e,t){w(e,t,function(){return v(this.impl[t])})}function T(e,t){e.forEach(function(e){t.forEach(function(t){e.prototype[t]=function(){var e=v(this);return e[t].apply(e,arguments)}})})}var M=new SideTable,S=new SideTable,P=Object.create(null);Object.getOwnPropertyNames(window);var O=/Firefox/.test(navigator.userAgent),C={get:function(){},set:function(){},configurable:!0,enumerable:!0},L=DOMImplementation,_=Event,N=Node,D=Window;e.assert=t,e.defineGetter=w,e.defineWrapGetter=E,e.forwardMethodsToWrapper=T,e.isWrapperFor=c,e.mixin=n,e.registerObject=d,e.registerWrapper=l,e.rewrap=y,e.unwrap=m,e.unwrapIfNeeded=g,e.wrap=v,e.wrapIfNeeded=b,e.wrappers=P})(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e instanceof k.ShadowRoot}function n(e){var t=e.localName;return"content"===t||"shadow"===t}function r(e){return!!e.shadowRoot}function i(e){var t;return e.parentNode||(t=e.defaultView)&&H(t)||null}function o(o,a,s){if(s.length)return s.shift();if(t(o))return o.insertionParent||e.getHostForShadowRoot(o);var l=e.eventParentsTable.get(o);if(l){for(var u=1;l.length>u;u++)s[u-1]=l[u];return l[0]}if(a&&n(o)){var c=o.parentNode;if(c&&r(c))for(var d=e.getShadowTrees(c),h=a.insertionParent,u=0;d.length>u;u++)if(d[u].contains(h))return h}return i(o)}function a(e){for(var r=[],i=e,a=[],l=[];i;){var u=null;if(n(i)){u=s(r);var c=r[r.length-1]||i;r.push(c)}else r.length||r.push(i);var d=r[r.length-1];a.push({target:d,currentTarget:i}),t(i)&&r.pop(),i=o(i,u,l)}return a}function s(e){for(var t=e.length-1;t>=0;t--)if(!n(e[t]))return e[t];return null}function l(r,i){for(var a=[];r;){for(var l=[],c=i,h=void 0;c;){var p=null;if(l.length){if(n(c)&&(p=s(l),u(h))){var f=l[l.length-1];l.push(f)}}else l.push(c);if(d(c,r))return l[l.length-1];t(c)&&l.pop(),h=c,c=o(c,p,a)}r=t(r)?e.getHostForShadowRoot(r):r.parentNode}}function u(e){return e.insertionParent}function c(e){for(var t;t=e.parentNode;)e=t;return e}function d(e,t){return c(e)===c(t)}function h(e){switch(e){case"DOMAttrModified":case"DOMAttributeNameChanged":case"DOMCharacterDataModified":case"DOMElementNameChanged":case"DOMNodeInserted":case"DOMNodeInsertedIntoDocument":case"DOMNodeRemoved":case"DOMNodeRemovedFromDocument":case"DOMSubtreeModified":return!0}return!1}function p(t){if(!I.get(t)){I.set(t,!0),h(t.type)||e.renderAllPending();var n=H(t.target),r=H(t);return f(r,n)}}function f(e,t){var n=a(t);return"load"===e.type&&2===n.length&&n[0].target instanceof k.Document&&n.shift(),v(e,n)&&m(e,n)&&g(e,n),B.set(e,w.NONE),F.set(e,null),e.defaultPrevented}function v(e,t){for(var n,r=t.length-1;r>0;r--){var i=t[r].target,o=t[r].currentTarget;if(i!==o&&(n=w.CAPTURING_PHASE,!b(t[r],e,n)))return!1}return!0}function m(e,t){var n=w.AT_TARGET;return b(t[0],e,n)}function g(e,t){for(var n,r=e.bubbles,i=1;t.length>i;i++){var o=t[i].target,a=t[i].currentTarget;if(o===a)n=w.AT_TARGET;else{if(!r||Y.get(e))continue;n=w.BUBBLING_PHASE}if(!b(t[i],e,n))return}}function b(e,t,n){var r=e.target,i=e.currentTarget,o=R.get(i);if(!o)return!0;if("relatedTarget"in t){var a=x(t),s=H(a.relatedTarget),u=l(i,s);if(u===r)return!0;U.set(t,u)}B.set(t,n);var c=t.type,d=!1;j.set(t,r),F.set(t,i);for(var h=0;o.length>h;h++){var p=o[h];if(p.removed)d=!0;else if(!(p.type!==c||!p.capture&&n===w.CAPTURING_PHASE||p.capture&&n===w.BUBBLING_PHASE))try{if("function"==typeof p.handler?p.handler.call(i,t):p.handler.handleEvent(t),Y.get(t))return!1}catch(f){window.onerror?window.onerror(f.message):console.error(f)}}if(d){var v=o.slice();o.length=0;for(var h=0;v.length>h;h++)v[h].removed||o.push(v[h])}return!q.get(t)}function y(e,t,n){this.type=e,this.handler=t,this.capture=Boolean(n)}function w(e,t){return e instanceof W?(this.impl=e,void 0):H(S(W,"Event",e,t))}function E(e){return e&&e.relatedTarget?Object.create(e,{relatedTarget:{value:x(e.relatedTarget)}}):e}function T(e,t,n){var r=window[e],i=function(t,n){return t instanceof r?(this.impl=t,void 0):H(S(r,e,t,n))};return i.prototype=Object.create(t.prototype),n&&D(i.prototype,n),r&&A(r,i,document.createEvent(e)),i}function M(e,t){return function(){arguments[t]=x(arguments[t]);var n=x(this);n[e].apply(n,arguments)}}function S(e,t,n,r){if(et)return new e(n,E(r));var i=x(document.createEvent(t)),o=Z[t],a=[n];return Object.keys(o).forEach(function(e){var t=null!=r&&e in r?r[e]:o[e];"relatedTarget"===e&&(t=x(t)),a.push(t)}),i["init"+t].apply(i,a),i}function P(e){return"function"==typeof e?!0:e&&e.handleEvent}function O(e){this.impl=e}function C(t){return t instanceof k.ShadowRoot&&(t=e.getHostForShadowRoot(t)),x(t)}function L(e){N(e,rt)}function _(t,n,r,i){e.renderAllPending();for(var o=H(it.call(n.impl,r,i)),s=a(o,this),l=0;s.length>l;l++){var u=s[l];if(u.currentTarget===t)return u.target}return null}var N=e.forwardMethodsToWrapper,D=e.mixin,A=e.registerWrapper,x=e.unwrap,H=e.wrap,k=e.wrappers;new SideTable;var R=new SideTable,I=new SideTable,j=new SideTable,F=new SideTable,U=new SideTable,B=new SideTable,q=new SideTable,Y=new SideTable;y.prototype={equals:function(e){return this.handler===e.handler&&this.type===e.type&&this.capture===e.capture},get removed(){return null===this.handler},remove:function(){this.handler=null}};var W=window.Event;w.prototype={get target(){return j.get(this)},get currentTarget(){return F.get(this)},get eventPhase(){return B.get(this)},stopPropagation:function(){q.set(this,!0)},stopImmediatePropagation:function(){q.set(this,!0),Y.set(this,!0)}},A(W,w,document.createEvent("Event"));var V=T("UIEvent",w),G=T("CustomEvent",w),X={get relatedTarget(){return U.get(this)||H(x(this).relatedTarget)}},z=D({initMouseEvent:M("initMouseEvent",14)},X),K=D({initFocusEvent:M("initFocusEvent",5)},X),Q=T("MouseEvent",V,z),$=T("FocusEvent",V,K),J=T("MutationEvent",w,{initMutationEvent:M("initMutationEvent",3),get relatedNode(){return H(this.impl.relatedNode)}}),Z=Object.create(null),et=function(){try{new window.MouseEvent("click")}catch(e){return!1}return!0}();if(!et){var tt=function(e,t,n){if(n){var r=Z[n];t=D(D({},r),t)}Z[e]=t};tt("Event",{bubbles:!1,cancelable:!1}),tt("CustomEvent",{detail:null},"Event"),tt("UIEvent",{view:null,detail:0},"Event"),tt("MouseEvent",{screenX:0,screenY:0,clientX:0,clientY:0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,button:0,relatedTarget:null},"UIEvent"),tt("FocusEvent",{relatedTarget:null},"UIEvent")}var nt=window.EventTarget,rt=["addEventListener","removeEventListener","dispatchEvent"];[Element,Window,Document].forEach(function(e){var t=e.prototype;rt.forEach(function(e){Object.defineProperty(t,e+"_",{value:t[e]})})}),O.prototype={addEventListener:function(e,t,n){if(P(t)){var r=new y(e,t,n),i=R.get(this);if(i){for(var o=0;i.length>o;o++)if(r.equals(i[o]))return}else i=[],R.set(this,i);i.push(r);var a=C(this);a.addEventListener_(e,p,!0)}},removeEventListener:function(e,t,n){n=Boolean(n);var r=R.get(this);if(r){for(var i=0,o=!1,a=0;r.length>a;a++)r[a].type===e&&r[a].capture===n&&(i++,r[a].handler===t&&(o=!0,r[a].remove()));if(o&&1===i){var s=C(this);s.removeEventListener_(e,p,!0)}}},dispatchEvent:function(e){var t=C(this);return t.dispatchEvent_(x(e))}},nt&&A(nt,O);var it=document.elementFromPoint;e.adjustRelatedTarget=l,e.elementFromPoint=_,e.wrapEventTargetMethods=L,e.wrappers.CustomEvent=G,e.wrappers.Event=w,e.wrappers.EventTarget=O,e.wrappers.FocusEvent=$,e.wrappers.MouseEvent=Q,e.wrappers.MutationEvent=J,e.wrappers.UIEvent=V}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e,t){Object.defineProperty(e,t,{enumerable:!1})}function n(){this.length=0,t(this,"length")}function r(e){if(null==e)return e;for(var t=new n,r=0,i=e.length;i>r;r++)t[r]=o(e[r]);return t.length=i,t}function i(e,t){e.prototype[t]=function(){return r(this.impl[t].apply(this.impl,arguments))}}var o=e.wrap;n.prototype={item:function(e){return this[e]}},t(n.prototype,"item"),e.wrappers.NodeList=n,e.addWrapNodeListMethod=i,e.wrapNodeList=r}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){u(e instanceof o)}function n(e,t,n,r){if(e.nodeType!==o.DOCUMENT_FRAGMENT_NODE)return e.parentNode&&e.parentNode.removeChild(e),e.parentNode_=t,e.previousSibling_=n,e.nextSibling_=r,n&&(n.nextSibling_=e),r&&(r.previousSibling_=e),[e];for(var i,a=[];i=e.firstChild;)e.removeChild(i),a.push(i),i.parentNode_=t;for(var s=0;a.length>s;s++)a[s].previousSibling_=a[s-1]||n,a[s].nextSibling_=a[s+1]||r;return n&&(n.nextSibling_=a[0]),r&&(r.previousSibling_=a[a.length-1]),a}function r(e){if(1===e.length)return h(e[0]);for(var t=h(document.createDocumentFragment()),n=0;e.length>n;n++)t.appendChild(h(e[n]));return t}function i(e){for(var t=e.firstChild;t;){u(t.parentNode===e);var n=t.nextSibling,r=h(t),i=r.parentNode;i&&b.call(i,r),t.previousSibling_=t.nextSibling_=t.parentNode_=null,t=n}e.firstChild_=e.lastChild_=null}function o(e){u(e instanceof f),a.call(this,e),this.parentNode_=void 0,this.firstChild_=void 0,this.lastChild_=void 0,this.nextSibling_=void 0,this.previousSibling_=void 0}var a=e.wrappers.EventTarget,s=e.wrappers.NodeList,l=e.defineWrapGetter,u=e.assert,c=e.mixin,d=e.registerWrapper,h=e.unwrap,p=e.wrap,f=window.Node,v=f.prototype.appendChild,m=f.prototype.insertBefore,g=f.prototype.replaceChild,b=f.prototype.removeChild,y=f.prototype.compareDocumentPosition;o.prototype=Object.create(a.prototype),c(o.prototype,{appendChild:function(e){t(e),this.invalidateShadowRenderer();var i=this.lastChild,o=null,a=n(e,this,i,o);return this.lastChild_=a[a.length-1],i||(this.firstChild_=a[0]),v.call(this.impl,r(a)),e},insertBefore:function(e,i){if(!i)return this.appendChild(e);t(e),t(i),u(i.parentNode===this),this.invalidateShadowRenderer();var o=i.previousSibling,a=i,s=n(e,this,o,a);this.firstChild===i&&(this.firstChild_=s[0]);var l=h(i),c=l.parentNode;return c&&m.call(c,r(s),l),e},removeChild:function(e){if(t(e),e.parentNode!==this)throw Error("NotFoundError");this.invalidateShadowRenderer();var n=this.firstChild,r=this.lastChild,i=e.nextSibling,o=e.previousSibling,a=h(e),s=a.parentNode;return s&&b.call(s,a),n===e&&(this.firstChild_=i),r===e&&(this.lastChild_=o),o&&(o.nextSibling_=i),i&&(i.previousSibling_=o),e.previousSibling_=e.nextSibling_=e.parentNode_=null,e},replaceChild:function(e,i){if(t(e),t(i),i.parentNode!==this)throw Error("NotFoundError");this.invalidateShadowRenderer();var o=i.previousSibling,a=i.nextSibling;a===e&&(a=e.nextSibling);var s=n(e,this,o,a);this.firstChild===i&&(this.firstChild_=s[0]),this.lastChild===i&&(this.lastChild_=s[s.length-1]),i.previousSibling_=null,i.nextSibling_=null,i.parentNode_=null;var l=h(i);return l.parentNode&&g.call(l.parentNode,r(s),l),i},hasChildNodes:function(){return null===this.firstChild},get parentNode(){return void 0!==this.parentNode_?this.parentNode_:p(this.impl.parentNode)},get firstChild(){return void 0!==this.firstChild_?this.firstChild_:p(this.impl.firstChild)},get lastChild(){return void 0!==this.lastChild_?this.lastChild_:p(this.impl.lastChild)},get nextSibling(){return void 0!==this.nextSibling_?this.nextSibling_:p(this.impl.nextSibling)},get previousSibling(){return void 0!==this.previousSibling_?this.previousSibling_:p(this.impl.previousSibling)},get parentElement(){for(var e=this.parentNode;e&&e.nodeType!==o.ELEMENT_NODE;)e=e.parentNode;return e},get textContent(){for(var e="",t=this.firstChild;t;t=t.nextSibling)e+=t.textContent;return e},set textContent(e){if(i(this),this.invalidateShadowRenderer(),""!==e){var t=this.impl.ownerDocument.createTextNode(e);this.appendChild(t)}},get childNodes(){for(var e=new s,t=0,n=this.firstChild;n;n=n.nextSibling)e[t++]=n;return e.length=t,e},cloneNode:function(e){if(!this.invalidateShadowRenderer())return p(this.impl.cloneNode(e));var t=p(this.impl.cloneNode(!1));if(e)for(var n=this.firstChild;n;n=n.nextSibling)t.appendChild(n.cloneNode(!0));return t},contains:function(e){if(!e)return!1;if(e===this)return!0;var t=e.parentNode;return t?this.contains(t):!1},compareDocumentPosition:function(e){return y.call(this.impl,h(e))}}),l(o,"ownerDocument"),d(f,o,document.createDocumentFragment()),delete o.prototype.querySelector,delete o.prototype.querySelectorAll,o.prototype=c(Object.create(a.prototype),o.prototype),e.wrappers.Node=o}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e,n){for(var r,i=e.firstElementChild;i;){if(i.matches(n))return i;if(r=t(i,n))return r;i=i.nextElementSibling}return null}function n(e,t,r){for(var i=e.firstElementChild;i;)i.matches(t)&&(r[r.length++]=i),n(i,t,r),i=i.nextElementSibling;return r}var r={querySelector:function(e){return t(this,e)},querySelectorAll:function(e){return n(this,e,new NodeList)}},i={getElementsByTagName:function(e){return this.querySelectorAll(e)},getElementsByClassName:function(e){return this.querySelectorAll("."+e)},getElementsByTagNameNS:function(e,t){if("*"===e)return this.getElementsByTagName(t);for(var n=new NodeList,r=this.getElementsByTagName(t),i=0,o=0;r.length>i;i++)r[i].namespaceURI===e&&(n[o++]=r[i]);return n.length=o,n}};e.GetElementsByInterface=i,e.SelectorsInterface=r}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;return e}function n(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.previousSibling;return e}var r=e.wrappers.NodeList,i={get firstElementChild(){return t(this.firstChild)},get lastElementChild(){return n(this.lastChild)},get childElementCount(){for(var e=0,t=this.firstElementChild;t;t=t.nextElementSibling)e++;return e},get children(){for(var e=new r,t=0,n=this.firstElementChild;n;n=n.nextElementSibling)e[t++]=n;return e.length=t,e}},o={get nextElementSibling(){return t(this.nextSibling)},get previousElementSibling(){return n(this.nextSibling)}};e.ChildNodeInterface=o,e.ParentNodeInterface=i}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}var n=e.ChildNodeInterface,r=e.wrappers.Node,i=e.mixin,o=e.registerWrapper,a=window.CharacterData;t.prototype=Object.create(r.prototype),i(t.prototype,{get textContent(){return this.data},set textContent(e){this.data=e}}),i(t.prototype,n),o(a,t,document.createTextNode("")),e.wrappers.CharacterData=t}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){i.call(this,e)}var n=e.ChildNodeInterface,r=e.GetElementsByInterface,i=e.wrappers.Node,o=e.ParentNodeInterface,a=e.SelectorsInterface;e.addWrapNodeListMethod;var s=e.mixin,l=e.registerWrapper,u=e.wrappers,c=new SideTable,d=window.Element,h=d.prototype.matches||d.prototype.mozMatchesSelector||d.prototype.msMatchesSelector||d.prototype.webkitMatchesSelector;t.prototype=Object.create(i.prototype),s(t.prototype,{createShadowRoot:function(){var t=new u.ShadowRoot(this);return c.set(this,t),e.getRendererForHost(this),this.invalidateShadowRenderer(!0),t},get shadowRoot(){return c.get(this)||null},setAttribute:function(e,t){this.impl.setAttribute(e,t),this.invalidateShadowRenderer()},matches:function(e){return h.call(this.impl,e)}}),s(t.prototype,n),s(t.prototype,r),s(t.prototype,o),s(t.prototype,a),l(d,t),e.wrappers.Element=t}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e){case"&":return"&";case"<":return"<";case'"':return"""}}function n(e){return e.replace(v,t)}function r(e){switch(e.nodeType){case Node.ELEMENT_NODE:for(var t,r=e.tagName.toLowerCase(),o="<"+r,a=e.attributes,s=0;t=a[s];s++)o+=" "+t.name+'="'+n(t.value)+'"';return o+=">",m[r]?o:o+i(e)+"";case Node.TEXT_NODE:return n(e.nodeValue);case Node.COMMENT_NODE:return"";default:throw console.error(e),Error("not implemented")}}function i(e){for(var t="",n=e.firstChild;n;n=n.nextSibling)t+=r(n);return t}function o(e,t,n){var r=n||"div";e.textContent="";var i=p(e.ownerDocument.createElement(r));i.innerHTML=t;for(var o;o=i.firstChild;)e.appendChild(f(o))}function a(e){u.call(this,e)}function s(t){c(a,t,function(){return e.renderAllPending(),this.impl[t]})}function l(t){Object.defineProperty(a.prototype,t,{value:function(){return e.renderAllPending(),this.impl[t].apply(this.impl,arguments)},configurable:!0,enumerable:!0})}var u=e.wrappers.Element,c=e.defineGetter,d=e.mixin,h=e.registerWrapper,p=e.unwrap,f=e.wrap,v=/&|<|"/g,m={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},g=window.HTMLElement;a.prototype=Object.create(u.prototype),d(a.prototype,{get innerHTML(){return i(this)},set innerHTML(e){o(this,e,this.tagName)},get outerHTML(){return r(this)},set outerHTML(e){if(this.invalidateShadowRenderer())throw Error("not implemented");this.impl.outerHTML=e}}),["clientHeight","clientLeft","clientTop","clientWidth","offsetHeight","offsetLeft","offsetTop","offsetWidth","scrollHeight","scrollLeft","scrollTop","scrollWidth"].forEach(s),["getBoundingClientRect","getClientRects","scrollIntoView"].forEach(l),h(g,a,document.createElement("b")),e.wrappers.HTMLElement=a,e.getInnerHTML=i,e.setInnerHTML=o}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,i=e.registerWrapper,o=window.HTMLContentElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get select(){return this.getAttribute("select")},set select(e){this.setAttribute("select",e)},setAttribute:function(e,t){n.prototype.setAttribute.call(this,e,t),"select"===(e+"").toLowerCase()&&this.invalidateShadowRenderer(!0)}}),o&&i(o,t),e.wrappers.HTMLContentElement=t}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e),this.olderShadowRoot_=null}var n=e.wrappers.HTMLElement,r=e.mixin,i=e.registerWrapper,o=window.HTMLShadowElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get olderShadowRoot(){return this.olderShadowRoot_},invalidateShadowRenderer:function(){n.prototype.invalidateShadowRenderer.call(this,!0)}}),o&&i(o,t),e.wrappers.HTMLShadowElement=t}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){if(!e.defaultView)return e;var t=d.get(e);if(!t){for(t=e.implementation.createHTMLDocument("");t.lastChild;)t.removeChild(t.lastChild);d.set(e,t)}return t}function n(e){for(var n,r=t(e.ownerDocument),i=r.createDocumentFragment();n=e.firstChild;)i.appendChild(n);return i}function r(e){i.call(this,e)}var i=e.wrappers.HTMLElement,o=e.getInnerHTML,a=e.mixin,s=e.registerWrapper,l=e.setInnerHTML,u=e.wrap,c=new SideTable,d=new SideTable,h=window.HTMLTemplateElement;r.prototype=Object.create(i.prototype),a(r.prototype,{get content(){if(h)return u(this.impl.content);var e=c.get(this);return e||(e=n(this),c.set(this,e)),e},get innerHTML(){return o(this.content)},set innerHTML(e){l(this.content,e),this.invalidateShadowRenderer()}}),h&&s(h,r),e.wrappers.HTMLTemplateElement=r}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e.localName){case"content":return new n(e);case"shadow":return new i(e);case"template":return new o(e)}r.call(this,e)}var n=e.wrappers.HTMLContentElement,r=e.wrappers.HTMLElement,i=e.wrappers.HTMLShadowElement,o=e.wrappers.HTMLTemplateElement;e.mixin;var a=e.registerWrapper,s=window.HTMLUnknownElement;t.prototype=Object.create(r.prototype),a(s,t),e.wrappers.HTMLUnknownElement=t}(this.ShadowDOMPolyfill),function(e){"use strict";var t=e.GetElementsByInterface,n=e.ParentNodeInterface,r=e.SelectorsInterface,i=e.mixin,o=e.registerObject,a=o(document.createDocumentFragment());i(a.prototype,n),i(a.prototype,r),i(a.prototype,t);var s=o(document.createTextNode("")),l=o(document.createComment(""));e.wrappers.Comment=l,e.wrappers.DocumentFragment=a,e.wrappers.Text=s}(this.ShadowDOMPolyfill),function(e){"use strict";function t(t){var r=l(t.impl.ownerDocument.createDocumentFragment());n.call(this,r),a(r,this);var i=t.shadowRoot;e.nextOlderShadowTreeTable.set(this,i),u.set(this,t)}var n=e.wrappers.DocumentFragment,r=e.elementFromPoint,i=e.getInnerHTML,o=e.mixin,a=e.rewrap,s=e.setInnerHTML,l=e.unwrap,u=new SideTable;t.prototype=Object.create(n.prototype),o(t.prototype,{get innerHTML(){return i(this)},set innerHTML(e){s(this,e),this.invalidateShadowRenderer()},invalidateShadowRenderer:function(){return u.get(this).invalidateShadowRenderer()},elementFromPoint:function(e,t){return r(this,this.ownerDocument,e,t)},getElementById:function(e){return this.querySelector("#"+e)}}),e.wrappers.ShadowRoot=t,e.getHostForShadowRoot=function(e){return u.get(e)}}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){e.previousSibling_=e.previousSibling,e.nextSibling_=e.nextSibling,e.parentNode_=e.parentNode}function n(e){e.firstChild_=e.firstChild,e.lastChild_=e.lastChild}function r(e){D(e instanceof N);for(var r=e.firstChild;r;r=r.nextSibling)t(r);n(e)}function i(e){var t=x(e);r(e),t.textContent=""}function o(e,n){var i=x(e),o=x(n);o.nodeType===N.DOCUMENT_FRAGMENT_NODE?r(n):(s(n),t(n)),e.lastChild_=e.lastChild,e.lastChild===e.firstChild&&(e.firstChild_=e.firstChild);var a=H(i.lastChild);a&&(a.nextSibling_=a.nextSibling),i.appendChild(o)}function a(e,n){var r=x(e),i=x(n);t(n),n.previousSibling&&(n.previousSibling.nextSibling_=n),n.nextSibling&&(n.nextSibling.previousSibling_=n),e.lastChild===n&&(e.lastChild_=n),e.firstChild===n&&(e.firstChild_=n),r.removeChild(i)}function s(e){var t=x(e),n=t.parentNode;n&&a(H(n),e)}function l(e,t){c(t).push(e),I.set(e,t);var n=R.get(e);n||R.set(e,n=[]),n.push(t)}function u(e){k.set(e,[])}function c(e){return k.get(e)}function d(e){for(var t=[],n=0,r=e.firstChild;r;r=r.nextSibling)t[n++]=r;return t}function h(e,t,n){for(var r=d(e),i=0;r.length>i;i++){var o=r[i];if(t(o)){if(n(o)===!1)return}else h(o,t,n)}}function p(e,t){var n=!1;return h(e,w,function(e){u(e);for(var r=0;t.length>r;r++){var i=t[r];void 0!==i&&v(i,e)&&(l(i,e),t[r]=void 0,n=!0)}}),n?t.filter(function(e){return void 0!==e}):t}function f(e,t){for(var n=0;t.length>n;n++)if(t[n]in e)return t[n]}function v(e,t){var n=t.getAttribute("select");if(!n)return!0;if(n=n.trim(),!n)return!0;if(e.nodeType!==N.ELEMENT_NODE)return!1;if(!B.test(n))return!1;if(":"===n[0]&&!q.test(n))return!1;try{return e.matches(n)}catch(r){return!1}}function m(){L=null,W.forEach(function(e){e.render()}),W=[]}function g(e){this.host=e,this.dirty=!1,this.associateNode(e)}function b(e){var t=F.get(e);return t||(t=new g(e),F.set(e,t)),t}function y(e){return"content"===e.localName}function w(e){return"content"===e.localName}function E(e){return"shadow"===e.localName}function T(e){return"shadow"===e.localName}function M(e){return!!e.shadowRoot}function S(e){return j.get(e)}function P(e){for(var t=[],n=e.shadowRoot;n;n=j.get(n))t.push(n);return t}function O(e,t){I.set(e,t)}function C(e){new g(e).render()}var L,_=e.wrappers.HTMLContentElement,N=e.wrappers.Node,D=e.assert,A=e.mixin,x=e.unwrap,H=e.wrap,k=new SideTable,R=new SideTable,I=new SideTable,j=new SideTable,F=new SideTable,U=new SideTable,B=/^[*.:#[a-zA-Z_|]/,q=RegExp("^:("+["link","visited","target","enabled","disabled","checked","indeterminate","nth-child","nth-last-child","nth-of-type","nth-last-of-type","first-child","last-child","first-of-type","last-of-type","only-of-type"].join("|")+")"),Y=f(window,["requestAnimationFrame","mozRequestAnimationFrame","webkitRequestAnimationFrame","setTimeout"]),W=[];g.prototype={render:function(){if(this.dirty){var e=this.host;this.treeComposition();var t=e.shadowRoot;if(t){this.removeAllChildNodes(this.host);var n=d(t);n.forEach(function(n){this.renderNode(e,t,n,!1)},this),this.dirty=!1}}},invalidate:function(){if(!this.dirty){if(this.dirty=!0,W.push(this),L)return;L=window[Y](m,0)}},renderNode:function(e,t,n,r){if(M(n)){this.appendChild(e,n);var i=b(n);i.dirty=!0,i.render()}else y(n)?this.renderInsertionPoint(e,t,n,r):E(n)?this.renderShadowInsertionPoint(e,t,n):this.renderAsAnyDomTree(e,t,n,r)},renderAsAnyDomTree:function(e,t,n,r){if(this.appendChild(e,n),M(n))C(n);else{var i=n,o=d(i);o.forEach(function(e){this.renderNode(i,t,e,r)},this)}},renderInsertionPoint:function(e,t,n,r){var i=c(n);i.length?(this.removeAllChildNodes(n),i.forEach(function(n){y(n)&&r?this.renderInsertionPoint(e,t,n,r):this.renderAsAnyDomTree(e,t,n,r)},this)):this.renderFallbackContent(e,n),this.remove(n)},renderShadowInsertionPoint:function(e,t,n){var r=S(t);if(r){I.set(r,n),n.olderShadowRoot_=r,this.remove(n);var i=d(r);i.forEach(function(t){this.renderNode(e,r,t,!0)},this)}else this.renderFallbackContent(e,n)},renderFallbackContent:function(e,t){var n=d(t);n.forEach(function(t){this.appendChild(e,t)},this)},treeComposition:function(){var e=this.host,t=e.shadowRoot,n=[],r=d(e);r.forEach(function(e){if(y(e)){var t=c(e);t&&t.length||(t=d(e)),n.push.apply(n,t)}else n.push(e)});for(var i,o;t;){if(i=void 0,h(t,T,function(e){return i=e,!1}),o=i,n=p(t,n),o){var a=S(t);if(a){t=a,O(t,o);continue}break}break}},appendChild:function(e,t){o(e,t),this.associateNode(t)},remove:function(e){s(e),this.associateNode(e)},removeAllChildNodes:function(e){i(e)},associateNode:function(e){U.set(e,this)}},N.prototype.invalidateShadowRenderer=function(e){var t=U.get(this);if(!t)return!1;var n;return(e||this.shadowRoot||(n=this.parentNode)&&(n.shadowRoot||n instanceof ShadowRoot))&&t.invalidate(),!0},_.prototype.getDistributedNodes=function(){return m(),c(this)},A(N.prototype,{get insertionParent(){return I.get(this)||null}}),e.eventParentsTable=R,e.getRendererForHost=b,e.getShadowTrees=P,e.nextOlderShadowTreeTable=j,e.renderAllPending=m,e.visual={removeAllChildNodes:i,appendChild:o,removeChild:a}}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){s.call(this,e)}function n(e){var n=document[e];t.prototype[e]=function(){return m(n.apply(this.impl,arguments))}}function r(e){this.impl=e}function i(e,t){var n=document.implementation[t];e.prototype[t]=function(){return m(n.apply(this.impl,arguments))}}function o(e,t){var n=document.implementation[t];e.prototype[t]=function(){return n.apply(this.impl,arguments)}}var a=e.GetElementsByInterface,s=e.wrappers.Node,l=e.ParentNodeInterface,u=e.SelectorsInterface,c=e.defineWrapGetter,d=e.elementFromPoint,h=e.forwardMethodsToWrapper,p=e.mixin,f=e.registerWrapper,v=e.unwrap,m=e.wrap,g=e.wrapEventTargetMethods;e.wrapNodeList;var b=new SideTable;t.prototype=Object.create(s.prototype),c(t,"documentElement"),c(t,"body"),c(t,"head"),["getElementById","createElement","createElementNS","createTextNode","createDocumentFragment","createEvent","createEventNS"].forEach(n);var y=document.adoptNode,w=document.write;p(t.prototype,{adoptNode:function(e){return y.call(this.impl,v(e)),e},elementFromPoint:function(e,t){return d(this,this,e,t)},write:function(e){for(var t=this.querySelectorAll("*"),n=t[t.length-1];n.nextSibling;)n=n.nextSibling;var r=n.parentNode;r.lastChild_=void 0,n.nextSibling_=void 0,w.call(this.impl,e)}}),h([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement],["appendChild","compareDocumentPosition","getElementsByClassName","getElementsByTagName","getElementsByTagNameNS","insertBefore","querySelector","querySelectorAll","removeChild","replaceChild"]),h([window.HTMLDocument||window.Document],["adoptNode","createDocumentFragment","createElement","createElementNS","createEvent","createEventNS","createTextNode","elementFromPoint","getElementById","write"]),p(t.prototype,a),p(t.prototype,l),p(t.prototype,u),p(t.prototype,{get implementation(){var e=b.get(this);return e?e:(e=new r(v(this).implementation),b.set(this,e),e)}}),f(window.Document,t,document.implementation.createHTMLDocument("")),window.HTMLDocument&&f(window.HTMLDocument,t),g([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement]),i(r,"createDocumentType"),i(r,"createDocument"),i(r,"createHTMLDocument"),o(r,"hasFeature"),f(window.DOMImplementation,r),h([window.DOMImplementation],["createDocumentType","createDocument","createHTMLDocument","hasFeature"]),e.wrappers.Document=t,e.wrappers.DOMImplementation=r}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.mixin,i=e.registerWrapper,o=e.unwrap,a=e.unwrapIfNeeded,s=e.wrap,l=window.Window;t.prototype=Object.create(n.prototype);var u=window.getComputedStyle;l.prototype.getComputedStyle=function(e,t){return u.call(this||window,a(e),t)},["addEventListener","removeEventListener","dispatchEvent"].forEach(function(e){l.prototype[e]=function(){var t=s(this||window);return t[e].apply(t,arguments)}}),r(t.prototype,{getComputedStyle:function(e,t){return u.call(o(this),a(e),t)}}),i(l,t),e.wrappers.Window=t}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){this.impl=e}function n(e){return new t(e)}function r(e){return e.map(n)}function i(e){var t=this;this.impl=new c(function(n){e.call(t,r(n),t)})}var o=e.defineGetter,a=e.defineWrapGetter,s=e.registerWrapper,l=e.unwrapIfNeeded,u=e.wrapNodeList;e.wrappers;var c=window.MutationObserver||window.WebKitMutationObserver;if(c){var d=window.MutationRecord;t.prototype={get addedNodes(){return u(this.impl.addedNodes)},get removedNodes(){return u(this.impl.removedNodes) +}},["target","previousSibling","nextSibling"].forEach(function(e){a(t,e)}),["type","attributeName","attributeNamespace","oldValue"].forEach(function(e){o(t,e,function(){return this.impl[e]})}),d&&s(d,t),window.Node,i.prototype={observe:function(e,t){this.impl.observe(l(e),t)},disconnect:function(){this.impl.disconnect()},takeRecords:function(){return r(this.impl.takeRecords())}},e.wrappers.MutationObserver=i,e.wrappers.MutationRecord=t}}(this.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=n[e],r=window[t];if(r){var i=document.createElement(e),o=i.constructor;window[t]=o}}e.isWrapperFor;var n={a:"HTMLAnchorElement",applet:"HTMLAppletElement",area:"HTMLAreaElement",audio:"HTMLAudioElement",br:"HTMLBRElement",base:"HTMLBaseElement",body:"HTMLBodyElement",button:"HTMLButtonElement",canvas:"HTMLCanvasElement",dl:"HTMLDListElement",datalist:"HTMLDataListElement",dir:"HTMLDirectoryElement",div:"HTMLDivElement",embed:"HTMLEmbedElement",fieldset:"HTMLFieldSetElement",font:"HTMLFontElement",form:"HTMLFormElement",frame:"HTMLFrameElement",frameset:"HTMLFrameSetElement",hr:"HTMLHRElement",head:"HTMLHeadElement",h1:"HTMLHeadingElement",html:"HTMLHtmlElement",iframe:"HTMLIFrameElement",input:"HTMLInputElement",li:"HTMLLIElement",label:"HTMLLabelElement",legend:"HTMLLegendElement",link:"HTMLLinkElement",map:"HTMLMapElement",menu:"HTMLMenuElement",menuitem:"HTMLMenuItemElement",meta:"HTMLMetaElement",meter:"HTMLMeterElement",del:"HTMLModElement",ol:"HTMLOListElement",object:"HTMLObjectElement",optgroup:"HTMLOptGroupElement",option:"HTMLOptionElement",output:"HTMLOutputElement",p:"HTMLParagraphElement",param:"HTMLParamElement",pre:"HTMLPreElement",progress:"HTMLProgressElement",q:"HTMLQuoteElement",script:"HTMLScriptElement",select:"HTMLSelectElement",source:"HTMLSourceElement",span:"HTMLSpanElement",style:"HTMLStyleElement",caption:"HTMLTableCaptionElement",col:"HTMLTableColElement",table:"HTMLTableElement",tr:"HTMLTableRowElement",thead:"HTMLTableSectionElement",tbody:"HTMLTableSectionElement",textarea:"HTMLTextAreaElement",title:"HTMLTitleElement",ul:"HTMLUListElement",video:"HTMLVideoElement"};Object.keys(n).forEach(t),Object.getOwnPropertyNames(e.wrappers).forEach(function(t){window[t]=e.wrappers[t]}),e.knownElements=n}(this.ShadowDOMPolyfill),function(){window.wrap=function(e){return e.impl?e:ShadowDOMPolyfill.wrap(e)},window.unwrap=function(e){return e.impl?ShadowDOMPolyfill.unwrap(e):e};var e=window.getComputedStyle;window.getComputedStyle=function(t,n){return e.call(window,wrap(t),n)},Object.defineProperties(HTMLElement.prototype,{webkitShadowRoot:{get:function(){return this.shadowRoot}}}),HTMLElement.prototype.webkitCreateShadowRoot=HTMLElement.prototype.createShadowRoot}()}else{var SideTable;"undefined"!=typeof WeakMap&&0>navigator.userAgent.indexOf("Firefox/")?SideTable=WeakMap:function(){var e=Object.defineProperty,t=Object.hasOwnProperty,n=(new Date).getTime()%1e9;SideTable=function(){this.name="__st"+(1e9*Math.random()>>>0)+(n++ +"__")},SideTable.prototype={set:function(t,n){e(t,this.name,{value:n,writable:!0})},get:function(e){return t.call(e,this.name)?e[this.name]:void 0},"delete":function(e){this.set(e,void 0)}}}(),function(){window.templateContent=window.templateContent||function(e){return e.content},window.wrap=window.unwrap=function(e){return e},window.createShadowRoot=function(e){return e.webkitCreateShadowRoot()},window.templateContent=function(e){if(window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(e),!e.content&&!e._content){for(var t=document.createDocumentFragment();e.firstChild;)t.appendChild(e.firstChild);e._content=t}return e.content||e._content}}()}if(function(e){Function.prototype.bind||(Function.prototype.bind=function(e){var t=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.slice();return r.push.apply(r,arguments),t.apply(e,r)}}),e.mixin=window.mixin}(window.Platform),function(e){"use strict";function t(e,t,n){var r="string"==typeof e?document.createElement(e):e.cloneNode(!0);if(r.innerHTML=t,n)for(var i in n)r.setAttribute(i,n[i]);return r}var n=DOMTokenList.prototype.add,r=DOMTokenList.prototype.remove;if(DOMTokenList.prototype.add=function(){for(var e=0;arguments.length>e;e++)n.call(this,arguments[e])},DOMTokenList.prototype.remove=function(){for(var e=0;arguments.length>e;e++)r.call(this,arguments[e])},DOMTokenList.prototype.toggle=function(e,t){1==arguments.length&&(t=!this.contains(e)),t?this.add(e):this.remove(e)},DOMTokenList.prototype.switch=function(e,t){e&&this.remove(e),t&&this.add(t)},NodeList.prototype.forEach=function(e,t){Array.prototype.slice.call(this).forEach(e,t)},HTMLCollection.prototype.forEach=function(e,t){Array.prototype.slice.call(this).forEach(e,t)},!window.performance){var i=Date.now();window.performance={now:function(){return Date.now()-i}}}window.requestAnimationFrame||(window.requestAnimationFrame=function(){var e=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame;return e?function(t){return e(function(){t(performance.now())})}:function(e){return window.setTimeout(e,1e3/60)}}()),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(){return window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||function(e){clearTimeout(e)}}()),e.createDOM=t}(window.Platform),window.templateContent=window.templateContent||function(e){return e.content},function(e){e=e||(window.Inspector={});var t;window.sinspect=function(e,r){t||(t=window.open("","ShadowDOM Inspector",null,!0),t.document.write(n),t.api={shadowize:shadowize}),o(e||wrap(document.body),r)};var n=["",""," "," ShadowDOM Inspector"," "," "," ",' ",'
    '," ",""].join("\n"),r=[],i=function(){var e=t.document,n=e.querySelector("#crumbs");n.textContent="";for(var i,a=0;i=r[a];a++){var s=e.createElement("a");s.href="#",s.textContent=i.localName,s.idx=a,s.onclick=function(e){for(var t;r.length>this.idx;)t=r.pop();o(t.shadow||t,t),e.preventDefault()},n.appendChild(e.createElement("li")).appendChild(s)}},o=function(e,n){var o=t.document;c=[];var a=n||e;r.push(a),i(),o.body.querySelector("#tree").innerHTML="
    "+u(e,e.childNodes)+"
    "},a=Array.prototype.forEach.call.bind(Array.prototype.forEach),s={STYLE:1,SCRIPT:1,"#comment":1,TEMPLATE:1},l=function(e){return s[e.nodeName]},u=function(e,t,n){if(l(e))return"";var r=n||"";if(e.localName||11==e.nodeType){var i=e.localName||"shadow-root",o=r+d(e);"content"==i&&(t=e.getDistributedNodes()),o+="
    ";var s=r+"  ";a(t,function(e){o+=u(e,e.childNodes,s)}),o+=r,{br:1}[i]||(o+="</"+i+">",o+="
    ")}else{var c=e.textContent.trim();o=c?r+'"'+c+'"'+"
    ":""}return o},c=[],d=function(e){var t="<",n=e.localName||"shadow-root";return e.webkitShadowRoot||e.shadowRoot?(t+=' ",c.push(e)):t+=n||"shadow-root",e.attributes&&a(e.attributes,function(e){t+=" "+e.name+(e.value?'="'+e.value+'"':"")}),t+=">"};shadowize=function(){var e=Number(this.attributes.idx.value),t=c[e];t?o(t.webkitShadowRoot||t.shadowRoot,t):(console.log("bad shadowize node"),console.dir(this))},e.output=u}(window.Inspector),function(e){"use strict";function t(e){return+e===e>>>0}function n(e){return+e}function r(e){return e===Object(e)}function i(e,t){return e===t?0!==e||1/e===1/t:R(e)&&R(t)?!0:e!==e&&t!==t}function o(e){return"string"!=typeof e?!1:(e=e.replace(/\s/g,""),""==e?!0:"."==e[0]?!1:U.test(e))}function a(e){return""==e.trim()?this:t(e)?(this.push(e+""),this):(e.split(/\./).filter(function(e){return e}).forEach(function(e){this.push(e)},this),void 0)}function s(e){for(var t=0;B>t&&e.check();)e.report(),t++}function l(e){for(var t in e)return!1;return!0}function u(e){return l(e.added)&&l(e.removed)&&l(e.changed)}function c(e,t){var n={},r={},i={};for(var o in t){var a=e[o];(void 0===a||a!==t[o])&&(o in e?a!==t[o]&&(i[o]=a):r[o]=void 0)}for(var o in e)o in t||(n[o]=e[o]);return Array.isArray(e)&&e.length!==t.length&&(i.length=e.length),{added:n,removed:r,changed:i}}function d(e,t){var n=t||(Array.isArray(e)?[]:{});for(var r in e)n[r]=e[r];return Array.isArray(e)&&(n.length=e.length),n}function h(e){this.callback=e,this.reporting=!0,A&&(this.boundInternalCallback=this.internalCallback.bind(this)),this.valid=!0,p(this),this.connect(),this.sync(!0)}function p(e){Y&&q.push(e)}function f(e){if(Y)for(var t=0;q.length>t;t++)if(q[t]===e){q[t]=void 0;break}}function v(e,t){this.object=e,h.call(this,t)}function m(e,t){if(!Array.isArray(e))throw Error("Provided object is not an Array");this.object=e,h.call(this,t)}function g(e,t){var n;return t.walkPropertiesFrom(e,function(e,r,i){i===t.length&&(n=r)}),n}function b(e,t,n){var i=!1;return t.walkPropertiesFrom(e,function(e,o,a){r(o)&&a==t.length-1&&(i=!0,o[e]=n)}),i}function y(e){var t="",n="obj",r=e.length;t+="if (obj";for(var i=0;r-1>i;i++){var o='["'+e[i]+'"]';n+=o,t+=" && "+n}return t+=") ",n+='["'+e[r-1]+'"]',t+="return "+n+"; else return undefined;",Function("obj",t)}function w(e,t){var n=""+t;return V[n]||(V[n]=y(t)),V[n](e)}function E(t,n,i,o,a){var s=void 0;return n.walkPropertiesFrom(t,function(t,l,u){if(u===n.length)return s=l,void 0;var c=i[u];if(!c||l!==c[0]){if(c)for(var d=0;c.length>d;d++){var h=c[d],p=o.get(h);1==p?(o.delete(h),e.unobserveCount++,Object.unobserve(h,a)):o.set(h,p-1)}if(c=l,r(c)){for(var c=[];r(l);){c.push(l);var p=o.get(l);p?o.set(l,p+1):(o.set(l,1),e.observeCount++,Object.observe(l,a)),l=Object.getPrototypeOf(l)}i[u]=c}}},this),s}function T(e,t,n){if(this.value=void 0,o(t)){var i=new a(t);return i.length?(r(e)&&(this.object=e,this.path=i,A?(this.observed=Array(i.length),this.observedMap=new Map,this.getPathValue=E):this.getPathValue=x?y(this.path):g,h.call(this,n)),void 0):(this.value=e,void 0)}}function M(e,t){if("function"==typeof Object.observe){var n=Object.getNotifier(e);return function(r,i){var o={object:e,type:r,name:t};2===arguments.length&&(o.oldValue=i),n.notify(o)}}}function S(e,t,n){for(var r={},i={},o=0;t.length>o;o++){var a=t[o];G[a.type]?(a.name in n||(n[a.name]=a.oldValue),"updated"!=a.type&&("new"!=a.type?a.name in r?(delete r[a.name],delete n[a.name]):i[a.name]=!0:a.name in i?delete i[a.name]:r[a.name]=!0)):(console.error("Unknown changeRecord type: "+a.type),console.error(a))}for(var s in r)r[s]=e[s];for(var s in i)i[s]=void 0;var l={};for(var s in n)if(!(s in r||s in i)){var u=e[s];n[s]!==u&&(l[s]=u)}return{added:r,removed:i,changed:l}}function P(e,t,n,r,i,o){for(var a=o-i+1,s=n-t+1,l=Array(a),u=0;a>u;u++)l[u]=Array(s),l[u][0]=u;for(var c=0;s>c;c++)l[0][c]=c;for(var u=1;a>u;u++)for(var c=1;s>c;c++)if(r[i+u-1]===e[t+c-1])l[u][c]=l[u-1][c-1];else{var d=l[u-1][c]+1,h=l[u][c-1]+1;l[u][c]=h>d?d:h}return l}function O(e){for(var t=e.length-1,n=e[0].length-1,r=e[t][n],i=[];t>0||n>0;)if(0!=t)if(0!=n){var o,a=e[t-1][n-1],s=e[t-1][n],l=e[t][n-1];o=l>s?a>s?s:a:a>l?l:a,o==a?(a==r?i.push(X):(i.push(z),r=a),t--,n--):o==s?(i.push(Q),t--,r=s):(i.push(K),n--,r=l)}else i.push(Q),t--;else i.push(K),n--;return i.reverse(),i}function C(e,t,n){for(var r=0;n>r;r++)if(e[r]!==t[r])return r;return n}function L(e,t,n){for(var r=e.length,i=t.length,o=0;n>o&&e[--r]===t[--i];)o++;return o}function _(e,t,n,r,i,o){function a(e,t,n){return{index:e,removed:t,addedCount:n}}var s=0,l=0,u=Math.min(n-t,o-i);if(0==t&&0==i&&(s=C(e,r,u)),n==e.length&&o==r.length&&(l=L(e,r,u-s)),t+=s,i+=s,n-=l,o-=l,0==n-t&&0==o-i)return[];if(t==n){for(var c=a(t,[],0);o>i;)c.removed.push(r[i++]);return[c]}if(i==o)return[a(t,[],n-t)];for(var d=O(P(e,t,n,r,i,o)),c=void 0,h=[],p=t,f=i,v=0;d.length>v;v++)switch(d[v]){case X:c&&(h.push(c),c=void 0),p++,f++;break;case z:c||(c=a(p,[],0)),c.addedCount++,p++,c.removed.push(r[f]),f++;break;case K:c||(c=a(p,[],0)),c.addedCount++,p++;break;case Q:c||(c=a(p,[],0)),c.removed.push(r[f]),f++}return c&&h.push(c),h}function N(e,t,r){function i(t,r){Object.keys(t).forEach(function(t){var i=n(t);if(!(I(i)||0>i||i>=a)){var l=r[i];e.length>i?s[i]=l:o.removed[i-e.length]=r[i]}})}var o,a="length"in r?n(r.length):e.length;e.length>a?o={index:a,removed:[],addedCount:e.length-a}:a>e.length&&(o={index:e.length,removed:Array(a-e.length),addedCount:0});var s=[];i(t.added,r),i(t.removed,r),i(t.changed,r);var l,u=[];for(var c in s){if(c=n(c),l){if(l.index+l.removed.length==c){l.removed.push(s[c]);continue}l.addedCount=Math.min(e.length,l.index+l.removed.length)-l.index,u.push(l),l=void 0}l={index:c,removed:[s[c]]}}return l?(l.addedCount=Math.min(e.length,l.index+l.removed.length)-l.index,o?l.index+l.removed.length==o.index?(l.addedCount=l.addedCount+o.addedCount,l.removed=l.removed.concat(o.removed),u.push(l)):(u.push(l),u.push(o)):u.push(l)):o&&u.push(o),u}function D(e,t,n){var r=[];return N(e,t,n).forEach(function(t){r=r.concat(_(e,t.index,t.index+t.addedCount,t.removed,0,t.removed.length))}),r}var A="function"==typeof Object.observe,x=!1;try{var H=Function("","return true;");x=H()}catch(k){}var R=e.Number.isNaN||function I(t){return"number"==typeof t&&e.isNaN(t)},j="__proto__"in{}?function(e){return e}:function(e){var t=e.__proto__;if(!t)return e;var n=Object.create(t);return Object.getOwnPropertyNames(e).forEach(function(t){Object.defineProperty(n,t,Object.getOwnPropertyDescriptor(e,t))}),n},F="[$a-z0-9_]+[$a-z0-9_\\d]*",U=RegExp("^(?:#?"+F+")?"+"(?:"+"(?:\\."+F+")"+")*"+"$","i");a.prototype=j({__proto__:[],toString:function(){return this.join(".")},walkPropertiesFrom:function(e,t,n){for(var r,i=0;this.length+1>i;i++)r=this[i],t.call(n,r,e,i),e=i==this.length||null===e||void 0===e?void 0:e[r]}});var B=1e3;h.prototype={valid:!1,internalCallback:function(e){this.valid&&this.reporting&&this.check(e)&&(this.report(),this.testingResults&&(this.testingResults.anyChanged=!0))},close:function(){this.valid&&(this.disconnect(),this.valid=!1,f(this))},deliver:function(e){this.valid&&(A?(this.testingResults=e,Object.deliverChangeRecords(this.boundInternalCallback),this.testingResults=void 0):s(this))},report:function(){if(this.reporting){this.sync(!1);try{this.callback.apply(void 0,this.reportArgs)}catch(e){h._errorThrownDuringCallback=!0,console.error("Exception caught during observer callback: "+e)}this.reportArgs=void 0}},reset:function(){this.valid&&(A&&(this.reporting=!1,Object.deliverChangeRecords(this.boundInternalCallback),this.reporting=!0),this.sync(!0))}};var q,Y=!A||e.forceCollectObservers;Y&&(q=[]);var W=!1;e.Platform=e.Platform||{},e.Platform.performMicrotaskCheckpoint=function(){if(Y&&!W){W=!0;var e=0,t={};do{e++;var n=q;q=[],t.anyChanged=!1;for(var r=0;n.length>r;r++){var i=n[r];i&&i.valid&&(A?i.deliver(t):i.check()&&(t.anyChanged=!0,i.report()),q.push(i))}}while(B>e&&t.anyChanged);W=!1}},Y&&(e.Platform.clearObservers=function(){q=[]}),v.prototype=j({__proto__:h.prototype,connect:function(){A&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){A||(this.oldObject=d(this.object))},check:function(e){var t,n;if(A){if(!e)return!1;n={},t=S(this.object,e,n)}else n=this.oldObject,t=c(this.object,this.oldObject);return u(t)?!1:(this.reportArgs=[t.added||{},t.removed||{},t.changed||{}],this.reportArgs.push(function(e){return n[e]}),!0)},disconnect:function(){A?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0,this.object=void 0}}),m.prototype=j({__proto__:v.prototype,sync:function(){A||(this.oldObject=this.object.slice())},check:function(e){var t;if(A){if(!e)return!1;var n={},r=S(this.object,e,n);t=D(this.object,r,n)}else t=_(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return t&&t.length?(this.reportArgs=[t],!0):!1}}),m.applySplices=function(e,t,n){n.forEach(function(n){for(var r=[n.index,n.removed.length],i=n.index;n.index+n.addedCount>i;)r.push(t[i]),i++;Array.prototype.splice.apply(e,r)})};var V={};T.prototype=j({__proto__:h.prototype,connect:function(){},disconnect:function(){this.object=void 0,this.value=void 0,this.sync(!0)},check:function(){return this.value=this.getPathValue(this.object,this.path,this.observed,this.observedMap,this.boundInternalCallback),i(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(e){e&&(this.value=this.getPathValue(this.object,this.path,this.observed,this.observedMap,this.boundInternalCallback)),this.oldValue=this.value}}),T.getValueAtPath=function(e,t){if(!o(t))return void 0;var n=new a(t);return n.length?r(e)?x?w(e,n):g(e,n):void 0:e},T.setValueAtPath=function(e,t,n){if(o(t)){var i=new a(t);i.length&&r(e)&&b(e,i,n)}};var G={"new":!0,updated:!0,deleted:!0};T.defineProperty=function(e,t,n){var r=M(e,t),i=new T(n.object,n.path,function(e,t){r&&r("updated",t)});return Object.defineProperty(e,t,{get:function(){return i.deliver(),i.value},set:function(e){T.setValueAtPath(n.object,n.path,e),i.deliver()},configurable:!0}),{close:function(){r&&i.deliver(),i.close(),delete e[t]}}};var X=0,z=1,K=2,Q=3;e.Observer=h,e.ArrayObserver=m,e.ObjectObserver=v,e.PathObserver=T}(this),function(e){"use strict";function t(e){if(!e)throw Error("Assertion failed")}function n(e){return e.ownerDocument.contains(e)}function r(e,t,n){console.error("Unhandled binding to Node: ",this,e,t,n)}function i(){}function o(){}function a(e,t,n){this.model=e,this.path=t,this.changed=n,this.observer=new PathObserver(this.model,this.path,this.changed),this.changed(this.observer.value)}function s(e){return function(t){e.data=void 0==t?"":t+""}}function l(e,t,n){if("textContent"!==e)return Node.prototype.bind.call(this,e,t,n);this.unbind("textContent");var r=new a(t,n,s(this));J.set(this,r)}function u(e){if("textContent"!=e)return Node.prototype.unbind.call(this,e);var t=J.get(this);t&&(t.dispose(),J.delete(this))}function c(){this.unbind("textContent"),Node.prototype.unbindAll.call(this)}function d(e,t,n){return n?function(n){n?e.setAttribute(t,""):e.removeAttribute(t)}:function(n){e.setAttribute(t,(void 0===n?"":n)+"")}}function h(){this.bindingMap=Object.create(null)}function p(e,t,n){var r=Z.get(this);r||(r=new h,Z.set(this,r)),r.add(this,e,t,n)}function f(e){var t=Z.get(this);t&&t.remove(e)}function v(){var e=Z.get(this);e&&(Z.delete(this),e.removeAll(),Node.prototype.unbindAll.call(this))}function m(e){switch(e.type){case"checkbox":return et;case"radio":case"select-multiple":case"select-one":return"change";default:return"input"}}function g(e,t,n,r){this.element=e,this.valueProperty=t,this.boundValueChanged=this.valueChanged.bind(this),this.boundUpdateBinding=this.updateBinding.bind(this),this.binding=new a(n,r,this.boundValueChanged),this.element.addEventListener(m(this.element),this.boundUpdateBinding,!0)}function b(e,t,n){g.call(this,e,"value",t,n)}function y(e){if(!n(e))return[];if(e.form)return K(e.form.elements,function(t){return t!=e&&"INPUT"==t.tagName&&"radio"==t.type&&t.name==e.name});var t=e.ownerDocument.querySelectorAll('input[type="radio"][name="'+e.name+'"]');return K(t,function(t){return t!=e&&!t.form})}function w(e,t,n){g.call(this,e,"checked",t,n)}function E(e,t,n){switch(e){case"value":this.unbind("value"),this.removeAttribute("value"),tt.set(this,new b(this,t,n));break;case"checked":this.unbind("checked"),this.removeAttribute("checked"),nt.set(this,new w(this,t,n));break;default:return Element.prototype.bind.call(this,e,t,n)}}function T(e){switch(e){case"value":var t=tt.get(this);t&&(t.unbind(),tt.delete(this));break;case"checked":var n=nt.get(this);n&&(n.unbind(),nt.delete(this));break;default:return Element.prototype.unbind.call(this,e)}}function M(){this.unbind("value"),this.unbind("checked"),Element.prototype.unbindAll.call(this)}function S(e){return ct[e.tagName]&&e.hasAttribute("template")}function P(e){return"TEMPLATE"==e.tagName||S(e)}function O(e){return dt&&"TEMPLATE"==e.tagName}function C(e,t){var n=e.querySelectorAll(ht);P(e)&&t(e),z(n,t)}function L(e){function t(e){HTMLTemplateElement.decorate(e)||L(e.content)}C(e,t)}function _(e,t){Object.getOwnPropertyNames(t).forEach(function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))})}function N(e){if(!e.defaultView)return e;var t=mt.get(e);if(!t){for(t=e.implementation.createHTMLDocument("");t.lastChild;)t.removeChild(t.lastChild);mt.set(e,t)}return t}function D(e){var t=e.ownerDocument.createElement("template");e.parentNode.insertBefore(t,e);for(var n=e.attributes,r=n.length;r-->0;){var i=n[r];ut[i.name]&&("template"!==i.name&&t.setAttribute(i.name,i.value),e.removeAttribute(i.name))}return t}function A(e,t,n){var r=e.content;if(n)return r.appendChild(t),void 0;for(var i;i=t.firstChild;)r.appendChild(i)}function x(e){"TEMPLATE"===e.tagName?dt||(ft?e.__proto__=HTMLTemplateElement.prototype:_(e,HTMLTemplateElement.prototype)):(_(e,HTMLTemplateElement.prototype),Object.defineProperty(e,"content",yt))}function H(e){var t=e.ref;return t?t.content:e.content}function k(e,t){this.type=e,this.value=t}function R(e){for(var t=[],n=e.length,r=0,i=0;n>i;){if(r=e.indexOf("{{",i),0>r){t.push(new k(Et,e.slice(i)));break}if(r>0&&r>i&&t.push(new k(Et,e.slice(i,r))),i=r+2,r=e.indexOf("}}",i),0>r){var o=e.slice(i-2),a=t[t.length-1];a&&a.type==Et?a.value+=o:t.push(new k(Et,o));break}var s=e.slice(i,r).trim();t.push(new k(Tt,s)),i=r+2}return t}function I(e,t,n,r,i){var o,a=i&&i[st];a&&"function"==typeof a&&(o=a(n,r,t,e),o&&(n=o,r="value")),e.bind(t,n,r)}function j(e,t,n,r,i){var o=R(n);if(o.length&&(1!=o.length||o[0].type!=Et)){if(1==o.length&&o[0].type==Tt)return I(e,t,r,o[0].value,i),void 0;for(var a=new V,s=0;o.length>s;s++){var l=o[s];l.type==Tt&&I(a,s,r,l.value,i)}a.combinator=function(e){for(var t="",n=0;o.length>n;n++){var r=o[n];if(r.type===Et)t+=r.value;else{var i=e[n];void 0!==i&&(t+=i)}}return t},e.bind(t,a,"value")}}function F(e,n,r){t(e);for(var i={},o=0;e.attributes.length>o;o++){var a=e.attributes[o];i[a.name]=a.value}P(e)&&(""===i[rt]&&(i[rt]="{{}}"),""===i[it]&&(i[it]="{{}}")),Object.keys(i).forEach(function(t){j(e,t,i[t],n,r)})}function U(e,n,r){t(e),e.nodeType===Node.ELEMENT_NODE?F(e,n,r):e.nodeType===Node.TEXT_NODE&&j(e,"textContent",e.data,n,r);for(var i=e.firstChild;i;i=i.nextSibling)U(i,n,r)}function B(e){if(Mt.delete(e),P(e)){var t=St.get(e);t&&(t.abandon(),St.delete(e))}e.unbindAll();for(var n=e.firstChild;n;n=n.nextSibling)B(n)}function q(e,t){var n=e.cloneNode(!1);P(n)&&(HTMLTemplateElement.decorate(n,e),t&&!n.hasAttribute(at)&&n.setAttribute(at,t));for(var r=e.firstChild;r;r=r.nextSibling)n.appendChild(q(r,t));return n}function Y(e,t,n){this.firstNode=e,this.lastNode=t,this.model=n}function W(e,t){if(e.firstChild)for(var n=new Y(e.firstChild,e.lastChild,t),r=n.firstNode;r;)Mt.set(r,n),r=r.nextSibling}function V(e){this.bindings={},this.values={},this.value=void 0,this.size=0,this.combinator_=e,this.boundResolve=this.resolve.bind(this),this.disposed=!1}function G(e){this.templateElement_=e,this.terminators=[],this.iteratedValue=void 0,this.arrayObserver=void 0,this.boundHandleSplices=this.handleSplices.bind(this),this.inputs=new V(this.resolveInputs.bind(this)),this.valueBinding=new a(this.inputs,"value",this.valueChanged.bind(this))}var X,z=Array.prototype.forEach.call.bind(Array.prototype.forEach),K=Array.prototype.filter.call.bind(Array.prototype.filter);e.Map&&"function"==typeof e.Map.prototype.forEach?X=e.Map:(X=function(){this.keys=[],this.values=[]},X.prototype={set:function(e,t){var n=this.keys.indexOf(e);0>n?(this.keys.push(e),this.values.push(t)):this.values[n]=t},get:function(e){var t=this.keys.indexOf(e);return 0>t?void 0:this.values[t]},"delete":function(e){var t=this.keys.indexOf(e);return 0>t?!1:(this.keys.splice(t,1),this.values.splice(t,1),!0)},forEach:function(e,t){for(var n=0;this.keys.length>n;n++)e.call(t||this,this.values[n],this.keys[n],this)}});var Q="__proto__"in{}?function(e){return e}:function(e){var t=e.__proto__;if(!t)return e;var n=Object.create(t);return Object.getOwnPropertyNames(e).forEach(function(t){Object.defineProperty(n,t,Object.getOwnPropertyDescriptor(e,t))}),n};"function"!=typeof document.contains&&(Document.prototype.contains=function(e){return e===this||e.parentNode===this?!0:this.documentElement.contains(e)});var $;"undefined"!=typeof WeakMap&&0>navigator.userAgent.indexOf("Firefox/")?$=WeakMap:function(){var e=Object.defineProperty,t=Object.hasOwnProperty,n=(new Date).getTime()%1e9;$=function(){this.name="__st"+(1e9*Math.random()>>>0)+(n++ +"__")},$.prototype={set:function(t,n){e(t,this.name,{value:n,writable:!0})},get:function(e){return t.call(e,this.name)?e[this.name]:void 0},"delete":function(e){this.set(e,void 0)}}}(),Node.prototype.bind=r,Node.prototype.unbind=i,Node.prototype.unbindAll=o;var J=new $("textContentBinding");a.prototype={dispose:function(){this.observer.close()},set value(e){PathObserver.setValueAtPath(this.model,this.path,e)},reset:function(){this.observer.reset()}},Text.prototype.bind=l,Text.prototype.unbind=u,Text.prototype.unbindAll=c;var Z=new $("attributeBindings");h.prototype={add:function(e,t,n,r){e.removeAttribute(t);var i="?"==t[t.length-1];i&&(t=t.slice(0,-1)),this.remove(t);var o=new a(n,r,d(e,t,i));this.bindingMap[t]=o},remove:function(e){var t=this.bindingMap[e];t&&(t.dispose(),delete this.bindingMap[e])},removeAll:function(){Object.keys(this.bindingMap).forEach(function(e){this.remove(e)},this)}},Element.prototype.bind=p,Element.prototype.unbind=f,Element.prototype.unbindAll=v;var et,tt=new $("valueBinding"),nt=new $("checkedBinding");(function(){var e=document.createElement("div"),t=e.appendChild(document.createElement("input"));t.setAttribute("type","checkbox");var n,r=0;t.addEventListener("click",function(){r++,n=n||"click"}),t.addEventListener("change",function(){r++,n=n||"change"});var i=document.createEvent("MouseEvent");i.initMouseEvent("click",!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null),t.dispatchEvent(i),et=1==r?"change":n})(),g.prototype={valueChanged:function(e){this.element[this.valueProperty]=this.produceElementValue(e)},updateBinding:function(){this.binding.value=this.element[this.valueProperty],this.binding.reset(),this.postUpdateBinding&&this.postUpdateBinding(),Platform.performMicrotaskCheckpoint()},unbind:function(){this.binding.dispose(),this.element.removeEventListener(m(this.element),this.boundUpdateBinding,!0)}},b.prototype=Q({__proto__:g.prototype,produceElementValue:function(e){return(null==e?"":e)+""}}),w.prototype=Q({__proto__:g.prototype,produceElementValue:function(e){return Boolean(e)},postUpdateBinding:function(){"INPUT"===this.element.tagName&&"radio"===this.element.type&&y(this.element).forEach(function(e){var t=nt.get(e);t&&(t.binding.value=!1)})}}),HTMLInputElement.prototype.bind=E,HTMLInputElement.prototype.unbind=T,HTMLInputElement.prototype.unbindAll=M;var rt="bind",it="repeat",ot="if",at="syntax",st="getBinding",lt="getInstanceModel",ut={template:!0,repeat:!0,bind:!0,ref:!0},ct={THEAD:!0,TBODY:!0,TFOOT:!0,TH:!0,TR:!0,TD:!0,COLGROUP:!0,COL:!0,CAPTION:!0,OPTION:!0},dt="undefined"!=typeof HTMLTemplateElement,ht="template, "+Object.keys(ct).map(function(e){return e.toLowerCase()+"[template]"}).join(", "),pt=function(){function e(e){r.indexOf(e)>=0||n.indexOf(e)>=0||(n.push(e),o==i.value&&(i.value=!i.value))}function t(){for(o=i.value,r=n,n=[];r.length;){var e=r.shift();e()}}var n=[],r=[],i={value:0},o=i.value;return new PathObserver(i,"value",t),e}();document.addEventListener("DOMContentLoaded",function(){L(document),Platform.performMicrotaskCheckpoint()},!1),dt||(e.HTMLTemplateElement=function(){throw TypeError("Illegal constructor")});var ft="__proto__"in{},vt=new $("templateContents"),mt=new $("templateContentsOwner"),gt=new $("templateInstanceRef");HTMLTemplateElement.decorate=function(e,n){if(e.templateIsDecorated_)return!1;var r=e,i=O(r),o=i,a=!i,s=!1;if(!i&&S(r)&&(t(!n),r=D(e),i=O(r),s=!0),r.templateIsDecorated_=!0,!i){x(r);var l=N(r.ownerDocument);vt.set(r,l.createDocumentFragment())}return n?gt.set(r,n):a?A(r,e,s):o&&L(r.content),!0},HTMLTemplateElement.bootstrap=L;var bt=e.HTMLUnknownElement||HTMLElement,yt={get:function(){return vt.get(this)},enumerable:!0,configurable:!0};dt||(HTMLTemplateElement.prototype=Object.create(bt.prototype),Object.defineProperty(HTMLTemplateElement.prototype,"content",yt));var wt=new $("templateModel");_(HTMLTemplateElement.prototype,{bind:function(e,t,n){switch(e){case rt:case it:case ot:var r=St.get(this);r||(r=new G(this),St.set(this,r)),r.inputs.bind(e,t,n||"");break;default:return Element.prototype.bind.call(this,e,t,n)}},unbind:function(e,t,n){switch(e){case rt:case it:case ot:var r=St.get(this);if(!r)break;r.inputs.unbind(e);break;default:return Element.prototype.unbind.call(this,e,t,n)}},unbindAll:function(){this.unbind(rt),this.unbind(it),this.unbind(ot),Element.prototype.unbindAll.call(this)},createInstance:function(){var e=H(this),t=this.getAttribute(at),n=q(e,t);return"function"==typeof HTMLTemplateElement.__instanceCreated&&HTMLTemplateElement.__instanceCreated(n),n},get model(){return wt.get(this)},set model(e){var t=HTMLTemplateElement.syntax[this.getAttribute(at)];wt.set(this,e),U(this,e,t)},get ref(){var e,t=this.getAttribute("ref");return t&&(e=this.ownerDocument.getElementById(t)),e||(e=gt.get(this)),e||null}});var Et=0,Tt=1,Mt=new $("templateInstance");Object.defineProperty(Node.prototype,"templateInstance",{get:function(){var e=Mt.get(this);return e?e:this.parentNode?this.parentNode.templateInstance:void 0}}),V.prototype={set combinator(e){this.combinator_=e,this.scheduleResolve()},bind:function(e,t,n){this.unbind(e),this.size++,this.bindings[e]=new a(t,n,function(t){this.values[e]=t,this.scheduleResolve()}.bind(this))},unbind:function(e,t){this.bindings[e]&&(this.size--,this.bindings[e].dispose(),delete this.bindings[e],delete this.values[e],t||this.scheduleResolve())},scheduleResolve:function(){pt(this.boundResolve)},resolve:function(){if(!this.disposed){if(!this.combinator_)throw Error("CompoundBinding attempted to resolve without a combinator");this.value=this.combinator_(this.values)}},dispose:function(){Object.keys(this.bindings).forEach(function(e){this.unbind(e,!0)},this),this.disposed=!0,this.value=void 0}},G.prototype={resolveInputs:function(e){return ot in e&&!e[ot]?void 0:it in e?e[it]:rt in e?[e[rt]]:void 0},valueChanged:function(e,t){Array.isArray(e)||(e=[]),this.unobserve(),this.iteratedValue=e,this.arrayObserver=new ArrayObserver(this.iteratedValue,this.boundHandleSplices);var n={index:0,addedCount:this.iteratedValue.length,removed:Array.isArray(t)?t:[]};(n.addedCount||n.removed.length)&&this.handleSplices([n])},getTerminatorAt:function(e){if(-1==e)return this.templateElement_;var t=this.terminators[e];if(t.nodeType!==Node.ELEMENT_NODE)return t;var n=St.get(t);return n?n.getTerminatorAt(n.terminators.length-1):t},insertInstanceAt:function(e,t){var n=this.getTerminatorAt(e-1),r=t[t.length-1]||n;this.terminators.splice(e,0,r);for(var i=this.templateElement_.parentNode,o=n.nextSibling,a=0;t.length>a;a++)i.insertBefore(t[a],o)},extractInstanceAt:function(e){var t=[],n=this.getTerminatorAt(e-1),r=this.getTerminatorAt(e);this.terminators.splice(e,1);for(var i=this.templateElement_.parentNode;r!==n;){var o=r;r=o.previousSibling,i.removeChild(o),t.push(o)}return t},getInstanceModel:function(e,t,n){var r=n&&n[lt];return r&&"function"==typeof r?r(e,t):t},getInstanceNodes:function(e,t){var n=[],r=this.templateElement_.createInstance();for(U(r,e,t),W(r,e);r.firstChild;)n.push(r.removeChild(r.firstChild));return n},handleSplices:function(e){var t=this.templateElement_;if(!t.parentNode||!t.ownerDocument.defaultView)return this.abandon(),St.delete(this),void 0; + var n=t.getAttribute(at),r=HTMLTemplateElement.syntax[n],i=new X,o=0;e.forEach(function(e){e.removed.forEach(function(t){var n=this.extractInstanceAt(e.index+o,n);i.set(t,n)},this),o-=e.addedCount},this),e.forEach(function(e){for(var n=e.index;e.index+e.addedCount>n;n++){var o=this.getInstanceModel(t,this.iteratedValue[n],r),a=i.get(o)||this.getInstanceNodes(o,r);this.insertInstanceAt(n,a)}},this),i.forEach(function(e){for(var t=0;e.length>t;t++)B(e[t])})},unobserve:function(){this.arrayObserver&&(this.arrayObserver.close(),this.arrayObserver=void 0)},abandon:function(){this.unobserve(),this.valueBinding.dispose(),this.terminators.length=0,this.inputs.dispose()}};var St=new $("templateIterator");e.CompoundBinding=V,Object.defineProperty(HTMLTemplateElement,at,{value:{},enumerable:!0}),HTMLTemplateElement.forAllTemplatesFrom_=C,HTMLTemplateElement.bindAllMustachesFrom_=U,HTMLTemplateElement.parseAndBind_=j}(this),function(e){function t(){logFlags.data&&console.group("Model.dirtyCheck()"),n(),logFlags.data&&console.groupEnd()}function n(){Platform.performMicrotaskCheckpoint()}document.write(""),HTMLTemplateElement.__instanceCreated=function(e){document.adoptNode(e),CustomElements.upgradeAll(e)};var r=125;window.addEventListener("WebComponentsReady",function(){t(),setInterval(n,r)}),e.flush=t,window.dirtyCheck=t}(window.Platform),function(e){function t(e){return r(e,a)}function n(e){return r(e,"stylesheet")}function r(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function i(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var r=n.createElement("base");return r.setAttribute("href",document.baseURI),n.head.appendChild(r),n.body.innerHTML=e,n}e||(e=window.HTMLImports={flags:{}});var o,a="import",s={documents:{},cache:{},preloadSelectors:["link[rel="+a+"]","script[src]","link[rel=stylesheet]"].join(","),load:function(e,t){o=new l(s.loaded,t),o.cache=s.cache,s.preload(e)},preload:function(e){var n=e.querySelectorAll(s.preloadSelectors);e===document&&(n=Array.prototype.filter.call(n,function(e){return t(e)})),o.addNodes(n)},loaded:function(e,r,o){if(t(r)){var a=s.documents[e];a||(a=i(o,e),u.resolvePathsInHTML(a),s.documents[e]=a,s.preload(a)),r.content=r.__resource=a}else r.__resource=o,n(r)&&u.resolvePathsInStylesheet(r)}},l=function(e,t){this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={},this.cache={}};l.prototype={addNodes:function(e){this.inflight+=e.length,f(e,this.require,this),this.checkDone()},require:function(e){var t=u.nodeUrl(e);e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){return this.pending[e]?(this.pending[e].push(t),!0):this.cache[e]?(this.onload(e,t,o.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,t){p.load(e,function(n,r){this.receive(e,t,n,r)}.bind(this))},receive:function(e,t,n,r){n||(o.cache[e]=r),o.pending[e].forEach(function(t){n||this.onload(e,t,r),this.tail()},this),o.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}};var u={nodeUrl:function(e){return u.resolveUrl(u.getDocumentUrl(document),u.hrefOrSrc(e))},hrefOrSrc:function(e){return e.getAttribute("href")||e.getAttribute("src")},documentUrlFromNode:function(e){var t=u.getDocumentUrl(e.ownerDocument);return t=t.split("#")[0]},getDocumentUrl:function(e){return e&&(e._URL||e.impl&&e.impl._URL||e.baseURI||e.URL)||""},resolveUrl:function(e,t,n){if(this.isAbsUrl(t))return t;var r=this.compressUrl(this.urlToPath(e)+t);return n&&(r=u.makeRelPath(u.getDocumentUrl(document),r)),r},isAbsUrl:function(e){return/(^data:)|(^http[s]?:)|(^\/)/.test(e)},urlToPath:function(e){var t=e.split("/");return t.pop(),t.push(""),t.join("/")},compressUrl:function(e){for(var t,n=e.split("/"),r=0;n.length>r;r++)t=n[r],".."===t&&(n.splice(r-1,2),r-=2);return n.join("/")},makeRelPath:function(e,t){var n,r;for(n=this.compressUrl(e).split("/"),r=this.compressUrl(t).split("/");n.length&&n[0]===r[0];)n.shift(),r.shift();for(var i=0,o=n.length-1;o>i;i++)r.unshift("..");var a=r.join("/");return a},resolvePathsInHTML:function(e){var t=u.documentUrlFromNode(e.body);window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(e);var n=e.body;u._resolvePathsInHTML(n,t)},_resolvePathsInHTML:function(e,t){if(u.resolveAttributes(e,t),u.resolveStyleElts(e,t),window.templateContent){var n=e.querySelectorAll("template");n&&f(n,function(e){u._resolvePathsInHTML(templateContent(e),t)})}},resolvePathsInStylesheet:function(e){var t=u.nodeUrl(e);e.__resource=u.resolveCssText(e.__resource,t)},resolveStyleElts:function(e,t){var n=e.querySelectorAll("style");n&&f(n,function(e){e.textContent=u.resolveCssText(e.textContent,t)})},resolveCssText:function(e,t){return e.replace(/url\([^)]*\)/g,function(e){var n=e.replace(/["']/g,"").slice(4,-1);return n=u.resolveUrl(t,n,!0),"url("+n+")"})},resolveAttributes:function(e,t){var n=e&&e.querySelectorAll(d);n&&f(n,function(e){this.resolveNodeAttributes(e,t)},this)},resolveNodeAttributes:function(e,t){c.forEach(function(n){var r=e.attributes[n];if(r&&r.value&&0>r.value.search(h)){var i=u.resolveUrl(t,r.value,!0);r.value=i}})}},c=["href","src","action"],d="["+c.join("],[")+"]",h="{{.*}}",p={async:!0,ok:function(e){return e.status>=200&&300>e.status||304===e.status},load:function(t,n,r){var i=new XMLHttpRequest;(e.flags.debug||e.flags.bust)&&(t+="?"+Math.random()),i.open("GET",t,p.async),i.addEventListener("readystatechange",function(){4===i.readyState&&n.call(r,!p.ok(i)&&i,i.response,t)}),i.send()}},f=Array.prototype.forEach.call.bind(Array.prototype.forEach);e.importer=s,e.getDocumentUrl=u.getDocumentUrl,"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e){var t=document.createEvent("HTMLEvents");return t.initEvent(e,!0,!0),t}),window.addEventListener("load",function(){s.load(document,function(){var e=window.ShadowDOMPolyfill?ShadowDOMPolyfill.wrap(document):document;HTMLImports.readyTime=(new Date).getTime(),e.body.dispatchEvent(new CustomEvent("HTMLImportsLoaded",{bubbles:!0}))})})}(window.HTMLImports),function(e){function t(e){w.push(e),y||(y=!0,m(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){y=!1;var e=w;w=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();i(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function i(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function o(e,t){for(var n=e;n;n=n.parentNode){var r=v.get(n);if(r)for(var i=0;r.length>i;i++){var o=r[i],a=o.options;if(n===e||a.subtree){var s=t(a);s&&o.enqueue(s)}}}}function a(e){this.callback_=e,this.nodes_=[],this.records_=[],this.uid_=++E}function s(e,t){this.type=e,this.target=t,this.addedNodes=[],this.removedNodes=[],this.previousSibling=null,this.nextSibling=null,this.attributeName=null,this.attributeNamespace=null,this.oldValue=null}function l(e){var t=new s(e.type,e.target);return t.addedNodes=e.addedNodes.slice(),t.removedNodes=e.removedNodes.slice(),t.previousSibling=e.previousSibling,t.nextSibling=e.nextSibling,t.attributeName=e.attributeName,t.attributeNamespace=e.attributeNamespace,t.oldValue=e.oldValue,t}function u(e,t){return T=new s(e,t)}function c(e){return M?M:(M=l(T),M.oldValue=e,M)}function d(){T=M=void 0}function h(e){return e===M||e===T}function p(e,t){return e===t?e:M&&h(e)?M:null}function f(e,t,n){this.observer=e,this.target=t,this.options=n,this.transientObservedNodes=[]}var v=new SideTable,m=window.msSetImmediate;if(!m){var g=[],b=Math.random()+"";window.addEventListener("message",function(e){if(e.data===b){var t=g;g=[],t.forEach(function(e){e()})}}),m=function(e){g.push(e),window.postMessage(b,"*")}}var y=!1,w=[],E=0;a.prototype={observe:function(e,t){if(e=n(e),!t.childList&&!t.attributes&&!t.characterData||t.attributeOldValue&&!t.attributes||t.attributeFilter&&t.attributeFilter.length&&!t.attributes||t.characterDataOldValue&&!t.characterData)throw new SyntaxError;var r=v.get(e);r||v.set(e,r=[]);for(var i,o=0;r.length>o;o++)if(r[o].observer===this){i=r[o],i.removeListeners(),i.options=t;break}i||(i=new f(this,e,t),r.push(i),this.nodes_.push(e)),i.addListeners()},disconnect:function(){this.nodes_.forEach(function(e){for(var t=v.get(e),n=0;t.length>n;n++){var r=t[n];if(r.observer===this){r.removeListeners(),t.splice(n,1);break}}},this),this.records_=[]},takeRecords:function(){var e=this.records_;return this.records_=[],e}};var T,M;f.prototype={enqueue:function(e){var n=this.observer.records_,r=n.length;if(n.length>0){var i=n[r-1],o=p(i,e);if(o)return n[r-1]=o,void 0}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;t.length>n;n++)if(t[n]===this){t.splice(n,1);break}},this)},handleEvent:function(e){switch(e.stopImmediatePropagation(),e.type){case"DOMAttrModified":var t=e.attrName,n=e.relatedNode.namespaceURI,r=e.target,i=new u("attributes",r);i.attributeName=t,i.attributeNamespace=n;var a=e.attrChange===MutationEvent.ADDITION?null:e.prevValue;o(r,function(e){return!e.attributes||e.attributeFilter&&e.attributeFilter.length&&-1===e.attributeFilter.indexOf(t)&&-1===e.attributeFilter.indexOf(n)?void 0:e.attributeOldValue?c(a):i});break;case"DOMCharacterDataModified":var r=e.target,i=u("characterData",r),a=e.prevValue;o(r,function(e){return e.characterData?e.characterDataOldValue?c(a):i:void 0});break;case"DOMNodeRemoved":this.addTransientObserver(e.target);case"DOMNodeInserted":var s,l,r=e.relatedNode,h=e.target;"DOMNodeInserted"===e.type?(s=[h],l=[]):(s=[],l=[h]);var p=h.previousSibling,f=h.nextSibling,i=u("childList",r);i.addedNodes=s,i.removedNodes=l,i.previousSibling=p,i.nextSibling=f,o(r,function(e){return e.childList?i:void 0})}d()}},e.JsMutationObserver=a}(this),!window.MutationObserver&&(window.MutationObserver=window.WebKitMutationObserver||window.JsMutationObserver,!MutationObserver))throw Error("no mutation observer support");(function(e){function t(t,o){var a=o||{};if(!t)throw Error("Name argument must not be empty");if(a.name=t,!a.prototype)throw Error("Options missing required prototype property");return a.lifecycle=a.lifecycle||{},a.ancestry=n(a.extends),r(a),i(a),a.prototype.setAttribute=c,a.prototype.removeAttribute=d,p(t,a),a.ctor=f(a),a.ctor.prototype=a.prototype,e.ready&&e.upgradeAll(document),a.ctor}function n(e){var t=w[e];return t?n(t.extends).concat([t]):[]}function r(e){for(var t,n=e.extends,r=0;t=e.ancestry[r];r++)n=t.is&&t.tag;e.tag=n||e.name,n&&(e.is=e.name)}function i(e){if(!Object.__proto__)if(e.is)var t=document.createElement(e.tag),n=Object.getPrototypeOf(t);else n=HTMLElement.prototype;e.native=n}function o(e){return a(E(e.tag),e)}function a(t,n){return n.is&&t.setAttribute("is",n.is),s(t,n),t.__upgraded__=!0,e.upgradeSubtree(t),u(t),t}function s(e,t){Object.__proto__?e.__proto__=t.prototype:(l(e,t.prototype,t.native),e.__proto__=t.prototype)}function l(e,t,n){for(var r={},i=t;i!==n&&i!==HTMLUnknownElement.prototype;){for(var o,a=Object.getOwnPropertyNames(i),s=0;o=a[s];s++)r[o]||(Object.defineProperty(e,o,Object.getOwnPropertyDescriptor(i,o)),r[o]=1);i=Object.getPrototypeOf(i)}}function u(e){e.readyCallback&&e.readyCallback()}function c(e,t){h.call(this,e,t,b)}function d(e,t){h.call(this,e,t,y)}function h(e,t,n){var r=this.getAttribute(e);n.apply(this,arguments),this.attributeChangedCallback&&this.getAttribute(e)!==r&&this.attributeChangedCallback(e,r)}function p(e,t){w[e]=t}function f(e){return function(){return o(e)}}function v(e){var t=w[e];return t?new t.ctor:E(e)}function m(e){if(!e.__upgraded__&&e.nodeType===Node.ELEMENT_NODE){var t=e.getAttribute("is")||e.localName,n=w[t];return n&&a(e,n)}}if(e||(e=window.CustomElements={flags:{}}),e.hasNative=(document.webkitRegister||document.register)&&"native"===e.flags.register,e.hasNative){document.register=document.register||document.webkitRegister;var g=function(){};e.registry={},e.upgradeElement=g}else{var b=HTMLElement.prototype.setAttribute,y=HTMLElement.prototype.removeAttribute,w={},E=document.createElement.bind(document);document.register=t,document.createElement=v,e.registry=w,e.upgrade=m}})(window.CustomElements),function(e){function t(e,n,r){var i=e.firstElementChild;if(!i)for(i=e.firstChild;i&&i.nodeType!==Node.ELEMENT_NODE;)i=i.nextSibling;for(;i;)n(i,r)!==!0&&t(i,n,r),i=i.nextElementSibling;return null}function n(e,r){t(e,function(e){return r(e)?!0:(e.webkitShadowRoot&&n(e.webkitShadowRoot,r),void 0)}),e.webkitShadowRoot&&n(e.webkitShadowRoot,r)}function r(e){return a(e)?(s(e),!0):(l(e),void 0)}function i(e){n(e,function(e){return r(e)?!0:void 0})}function o(e){return r(e)||i(e)}function a(t){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var n=t.getAttribute("is")||t.localName,r=e.registry[n];if(r)return logFlags.dom&&console.group("upgrade:",t.localName),e.upgrade(t),logFlags.dom&&console.groupEnd(),!0}}function s(e){l(e),d(e)&&n(e,function(e){l(e)})}function l(e){(e.insertedCallback||e.__upgraded__&&logFlags.dom)&&(logFlags.dom&&console.group("inserted:",e.localName),d(e)&&(e.__inserted=(e.__inserted||0)+1,1>e.__inserted&&(e.__inserted=1),e.__inserted>1?logFlags.dom&&console.warn("inserted:",e.localName,"insert/remove count:",e.__inserted):e.insertedCallback&&(logFlags.dom&&console.log("inserted:",e.localName),e.insertedCallback())),logFlags.dom&&console.groupEnd())}function u(e){c(e),n(e,function(e){c(e)})}function c(e){(e.removedCallback||e.__upgraded__&&logFlags.dom)&&(logFlags.dom&&console.log("removed:",e.localName),d(e)||(e.__inserted=(e.__inserted||0)-1,e.__inserted>0&&(e.__inserted=0),0>e.__inserted?logFlags.dom&&console.warn("removed:",e.localName,"insert/remove count:",e.__inserted):e.removedCallback&&e.removedCallback()))}function d(e){for(var t=e;t;){if(t==e.ownerDocument)return!0;t=t.parentNode||t.host}}function h(e){e.webkitShadowRoot&&!e.webkitShadowRoot.__watched&&(logFlags.dom&&console.log("watching shadow-root for: ",e.localName),g(e.webkitShadowRoot),e.webkitShadowRoot.__watched=!0)}function p(e){h(e),n(e,function(){h(e)})}function f(e){switch(e.localName){case"style":case"script":case"template":case void 0:return!0}}function v(e){if(logFlags.dom){var t=e[0];if(t&&"childList"===t.type&&t.addedNodes&&t.addedNodes){for(var n=t.addedNodes[0];n&&n!==document&&!n.host;)n=n.parentNode;var r=n&&(n.URL||n._URL||n.host&&n.host.localName)||"";r=r.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",e.length,r||"")}e.forEach(function(e){"childList"===e.type&&(E(e.addedNodes,function(e){f(e)||o(e)}),E(e.removedNodes,function(e){f(e)||u(e)}))}),logFlags.dom&&console.groupEnd()}function m(){v(w.takeRecords())}function g(e){w.observe(e,{childList:!0,subtree:!0})}function b(e){g(e)}function y(e){logFlags.dom&&console.group("upgradeDocument: ",(e.URL||e._URL||"").split("/").pop()),o(e),logFlags.dom&&console.groupEnd()}var w=new MutationObserver(v),E=Array.prototype.forEach.call.bind(Array.prototype.forEach);e.watchShadow=h,e.watchAllShadows=p,e.upgradeAll=o,e.upgradeSubtree=i,e.observeDocument=b,e.upgradeDocument=y,e.takeRecords=m}(window.CustomElements),function(){function parseElementElement(e){var t={name:"","extends":null};takeAttributes(e,t);var n=HTMLElement.prototype;if(t.extends){var r=document.createElement(t.extends);n=r.__proto__||Object.getPrototypeOf(r)}t.prototype=Object.create(n),e.options=t;var i=e.querySelector("script,scripts");i&&executeComponentScript(i.textContent,e,t.name);var o=document.register(t.name,t);e.ctor=o;var a=e.getAttribute("constructor");a&&(window[a]=o)}function takeAttributes(e,t){for(var n in t){var r=e.attributes[n];r&&(t[n]=r.value)}}function executeComponentScript(inScript,inContext,inName){context=inContext;var owner=context.ownerDocument,url=owner._URL||owner.URL||owner.impl&&(owner.impl._URL||owner.impl.URL),match=url.match(/.*\/([^.]*)[.]?.*$/);if(match){var name=match[1];url+=name!=inName?":"+inName:""}var code="__componentScript('"+inName+"', function(){"+inScript+"});"+"\n//@ sourceURL="+url+"\n";eval(code)}function mixin(e){for(var t=e||{},n=1;arguments.length>n;n++){var r=arguments[n];try{for(var i in r)copyProperty(i,r,t)}catch(o){}}return t}function copyProperty(e,t,n){var r=getPropertyDescriptor(t,e);Object.defineProperty(n,e,r)}function getPropertyDescriptor(e,t){if(e){var n=Object.getOwnPropertyDescriptor(e,t);return n||getPropertyDescriptor(Object.getPrototypeOf(e),t)}}var HTMLElementElement=function(e){return e.register=HTMLElementElement.prototype.register,parseElementElement(e),e};HTMLElementElement.prototype={register:function(e){e&&(this.options.lifecycle=e.lifecycle,e.prototype&&mixin(this.options.prototype,e.prototype))}};var context;window.__componentScript=function(e,t){t.call(context)},window.HTMLElementElement=HTMLElementElement,window.mixin=mixin}(),function(){function e(e){return e.ownerDocument===document||e.ownerDocument.impl===document}function t(e){return"link"===e.localName&&e.getAttribute("rel")===r}function n(e){return e.parentNode&&"element"===e.parentNode.localName?!0:void 0}var r="import",i={selectors:["link[rel="+r+"]","link[rel=stylesheet]","script[src]","script","style","element"],map:{link:"parseLink",script:"parseScript",element:"parseElement",style:"parseStyle"},parse:function(e){if(!e.__parsed){e.__parsed=!0;var t=e.querySelectorAll(o.selectors);a(t,function(e){o[o.map[e.localName]](e)}),CustomElements.upgradeDocument(e),CustomElements.observeDocument(e)}},parseLink:function(r){t(r)?r.content&&o.parse(r.content):e(r)||!r.parentNode||n(r)||document.head.appendChild(r)},parseScript:function(t){if(!e(t)&&!n(t)){var r=t.__resource||t.textContent;r&&(r+="\n//@ sourceURL="+t.__nodeUrl+"\n",eval.call(window,r))}},parseStyle:function(t){e(t)||n(t)||document.querySelector("head").appendChild(t)},parseElement:function(e){new HTMLElementElement(e)}},o=i,a=Array.prototype.forEach.call.bind(Array.prototype.forEach);CustomElements.parser=i}(),function(){function e(){setTimeout(function(){CustomElements.parser.parse(document),CustomElements.ready=!0,CustomElements.readyTime=(new Date).getTime(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.body.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))},0)}"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e){var t=document.createEvent("HTMLEvents");return t.initEvent(e,!0,!0),t}),window.HTMLImports?document.addEventListener("HTMLImportsLoaded",e):window.addEventListener("load",e)}(),function(){function e(){}if(document.write(""),window.ShadowDOMPolyfill){CustomElements.watchShadow=e,CustomElements.watchAllShadows=e;var t=["upgradeAll","upgradeSubtree","observeDocument","upgradeDocument"],n={};t.forEach(function(e){n[e]=CustomElements[e]}),t.forEach(function(e){CustomElements[e]=function(t){return n[e](wrap(t))}})}}(),function(e){e=e||{};var t={shadow:function(e){return e?e.shadowRoot||e.webkitShadowRoot:void 0},canTarget:function(e){return e&&Boolean(e.elementFromPoint)},targetingShadow:function(e){var t=this.shadow(e);return this.canTarget(t)?t:void 0},searchRoot:function(e,t,n){if(e){var r,i,o,a=e.elementFromPoint(t,n);for(i=this.targetingShadow(a);i;){if(r=i.elementFromPoint(t,n)){var s=this.targetingShadow(r);return this.searchRoot(s,t,n)||r}o=i.querySelector("shadow"),i=o&&o.olderShadowRoot}return a}},findTarget:function(e){var t=e.clientX,n=e.clientY;return this.searchRoot(document,t,n)}};e.targetFinding=t,e.findTarget=t.findTarget.bind(t),window.PointerEventsPolyfill=e}(window.PointerEventsPolyfill),function(){function e(e){return'[touch-action="'+e+'"]'}function t(e){return"{ -ms-touch-action: "+e+"; touch-action: "+e+"; }"}var n=["none","pan-x","pan-y",{rule:"pan-x pan-y",selectors:["scroll","pan-x pan-y","pan-y pan-x"]}],r="";n.forEach(function(n){r+=n+""===n?e(n)+t(n):n.selectors.map(e)+t(n.rule)});var i=document.createElement("style");i.textContent=r;var o=document.querySelector("head");o.insertBefore(i,o.firstChild)}(),function(e){function t(e,t){var t=t||{},i=t.buttons;if(void 0===i)switch(t.which){case 1:i=1;break;case 2:i=4;break;case 3:i=2;break;default:i=0}var o;if(n)o=new MouseEvent(e,t);else{o=document.createEvent("MouseEvent");var a={bubbles:!1,cancelable:!1,view:null,detail:null,screenX:0,screenY:0,clientX:0,clientY:0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,button:0,relatedTarget:null};Object.keys(a).forEach(function(e){e in t&&(a[e]=t[e])}),o.initMouseEvent(e,a.bubbles,a.cancelable,a.view,a.detail,a.screenX,a.screenY,a.clientX,a.clientY,a.ctrlKey,a.altKey,a.shiftKey,a.metaKey,a.button,a.relatedTarget)}r||Object.defineProperty(o,"buttons",{get:function(){return i},enumerable:!0});var s=0;return s=t.pressure?t.pressure:i?.5:0,Object.defineProperties(o,{pointerId:{value:t.pointerId||0,enumerable:!0},width:{value:t.width||0,enumerable:!0},height:{value:t.height||0,enumerable:!0},pressure:{value:s,enumerable:!0},tiltX:{value:t.tiltX||0,enumerable:!0},tiltY:{value:t.tiltY||0,enumerable:!0},pointerType:{value:t.pointerType||"",enumerable:!0},hwTimestamp:{value:t.hwTimestamp||0,enumerable:!0},isPrimary:{value:t.isPrimary||!1,enumerable:!0}}),o}var n=!1,r=!1;try{var i=new MouseEvent("click",{buttons:1});n=!0,r=1===i.buttons}catch(o){}e.PointerEvent=t}(window),function(e){function t(){this.ids=[],this.pointers=[]}t.prototype={set:function(e,t){var n=this.ids.indexOf(e);n>-1?this.pointers[n]=t:(this.ids.push(e),this.pointers.push(t))},has:function(e){return this.ids.indexOf(e)>-1},"delete":function(e){var t=this.ids.indexOf(e);t>-1&&(this.ids.splice(t,1),this.pointers.splice(t,1))},get:function(e){var t=this.ids.indexOf(e);return this.pointers[t]},get size(){return this.pointers.length},clear:function(){this.ids.length=0,this.pointers.length=0}},e.PointerMap=t}(window.PointerEventsPolyfill),function(e){var t;if("undefined"!=typeof WeakMap&&0>navigator.userAgent.indexOf("Firefox/"))t=WeakMap;else{var n=Object.defineProperty,r=Object.hasOwnProperty,i=(new Date).getTime()%1e9;t=function(){this.name="__st"+(1e9*Math.random()>>>0)+(i++ +"__")},t.prototype={set:function(e,t){n(e,this.name,{value:t,writable:!0})},get:function(e){return r.call(e,this.name)?e[this.name]:void 0},"delete":function(e){this.set(e,void 0)}}}e.SideTable=t}(window.PointerEventsPolyfill),function(e){var t={targets:new e.SideTable,handledEvents:new e.SideTable,scrollType:new e.SideTable,pointermap:new e.PointerMap,events:[],eventMap:{},eventSources:{},registerSource:function(e,t){var n=t,r=n.events;r&&(this.events=this.events.concat(r),r.forEach(function(e){n[e]&&(this.eventMap[e]=n[e].bind(n))},this),this.eventSources[e]=n)},registerTarget:function(e,t){this.scrollType.set(e,t||"none"),this.listen(this.events,e,this.boundHandler)},unregisterTarget:function(e){this.scrollType.set(e,null),this.unlisten(this.events,e,this.boundHandler)},down:function(e){this.fireEvent("pointerdown",e)},move:function(e){this.fireEvent("pointermove",e)},up:function(e){this.fireEvent("pointerup",e)},enter:function(e){e.bubbles=!1,this.fireEvent("pointerenter",e)},leave:function(e){e.bubbles=!1,this.fireEvent("pointerleave",e)},over:function(e){e.bubbles=!0,this.fireEvent("pointerover",e)},out:function(e){e.bubbles=!0,this.fireEvent("pointerout",e)},cancel:function(e){this.fireEvent("pointercancel",e)},leaveOut:function(e){e.target.contains(e.relatedTarget)||this.leave(e),this.out(e)},enterOver:function(e){e.target.contains(e.relatedTarget)||this.enter(e),this.over(e)},eventHandler:function(e){if(!this.handledEvents.get(e)){var t=e.type,n=this.eventMap&&this.eventMap[t];n&&n(e),this.handledEvents.set(e,!0)}},listen:function(e,t,n){e.forEach(function(e){this.addEvent(e,n,!1,t)},this)},unlisten:function(e,t,n){e.forEach(function(e){this.removeEvent(e,n,!1,t)},this)},addEvent:function(e,t,n,r){r.addEventListener(e,t,n)},removeEvent:function(e,t,n,r){r.removeEventListener(e,t,n)},makeEvent:function(e,t){var n=new PointerEvent(e,t);return this.targets.set(n,this.targets.get(t)||t.target),n},fireEvent:function(e,t){var n=this.makeEvent(e,t);return this.dispatchEvent(n)},cloneEvent:function(e){var t={};for(var n in e)t[n]=e[n];return t},getTarget:function(e){return this.captureInfo&&this.captureInfo.id===e.pointerId?this.captureInfo.target:this.targets.get(e)},setCapture:function(e,t){this.captureInfo&&this.releaseCapture(this.captureInfo.id),this.captureInfo={id:e,target:t};var n=new PointerEvent("gotpointercapture",{bubbles:!0});this.implicitRelease=this.releaseCapture.bind(this,e),document.addEventListener("pointerup",this.implicitRelease),document.addEventListener("pointercancel",this.implicitRelease),this.targets.set(n,t),this.asyncDispatchEvent(n)},releaseCapture:function(e){if(this.captureInfo&&this.captureInfo.id===e){var t=new PointerEvent("lostpointercapture",{bubbles:!0}),n=this.captureInfo.target;this.captureInfo=null,document.removeEventListener("pointerup",this.implicitRelease),document.removeEventListener("pointercancel",this.implicitRelease),this.targets.set(t,n),this.asyncDispatchEvent(t)}},dispatchEvent:function(e){var t=this.getTarget(e);return t?t.dispatchEvent(e):void 0},asyncDispatchEvent:function(e){setTimeout(this.dispatchEvent.bind(this,e),0)}};t.boundHandler=t.eventHandler.bind(t),e.dispatcher=t}(window.PointerEventsPolyfill),function(e){var t=e.dispatcher,n=Array.prototype.forEach.call.bind(Array.prototype.forEach),r=Array.prototype.map.call.bind(Array.prototype.map),i={ATTRIB:"touch-action",SELECTOR:"[touch-action]",EMITTER:"none",XSCROLLER:"pan-x",YSCROLLER:"pan-y",SCROLLER:/^(?:pan-x pan-y)|(?:pan-y pan-x)|scroll$/,OBSERVER_INIT:{subtree:!0,childList:!0,attributes:!0,attributeFilter:["touch-action"]},watchSubtree:function(t){e.targetFinding.canTarget(t)&&s.observe(t,this.OBSERVER_INIT)},enableOnSubtree:function(e){var t=e||document;this.watchSubtree(e),t===document&&"complete"!==document.readyState?this.installOnLoad():this.installNewSubtree(t)},installNewSubtree:function(e){n(this.findElements(e),this.addElement,this)},findElements:function(e){var t=e||document;return t.querySelectorAll?t.querySelectorAll(this.SELECTOR):[]},touchActionToScrollType:function(e){var t=e;return t===this.EMITTER?"none":t===this.XSCROLLER?"X":t===this.YSCROLLER?"Y":this.SCROLLER.exec(t)?"XY":void 0},removeElement:function(n){t.unregisterTarget(n);var r=e.targetFinding.shadow(n);r&&t.unregisterTarget(r)},addElement:function(n){var r=n.getAttribute&&n.getAttribute(this.ATTRIB),i=this.touchActionToScrollType(r);if(i){t.registerTarget(n,i);var o=e.targetFinding.shadow(n);o&&t.registerTarget(o,i)}},elementChanged:function(e){this.removeElement(e),this.addElement(e)},concatLists:function(e,t){for(var n,r=0,i=t.length;i>r&&(n=t[r]);r++)e.push(n);return e},installOnLoad:function(){document.addEventListener("DOMContentLoaded",this.installNewSubtree.bind(this,document))},flattenMutationTree:function(e){var t=r(e,this.findElements,this);return t.push(e),t.reduce(this.concatLists,[])},mutationWatcher:function(e){e.forEach(this.mutationHandler,this)},mutationHandler:function(e){var t=e;if("childList"===t.type){var n=this.flattenMutationTree(t.addedNodes);n.forEach(this.addElement,this);var r=this.flattenMutationTree(t.removedNodes);r.forEach(this.removeElement,this)}else"attributes"===t.type&&this.elementChanged(t.target)}},o=i.mutationWatcher.bind(i);e.installer=i,e.register=i.enableOnSubtree.bind(i),e.setTouchAction=function(e,n){var r=this.touchActionToScrollType(n);r?t.registerTarget(e,r):t.unregisterTarget(e)}.bind(i);var a=window.MutationObserver||window.WebKitMutationObserver;if(a)var s=new a(o);else i.watchSubtree=function(){console.warn("PointerEventsPolyfill: MutationObservers not found, touch-action will not be dynamically detected")}}(window.PointerEventsPolyfill),function(e){var t=e.dispatcher,n=e.installer,r=e.findTarget,i=t.pointermap,o=t.scrollType,a=Array.prototype.map.call.bind(Array.prototype.map),s=2500,l=25,u={events:["touchstart","touchmove","touchend","touchcancel"],POINTER_TYPE:"touch",firstTouch:null,isPrimaryTouch:function(e){return this.firstTouch===e.identifier},setPrimaryTouch:function(e){null===this.firstTouch&&(this.firstTouch=e.identifier,this.firstXY={X:e.clientX,Y:e.clientY},this.scrolling=!1)},removePrimaryTouch:function(e){this.isPrimaryTouch(e)&&(this.firstTouch=null,this.firstXY=null)},touchToPointer:function(e){var n=t.cloneEvent(e);return n.pointerId=e.identifier+2,n.target=r(n),n.bubbles=!0,n.cancelable=!0,n.button=0,n.buttons=1,n.width=e.webkitRadiusX||e.radiusX,n.height=e.webkitRadiusY||e.radiusY,n.pressure=e.webkitForce||e.force,n.isPrimary=this.isPrimaryTouch(e),n.pointerType=this.POINTER_TYPE,n},processTouches:function(e,t){var n=e.changedTouches,r=a(n,this.touchToPointer,this);r.forEach(t,this)},shouldScroll:function(e){if(this.firstXY){var t,n=o.get(e.currentTarget);if("none"===n)t=!1;else if("XY"===n)t=!0;else{var r=e.changedTouches[0],i=n,a="Y"===n?"X":"Y",s=Math.abs(r["client"+i]-this.firstXY[i]),l=Math.abs(r["client"+a]-this.firstXY[a]);t=s>=l}return this.firstXY=null,t}},findTouch:function(e,t){for(var n,r=0,i=e.length;i>r&&(n=e[r]);r++)if(n.identifier===t)return!0},vacuumTouches:function(e){var t=e.touches;if(i.size>=t.length){var n=[];i.ids.forEach(function(e){if(1!==e&&!this.findTouch(t,e-2)){var r=i.get(e).out;n.push(this.touchToPointer(r))}},this),n.forEach(this.cancelOut,this)}},touchstart:function(e){this.vacuumTouches(e),this.setPrimaryTouch(e.changedTouches[0]),this.dedupSynthMouse(e),this.scrolling||this.processTouches(e,this.overDown)},overDown:function(e){i.set(e.pointerId,{target:e.target,out:e,outTarget:e.target}),t.over(e),t.down(e)},touchmove:function(e){this.scrolling||(this.shouldScroll(e)?(this.scrolling=!0,this.touchcancel(e)):(e.preventDefault(),this.processTouches(e,this.moveOverOut)))},moveOverOut:function(e){var n=e,r=i.get(n.pointerId),o=r.out,a=r.outTarget;t.move(n),o&&a!==n.target&&(o.relatedTarget=n.target,n.relatedTarget=a,o.target=a,t.leaveOut(o),t.enterOver(n)),r.out=n,r.outTarget=n.target},touchend:function(e){this.dedupSynthMouse(e),this.processTouches(e,this.upOut)},upOut:function(e){this.scrolling||(t.up(e),t.out(e)),this.cleanUpPointer(e)},touchcancel:function(e){this.processTouches(e,this.cancelOut)},cancelOut:function(e){t.cancel(e),t.out(e),this.cleanUpPointer(e)},cleanUpPointer:function(e){i.delete(e.pointerId),this.removePrimaryTouch(e)},dedupSynthMouse:function(e){var t=c.lastTouches,n=e.changedTouches[0];if(this.isPrimaryTouch(n)){var r={x:n.clientX,y:n.clientY};t.push(r);var i=function(e,t){var n=e.indexOf(t);n>-1&&e.splice(n,1)}.bind(null,t,r);setTimeout(i,s)}}},c={POINTER_ID:1,POINTER_TYPE:"mouse",events:["mousedown","mousemove","mouseup","mouseover","mouseout"],global:["mousedown","mouseup","mouseover","mouseout"],lastTouches:[],mouseHandler:t.eventHandler.bind(t),isEventSimulatedFromTouch:function(e){for(var t,n=this.lastTouches,r=e.clientX,i=e.clientY,o=0,a=n.length;a>o&&(t=n[o]);o++){var s=Math.abs(r-t.x),u=Math.abs(i-t.y); + if(l>=s&&l>=u)return!0}},prepareEvent:function(e){var n=t.cloneEvent(e);return n.pointerId=this.POINTER_ID,n.isPrimary=!0,n.pointerType=this.POINTER_TYPE,n},mousedown:function(e){if(!this.isEventSimulatedFromTouch(e)){var n=i.has(this.POINTER_ID);if(n&&(this.cancel(e),n=!1),!n){var r=this.prepareEvent(e);i.set(this.POINTER_ID,e),t.down(r),t.listen(this.global,document,this.mouseHandler)}}},mousemove:function(e){if(!this.isEventSimulatedFromTouch(e)){var n=this.prepareEvent(e);t.move(n)}},mouseup:function(e){if(!this.isEventSimulatedFromTouch(e)){var n=i.get(this.POINTER_ID);if(n&&n.button===e.button){var r=this.prepareEvent(e);t.up(r),this.cleanupMouse()}}},mouseover:function(e){if(!this.isEventSimulatedFromTouch(e)){var n=this.prepareEvent(e);t.enterOver(n)}},mouseout:function(e){if(!this.isEventSimulatedFromTouch(e)){var n=this.prepareEvent(e);t.leaveOut(n)}},cancel:function(e){var n=this.prepareEvent(e);t.cancel(n),this.cleanupMouse()},cleanupMouse:function(){i.delete(this.POINTER_ID),t.unlisten(this.global,document,this.mouseHandler)}},d={events:["MSPointerDown","MSPointerMove","MSPointerUp","MSPointerOut","MSPointerOver","MSPointerCancel","MSGotPointerCapture","MSLostPointerCapture"],POINTER_TYPES:["","unavailable","touch","pen","mouse"],prepareEvent:function(e){var n=t.cloneEvent(e);return n.pointerType=this.POINTER_TYPES[e.pointerType],n},cleanup:function(e){i.delete(e)},MSPointerDown:function(e){i.set(e.pointerId,e);var n=this.prepareEvent(e);t.down(n)},MSPointerMove:function(e){var n=this.prepareEvent(e);t.move(n)},MSPointerUp:function(e){var n=this.prepareEvent(e);t.up(n),this.cleanup(e.pointerId)},MSPointerOut:function(e){var n=this.prepareEvent(e);t.leaveOut(n)},MSPointerOver:function(e){var n=this.prepareEvent(e);t.enterOver(n)},MSPointerCancel:function(e){var n=this.prepareEvent(e);t.cancel(n),this.cleanup(e.pointerId)},MSLostPointerCapture:function(e){var n=t.makeEvent("lostpointercapture",e);t.dispatchEvent(n)},MSGotPointerCapture:function(e){var n=t.makeEvent("gotpointercapture",e);t.dispatchEvent(n)}};if(void 0===window.navigator.pointerEnabled){if(window.navigator.msPointerEnabled){var h=window.navigator.msMaxTouchPoints;Object.defineProperty(window.navigator,"maxTouchPoints",{value:h,enumerable:!0}),t.registerSource("ms",d),t.registerTarget(document)}else t.registerSource("mouse",c),"ontouchstart"in window&&t.registerSource("touch",u),n.enableOnSubtree(document),t.listen(["mousemove"],document,t.boundHandler);Object.defineProperty(window.navigator,"pointerEnabled",{value:!0,enumerable:!0})}}(window.PointerEventsPolyfill),function(e){function t(e){if(!i.pointermap.has(e))throw Error("InvalidPointerId")}var n,r,i=e.dispatcher,o=window.navigator;o.msPointerEnabled?(n=function(e){t(e),this.msSetPointerCapture(e)},r=function(e){t(e),this.msReleasePointerCapture(e)}):(n=function(e){t(e),i.setCapture(e,this)},r=function(e){t(e),i.releaseCapture(e,this)}),Element.prototype.setPointerCapture||Object.defineProperties(Element.prototype,{setPointerCapture:{value:n},releasePointerCapture:{value:r}})}(window.PointerEventsPolyfill),PointerGestureEvent.prototype.preventTap=function(){this.tapPrevented=!0},function(e){e=e||{},e.utils={LCA:{find:function(e,t){if(e===t)return e;if(e.contains){if(e.contains(t))return e;if(t.contains(e))return t}var n=this.depth(e),r=this.depth(t),i=n-r;for(i>0?e=this.walk(e,i):t=this.walk(t,-i);e&&t&&e!==t;)e=this.walk(e,1),t=this.walk(t,1);return e},walk:function(e,t){for(var n=0;t>n;n++)e=e.parentNode;return e},depth:function(e){for(var t=0;e;)t++,e=e.parentNode;return t}}},e.findLCA=function(t,n){return e.utils.LCA.find(t,n)},window.PointerGestures=e}(window.PointerGestures),function(e){var t;if("undefined"!=typeof WeakMap&&0>navigator.userAgent.indexOf("Firefox/"))t=WeakMap;else{var n=Object.defineProperty,r=Object.hasOwnProperty,i=(new Date).getTime()%1e9;t=function(){this.name="__st"+(1e9*Math.random()>>>0)+(i++ +"__")},t.prototype={set:function(e,t){n(e,this.name,{value:t,writable:!0})},get:function(e){return r.call(e,this.name)?e[this.name]:void 0},"delete":function(e){this.set(e,void 0)}}}e.SideTable=t}(window.PointerGestures),function(e){function t(){this.ids=[],this.pointers=[]}t.prototype={set:function(e,t){var n=this.ids.indexOf(e);n>-1?this.pointers[n]=t:(this.ids.push(e),this.pointers.push(t))},has:function(e){return this.ids.indexOf(e)>-1},"delete":function(e){var t=this.ids.indexOf(e);t>-1&&(this.ids.splice(t,1),this.pointers.splice(t,1))},get:function(e){var t=this.ids.indexOf(e);return this.pointers[t]},get size(){return this.pointers.length},clear:function(){this.ids.length=0,this.pointers.length=0}},window.Map&&(t=window.Map),e.PointerMap=t}(window.PointerGestures),function(e){var t={handledEvents:new e.SideTable,targets:new e.SideTable,handlers:{},recognizers:{},events:["pointerdown","pointermove","pointerup","pointerover","pointerout","pointercancel"],registerRecognizer:function(e,t){var n=t;this.recognizers[e]=n,this.events.forEach(function(e){if(n[e]){var t=n[e].bind(n);this.addHandler(e,t)}},this)},addHandler:function(e,t){var n=e;this.handlers[n]||(this.handlers[n]=[]),this.handlers[n].push(t)},registerTarget:function(e){this.listen(this.events,e)},unregisterTarget:function(e){this.unlisten(this.events,e)},eventHandler:function(e){if(!this.handledEvents.get(e)){var t,n=e.type;(t=this.handlers[n])&&this.makeQueue(t,e),this.handledEvents.set(e,!0)}},makeQueue:function(e,t){var n=this.cloneEvent(t);setTimeout(this.runQueue.bind(this,e,n),0)},runQueue:function(e,t){this.currentPointerId=t.pointerId;for(var n,r=0,i=e.length;i>r&&(n=e[r]);r++)n(t);this.currentPointerId=0},listen:function(e,t){e.forEach(function(e){this.addEvent(e,this.boundHandler,!1,t)},this)},unlisten:function(e){e.forEach(function(e){this.removeEvent(e,this.boundHandler,!1,inTarget)},this)},addEvent:function(e,t,n,r){r.addEventListener(e,t,n)},removeEvent:function(e,t,n,r){r.removeEventListener(e,t,n)},makeEvent:function(e,t){return new PointerGestureEvent(e,t)},cloneEvent:function(e){var t={};for(var n in e)t[n]=e[n];return t},dispatchEvent:function(e,t){var n=t||this.targets.get(e);n&&(n.dispatchEvent(e),e.tapPrevented&&this.preventTap(this.currentPointerId))},asyncDispatchEvent:function(e,t){var n=function(){this.dispatchEvent(e,t)}.bind(this);setTimeout(n,0)},preventTap:function(e){var t=this.recognizers.tap;t&&t.preventTap(e)}};t.boundHandler=t.eventHandler.bind(t),e.dispatcher=t,e.register=function(t){var n=window.PointerEventsPolyfill;n&&n.register(t),e.dispatcher.registerTarget(t)},t.registerTarget(document)}(window.PointerGestures),function(e){var t=e.dispatcher,n={HOLD_DELAY:200,WIGGLE_THRESHOLD:16,events:["pointerdown","pointermove","pointerup","pointercancel"],heldPointer:null,holdJob:null,pulse:function(){var e=Date.now()-this.heldPointer.timeStamp,t=this.held?"holdpulse":"hold";this.fireHold(t,e),this.held=!0},cancel:function(){clearInterval(this.holdJob),this.held&&this.fireHold("release"),this.held=!1,this.heldPointer=null,this.target=null,this.holdJob=null},pointerdown:function(e){e.isPrimary&&!this.heldPointer&&(this.heldPointer=e,this.target=e.target,this.holdJob=setInterval(this.pulse.bind(this),this.HOLD_DELAY))},pointerup:function(e){this.heldPointer&&this.heldPointer.pointerId===e.pointerId&&this.cancel()},pointercancel:function(){this.cancel()},pointermove:function(e){if(this.heldPointer&&this.heldPointer.pointerId===e.pointerId){var t=e.clientX-this.heldPointer.clientX,n=e.clientY-this.heldPointer.clientY;t*t+n*n>this.WIGGLE_THRESHOLD&&this.cancel()}},fireHold:function(e,n){var r={pointerType:this.heldPointer.pointerType};n&&(r.holdTime=n);var i=t.makeEvent(e,r);t.dispatchEvent(i,this.target),i.tapPrevented&&t.preventTap(this.heldPointer.pointerId)}};t.registerRecognizer("hold",n)}(window.PointerGestures),function(e){var t=e.dispatcher,n=new e.PointerMap,r={events:["pointerdown","pointermove","pointerup","pointercancel"],WIGGLE_THRESHOLD:4,clampDir:function(e){return e>0?1:-1},calcPositionDelta:function(e,t){var n=0,r=0;return e&&t&&(n=t.pageX-e.pageX,r=t.pageY-e.pageY),{x:n,y:r}},fireTrack:function(e,n,r){var i=r,o=this.calcPositionDelta(i.downEvent,n),a=this.calcPositionDelta(i.lastMoveEvent,n);a.x&&(i.xDirection=this.clampDir(a.x)),a.y&&(i.yDirection=this.clampDir(a.y));var s={dx:o.x,dy:o.y,ddx:a.x,ddy:a.y,clientX:n.clientX,clientY:n.clientY,pageX:n.pageX,pageY:n.pageY,screenX:n.screenX,screenY:n.screenY,xDirection:i.xDirection,yDirection:i.yDirection,trackInfo:i.trackInfo,pointerType:n.pointerType};"trackend"===e&&(s._releaseTarget=n.target);var l=t.makeEvent(e,s);i.lastMoveEvent=n,t.dispatchEvent(l,i.downTarget)},pointerdown:function(e){if(e.isPrimary&&("mouse"===e.pointerType?1===e.buttons:!0)){var t={downEvent:e,downTarget:e.target,trackInfo:{},lastMoveEvent:null,xDirection:0,yDirection:0,tracking:!1};n.set(e.pointerId,t)}},pointermove:function(e){var t=n.get(e.pointerId);if(t)if(t.tracking)this.fireTrack("track",e,t);else{var r=this.calcPositionDelta(t.downEvent,e),i=r.x*r.x+r.y*r.y;i>this.WIGGLE_THRESHOLD&&(t.tracking=!0,this.fireTrack("trackstart",t.downEvent,t),this.fireTrack("track",e,t))}},pointerup:function(e){var t=n.get(e.pointerId);t&&(t.tracking&&this.fireTrack("trackend",e,t),n.delete(e.pointerId))},pointercancel:function(e){this.pointerup(e)}};t.registerRecognizer("track",r)}(window.PointerGestures),function(e){var t=e.dispatcher,n={MIN_VELOCITY:.5,MAX_QUEUE:4,moveQueue:[],target:null,pointerId:null,events:["pointerdown","pointermove","pointerup","pointercancel"],pointerdown:function(e){e.isPrimary&&!this.pointerId&&(this.pointerId=e.pointerId,this.target=e.target,this.addMove(e))},pointermove:function(e){e.pointerId===this.pointerId&&this.addMove(e)},pointerup:function(e){e.pointerId===this.pointerId&&this.fireFlick(e),this.cleanup()},pointercancel:function(){this.cleanup()},cleanup:function(){this.moveQueue=[],this.target=null,this.pointerId=null},addMove:function(e){this.moveQueue.length>=this.MAX_QUEUE&&this.moveQueue.shift(),this.moveQueue.push(e)},fireFlick:function(e){for(var n,r,i,o,a,s,l,u=e,c=this.moveQueue.length,d=0,h=0,p=0,f=0;c>f&&(l=this.moveQueue[f]);f++)n=u.timeStamp-l.timeStamp,r=u.clientX-l.clientX,i=u.clientY-l.clientY,o=r/n,a=i/n,s=Math.sqrt(o*o+a*a),s>p&&(d=o,h=a,p=s);var v=Math.abs(d)>Math.abs(h)?"x":"y",m=this.calcAngle(d,h);if(Math.abs(p)>=this.MIN_VELOCITY){var g=t.makeEvent("flick",{xVelocity:d,yVelocity:h,velocity:p,angle:m,majorAxis:v,pointerType:e.pointerType});t.dispatchEvent(g,this.target)}},calcAngle:function(e,t){return 180*Math.atan2(t,e)/Math.PI}};t.registerRecognizer("flick",n)}(window.PointerGestures),function(e){var t=e.dispatcher,n=new e.PointerMap,r={events:["pointerdown","pointermove","pointerup","pointercancel"],pointerdown:function(e){e.isPrimary&&!e.tapPrevented&&n.set(e.pointerId,{target:e.target,x:e.clientX,y:e.clientY})},pointermove:function(e){if(e.isPrimary){var t=n.get(e.pointerId);t&&e.tapPrevented&&n.delete(e.pointerId)}},pointerup:function(r){var i=n.get(r.pointerId);if(i&&!r.tapPrevented){var o=e.findLCA(i.target,r.target);if(o){var a=t.makeEvent("tap",{x:r.clientX,y:r.clientY,pointerType:r.pointerType});t.dispatchEvent(a,o)}}n.delete(r.pointerId)},pointercancel:function(e){n.delete(e.pointerId)},preventTap:function(e){n.delete(e)}};t.registerRecognizer("tap",r)}(window.PointerGestures),function(){var e=Array.prototype.forEach.call.bind(Array.prototype.forEach);window.forEach=e}(),function(){function e(e,n){1==arguments.length&&(n=e,e=null),n&&n.hasOwnProperty("constructor")||(n.constructor=function(){this.super()});var r=n.constructor,o=e&&e.prototype||Object.prototype;return r.prototype=t(o,n),"super"in r.prototype||(r.prototype.super=i),r}function t(e,t){return Object.create(e,n(t))}function n(e){var t={};for(var n in e)t[n]=r(e,n);return t}function r(e,t){return e&&Object.getOwnPropertyDescriptor(e,t)||r(Object.getPrototypeOf(e),t)}function i(e){var t=i.caller,n=t._nom;if(!n&&(n=t._nom=s.call(this,t),!n))return console.warn('called super() on a method not in "this"'),void 0;"_super"in t||a(t,n,Object.getPrototypeOf(this));var r=t._super;if(r){var o=r[n];return"_super"in o||a(o,n,r),o.apply(this,e||[])}}function o(e,t,n){for(var r=e;r&&(!r.hasOwnProperty(t)||r[t]==n);)r=Object.getPrototypeOf(r);return r}function a(e,t,n){e._super=o(n,t,e),e._super&&(e._super[t]._nom=t)}function s(e){for(var t in this){var n=r(this,t);if(n.value==e)return t}}window.$class=e,window.extend=t,window.$super=i}(),function(){function e(e,r){if(e!=window){if(!(e&&e instanceof HTMLElement))throw"First argument to Polymer.register must be an HTMLElement";var i=mixin({},Polymer.base,r);i.elementElement=e,Polymer.addResolvePath(i,e),i.installTemplate=function(){this.super(),n.call(this,e)},i.readyCallback=t,Polymer.parseHostEvents(e.attributes,i),Polymer.publishAttributes(e,i),Polymer.installSheets(e),Polymer.shimStyling(e),e.register({prototype:i}),logFlags.comps&&console.log("Polymer: element registered"+e.options.name)}}function t(){this.installTemplate(),i.call(this)}function n(e){var t=e.querySelector("template");if(t){var n=this.webkitCreateShadowRoot();return n.applyAuthorStyles=this.applyAuthorStyles,CustomElements.watchShadow(this),n.host=this,n.appendChild(t.createInstance()),PointerGestures.register(n),PointerEventsPolyfill.setTouchAction(n,this.getAttribute("touch-action")),r.call(this,n),n}}function r(e){CustomElements.takeRecords(),Polymer.bindModel.call(this,e),Polymer.marshalNodeReferences.call(this,e);var t=Polymer.accumulateEvents(e);Polymer.bindAccumulatedLocalEvents.call(this,e,t)}function i(){Polymer.observeProperties.call(this),Polymer.takeAttributes.call(this);var e=Polymer.accumulateHostEvents.call(this);Polymer.bindAccumulatedHostEvents.call(this,e),this.ready&&this.ready()}function o(e,t){for(var n=e;n&&n!=this;){var r=Array.prototype.indexOf.call(t,n);if(r>=0)return r;n=n.parentNode}}window.logFlags||{},window.Polymer={register:e,findDistributedTarget:o,instanceReady:i}}(),function(e){var t=window.logFlags||{},n={"super":$super,isPolymerElement:!0,bind:function(){Polymer.bind.apply(this,arguments)},unbind:function(){Polymer.unbind.apply(this,arguments)},job:function(){return Polymer.job.apply(this,arguments)},asyncMethod:function(e,t,n){var r=t&&t.length?t:[t];return window.setTimeout(function(){(this[e]||e).apply(this,r)}.bind(this),n||0)},dispatch:function(e,t){this[e]&&this[e].apply(this,t)},fire:function(e,n,r){var i=r||this;return t.events&&console.log("[%s]: sending [%s]",i.localName,e),i.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:n})),n},asend:function(){this.asyncMethod("send",arguments)},classFollows:function(e,t,n){t&&t.classList.remove(n),e&&e.classList.add(n)}};n.send=n.fire,e.base=n}(window.Polymer),function(){function e(e,n,r,i){t.bind&&console.log("[%s]: bindProperties: [%s] to [%s].[%s]",r.localName||"object",i,e.localName,n);var o=PathObserver.getValueAtPath(r,i);(null==o||void 0===o)&&PathObserver.setValueAtPath(r,i,e[n]),Object.defineProperty(e,n,{get:function(){return PathObserver.getValueAtPath(r,i)},set:function(e){PathObserver.setValueAtPath(r,i,e)},configurable:!0,enumerable:!0})}var t=window.logFlags||{};Polymer.bindProperties=e}(),function(){function e(e,t,n){var r=u.get(e);r||u.set(e,r={}),r[t.toLowerCase()]=n}function t(e,t){var n=u.get(e);n&&delete n[t.toLowerCase()]}function n(n){var r=n.prototype,i=r.bind,o=r.unbind;r.bind=function(t,n,r){i.apply(this,arguments),e(this,t,r)},r.unbind=function(e){o.apply(this,arguments),t(this,e)}}function r(e){return e&&u.get(e)||c}function i(e,t){return r(e)[t.toLowerCase()]}function o(e){l.bind&&console.group("[%s] bindModel",this.localName),HTMLTemplateElement.bindAllMustachesFrom_(e,this),l.bind&&console.groupEnd()}function a(t,n,r){var i=Polymer.propertyForAttribute.call(this,t);i?(e(this,i,r),Polymer.bindProperties(this,i,n,r)):HTMLElement.prototype.bind.apply(this,arguments)}function s(e){var n=Polymer.propertyForAttribute.call(this,e);n?(t(this,e),Object.defineProperty(this,e,{value:this[e],enumerable:!0,writable:!0,configurable:!0})):HTMLElement.prototype.unbind.apply(this,arguments)}var l=window.logFlags||{},u=new SideTable;[Node,Element,Text,HTMLInputElement].forEach(n);var c={},d=/\{\{([^{}]*)}}/;Polymer.bind=a,Polymer.unbind=s,Polymer.getBinding=i,Polymer.bindModel=o,Polymer.bindPattern=d}(),function(){function e(){forEach(this.attributes,function(e){var i=t.call(this,e.name);if(i){if(e.value.search(r)>=0)return;var o=this[i],a=n(e.value,o);a!==o&&(this[i]=a)}},this)}function t(e){var t=Object.keys(this[i]);return t[t.map(l).indexOf(e.toLowerCase())]}function n(e,t){var n=typeof t;switch(t instanceof Date&&(n="date"),n){case"string":return e;case"date":return new Date(Date.parse(e)||Date.now());case"boolean":if(""==e)return!0}switch(e){case"true":return!0;case"false":return!1}var r=parseFloat(e);return r+""===e?r:e}var r=Polymer.bindPattern,i="__published",o="attributes",a="publish",s=function(e,t){var n={},r=e.getAttribute(o);if(r){var s=r.split(r.indexOf(",")>=0?",":" ");s.forEach(function(e){e=e.trim(),e&&(n[e]=null)})}var l=e.options.prototype;Object.keys(n).forEach(function(e){e in t||e in l||(t[e]=n[e])});var u=t[a];u&&(Object.keys(u).forEach(function(e){t[e]=u[e]}),n=mixin(n,u)),t[i]=mixin({},l[i],n)},l=String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase);Polymer.takeAttributes=e,Polymer.publishAttributes=s,Polymer.propertyForAttribute=t}(),Polymer.marshalNodeReferences=function(e){var t=this.$=this.$||{};if(e){var n=e.querySelectorAll("[id]");forEach(n,function(e){t[e.id]=e})}},function(){function e(e,t,n){var r=n.bind(this);for(var i in t)l.events&&console.log('[%s] bindAccumulatedEvents: addEventListener("%s", listen)',e.localName||"root",i),e.addEventListener(i,r)}function t(t){e.call(this,this,t,i)}function n(t,n){e.call(this,t,n,r)}function r(e){if(!e.cancelBubble){e.on=u+e.type,l.events&&console.group("[%s]: listenLocal [%s]",this.localName,e.on);for(var t=e.target;t&&t!=this;){var n=w(t);if(n&&a.call(n,t,e))return;t=t.parentNode}l.events&&console.groupEnd()}}function i(e){e.cancelBubble||(l.events&&console.group("[%s]: listenHost [%s]",this.localName,e.type),s.call(this,this,e),l.events&&console.groupEnd())}function o(e){var t=T.get(e);return t||(t=[],T.set(e,t)),t}function a(e,t){if(e.attributes){var n=o(t);if(0>n.indexOf(e)){n.push(e);var r=e.getAttribute(t.on);r&&(l.events&&console.log("[%s] found handler name [%s]",this.localName,r),E(this,r,[t,t.detail,e]))}}return t.cancelBubble}function s(e,t){var n=M.call(e,t.type);return n&&(l.events&&console.log("[%s] found host handler name [%s]",e.localName,n),E(e,n,[t,t.detail,e])),t.cancelBubble}var l=window.logFlags||{},u="on-",c=function(e,t){t.eventDelegates=d(e)},d=function(e){var t={};if(e)for(var n,r=0;n=e[r];r++)n.name.slice(0,u.length)==u&&(t[n.name.slice(u.length)]=n.value);return t},h=function(e,t){var n=t||{};return p(e,n),m(e,n),g(e,n),n},p=function(e,t){var n=e.attributes;if(n)for(var r,i=0;r=n[i];i++)r.name.slice(0,u.length)===u&&v(r.name.slice(u.length),t)},f={webkitanimationstart:"webkitAnimationStart",webkitanimationend:"webkitAnimationEnd",webkittransitionend:"webkitTransitionEnd",domfocusout:"DOMFocusOut",domfocusin:"DOMFocusIn"},v=function(e,t){var n=f[e]||e;t[n]=1},m=function(e,t){for(var n,r=e.childNodes,i=0;n=r[i];i++)h(n,t)},g=function(e,t){if("template"==e.localName){var n=b(e);n&&m(n,t)}},b=function(e){return e.ref?e.ref.content:e.content},y=function(e){for(var t=e||{},n=this.__proto__;n&&n!==HTMLElement.prototype;){if(n.hasOwnProperty("eventDelegates"))for(var r in n.eventDelegates)v(r,t);n=n.__proto__}return t},w=function(e){for(var t=e;t.parentNode&&"shadow-root"!==t.localName;)t=t.parentNode;return t.host},E=function(e,t,n){e&&(l.events&&console.group("[%s] dispatch [%s]",e.localName,t),e.dispatch(t,n),l.events&&console.groupEnd())},T=new SideTable("handledList"),M=function(e){for(var t=this;t;){if(t.hasOwnProperty("eventDelegates")){var n=t.eventDelegates[e]||t.eventDelegates[e.toLowerCase()];if(n)return n}t=t.__proto__}};Polymer.parseHostEvents=c,Polymer.accumulateEvents=h,Polymer.accumulateHostEvents=y,Polymer.bindAccumulatedHostEvents=t,Polymer.bindAccumulatedLocalEvents=n}(),function(){function e(){for(var e in this)t.call(this,e)}function t(e){n.call(this,e)&&(i.observe&&console.log("["+this.localName+"] watching ["+e+"]"),new PathObserver(this,e,function(t,n){i.data&&console.log("[%s#%s] watch: [%s] now [%s] was [%s]",this.localName,this.node.id||"",e,this[e],n),r.call(this,e,n)}.bind(this)))}function n(e){return"_"!=e[0]&&!(e in Object.prototype)&&Boolean(this[e+o])}function r(e,t){var n=e+o;this[n]&&this[n](t)}var i=window.logFlags||{},o="Changed";Polymer.observeProperties=e}(),function(){function e(e){t(e),n(e)}function t(e){var t=e.querySelectorAll("[rel=stylesheet]"),n=e.querySelector("template");if(n)var r=templateContent(n);r&&f(t,function(e){if(!e.hasAttribute(p)){e.parentNode.removeChild(e);var t=o(e);t&&r.insertBefore(t,r.firstChild)}})}function n(e){var t=e.globalStyles||(e.globalStyles=l(e,"global"));a(t,u.head)}function r(e,t){var n=t.controllerStyles||(t.controllerStyles=l(t,"controller"));c.queue(function(){var t=i(e);t&&(Polymer.shimPolyfillDirectives(n,e.localName),a(n,t))})}function i(e){for(var t=e;t.parentNode&&"shadow-root"!=t.localName;)t=t.parentNode;return t==u?u.head:t}function o(e){if(e.__resource){var t=u.createElement("style");return t.textContent=e.__resource,t}console.warn("Could not find content for stylesheet",e)}function a(e,t){e.forEach(function(e){t.appendChild(e.cloneNode(!0))})}function s(e,t){return h?h.call(e,t):void 0}function l(e,t){var n=[],r=e.querySelectorAll("[rel=stylesheet]"),i="["+p+"="+t+"]";Array.prototype.forEach.call(r,function(e){s(e,i)&&(e.parentNode.removeChild(e),n.push(o(e)))});var a=e.querySelectorAll("style");return Array.prototype.forEach.call(a,function(e){s(e,i)&&(e.parentNode.removeChild(e),n.push(e))}),n}window.logFlags||{};var u=window.ShadowDOMPolyfill?ShadowDOMPolyfill.wrap(document):document,c={list:[],queue:function(e){e&&c.list.push(e),c.queueFlush()},queueFlush:function(){c.flushing||(c.flushing=!0,requestAnimationFrame(c.flush))},flush:function(){c.list.forEach(function(e){e()}),c.list=[],c.flushing=!1}},d=HTMLElement.prototype,h=d.matches||d.matchesSelector||d.webkitMatchesSelector||d.mozMatchesSelector,p="polymer-scope",f=Array.prototype.forEach.call.bind(Array.prototype.forEach);Polymer.installSheets=e,Polymer.installControllerStyles=r}(),function(){var e=Array.prototype.forEach.call.bind(Array.prototype.forEach),t=Array.prototype.concat.call.bind(Array.prototype.concat),n=Array.prototype.slice.call.bind(Array.prototype.slice),r={hostRuleRe:/@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,selectorRe:/([^{]*)({[\s\S]*?})/gim,hostFixableRe:/^[.\[:]/,cssCommentRe:/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,cssPolyfillCommentRe:/\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,selectorReSuffix:"([>\\s~+[.,{:][\\s\\S]*)?$",hostRe:/@host/gim,cache:{},shimStyling:function(e){if(window.ShadowDOMPolyfill&&e){var t=e.options.name;r.cacheDefinition(e),r.shimPolyfillDirectives(e.styles,t),r.applyShimming(r.stylesForElement(e),t)}},shimShadowDOMStyling:function(e,t){window.ShadowDOMPolyfill&&(r.shimPolyfillDirectives(e,t),r.applyShimming(e,t))},applyShimming:function(e,t){this.shimAtHost(e,t),this.shimScoping(e,t)},cacheDefinition:function(e){var t=e.options.name,i=e.querySelector("template"),o=i&&templateContent(i),a=o&&o.querySelectorAll("style");e.styles=a?n(a):[],e.templateContent=o,r.cache[t]=e},stylesForElement:function(e){var r=e.styles,i=e.templateContent&&e.templateContent.querySelector("shadow");if(i||null===e.templateContent){var o=this.findExtendee(e.options.name);if(o){var a=this.stylesForElement(o);r=t(n(a),n(r))}}return r},findExtendee:function(e){var t=this.cache[e];return t&&this.cache[t.options.extends]},shimPolyfillDirectives:function(t,n){window.ShadowDOMPolyfill&&t&&e(t,function(e){e.textContent=this.convertPolyfillDirectives(e.textContent,n)},this)},shimAtHost:function(e,t){if(e){var n=this.convertAtHostStyles(e,t);this.addCssToDocument(n)}},shimScoping:function(e,t){e&&this.applyPseudoScoping(e,t)},convertPolyfillDirectives:function(e){for(var t,n="",r=0;t=this.cssPolyfillCommentRe.exec(e);)n+=e.substring(r,t.index),n+=t[1].slice(0,-2)+"{",r=this.cssPolyfillCommentRe.lastIndex;return n+=e.substring(r,e.length)},findAtHostRules:function(e,t){return Array.prototype.filter.call(e,this.isHostRule.bind(this,t))},isHostRule:function(e,t){return t.selectorText&&t.selectorText.match(e)||t.cssRules&&this.findAtHostRules(t.cssRules,e).length||t.type==CSSRule.WEBKIT_KEYFRAMES_RULE},convertAtHostStyles:function(e,t){for(var n,r=this.stylesToCssText(e),i="",o=0;n=this.hostRuleRe.exec(r);)i+=r.substring(o,n.index),i+=this.scopeHostCss(n[1],t),o=this.hostRuleRe.lastIndex;i+=r.substring(o,r.length);var a=RegExp("^"+t+this.selectorReSuffix,"m"),r=this.rulesToCss(this.findAtHostRules(this.cssToRules(i),a));return r},scopeHostCss:function(e,t){for(var n,r="";n=this.selectorRe.exec(e);)r+=this.scopeHostSelector(n[1],t)+" "+n[2]+"\n ";return r},scopeHostSelector:function(e,t){var n=[],r=e.split(",");return r.forEach(function(e){e=e.trim(),e.indexOf("*")>=0?e=e.replace("*",t):e.match(this.hostFixableRe)&&(e=t+e),n.push(e)},this),n.join(", ")},applyPseudoScoping:function(t,n){e(t,function(e){e.parentNode&&e.parentNode.removeChild(e)});var r=this.stylesToCssText(t).replace(this.hostRuleRe,""),i=this.cssToRules(r),r=this.pseudoScopeRules(i,n);this.addCssToDocument(r)},pseudoScopeRules:function(t,n){var r="";return e(t,function(e){e.selectorText&&e.style&&e.style.cssText?(r+=this.pseudoScopeSelector(e.selectorText,n)+" {\n ",r+=e.style.cssText+"\n}\n\n"):e.media?(r+="@media "+e.media.mediaText+" {\n",r+=this.pseudoScopeRules(e.cssRules,n),r+="\n}\n\n"):e.cssText&&(r+=e.cssText+"\n\n")},this),r},pseudoScopeSelector:function(e,t){var n=[],r=e.split(",");return r.forEach(function(e){n.push(t+" "+e.trim())}),n.join(", ")},stylesToCssText:function(t,n){var r="";return e(t,function(e){r+=e.textContent+"\n\n"}),n||(r=this.stripCssComments(r)),r},stripCssComments:function(e){return e.replace(this.cssCommentRe,"")},cssToRules:function(e){var t=document.createElement("style");t.textContent=e,document.head.appendChild(t);var n=t.sheet.cssRules;return t.parentNode.removeChild(t),n},rulesToCss:function(e){for(var t=0,n=[];e.length>t;t++)n.push(e[t].cssText);return n.join("\n\n")},addCssToDocument:function(e){e&&this.getSheet().appendChild(document.createTextNode(e))},getSheet:function(){return this.sheet||(this.sheet=document.createElement("style")),this.sheet},apply:function(){this.addCssToDocument("style { display: none !important; }\n"),document.head.appendChild(this.getSheet())}};document.addEventListener("WebComponentsReady",function(){r.apply()}),Polymer.shimStyling=r.shimStyling,Polymer.shimShadowDOMStyling=r.shimShadowDOMStyling,Polymer.shimPolyfillDirectives=r.shimPolyfillDirectives.bind(r)}(window),function(){function e(e,t){var r=n(t);e.resolvePath=function(e){return r+e}}function t(e){if(e){var t=e.split("/");return t.pop(),t.push(""),t.join("/")}return""}function n(e){return t(HTMLImports.getDocumentUrl(e.ownerDocument))}Polymer.addResolvePath=e}(),function(){function e(e,n,r){var i=e||new t(this);return i.stop(),i.go(n,r),i}var t=function(e){this.context=e};t.prototype={go:function(e,t){this.callback=e,this.handle=setTimeout(function(){this.handle=null,e.call(this.context)}.bind(this),t)},stop:function(){this.handle&&(clearTimeout(this.handle),this.handle=null)},complete:function(){this.handle&&(this.stop(),this.callback.call(this.context))}},Polymer.job=e}(),function(){document.write("\n"),document.write("\n"),document.write('\n'),document.write('\n'),document.write("\n"),document.write(""),document.write("\n"),window.addEventListener("WebComponentsReady",function(){document.body.style.webkitTransition="opacity 0.3s",document.body.style.opacity=1})}(); +//@ sourceMappingURL=polymer.min.js.map \ No newline at end of file diff --git a/components/handsontable/demo/web_component/x-tile.html b/components/handsontable/demo/web_component/x-tile.html new file mode 100644 index 00000000..3fe75c20 --- /dev/null +++ b/components/handsontable/demo/web_component/x-tile.html @@ -0,0 +1,45 @@ + + + + + diff --git a/components/handsontable/dist/README.md b/components/handsontable/dist/README.md new file mode 100644 index 00000000..efbb1a24 --- /dev/null +++ b/components/handsontable/dist/README.md @@ -0,0 +1,29 @@ +# Handsontable distributions + +## Full distribution (recommended) + +The full distribution allows you to use Handsontable by just including jQuery and 2 files: +```html + + + +``` + +**jquery.handsontable.full.js** and **jquery.handsontable.full.css** are compiled with ___all___ the needed dependencies. + +Using this has the same effect as loading all the dependencies from the Bare distribution (see below). + +## Bare distribution + +If you are a "Bob the Builder" kind of hacker, you will need to load Handsontable JS, CSS and their dependecies: +```html + + + + + + + +``` + +**jquery.handsontable.js** and **jquery.handsontable.css** are compiled ___without___ the needed dependencies. \ No newline at end of file diff --git a/components/handsontable/dist/jquery.handsontable.css b/components/handsontable/dist/jquery.handsontable.css new file mode 100644 index 00000000..d83a0f30 --- /dev/null +++ b/components/handsontable/dist/jquery.handsontable.css @@ -0,0 +1,360 @@ +/** + * Handsontable 0.9.4 + * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs + * + * Copyright 2012, Marcin Warpechowski + * Licensed under the MIT license. + * http://handsontable.com/ + * + * Date: Fri Jun 07 2013 01:26:00 GMT+0200 (Central European Daylight Time) + */ + +.handsontable { + position: relative; + font-family: Arial, Helvetica, sans-serif; + line-height: 1.3em; + font-size: 13px; +} + +.handsontable.hidden { + display: none; + left: 0; + position: absolute; + top: 0; +} + +.handsontable * { + box-sizing: content-box; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; +} + +.handsontable table { + border-collapse: separate; /*it must be separate, otherwise there are offset miscalculations in WebKit: http://stackoverflow.com/questions/2655987/border-collapse-differences-in-ff-and-webkit*/ + position: relative; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + -ms-user-select: none; + /*user-select: none; /*no browser supports unprefixed version*/ + border-spacing: 0; + margin: 0; + border-width: 0; + table-layout: fixed; + width: 0; + outline-width: 0; + /* reset bootstrap table style. for more info see: https://github.com/warpech/jquery-handsontable/issues/224 */ + max-width: none; + max-height: none; +} + +.handsontable col { + width: 50px; +} + +.handsontable col.rowHeader { + width: 50px; +} + +.handsontable th, +.handsontable td { + border-right: 1px solid #CCC; + border-bottom: 1px solid #CCC; + height: 22px; + empty-cells: show; + line-height: 21px; + padding: 0 4px 0 4px; /* top, bottom padding different than 0 is handled poorly by FF with HTML5 doctype */ + background-color: #FFF; + font-size: 12px; + vertical-align: top; + overflow: hidden; + outline-width: 0; +} + +.handsontable th:last-child { + /*Foundation framework fix*/ + border-right: 1px solid #CCC; + border-bottom: 1px solid #CCC; +} + +.handsontable tr:first-child th.htNoFrame, +.handsontable th:first-child.htNoFrame, +.handsontable th.htNoFrame { + border-left-width: 0; + background-color: white; + border-color: #FFF; +} + +.handsontable th:first-child, +.handsontable td:first-child, +.handsontable .htNoFrame + th, +.handsontable .htNoFrame + td { + border-left: 1px solid #CCC; +} + +.handsontable tr:first-child th, +.handsontable tr:first-child td { + border-top: 1px solid #CCC; +} + +.handsontable thead tr:last-child th { + border-bottom-width: 0; +} + +.handsontable thead tr.lastChild th { + border-bottom-width: 0; +} + +.handsontable th { + background-color: #EEE; + color: #222; + text-align: center; + font-weight: normal; + white-space: nowrap; +} + +.handsontable th .small { + font-size: 12px; +} + +.handsontable thead th { + padding: 0; +} + +.handsontable th.active { + background-color: #CCC; +} + +.handsontable thead th .relative { + position: relative; + padding: 2px 4px; +} + +/* plugins */ + +.handsontable .manualColumnMover { + position: absolute; + left: 0; + top: 0; + background-color: transparent; + width: 5px; + height: 25px; + z-index: 999; + cursor: move; +} + +.handsontable th .manualColumnMover:hover, +.handsontable th .manualColumnMover.active { + background-color: #88F; +} + +.handsontable .manualColumnResizer { + position: absolute; + right: -1px; + top: 0; + background-color: transparent; + width: 5px; + height: 25px; + z-index: 999; + cursor: col-resize; +} + +.handsontable th .manualColumnResizer:hover, +.handsontable .manualColumnResizerLine .manualColumnResizer { + background-color: #AAB; +} + +.handsontable .columnSorting:hover { + text-decoration: underline; + cursor: pointer; +} + +/* border line */ +.handsontable .wtBorder { + position: absolute; + font-size: 0; +} + +.handsontable td.area { + background-color: #EEF4FF; +} + +/* fill handle */ +.handsontable .wtBorder.corner { + font-size: 0; + cursor: crosshair; +} + +.handsontable .htBorder.htFillBorder { + background: red; + width: 1px; + height: 1px; +} + +/* textarea border color */ +textarea.handsontableInput { + border: 2px solid #5292F7; + outline-width: 0; + margin: 0; + padding: 1px 4px 0 2px; + font-family: Arial, Helvetica, sans-serif; /*repeat from .handsontable (inherit doesn't work with IE<8) */ + line-height: 1.3em; /*repeat from .handsontable (inherit doesn't work with IE<8) */ + font-size: 13px; + box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.4); + resize: none; + + /*below are needed to overwrite stuff added by jQuery UI Bootstrap theme*/ + display: inline-block; + font-size: 13px; + color: #000; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.handsontableInputHolder { + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 1px; +} + +/* +TextRenderer readOnly cell +*/ +.handsontable .htDimmed { + font-style: italic; + color: #777; +} + +/* +AutocompleteRenderer down arrow +*/ +.handsontable .htAutocomplete { + position: relative; + padding-right: 20px; +} + +.handsontable .htAutocompleteArrow { + position: absolute; + top: 0; + right: 0; + font-size: 10px; + color: #EEE; + cursor: default; + width: 16px; + text-align: center; +} + +.handsontable td .htAutocompleteArrow:hover { + color: #777; +} + +/* +CheckboxRenderer +*/ +.handsontable .htCheckboxRendererInput.noValue { + opacity: 0.5; +} + +/* +NumericRenderer +*/ +.handsontable .htNumeric { + text-align: right; +} + +/* typeahead rules. Needed only if you are using the autocomplete feature */ +.handsontable .typeahead { + position: absolute; + font-family: Arial, Helvetica, sans-serif; + line-height: 1.3em; + font-size: 13px; + z-index: 10; + top: 100%; + left: 0; + float: left; + display: none; + min-width: 160px; + padding: 4px 0; + margin: 2px 0 0 0; + list-style: none; + background-color: white; + border-color: #CCC; + border-color: rgba(0, 0, 0, 0.2); + border-style: solid; + border-width: 1px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + background-clip: padding-box; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.handsontable .typeahead li { + line-height: 18px; + min-height: 18px; + display: list-item; + margin: 0; +} + +.handsontable .typeahead a { + display: block; + padding: 3px 15px; + clear: both; + font-weight: normal; + line-height: 18px; + min-height: 18px; + color: #333; + white-space: nowrap; +} + +.handsontable .typeahead li > a:hover, +.handsontable .typeahead .active > a, +.handsontable .typeahead .active > a:hover { + color: white; + text-decoration: none; + background-color: #08C; +} + +.handsontable .typeahead a { + color: #08C; + text-decoration: none; +} + +/*context menu rules*/ +ul.context-menu-list { + color: black; +} + +ul.context-menu-list li { + margin-bottom: 0; /*Foundation framework fix*/ +} + +/** + * dragdealer + */ + +.handsontable .dragdealer { + position: relative; + width: 9px; + height: 9px; + background: #F8F8F8; + border: 1px solid #DDD; +} + +.handsontable .dragdealer .handle { + position: absolute; + width: 9px; + height: 9px; + background: #C5C5C5; +} + +.handsontable .dragdealer .disabled { + background: #898989; +} \ No newline at end of file diff --git a/src/3rdparty/handsontable/jquery.handsontable.full.css b/components/handsontable/dist/jquery.handsontable.full.css similarity index 99% rename from src/3rdparty/handsontable/jquery.handsontable.full.css rename to components/handsontable/dist/jquery.handsontable.full.css index 6f794ee7..4b5a0b8f 100644 --- a/src/3rdparty/handsontable/jquery.handsontable.full.css +++ b/components/handsontable/dist/jquery.handsontable.full.css @@ -1,12 +1,12 @@ /** - * Handsontable 0.9.0-beta2 + * Handsontable 0.9.4 * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs * * Copyright 2012, Marcin Warpechowski * Licensed under the MIT license. * http://handsontable.com/ * - * Date: Wed May 15 2013 02:32:54 GMT+0200 (Central European Daylight Time) + * Date: Fri Jun 07 2013 01:26:00 GMT+0200 (Central European Daylight Time) */ .handsontable { diff --git a/src/3rdparty/handsontable/jquery.handsontable.full.js b/components/handsontable/dist/jquery.handsontable.full.js similarity index 85% rename from src/3rdparty/handsontable/jquery.handsontable.full.js rename to components/handsontable/dist/jquery.handsontable.full.js index c7364a72..000a88c8 100644 --- a/src/3rdparty/handsontable/jquery.handsontable.full.js +++ b/components/handsontable/dist/jquery.handsontable.full.js @@ -1,12 +1,12 @@ /** - * Handsontable 0.9.0-beta2 + * Handsontable 0.9.4 * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs * * Copyright 2012, Marcin Warpechowski * Licensed under the MIT license. * http://handsontable.com/ * - * Date: Wed May 15 2013 02:32:54 GMT+0200 (Central European Daylight Time) + * Date: Fri Jun 07 2013 01:26:00 GMT+0200 (Central European Daylight Time) */ /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */ @@ -25,8 +25,6 @@ var Handsontable = { //class namespace */ Handsontable.Core = function (rootElement, userSettings) { var priv - , hooks - , eventMap , datamap , grid , selection @@ -65,51 +63,6 @@ Handsontable.Core = function (rootElement, userSettings) { firstRun: true }; - hooks = { - beforeInitWalkontable: [], - - beforeInit: [], - beforeRender: [], - beforeChange: [], - beforeGet: [], - beforeSet: [], - beforeGetCellMeta: [], - beforeAutofill: [], - - afterInit: [], - afterLoadData: [], - afterRender: [], - afterChange: [], - afterGetCellMeta: [], - afterGetColHeader: [], - afterGetColWidth: [], - afterDestroy: [], - afterRemoveRow: [], - afterCreateRow: [], - afterRemoveCol: [], - afterCreateCol: [], - afterColumnResize: [], - afterColumnMove: [], - afterDeselect: [], - afterSelection: [], - afterSelectionByProp: [], - afterSelectionEnd: [], - afterSelectionEndByProp: [], - afterCopyLimit: [] - }; - - eventMap = { - onBeforeChange: "beforeChange", - onChange: "afterChange", - onCreateRow: "afterCreateRow", - onCreateCol: "afterCreateCol", - onSelection: "afterSelection", - onCopyLimit: "afterCopyLimit", - onSelectionEnd: "afterSelectionEnd", - onSelectionByProp: "afterSelectionByProp", - onSelectionEndByProp: "afterSelectionEndByProp" - }; - datamap = { recursiveDuckSchema: function (obj) { var schema; @@ -175,7 +128,7 @@ Handsontable.Core = function (rootElement, userSettings) { }, colToProp: function (col) { - col = Handsontable.PluginModifiers.run(instance, 'col', col); + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); if (priv.colToProp && typeof priv.colToProp[col] !== 'undefined') { return priv.colToProp[col]; } @@ -192,7 +145,7 @@ Handsontable.Core = function (rootElement, userSettings) { else { col = prop; } - col = Handsontable.PluginModifiers.run(instance, 'col', col); + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); return col; }, @@ -238,7 +191,7 @@ Handsontable.Core = function (rootElement, userSettings) { GridSettings.prototype.data.splice(index, 0, row); } - instance.runHooks('afterCreateRow', index); + instance.PluginHooks.run('afterCreateRow', index); instance.forceFullRender = true; //used when data was changed }, @@ -259,19 +212,19 @@ Handsontable.Core = function (rootElement, userSettings) { if (typeof data[r] === 'undefined') { data[r] = []; } - data[r].push(''); + data[r].push(null); } // Add new column constructor priv.columnSettings.push(constructor); } else { for (; r < rlen; r++) { - data[r].splice(index, 0, ''); + data[r].splice(index, 0, null); } // Add new column constructor at given index priv.columnSettings.splice(index, 0, constructor); } - instance.runHooks('afterCreateCol', index); + instance.PluginHooks.run('afterCreateCol', index); instance.forceFullRender = true; //used when data was changed }, @@ -288,7 +241,7 @@ Handsontable.Core = function (rootElement, userSettings) { index = -amount; } GridSettings.prototype.data.splice(index, amount); - instance.runHooks('afterRemoveRow', index, amount); + instance.PluginHooks.run('afterRemoveRow', index, amount); instance.forceFullRender = true; //used when data was changed }, @@ -311,7 +264,7 @@ Handsontable.Core = function (rootElement, userSettings) { for (var r = 0, rlen = instance.countRows(); r < rlen; r++) { data[r].splice(index, amount); } - instance.runHooks('afterRemoveCol', index, amount); + instance.PluginHooks.run('afterRemoveCol', index, amount); priv.columnSettings.splice(index, amount); instance.forceFullRender = true; //used when data was changed }, @@ -323,43 +276,22 @@ Handsontable.Core = function (rootElement, userSettings) { * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array */ - spliceCol: function (col, index, amount/*, elements... */) { - var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [] - , before = [] - , removed = [] - , after = [] - , result - , data = GridSettings.prototype.data - , diff = elements.length - amount - , split = index + amount - , length = data.length - , r = 0; - // Prepare data table - for (; r < length; r++) { - if (r < index) { - before.push(data[r][col]); - } - else if (r >= split) { - after.push(data[r][col]); - } - else { - removed.push(data[r][col]); - } - } - // Calculate result data - result = [].concat(before, elements, after); - // Create missing rows - if (diff > 0) { - length += diff; - instance.alter('insert_row', null, diff, 'spliceCol', true); - } - for (r = 0; r < length; r++) { - data[r][col] = typeof result[r] !== "undefined" ? result[r] : null; + spliceCol: function (col, index, amount/*, elements...*/) { + var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : []; + + var colData = instance.getDataAtCol(col); + var removed = colData.slice(index, index + amount); + var after = colData.slice(index + amount); + + Handsontable.helper.extendArray(elements, after); + var i = 0; + while (i < amount) { + elements.push(null); //add null in place of removed elements + i++; } - // Re-render table - instance.forceFullRender = true; //used when data was changed - selection.refreshBorders(); - // Return removed elements + Handsontable.helper.to2dArray(elements); + instance.populateFromArray(index, col, elements, null, null, 'spliceCol'); + return removed; }, @@ -370,43 +302,21 @@ Handsontable.Core = function (rootElement, userSettings) { * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array */ - spliceRow: function (row, index, amount/*, elements... */) { - var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [] - , before = [] - , removed = [] - , after = [] - , result - , data = GridSettings.prototype.data[row] - , diff = elements.length - amount - , split = index + amount - , length = data.length - , c = 0; - // Prepare data table - for (; c < length; c++) { - if (c < index) { - before.push(data[c]); - } - else if (c >= split) { - after.push(data[c]); - } - else { - removed.push(data[c]); - } - } - // Calculate result data - result = [].concat(before, elements, after); - // Create missing rows - if (diff > 0) { - length += diff; - instance.alter('insert_col', null, diff, 'spliceRow', true); - } - for (c = 0; c < length; c++) { - data[c] = typeof result[c] !== "undefined" ? result[c] : null; + spliceRow: function (row, index, amount/*, elements...*/) { + var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : []; + + var rowData = instance.getDataAtRow(row); + var removed = rowData.slice(index, index + amount); + var after = rowData.slice(index + amount); + + Handsontable.helper.extendArray(elements, after); + var i = 0; + while (i < amount) { + elements.push(null); //add null in place of removed elements + i++; } - // Re-render table - instance.forceFullRender = true; //used when data was changed - selection.refreshBorders(); - // Return removed elements + instance.populateFromArray(row, index, [elements], null, null, 'spliceRow'); + return removed; }, @@ -419,7 +329,7 @@ Handsontable.Core = function (rootElement, userSettings) { get: function (row, prop) { datamap.getVars.row = row; datamap.getVars.prop = prop; - instance.runHooks('beforeGet', datamap.getVars); + instance.PluginHooks.run('beforeGet', datamap.getVars); if (typeof datamap.getVars.prop === 'string' && datamap.getVars.prop.indexOf('.') > -1) { var sliced = datamap.getVars.prop.split("."); var out = priv.settings.data[datamap.getVars.row]; @@ -470,7 +380,7 @@ Handsontable.Core = function (rootElement, userSettings) { datamap.setVars.row = row; datamap.setVars.prop = prop; datamap.setVars.value = value; - instance.runHooks('beforeSet', datamap.setVars, source || "datamapGet"); + instance.PluginHooks.run('beforeSet', datamap.setVars, source || "datamapGet"); if (typeof datamap.setVars.prop === 'string' && datamap.setVars.prop.indexOf('.') > -1) { var sliced = datamap.setVars.prop.split("."); var out = priv.settings.data[datamap.setVars.row]; @@ -619,7 +529,7 @@ Handsontable.Core = function (rootElement, userSettings) { changes.push([r, c, oldData[r] ? oldData[r][c] : null, newData[r][c]]); } } - instance.runHooks('afterChange', changes, source || action); + instance.PluginHooks.run('afterChange', changes, source || action); if (!keepEmptyRows) { grid.adjustRowsAndCols(); //makes sure that we did not add rows that will be removed in next refresh } @@ -731,40 +641,95 @@ Handsontable.Core = function (rootElement, userSettings) { * @param {Array} input 2d array * @param {Object} [end] End selection position (only for drag-down mode) * @param {String} [source="populateFromArray"] + * @param {String} [method="overwrite"] * @return {Object|undefined} ending td in pasted area (only if any cell was changed) */ - populateFromArray: function (start, input, end, source) { + populateFromArray: function (start, input, end, source, method) { var r, rlen, c, clen, setData = [], current = {}; rlen = input.length; if (rlen === 0) { return false; } - current.row = start.row; - current.col = start.col; - for (r = 0; r < rlen; r++) { - if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) { - break; - } - current.col = start.col; - clen = input[r] ? input[r].length : 0; - for (c = 0; c < clen; c++) { - if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) { - break; + + var repeatCol + , repeatRow + , cmax + , rmax; + + // insert data with specified pasteMode method + switch (method) { + case 'shift_down' : + repeatCol = end ? end.col - start.col + 1 : 0; + repeatRow = end ? end.row - start.row + 1 : 0; + input = Handsontable.helper.translateRowsToColumns(input); + for (c = 0, clen = input.length, cmax = Math.max(clen, repeatCol); c < cmax; c++) { + if (c < clen) { + for (r = 0, rlen = input[c].length; r < repeatRow - rlen; r++) { + input[c].push(input[c][r % rlen]); + } + input[c].unshift(start.col + c, start.row, 0); + instance.spliceCol.apply(instance, input[c]); + } + else { + input[c % clen][0] = start.col + c; + instance.spliceCol.apply(instance, input[c % clen]); + } } - if (instance.getCellMeta(current.row, current.col).isWritable) { - setData.push([current.row, current.col, input[r][c]]); + break; + + case 'shift_right' : + repeatCol = end ? end.col - start.col + 1 : 0; + repeatRow = end ? end.row - start.row + 1 : 0; + for (r = 0, rlen = input.length, rmax = Math.max(rlen, repeatRow); r < rmax; r++) { + if (r < rlen) { + for (c = 0, clen = input[r].length; c < repeatCol - clen; c++) { + input[r].push(input[r][c % clen]); + } + input[r].unshift(start.row + r, start.col, 0); + instance.spliceRow.apply(instance, input[r]); + } + else { + input[r % rlen][0] = start.row + r; + instance.spliceRow.apply(instance, input[r % rlen]); + } } - current.col++; - if (end && c === clen - 1) { - c = -1; + break; + + case 'overwrite' : + default: + // overwrite and other not specified options + current.row = start.row; + current.col = start.col; + for (r = 0; r < rlen; r++) { + if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) { + break; + } + current.col = start.col; + clen = input[r] ? input[r].length : 0; + for (c = 0; c < clen; c++) { + if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) { + break; + } + if (instance.getCellMeta(current.row, current.col).isWritable) { + setData.push([current.row, current.col, input[r][c]]); + } + current.col++; + if (end && c === clen - 1) { + c = -1; + } + } + current.row++; + if (end && r === rlen - 1) { + r = -1; + } } - } - current.row++; - if (end && r === rlen - 1) { - r = -1; - } + instance.setDataAtCell(setData, null, null, source || 'populateFromArray'); + break; } - instance.setDataAtCell(setData, null, null, source || 'populateFromArray'); + + instance.forceFullRender = true; //used when data was changed + grid.adjustRowsAndCols(); + selection.refreshBorders(null, true); }, /** @@ -829,8 +794,8 @@ Handsontable.Core = function (rootElement, userSettings) { */ finish: function () { var sel = instance.getSelected(); - instance.runHooks("afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]); - instance.runHooks("afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3])); + instance.PluginHooks.run("afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]); + instance.PluginHooks.run("afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3])); instance.selection.inProgress = false; }, @@ -879,8 +844,8 @@ Handsontable.Core = function (rootElement, userSettings) { } //trigger handlers - instance.runHooks("afterSelection", priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col()); - instance.runHooks("afterSelectionByProp", priv.selStart.row(), datamap.colToProp(priv.selStart.col()), priv.selEnd.row(), datamap.colToProp(priv.selEnd.col())); + instance.PluginHooks.run("afterSelection", priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col()); + instance.PluginHooks.run("afterSelectionByProp", priv.selStart.row(), datamap.colToProp(priv.selStart.col()), priv.selEnd.row(), datamap.colToProp(priv.selEnd.col())); if (scrollToCell !== false) { instance.view.scrollViewport(coords); @@ -1032,7 +997,7 @@ Handsontable.Core = function (rootElement, userSettings) { instance.view.wt.selections.area.clear(); editproxy.destroy(); selection.refreshBorders(); - instance.runHooks('afterDeselect'); + instance.PluginHooks.run('afterDeselect'); }, /** @@ -1192,7 +1157,7 @@ Handsontable.Core = function (rootElement, userSettings) { if (start) { _data = SheetClip.parse(datamap.getText(priv.selStart.coords(), priv.selEnd.coords())); - instance.runHooks('beforeAutofill', start, end, _data); + instance.PluginHooks.run('beforeAutofill', start, end, _data); grid.populateFromArray(start, _data, end, 'autofill'); @@ -1241,40 +1206,28 @@ Handsontable.Core = function (rootElement, userSettings) { } function onPaste(str) { - instance.addHookOnce('afterChange', function (changes, source) { + var input = str.replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, '') //remove newline from the start and the end of the input + , inputArray = SheetClip.parse(input) + , coords = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]) + , areaStart = coords.TL + , areaEnd = { + row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row), + col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col) + }; + + instance.PluginHooks.once('afterChange', function (changes, source) { if (changes.length) { - var last = changes[changes.length - 1]; - selection.setRangeEnd({row: last[0], col: instance.propToCol(last[1])}); + instance.selectCell(areaStart.row, areaStart.col, areaEnd.row, areaEnd.col); } }); - var input = str.replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, '') //remove newline from the start and the end of the input - , inputArray = SheetClip.parse(input) - , coords = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]); - - // fix grid size with specified pasteMode method in settings - switch (priv.settings.pasteMode) { - case 'shift_down' : - instance.alter('insert_row', coords.TL.row, inputArray.length); - break; - case 'shift_right' : - instance.alter('insert_col', coords.TL.col, inputArray[0].length); - break; - default: - // overwrite and other notspecified options - just do nothing - break; - } - - grid.populateFromArray(coords.TL, inputArray, { - row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row), - col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col) - }, 'paste'); + grid.populateFromArray(areaStart, inputArray, areaEnd, 'paste', priv.settings.pasteMode); } var $body = $(document.body); function onKeyDown(event) { - if (priv.settings.beforeOnKeyDown) { + if (priv.settings.beforeOnKeyDown) { // HOT in HOT Plugin priv.settings.beforeOnKeyDown.call(instance, event); } @@ -1309,81 +1262,77 @@ Handsontable.Core = function (rootElement, userSettings) { var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart; - switch (event.keyCode) { - case 38: /* arrow up */ - if (event.shiftKey) { - selection.transformEnd(-1, 0); - } - else { - selection.transformStart(-1, 0); - } - event.preventDefault(); - event.stopPropagation(); //required by HandsontableEditor - break; + instance.PluginHooks.run('beforeKeyDown', event); + if (!event.isImmediatePropagationStopped()) { - case 9: /* tab */ - var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves; - if (event.shiftKey) { - selection.transformStart(-tabMoves.row, -tabMoves.col); //move selection left - } - else { - selection.transformStart(tabMoves.row, tabMoves.col, true); //move selection right (add a new column if needed) - } - event.preventDefault(); - event.stopPropagation(); //required by HandsontableEditor - break; + switch (event.keyCode) { + case 38: /* arrow up */ + if (event.shiftKey) { + selection.transformEnd(-1, 0); + } + else { + selection.transformStart(-1, 0); + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; - case 39: /* arrow right */ - if (event.shiftKey) { - selection.transformEnd(0, 1); - } - else { - selection.transformStart(0, 1); - } - event.preventDefault(); - event.stopPropagation(); //required by HandsontableEditor - break; + case 9: /* tab */ + var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves; + if (event.shiftKey) { + selection.transformStart(-tabMoves.row, -tabMoves.col); //move selection left + } + else { + selection.transformStart(tabMoves.row, tabMoves.col, true); //move selection right (add a new column if needed) + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; - case 37: /* arrow left */ - if (event.shiftKey) { - selection.transformEnd(0, -1); - } - else { - selection.transformStart(0, -1); - } - event.preventDefault(); - event.stopPropagation(); //required by HandsontableEditor - break; + case 39: /* arrow right */ + if (event.shiftKey) { + selection.transformEnd(0, 1); + } + else { + selection.transformStart(0, 1); + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; + + case 37: /* arrow left */ + if (event.shiftKey) { + selection.transformEnd(0, -1); + } + else { + selection.transformStart(0, -1); + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; - case 8: /* backspace */ - case 46: /* delete */ - if (priv.settings.onDeleteDown) { - priv.settings.onDeleteDown(event); - } else { + case 8: /* backspace */ + case 46: /* delete */ selection.empty(event); event.preventDefault(); - } - break; + break; - case 40: /* arrow down */ - if (event.shiftKey) { - selection.transformEnd(1, 0); //expanding selection down with shift - } - else { - selection.transformStart(1, 0); //move selection down - } - event.preventDefault(); - event.stopPropagation(); //required by HandsontableEditor - break; + case 40: /* arrow down */ + if (event.shiftKey) { + selection.transformEnd(1, 0); //expanding selection down with shift + } + else { + selection.transformStart(1, 0); //move selection down + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; - case 113: /* F2 */ - event.preventDefault(); //prevent Opera from opening Go to Page dialog - break; + case 113: /* F2 */ + event.preventDefault(); //prevent Opera from opening Go to Page dialog + break; - case 13: /* return/enter */ - if (priv.settings.onEnterDown) { - priv.settings.onEnterDown(event); - } else { + case 13: /* return/enter */ var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves; if (event.shiftKey) { @@ -1394,49 +1343,50 @@ Handsontable.Core = function (rootElement, userSettings) { } event.preventDefault(); //don't add newline to field - } - break; + break; - case 36: /* home */ - if (event.ctrlKey || event.metaKey) { - rangeModifier({row: 0, col: priv.selStart.col()}); - } - else { - rangeModifier({row: priv.selStart.row(), col: 0}); - } - event.preventDefault(); //don't scroll the window - event.stopPropagation(); //required by HandsontableEditor - break; + case 36: /* home */ + if (event.ctrlKey || event.metaKey) { + rangeModifier({row: 0, col: priv.selStart.col()}); + } + else { + rangeModifier({row: priv.selStart.row(), col: 0}); + } + event.preventDefault(); //don't scroll the window + event.stopPropagation(); //required by HandsontableEditor + break; - case 35: /* end */ - if (event.ctrlKey || event.metaKey) { - rangeModifier({row: instance.countRows() - 1, col: priv.selStart.col()}); - } - else { - rangeModifier({row: priv.selStart.row(), col: instance.countCols() - 1}); - } - event.preventDefault(); //don't scroll the window - event.stopPropagation(); //required by HandsontableEditor - break; + case 35: /* end */ + if (event.ctrlKey || event.metaKey) { + rangeModifier({row: instance.countRows() - 1, col: priv.selStart.col()}); + } + else { + rangeModifier({row: priv.selStart.row(), col: instance.countCols() - 1}); + } + event.preventDefault(); //don't scroll the window + event.stopPropagation(); //required by HandsontableEditor + break; - case 33: /* pg up */ - selection.transformStart(-instance.countVisibleRows(), 0); - instance.view.wt.scrollVertical(-instance.countVisibleRows()); - instance.view.render(); - event.preventDefault(); //don't page up the window - event.stopPropagation(); //required by HandsontableEditor - break; + case 33: /* pg up */ + selection.transformStart(-instance.countVisibleRows(), 0); + instance.view.wt.scrollVertical(-instance.countVisibleRows()); + instance.view.render(); + event.preventDefault(); //don't page up the window + event.stopPropagation(); //required by HandsontableEditor + break; - case 34: /* pg down */ - selection.transformStart(instance.countVisibleRows(), 0); - instance.view.wt.scrollVertical(instance.countVisibleRows()); - instance.view.render(); - event.preventDefault(); //don't page down the window - event.stopPropagation(); //required by HandsontableEditor - break; + case 34: /* pg down */ + selection.transformStart(instance.countVisibleRows(), 0); + instance.view.wt.scrollVertical(instance.countVisibleRows()); + instance.view.render(); + event.preventDefault(); //don't page down the window + event.stopPropagation(); //required by HandsontableEditor + break; + + default: + break; + } - default: - break; } } } @@ -1473,7 +1423,7 @@ Handsontable.Core = function (rootElement, userSettings) { instance.copyPaste.copyable(datamap.getText({row: startRow, col: startCol}, {row: finalEndRow, col: finalEndCol})); if (endRow !== finalEndRow || endCol !== finalEndCol) { - instance.runHooks("afterCopyLimit", endRow - startRow + 1, endCol - startCol + 1, priv.settings.copyRowsLimit, priv.settings.copyColsLimit); + instance.PluginHooks.run("afterCopyLimit", endRow - startRow + 1, endCol - startCol + 1, priv.settings.copyRowsLimit, priv.settings.copyColsLimit); } }, @@ -1493,7 +1443,7 @@ Handsontable.Core = function (rootElement, userSettings) { }; this.init = function () { - instance.runHooks('beforeInit'); + instance.PluginHooks.run('beforeInit'); editproxy.init(); @@ -1506,10 +1456,10 @@ Handsontable.Core = function (rootElement, userSettings) { this.view.render(); if (typeof priv.firstRun === 'object') { - instance.runHooks('afterChange', priv.firstRun[0], priv.firstRun[1]); + instance.PluginHooks.run('afterChange', priv.firstRun[0], priv.firstRun[1]); priv.firstRun = false; } - instance.runHooks('afterInit'); + instance.PluginHooks.run('afterInit'); }; function validateChanges(changes, source) { @@ -1567,7 +1517,7 @@ Handsontable.Core = function (rootElement, userSettings) { } if (changes.length) { - var result = instance.runHooks("beforeChange", changes, source); + var result = instance.PluginHooks.execute("beforeChange", changes, source); if (typeof result === 'function') { $.when(result).then(function () { validated.resolve(); @@ -1628,7 +1578,7 @@ Handsontable.Core = function (rootElement, userSettings) { instance.forceFullRender = true; //used when data was changed grid.adjustRowsAndCols(); selection.refreshBorders(); - instance.runHooks('afterChange', changes, source || 'edit'); + instance.PluginHooks.run('afterChange', changes, source || 'edit'); } function setDataInputToArray(row, prop_or_col, value) { @@ -1741,13 +1691,14 @@ Handsontable.Core = function (rootElement, userSettings) { * @param {Number=} endRow End row (use when you want to cut input when certain row is reached) * @param {Number=} endCol End column (use when you want to cut input when certain column is reached) * @param {String=} [source="populateFromArray"] + * @param {String=} [method="overwrite"] * @return {Object|undefined} ending td in pasted area (only if any cell was changed) */ - this.populateFromArray = function (row, col, input, endRow, endCol, source) { - if(typeof input !== 'object') { + this.populateFromArray = function (row, col, input, endRow, endCol, source, method) { + if (typeof input !== 'object') { throw new Error("populateFromArray parameter `input` must be an array"); //API changed in 0.9-beta2, let's check if you use it correctly } - return grid.populateFromArray({row: row, col: col}, input, typeof endRow === 'number' ? {row: endRow, col: endCol} : null, source); + return grid.populateFromArray({row: row, col: col}, input, typeof endRow === 'number' ? {row: endRow, col: endCol} : null, source, method); }; /** @@ -1877,13 +1828,13 @@ Handsontable.Core = function (rootElement, userSettings) { datamap.createMap(); grid.adjustRowsAndCols(); - instance.runHooks('afterLoadData'); + instance.PluginHooks.run('afterLoadData'); if (priv.firstRun) { priv.firstRun = [null, 'loadData']; } else { - instance.runHooks('afterChange', null, 'loadData'); + instance.PluginHooks.run('afterChange', null, 'loadData'); instance.render(); } priv.isPopulated = true; @@ -1936,8 +1887,8 @@ Handsontable.Core = function (rootElement, userSettings) { continue; //loadData will be triggered later } else { - if (hooks[i] !== void 0 || eventMap[i] !== void 0) { - instance.addHook(i, settings[i]); + if (instance.PluginHooks.hooks[i] !== void 0 || instance.PluginHooks.legacy[i] !== void 0) { + instance.PluginHooks.add(i, settings[i]); } else { // Update settings @@ -1986,8 +1937,8 @@ Handsontable.Core = function (rootElement, userSettings) { proto = priv.columnSettings[i].prototype; // Use settings provided by user - if (settings.columns) { - column = settings.columns[i]; + if (GridSettings.prototype.columns) { + column = GridSettings.prototype.columns[i]; for (prop in column) { if (column.hasOwnProperty(prop)) { proto[prop] = column[prop]; @@ -2209,10 +2160,13 @@ Handsontable.Core = function (rootElement, userSettings) { cellProperties = new cellConstructor(); - instance.runHooks('beforeGetCellMeta', row, col, cellProperties); + instance.PluginHooks.run('beforeGetCellMeta', row, col, cellProperties); - if (typeof cellProperties.type === 'string' && Handsontable.cellTypes[cellProperties.type]) { + if (typeof cellProperties.type === 'string' && cellProperties.type !== 'text') { type = Handsontable.cellTypes[cellProperties.type]; + if (type === void 0) { + throw new Error('You declared cell type "' + cellProperties.type + '" as a string that is not mapped to a known object. Cell type must be an object or a string mapped to an object in Handsontable.cellTypes'); + } } else if (typeof cellProperties.type === 'object') { type = cellProperties.type; @@ -2220,14 +2174,14 @@ Handsontable.Core = function (rootElement, userSettings) { if (type) { for (i in type) { - if (type.hasOwnProperty(i)) { + if (type.hasOwnProperty(i) && cellProperties[i] === Handsontable.cellTypes.text[i]) { cellProperties[i] = type[i]; } } } cellProperties.isWritable = !cellProperties.readOnly; - instance.runHooks('afterGetCellMeta', row, col, cellProperties); + instance.PluginHooks.run('afterGetCellMeta', row, col, cellProperties); return cellProperties; }; @@ -2273,7 +2227,7 @@ Handsontable.Core = function (rootElement, userSettings) { return out; } else { - col = Handsontable.PluginModifiers.run(instance, 'col', col); + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].title) { return priv.settings.columns[col].title; @@ -2299,7 +2253,7 @@ Handsontable.Core = function (rootElement, userSettings) { * @return {Number} */ this.getColWidth = function (col) { - col = Handsontable.PluginModifiers.run(instance, 'col', col); + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); var response = {}; if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].width) { response.width = priv.settings.columns[col].width; @@ -2310,7 +2264,7 @@ Handsontable.Core = function (rootElement, userSettings) { else { response.width = 50; } - instance.runHooks('afterGetColWidth', col, response); + instance.PluginHooks.run('afterGetColWidth', col, response); return response.width; }; @@ -2369,7 +2323,7 @@ Handsontable.Core = function (rootElement, userSettings) { * @return {Number} */ this.countVisibleRows = function () { - return instance.view.wt.wtTable.countVisibleRows(); + return instance.view.wt.wtTable.rowStrategy.countVisible(); }; /** @@ -2377,7 +2331,7 @@ Handsontable.Core = function (rootElement, userSettings) { * @return {Number} */ this.countVisibleCols = function () { - return instance.view.wt.wtTable.countVisibleColumns(); + return instance.view.wt.wtTable.columnStrategy.countVisible(); }; /** @@ -2530,7 +2484,7 @@ Handsontable.Core = function (rootElement, userSettings) { instance.rootElement.off('.handsontable'); $(window).off('.' + instance.guid); $(document.documentElement).off('.' + instance.guid); - instance.runHooks('afterDestroy'); + instance.PluginHooks.run('afterDestroy'); }; /** @@ -2542,89 +2496,36 @@ Handsontable.Core = function (rootElement, userSettings) { return instance.rootElement.data("handsontable"); }; - /** - * Add PluginHook to this instance - * @public - */ - this.addHook = function (key, fn) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } - - if (typeof hooks[key] === "undefined") { - hooks[key] = []; - } - - if (fn instanceof Array) { - for (var i = 0, len = fn.length; i < len; i++) { - hooks[key].push(fn[i]); - } - } else { - hooks[key].push(fn); - } - }; - - /** - * Add 'once run' PluginHook to this instance - * @public - */ - this.addHookOnce = function (key, fn) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } + (function () { + // Create new instance of plugin hooks + instance.PluginHooks = new Handsontable.PluginHookClass(); - if (typeof hooks[key] === "undefined") { - hooks[key] = []; - } + // Upgrade methods to call of global PluginHooks instance + var _run = instance.PluginHooks.run + , _exe = instance.PluginHooks.execute; - var wrapper = function () { - this.removeHook(key, wrapper); - return fn.apply(this, arguments); + instance.PluginHooks.run = function (key, p1, p2, p3, p4, p5) { + _run.call(this, instance, key, p1, p2, p3, p4, p5); + Handsontable.PluginHooks.run(instance, key, p1, p2, p3, p4, p5); }; - hooks[key].push(wrapper); + instance.PluginHooks.execute = function (key, p1, p2, p3, p4, p5) { + p1 = _exe.call(this, instance, key, p1, p2, p3, p4, p5); + p1 = Handsontable.PluginHooks.execute(instance, key, p1, p2, p3, p4, p5); - }; + return p1; + }; - /** - * Remove PluginHook from this instance - * @public - * @return {Boolean} - */ - this.removeHook = function (key, fn) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } + // Map old API with new methods + instance.addHook = instance.PluginHooks.add; + instance.addHookOnce = instance.PluginHooks.once; - for (var i = 0, len = hooks[key].length; i < len; i++) { - if (hooks[key][i] == fn) { - hooks[key].splice(i, 1); - return true; - } - } - return false; - }; + instance.removeHook = instance.PluginHooks.remove; - /** - * Run all PluginHooks (global and public) - * @public - */ - this.runHooks = function (key, p1, p2, p3, p4, p5) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } + instance.runHooks = instance.PluginHooks.run; + instance.runHooksAndReturn = instance.PluginHooks.execute; - if (typeof hooks[key] !== 'undefined') { - for (var i = 0, len = hooks[key].length; i < len; i++) { - hooks[key][i].call(instance, p1, p2, p3, p4, p5); - } - } - Handsontable.PluginHooks.run(instance, key, p1, p2, p3, p4, p5); - }; + })(); this.timeouts = {}; @@ -2652,7 +2553,7 @@ Handsontable.Core = function (rootElement, userSettings) { /** * Handsontable version */ - this.version = '0.9.0-beta2'; //inserted by grunt from package.json + this.version = '0.9.4'; //inserted by grunt from package.json }; var DefaultSettings = function () { @@ -2803,8 +2704,11 @@ Handsontable.TableView = function (instance) { var next = event.target; if (next !== that.wt.wtTable.spreader) { //immediate click on "spreader" means click on the right side of vertical scrollbar - while (next !== null && next !== document.documentElement) { + while (next !== document.documentElement) { //X-HANDSONTABLE is the tag name in Web Components version of HOT. Removal of this breaks cell selection + if(next === null) { + return; //click on something that was a row but now is detached (possibly because your click triggered a rerender) + } if (next === instance.rootElement[0] || next.nodeName === 'X-HANDSONTABLE' || next.id === 'context-menu-layer' || $(next).is('.context-menu-list') || $(next).is('.typeahead li')) { return; //click inside container } @@ -2897,18 +2801,20 @@ Handsontable.TableView = function (instance) { data: instance.getDataAtCell, totalRows: instance.countRows, totalColumns: instance.countCols, + scrollbarModelV: this.settings.scrollbarModelV, + scrollbarModelH: this.settings.scrollbarModelH, offsetRow: 0, offsetColumn: 0, width: this.getWidth(), height: this.getHeight(), fixedColumnsLeft: this.settings.fixedColumnsLeft, fixedRowsTop: this.settings.fixedRowsTop, - rowHeaders: this.settings.rowHeaders ? function (index, TH) { + rowHeaders: this.settings.rowHeaders ? [function (index, TH) { that.appendRowHeader(index, TH); - } : null, - columnHeaders: this.settings.colHeaders ? function (index, TH) { + }] : [], + columnHeaders: this.settings.colHeaders ? [function (index, TH) { that.appendColHeader(index, TH); - } : null, + }] : [], columnWidth: instance.getColWidth, cellRenderer: function (row, column, TD) { that.applyCellTypeMethod('renderer', TD, row, column); @@ -2993,7 +2899,7 @@ Handsontable.TableView = function (instance) { } }; - instance.runHooks('beforeInitWalkontable', walkontableConfig); + instance.PluginHooks.run('beforeInitWalkontable', walkontableConfig); this.wt = new Walkontable(walkontableConfig); @@ -3043,7 +2949,7 @@ Handsontable.TableView.prototype.getHeight = function () { Handsontable.TableView.prototype.beforeRender = function (force) { if (force) { - this.instance.runHooks('beforeRender'); + this.instance.PluginHooks.run('beforeRender'); this.wt.update('width', this.getWidth()); this.wt.update('height', this.getHeight()); } @@ -3053,17 +2959,17 @@ Handsontable.TableView.prototype.render = function () { this.wt.draw(!this.instance.forceFullRender); this.instance.rootElement.triggerHandler('render.handsontable'); if (this.instance.forceFullRender) { - this.instance.runHooks('afterRender'); + this.instance.PluginHooks.run('afterRender'); } this.instance.forceFullRender = false; }; Handsontable.TableView.prototype.applyCellTypeMethod = function (methodName, td, row, col) { var prop = this.instance.colToProp(col) - , cellProperties = this.instance.getCellMeta(row, col); - if (cellProperties[methodName]) { - return cellProperties[methodName](this.instance, td, row, col, prop, this.instance.getDataAtRowProp(row, prop), cellProperties); - } + , cellProperties = this.instance.getCellMeta(row, col) + , method = Handsontable.helper.getCellMethod(methodName, cellProperties[methodName]); //methodName is 'renderer' or 'editor' + + return method(this.instance, td, row, col, prop, this.instance.getDataAtRowProp(row, prop), cellProperties); }; /** @@ -3093,7 +2999,12 @@ Handsontable.TableView.prototype.scrollViewport = function (coords) { * @param TH */ Handsontable.TableView.prototype.appendRowHeader = function (row, TH) { - this.wt.wtDom.avoidInnerHTML(TH, this.instance.getRowHeader(row)); + if (row > -1) { + this.wt.wtDom.avoidInnerHTML(TH, this.instance.getRowHeader(row)); + } + else { + this.wt.wtDom.empty(TH); + } }; /** @@ -3115,7 +3026,7 @@ Handsontable.TableView.prototype.appendColHeader = function (col, TH) { TH.removeChild(TH.firstChild); //empty TH node } TH.appendChild(DIV); - this.instance.runHooks('afterGetColHeader', col, TH); + this.instance.PluginHooks.run('afterGetColHeader', col, TH); }; /** * Returns true if keyCode represents a printable character @@ -3179,6 +3090,21 @@ Handsontable.helper.spreadsheetColumnLabel = function (index) { return columnLabel; }; +/** + * Checks if value of n is a numeric one + * http://jsperf.com/isnan-vs-isnumeric/4 + * @param n + * @returns {boolean} + */ +Handsontable.helper.isNumeric = function (n) { + var t = typeof n; + return t == 'number' ? !isNaN(n) && isFinite(n) : + t == 'string' ? !n.length ? false : + n.length == 1 ? /\d/.test(n) : + /^\s*[+-]?\s*(?:(?:\d+(?:\.\d+)?(?:e[+-]?\d+)?)|(?:0x[a-f\d]+))\s*$/i.test(n) : + t == 'object' ? !!n && typeof n.valueOf() == "number" && !(n instanceof Date) : false; +}; + /** * Checks if child is a descendant of given parent node * http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another @@ -3270,6 +3196,60 @@ Handsontable.helper.columnFactory = function (GridSettings, conflictList, defaul return ColumnSettings; }; + +Handsontable.helper.translateRowsToColumns = function (input) { + var i + , ilen + , j + , jlen + , output = [] + , olen = 0; + + for (i = 0, ilen = input.length; i < ilen; i++) { + for (j = 0, jlen = input[i].length; j < jlen; j++) { + if (j == olen) { + output.push([]); + olen++; + } + output[j].push(input[i][j]) + } + } + return output; +}; + +Handsontable.helper.to2dArray = function (arr) { + var i = 0 + , ilen = arr.length; + while (i < ilen) { + arr[i] = [arr[i]]; + i++; + } +}; + +Handsontable.helper.extendArray = function (arr, extension) { + var i = 0 + , ilen = extension.length; + while (i < ilen) { + arr.push(extension[i]); + i++; + } +}; + +/** + * Returns cell renderer or editor function directly or through lookup map + */ +Handsontable.helper.getCellMethod = function (methodName, methodFunction) { + if (typeof methodFunction === 'string') { + var result = Handsontable.cellLookup[methodName][methodFunction]; + if (result === void 0) { + throw new Error('You declared cell ' + methodName + ' "' + methodFunction + '" as a string that is not mapped to a known function. Cell ' + methodName + ' must be a function or a string mapped to a function in Handsontable.cellLookup.' + methodName + ' lookup object'); + } + return result; + } + else { + return methodFunction; + } +}; /** * Handsontable UndoRedo class */ @@ -3530,7 +3510,7 @@ Handsontable.CheckboxRenderer = function (instance, TD, row, col, prop, value, c * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) */ Handsontable.NumericRenderer = function (instance, TD, row, col, prop, value, cellProperties) { - if (typeof value === 'number') { + if (Handsontable.helper.isNumeric(value)) { if (typeof cellProperties.language !== 'undefined') { numeral.language(cellProperties.language) } @@ -3584,6 +3564,11 @@ HandsontableTextEditorClass.prototype.bindEvents = function () { this.$textareaParent.off('.editor').on('keydown.editor', function (event) { //if we are here then isCellEdited === true + that.instance.PluginHooks.run('beforeKeyDown', event); + if(event.isImmediatePropagationStopped()) { //event was cancelled in beforeKeyDown + return; + } + var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL) if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) { @@ -4390,111 +4375,170 @@ Handsontable.cellTypes = { date: Handsontable.DateCell, handsontable: Handsontable.HandsontableCell }; -Handsontable.PluginHooks = (function () { - var hooks = { - beforeInitWalkontable: [], - - beforeInit: [], - beforeRender: [], - beforeChange: [], - beforeGet: [], - beforeSet: [], - beforeGetCellMeta: [], - beforeAutofill : [], - - afterInit: [], - afterLoadData: [], - afterRender: [], - afterChange: [], - afterGetCellMeta: [], - afterGetColHeader: [], - afterGetColWidth: [], - afterDestroy: [], - afterRemoveRow: [], - afterCreateRow: [], - afterRemoveCol: [], - afterCreateCol: [], - afterColumnResize: [], - afterColumnMove: [], - afterDeselect: [], - afterSelection: [], - afterSelectionByProp: [], - afterSelectionEnd: [], - afterSelectionEndByProp: [], - afterCopyLimit: [] - }; - - var eventMap = { - onBeforeChange : "beforeChange", - onChange : "afterChange", - onCreateRow : "afterCreateRow", - onCreateCol : "afterCreateCol", - onSelection : "afterSelection", - onCopyLimit : "afterCopyLimit", - onSelectionEnd : "afterSelectionEnd", - onSelectionByProp: "afterSelectionByProp", - onSelectionEndByProp: "afterSelectionEndByProp" - }; - return { - add: function (key, fn) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } +//here setup the friendly aliases that are used by cellProperties.renderer and cellProperties.editor +Handsontable.cellLookup = { + renderer: { + autocomplete: Handsontable.AutocompleteRenderer, + checkbox: Handsontable.CheckboxRenderer, + text: Handsontable.TextRenderer, + numeric: Handsontable.NumericRenderer + }, + editor: { + autocomplete: Handsontable.AutocompleteEditor, + checkbox: Handsontable.CheckboxEditor, + text: Handsontable.TextEditor, + date: Handsontable.DateEditor, + handsontable: Handsontable.HandsontableEditor + } +}; +Handsontable.PluginHookClass = (function () { + + var legacy = { + onBeforeChange: "beforeChange", + onChange: "afterChange", + onCreateRow: "afterCreateRow", + onCreateCol: "afterCreateCol", + onSelection: "afterSelection", + onCopyLimit: "afterCopyLimit", + onSelectionEnd: "afterSelectionEnd", + onSelectionByProp: "afterSelectionByProp", + onSelectionEndByProp: "afterSelectionEndByProp" + }; - hooks[key].push(fn); - }, - remove: function (key, fn) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } + function PluginHookClass () { + + this.hooks = { + // Hooks + beforeInitWalkontable : [], + + beforeInit : [], + beforeRender : [], + beforeChange : [], + beforeGet : [], + beforeSet : [], + beforeGetCellMeta : [], + beforeAutofill : [], + beforeKeyDown : [], + + afterInit : [], + afterLoadData : [], + afterRender : [], + afterChange : [], + afterGetCellMeta : [], + afterGetColHeader : [], + afterGetColWidth : [], + afterDestroy : [], + afterRemoveRow : [], + afterCreateRow : [], + afterRemoveCol : [], + afterCreateCol : [], + afterColumnResize : [], + afterColumnMove : [], + afterDeselect : [], + afterSelection : [], + afterSelectionByProp : [], + afterSelectionEnd : [], + afterSelectionEndByProp : [], + afterCopyLimit : [], + + // Modifiers + modifyCol : [] + }; - for(var i = 0, len = hooks[key].length; i < len; i++) { - if (hooks[key][i] == fn) { - hooks[key].splice(i, 1); - return true; - } - } - return false; - }, - run: function (instance, key, p1, p2, p3, p4, p5) { - // provide support for old versions of HOT - if (key in eventMap) { - key = eventMap[key]; - } + this.legacy = legacy; - //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture - if (typeof hooks[key] !== 'undefined') { - for (var i = 0, len = hooks[key].length; i < len; i++) { - hooks[key][i].call(instance, p1, p2, p3, p4, p5); - } - } - } } -})(); - -Handsontable.PluginModifiers = { - modifiers: { - col: [] - }, - - push: function (key, fn) { - this.modifiers[key].push(fn); - }, - unshift: function (key, fn) { - this.modifiers[key].unshift(fn); - }, + PluginHookClass.prototype.add = function (key, fn) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } - run: function (instance, key, p1, p2, p3, p4, p5) { - for (var i = 0, ilen = this.modifiers[key].length; i < ilen; i++) { - p1 = this.modifiers[key][i].call(instance, p1, p2, p3, p4, p5); + if (typeof this.hooks[key] === "undefined") { + this.hooks[key] = []; } + + if (fn instanceof Array) { + for (var i = 0, len = fn.length; i < len; i++) { + this.hooks[key].push(fn[i]); + } + } else { + this.hooks[key].push(fn); + } + + return this; + }; + + PluginHookClass.prototype.once = function (key, fn) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + var instance = this + , _remove = this.remove + , wrapper = function () { + _remove.call(instance, key, wrapper); + + return fn.apply(instance, arguments); + }; + + return this.add(key, wrapper); + }; + + PluginHookClass.prototype.remove = function (key, fn) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + for (var i = 0, len = this.hooks[key].length; i < len; i++) { + if (this.hooks[key][i] == fn) { + this.hooks[key].splice(i, 1); + return true; + } + } + return false; + } + + PluginHookClass.prototype.run = function (instance, key, p1, p2, p3, p4, p5) { + + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture + if (typeof this.hooks[key] !== 'undefined') { + for (var i = 0, len = this.hooks[key].length; i < len; i++) { + this.hooks[key][i].call(instance, p1, p2, p3, p4, p5); + } + } + } + + PluginHookClass.prototype.execute = function (instance, key, p1, p2, p3, p4, p5) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture + if (typeof this.hooks[key] !== 'undefined') { + for (var i = 0, len = this.hooks[key].length; i < len; i++) { + p1 = this.hooks[key][i].call(instance, p1, p2, p3, p4, p5); + } + } + return p1; } -}; + + return PluginHookClass; + +})(); + +Handsontable.PluginHooks = new Handsontable.PluginHookClass(); function HandsontableAutoColumnSize() { var that = this , instance @@ -4612,8 +4656,8 @@ function HandsontableAutoColumnSize() { var str = 9999999999; tmp.noRendererTd.appendChild(document.createTextNode(str)); - - cellProperties.renderer(instance, tmp.rendererTd, 0, col, instance.colToProp(col), str, cellProperties); + var renderer = Handsontable.helper.getCellMethod('renderer', cellProperties.renderer); + renderer(instance, tmp.rendererTd, 0, col, instance.colToProp(col), str, cellProperties); width += tmp.$renderer.width() - tmp.$noRenderer.width(); //add renderer overhead to the calculated width } @@ -4956,7 +5000,7 @@ function HandsontableManualColumnMove() { instance.forceFullRender = true; instance.view.render(); //updates all ghostStyle.display = 'none'; - instance.runHooks('afterColumnMove', startCol, endCol); + instance.PluginHooks.run('afterColumnMove', startCol, endCol); } }); @@ -5019,7 +5063,7 @@ var htManualColumnMove = new HandsontableManualColumnMove(); Handsontable.PluginHooks.add('beforeInit', htManualColumnMove.beforeInit); Handsontable.PluginHooks.add('afterInit', htManualColumnMove.afterInit); Handsontable.PluginHooks.add('afterGetColHeader', htManualColumnMove.getColHeader); -Handsontable.PluginModifiers.push('col', htManualColumnMove.modifyCol); +Handsontable.PluginHooks.add('modifyCol', htManualColumnMove.modifyCol); function HandsontableManualColumnResize() { var pressed @@ -5065,7 +5109,7 @@ function HandsontableManualColumnResize() { instance.forceFullRender = true; instance.view.render(); //updates all lineStyle.display = 'none'; - instance.runHooks('afterColumnResize', currentCol, newSize); + instance.PluginHooks.run('afterColumnResize', currentCol, newSize); } }); @@ -5082,7 +5126,7 @@ function HandsontableManualColumnResize() { autoresizeTimeout = setTimeout(function () { if (dblclick >= 2) { setManualSize(currentCol, htAutoColumnSize.determineColumnWidth.call(instance, currentCol)); - instance.runHooks('afterColumnResize', currentCol, newSize); + instance.PluginHooks.run('afterColumnResize', currentCol, newSize); } dblclick = 0; autoresizeTimeout = null; @@ -5143,6 +5187,233 @@ Handsontable.PluginHooks.add('afterInit', htManualColumnResize.afterInit); Handsontable.PluginHooks.add('afterGetColHeader', htManualColumnResize.getColHeader); Handsontable.PluginHooks.add('afterGetColWidth', htManualColumnResize.getColWidth); +function HandsontableObserveChanges() { + // begin shim code + // fragments from https://github.com/Starcounter-Jack/JSON-Patch/blob/master/src/json-patch-duplex.js + // + // json-patch.js 0.3 + // (c) 2013 Joachim Wester + // MIT license + var observeOps = { + 'new': function (patches, path) { + var patch = { + op: "add", + path: path + "/" + this.name, + value: this.object[this.name] + }; + patches.push(patch); + }, + deleted: function (patches, path) { + var patch = { + op: "remove", + path: path + "/" + this.name + }; + patches.push(patch); + }, + updated: function (patches, path) { + var patch = { + op: "replace", + path: path + "/" + this.name, + value: this.object[this.name] + }; + patches.push(patch); + } + }; + function markPaths(observer, node) { + for(var key in node) { + var kid = node[key]; + if(kid instanceof Object) { + Object.unobserve(kid, observer); + kid.____Path = node.____Path + "/" + key; + markPaths(observer, kid); + } + } + } + function clearPaths(observer, node) { + delete node.____Path; + Object.observe(node, observer); + for(var key in node) { + var kid = node[key]; + if(kid instanceof Object) { + clearPaths(observer, kid); + } + } + } + var beforeDict = []; + var callbacks = []; + function observe(obj, callback) { + var patches = []; + var root = obj; + if(Object.observe) { + var observer = function (arr) { + if(!root.___Path) { + Object.unobserve(root, observer); + root.____Path = ""; + markPaths(observer, root); + arr.forEach(function (elem) { + if(elem.name != "____Path") { + observeOps[elem.type].call(elem, patches, elem.object.____Path); + } + }); + clearPaths(observer, root); + } + if(callback) { + callback.call(patches); + } + }; + } else { + observer = { + }; + var mirror; + for(var i = 0, ilen = beforeDict.length; i < ilen; i++) { + if(beforeDict[i].obj === obj) { + mirror = beforeDict[i]; + break; + } + } + if(!mirror) { + mirror = { + obj: obj + }; + beforeDict.push(mirror); + } + mirror.value = JSON.parse(JSON.stringify(obj)); + if(callback) { + callbacks.push(callback); + var next; + var intervals = [ + 100 + ]; + var currentInterval = 0; + var dirtyCheck = function () { + var temp = generate(observer); + if(temp.length > 0) { + observer.patches = []; + callback.call(null, temp); + } + }; + var fastCheck = function (e) { + clearTimeout(next); + next = setTimeout(function () { + dirtyCheck(); + currentInterval = 0; + next = setTimeout(slowCheck, intervals[currentInterval++]); + }, 0); + }; + var slowCheck = function () { + dirtyCheck(); + if(currentInterval == intervals.length) { + currentInterval = intervals.length - 1; + } + next = setTimeout(slowCheck, intervals[currentInterval++]); + }; + [ + "mousedown", + "mouseup", + "keydown" + ].forEach(function (str) { + window.addEventListener(str, fastCheck); + }); + next = setTimeout(slowCheck, intervals[currentInterval++]); + } + } + observer.patches = patches; + observer.object = obj; + return _observe(observer, obj, patches); + } + + /// Listen to changes on an object tree, accumulate patches + function _observe(observer, obj, patches) { + if(Object.observe) { + Object.observe(obj, observer); + } + for(var key in obj) { + if(obj.hasOwnProperty(key)) { + var v = obj[key]; + if(v && typeof (v) === "object") { + _observe(observer, v, patches); + } + } + } + return observer; + } + function generate(observer) { + if(Object.observe) { + Object.deliverChangeRecords(observer); + } else { + var mirror; + for(var i = 0, ilen = beforeDict.length; i < ilen; i++) { + if(beforeDict[i].obj === observer.object) { + mirror = beforeDict[i]; + break; + } + } + _generate(mirror.value, observer.object, observer.patches, ""); + } + return observer.patches; + } + + function _generate(mirror, obj, patches, path) { + var newKeys = Object.keys(obj); + var oldKeys = Object.keys(mirror); + var changed = false; + var deleted = false; + var added = false; + for(var t = 0; t < oldKeys.length; t++) { + var key = oldKeys[t]; + var oldVal = mirror[key]; + if(obj.hasOwnProperty(key)) { + var newVal = obj[key]; + if(oldVal instanceof Object) { + _generate(oldVal, newVal, patches, path + "/" + key); + } else { + if(oldVal != newVal) { + changed = true; + patches.push({ + op: "replace", + path: path + "/" + key, + value: newVal + }); + mirror[key] = newVal; + } + } + } else { + patches.push({ + op: "remove", + path: path + "/" + key + }); + deleted = true; + } + } + if(!deleted && newKeys.length == oldKeys.length) { + return; + } + for(var t = 0; t < newKeys.length; t++) { + var key = newKeys[t]; + if(!mirror.hasOwnProperty(key)) { + patches.push({ + op: "add", + path: path + "/" + key, + value: obj[key] + }); + } + } + } + //end shim code + + + this.afterLoadData = function () { + if (!this.observer && this.getSettings().observeChanges) { + var that = this; + this.observer = observe(this.getData(), function () { + that.render(); + }); + } + }; +} +var htObserveChanges = new HandsontableObserveChanges(); + +Handsontable.PluginHooks.add('afterLoadData', htObserveChanges.afterLoadData); /* * jQuery.fn.autoResize 1.1+ * -- @@ -5586,7 +5857,7 @@ CopyPaste.prototype.triggerCut = function (event) { if (that.cutCallback) { setTimeout(function () { that.cutCallback(event); - }, 0); + }, 50); } }; @@ -5595,7 +5866,7 @@ CopyPaste.prototype.triggerPaste = function (event, str) { if (that.pasteCallback) { setTimeout(function () { that.pasteCallback((str || that.elTextarea.value).replace(/\n$/, ''), event); //remove trailing newline - }, 0); + }, 50); } }; @@ -5669,7 +5940,7 @@ function WalkontableBorder(instance, settings) { * @param {Array} corners */ WalkontableBorder.prototype.appear = function (corners) { - var isMultiple, $from, $to, fromOffset, toOffset, containerOffset, top, minTop, left, minLeft, height, width; + var isMultiple, fromTD, toTD, fromOffset, toOffset, containerOffset, top, minTop, left, minLeft, height, width; if (this.disabled) { return; } @@ -5695,7 +5966,7 @@ WalkontableBorder.prototype.appear = function (corners) { hideBottom = true; } - ilen = instance.wtTable.countVisibleRows(); + ilen = instance.wtTable.rowStrategy.countVisible(); for (i = 0; i < ilen; i++) { s = instance.wtTable.rowFilter.visibleToSource(i); @@ -5726,7 +5997,7 @@ WalkontableBorder.prototype.appear = function (corners) { hideRight = true; } - ilen = instance.wtTable.countVisibleColumns(); + ilen = instance.wtTable.columnStrategy.countVisible(); for (i = 0; i < ilen; i++) { s = instance.wtTable.columnFilter.visibleToSource(i); @@ -5747,25 +6018,26 @@ WalkontableBorder.prototype.appear = function (corners) { if (fromRow !== void 0 && fromColumn !== void 0) { isMultiple = (fromRow !== toRow || fromColumn !== toColumn); - $from = $(instance.wtTable.getCell([fromRow, fromColumn])); - $to = isMultiple ? $(instance.wtTable.getCell([toRow, toColumn])) : $from; - fromOffset = this.wtDom.offset($from[0]); - toOffset = isMultiple ? this.wtDom.offset($to[0]) : fromOffset; + fromTD = instance.wtTable.getCell([fromRow, fromColumn]); + toTD = isMultiple ? instance.wtTable.getCell([toRow, toColumn]) : fromTD; + fromOffset = this.wtDom.offset(fromTD); + toOffset = isMultiple ? this.wtDom.offset(toTD) : fromOffset; containerOffset = this.wtDom.offset(instance.wtTable.TABLE); minTop = fromOffset.top; - height = toOffset.top + $to.outerHeight() - minTop; + height = toOffset.top + this.wtDom.outerHeight(toTD) - minTop; minLeft = fromOffset.left; - width = toOffset.left + $to.outerWidth() - minLeft; + width = toOffset.left + this.wtDom.outerWidth(toTD) - minLeft; top = minTop - containerOffset.top - 1; left = minLeft - containerOffset.left - 1; - if (parseInt($from.css('border-top-width'), 10) > 0) { + var style = this.wtDom.getComputedStyle(fromTD); + if (parseInt(style['borderTopWidth'], 10) > 0) { top += 1; height -= 1; } - if (parseInt($from.css('border-left-width'), 10) > 0) { + if (parseInt(style['borderLeftWidth'], 10) > 0) { left += 1; width -= 1; } @@ -5891,6 +6163,28 @@ WalkontableCellFilter.prototype.visibleToSource = function (n) { WalkontableCellFilter.prototype.sourceToVisible = function (n) { return this.source(this.unOffsetted(this.unFixed(n))); }; +/** + * WalkontableCellStrategy + * @constructor + */ +function WalkontableCellStrategy() { +} + +WalkontableCellStrategy.prototype.getSize = function (index) { + return this.cellSizes[index]; +}; + +WalkontableCellStrategy.prototype.getContainerSize = function (proposedSize) { + return typeof this.containerSizeFn === 'function' ? this.containerSizeFn(proposedSize) : this.containerSizeFn; +}; + +WalkontableCellStrategy.prototype.countVisible = function () { + return this.cellCount; +}; + +WalkontableCellStrategy.prototype.isLastIncomplete = function () { + return this.remainingSize > 0; +}; /** * WalkontableClassNameList * @constructor @@ -5926,7 +6220,7 @@ WalkontableColumnFilter.prototype.readSettings = function (instance) { this.offset = instance.wtSettings.settings.offsetColumn; this.total = instance.getSetting('totalColumns'); this.fixedCount = instance.getSetting('fixedColumnsLeft'); - this.countTH = instance.hasSetting('rowHeaders') ? 1 : 0; + this.countTH = instance.getSetting('rowHeaders').length; }; WalkontableColumnFilter.prototype.offsettedTH = function (n) { @@ -5958,8 +6252,10 @@ function WalkontableColumnStrategy(containerSizeFn, sizeAtIndex, strategy) { this.containerSizeFn = containerSizeFn; this.cellSizesSum = 0; this.cellSizes = []; + this.cellStretch = []; this.cellCount = 0; this.remainingSize = 0; + this.strategy = strategy; //step 1 - determine cells that fit containerSize and cache their widths while (true) { @@ -5978,44 +6274,46 @@ function WalkontableColumnStrategy(containerSizeFn, sizeAtIndex, strategy) { } var containerSize = this.getContainerSize(this.cellSizesSum); - this.remainingSize = this.cellSizesSum - containerSize; //negative value means the last cell is fully visible and there is some space left for stretching //positive value means the last cell is not fully visible +} + +WalkontableColumnStrategy.prototype = new WalkontableCellStrategy(); + +WalkontableColumnStrategy.prototype.getSize = function (index) { + return this.cellSizes[index] + (this.cellStretch[index] || 0); +}; +WalkontableColumnStrategy.prototype.stretch = function () { //step 2 - apply stretching strategy - if (strategy === 'all') { + var containerSize = this.getContainerSize(this.cellSizesSum) + , i = 0; + this.remainingSize = this.cellSizesSum - containerSize; + + this.cellStretch.length = 0; //clear previous stretch + + if (this.strategy === 'all') { if (this.remainingSize < 0) { var ratio = containerSize / this.cellSizesSum; var newSize; - for (i = 0; i < this.cellCount - 1; i++) { //"i < ilen - 1" is needed because last cellSize is adjusted after the loop + while (i < this.cellCount - 1) { //"i < this.cellCount - 1" is needed because last cellSize is adjusted after the loop newSize = Math.floor(ratio * this.cellSizes[i]); this.remainingSize += newSize - this.cellSizes[i]; - this.cellSizes[i] = newSize; + this.cellStretch[i] = newSize - this.cellSizes[i]; + i++; } - this.cellSizes[this.cellCount - 1] -= this.remainingSize; + this.cellStretch[this.cellCount - 1] = -this.remainingSize; this.remainingSize = 0; } } - else if (strategy === 'last') { + else if (this.strategy === 'last') { if (this.remainingSize < 0) { - this.cellSizes[this.cellCount - 1] -= this.remainingSize; + this.cellStretch[this.cellCount - 1] = -this.remainingSize; this.remainingSize = 0; } } -} - -WalkontableColumnStrategy.prototype.getContainerSize = function (proposedWidth) { - var containerSize = typeof this.containerSizeFn === 'function' ? this.containerSizeFn(proposedWidth) : this.containerSizeFn; - if (containerSize === void 0 || containerSize === null || containerSize < 1) { - containerSize = Infinity; - } - return containerSize; -}; - -WalkontableColumnStrategy.prototype.getSize = function (index) { - return this.cellSizes[index]; }; function Walkontable(settings) { var that = this, @@ -6026,6 +6324,8 @@ function Walkontable(settings) { this.wtDom = new WalkontableDom(); this.wtTable = new WalkontableTable(this); this.wtScroll = new WalkontableScroll(this); + this.wtScrollbars = new WalkontableScrollbars(this); + this.wtViewport = new WalkontableViewport(this); this.wtWheel = new WalkontableWheel(this); this.wtEvent = new WalkontableEvent(this); @@ -6034,10 +6334,10 @@ function Walkontable(settings) { for (var c = 0, clen = this.wtTable.THEAD.childNodes[0].childNodes.length; c < clen; c++) { originalHeaders.push(this.wtTable.THEAD.childNodes[0].childNodes[c].innerHTML); } - if (!this.hasSetting('columnHeaders')) { - this.update('columnHeaders', function (column, TH) { + if (!this.getSetting('columnHeaders').length) { + this.update('columnHeaders', [function (column, TH) { that.wtDom.avoidInnerHTML(TH, originalHeaders[column]); - }); + }]); } } @@ -6111,7 +6411,7 @@ Walkontable.prototype.hasSetting = function (key) { }; Walkontable.prototype.destroy = function () { - this.wtScroll.destroy(); + this.wtScrollbars.destroy(); clearTimeout(this.wheelTimeout); clearTimeout(this.dblClickTimeout); }; @@ -6294,7 +6594,8 @@ WalkontableDom.prototype.isVisible = function (elem) { return false; //IE7-8 throws "Unspecified error" when offsetParent is not found - we catch it here } - if (elem.offsetWidth > 0 || (elem.parentNode && elem.parentNode.offsetWidth > 0)) { +// if (elem.offsetWidth > 0 || (elem.parentNode && elem.parentNode.offsetWidth > 0)) { //IE10 was mistaken here + if (elem.offsetWidth > 0) { return true; } @@ -6304,8 +6605,13 @@ WalkontableDom.prototype.isVisible = function (elem) { if (next === null) { //parent detached from DOM return false; } - else if (next.nodeType === 11) { //IE7 reports this after detaching element from DOM - return false; + else if (next.nodeType === 11) { + if (next.nodeName === '#document-fragment') { //Shadow DOM + return true; + } + else { //IE7 reports nodeType === 11 after detaching element from DOM + return false; + } } else if (next.style.display === 'none') { return false; @@ -6323,23 +6629,47 @@ WalkontableDom.prototype.isVisible = function (elem) { }; /** - * seems getBounding is usually faster: http://jsperf.com/offset-vs-getboundingclientrect/4 - * but maybe offset + cache would work? - * edit: after more tests turns out offsetLeft/Top is faster + * Returns elements top and left offset relative to the document. In our usage case compatible with jQuery but 2x faster + * @param {HTMLElement} elem + * @return {Object} */ - WalkontableDom.prototype.offset = function (elem) { var offsetLeft = elem.offsetLeft - , offsetTop = elem.offsetTop; + , offsetTop = elem.offsetTop + , lastElem = elem; + while (elem = elem.offsetParent) { + if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0 + break; + } offsetLeft += elem.offsetLeft; offsetTop += elem.offsetTop; + lastElem = elem; + } + + if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6 + //if(lastElem !== document.body) { //faster but does gives false positive in Firefox + offsetLeft += window.pageXOffset || document.documentElement.scrollLeft; + offsetTop += window.pageYOffset || document.documentElement.scrollTop; } + return { left: offsetLeft, top: offsetTop }; }; + +WalkontableDom.prototype.getComputedStyle = function (elem) { + return elem.currentStyle || document.defaultView.getComputedStyle(elem); +}; + +WalkontableDom.prototype.outerWidth = function (elem) { + return elem.offsetWidth; +}; + +WalkontableDom.prototype.outerHeight = function (elem) { + return elem.offsetHeight; +}; function WalkontableEvent(instance) { var that = this; @@ -6528,25 +6858,46 @@ WalkontableRowFilter.prototype.readSettings = function (instance) { this.total = instance.getSetting('totalRows'); this.fixedCount = instance.getSetting('fixedRowsTop'); }; -function WalkontableScroll(instance) { - this.instance = instance; - this.wtScrollbarV = new WalkontableVerticalScrollbar(instance); - this.wtScrollbarH = new WalkontableHorizontalScrollbar(instance); +/** + * WalkontableRowStrategy + * @param containerSizeFn + * @param sizeAtIndex + * @constructor + */ +function WalkontableRowStrategy(containerSizeFn, sizeAtIndex) { + this.containerSizeFn = containerSizeFn; + this.sizeAtIndex = sizeAtIndex; + this.cellSizesSum = 0; + this.cellSizes = []; + this.cellCount = 0; + this.remainingSize = -Infinity; } -WalkontableScroll.prototype.destroy = function () { - clearInterval(this.wtScrollbarV.dragdealer.interval); - clearInterval(this.wtScrollbarH.dragdealer.interval); +WalkontableRowStrategy.prototype = new WalkontableCellStrategy(); + +WalkontableRowStrategy.prototype.add = function (i, TD) { + if (this.remainingSize < 0) { + var size = this.sizeAtIndex(i, TD); + if (size === void 0) { + return; //total rows exceeded + } + var containerSize = this.getContainerSize(this.cellSizesSum + size); + this.cellSizes.push(size); + this.cellSizesSum += size; + this.cellCount++; + this.remainingSize = this.cellSizesSum - containerSize; + } }; -WalkontableScroll.prototype.refreshScrollbars = function () { - this.wtScrollbarH.readSettings(); - this.wtScrollbarV.readSettings(); - this.wtScrollbarH.prepare(); - this.wtScrollbarV.prepare(); - this.wtScrollbarH.refresh(); - this.wtScrollbarV.refresh(); +WalkontableRowStrategy.prototype.remove = function () { + var size = this.cellSizes.pop(); + this.cellSizesSum -= size; + this.cellCount--; + this.remainingSize += size; }; +function WalkontableScroll(instance) { + this.instance = instance; +} WalkontableScroll.prototype.scrollVertical = function (delta) { if (!this.instance.drawn) { @@ -6558,25 +6909,18 @@ WalkontableScroll.prototype.scrollVertical = function (delta) { , offset = instance.getSetting('offsetRow') , fixedCount = instance.getSetting('fixedRowsTop') , total = instance.getSetting('totalRows') - , maxSize = (instance.getSetting('height') || Infinity); //Infinity is needed, otherwise you could scroll a table that did not have height specified + , maxSize = instance.wtViewport.getViewportHeight(); if (total > 0) { - if (maxSize !== Infinity) { - var cellOffset = instance.wtDom.offset(instance.wtTable.TBODY) - , tableOffset = instance.wtTable.tableOffset - , columnHeaderHeight = cellOffset.top - tableOffset.top; //in future may be merged with `containerHeightFn` in WalkontableTable.prototype.refreshStretching - - maxSize -= columnHeaderHeight; - maxSize -= instance.getSetting('scrollbarHeight'); - } - - newOffset = this.scrollLogic(delta, offset, total, fixedCount, maxSize, function (row) { - if (row - offset < fixedCount) { + newOffset = this.scrollLogicVertical(delta, offset, total, fixedCount, maxSize, function (row) { + if (row - offset < fixedCount && row - offset >= 0) { return instance.getSetting('rowHeight', row - offset); } else { return instance.getSetting('rowHeight', row); } + }, function (isReverse) { + instance.wtTable.verticalRenderReverse = isReverse; }); } else { @@ -6584,7 +6928,7 @@ WalkontableScroll.prototype.scrollVertical = function (delta) { } if (newOffset !== offset) { - instance.update('offsetRow', newOffset); + this.instance.wtScrollbars.vertical.scrollTo(newOffset); } return instance; }; @@ -6599,11 +6943,11 @@ WalkontableScroll.prototype.scrollHorizontal = function (delta) { , offset = instance.getSetting('offsetColumn') , fixedCount = instance.getSetting('fixedColumnsLeft') , total = instance.getSetting('totalColumns') - , maxSize = instance.getSetting('width') - instance.getSetting('rowHeaderWidth'); + , maxSize = instance.wtViewport.getViewportWidth(); if (total > 0) { - newOffset = this.scrollLogic(delta, offset, total, fixedCount, maxSize, function (col) { - if (col - offset < fixedCount) { + newOffset = this.scrollLogicHorizontal(delta, offset, total, fixedCount, maxSize, function (col) { + if (col - offset < fixedCount && col - offset >= 0) { return instance.getSetting('columnWidth', col - offset); } else { @@ -6616,12 +6960,26 @@ WalkontableScroll.prototype.scrollHorizontal = function (delta) { } if (newOffset !== offset) { - instance.update('offsetColumn', newOffset); + this.instance.wtScrollbars.horizontal.scrollTo(newOffset); } return instance; }; -WalkontableScroll.prototype.scrollLogic = function (delta, offset, total, fixedCount, maxSize, cellSizeFn) { +WalkontableScroll.prototype.scrollLogicVertical = function (delta, offset, total, fixedCount, maxSize, cellSizeFn, setReverseRenderFn) { + var newOffset = offset + delta; + + if (newOffset >= total - fixedCount) { + newOffset = total - fixedCount - 1; + setReverseRenderFn(true); + } + else if (newOffset < 0) { + newOffset = 0; + } + + return newOffset; +}; + +WalkontableScroll.prototype.scrollLogicHorizontal = function (delta, offset, total, fixedCount, maxSize, cellSizeFn) { var newOffset = offset + delta , sum = 0 , col; @@ -6678,10 +7036,14 @@ WalkontableScroll.prototype.scrollViewport = function (coords) { } if (coords[0] > lastVisibleRow) { - this.scrollVertical(coords[0] - lastVisibleRow + 1); +// this.scrollVertical(coords[0] - lastVisibleRow + 1); + this.scrollVertical(coords[0] - fixedRowsTop - offsetRow); + this.instance.wtTable.verticalRenderReverse = true; } - else if (coords[0] === lastVisibleRow && this.instance.wtTable.isLastRowIncomplete()) { - this.scrollVertical(coords[0] - lastVisibleRow + 1); + else if (coords[0] === lastVisibleRow && this.instance.wtTable.rowStrategy.isLastIncomplete()) { +// this.scrollVertical(coords[0] - lastVisibleRow + 1); + this.scrollVertical(coords[0] - fixedRowsTop - offsetRow); + this.instance.wtTable.verticalRenderReverse = true; } else if (coords[0] - fixedRowsTop < offsetRow) { this.scrollVertical(coords[0] - fixedRowsTop - offsetRow); @@ -6693,7 +7055,7 @@ WalkontableScroll.prototype.scrollViewport = function (coords) { if (coords[1] > lastVisibleColumn) { this.scrollHorizontal(coords[1] - lastVisibleColumn + 1); } - else if (coords[1] === lastVisibleColumn && this.instance.wtTable.isLastColumnIncomplete()) { + else if (coords[1] === lastVisibleColumn && this.instance.wtTable.columnStrategy.isLastIncomplete()) { this.scrollHorizontal(coords[1] - lastVisibleColumn + 1); } else if (coords[1] - fixedColumnsLeft < offsetColumn) { @@ -6705,6 +7067,7 @@ WalkontableScroll.prototype.scrollViewport = function (coords) { return this.instance; }; + function WalkontableScrollbar() { } @@ -6713,8 +7076,6 @@ WalkontableScrollbar.prototype.init = function () { //reference to instance this.$table = $(this.instance.wtTable.TABLE); - this.$thead = this.$table.find('thead'); - this.$tbody = this.$table.find('tbody'); //create elements this.slider = document.createElement('DIV'); @@ -6730,7 +7091,8 @@ WalkontableScrollbar.prototype.init = function () { this.handle.className = 'handle'; this.slider.appendChild(this.handle); - this.instance.wtTable.parent.appendChild(this.slider); + this.container = this.instance.wtTable.parent; + this.container.appendChild(this.slider); var firstRun = true; this.dragTimeout = null; @@ -6837,55 +7199,38 @@ WalkontableScrollbar.prototype.refresh = function () { , handleSize , handlePosition , visibleCount = this.visibleCount - , tableOuterWidth = this.$table.outerWidth() || this.$tbody.outerWidth() || this.$thead.outerWidth() //IE8 reports 0 as offsetWidth - , tableOuterHeight = this.$table.outerHeight() - , tableWidth = this.instance.hasSetting('width') ? this.instance.getSetting('width') : tableOuterWidth - , tableHeight = this.instance.hasSetting('height') ? this.instance.getSetting('height') : tableOuterHeight; - - if (!tableWidth) { - //throw new Error("I could not compute table width. Is the
    element attached to the DOM?"); - return; - } - if (!tableHeight) { - //throw new Error("I could not compute table height. Is the
    element attached to the DOM?"); - return; - } + , tableWidth = this.instance.wtViewport.getWorkspaceWidth() + , tableHeight = this.instance.wtViewport.getWorkspaceHeight(); - if (this.instance.hasSetting('width') && this.instance.wtScroll.wtScrollbarV.visible) { - tableWidth -= this.instance.getSetting('scrollbarWidth'); - } - if (tableWidth > tableOuterWidth + this.instance.getSetting('scrollbarWidth')) { - tableWidth = tableOuterWidth; + if (tableWidth === Infinity) { + tableWidth = this.instance.wtViewport.getWorkspaceActualWidth(); } - if (this.instance.hasSetting('height') && this.instance.wtScroll.wtScrollbarH.visible) { - tableHeight -= this.instance.getSetting('scrollbarHeight'); - } - if (tableHeight > tableOuterHeight + this.instance.getSetting('scrollbarHeight')) { - tableHeight = tableOuterHeight; + if (tableHeight === Infinity) { + tableHeight = this.instance.wtViewport.getWorkspaceActualHeight(); } if (this.type === 'vertical') { - if (this.instance.wtTable.isLastRowIncomplete()) { + if (this.instance.wtTable.rowStrategy.isLastIncomplete()) { visibleCount--; } sliderSize = tableHeight - 2; //2 is sliders border-width - this.sliderStyle.top = this.$table.position().top + 'px'; + this.sliderStyle.top = this.instance.wtDom.offset(this.$table[0]).top - this.instance.wtDom.offset(this.container).top + 'px'; this.sliderStyle.left = tableWidth - 1 + 'px'; //1 is sliders border-width - this.sliderStyle.height = sliderSize + 'px'; + this.sliderStyle.height = Math.max(sliderSize, 0) + 'px'; } else { //horizontal - if (this.instance.wtTable.isLastColumnIncomplete()) { + if (this.instance.wtTable.columnStrategy.isLastIncomplete()) { visibleCount--; } sliderSize = tableWidth - 2; //2 is sliders border-width - this.sliderStyle.left = this.$table.position().left + 'px'; + this.sliderStyle.left = this.instance.wtDom.offset(this.$table[0]).left - this.instance.wtDom.offset(this.container).left + 'px'; this.sliderStyle.top = tableHeight - 1 + 'px'; //1 is sliders border-width - this.sliderStyle.width = sliderSize + 'px'; + this.sliderStyle.width = Math.max(sliderSize, 0) + 'px'; } ratio = this.getHandleSizeRatio(visibleCount, this.total); @@ -6912,6 +7257,10 @@ WalkontableScrollbar.prototype.refresh = function () { this.sliderStyle.display = 'block'; }; +WalkontableScrollbar.prototype.destroy = function () { + clearInterval(this.dragdealer.interval); +}; + /// var WalkontableVerticalScrollbar = function (instance) { @@ -6922,11 +7271,18 @@ var WalkontableVerticalScrollbar = function (instance) { WalkontableVerticalScrollbar.prototype = new WalkontableScrollbar(); +WalkontableVerticalScrollbar.prototype.scrollTo = function (cell) { + this.instance.update('offsetRow', cell); +}; + WalkontableVerticalScrollbar.prototype.readSettings = function () { this.scrollMode = this.instance.getSetting('scrollV'); this.offset = this.instance.getSetting('offsetRow'); this.total = this.instance.getSetting('totalRows'); - this.visibleCount = this.instance.wtTable.countVisibleRows(); + this.visibleCount = this.instance.wtTable.rowStrategy.countVisible(); + if(this.visibleCount > 1 && this.instance.wtTable.rowStrategy.isLastIncomplete()) { + this.visibleCount--; + } this.handlePosition = parseInt(this.handleStyle.top, 10); this.sliderSize = parseInt(this.sliderStyle.height, 10); this.fixedCount = this.instance.getSetting('fixedRowsTop'); @@ -6942,59 +7298,291 @@ var WalkontableHorizontalScrollbar = function (instance) { WalkontableHorizontalScrollbar.prototype = new WalkontableScrollbar(); +WalkontableHorizontalScrollbar.prototype.scrollTo = function (cell) { + this.instance.update('offsetColumn', cell); +}; + WalkontableHorizontalScrollbar.prototype.readSettings = function () { this.scrollMode = this.instance.getSetting('scrollH'); this.offset = this.instance.getSetting('offsetColumn'); this.total = this.instance.getSetting('totalColumns'); - this.visibleCount = this.instance.wtTable.countVisibleColumns(); + this.visibleCount = this.instance.wtTable.columnStrategy.countVisible(); + if(this.visibleCount > 1 && this.instance.wtTable.columnStrategy.isLastIncomplete()) { + this.visibleCount--; + } this.handlePosition = parseInt(this.handleStyle.left, 10); this.sliderSize = parseInt(this.sliderStyle.width, 10); this.fixedCount = this.instance.getSetting('fixedColumnsLeft'); }; -function WalkontableSelection(instance, settings) { - this.instance = instance; - this.settings = settings; - this.selected = []; - if (settings.border) { - this.border = new WalkontableBorder(instance, settings); - } +function WalkontableScrollbarNative() { + this.lastWindowScrollPosition = NaN; } -WalkontableSelection.prototype.add = function (coords) { - this.selected.push(coords); -}; +WalkontableScrollbarNative.prototype.init = function () { + this.fixedContainer = this.instance.wtTable.TABLE.parentNode.parentNode.parentNode; + this.fixed = this.instance.wtTable.TABLE.parentNode.parentNode; + this.TABLE = this.instance.wtTable.TABLE; + this.$scrollHandler = $(window); //in future remove jQuery from here -WalkontableSelection.prototype.clear = function () { - this.selected.length = 0; //http://jsperf.com/clear-arrayxxx + var that = this; + this.$scrollHandler.on('scroll.walkontable', function () { + if (!that.instance.wtTable.parent.parentNode) { + //Walkontable was detached from DOM, but this handler was not removed + that.destroy(); + return; + } + + that.onScroll(); + }); + + this.readSettings(); }; -/** - * Returns the top left (TL) and bottom right (BR) selection coordinates - * @returns {Object} - */ -WalkontableSelection.prototype.getCorners = function () { - var minRow - , minColumn - , maxRow - , maxColumn - , i - , ilen = this.selected.length; +WalkontableScrollbarNative.prototype.onScroll = function () { + this.readSettings(); + if (this.windowScrollPosition === this.lastWindowScrollPosition) { + return; + } + this.lastWindowScrollPosition = this.windowScrollPosition; - if (ilen > 0) { - minRow = maxRow = this.selected[0][0]; - minColumn = maxColumn = this.selected[0][1]; + var scrollDelta; + var newOffset = 0; - if (ilen > 1) { - for (i = 1; i < ilen; i++) { - if (this.selected[i][0] < minRow) { - minRow = this.selected[i][0]; - } - else if (this.selected[i][0] > maxRow) { - maxRow = this.selected[i][0]; - } + if (this.windowScrollPosition > this.tableParentOffset) { + scrollDelta = this.windowScrollPosition - this.tableParentOffset; + newOffset = Math.ceil(scrollDelta / 20, 10); + newOffset = Math.min(newOffset, this.total) + } - if (this.selected[i][1] < minColumn) { - minColumn = this.selected[i][1]; + this.instance.update('offsetRow', newOffset); + this.instance.draw(); +}; + +WalkontableScrollbarNative.prototype.prepare = function () { +}; + +WalkontableScrollbarNative.prototype.availableSize = function () { + var availableSize; + + //var last = this.getLastCell(); + if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table + if (this.instance.wtTable.getLastVisibleRow() === this.total - 1) { + availableSize = this.instance.wtDom.outerHeight(this.TABLE); + } + else { + availableSize = this.windowSize; + } + } + else { + availableSize = this.windowSize - (this.tableParentOffset - this.windowScrollPosition); + } + + return availableSize; +}; + +WalkontableScrollbarNative.prototype.refresh = function () { + var last = this.getLastCell(); + this.measureBefore = this.offset * this.cellSize; + this.measureInside = this.getTableSize(); + if (last === -1) { //last -1 means that viewport is scrolled behind the table + this.measureAfter = 0; + } + else { + this.measureAfter = (this.total - last - 1) * this.cellSize; + } + this.applyToDOM(); +}; + +WalkontableScrollbarNative.prototype.destroy = function () { + this.$scrollHandler.off('scroll.walkontable'); +}; + +/// + +var WalkontableVerticalScrollbarNative = function (instance) { + this.instance = instance; + this.type = 'vertical'; + this.cellSize = 20; + this.init(); +}; + +WalkontableVerticalScrollbarNative.prototype = new WalkontableScrollbarNative(); + +WalkontableVerticalScrollbarNative.prototype.getLastCell = function () { + return this.instance.wtTable.getLastVisibleRow(); +}; + +WalkontableVerticalScrollbarNative.prototype.getTableSize = function () { + return this.instance.wtDom.outerHeight(this.TABLE); +}; + +WalkontableVerticalScrollbarNative.prototype.applyToDOM = function () { + if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table + this.fixed.style.position = 'fixed'; + this.fixed.style.top = '0'; + this.fixed.style.left = this.tableParentOtherOffset; + } + else { + this.fixed.style.position = 'relative'; + } + + var debug = false; + if (debug) { + //this.fixedContainer.style.borderTop = this.measureBefore + 'px solid red'; + //this.fixedContainer.style.borderBottom = (this.tableSize + this.measureAfter) + 'px solid blue'; + } + else { + this.fixedContainer.style.paddingTop = this.measureBefore + 'px'; + this.fixedContainer.style.paddingBottom = (this.measureInside + this.measureAfter) + 'px'; + } +}; + +WalkontableVerticalScrollbarNative.prototype.scrollTo = function (cell) { + this.$scrollHandler.scrollTop(this.tableParentOffset + cell * this.cellSize); +}; + +WalkontableVerticalScrollbarNative.prototype.readSettings = function () { + var offset = this.instance.wtDom.offset(this.fixedContainer); + this.tableParentOffset = offset.top; + this.tableParentOtherOffset = offset.left; + this.windowSize = this.$scrollHandler.height(); + this.windowScrollPosition = this.$scrollHandler.scrollTop(); + this.offset = this.instance.getSetting('offsetRow'); + this.total = this.instance.getSetting('totalRows'); +}; + +/// + +var WalkontableHorizontalScrollbarNative = function (instance) { + this.instance = instance; + this.type = 'horizontal'; + this.cellSize = 50; + this.init(); +}; + +WalkontableHorizontalScrollbarNative.prototype = new WalkontableScrollbarNative(); + +WalkontableHorizontalScrollbarNative.prototype.getLastCell = function () { + return this.instance.wtTable.getLastVisibleColumn(); +}; + +WalkontableHorizontalScrollbarNative.prototype.getTableSize = function () { + return this.instance.wtDom.outerWidth(this.TABLE); +}; + +WalkontableHorizontalScrollbarNative.prototype.applyToDOM = function () { + if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table + this.fixed.style.position = 'fixed'; + this.fixed.style.left = '0'; + this.fixed.style.top = this.tableParentOtherOffset; + } + else { + this.fixed.style.position = 'relative'; + } + + var debug = false; + if (debug) { + //this.fixedContainer.style.borderLeft = this.measureBefore + 'px solid red'; + //this.fixedContainer.style.borderBottom = (this.tableSize + this.measureAfter) + 'px solid blue'; + } + else { + this.fixedContainer.style.paddingLeft = this.measureBefore + 'px'; + this.fixedContainer.style.paddingRight = (this.measureInside + this.measureAfter) + 'px'; + } +}; + +WalkontableHorizontalScrollbarNative.prototype.scrollTo = function (cell) { + this.$scrollHandler.scrollLeft(this.tableParentOffset + cell * this.cellSize); +}; + +WalkontableHorizontalScrollbarNative.prototype.readSettings = function () { + var offset = this.instance.wtDom.offset(this.fixedContainer); + this.tableParentOffset = offset.left; + this.tableParentOtherOffset = offset.top; + this.windowSize = this.$scrollHandler.width(); + this.windowScrollPosition = this.$scrollHandler.scrollLeft(); + this.offset = this.instance.getSetting('offsetColumn'); + this.total = this.instance.getSetting('totalColumns'); +}; +function WalkontableScrollbars(instance) { + switch (instance.getSetting('scrollbarModelV')) { + case 'dragdealer': + this.vertical = new WalkontableVerticalScrollbar(instance); + break; + + case 'native': + this.vertical = new WalkontableVerticalScrollbarNative(instance); + break; + } + + switch (instance.getSetting('scrollbarModelH')) { + case 'dragdealer': + this.horizontal = new WalkontableHorizontalScrollbar(instance); + break; + + case 'native': + this.horizontal = new WalkontableHorizontalScrollbarNative(instance); + break; + } +} + +WalkontableScrollbars.prototype.destroy = function () { + this.vertical.destroy(); + this.horizontal.destroy(); +}; + +WalkontableScrollbars.prototype.refresh = function () { + this.horizontal.readSettings(); + this.vertical.readSettings(); + this.horizontal.prepare(); + this.vertical.prepare(); + this.horizontal.refresh(); + this.vertical.refresh(); +}; +function WalkontableSelection(instance, settings) { + this.instance = instance; + this.settings = settings; + this.selected = []; + if (settings.border) { + this.border = new WalkontableBorder(instance, settings); + } +} + +WalkontableSelection.prototype.add = function (coords) { + this.selected.push(coords); +}; + +WalkontableSelection.prototype.clear = function () { + this.selected.length = 0; //http://jsperf.com/clear-arrayxxx +}; + +/** + * Returns the top left (TL) and bottom right (BR) selection coordinates + * @returns {Object} + */ +WalkontableSelection.prototype.getCorners = function () { + var minRow + , minColumn + , maxRow + , maxColumn + , i + , ilen = this.selected.length; + + if (ilen > 0) { + minRow = maxRow = this.selected[0][0]; + minColumn = maxColumn = this.selected[0][1]; + + if (ilen > 1) { + for (i = 1; i < ilen; i++) { + if (this.selected[i][0] < minRow) { + minRow = this.selected[i][0]; + } + else if (this.selected[i][0] > maxRow) { + maxRow = this.selected[i][0]; + } + + if (this.selected[i][1] < minColumn) { + minColumn = this.selected[i][1]; } else if (this.selected[i][1] > maxColumn) { maxColumn = this.selected[i][1]; @@ -7009,8 +7597,8 @@ WalkontableSelection.prototype.getCorners = function () { WalkontableSelection.prototype.draw = function () { var corners, r, c, source_r, source_c; - var visibleRows = this.instance.wtTable.countVisibleRows() - , visibleColumns = this.instance.wtTable.countVisibleColumns(); + var visibleRows = this.instance.wtTable.rowStrategy.countVisible() + , visibleColumns = this.instance.wtTable.columnStrategy.countVisible(); if (this.selected.length) { corners = this.getCorners(); @@ -7053,6 +7641,8 @@ function WalkontableSettings(instance, settings) { //presentation mode scrollH: 'auto', //values: scroll (always show scrollbar), auto (show scrollbar if table does not fit in the container), none (never show scrollbar) scrollV: 'auto', //values: see above + scrollbarModelH: 'dragdealer', //values: dragdealer, native + scrollbarModelV: 'dragdealer', //values: dragdealer, native stretchH: 'hybrid', //values: hybrid, all, last, none currentRowClassName: null, currentColumnClassName: null, @@ -7063,8 +7653,8 @@ function WalkontableSettings(instance, settings) { offsetColumn: 0, fixedColumnsLeft: 0, fixedRowsTop: 0, - rowHeaders: null, //this must be a function in format: function (row, TH) {} - columnHeaders: null, //this must be a function in format: function (col, TH) {} + rowHeaders: [], //this must be array of functions: [function (row, TH) {}] + columnHeaders: [], //this must be array of functions: [function (column, TH) {}] totalRows: void 0, totalColumns: void 0, width: null, @@ -7087,8 +7677,7 @@ function WalkontableSettings(instance, settings) { //constants scrollbarWidth: 10, - scrollbarHeight: 10, - rowHeaderWidth: 50 + scrollbarHeight: 10 }; //reference to settings @@ -7106,8 +7695,6 @@ function WalkontableSettings(instance, settings) { } } } - - this.rowHeightCache = []; } /** @@ -7158,8 +7745,10 @@ WalkontableSettings.prototype.has = function (key) { */ WalkontableSettings.prototype.rowHeight = function (row) { - if (typeof this.rowHeightCache[row] !== 'undefined') { - return this.rowHeightCache[row]; + var visible_r = this.instance.wtTable.rowFilter.sourceToVisible(row); + var size = this.instance.wtTable.rowStrategy.getSize(visible_r); + if (size !== void 0) { + return size; } return 20; }; @@ -7167,12 +7756,12 @@ WalkontableSettings.prototype.rowHeight = function (row) { WalkontableSettings.prototype.columnWidth = function (column) { return Math.min(200, this._getSetting('columnWidth', column)); }; -var FLAG_VISIBLE_HORIZONTAL = 0x1; // 000001 -var FLAG_VISIBLE_VERTICAL = 0x2; // 000010 -var FLAG_PARTIALLY_VISIBLE_HORIZONTAL = 0x4; // 000100 -var FLAG_PARTIALLY_VISIBLE_VERTICAL = 0x8; // 001000 -var FLAG_NOT_VISIBLE_HORIZONTAL = 0x10; // 010000 -var FLAG_NOT_VISIBLE_VERTICAL = 0x20; // 100000 +/*var FLAG_VISIBLE_HORIZONTAL = 0x1; // 000001 + var FLAG_VISIBLE_VERTICAL = 0x2; // 000010 + var FLAG_PARTIALLY_VISIBLE_HORIZONTAL = 0x4; // 000100 + var FLAG_PARTIALLY_VISIBLE_VERTICAL = 0x8; // 001000 + var FLAG_NOT_VISIBLE_HORIZONTAL = 0x10; // 010000 + var FLAG_NOT_VISIBLE_VERTICAL = 0x20; // 100000*/ function WalkontableTable(instance) { //reference to instance @@ -7192,14 +7781,6 @@ function WalkontableTable(instance) { var parent = this.TABLE.parentNode; if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) { var spreader = document.createElement('DIV'); - if (this.instance.hasSetting('width') && this.instance.hasSetting('height')) { - var spreaderStyle = spreader.style; - spreaderStyle.position = 'absolute'; - spreaderStyle.top = '0'; - spreaderStyle.left = '0'; - spreaderStyle.width = '4000px'; - spreaderStyle.height = '4000px'; - } spreader.className = 'wtSpreader'; if (parent) { parent.insertBefore(spreader, this.TABLE); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it @@ -7252,7 +7833,7 @@ function WalkontableTable(instance) { this.TABLE.insertBefore(this.COLGROUP, this.THEAD); } - if (this.instance.hasSetting('columnHeaders')) { + if (this.instance.getSetting('columnHeaders').length) { if (!this.THEAD.childNodes.length) { var TR = document.createElement('TR'); this.THEAD.appendChild(TR); @@ -7268,49 +7849,57 @@ function WalkontableTable(instance) { this.rowFilter = new WalkontableRowFilter(); this.columnFilter = new WalkontableColumnFilter(); + + this.verticalRenderReverse = false; } WalkontableTable.prototype.refreshHiderDimensions = function () { - var height = this.instance.getSetting('height'); - var width = this.instance.getSetting('width'); + var height = this.instance.wtViewport.getWorkspaceHeight(); + var width = this.instance.wtViewport.getWorkspaceWidth(); + + var spreaderStyle = this.spreader.style; - if (height || width) { + if (height !== Infinity || width !== Infinity) { this.hiderStyle.overflow = 'hidden'; - } - if (height) { - if (this.instance.wtScroll.wtScrollbarH.visible) { - this.hiderStyle.height = height - this.instance.getSetting('scrollbarHeight') + 'px'; + spreaderStyle.position = 'absolute'; + spreaderStyle.top = '0'; + spreaderStyle.left = '0'; + + if (this.instance.getSetting('scrollbarModelV') === 'dragdealer') { + spreaderStyle.height = '4000px'; } - else { - this.hiderStyle.height = height + 'px'; + + if (this.instance.getSetting('scrollbarModelH') === 'dragdealer') { + spreaderStyle.width = '4000px'; } - } - if (width) { - if (this.instance.wtScroll.wtScrollbarV.visible) { - this.hiderStyle.width = width - this.instance.getSetting('scrollbarWidth') + 'px'; + + if (height === Infinity) { + height = this.instance.wtViewport.getWorkspaceActualHeight(); } - else { - this.hiderStyle.width = width + 'px'; + this.hiderStyle.height = height + 'px'; + + if (width === Infinity) { + width = this.instance.wtViewport.getWorkspaceActualWidth(); } + this.hiderStyle.width = width + 'px'; + } + else { + spreaderStyle.position = 'relative'; + spreaderStyle.width = 'auto'; + spreaderStyle.height = 'auto'; } }; WalkontableTable.prototype.refreshStretching = function () { var instance = this.instance , stretchH = instance.getSetting('stretchH') - , scrollH = instance.getSetting('scrollH') - , scrollbarWidth = instance.getSetting('scrollbarWidth') + , totalRows = instance.getSetting('totalRows') , totalColumns = instance.getSetting('totalColumns') - , offsetColumn = instance.getSetting('offsetColumn') - , rowHeaderWidth = instance.hasSetting('rowHeaders') ? instance.getSetting('rowHeaderWidth') : 0 - , containerWidth = instance.getSetting('width') - rowHeaderWidth; + , offsetColumn = instance.getSetting('offsetColumn'); var containerWidthFn = function (cacheWidth) { - if (scrollH === 'scroll' || (scrollH === 'auto' && cacheWidth > containerWidth)) { - return containerWidth - scrollbarWidth; - } - return containerWidth; + return that.instance.wtViewport.getViewportWidth(cacheWidth); }; var that = this; @@ -7331,18 +7920,40 @@ WalkontableTable.prototype.refreshStretching = function () { } } + var containerHeightFn = function (cacheHeight) { + return that.instance.wtViewport.getViewportHeight(cacheHeight); + }; + + var rowHeightFn = function (i, TD) { + var source_r = that.rowFilter.visibleToSource(i); + if (source_r < totalRows) { + if (that.verticalRenderReverse && i === 0) { + return that.wtDom.outerHeight(TD) - 1; + } + else { + return that.wtDom.outerHeight(TD); + } + } + }; + this.columnStrategy = new WalkontableColumnStrategy(containerWidthFn, columnWidthFn, stretchH); - this.rowStrategy = { - cells: [], - cellCount: 0, - remainingSize: 0 - } + this.rowStrategy = new WalkontableRowStrategy(containerHeightFn, rowHeightFn); }; WalkontableTable.prototype.adjustAvailableNodes = function () { var displayTds - , displayThs = this.instance.hasSetting('rowHeaders') ? 1 : 0 - , TR; + , rowHeaders = this.instance.getSetting('rowHeaders') + , displayThs = rowHeaders.length + , columnHeaders = this.instance.getSetting('columnHeaders') + , TR + , TD + , c; + + //adjust COLGROUP + while (this.colgroupChildrenLength < displayThs) { + this.COLGROUP.appendChild(document.createElement('COL')); + this.colgroupChildrenLength++; + } this.refreshStretching(); displayTds = this.columnStrategy.cellCount; @@ -7358,7 +7969,7 @@ WalkontableTable.prototype.adjustAvailableNodes = function () { } //adjust THEAD - if (this.instance.hasSetting('columnHeaders')) { + if (columnHeaders.length) { TR = this.THEAD.firstChild; while (this.theadChildrenLength < displayTds + displayThs) { TR.appendChild(document.createElement('TH')); @@ -7369,6 +7980,37 @@ WalkontableTable.prototype.adjustAvailableNodes = function () { this.theadChildrenLength--; } } + + //draw COLGROUP + for (c = 0; c < this.colgroupChildrenLength; c++) { + if (c < displayThs) { + this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader'); + } + else { + this.wtDom.removeClass(this.COLGROUP.childNodes[c], 'rowHeader'); + } + } + + //draw THEAD + if (columnHeaders.length) { + TR = this.THEAD.firstChild; + if (displayThs) { + TD = TR.firstChild; //actually it is TH but let's reuse single variable + for (c = 0; c < displayThs; c++) { + rowHeaders[c](-displayThs + c, TD); + if (this.hasEmptyCellProblem) { //IE7 + TD.innerHTML = ' '; + } + TD = TD.nextSibling; + } + } + } + + for (c = 0; c < displayTds; c++) { + if (columnHeaders.length) { + columnHeaders[0](this.columnFilter.visibleToSource(c), TR.childNodes[displayThs + c]); + } + } }; WalkontableTable.prototype.adjustColumns = function (TR, desiredCount) { @@ -7390,10 +8032,10 @@ WalkontableTable.prototype.draw = function (selectionsOnly) { if (!selectionsOnly) { this.tableOffset = this.wtDom.offset(this.TABLE); - // this.TABLE.removeChild(this.TBODY); //possible future optimization - http://jsperf.com/table-scrolling/9 - this.adjustAvailableNodes(); this._doDraw(); - // this.TABLE.appendChild(this.TBODY); + } + else { + this.instance.wtScrollbars.refresh(); } this.refreshPositions(selectionsOnly); @@ -7407,53 +8049,48 @@ WalkontableTable.prototype._doDraw = function () { , source_r , c , source_c + , offsetRow = this.instance.getSetting('offsetRow') , totalRows = this.instance.getSetting('totalRows') - , displayTds = this.columnStrategy.cellCount - , rowHeaders = this.instance.hasSetting('rowHeaders') - , displayThs = rowHeaders ? 1 : 0 + , totalColumns = this.instance.getSetting('totalColumns') + , displayTds + , rowHeaders = this.instance.getSetting('rowHeaders') + , displayThs = rowHeaders.length , TR - , TD; + , TD + , adjusted = false + , workspaceWidth; - //draw COLGROUP - for (c = 0; c < this.colgroupChildrenLength; c++) { - if (c < displayThs) { - this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader'); - } - else { - this.wtDom.removeClass(this.COLGROUP.childNodes[c], 'rowHeader'); - } - } + this.instance.wtViewport.resetSettings(); - //draw THEAD - var columnHeaders = this.instance.hasSetting('columnHeaders'); - if (columnHeaders) { - TR = this.THEAD.firstChild; - if (displayThs) { - TD = TR.firstChild; //actually it is TH but let's reuse single variable - this.wtDom.empty(TD); - if (this.hasEmptyCellProblem) { //IE7 - TD.innerHTML = ' '; - } + var noPartial = false; + if (this.verticalRenderReverse) { + if (offsetRow === totalRows - this.rowFilter.fixedCount - 1) { + noPartial = true; } - } - - for (c = 0; c < displayTds; c++) { - if (columnHeaders) { - this.instance.getSetting('columnHeaders', this.columnFilter.visibleToSource(c), TR.childNodes[displayThs + c]); + else { + this.instance.update('offsetRow', offsetRow + 1); //if we are scrolling reverse + this.rowFilter.readSettings(this.instance); } - this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px'; } //draw TBODY - if (displayTds > 0) { + if (totalColumns > 0) { source_r = this.rowFilter.visibleToSource(r); - while (source_r < totalRows) { - if (r >= this.tbodyChildrenLength) { + + var first = true; + + while (source_r < totalRows && source_r >= 0) { + if (r >= this.tbodyChildrenLength || (this.verticalRenderReverse && r >= this.rowFilter.fixedCount)) { TR = document.createElement('TR'); - if (displayThs) { + for (c = 0; c < displayThs; c++) { TR.appendChild(document.createElement('TH')); } - this.TBODY.appendChild(TR); + if (this.verticalRenderReverse && r >= this.rowFilter.fixedCount) { + this.TBODY.insertBefore(TR, this.TBODY.childNodes[this.rowFilter.fixedCount] || this.TBODY.firstChild); + } + else { + this.TBODY.appendChild(TR); + } this.tbodyChildrenLength++; } else if (r === 0) { @@ -7462,15 +8099,35 @@ WalkontableTable.prototype._doDraw = function () { else { TR = TR.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes } - this.rowStrategy.cellCount++; //TH - if (displayThs) { - this.instance.getSetting('rowHeaders', source_r, TR.firstChild); + TD = TR.firstChild; + for (c = 0; c < displayThs; c++) { + rowHeaders[c](source_r, TD); //actually TH + TD = TD.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes } - //TD - this.adjustColumns(TR, displayTds + displayThs); + if (first) { +// if (r === 0) { + first = false; + + this.adjustAvailableNodes(); + adjusted = true; + displayTds = this.columnStrategy.cellCount; + + //TD + this.adjustColumns(TR, displayTds + displayThs); + + workspaceWidth = this.instance.wtViewport.getWorkspaceWidth(); + this.columnStrategy.stretch(); + for (c = 0; c < displayTds; c++) { + this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px'; + } + } + else { + //TD + this.adjustColumns(TR, displayTds + displayThs); + } for (c = 0; c < displayTds; c++) { source_c = this.columnFilter.visibleToSource(c); @@ -7488,25 +8145,70 @@ WalkontableTable.prototype._doDraw = function () { } } + offsetRow = this.instance.getSetting('offsetRow'); //refresh the value + //after last column is rendered, check if last cell is fully displayed - var isCellVisible = this.isCellVisible(source_r, source_c, TD); - if (isCellVisible & (FLAG_NOT_VISIBLE_VERTICAL | FLAG_PARTIALLY_VISIBLE_VERTICAL)) { //when it is invisible or partially visible, don't render more rows - break; + if (this.verticalRenderReverse && noPartial) { + if (-this.wtDom.outerHeight(TR.firstChild) < this.rowStrategy.remainingSize) { + this.TBODY.removeChild(TR); + this.instance.update('offsetRow', offsetRow + 1); + this.tbodyChildrenLength--; + this.rowFilter.readSettings(this.instance); + break; + + } + else { + this.rowStrategy.add(r, TD); + } + } + else { + this.rowStrategy.add(r, TD); + + if (this.rowStrategy.isLastIncomplete()) { + break; + } + } + + if (this.verticalRenderReverse && r >= this.rowFilter.fixedCount) { + if (offsetRow === 0) { + break; + } + this.instance.update('offsetRow', offsetRow - 1); + this.rowFilter.readSettings(this.instance); + } + else { + r++; } - r++; source_r = this.rowFilter.visibleToSource(r); } } - r = this.countVisibleRows(); + + if (!adjusted) { + this.adjustAvailableNodes(); + } + + r = this.rowStrategy.countVisible(); while (this.tbodyChildrenLength > r) { this.TBODY.removeChild(this.TBODY.lastChild); this.tbodyChildrenLength--; } -}; + + this.instance.wtScrollbars.refresh(); + + if (workspaceWidth !== this.instance.wtViewport.getWorkspaceWidth()) { + //workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching + this.columnStrategy.stretch(); + for (c = 0; c < displayTds; c++) { + this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px'; + } + } + + this.verticalRenderReverse = false; +} +; WalkontableTable.prototype.refreshPositions = function (selectionsOnly) { - this.instance.wtScroll.refreshScrollbars(); this.refreshHiderDimensions(); this.refreshSelections(selectionsOnly); }; @@ -7519,8 +8221,8 @@ WalkontableTable.prototype.refreshSelections = function (selectionsOnly) { , s , slen , classNames = [] - , visibleRows = this.countVisibleRows() - , visibleColumns = this.countVisibleColumns(); + , visibleRows = this.rowStrategy.countVisible() + , visibleColumns = this.columnStrategy.countVisible(); this.oldCellCache = this.currentCellCache; this.currentCellCache = new WalkontableClassNameCache(); @@ -7560,43 +8262,36 @@ WalkontableTable.prototype.refreshSelections = function (selectionsOnly) { } }; -WalkontableTable.prototype.isCellVisible = function (r, c, TD) { - var out = 0 - , cellOffset = this.wtDom.offset(TD) - , tableOffset = this.tableOffset - , innerOffsetTop = cellOffset.top - tableOffset.top - , $td = $(TD) - , height = $td.outerHeight() - , tableHeight = this.instance.hasSetting('height') ? this.instance.getSetting('height') : Infinity; +/* this function is not used currently (was used in _doDraw) + WalkontableTable.prototype.isCellVisible = function (r, c) { + var out = 0; - this.instance.wtSettings.rowHeightCache[r] = height; - - - if (innerOffsetTop > tableHeight) { - out |= FLAG_NOT_VISIBLE_VERTICAL; - } - else if (innerOffsetTop + height > tableHeight) { - this.rowStrategy.remainingSize = tableHeight - innerOffsetTop; //hackish but enough - out |= FLAG_PARTIALLY_VISIBLE_VERTICAL; - } - else { - out |= FLAG_VISIBLE_VERTICAL; - } + if (this.isRowInViewport(r)) { + if (this.getLastVisibleRow() === c && this.rowStrategy.remainingSize > 0) { + out |= FLAG_PARTIALLY_VISIBLE_VERTICAL; + } + else { + out |= FLAG_VISIBLE_VERTICAL; + } + } + else { + out |= FLAG_NOT_VISIBLE_VERTICAL; + } - if (this.isColumnInViewport(c)) { - if (this.getLastVisibleColumn() === c && this.columnStrategy.remainingSize > 0) { - out |= FLAG_PARTIALLY_VISIBLE_HORIZONTAL; - } - else { - out |= FLAG_VISIBLE_HORIZONTAL; - } - } - else { - out |= FLAG_NOT_VISIBLE_HORIZONTAL; - } + if (this.isColumnInViewport(c)) { + if (this.getLastVisibleColumn() === c && this.columnStrategy.remainingSize > 0) { + out |= FLAG_PARTIALLY_VISIBLE_HORIZONTAL; + } + else { + out |= FLAG_VISIBLE_HORIZONTAL; + } + } + else { + out |= FLAG_NOT_VISIBLE_HORIZONTAL; + } - return out; -}; + return out; + };*/ /** * getCell @@ -7635,30 +8330,16 @@ WalkontableTable.prototype.getCoords = function (TD) { ]; }; -WalkontableTable.prototype.countVisibleRows = function () { - return (this.rowStrategy && this.rowStrategy.cellCount) || 0; -}; - -WalkontableTable.prototype.countVisibleColumns = function () { - return (this.columnStrategy && this.columnStrategy.cellCount) || 0; -}; - +//returns -1 if no row is visible WalkontableTable.prototype.getLastVisibleRow = function () { return this.rowFilter.visibleToSource(this.rowStrategy.cellCount - 1); }; +//returns -1 if no column is visible WalkontableTable.prototype.getLastVisibleColumn = function () { return this.columnFilter.visibleToSource(this.columnStrategy.cellCount - 1); }; -WalkontableTable.prototype.isLastRowIncomplete = function () { - return (this.rowStrategy.remainingSize > 0); -}; - -WalkontableTable.prototype.isLastColumnIncomplete = function () { - return (this.columnStrategy.remainingSize > 0); -}; - WalkontableTable.prototype.isRowBeforeViewport = function (r) { return (this.rowFilter.sourceToVisible(r) < this.rowFilter.fixedCount && r >= this.rowFilter.fixedCount); }; @@ -7682,27 +8363,180 @@ WalkontableTable.prototype.isRowInViewport = function (r) { WalkontableTable.prototype.isColumnInViewport = function (c) { return (!this.isColumnBeforeViewport(c) && !this.isColumnAfterViewport(c)); }; -function WalkontableWheel(instance) { - var that = this; - //reference to instance +WalkontableTable.prototype.isLastRowFullyVisible = function () { + return (this.getLastVisibleRow() === this.instance.getSetting('totalRows') - 1 && !this.rowStrategy.isLastIncomplete()); +}; + +WalkontableTable.prototype.isLastColumnFullyVisible = function () { + return (this.getLastVisibleColumn() === this.instance.getSetting('totalColumns') - 1 && !this.columnStrategy.isLastIncomplete()); +}; +function WalkontableViewport(instance) { this.instance = instance; - $(this.instance.wtTable.TABLE).on('mousewheel', function (event, delta, deltaX, deltaY) { - clearTimeout(that.instance.wheelTimeout); - that.instance.wheelTimeout = setTimeout(function () { //timeout is needed because with fast-wheel scrolling mousewheel event comes dozen times per second + this.resetSettings(); +} + +/*WalkontableViewport.prototype.isInSightVertical = function () { + //is table outside viewport bottom edge + if (tableTop > windowHeight + scrollTop) { + return -1; + } + + //is table outside viewport top edge + else if (scrollTop > tableTop + tableFakeHeight) { + return -2; + } + + //table is in viewport but how much exactly? + else { + + } +};*/ + +//used by scrollbar +WalkontableViewport.prototype.getWorkspaceHeight = function (proposedHeight) { + var height = this.instance.getSetting('height'); + + if (height === Infinity || height === void 0 || height === null || height < 1) { + if (this.instance.wtScrollbars.vertical instanceof WalkontableScrollbarNative) { + height = this.instance.wtScrollbars.vertical.availableSize(); + } + else { + height = Infinity; + } + } + + if (height !== Infinity) { + if (proposedHeight >= height) { + height -= this.instance.getSetting('scrollbarHeight'); + } + else if (this.instance.wtScrollbars.horizontal.visible) { + height -= this.instance.getSetting('scrollbarHeight'); + } + } + + return height; +}; + +WalkontableViewport.prototype.getWorkspaceWidth = function (proposedWidth) { + var width = this.instance.getSetting('width'); + + if (width === Infinity || width === void 0 || width === null || width < 1) { + if (this.instance.wtScrollbars.horizontal instanceof WalkontableScrollbarNative) { + width = this.instance.wtScrollbars.horizontal.availableSize(); + } + else { + width = Infinity; + } + } + + if (width !== Infinity) { + if (proposedWidth >= width) { + width -= this.instance.getSetting('scrollbarWidth'); + } + else if (this.instance.wtScrollbars.vertical.visible) { + width -= this.instance.getSetting('scrollbarWidth'); + } + } + return width; +}; + +WalkontableViewport.prototype.getWorkspaceActualHeight = function () { + return this.instance.wtDom.outerHeight(this.instance.wtTable.TABLE); +}; + +WalkontableViewport.prototype.getWorkspaceActualWidth = function () { + return this.instance.wtDom.outerWidth(this.instance.wtTable.TABLE) || this.instance.wtDom.outerWidth(this.instance.wtTable.TBODY) || this.instance.wtDom.outerWidth(this.instance.wtTable.THEAD); //IE8 reports 0 as
    offsetWidth; +}; + +WalkontableViewport.prototype.getViewportHeight = function (proposedHeight) { + var containerHeight = this.getWorkspaceHeight(proposedHeight); + + if (containerHeight === Infinity) { + return containerHeight; + } + + if (isNaN(this.columnHeaderHeight)) { + var cellOffset = this.instance.wtDom.offset(this.instance.wtTable.TBODY) + , tableOffset = this.instance.wtTable.tableOffset; + this.columnHeaderHeight = cellOffset.top - tableOffset.top; + } + + if (this.columnHeaderHeight > 0) { + return containerHeight - this.columnHeaderHeight; + } + else { + return containerHeight; + } +}; + +WalkontableViewport.prototype.getViewportWidth = function (proposedWidth) { + var containerWidth = this.getWorkspaceWidth(proposedWidth); + + if (containerWidth === Infinity) { + return containerWidth; + } + + if (isNaN(this.rowHeaderWidth)) { + var TR = this.instance.wtTable.TBODY ? this.instance.wtTable.TBODY.firstChild : null; + if (TR) { + var TD = TR.firstChild; + this.rowHeaderWidth = 0; + while (TD && TD.nodeName === 'TH') { + this.rowHeaderWidth += this.instance.wtDom.outerWidth(TD); + TD = TD.nextSibling; + } + } + } + + if (this.rowHeaderWidth > 0) { + return containerWidth - this.rowHeaderWidth; + } + else { + return containerWidth; + } +}; + +WalkontableViewport.prototype.resetSettings = function () { + this.rowHeaderWidth = NaN; + this.columnHeaderHeight = NaN; +}; +function WalkontableWheel(instance) { + if (instance.getSetting('scrollbarModelV') === 'native' || instance.getSetting('scrollbarModelH') === 'native') { + return; + } + + $(instance.wtTable.TABLE).on('mousewheel', function (event, delta, deltaX, deltaY) { + if (deltaY > 0 && instance.getSetting('offsetRow') === 0) { + return; //attempt to scroll up when it's already showing first row + } + else if (deltaY < 0 && instance.wtTable.isLastRowFullyVisible()) { + return; //attempt to scroll down when it's already showing last row + } + else if (deltaX < 0 && instance.getSetting('offsetColumn') === 0) { + return; //attempt to scroll left when it's already showing first column + } + else if (deltaX > 0 && instance.wtTable.isLastColumnFullyVisible()) { + return; //attempt to scroll right when it's already showing last column + } + + //now we are sure we really want to scroll + clearTimeout(instance.wheelTimeout); + instance.wheelTimeout = setTimeout(function () { //timeout is needed because with fast-wheel scrolling mousewheel event comes dozen times per second if (deltaY) { //ceil is needed because jquery-mousewheel reports fractional mousewheel deltas on touchpad scroll //see http://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers - if (that.instance.wtScroll.wtScrollbarV.visible) { // if we see scrollbar - that.instance.scrollVertical(-Math.ceil(deltaY)).draw(); + if (instance.wtScrollbars.vertical.visible) { // if we see scrollbar + instance.scrollVertical(-Math.ceil(deltaY)).draw(); } } else if (deltaX) { - if (that.instance.wtScroll.wtScrollbarH.visible) { // if we see scrollbar - that.instance.scrollHorizontal(Math.ceil(deltaX)).draw(); + if (instance.wtScrollbars.horizontal.visible) { // if we see scrollbar + instance.scrollHorizontal(Math.ceil(deltaX)).draw(); } } }, 0); + event.preventDefault(); }); } diff --git a/components/handsontable/dist/jquery.handsontable.js b/components/handsontable/dist/jquery.handsontable.js new file mode 100644 index 00000000..51d55495 --- /dev/null +++ b/components/handsontable/dist/jquery.handsontable.js @@ -0,0 +1,9274 @@ +/** + * Handsontable 0.9.4 + * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs + * + * Copyright 2012, Marcin Warpechowski + * Licensed under the MIT license. + * http://handsontable.com/ + * + * Date: Fri Jun 07 2013 01:26:00 GMT+0200 (Central European Daylight Time) + */ +/*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */ + +var Handsontable = { //class namespace + extension: {}, //extenstion namespace + helper: {} //helper namespace +}; + +(function ($, window, Handsontable) { + "use strict"; +/** + * Handsontable constructor + * @param rootElement The jQuery element in which Handsontable DOM will be inserted + * @param userSettings + * @constructor + */ +Handsontable.Core = function (rootElement, userSettings) { + var priv + , datamap + , grid + , selection + , editproxy + , autofill + , instance = this + , GridSettings = function () { + }; + + Handsontable.helper.inherit(GridSettings, DefaultSettings); //create grid settings as a copy of default settings + Handsontable.helper.extend(GridSettings.prototype, userSettings); //overwrite defaults with user settings + + this.rootElement = rootElement; + this.guid = 'ht_' + Handsontable.helper.randomString(); //this is the namespace for global events + + if (!this.rootElement[0].id) { + this.rootElement[0].id = this.guid; //if root element does not have an id, assign a random id + } + + priv = { + columnSettings: [], + columnsSettingConflicts: ['data', 'width'], + settings: new GridSettings(), // current settings instance + settingsFromDOM: {}, + selStart: new Handsontable.SelectionPoint(), + selEnd: new Handsontable.SelectionPoint(), + editProxy: false, + isPopulated: null, + scrollable: null, + undoRedo: null, + extensions: {}, + colToProp: null, + propToCol: null, + dataSchema: null, + dataType: 'array', + firstRun: true + }; + + datamap = { + recursiveDuckSchema: function (obj) { + var schema; + if ($.isPlainObject(obj)) { + schema = {}; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + if ($.isPlainObject(obj[i])) { + schema[i] = datamap.recursiveDuckSchema(obj[i]); + } + else { + schema[i] = null; + } + } + } + } + else { + schema = []; + } + return schema; + }, + + recursiveDuckColumns: function (schema, lastCol, parent) { + var prop, i; + if (typeof lastCol === 'undefined') { + lastCol = 0; + parent = ''; + } + if ($.isPlainObject(schema)) { + for (i in schema) { + if (schema.hasOwnProperty(i)) { + if (schema[i] === null) { + prop = parent + i; + priv.colToProp.push(prop); + priv.propToCol[prop] = lastCol; + lastCol++; + } + else { + lastCol = datamap.recursiveDuckColumns(schema[i], lastCol, i + '.'); + } + } + } + } + return lastCol; + }, + + createMap: function () { + if (typeof datamap.getSchema() === "undefined") { + throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`"); + } + var i, ilen, schema = datamap.getSchema(); + priv.colToProp = []; + priv.propToCol = {}; + if (priv.settings.columns) { + for (i = 0, ilen = priv.settings.columns.length; i < ilen; i++) { + priv.colToProp[i] = priv.settings.columns[i].data; + priv.propToCol[priv.settings.columns[i].data] = i; + } + } + else { + datamap.recursiveDuckColumns(schema); + } + }, + + colToProp: function (col) { + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); + if (priv.colToProp && typeof priv.colToProp[col] !== 'undefined') { + return priv.colToProp[col]; + } + else { + return col; + } + }, + + propToCol: function (prop) { + var col; + if (typeof priv.propToCol[prop] !== 'undefined') { + col = priv.propToCol[prop]; + } + else { + col = prop; + } + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); + return col; + }, + + getSchema: function () { + if (priv.settings.dataSchema) { + if (typeof priv.settings.dataSchema === 'function') { + return priv.settings.dataSchema(); + } + return priv.settings.dataSchema; + } + return priv.duckDataSchema; + }, + + /** + * Creates row at the bottom of the data array + * @param {Number} [index] Optional. Index of the row before which the new row will be inserted + */ + createRow: function (index) { + var row + , rowCount = instance.countRows(); + + if (typeof index !== 'number' || index >= rowCount) { + index = rowCount; + } + + if (priv.dataType === 'array') { + row = []; + for (var c = 0, clen = instance.countCols(); c < clen; c++) { + row.push(null); + } + } + else if (priv.dataType === 'function') { + row = priv.settings.dataSchema(index); + } + else { + row = $.extend(true, {}, datamap.getSchema()); + } + + if (index === rowCount) { + GridSettings.prototype.data.push(row); + } + else { + GridSettings.prototype.data.splice(index, 0, row); + } + + instance.PluginHooks.run('afterCreateRow', index); + instance.forceFullRender = true; //used when data was changed + }, + + /** + * Creates col at the right of the data array + * @param {Object} [index] Optional. Index of the column before which the new column will be inserted + */ + createCol: function (index) { + if (priv.dataType === 'object' || priv.settings.columns) { + throw new Error("Cannot create new column. When data source in an object, you can only have as much columns as defined in first data row, data schema or in the 'columns' setting"); + } + var r = 0, rlen = instance.countRows() + , data = GridSettings.prototype.data + , constructor = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts, Handsontable.TextCell); + + if (typeof index !== 'number' || index >= instance.countCols()) { + for (; r < rlen; r++) { + if (typeof data[r] === 'undefined') { + data[r] = []; + } + data[r].push(null); + } + // Add new column constructor + priv.columnSettings.push(constructor); + } + else { + for (; r < rlen; r++) { + data[r].splice(index, 0, null); + } + // Add new column constructor at given index + priv.columnSettings.splice(index, 0, constructor); + } + instance.PluginHooks.run('afterCreateCol', index); + instance.forceFullRender = true; //used when data was changed + }, + + /** + * Removes row from the data array + * @param {Number} [index] Optional. Index of the row to be removed. If not provided, the last row will be removed + * @param {Number} [amount] Optional. Amount of the rows to be removed. If not provided, one row will be removed + */ + removeRow: function (index, amount) { + if (!amount) { + amount = 1; + } + if (typeof index !== 'number') { + index = -amount; + } + GridSettings.prototype.data.splice(index, amount); + instance.PluginHooks.run('afterRemoveRow', index, amount); + instance.forceFullRender = true; //used when data was changed + }, + + /** + * Removes column from the data array + * @param {Number} [index] Optional. Index of the column to be removed. If not provided, the last column will be removed + * @param {Number} [amount] Optional. Amount of the columns to be removed. If not provided, one column will be removed + */ + removeCol: function (index, amount) { + if (priv.dataType === 'object' || priv.settings.columns) { + throw new Error("cannot remove column with object data source or columns option specified"); + } + if (!amount) { + amount = 1; + } + if (typeof index !== 'number') { + index = -amount; + } + var data = GridSettings.prototype.data; + for (var r = 0, rlen = instance.countRows(); r < rlen; r++) { + data[r].splice(index, amount); + } + instance.PluginHooks.run('afterRemoveCol', index, amount); + priv.columnSettings.splice(index, amount); + instance.forceFullRender = true; //used when data was changed + }, + + /** + * Add / removes data from the column + * @param {Number} col Index of column in which do you want to do splice. + * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end + * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed + * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array + */ + spliceCol: function (col, index, amount/*, elements...*/) { + var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : []; + + var colData = instance.getDataAtCol(col); + var removed = colData.slice(index, index + amount); + var after = colData.slice(index + amount); + + Handsontable.helper.extendArray(elements, after); + var i = 0; + while (i < amount) { + elements.push(null); //add null in place of removed elements + i++; + } + Handsontable.helper.to2dArray(elements); + instance.populateFromArray(index, col, elements, null, null, 'spliceCol'); + + return removed; + }, + + /** + * Add / removes data from the row + * @param {Number} row Index of row in which do you want to do splice. + * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end + * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed + * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array + */ + spliceRow: function (row, index, amount/*, elements...*/) { + var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : []; + + var rowData = instance.getDataAtRow(row); + var removed = rowData.slice(index, index + amount); + var after = rowData.slice(index + amount); + + Handsontable.helper.extendArray(elements, after); + var i = 0; + while (i < amount) { + elements.push(null); //add null in place of removed elements + i++; + } + instance.populateFromArray(row, index, [elements], null, null, 'spliceRow'); + + return removed; + }, + + /** + * Returns single value from the data array + * @param {Number} row + * @param {Number} prop + */ + getVars: {}, + get: function (row, prop) { + datamap.getVars.row = row; + datamap.getVars.prop = prop; + instance.PluginHooks.run('beforeGet', datamap.getVars); + if (typeof datamap.getVars.prop === 'string' && datamap.getVars.prop.indexOf('.') > -1) { + var sliced = datamap.getVars.prop.split("."); + var out = priv.settings.data[datamap.getVars.row]; + if (!out) { + return null; + } + for (var i = 0, ilen = sliced.length; i < ilen; i++) { + out = out[sliced[i]]; + if (typeof out === 'undefined') { + return null; + } + } + return out; + } + else if (typeof datamap.getVars.prop === 'function') { + /** + * allows for interacting with complex structures, for example + * d3/jQuery getter/setter properties: + * + * {columns: [{ + * data: function(row, value){ + * if(arguments.length === 1){ + * return row.property(); + * } + * row.property(value); + * } + * }]} + */ + return datamap.getVars.prop(priv.settings.data.slice( + datamap.getVars.row, + datamap.getVars.row + 1 + )[0]); + } + else { + return priv.settings.data[datamap.getVars.row] ? priv.settings.data[datamap.getVars.row][datamap.getVars.prop] : null; + } + }, + + /** + * Saves single value to the data array + * @param {Number} row + * @param {Number} prop + * @param {String} value + * @param {String} [source] Optional. Source of hook runner. + */ + setVars: {}, + set: function (row, prop, value, source) { + datamap.setVars.row = row; + datamap.setVars.prop = prop; + datamap.setVars.value = value; + instance.PluginHooks.run('beforeSet', datamap.setVars, source || "datamapGet"); + if (typeof datamap.setVars.prop === 'string' && datamap.setVars.prop.indexOf('.') > -1) { + var sliced = datamap.setVars.prop.split("."); + var out = priv.settings.data[datamap.setVars.row]; + for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) { + out = out[sliced[i]]; + } + out[sliced[i]] = datamap.setVars.value; + } + else if (typeof datamap.setVars.prop === 'function') { + /* see the `function` handler in `get` */ + datamap.setVars.prop(priv.settings.data.slice( + datamap.setVars.row, + datamap.setVars.row + 1 + )[0], datamap.setVars.value); + } + else { + priv.settings.data[datamap.setVars.row][datamap.setVars.prop] = datamap.setVars.value; + } + }, + + /** + * Clears the data array + */ + clear: function () { + for (var r = 0; r < instance.countRows(); r++) { + for (var c = 0; c < instance.countCols(); c++) { + datamap.set(r, datamap.colToProp(c), ''); + } + } + }, + + /** + * Returns the data array + * @return {Array} + */ + getAll: function () { + return priv.settings.data; + }, + + /** + * Returns data range as array + * @param {Object} start Start selection position + * @param {Object} end End selection position + * @return {Array} + */ + getRange: function (start, end) { + var r, rlen, c, clen, output = [], row; + rlen = Math.max(start.row, end.row); + clen = Math.max(start.col, end.col); + for (r = Math.min(start.row, end.row); r <= rlen; r++) { + row = []; + for (c = Math.min(start.col, end.col); c <= clen; c++) { + row.push(datamap.get(r, datamap.colToProp(c))); + } + output.push(row); + } + return output; + }, + + /** + * Return data as text (tab separated columns) + * @param {Object} start (Optional) Start selection position + * @param {Object} end (Optional) End selection position + * @return {String} + */ + getText: function (start, end) { + return SheetClip.stringify(datamap.getRange(start, end)); + } + }; + + grid = { + /** + * Inserts or removes rows and columns + * @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col" + * @param {Number} index + * @param {Number} amount + * @param {String} [source] Optional. Source of hook runner. + * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows. + */ + alter: function (action, index, amount, source, keepEmptyRows) { + var oldData, newData, changes, r, rlen, c, clen, delta; + oldData = $.extend(true, [], datamap.getAll()); + + switch (action) { + case "insert_row": + if (!amount) { + amount = 1; + } + delta = 0; + while (delta < amount && instance.countRows() < priv.settings.maxRows) { + datamap.createRow(index); + delta++; + } + if (delta) { + if (priv.selStart.exists() && priv.selStart.row() >= index) { + priv.selStart.row(priv.selStart.row() + delta); + selection.transformEnd(delta, 0); //will call render() internally + } + else { + selection.refreshBorders(); //it will call render and prepare methods + } + } + break; + + case "insert_col": + if (!amount) { + amount = 1; + } + delta = 0; + while (delta < amount && instance.countCols() < priv.settings.maxCols) { + datamap.createCol(index); + delta++; + } + if (delta) { + if (priv.selStart.exists() && priv.selStart.col() >= index) { + priv.selStart.col(priv.selStart.col() + delta); + selection.transformEnd(0, delta); //will call render() internally + } + else { + selection.refreshBorders(); //it will call render and prepare methods + } + } + break; + + case "remove_row": + datamap.removeRow(index, amount); + grid.adjustRowsAndCols(); + selection.refreshBorders(); //it will call render and prepare methods + break; + + case "remove_col": + datamap.removeCol(index, amount); + grid.adjustRowsAndCols(); + selection.refreshBorders(); //it will call render and prepare methods + break; + + default: + throw Error('There is no such action "' + action + '"'); + break; + } + + changes = []; + newData = datamap.getAll(); + for (r = 0, rlen = newData.length; r < rlen; r++) { + for (c = 0, clen = newData[r].length; c < clen; c++) { + changes.push([r, c, oldData[r] ? oldData[r][c] : null, newData[r][c]]); + } + } + instance.PluginHooks.run('afterChange', changes, source || action); + if (!keepEmptyRows) { + grid.adjustRowsAndCols(); //makes sure that we did not add rows that will be removed in next refresh + } + }, + + /** + * Makes sure there are empty rows at the bottom of the table + */ + adjustRowsAndCols: function () { + var r, rlen, emptyRows = instance.countEmptyRows(true), emptyCols; + + //should I add empty rows to data source to meet minRows? + rlen = instance.countRows(); + if (rlen < priv.settings.minRows) { + for (r = 0; r < priv.settings.minRows - rlen; r++) { + datamap.createRow(); + } + } + + //should I add empty rows to meet minSpareRows? + if (emptyRows < priv.settings.minSpareRows) { + for (; emptyRows < priv.settings.minSpareRows && instance.countRows() < priv.settings.maxRows; emptyRows++) { + datamap.createRow(); + } + } + + //count currently empty cols + emptyCols = instance.countEmptyCols(true); + + //should I add empty cols to meet minCols? + if (!priv.settings.columns && instance.countCols() < priv.settings.minCols) { + for (; instance.countCols() < priv.settings.minCols; emptyCols++) { + datamap.createCol(); + } + } + + //should I add empty cols to meet minSpareCols? + if (!priv.settings.columns && priv.dataType === 'array' && emptyCols < priv.settings.minSpareCols) { + for (; emptyCols < priv.settings.minSpareCols && instance.countCols() < priv.settings.maxCols; emptyCols++) { + datamap.createCol(); + } + } + + if (priv.settings.enterBeginsEditing) { + for (; (((priv.settings.minRows || priv.settings.minSpareRows) && instance.countRows() > priv.settings.minRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows)); emptyRows--) { + datamap.removeRow(); + } + } + + if (priv.settings.enterBeginsEditing && !priv.settings.columns) { + for (; (((priv.settings.minCols || priv.settings.minSpareCols) && instance.countCols() > priv.settings.minCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols)); emptyCols--) { + datamap.removeCol(); + } + } + + var rowCount = instance.countRows(); + var colCount = instance.countCols(); + + if (rowCount === 0 || colCount === 0) { + selection.deselect(); + } + + if (priv.selStart.exists()) { + var selectionChanged; + var fromRow = priv.selStart.row(); + var fromCol = priv.selStart.col(); + var toRow = priv.selEnd.row(); + var toCol = priv.selEnd.col(); + + //if selection is outside, move selection to last row + if (fromRow > rowCount - 1) { + fromRow = rowCount - 1; + selectionChanged = true; + if (toRow > fromRow) { + toRow = fromRow; + } + } else if (toRow > rowCount - 1) { + toRow = rowCount - 1; + selectionChanged = true; + if (fromRow > toRow) { + fromRow = toRow; + } + } + + //if selection is outside, move selection to last row + if (fromCol > colCount - 1) { + fromCol = colCount - 1; + selectionChanged = true; + if (toCol > fromCol) { + toCol = fromCol; + } + } else if (toCol > colCount - 1) { + toCol = colCount - 1; + selectionChanged = true; + if (fromCol > toCol) { + fromCol = toCol; + } + } + + if (selectionChanged) { + instance.selectCell(fromRow, fromCol, toRow, toCol); + } + } + }, + + /** + * Populate cells at position with 2d array + * @param {Object} start Start selection position + * @param {Array} input 2d array + * @param {Object} [end] End selection position (only for drag-down mode) + * @param {String} [source="populateFromArray"] + * @param {String} [method="overwrite"] + * @return {Object|undefined} ending td in pasted area (only if any cell was changed) + */ + populateFromArray: function (start, input, end, source, method) { + var r, rlen, c, clen, setData = [], current = {}; + rlen = input.length; + if (rlen === 0) { + return false; + } + + var repeatCol + , repeatRow + , cmax + , rmax; + + // insert data with specified pasteMode method + switch (method) { + case 'shift_down' : + repeatCol = end ? end.col - start.col + 1 : 0; + repeatRow = end ? end.row - start.row + 1 : 0; + input = Handsontable.helper.translateRowsToColumns(input); + for (c = 0, clen = input.length, cmax = Math.max(clen, repeatCol); c < cmax; c++) { + if (c < clen) { + for (r = 0, rlen = input[c].length; r < repeatRow - rlen; r++) { + input[c].push(input[c][r % rlen]); + } + input[c].unshift(start.col + c, start.row, 0); + instance.spliceCol.apply(instance, input[c]); + } + else { + input[c % clen][0] = start.col + c; + instance.spliceCol.apply(instance, input[c % clen]); + } + } + break; + + case 'shift_right' : + repeatCol = end ? end.col - start.col + 1 : 0; + repeatRow = end ? end.row - start.row + 1 : 0; + for (r = 0, rlen = input.length, rmax = Math.max(rlen, repeatRow); r < rmax; r++) { + if (r < rlen) { + for (c = 0, clen = input[r].length; c < repeatCol - clen; c++) { + input[r].push(input[r][c % clen]); + } + input[r].unshift(start.row + r, start.col, 0); + instance.spliceRow.apply(instance, input[r]); + } + else { + input[r % rlen][0] = start.row + r; + instance.spliceRow.apply(instance, input[r % rlen]); + } + } + break; + + case 'overwrite' : + default: + // overwrite and other not specified options + current.row = start.row; + current.col = start.col; + for (r = 0; r < rlen; r++) { + if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) { + break; + } + current.col = start.col; + clen = input[r] ? input[r].length : 0; + for (c = 0; c < clen; c++) { + if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) { + break; + } + if (instance.getCellMeta(current.row, current.col).isWritable) { + setData.push([current.row, current.col, input[r][c]]); + } + current.col++; + if (end && c === clen - 1) { + c = -1; + } + } + current.row++; + if (end && r === rlen - 1) { + r = -1; + } + } + instance.setDataAtCell(setData, null, null, source || 'populateFromArray'); + break; + } + + instance.forceFullRender = true; //used when data was changed + grid.adjustRowsAndCols(); + selection.refreshBorders(null, true); + }, + + /** + * Returns the top left (TL) and bottom right (BR) selection coordinates + * @param {Object[]} coordsArr + * @returns {Object} + */ + getCornerCoords: function (coordsArr) { + function mapProp(func, array, prop) { + function getProp(el) { + return el[prop]; + } + + if (Array.prototype.map) { + return func.apply(Math, array.map(getProp)); + } + return func.apply(Math, $.map(array, getProp)); + } + + return { + TL: { + row: mapProp(Math.min, coordsArr, "row"), + col: mapProp(Math.min, coordsArr, "col") + }, + BR: { + row: mapProp(Math.max, coordsArr, "row"), + col: mapProp(Math.max, coordsArr, "col") + } + }; + }, + + /** + * Returns array of td objects given start and end coordinates + */ + getCellsAtCoords: function (start, end) { + var corners = grid.getCornerCoords([start, end]); + var r, c, output = []; + for (r = corners.TL.row; r <= corners.BR.row; r++) { + for (c = corners.TL.col; c <= corners.BR.col; c++) { + output.push(instance.view.getCellAtCoords({ + row: r, + col: c + })); + } + } + return output; + } + }; + + this.selection = selection = { //this public assignment is only temporary + inProgress: false, + + /** + * Sets inProgress to true. This enables onSelectionEnd and onSelectionEndByProp to function as desired + */ + begin: function () { + instance.selection.inProgress = true; + }, + + /** + * Sets inProgress to false. Triggers onSelectionEnd and onSelectionEndByProp + */ + finish: function () { + var sel = instance.getSelected(); + instance.PluginHooks.run("afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]); + instance.PluginHooks.run("afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3])); + instance.selection.inProgress = false; + }, + + isInProgress: function () { + return instance.selection.inProgress; + }, + + /** + * Starts selection range on given td object + * @param {Object} coords + */ + setRangeStart: function (coords) { + priv.selStart.coords(coords); + selection.setRangeEnd(coords); + }, + + /** + * Ends selection range on given td object + * @param {Object} coords + * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to range end + */ + setRangeEnd: function (coords, scrollToCell) { + instance.selection.begin(); + + priv.selEnd.coords(coords); + if (!priv.settings.multiSelect) { + priv.selStart.coords(coords); + } + + //set up current selection + instance.view.wt.selections.current.clear(); + instance.view.wt.selections.current.add(priv.selStart.arr()); + + //set up area selection + instance.view.wt.selections.area.clear(); + if (selection.isMultiple()) { + instance.view.wt.selections.area.add(priv.selStart.arr()); + instance.view.wt.selections.area.add(priv.selEnd.arr()); + } + + //set up highlight + if (priv.settings.currentRowClassName || priv.settings.currentColClassName) { + instance.view.wt.selections.highlight.clear(); + instance.view.wt.selections.highlight.add(priv.selStart.arr()); + instance.view.wt.selections.highlight.add(priv.selEnd.arr()); + } + + //trigger handlers + instance.PluginHooks.run("afterSelection", priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col()); + instance.PluginHooks.run("afterSelectionByProp", priv.selStart.row(), datamap.colToProp(priv.selStart.col()), priv.selEnd.row(), datamap.colToProp(priv.selEnd.col())); + + if (scrollToCell !== false) { + instance.view.scrollViewport(coords); + + instance.view.wt.draw(true); //these two lines are needed to fix scrolling viewport when cell dimensions are significantly bigger than assumed by Walkontable + instance.view.scrollViewport(coords); + } + selection.refreshBorders(); + }, + + /** + * Destroys editor, redraws borders around cells, prepares editor + * @param {Boolean} revertOriginal + * @param {Boolean} keepEditor + */ + refreshBorders: function (revertOriginal, keepEditor) { + if (!keepEditor) { + editproxy.destroy(revertOriginal); + } + instance.view.render(); + if (selection.isSelected() && !keepEditor) { + editproxy.prepare(); + } + }, + + /** + * Returns information if we have a multiselection + * @return {Boolean} + */ + isMultiple: function () { + return !(priv.selEnd.col() === priv.selStart.col() && priv.selEnd.row() === priv.selStart.row()); + }, + + /** + * Selects cell relative to current cell (if possible) + */ + transformStart: function (rowDelta, colDelta, force) { + if (priv.selStart.row() + rowDelta > instance.countRows() - 1) { + if (force && priv.settings.minSpareRows > 0) { + instance.alter("insert_row", instance.countRows()); + } + else if (priv.settings.autoWrapCol && priv.selStart.col() + colDelta < instance.countCols() - 1) { + rowDelta = 1 - instance.countRows(); + colDelta = 1; + } + } + else if (priv.settings.autoWrapCol && priv.selStart.row() + rowDelta < 0 && priv.selStart.col() + colDelta >= 0) { + rowDelta = instance.countRows() - 1; + colDelta = -1; + } + if (priv.selStart.col() + colDelta > instance.countCols() - 1) { + if (force && priv.settings.minSpareCols > 0) { + instance.alter("insert_col", instance.countCols()); + } + else if (priv.settings.autoWrapRow && priv.selStart.row() + rowDelta < instance.countRows() - 1) { + rowDelta = 1; + colDelta = 1 - instance.countCols(); + } + } + else if (priv.settings.autoWrapRow && priv.selStart.col() + colDelta < 0 && priv.selStart.row() + rowDelta >= 0) { + rowDelta = -1; + colDelta = instance.countCols() - 1; + } + + var totalRows = instance.countRows(); + var totalCols = instance.countCols(); + var coords = { + row: (priv.selStart.row() + rowDelta), + col: priv.selStart.col() + colDelta + }; + + if (coords.row < 0) { + coords.row = 0; + } + else if (coords.row > 0 && coords.row >= totalRows) { + coords.row = totalRows - 1; + } + + if (coords.col < 0) { + coords.col = 0; + } + else if (coords.col > 0 && coords.col >= totalCols) { + coords.col = totalCols - 1; + } + + selection.setRangeStart(coords); + }, + + /** + * Sets selection end cell relative to current selection end cell (if possible) + */ + transformEnd: function (rowDelta, colDelta) { + if (priv.selEnd.exists()) { + var totalRows = instance.countRows(); + var totalCols = instance.countCols(); + var coords = { + row: priv.selEnd.row() + rowDelta, + col: priv.selEnd.col() + colDelta + }; + + if (coords.row < 0) { + coords.row = 0; + } + else if (coords.row > 0 && coords.row >= totalRows) { + coords.row = totalRows - 1; + } + + if (coords.col < 0) { + coords.col = 0; + } + else if (coords.col > 0 && coords.col >= totalCols) { + coords.col = totalCols - 1; + } + + selection.setRangeEnd(coords); + } + }, + + /** + * Returns true if currently there is a selection on screen, false otherwise + * @return {Boolean} + */ + isSelected: function () { + return priv.selEnd.exists(); + }, + + /** + * Returns true if coords is within current selection coords + * @return {Boolean} + */ + inInSelection: function (coords) { + if (!selection.isSelected()) { + return false; + } + var sel = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]); + return (sel.TL.row <= coords.row && sel.BR.row >= coords.row && sel.TL.col <= coords.col && sel.BR.col >= coords.col); + }, + + /** + * Deselects all selected cells + */ + deselect: function () { + if (!selection.isSelected()) { + return; + } + instance.selection.inProgress = false; //needed by HT inception + priv.selEnd = new Handsontable.SelectionPoint(); //create new empty point to remove the existing one + instance.view.wt.selections.current.clear(); + instance.view.wt.selections.area.clear(); + editproxy.destroy(); + selection.refreshBorders(); + instance.PluginHooks.run('afterDeselect'); + }, + + /** + * Select all cells + */ + selectAll: function () { + if (!priv.settings.multiSelect) { + return; + } + selection.setRangeStart({ + row: 0, + col: 0 + }); + selection.setRangeEnd({ + row: instance.countRows() - 1, + col: instance.countCols() - 1 + }, false); + }, + + /** + * Deletes data from selected cells + */ + empty: function () { + if (!selection.isSelected()) { + return; + } + var corners = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]); + var r, c, changes = []; + for (r = corners.TL.row; r <= corners.BR.row; r++) { + for (c = corners.TL.col; c <= corners.BR.col; c++) { + if (instance.getCellMeta(r, c).isWritable) { + changes.push([r, c, '']); + } + } + } + instance.setDataAtCell(changes); + } + }; + + this.autofill = autofill = { //this public assignment is only temporary + handle: null, + + /** + * Create fill handle and fill border objects + */ + init: function () { + if (!autofill.handle) { + autofill.handle = {}; + } + else { + autofill.handle.disabled = false; + } + }, + + /** + * Hide fill handle and fill border permanently + */ + disable: function () { + autofill.handle.disabled = true; + }, + + /** + * Selects cells down to the last row in the left column, then fills down to that cell + */ + selectAdjacent: function () { + var select, data, r, maxR, c; + + if (selection.isMultiple()) { + select = instance.view.wt.selections.area.getCorners(); + } + else { + select = instance.view.wt.selections.current.getCorners(); + } + + data = datamap.getAll(); + rows : for (r = select[2] + 1; r < instance.countRows(); r++) { + for (c = select[1]; c <= select[3]; c++) { + if (data[r][c]) { + break rows; + } + } + if (!!data[r][select[1] - 1] || !!data[r][select[3] + 1]) { + maxR = r; + } + } + if (maxR) { + instance.view.wt.selections.fill.clear(); + instance.view.wt.selections.fill.add([select[0], select[1]]); + instance.view.wt.selections.fill.add([maxR, select[3]]); + autofill.apply(); + } + }, + + /** + * Apply fill values to the area in fill border, omitting the selection border + */ + apply: function () { + var drag, select, start, end, _data; + + autofill.handle.isDragged = 0; + + drag = instance.view.wt.selections.fill.getCorners(); + if (!drag) { + return; + } + + instance.view.wt.selections.fill.clear(); + + if (selection.isMultiple()) { + select = instance.view.wt.selections.area.getCorners(); + } + else { + select = instance.view.wt.selections.current.getCorners(); + } + + if (drag[0] === select[0] && drag[1] < select[1]) { + start = { + row: drag[0], + col: drag[1] + }; + end = { + row: drag[2], + col: select[1] - 1 + }; + } + else if (drag[0] === select[0] && drag[3] > select[3]) { + start = { + row: drag[0], + col: select[3] + 1 + }; + end = { + row: drag[2], + col: drag[3] + }; + } + else if (drag[0] < select[0] && drag[1] === select[1]) { + start = { + row: drag[0], + col: drag[1] + }; + end = { + row: select[0] - 1, + col: drag[3] + }; + } + else if (drag[2] > select[2] && drag[1] === select[1]) { + start = { + row: select[2] + 1, + col: drag[1] + }; + end = { + row: drag[2], + col: drag[3] + }; + } + + if (start) { + + _data = SheetClip.parse(datamap.getText(priv.selStart.coords(), priv.selEnd.coords())); + instance.PluginHooks.run('beforeAutofill', start, end, _data); + + grid.populateFromArray(start, _data, end, 'autofill'); + + selection.setRangeStart({row: drag[0], col: drag[1]}); + selection.setRangeEnd({row: drag[2], col: drag[3]}); + } + /*else { + //reset to avoid some range bug + selection.refreshBorders(); + }*/ + }, + + /** + * Show fill border + */ + showBorder: function (coords) { + coords.row = coords[0]; + coords.col = coords[1]; + + var corners = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]); + if (priv.settings.fillHandle !== 'horizontal' && (corners.BR.row < coords.row || corners.TL.row > coords.row)) { + coords = [coords.row, corners.BR.col]; + } + else if (priv.settings.fillHandle !== 'vertical') { + coords = [corners.BR.row, coords.col]; + } + else { + return; //wrong direction + } + + instance.view.wt.selections.fill.clear(); + instance.view.wt.selections.fill.add([priv.selStart.coords().row, priv.selStart.coords().col]); + instance.view.wt.selections.fill.add([priv.selEnd.coords().row, priv.selEnd.coords().col]); + instance.view.wt.selections.fill.add(coords); + instance.view.render(); + } + }; + + editproxy = { //this public assignment is only temporary + /** + * Create input field + */ + init: function () { + function onCut() { + selection.empty(); + } + + function onPaste(str) { + var input = str.replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, '') //remove newline from the start and the end of the input + , inputArray = SheetClip.parse(input) + , coords = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]) + , areaStart = coords.TL + , areaEnd = { + row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row), + col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col) + }; + + instance.PluginHooks.once('afterChange', function (changes, source) { + if (changes.length) { + instance.selectCell(areaStart.row, areaStart.col, areaEnd.row, areaEnd.col); + } + }); + + grid.populateFromArray(areaStart, inputArray, areaEnd, 'paste', priv.settings.pasteMode); + } + + var $body = $(document.body); + + function onKeyDown(event) { + if (priv.settings.beforeOnKeyDown) { // HOT in HOT Plugin + priv.settings.beforeOnKeyDown.call(instance, event); + } + + if ($body.children('.context-menu-list:visible').length) { + return; + } + + if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) { + //when CTRL is pressed, prepare selectable text in textarea + //http://stackoverflow.com/questions/3902635/how-does-one-capture-a-macs-command-key-via-javascript + editproxy.setCopyableText(); + return; + } + + priv.lastKeyCode = event.keyCode; + if (selection.isSelected()) { + var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL) + if (Handsontable.helper.isPrintableChar(event.keyCode) && ctrlDown) { + if (event.keyCode === 65) { //CTRL + A + selection.selectAll(); //select all cells + editproxy.setCopyableText(); + event.preventDefault(); + } + else if (event.keyCode === 89 || (event.shiftKey && event.keyCode === 90)) { //CTRL + Y or CTRL + SHIFT + Z + priv.undoRedo && priv.undoRedo.redo(); + } + else if (event.keyCode === 90) { //CTRL + Z + priv.undoRedo && priv.undoRedo.undo(); + } + return; + } + + var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart; + + instance.PluginHooks.run('beforeKeyDown', event); + if (!event.isImmediatePropagationStopped()) { + + switch (event.keyCode) { + case 38: /* arrow up */ + if (event.shiftKey) { + selection.transformEnd(-1, 0); + } + else { + selection.transformStart(-1, 0); + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; + + case 9: /* tab */ + var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves; + if (event.shiftKey) { + selection.transformStart(-tabMoves.row, -tabMoves.col); //move selection left + } + else { + selection.transformStart(tabMoves.row, tabMoves.col, true); //move selection right (add a new column if needed) + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; + + case 39: /* arrow right */ + if (event.shiftKey) { + selection.transformEnd(0, 1); + } + else { + selection.transformStart(0, 1); + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; + + case 37: /* arrow left */ + if (event.shiftKey) { + selection.transformEnd(0, -1); + } + else { + selection.transformStart(0, -1); + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; + + case 8: /* backspace */ + case 46: /* delete */ + selection.empty(event); + event.preventDefault(); + break; + + case 40: /* arrow down */ + if (event.shiftKey) { + selection.transformEnd(1, 0); //expanding selection down with shift + } + else { + selection.transformStart(1, 0); //move selection down + } + event.preventDefault(); + event.stopPropagation(); //required by HandsontableEditor + break; + + case 113: /* F2 */ + event.preventDefault(); //prevent Opera from opening Go to Page dialog + break; + + case 13: /* return/enter */ + var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves; + + if (event.shiftKey) { + selection.transformStart(-enterMoves.row, -enterMoves.col); //move selection up + } + else { + selection.transformStart(enterMoves.row, enterMoves.col, true); //move selection down (add a new row if needed) + } + + event.preventDefault(); //don't add newline to field + break; + + case 36: /* home */ + if (event.ctrlKey || event.metaKey) { + rangeModifier({row: 0, col: priv.selStart.col()}); + } + else { + rangeModifier({row: priv.selStart.row(), col: 0}); + } + event.preventDefault(); //don't scroll the window + event.stopPropagation(); //required by HandsontableEditor + break; + + case 35: /* end */ + if (event.ctrlKey || event.metaKey) { + rangeModifier({row: instance.countRows() - 1, col: priv.selStart.col()}); + } + else { + rangeModifier({row: priv.selStart.row(), col: instance.countCols() - 1}); + } + event.preventDefault(); //don't scroll the window + event.stopPropagation(); //required by HandsontableEditor + break; + + case 33: /* pg up */ + selection.transformStart(-instance.countVisibleRows(), 0); + instance.view.wt.scrollVertical(-instance.countVisibleRows()); + instance.view.render(); + event.preventDefault(); //don't page up the window + event.stopPropagation(); //required by HandsontableEditor + break; + + case 34: /* pg down */ + selection.transformStart(instance.countVisibleRows(), 0); + instance.view.wt.scrollVertical(instance.countVisibleRows()); + instance.view.render(); + event.preventDefault(); //don't page down the window + event.stopPropagation(); //required by HandsontableEditor + break; + + default: + break; + } + + } + } + } + + instance.copyPaste = new CopyPaste(instance.rootElement[0]); + instance.copyPaste.onCut(onCut); + instance.copyPaste.onPaste(onPaste); + instance.rootElement.on('keydown.handsontable.' + instance.guid, onKeyDown); + }, + + /** + * Destroy current editor, if exists + * @param {Boolean} revertOriginal + */ + destroy: function (revertOriginal) { + if (typeof priv.editorDestroyer === "function") { + var destroyer = priv.editorDestroyer; //this copy is needed, otherwise destroyer can enter an infinite loop + priv.editorDestroyer = null; + destroyer(revertOriginal); + } + }, + + /** + * Prepares copyable text in the invisible textarea + */ + setCopyableText: function () { + var startRow = Math.min(priv.selStart.row(), priv.selEnd.row()); + var startCol = Math.min(priv.selStart.col(), priv.selEnd.col()); + var endRow = Math.max(priv.selStart.row(), priv.selEnd.row()); + var endCol = Math.max(priv.selStart.col(), priv.selEnd.col()); + var finalEndRow = Math.min(endRow, startRow + priv.settings.copyRowsLimit - 1); + var finalEndCol = Math.min(endCol, startCol + priv.settings.copyColsLimit - 1); + + instance.copyPaste.copyable(datamap.getText({row: startRow, col: startCol}, {row: finalEndRow, col: finalEndCol})); + + if (endRow !== finalEndRow || endCol !== finalEndCol) { + instance.PluginHooks.run("afterCopyLimit", endRow - startRow + 1, endCol - startCol + 1, priv.settings.copyRowsLimit, priv.settings.copyColsLimit); + } + }, + + /** + * Prepare text input to be displayed at given grid cell + */ + prepare: function () { + if (!instance.getCellMeta(priv.selStart.row(), priv.selStart.col()).isWritable) { + return; + } + + instance.listen(); + var TD = instance.view.getCellAtCoords(priv.selStart.coords()); + priv.editorDestroyer = instance.view.applyCellTypeMethod('editor', TD, priv.selStart.row(), priv.selStart.col()); + //presumably TD can be removed from here. Cell editor should also listen for changes if editable cell is outside from viewport + } + }; + + this.init = function () { + instance.PluginHooks.run('beforeInit'); + editproxy.init(); + + + this.updateSettings(priv.settings, true); + this.parseSettingsFromDOM(); + this.focusCatcher = new Handsontable.FocusCatcher(this); + this.view = new Handsontable.TableView(this); + + this.forceFullRender = true; //used when data was changed + this.view.render(); + + if (typeof priv.firstRun === 'object') { + instance.PluginHooks.run('afterChange', priv.firstRun[0], priv.firstRun[1]); + priv.firstRun = false; + } + instance.PluginHooks.run('afterInit'); + }; + + function validateChanges(changes, source) { + var validated = $.Deferred(); + var deferreds = []; + + //validate strict autocompletes + var process = function (i) { + var deferred = $.Deferred(); + deferreds.push(deferred); + + var originalVal = changes[i][3]; + var lowercaseVal = typeof originalVal === 'string' ? originalVal.toLowerCase() : null; + + return function (source) { + var found = false; + for (var s = 0, slen = source.length; s < slen; s++) { + if (originalVal === source[s]) { + found = true; //perfect match + break; + } + else if (lowercaseVal === source[s].toLowerCase()) { + changes[i][3] = source[s]; //good match, fix the case + found = true; + break; + } + } + if (!found) { + changes[i] = null; + } + deferred.resolve(); + } + }; + + for (var i = changes.length - 1; i >= 0; i--) { + var cellProperties = instance.getCellMeta(changes[i][0], datamap.propToCol(changes[i][1])); + if (cellProperties.strict && cellProperties.source) { + $.isFunction(cellProperties.source) ? cellProperties.source(changes[i][3], process(i)) : process(i)(cellProperties.source); + } + } + + $.when.apply($, deferreds).then(function () { + for (var i = changes.length - 1; i >= 0; i--) { + if (changes[i] === null) { + changes.splice(i, 1); + } else { + var cellProperties = instance.getCellMeta(changes[i][0], datamap.propToCol(changes[i][1])); + + if (cellProperties.dataType === 'number' && typeof changes[i][3] === 'string') { + if (changes[i][3].length > 0 && /^[0-9\s]*[.]*[0-9]*$/.test(changes[i][3])) { + changes[i][3] = numeral().unformat(changes[i][3] || '0'); //numeral cannot unformat empty string + } + } + } + } + + if (changes.length) { + var result = instance.PluginHooks.execute("beforeChange", changes, source); + if (typeof result === 'function') { + $.when(result).then(function () { + validated.resolve(); + }); + } + else { + if (result === false) { + changes.splice(0, changes.length); //invalidate all changes (remove everything from array) + } + validated.resolve(); + } + } + else { + validated.resolve(); + } + }); + + return $.when(validated); + } + + var fireEvent = function (name, params) { + instance.rootElement.triggerHandler(name, params); + }; + + /** + * Internal function to apply changes. Called after validateChanges + * @param {Array} changes Array in form of [row, prop, oldValue, newValue] + * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback) + */ + function applyChanges(changes, source) { + var i = changes.length - 1; + + if (i < 0) { + return; + } + + for (; 0 <= i; i--) { + if (changes[i] === null) { + changes.splice(i, 1); + continue; + } + + if (priv.settings.minSpareRows) { + while (changes[i][0] > instance.countRows() - 1) { + datamap.createRow(); + } + } + + if (priv.dataType === 'array' && priv.settings.minSpareCols) { + while (datamap.propToCol(changes[i][1]) > instance.countCols() - 1) { + datamap.createCol(); + } + } + + datamap.set(changes[i][0], changes[i][1], changes[i][3]); + } + + instance.forceFullRender = true; //used when data was changed + grid.adjustRowsAndCols(); + selection.refreshBorders(); + instance.PluginHooks.run('afterChange', changes, source || 'edit'); + } + + function setDataInputToArray(row, prop_or_col, value) { + if (typeof row === "object") { //is it an array of changes + return row; + } + else if ($.isPlainObject(value)) { //backwards compatibility + return value; + } + else { + return [ + [row, prop_or_col, value] + ]; + } + } + + /** + * Set data at given cell + * @public + * @param {Number|Array} row or array of changes in format [[row, col, value], ...] + * @param {Number|String} col or source String + * @param {String} value + * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback) + */ + this.setDataAtCell = function (row, col, value, source) { + var input = setDataInputToArray(row, col, value) + , i + , ilen + , changes = [] + , prop; + + for (i = 0, ilen = input.length; i < ilen; i++) { + if (typeof input[i] !== 'object') { + throw new Error('Method `setDataAtCell` accepts row number or changes array of arrays as its first parameter'); + } + if (typeof input[i][1] !== 'number') { + throw new Error('Method `setDataAtCell` accepts row and column number as its parameters. If you want to use object property name, use method `setDataAtRowProp`'); + } + prop = datamap.colToProp(input[i][1]); + changes.push([ + input[i][0], + prop, + datamap.get(input[i][0], prop), + input[i][2] + ]); + } + + if (!source && typeof row === "object") { + source = col; + } + + validateChanges(changes, source).then(function () { + applyChanges(changes, source); + }); + }; + + + /** + * Set data at given row property + * @public + * @param {Number|Array} row or array of changes in format [[row, prop, value], ...] + * @param {String} prop or source String + * @param {String} value + * @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback) + */ + this.setDataAtRowProp = function (row, prop, value, source) { + var input = setDataInputToArray(row, prop, value) + , i + , ilen + , changes = []; + + for (i = 0, ilen = input.length; i < ilen; i++) { + changes.push([ + input[i][0], + input[i][1], + datamap.get(input[i][0], input[i][1]), + input[i][2] + ]); + } + + if (!source && typeof row === "object") { + source = prop; + } + + validateChanges(changes, source).then(function () { + applyChanges(changes, source); + }); + }; + + /** + * Listen to keyboard input + */ + this.listen = function () { + instance.focusCatcher.listen(); + }; + + /** + * Destroys current editor, renders and selects current cell. If revertOriginal != true, edited data is saved + * @param {Boolean} revertOriginal + */ + this.destroyEditor = function (revertOriginal) { + selection.refreshBorders(revertOriginal); + }; + + /** + * Populate cells at position with 2d array + * @param {Number} row Start row + * @param {Number} col Start column + * @param {Array} input 2d array + * @param {Number=} endRow End row (use when you want to cut input when certain row is reached) + * @param {Number=} endCol End column (use when you want to cut input when certain column is reached) + * @param {String=} [source="populateFromArray"] + * @param {String=} [method="overwrite"] + * @return {Object|undefined} ending td in pasted area (only if any cell was changed) + */ + this.populateFromArray = function (row, col, input, endRow, endCol, source, method) { + if (typeof input !== 'object') { + throw new Error("populateFromArray parameter `input` must be an array"); //API changed in 0.9-beta2, let's check if you use it correctly + } + return grid.populateFromArray({row: row, col: col}, input, typeof endRow === 'number' ? {row: endRow, col: endCol} : null, source, method); + }; + + /** + * Adds/removes data from the column + * @param {Number} col Index of column in which do you want to do splice. + * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end + * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed + * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array + */ + this.spliceCol = function (col, index, amount/*, elements... */) { + return datamap.spliceCol.apply(null, arguments); + }; + + /** + * Adds/removes data from the row + * @param {Number} row Index of column in which do you want to do splice. + * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end + * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed + * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array + */ + this.spliceRow = function (row, index, amount/*, elements... */) { + return datamap.spliceRow.apply(null, arguments); + }; + + /** + * Returns the top left (TL) and bottom right (BR) selection coordinates + * @param {Object[]} coordsArr + * @returns {Object} + */ + this.getCornerCoords = function (coordsArr) { + return grid.getCornerCoords(coordsArr); + }; + + /** + * Returns current selection. Returns undefined if there is no selection. + * @public + * @return {Array} [`startRow`, `startCol`, `endRow`, `endCol`] + */ + this.getSelected = function () { //https://github.com/warpech/jquery-handsontable/issues/44 //cjl + if (selection.isSelected()) { + return [priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col()]; + } + }; + + /** + * Parse settings from DOM and CSS + * @public + */ + this.parseSettingsFromDOM = function () { + var overflow = this.rootElement.css('overflow'); + if (overflow === 'scroll' || overflow === 'auto') { + this.rootElement[0].style.overflow = 'visible'; + priv.settingsFromDOM.overflow = overflow; + } + else if (priv.settings.width === void 0 || priv.settings.height === void 0) { + priv.settingsFromDOM.overflow = 'auto'; + } + + if (priv.settings.width === void 0) { + priv.settingsFromDOM.width = this.rootElement.width(); + } + else { + priv.settingsFromDOM.width = void 0; + } + + priv.settingsFromDOM.height = void 0; + if (priv.settings.height === void 0) { + if (priv.settingsFromDOM.overflow === 'scroll' || priv.settingsFromDOM.overflow === 'auto') { + //this needs to read only CSS/inline style and not actual height + //so we need to call getComputedStyle on cloned container + var clone = this.rootElement[0].cloneNode(false); + var parent = this.rootElement[0].parentNode; + if (parent) { + clone.removeAttribute('id'); + parent.appendChild(clone); + var computedHeight = parseInt(window.getComputedStyle(clone, null).getPropertyValue('height'), 10); + if (computedHeight > 0) { + priv.settingsFromDOM.height = computedHeight; + } + parent.removeChild(clone); + } + } + } + }; + + /** + * Render visible data + * @public + */ + this.render = function () { + if (instance.view) { + instance.forceFullRender = true; //used when data was changed + instance.parseSettingsFromDOM(); + selection.refreshBorders(null, true); + } + }; + + /** + * Load data from array + * @public + * @param {Array} data + */ + this.loadData = function (data) { + if (!(data.push && data.splice)) { //check if data is array. Must use duck-type check so Backbone Collections also pass it + throw new Error("loadData only accepts array of objects or array of arrays (" + typeof data + " given)"); + } + + priv.isPopulated = false; + GridSettings.prototype.data = data; + + if (priv.settings.dataSchema instanceof Array || data[0] instanceof Array) { + priv.dataType = 'array'; + } + else if ($.isFunction(priv.settings.dataSchema)) { + priv.dataType = 'function'; + } + else { + priv.dataType = 'object'; + } + + if (data[0]) { + priv.duckDataSchema = datamap.recursiveDuckSchema(data[0]); + } + else { + priv.duckDataSchema = {}; + } + datamap.createMap(); + + grid.adjustRowsAndCols(); + instance.PluginHooks.run('afterLoadData'); + + if (priv.firstRun) { + priv.firstRun = [null, 'loadData']; + } + else { + instance.PluginHooks.run('afterChange', null, 'loadData'); + instance.render(); + } + priv.isPopulated = true; + instance.clearUndo(); + }; + + /** + * Return the current data object (the same that was passed by `data` configuration option or `loadData` method). Optionally you can provide cell range `r`, `c`, `r2`, `c2` to get only a fragment of grid data + * @public + * @param {Number} r (Optional) From row + * @param {Number} c (Optional) From col + * @param {Number} r2 (Optional) To row + * @param {Number} c2 (Optional) To col + * @return {Array|Object} + */ + this.getData = function (r, c, r2, c2) { + if (typeof r === 'undefined') { + return datamap.getAll(); + } + else { + return datamap.getRange({row: r, col: c}, {row: r2, col: c2}); + } + }; + + /** + * Update settings + * @public + */ + this.updateSettings = function (settings, init) { + var i, r, rlen, c, clen; + + if (typeof settings.rows !== "undefined") { + throw new Error("'rows' setting is no longer supported. do you mean startRows, minRows or maxRows?"); + } + if (typeof settings.cols !== "undefined") { + throw new Error("'cols' setting is no longer supported. do you mean startCols, minCols or maxCols?"); + } + + if (typeof settings.undo !== "undefined") { + if (priv.undoRedo && settings.undo === false) { + priv.undoRedo = null; + } + else if (!priv.undoRedo && settings.undo === true) { + priv.undoRedo = new Handsontable.UndoRedo(instance); + } + } + + for (i in settings) { + if (i === 'data') { + continue; //loadData will be triggered later + } + else { + if (instance.PluginHooks.hooks[i] !== void 0 || instance.PluginHooks.legacy[i] !== void 0) { + instance.PluginHooks.add(i, settings[i]); + } + else { + // Update settings + if (!init && settings.hasOwnProperty(i)) { + GridSettings.prototype[i] = settings[i]; + } + + //launch extensions + if (Handsontable.extension[i]) { + priv.extensions[i] = new Handsontable.extension[i](instance, settings[i]); + } + } + } + } + + // Load data or create data map + if (settings.data === void 0 && priv.settings.data === void 0) { + var data = []; + var row; + for (r = 0, rlen = priv.settings.startRows; r < rlen; r++) { + row = []; + for (c = 0, clen = priv.settings.startCols; c < clen; c++) { + row.push(null); + } + data.push(row); + } + instance.loadData(data); //data source created just now + } + else if (settings.data !== void 0) { + instance.loadData(settings.data); //data source given as option + } + else if (settings.columns !== void 0) { + datamap.createMap(); + } + + // Init columns constructors configuration + clen = instance.countCols(); + + if (clen > 0) { + var prop, proto, column; + + for (i = 0; i < clen; i++) { + priv.columnSettings[i] = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts, Handsontable.TextCell); + + // shortcut for prototype + proto = priv.columnSettings[i].prototype; + + // Use settings provided by user + if (GridSettings.prototype.columns) { + column = GridSettings.prototype.columns[i]; + for (prop in column) { + if (column.hasOwnProperty(prop)) { + proto[prop] = column[prop]; + } + } + } + } + } + + if (typeof settings.fillHandle !== "undefined") { + if (autofill.handle && settings.fillHandle === false) { + autofill.disable(); + } + else if (!autofill.handle && settings.fillHandle !== false) { + autofill.init(); + } + } + + grid.adjustRowsAndCols(); + if (instance.view) { + instance.forceFullRender = true; //used when data was changed + selection.refreshBorders(null, true); + } + }; + + /** + * Returns current settings object + * @return {Object} + */ + this.getSettings = function () { + return priv.settings; + }; + + /** + * Returns current settingsFromDOM object + * @return {Object} + */ + this.getSettingsFromDOM = function () { + return priv.settingsFromDOM; + }; + + /** + * Clears grid + * @public + */ + this.clear = function () { + selection.selectAll(); + selection.empty(); + }; + + /** + * Return true if undo can be performed, false otherwise + * @public + */ + this.isUndoAvailable = function () { + return priv.undoRedo && priv.undoRedo.isUndoAvailable(); + }; + + /** + * Return true if redo can be performed, false otherwise + * @public + */ + this.isRedoAvailable = function () { + return priv.undoRedo && priv.undoRedo.isRedoAvailable(); + }; + + /** + * Undo last edit + * @public + */ + this.undo = function () { + priv.undoRedo && priv.undoRedo.undo(); + }; + + /** + * Redo edit (used to reverse an undo) + * @public + */ + this.redo = function () { + priv.undoRedo && priv.undoRedo.redo(); + }; + + /** + * Clears undo history + * @public + */ + this.clearUndo = function () { + priv.undoRedo && priv.undoRedo.clear(); + }; + + /** + * Inserts or removes rows and columns + * @param {String} action See grid.alter for possible values + * @param {Number} index + * @param {Number} amount + * @param {String} [source] Optional. Source of hook runner. + * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows. + * @public + */ + this.alter = function (action, index, amount, source, keepEmptyRows) { + grid.alter(action, index, amount, source, keepEmptyRows); + }; + + /** + * Returns
    element corresponding to params row, col + * @param {Number} row + * @param {Number} col + * @public + * @return {Element} + */ + this.getCell = function (row, col) { + return instance.view.getCellAtCoords({row: row, col: col}); + }; + + /** + * Returns property name associated with column number + * @param {Number} col + * @public + * @return {String} + */ + this.colToProp = function (col) { + return datamap.colToProp(col); + }; + + /** + * Returns column number associated with property name + * @param {String} prop + * @public + * @return {Number} + */ + this.propToCol = function (prop) { + return datamap.propToCol(prop); + }; + + /** + * Return value at `row`, `col` + * @param {Number} row + * @param {Number} col + * @public + * @return value (mixed data type) + */ + this.getDataAtCell = function (row, col) { + return datamap.get(row, datamap.colToProp(col)); + }; + + /** + * Return value at `row`, `prop` + * @param {Number} row + * @param {String} prop + * @public + * @return value (mixed data type) + */ + this.getDataAtRowProp = function (row, prop) { + return datamap.get(row, prop); + }; + + /** + * Return value at `col` + * @param {Number} col + * @public + * @return value (mixed data type) + */ + this.getDataAtCol = function (col) { + return [].concat.apply([], datamap.getRange({row: 0, col: col}, {row: priv.settings.data.length - 1, col: col})); + }; + + /** + * Return value at `prop` + * @param {String} prop + * @public + * @return value (mixed data type) + */ + this.getDataAtProp = function (prop) { + return [].concat.apply([], datamap.getRange({row: 0, col: datamap.propToCol(prop)}, {row: priv.settings.data.length - 1, col: datamap.propToCol(prop)})); + }; + + /** + * Return value at `row` + * @param {Number} row + * @public + * @return value (mixed data type) + */ + this.getDataAtRow = function (row) { + return priv.settings.data[row]; + }; + + /** + * Returns cell meta data object corresponding to params row, col + * @param {Number} row + * @param {Number} col + * @public + * @return {Object} + */ + this.getCellMeta = function (row, col) { + var cellConstructor = function () { + } + , prop = datamap.colToProp(col) + , cellProperties + , type + , i; + + if ("undefined" === typeof priv.columnSettings[col]) { + priv.columnSettings[col] = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts, Handsontable.TextCell); + } + + cellConstructor.prototype = new priv.columnSettings[col](); + + if (priv.settings.cells) { + var settings = priv.settings.cells(row, col, prop) || {} + , key; + + for (key in settings) { + if (settings.hasOwnProperty(key)) { + cellConstructor.prototype[key] = settings[key]; + } + } + + } + + cellProperties = new cellConstructor(); + + instance.PluginHooks.run('beforeGetCellMeta', row, col, cellProperties); + + if (typeof cellProperties.type === 'string' && cellProperties.type !== 'text') { + type = Handsontable.cellTypes[cellProperties.type]; + if (type === void 0) { + throw new Error('You declared cell type "' + cellProperties.type + '" as a string that is not mapped to a known object. Cell type must be an object or a string mapped to an object in Handsontable.cellTypes'); + } + } + else if (typeof cellProperties.type === 'object') { + type = cellProperties.type; + } + + if (type) { + for (i in type) { + if (type.hasOwnProperty(i) && cellProperties[i] === Handsontable.cellTypes.text[i]) { + cellProperties[i] = type[i]; + } + } + } + + cellProperties.isWritable = !cellProperties.readOnly; + instance.PluginHooks.run('afterGetCellMeta', row, col, cellProperties); + + return cellProperties; + }; + + /** + * Return array of row headers (if they are enabled). If param `row` given, return header at given row as string + * @param {Number} row (Optional) + * @return {Array|String} + */ + this.getRowHeader = function (row) { + if (row === void 0) { + var out = []; + for (var i = 0, ilen = instance.countRows(); i < ilen; i++) { + out.push(instance.getRowHeader(i)); + } + return out; + } + else if (Object.prototype.toString.call(priv.settings.rowHeaders) === '[object Array]' && priv.settings.rowHeaders[row] !== void 0) { + return priv.settings.rowHeaders[row]; + } + else if (typeof priv.settings.rowHeaders === 'function') { + return priv.settings.rowHeaders(row); + } + else if (priv.settings.rowHeaders && typeof priv.settings.rowHeaders !== 'string' && typeof priv.settings.rowHeaders !== 'number') { + return row + 1; + } + else { + return priv.settings.rowHeaders; + } + }; + + /** + * Return array of column headers (if they are enabled). If param `col` given, return header at given column as string + * @param {Number} col (Optional) + * @return {Array|String} + */ + this.getColHeader = function (col) { + if (col === void 0) { + var out = []; + for (var i = 0, ilen = instance.countCols(); i < ilen; i++) { + out.push(instance.getColHeader(i)); + } + return out; + } + else { + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); + + if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].title) { + return priv.settings.columns[col].title; + } + else if (Object.prototype.toString.call(priv.settings.colHeaders) === '[object Array]' && priv.settings.colHeaders[col] !== void 0) { + return priv.settings.colHeaders[col]; + } + else if (typeof priv.settings.colHeaders === 'function') { + return priv.settings.colHeaders(col); + } + else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') { + return Handsontable.helper.spreadsheetColumnLabel(col); + } + else { + return priv.settings.colHeaders; + } + } + }; + + /** + * Return column width + * @param {Number} col + * @return {Number} + */ + this.getColWidth = function (col) { + col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col); + var response = {}; + if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].width) { + response.width = priv.settings.columns[col].width; + } + else if (Object.prototype.toString.call(priv.settings.colWidths) === '[object Array]' && priv.settings.colWidths[col] !== void 0) { + response.width = priv.settings.colWidths[col]; + } + else { + response.width = 50; + } + instance.PluginHooks.run('afterGetColWidth', col, response); + return response.width; + }; + + /** + * Return total number of rows in grid + * @return {Number} + */ + this.countRows = function () { + return priv.settings.data.length; + }; + + /** + * Return total number of columns in grid + * @return {Number} + */ + this.countCols = function () { + if (priv.dataType === 'object' || priv.dataType === 'function') { + if (priv.settings.columns && priv.settings.columns.length) { + return priv.settings.columns.length; + } + else { + return priv.colToProp.length; + } + } + else if (priv.dataType === 'array') { + if (priv.settings.columns && priv.settings.columns.length) { + return priv.settings.columns.length; + } + else if (priv.settings.data && priv.settings.data[0] && priv.settings.data[0].length) { + return priv.settings.data[0].length; + } + else { + return 0; + } + } + }; + + /** + * Return index of first visible row + * @return {Number} + */ + this.rowOffset = function () { + return instance.view.wt.getSetting('offsetRow'); + }; + + /** + * Return index of first visible column + * @return {Number} + */ + this.colOffset = function () { + return instance.view.wt.getSetting('offsetColumn'); + }; + + /** + * Return number of visible rows + * @return {Number} + */ + this.countVisibleRows = function () { + return instance.view.wt.wtTable.rowStrategy.countVisible(); + }; + + /** + * Return number of visible columns + * @return {Number} + */ + this.countVisibleCols = function () { + return instance.view.wt.wtTable.columnStrategy.countVisible(); + }; + + /** + * Return number of empty rows + * @return {Boolean} ending If true, will only count empty rows at the end of the data source + */ + this.countEmptyRows = function (ending) { + var i = instance.countRows() - 1 + , empty = 0; + while (i >= 0) { + if (instance.isEmptyRow(i)) { + empty++; + } + else if (ending) { + break; + } + i--; + } + return empty; + }; + + /** + * Return number of empty columns + * @return {Boolean} ending If true, will only count empty columns at the end of the data source row + */ + this.countEmptyCols = function (ending) { + if (instance.countRows() < 1) { + return 0; + } + + var i = instance.countCols() - 1 + , empty = 0; + while (i >= 0) { + if (instance.isEmptyCol(i)) { + empty++; + } + else if (ending) { + break; + } + i--; + } + return empty; + }; + + /** + * Return true if the row at the given index is empty, false otherwise + * @param {Number} r Row index + * @return {Boolean} + */ + this.isEmptyRow = function (r) { + if (priv.settings.isEmptyRow) { + return priv.settings.isEmptyRow.call(this, r); + } + + var val; + for (var c = 0, clen = this.countCols(); c < clen; c++) { + val = this.getDataAtCell(r, c); + if (val !== '' && val !== null && typeof val !== 'undefined') { + return false; + } + } + return true; + }; + + /** + * Return true if the column at the given index is empty, false otherwise + * @param {Number} c Column index + * @return {Boolean} + */ + this.isEmptyCol = function (c) { + if (priv.settings.isEmptyCol) { + return priv.settings.isEmptyCol.call(this, c); + } + + var val; + for (var r = 0, rlen = this.countRows(); r < rlen; r++) { + val = this.getDataAtCell(r, c); + if (val !== '' && val !== null && typeof val !== 'undefined') { + return false; + } + } + return true; + }; + + /** + * Selects cell on grid. Optionally selects range to another cell + * @param {Number} row + * @param {Number} col + * @param {Number} [endRow] + * @param {Number} [endCol] + * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to the selection + * @public + * @return {Boolean} + */ + this.selectCell = function (row, col, endRow, endCol, scrollToCell) { + if (typeof row !== 'number' || row < 0 || row >= instance.countRows()) { + return false; + } + if (typeof col !== 'number' || col < 0 || col >= instance.countCols()) { + return false; + } + if (typeof endRow !== "undefined") { + if (typeof endRow !== 'number' || endRow < 0 || endRow >= instance.countRows()) { + return false; + } + if (typeof endCol !== 'number' || endCol < 0 || endCol >= instance.countCols()) { + return false; + } + } + priv.selStart.coords({row: row, col: col}); + instance.listen(); //needed or otherwise prepare won't focus the cell. selectionSpec tests this (should move focus to selected cell) + if (typeof endRow === "undefined") { + selection.setRangeEnd({row: row, col: col}, scrollToCell); + } + else { + selection.setRangeEnd({row: endRow, col: endCol}, scrollToCell); + } + + instance.selection.finish(); + return true; + }; + + this.selectCellByProp = function (row, prop, endRow, endProp, scrollToCell) { + arguments[1] = datamap.propToCol(arguments[1]); + if (typeof arguments[3] !== "undefined") { + arguments[3] = datamap.propToCol(arguments[3]); + } + return instance.selectCell.apply(instance, arguments); + }; + + /** + * Deselects current sell selection on grid + * @public + */ + this.deselectCell = function () { + selection.deselect(); + }; + + /** + * Remove grid from DOM + * @public + */ + this.destroy = function () { + instance.clearTimeouts(); + if (instance.view) { //in case HT is destroyed before initialization has finished + instance.view.wt.destroy(); + } + instance.rootElement.empty(); + instance.rootElement.removeData('handsontable'); + instance.rootElement.off('.handsontable'); + $(window).off('.' + instance.guid); + $(document.documentElement).off('.' + instance.guid); + instance.PluginHooks.run('afterDestroy'); + }; + + /** + * Return Handsontable instance + * @public + * @return {Object} + */ + this.getInstance = function () { + return instance.rootElement.data("handsontable"); + }; + + (function () { + // Create new instance of plugin hooks + instance.PluginHooks = new Handsontable.PluginHookClass(); + + // Upgrade methods to call of global PluginHooks instance + var _run = instance.PluginHooks.run + , _exe = instance.PluginHooks.execute; + + instance.PluginHooks.run = function (key, p1, p2, p3, p4, p5) { + _run.call(this, instance, key, p1, p2, p3, p4, p5); + Handsontable.PluginHooks.run(instance, key, p1, p2, p3, p4, p5); + }; + + instance.PluginHooks.execute = function (key, p1, p2, p3, p4, p5) { + p1 = _exe.call(this, instance, key, p1, p2, p3, p4, p5); + p1 = Handsontable.PluginHooks.execute(instance, key, p1, p2, p3, p4, p5); + + return p1; + }; + + // Map old API with new methods + instance.addHook = instance.PluginHooks.add; + instance.addHookOnce = instance.PluginHooks.once; + + instance.removeHook = instance.PluginHooks.remove; + + instance.runHooks = instance.PluginHooks.run; + instance.runHooksAndReturn = instance.PluginHooks.execute; + + })(); + + this.timeouts = {}; + + /** + * Sets timeout. Purpose of this method is to clear all known timeouts when `destroy` method is called + * @public + */ + this.registerTimeout = function (key, handle, ms) { + clearTimeout(this.timeouts[key]); + this.timeouts[key] = setTimeout(handle, ms || 0); + }; + + /** + * Clears all known timeouts + * @public + */ + this.clearTimeouts = function () { + for (var key in this.timeouts) { + if (this.timeouts.hasOwnProperty(key)) { + clearTimeout(this.timeouts[key]); + } + } + }; + + /** + * Handsontable version + */ + this.version = '0.9.4'; //inserted by grunt from package.json +}; + +var DefaultSettings = function () { +}; +DefaultSettings.prototype = { + data: void 0, + width: void 0, + height: void 0, + startRows: 5, + startCols: 5, + minRows: 0, + minCols: 0, + maxRows: Infinity, + maxCols: Infinity, + minSpareRows: 0, + minSpareCols: 0, + multiSelect: true, + fillHandle: true, + undo: true, + outsideClickDeselects: true, + enterBeginsEditing: true, + enterMoves: {row: 1, col: 0}, + tabMoves: {row: 0, col: 1}, + autoWrapRow: false, + autoWrapCol: false, + copyRowsLimit: 1000, + copyColsLimit: 1000, + pasteMode: 'overwrite', + currentRowClassName: void 0, + currentColClassName: void 0, + stretchH: 'hybrid', + isEmptyRow: void 0, + isEmptyCol: void 0, + observeDOMVisibility: true +}; + +$.fn.handsontable = function (action) { + var i + , ilen + , args + , output + , userSettings + , $this = this.first() // Use only first element from list + , instance = $this.data('handsontable'); + + // Init case + if (typeof action !== 'string') { + userSettings = action || {}; + if (instance) { + instance.updateSettings(userSettings); + } + else { + instance = new Handsontable.Core($this, userSettings); + $this.data('handsontable', instance); + instance.init(); + } + + return $this; + } + // Action case + else { + args = []; + if (arguments.length > 1) { + for (i = 1, ilen = arguments.length; i < ilen; i++) { + args.push(arguments[i]); + } + } + + if (instance) { + if (typeof instance[action] !== 'undefined') { + output = instance[action].apply(instance, args); + } + else { + throw new Error('Handsontable do not provide action: ' + action); + } + } + + return output; + } +}; +Handsontable.FocusCatcher = function (instance) { + this.el = document.createElement('DIV'); + this.el.style.position = 'fixed'; + this.el.style.top = '0'; + this.el.style.left = '0'; + this.el.style.width = '1px'; + this.el.style.height = '1px'; + this.el.setAttribute('tabindex', 10000); //http://www.barryvan.com.au/2009/01/onfocus-and-onblur-for-divs-in-fx/; 32767 is max tabindex for IE7,8 + instance.rootElement.append(this.el); + + this.$el = $(this.el); +}; + +Handsontable.FocusCatcher.prototype.listen = function () { + this.el.focus(); +}; +/** + * Handsontable TableView constructor + * @param {Object} instance + */ +Handsontable.TableView = function (instance) { + var that = this + , $window = $(window) + , $documentElement = $(document.documentElement); + + this.instance = instance; + this.settings = instance.getSettings(); + this.settingsFromDOM = instance.getSettingsFromDOM(); + + instance.rootElement.data('originalStyle', instance.rootElement.attr('style')); //needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions + instance.rootElement.addClass('handsontable'); + + var table = document.createElement('TABLE'); + table.className = 'htCore'; + table.appendChild(document.createElement('THEAD')); + table.appendChild(document.createElement('TBODY')); + + instance.$table = $(table); + instance.rootElement.prepend(instance.$table); + + $documentElement.on('keyup.' + instance.guid, function (event) { + if (instance.selection.isInProgress() && !event.shiftKey) { + instance.selection.finish(); + } + }); + + var isMouseDown + , dragInterval; + + $documentElement.on('mouseup.' + instance.guid, function (event) { + if (instance.selection.isInProgress() && event.which === 1) { //is left mouse button + instance.selection.finish(); + } + + isMouseDown = false; + clearInterval(dragInterval); + dragInterval = null; + + if (instance.autofill.handle && instance.autofill.handle.isDragged) { + if (instance.autofill.handle.isDragged > 1) { + instance.autofill.apply(); + } + instance.autofill.handle.isDragged = 0; + } + }); + + $documentElement.on('mousedown.' + instance.guid, function (event) { + var next = event.target; + + if (next !== that.wt.wtTable.spreader) { //immediate click on "spreader" means click on the right side of vertical scrollbar + while (next !== document.documentElement) { + //X-HANDSONTABLE is the tag name in Web Components version of HOT. Removal of this breaks cell selection + if(next === null) { + return; //click on something that was a row but now is detached (possibly because your click triggered a rerender) + } + if (next === instance.rootElement[0] || next.nodeName === 'X-HANDSONTABLE' || next.id === 'context-menu-layer' || $(next).is('.context-menu-list') || $(next).is('.typeahead li')) { + return; //click inside container + } + next = next.parentNode; + } + } + + if (that.settings.outsideClickDeselects) { + instance.deselectCell(); + } + else { + instance.destroyEditor(); + } + }); + + instance.$table.on('selectstart', function (event) { + //https://github.com/warpech/jquery-handsontable/issues/160 + //selectstart is IE only event. Prevent text from being selected when performing drag down in IE8 + event.preventDefault(); + }); + + instance.$table.on('mouseenter', function () { + if (dragInterval) { //if dragInterval was set (that means mouse was really outside of table, not over an element that is outside of in DOM + clearInterval(dragInterval); + dragInterval = null; + } + }); + + instance.$table.on('mouseleave', function (event) { + if (!(isMouseDown || (instance.autofill.handle && instance.autofill.handle.isDragged))) { + return; + } + + var tolerance = 1 //this is needed because width() and height() contains stuff like cell borders + , offset = that.wt.wtDom.offset(table) + , offsetTop = offset.top + tolerance + , offsetLeft = offset.left + tolerance + , width = that.containerWidth - that.wt.getSetting('scrollbarWidth') - 2 * tolerance + , height = that.containerHeight - that.wt.getSetting('scrollbarHeight') - 2 * tolerance + , method + , row = 0 + , col = 0 + , dragFn; + + if (event.pageY < offsetTop) { //top edge crossed + row = -1; + method = 'scrollVertical'; + } + else if (event.pageY >= offsetTop + height) { //bottom edge crossed + row = 1; + method = 'scrollVertical'; + } + else if (event.pageX < offsetLeft) { //left edge crossed + col = -1; + method = 'scrollHorizontal'; + } + else if (event.pageX >= offsetLeft + width) { //right edge crossed + col = 1; + method = 'scrollHorizontal'; + } + + if (method) { + dragFn = function () { + if (isMouseDown || (instance.autofill.handle && instance.autofill.handle.isDragged)) { + //instance.selection.transformEnd(row, col); + that.wt[method](row + col).draw(); + } + }; + dragFn(); + dragInterval = setInterval(dragFn, 100); + } + }); + + var clearTextSelection = function () { + //http://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript + if (window.getSelection) { + if (window.getSelection().empty) { // Chrome + window.getSelection().empty(); + } else if (window.getSelection().removeAllRanges) { // Firefox + window.getSelection().removeAllRanges(); + } + } else if (document.selection) { // IE? + document.selection.empty(); + } + }; + + var walkontableConfig = { + table: table, + stretchH: this.settings.stretchH, + data: instance.getDataAtCell, + totalRows: instance.countRows, + totalColumns: instance.countCols, + scrollbarModelV: this.settings.scrollbarModelV, + scrollbarModelH: this.settings.scrollbarModelH, + offsetRow: 0, + offsetColumn: 0, + width: this.getWidth(), + height: this.getHeight(), + fixedColumnsLeft: this.settings.fixedColumnsLeft, + fixedRowsTop: this.settings.fixedRowsTop, + rowHeaders: this.settings.rowHeaders ? [function (index, TH) { + that.appendRowHeader(index, TH); + }] : [], + columnHeaders: this.settings.colHeaders ? [function (index, TH) { + that.appendColHeader(index, TH); + }] : [], + columnWidth: instance.getColWidth, + cellRenderer: function (row, column, TD) { + that.applyCellTypeMethod('renderer', TD, row, column); + }, + selections: { + current: { + className: 'current', + border: { + width: 2, + color: '#5292F7', + style: 'solid', + cornerVisible: function () { + return that.settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple() + } + } + }, + area: { + className: 'area', + border: { + width: 1, + color: '#89AFF9', + style: 'solid', + cornerVisible: function () { + return that.settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple() + } + } + }, + highlight: { + highlightRowClassName: that.settings.currentRowClassName, + highlightColumnClassName: that.settings.currentColClassName + }, + fill: { + className: 'fill', + border: { + width: 1, + color: 'red', + style: 'solid' + } + } + }, + onCellMouseDown: function (event, coords, TD) { + isMouseDown = true; + var coordsObj = {row: coords[0], col: coords[1]}; + if (event.button === 2 && instance.selection.inInSelection(coordsObj)) { //right mouse button + //do nothing + } + else if (event.shiftKey) { + instance.selection.setRangeEnd(coordsObj); + } + else { + instance.selection.setRangeStart(coordsObj); + } + event.preventDefault(); + clearTextSelection(); + + if (that.settings.afterOnCellMouseDown) { + that.settings.afterOnCellMouseDown.call(instance, event, coords, TD); + } + setTimeout(function () { + instance.listen(); //fix IE7-8 bug that sets focus to TD after mousedown + }); + }, + onCellMouseOver: function (event, coords, TD) { + var coordsObj = {row: coords[0], col: coords[1]}; + if (isMouseDown) { + instance.selection.setRangeEnd(coordsObj); + } + else if (instance.autofill.handle && instance.autofill.handle.isDragged) { + instance.autofill.handle.isDragged++; + instance.autofill.showBorder(coords); + } + }, + onCellCornerMouseDown: function (event) { + instance.autofill.handle.isDragged = 1; + event.preventDefault(); + }, + onCellCornerDblClick: function () { + instance.autofill.selectAdjacent(); + }, + beforeDraw: function (force) { + that.beforeRender(force); + } + }; + + instance.PluginHooks.run('beforeInitWalkontable', walkontableConfig); + + this.wt = new Walkontable(walkontableConfig); + + $window.on('resize.' + instance.guid, function () { + instance.registerTimeout('resizeTimeout', function () { + instance.parseSettingsFromDOM(); + var newWidth = that.getWidth(); + var newHeight = that.getHeight(); + if (walkontableConfig.width !== newWidth || walkontableConfig.height !== newHeight) { + instance.forceFullRender = true; + that.render(); + walkontableConfig.width = newWidth; + walkontableConfig.height = newHeight; + } + }, 60); + }); + + $(that.wt.wtTable.spreader).on('mousedown.handsontable, contextmenu.handsontable', function (event) { + if (event.target === that.wt.wtTable.spreader && event.which === 3) { //right mouse button exactly on spreader means right clickon the right hand side of vertical scrollbar + event.stopPropagation(); + } + }); + + $documentElement.on('click.' + instance.guid, function () { + if (that.settings.observeDOMVisibility) { + if (that.wt.drawInterrupted) { + that.instance.forceFullRender = true; + that.render(); + } + } + }); +}; + +Handsontable.TableView.prototype.isCellEdited = function () { + return (this.instance.textEditor && this.instance.textEditor.isCellEdited) || (this.instance.autocompleteEditor && this.instance.autocompleteEditor.isCellEdited) || (this.instance.handsontableEditor && this.instance.handsontableEditor.isCellEdited); +}; + +Handsontable.TableView.prototype.getWidth = function () { + var val = this.settings.width !== void 0 ? this.settings.width : this.settingsFromDOM.width; + return typeof val === 'function' ? val() : val; +}; + +Handsontable.TableView.prototype.getHeight = function () { + var val = this.settings.height !== void 0 ? this.settings.height : this.settingsFromDOM.height; + return typeof val === 'function' ? val() : val; +}; + +Handsontable.TableView.prototype.beforeRender = function (force) { + if (force) { + this.instance.PluginHooks.run('beforeRender'); + this.wt.update('width', this.getWidth()); + this.wt.update('height', this.getHeight()); + } +}; + +Handsontable.TableView.prototype.render = function () { + this.wt.draw(!this.instance.forceFullRender); + this.instance.rootElement.triggerHandler('render.handsontable'); + if (this.instance.forceFullRender) { + this.instance.PluginHooks.run('afterRender'); + } + this.instance.forceFullRender = false; +}; + +Handsontable.TableView.prototype.applyCellTypeMethod = function (methodName, td, row, col) { + var prop = this.instance.colToProp(col) + , cellProperties = this.instance.getCellMeta(row, col) + , method = Handsontable.helper.getCellMethod(methodName, cellProperties[methodName]); //methodName is 'renderer' or 'editor' + + return method(this.instance, td, row, col, prop, this.instance.getDataAtRowProp(row, prop), cellProperties); +}; + +/** + * Returns td object given coordinates + */ +Handsontable.TableView.prototype.getCellAtCoords = function (coords) { + var td = this.wt.wtTable.getCell([coords.row, coords.col]); + if (td < 0) { //there was an exit code (cell is out of bounds) + return null; + } + else { + return td; + } +}; + +/** + * Scroll viewport to selection + * @param coords + */ +Handsontable.TableView.prototype.scrollViewport = function (coords) { + this.wt.scrollViewport([coords.row, coords.col]); +}; + +/** + * Append row header to a TH element + * @param row + * @param TH + */ +Handsontable.TableView.prototype.appendRowHeader = function (row, TH) { + if (row > -1) { + this.wt.wtDom.avoidInnerHTML(TH, this.instance.getRowHeader(row)); + } + else { + this.wt.wtDom.empty(TH); + } +}; + +/** + * Append column header to a TH element + * @param col + * @param TH + */ +Handsontable.TableView.prototype.appendColHeader = function (col, TH) { + var DIV = document.createElement('DIV') + , SPAN = document.createElement('SPAN'); + + DIV.className = 'relative'; + SPAN.className = 'colHeader'; + + this.wt.wtDom.avoidInnerHTML(SPAN, this.instance.getColHeader(col)); + DIV.appendChild(SPAN); + + while (TH.firstChild) { + TH.removeChild(TH.firstChild); //empty TH node + } + TH.appendChild(DIV); + this.instance.PluginHooks.run('afterGetColHeader', col, TH); +}; +/** + * Returns true if keyCode represents a printable character + * @param {Number} keyCode + * @return {Boolean} + */ +Handsontable.helper.isPrintableChar = function (keyCode) { + return ((keyCode == 32) || //space + (keyCode >= 48 && keyCode <= 57) || //0-9 + (keyCode >= 96 && keyCode <= 111) || //numpad + (keyCode >= 186 && keyCode <= 192) || //;=,-./` + (keyCode >= 219 && keyCode <= 222) || //[]{}\|"' + keyCode >= 226 || //special chars (229 for Asian chars) + (keyCode >= 65 && keyCode <= 90)); //a-z +}; + +/** + * Converts a value to string + * @param value + * @return {String} + */ +Handsontable.helper.stringify = function (value) { + switch (typeof value) { + case 'string': + case 'number': + return value + ''; + break; + + case 'object': + if (value === null) { + return ''; + } + else { + return value.toString(); + } + break; + + case 'undefined': + return ''; + break; + + default: + return value.toString(); + } +}; + +/** + * Generates spreadsheet-like column names: A, B, C, ..., Z, AA, AB, etc + * @param index + * @returns {String} + */ +Handsontable.helper.spreadsheetColumnLabel = function (index) { + var dividend = index + 1; + var columnLabel = ''; + var modulo; + while (dividend > 0) { + modulo = (dividend - 1) % 26; + columnLabel = String.fromCharCode(65 + modulo) + columnLabel; + dividend = parseInt((dividend - modulo) / 26, 10); + } + return columnLabel; +}; + +/** + * Checks if value of n is a numeric one + * http://jsperf.com/isnan-vs-isnumeric/4 + * @param n + * @returns {boolean} + */ +Handsontable.helper.isNumeric = function (n) { + var t = typeof n; + return t == 'number' ? !isNaN(n) && isFinite(n) : + t == 'string' ? !n.length ? false : + n.length == 1 ? /\d/.test(n) : + /^\s*[+-]?\s*(?:(?:\d+(?:\.\d+)?(?:e[+-]?\d+)?)|(?:0x[a-f\d]+))\s*$/i.test(n) : + t == 'object' ? !!n && typeof n.valueOf() == "number" && !(n instanceof Date) : false; +}; + +/** + * Checks if child is a descendant of given parent node + * http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another + * @param parent + * @param child + * @returns {boolean} + */ +Handsontable.helper.isDescendant = function (parent, child) { + var node = child.parentNode; + while (node != null) { + if (node == parent) { + return true; + } + node = node.parentNode; + } + return false; +}; + +/** + * Generates a random hex string. Used as namespace for Handsontable instance events. + * @return {String} - 16 character random string: "92b1bfc74ec4" + */ +Handsontable.helper.randomString = function () { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + + return s4() + s4() + s4() + s4(); +}; + +/** + * Inherit without without calling parent constructor, and setting `Child.prototype.constructor` to `Child` instead of `Parent`. + * Creates temporary dummy function to call it as constructor. + * Described in ticket: https://github.com/warpech/jquery-handsontable/pull/516 + * @param {Object} Child child class + * @param {Object} Parent parent class + * @return {Object} extended Child + */ +Handsontable.helper.inherit = function (Child, Parent) { + function Bridge() { + } + + Bridge.prototype = Parent.prototype; + Child.prototype = new Bridge(); + Child.prototype.constructor = Child; + return Child; +}; + +/** + * Perform shallow extend of a target object with extension's own properties + * @param {Object} target An object that will receive the new properties + * @param {Object} extension An object containing additional properties to merge into the target + */ +Handsontable.helper.extend = function (target, extension) { + for (var i in extension) { + if (extension.hasOwnProperty(i)) { + target[i] = extension[i]; + } + } +}; + +/** + * Factory for columns constructors. + * @param {Object} GridSettings + * @param {Array} conflictList + * @param {Object} defaultCell + * @return {Object} ColumnSettings + */ +Handsontable.helper.columnFactory = function (GridSettings, conflictList, defaultCell) { + var i = 0, len = conflictList.length, ColumnSettings = function () { + }; + + // Inherit prototype from grid settings + ColumnSettings.prototype = new GridSettings(); + + // Clear conflict settings + for (; i < len; i++) { + ColumnSettings.prototype[conflictList[i]] = void 0; + } + + // Inherit settings from default (text) cell + for (i in defaultCell) { + if (defaultCell.hasOwnProperty(i)) { + ColumnSettings.prototype[i] = defaultCell[i]; + } + } + + return ColumnSettings; +}; + +Handsontable.helper.translateRowsToColumns = function (input) { + var i + , ilen + , j + , jlen + , output = [] + , olen = 0; + + for (i = 0, ilen = input.length; i < ilen; i++) { + for (j = 0, jlen = input[i].length; j < jlen; j++) { + if (j == olen) { + output.push([]); + olen++; + } + output[j].push(input[i][j]) + } + } + return output; +}; + +Handsontable.helper.to2dArray = function (arr) { + var i = 0 + , ilen = arr.length; + while (i < ilen) { + arr[i] = [arr[i]]; + i++; + } +}; + +Handsontable.helper.extendArray = function (arr, extension) { + var i = 0 + , ilen = extension.length; + while (i < ilen) { + arr.push(extension[i]); + i++; + } +}; + +/** + * Returns cell renderer or editor function directly or through lookup map + */ +Handsontable.helper.getCellMethod = function (methodName, methodFunction) { + if (typeof methodFunction === 'string') { + var result = Handsontable.cellLookup[methodName][methodFunction]; + if (result === void 0) { + throw new Error('You declared cell ' + methodName + ' "' + methodFunction + '" as a string that is not mapped to a known function. Cell ' + methodName + ' must be a function or a string mapped to a function in Handsontable.cellLookup.' + methodName + ' lookup object'); + } + return result; + } + else { + return methodFunction; + } +}; +/** + * Handsontable UndoRedo class + */ +Handsontable.UndoRedo = function (instance) { + var that = this; + this.instance = instance; + this.clear(); + Handsontable.PluginHooks.add("afterChange", function (changes, origin) { + if (origin !== 'undo' && origin !== 'redo') { + that.add(changes, origin); + } + }); +}; + +/** + * Undo operation from current revision + */ +Handsontable.UndoRedo.prototype.undo = function () { + var i, ilen; + if (this.isUndoAvailable()) { + var setData = $.extend(true, [], this.data[this.rev]); + for (i = 0, ilen = setData.length; i < ilen; i++) { + setData[i].splice(3, 1); + } + this.instance.setDataAtRowProp(setData, null, null, 'undo'); + this.rev--; + } +}; + +/** + * Redo operation from current revision + */ +Handsontable.UndoRedo.prototype.redo = function () { + var i, ilen; + if (this.isRedoAvailable()) { + this.rev++; + var setData = $.extend(true, [], this.data[this.rev]); + for (i = 0, ilen = setData.length; i < ilen; i++) { + setData[i].splice(2, 1); + } + this.instance.setDataAtRowProp(setData, null, null, 'redo'); + } +}; + +/** + * Returns true if undo point is available + * @return {Boolean} + */ +Handsontable.UndoRedo.prototype.isUndoAvailable = function () { + return (this.rev >= 0); +}; + +/** + * Returns true if redo point is available + * @return {Boolean} + */ +Handsontable.UndoRedo.prototype.isRedoAvailable = function () { + return (this.rev < this.data.length - 1); +}; + +/** + * Add new history poins + * @param changes + */ +Handsontable.UndoRedo.prototype.add = function (changes, source) { + this.rev++; + this.data.splice(this.rev); //if we are in point abcdef(g)hijk in history, remove everything after (g) + this.data.push(changes); +}; + +/** + * Clears undo history + */ +Handsontable.UndoRedo.prototype.clear = function () { + this.data = []; + this.rev = -1; +}; +Handsontable.SelectionPoint = function () { + this._row = null; //private use intended + this._col = null; +}; + +Handsontable.SelectionPoint.prototype.exists = function () { + return (this._row !== null); +}; + +Handsontable.SelectionPoint.prototype.row = function (val) { + if (val !== void 0) { + this._row = val; + } + return this._row; +}; + +Handsontable.SelectionPoint.prototype.col = function (val) { + if (val !== void 0) { + this._col = val; + } + return this._col; +}; + +Handsontable.SelectionPoint.prototype.coords = function (coords) { + if (coords !== void 0) { + this._row = coords.row; + this._col = coords.col; + } + return { + row: this._row, + col: this._col + } +}; + +Handsontable.SelectionPoint.prototype.arr = function (arr) { + if (arr !== void 0) { + this._row = arr[0]; + this._col = arr[1]; + } + return [this._row, this._col] +}; +/** + * Default text renderer + * @param {Object} instance Handsontable instance + * @param {Element} TD Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.TextRenderer = function (instance, TD, row, col, prop, value, cellProperties) { + var escaped = Handsontable.helper.stringify(value); + if (escaped.match(/\n/)) { + escaped = escaped.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); //escape html special chars + TD.innerHTML = escaped.replace(/\n/g, '
    '); + } + else { + instance.view.wt.wtDom.empty(TD); //TODO identify under what circumstances this line can be removed + TD.appendChild(document.createTextNode(escaped)); + //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips + } + if (cellProperties.readOnly) { + TD.className = 'htDimmed'; + } +}; +/** + * Autocomplete renderer + * @param {Object} instance Handsontable instance + * @param {Element} TD Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.AutocompleteRenderer = function (instance, TD, row, col, prop, value, cellProperties) { + var TEXT = document.createElement('DIV'); + TEXT.className = 'htAutocomplete'; + + var ARROW = document.createElement('DIV'); + ARROW.className = 'htAutocompleteArrow'; + ARROW.appendChild(document.createTextNode('\u25BC')); + //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips + + if (!instance.acArrowListener) { + //not very elegant but easy and fast + instance.acArrowListener = function () { + instance.view.wt.getSetting('onCellDblClick'); + }; + instance.rootElement.on('mouseup', '.htAutocompleteArrow', instance.acArrowListener); //this way we don't bind event listener to each arrow. We rely on propagation instead + } + + Handsontable.TextRenderer(instance, TEXT, row, col, prop, value, cellProperties); + + if (!TEXT.firstChild) { //http://jsperf.com/empty-node-if-needed + //otherwise empty fields appear borderless in demo/renderers.html (IE) + TEXT.appendChild(document.createTextNode('\u00A0')); //\u00A0 equals   for a text node + //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips + } + + TEXT.appendChild(ARROW); + instance.view.wt.wtDom.empty(TD); //TODO identify under what circumstances this line can be removed + TD.appendChild(TEXT); +}; +/** + * Checkbox renderer + * @param {Object} instance Handsontable instance + * @param {Element} TD Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.CheckboxRenderer = function (instance, TD, row, col, prop, value, cellProperties) { + if (typeof cellProperties.checkedTemplate === "undefined") { + cellProperties.checkedTemplate = true; + } + if (typeof cellProperties.uncheckedTemplate === "undefined") { + cellProperties.uncheckedTemplate = false; + } + + instance.view.wt.wtDom.empty(TD); //TODO identify under what circumstances this line can be removed + + var INPUT = document.createElement('INPUT'); + INPUT.className = 'htCheckboxRendererInput'; + INPUT.type = 'checkbox'; + INPUT.setAttribute('autocomplete', 'off'); + + if (value === cellProperties.checkedTemplate || value === Handsontable.helper.stringify(cellProperties.checkedTemplate)) { + INPUT.checked = true; + TD.appendChild(INPUT); + } + else if (value === cellProperties.uncheckedTemplate || value === Handsontable.helper.stringify(cellProperties.uncheckedTemplate)) { + TD.appendChild(INPUT); + } + else if (value === null) { //default value + INPUT.className += ' noValue'; + TD.appendChild(INPUT); + } + else { + TD.appendChild(document.createTextNode('#bad value#')); + //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips + } + + var $input = $(INPUT); + + if (cellProperties.readOnly) { + $input.on('click', function (event) { + event.preventDefault(); + }); + } + else { + $input.on('mousedown', function (event) { + if (!this.checked) { + instance.setDataAtRowProp(row, prop, cellProperties.checkedTemplate); + } + else { + instance.setDataAtRowProp(row, prop, cellProperties.uncheckedTemplate); + } + + event.stopPropagation(); //otherwise can confuse cell mousedown handler + }); + + $input.on('mouseup', function (event) { + event.stopPropagation(); //otherwise can confuse cell dblclick handler + }); + } + + return TD; +}; +/** + * Numeric cell renderer + * @param {Object} instance Handsontable instance + * @param {Element} TD Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Value to render (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.NumericRenderer = function (instance, TD, row, col, prop, value, cellProperties) { + if (Handsontable.helper.isNumeric(value)) { + if (typeof cellProperties.language !== 'undefined') { + numeral.language(cellProperties.language) + } + instance.view.wt.wtDom.empty(TD); //TODO identify under what circumstances this line can be removed + TD.className = 'htNumeric'; + TD.appendChild(document.createTextNode(numeral(value).format(cellProperties.format || '0'))); //docs: http://numeraljs.com/ + //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips + } + else { + Handsontable.TextRenderer(instance, TD, row, col, prop, value, cellProperties); + } +}; +function HandsontableTextEditorClass(instance) { + this.isCellEdited = false; + this.instance = instance; + this.createElements(); + this.bindEvents(); +} + +HandsontableTextEditorClass.prototype.createElements = function () { + this.wtDom = new WalkontableDom(); + + this.TEXTAREA = document.createElement('TEXTAREA'); + this.TEXTAREA.className = 'handsontableInput'; + this.textareaStyle = this.TEXTAREA.style; + this.textareaStyle.width = 0; + this.textareaStyle.height = 0; + this.$textarea = $(this.TEXTAREA); + + this.TEXTAREA_PARENT = document.createElement('DIV'); + this.TEXTAREA_PARENT.className = 'handsontableInputHolder'; + this.textareaParentStyle = this.TEXTAREA_PARENT.style; + this.textareaParentStyle.top = 0; + this.textareaParentStyle.left = 0; + this.textareaParentStyle.display = 'none'; + this.$textareaParent = $(this.TEXTAREA_PARENT); + + this.TEXTAREA_PARENT.appendChild(this.TEXTAREA); + this.instance.rootElement[0].appendChild(this.TEXTAREA_PARENT); + + var that = this; + Handsontable.PluginHooks.add('afterRender', function () { + that.instance.registerTimeout('refresh_editor_dimensions', function () { + that.refreshDimensions(); + }, 0); + }); +}; + +HandsontableTextEditorClass.prototype.bindEvents = function () { + var that = this; + this.$textareaParent.off('.editor').on('keydown.editor', function (event) { + //if we are here then isCellEdited === true + + that.instance.PluginHooks.run('beforeKeyDown', event); + if(event.isImmediatePropagationStopped()) { //event was cancelled in beforeKeyDown + return; + } + + var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL) + + if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) { + //when CTRL or its equivalent is pressed and cell is edited, don't prepare selectable text in textarea + event.stopImmediatePropagation(); + return; + } + + switch (event.keyCode) { + case 38: /* arrow up */ + case 40: /* arrow down */ + that.finishEditing(false); + break; + + case 9: /* tab */ + that.finishEditing(false); + event.preventDefault(); + break; + + case 39: /* arrow right */ + if (that.getCaretPosition(that.TEXTAREA) === that.TEXTAREA.value.length) { + that.finishEditing(false); + } + else { + event.stopImmediatePropagation(); + } + break; + + case 37: /* arrow left */ + if (that.getCaretPosition(that.TEXTAREA) === 0) { + that.finishEditing(false); + } + else { + event.stopImmediatePropagation(); + } + break; + + case 27: /* ESC */ + that.instance.destroyEditor(true); + event.stopImmediatePropagation(); + break; + + case 13: /* return/enter */ + var selected = that.instance.getSelected(); + var isMultipleSelection = !(selected[0] === selected[2] && selected[1] === selected[3]); + if ((event.ctrlKey && !isMultipleSelection) || event.altKey) { //if ctrl+enter or alt+enter, add new line + that.TEXTAREA.value = that.TEXTAREA.value + '\n'; + that.TEXTAREA.focus(); + event.stopImmediatePropagation(); + } + else { + that.finishEditing(false, ctrlDown); + } + event.preventDefault(); //don't add newline to field + break; + + default: + event.stopImmediatePropagation(); //backspace, delete, home, end, CTRL+A, CTRL+C, CTRL+V, CTRL+X should only work locally when cell is edited (not in table context) + break; + } + }); +}; + +HandsontableTextEditorClass.prototype.bindTemporaryEvents = function (td, row, col, prop, value, cellProperties) { + this.TD = td; + this.row = row; + this.col = col; + this.prop = prop; + this.originalValue = value; + this.cellProperties = cellProperties; + + var that = this; + + this.instance.focusCatcher.$el.on('keydown.editor', function (event) { + var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL) + if (!that.isCellEdited) { + if (Handsontable.helper.isPrintableChar(event.keyCode)) { + if (!ctrlDown) { //disregard CTRL-key shortcuts + that.beginEditing(row, col, prop); + } + } + else if (event.keyCode === 113) { //f2 + that.beginEditing(row, col, prop, true); //show edit field + event.stopImmediatePropagation(); + event.preventDefault(); //prevent Opera from opening Go to Page dialog + } + else if (event.keyCode === 13 && that.instance.getSettings().enterBeginsEditing) { //enter + var selected = that.instance.getSelected(); + var isMultipleSelection = !(selected[0] === selected[2] && selected[1] === selected[3]); + if ((ctrlDown && !isMultipleSelection) || event.altKey) { //if ctrl+enter or alt+enter, add new line + that.beginEditing(row, col, prop, true, '\n'); //show edit field + } + else { + that.beginEditing(row, col, prop, true); //show edit field + } + event.preventDefault(); //prevent new line at the end of textarea + event.stopImmediatePropagation(); + } + } + }); + + function onDblClick() { + that.beginEditing(row, col, prop, true); + } + + this.instance.view.wt.update('onCellDblClick', onDblClick); +}; + +HandsontableTextEditorClass.prototype.unbindTemporaryEvents = function () { + this.instance.focusCatcher.$el.off(".editor"); + this.instance.view.wt.update('onCellDblClick', null); +}; + +/** + * Returns caret position in edit proxy + * @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea + * @return {Number} + */ +HandsontableTextEditorClass.prototype.getCaretPosition = function (el) { + if (el.selectionStart) { + return el.selectionStart; + } + else if (document.selection) { + el.focus(); + var r = document.selection.createRange(); + if (r == null) { + return 0; + } + var re = el.createTextRange(), + rc = re.duplicate(); + re.moveToBookmark(r.getBookmark()); + rc.setEndPoint('EndToStart', re); + return rc.text.length; + } + return 0; +}; + +/** + * Sets caret position in edit proxy + * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/ + * @param {Number} + */ +HandsontableTextEditorClass.prototype.setCaretPosition = function (el, pos) { + if (el.setSelectionRange) { + el.focus(); + el.setSelectionRange(pos, pos); + } + else if (el.createTextRange) { + var range = el.createTextRange(); + range.collapse(true); + range.moveEnd('character', pos); + range.moveStart('character', pos); + range.select(); + } +}; + +HandsontableTextEditorClass.prototype.beginEditing = function (row, col, prop, useOriginalValue, suffix) { + if (this.isCellEdited) { + return; + } + this.isCellEdited = true; + this.row = row; + this.col = col; + this.prop = prop; + + var coords = {row: row, col: col}; + this.instance.view.scrollViewport(coords); + this.instance.view.render(); + + this.$textarea.on('cut.editor', function (event) { + event.stopPropagation(); + }); + + this.$textarea.on('paste.editor', function (event) { + event.stopPropagation(); + }); + + if (useOriginalValue) { + this.TEXTAREA.value = Handsontable.helper.stringify(this.originalValue) + (suffix || ''); + } + else { + this.TEXTAREA.value = ''; + } + + this.refreshDimensions(); //need it instantly, to prevent https://github.com/warpech/jquery-handsontable/issues/348 + this.TEXTAREA.focus(); + this.setCaretPosition(this.TEXTAREA, this.TEXTAREA.value.length); +}; + +HandsontableTextEditorClass.prototype.refreshDimensions = function () { + if (!this.isCellEdited) { + return; + } + + ///start prepare textarea position + this.TD = this.instance.getCell(this.row, this.col); + var $td = $(this.TD); //because old td may have been scrolled out with scrollViewport + var currentOffset = this.wtDom.offset(this.TD); + var containerOffset = this.wtDom.offset(this.instance.rootElement[0]); + var scrollTop = this.instance.rootElement.scrollTop(); + var scrollLeft = this.instance.rootElement.scrollLeft(); + var editTop = currentOffset.top - containerOffset.top + scrollTop - 1; + var editLeft = currentOffset.left - containerOffset.left + scrollLeft - 1; + + var settings = this.instance.getSettings(); + var rowHeadersCount = settings.rowHeaders === false ? 0 : 1; + var colHeadersCount = settings.colHeaders === false ? 0 : 1; + + if (editTop < 0) { + editTop = 0; + } + if (editLeft < 0) { + editLeft = 0; + } + + if (rowHeadersCount > 0 && parseInt($td.css('border-top-width'), 10) > 0) { + editTop += 1; + } + if (colHeadersCount > 0 && parseInt($td.css('border-left-width'), 10) > 0) { + editLeft += 1; + } + + if ($.browser.msie && parseInt($.browser.version, 10) <= 7) { + editTop -= 1; + } + + this.textareaParentStyle.top = editTop + 'px'; + this.textareaParentStyle.left = editLeft + 'px'; + ///end prepare textarea position + + var width = $td.width() + , height = $td.outerHeight() - 4; + + if (parseInt($td.css('border-top-width'), 10) > 0) { + height -= 1; + } + if (parseInt($td.css('border-left-width'), 10) > 0) { + if (rowHeadersCount > 0) { + width -= 1; + } + } + + this.$textarea.autoResize({ + maxHeight: 200, + minHeight: height, + minWidth: width, + maxWidth: Math.max(168, width), + animate: false, + extraSpace: 0 + }); + + this.textareaParentStyle.display = 'block'; +}; + +HandsontableTextEditorClass.prototype.finishEditing = function (isCancelled, ctrlDown) { + if (this.isCellEdited) { + this.isCellEdited = false; + if (!isCancelled) { + var val = [ + [$.trim(this.TEXTAREA.value)] + ]; + if (ctrlDown) { //if ctrl+enter and multiple cells selected, behave like Excel (finish editing and apply to all cells) + var sel = this.instance.getSelected(); + this.instance.populateFromArray(sel[0], sel[1], val, sel[2], sel[3], 'edit'); + } + else { + this.instance.populateFromArray(this.row, this.col, val, null, null, 'edit'); + } + } + } + + this.unbindTemporaryEvents(); + if (document.activeElement === this.TEXTAREA) { + this.instance.listen(); //don't refocus the table if user focused some cell outside of HT on purpose + } + + this.textareaParentStyle.display = 'none'; +}; + +/** + * Default text editor + * @param {Object} instance Handsontable instance + * @param {Element} td Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Original value (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.TextEditor = function (instance, td, row, col, prop, value, cellProperties) { + if (!instance.textEditor) { + instance.textEditor = new HandsontableTextEditorClass(instance); + } + instance.textEditor.bindTemporaryEvents(td, row, col, prop, value, cellProperties); + return function (isCancelled) { + instance.textEditor.finishEditing(isCancelled); + } +}; +function HandsontableAutocompleteEditorClass(instance) { + this.isCellEdited = false; + this.instance = instance; + this.createElements(); + this.bindEvents(); + this.emptyStringLabel = '\u00A0\u00A0\u00A0'; //3 non-breaking spaces +} + +Handsontable.helper.inherit(HandsontableAutocompleteEditorClass, HandsontableTextEditorClass); + +/** + * @see HandsontableTextEditorClass.prototype.createElements + */ +HandsontableAutocompleteEditorClass.prototype.createElements = function () { + HandsontableTextEditorClass.prototype.createElements.call(this); + + this.$textarea.typeahead(); + this.typeahead = this.$textarea.data('typeahead'); + this.typeahead._render = this.typeahead.render; + this.typeahead.minLength = 0; + + this.typeahead.lookup = function () { + var items; + this.query = this.$element.val(); + items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source; + return items ? this.process(items) : this; + }; + + this.typeahead.matcher = function () { + return true; + }; + + var _process = this.typeahead.process; + var that = this; + this.typeahead.process = function (items) { + var cloned = false; + for (var i = 0, ilen = items.length; i < ilen; i++) { + if (items[i] === '') { + //this is needed because because of issue #254 + //empty string ('') is a falsy value and breaks the loop in bootstrap-typeahead.js method `sorter` + //best solution would be to change line: `while (item = items.shift()) {` + // to: `while ((item = items.shift()) !== void 0) {` + if (!cloned) { + //need to clone items before applying emptyStringLabel + //(otherwise validateChanges fails for empty string) + items = $.extend([], items); + cloned = true; + } + items[i] = that.emptyStringLabel; + } + } + return _process.call(this, items); + }; +}; + +/** + * @see HandsontableTextEditorClass.prototype.bindEvents + */ +HandsontableAutocompleteEditorClass.prototype.bindEvents = function () { + var that = this; + + this.$textarea.off('keydown').off('keyup').off('keypress'); //unlisten + + this.$textareaParent.off('.acEditor').on('keydown.acEditor', function (event) { + switch (event.keyCode) { + case 38: /* arrow up */ + that.typeahead.prev(); + event.stopImmediatePropagation(); //stops TextEditor and core onKeyDown handler + break; + + case 40: /* arrow down */ + that.typeahead.next(); + event.stopImmediatePropagation(); //stops TextEditor and core onKeyDown handler + break; + + case 13: /* enter */ + event.preventDefault(); + break; + } + }); + + this.$textareaParent.on('keyup.acEditor', function (event) { + if (Handsontable.helper.isPrintableChar(event.keyCode) || event.keyCode === 113 || event.keyCode === 13 || event.keyCode === 8 || event.keyCode === 46) { + that.typeahead.lookup(); + } + }); + + + HandsontableTextEditorClass.prototype.bindEvents.call(this); +}; +/** + * @see HandsontableTextEditorClass.prototype.bindTemporaryEvents + */ +HandsontableAutocompleteEditorClass.prototype.bindTemporaryEvents = function (td, row, col, prop, value, cellProperties) { + var that = this + , i + , j; + + this.typeahead.select = function () { + var output = this.hide(); //need to hide it before destroyEditor, because destroyEditor checks if menu is expanded + that.instance.destroyEditor(true); + var val = this.$menu.find('.active').attr('data-value'); + if (val === that.emptyStringLabel) { + val = ''; + } + if (typeof cellProperties.onSelect === 'function') { + cellProperties.onSelect(row, col, prop, val, this.$menu.find('.active').index()); + } + else { + that.instance.setDataAtRowProp(row, prop, val); + } + return output; + }; + + this.typeahead.render = function (items) { + that.typeahead._render.call(this, items); + if (!cellProperties.strict) { + this.$menu.find('li:eq(0)').removeClass('active'); + } + return this; + }; + + /* overwrite typeahead options and methods (matcher, sorter, highlighter, updater, etc) if provided in cellProperties */ + for (i in cellProperties) { + // if (cellProperties.hasOwnProperty(i)) { + if (i === 'options') { + for (j in cellProperties.options) { + // if (cellProperties.options.hasOwnProperty(j)) { + this.typeahead.options[j] = cellProperties.options[j]; + // } + } + } + else { + this.typeahead[i] = cellProperties[i]; + } + // } + } + + HandsontableTextEditorClass.prototype.bindTemporaryEvents.call(this, td, row, col, prop, value, cellProperties); + + function onDblClick() { + that.beginEditing(row, col, prop, true); + that.instance.registerTimeout('IE9_align_fix', function () { //otherwise is misaligned in IE9 + that.typeahead.lookup(); + }, 1); + } + + this.instance.view.wt.update('onCellDblClick', onDblClick); +}; +/** + * @see HandsontableTextEditorClass.prototype.finishEditing + */ +HandsontableAutocompleteEditorClass.prototype.finishEditing = function (isCancelled, ctrlDown) { + if (!isCancelled) { + if (this.isMenuExpanded() && this.typeahead.$menu.find('.active').length) { + this.typeahead.select(); + this.isCellEdited = false; //cell value was updated by this.typeahead.select (issue #405) + } + else if (this.cellProperties.strict) { + this.isCellEdited = false; //cell value was not picked from this.typeahead.select (issue #405) + } + } + + HandsontableTextEditorClass.prototype.finishEditing.call(this, isCancelled, ctrlDown); +}; + +HandsontableAutocompleteEditorClass.prototype.isMenuExpanded = function () { + if (this.typeahead.$menu.is(":visible")) { + return this.typeahead; + } + else { + return false; + } +}; + +/** + * Autocomplete editor + * @param {Object} instance Handsontable instance + * @param {Element} td Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Original value (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.AutocompleteEditor = function (instance, td, row, col, prop, value, cellProperties) { + if (!instance.autocompleteEditor) { + instance.autocompleteEditor = new HandsontableAutocompleteEditorClass(instance); + } + instance.autocompleteEditor.bindTemporaryEvents(td, row, col, prop, value, cellProperties); + return function (isCancelled) { + instance.autocompleteEditor.finishEditing(isCancelled); + } +}; +function toggleCheckboxCell(instance, row, prop, cellProperties) { + if (Handsontable.helper.stringify(instance.getDataAtRowProp(row, prop)) === Handsontable.helper.stringify(cellProperties.checkedTemplate)) { + instance.setDataAtRowProp(row, prop, cellProperties.uncheckedTemplate); + } + else { + instance.setDataAtRowProp(row, prop, cellProperties.checkedTemplate); + } +} + +/** + * Checkbox editor + * @param {Object} instance Handsontable instance + * @param {Element} td Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Original value (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.CheckboxEditor = function (instance, td, row, col, prop, value, cellProperties) { + if (typeof cellProperties === "undefined") { + cellProperties = {}; + } + if (typeof cellProperties.checkedTemplate === "undefined") { + cellProperties.checkedTemplate = true; + } + if (typeof cellProperties.uncheckedTemplate === "undefined") { + cellProperties.uncheckedTemplate = false; + } + + instance.$table.on("keydown.editor", function (event) { + var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL) + if (!ctrlDown && Handsontable.helper.isPrintableChar(event.keyCode)) { + toggleCheckboxCell(instance, row, prop, cellProperties); + event.stopImmediatePropagation(); //stops core onKeyDown handler + event.preventDefault(); //some keys have special behavior, eg. space bar scrolls screen down + } + }); + + instance.view.wt.update('onCellDblClick', function () { + toggleCheckboxCell(instance, row, prop, cellProperties); + }); + + return function () { + instance.$table.off(".editor"); + instance.view.wt.update('onCellDblClick', null); + } +}; + + + +function HandsontableDateEditorClass(instance) { + if (!$.datepicker) { + throw new Error("jQuery UI Datepicker dependency not found. Did you forget to include jquery-ui.custom.js or its substitute?"); + } + + this.isCellEdited = false; + this.instance = instance; + this.createElements(); + this.bindEvents(); +} + +Handsontable.helper.inherit(HandsontableDateEditorClass, HandsontableTextEditorClass); + +/** + * @see HandsontableTextEditorClass.prototype.createElements + */ +HandsontableDateEditorClass.prototype.createElements = function () { + HandsontableTextEditorClass.prototype.createElements.call(this); + + this.datePicker = document.createElement('DIV'); + this.datePickerStyle = this.datePicker.style; + this.datePickerStyle.position = 'absolute'; + this.datePickerStyle.top = 0; + this.datePickerStyle.left = 0; + this.datePickerStyle.zIndex = 99; + this.instance.rootElement[0].appendChild(this.datePicker); + this.$datePicker = $(this.datePicker); + + var that = this; + var defaultOptions = { + dateFormat: "yy-mm-dd", + showButtonPanel: true, + changeMonth: true, + changeYear: true, + altField: this.$textarea, + onSelect: function () { + that.finishEditing(false); + } + }; + this.$datePicker.datepicker(defaultOptions); + this.hideDatepicker(); +}; + +/** + * @see HandsontableTextEditorClass.prototype.beginEditing + */ +HandsontableDateEditorClass.prototype.beginEditing = function (row, col, prop, useOriginalValue, suffix) { + HandsontableTextEditorClass.prototype.beginEditing.call(this, row, col, prop, useOriginalValue, suffix); + this.showDatepicker(); +}; + +/** + * @see HandsontableTextEditorClass.prototype.finishEditing + */ +HandsontableDateEditorClass.prototype.finishEditing = function (isCancelled, ctrlDown) { + this.hideDatepicker(); + HandsontableTextEditorClass.prototype.finishEditing.call(this, isCancelled, ctrlDown); +}; + +HandsontableDateEditorClass.prototype.showDatepicker = function () { + var $td = $(this.instance.dateEditor.TD); + var position = $td.position(); + this.datePickerStyle.top = (position.top + $td.height()) + 'px'; + this.datePickerStyle.left = position.left + 'px'; + + var dateOptions = { + defaultDate: this.originalValue || void 0 + }; + $.extend(dateOptions, this.cellProperties); + this.$datePicker.datepicker("option", dateOptions); + if (this.originalValue) { + this.$datePicker.datepicker("setDate", this.originalValue); + } + this.datePickerStyle.display = 'block'; +}; + +HandsontableDateEditorClass.prototype.hideDatepicker = function () { + this.datePickerStyle.display = 'none'; +}; + +/** + * Date editor (uses jQuery UI Datepicker) + * @param {Object} instance Handsontable instance + * @param {Element} td Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Original value (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.DateEditor = function (instance, td, row, col, prop, value, cellProperties) { + if (!instance.dateEditor) { + instance.dateEditor = new HandsontableDateEditorClass(instance); + } + instance.dateEditor.bindTemporaryEvents(td, row, col, prop, value, cellProperties); + return function (isCancelled) { + instance.dateEditor.finishEditing(isCancelled); + } +}; +/** + * This is inception. Using Handsontable as Handsontable editor + */ + +function HandsontableHandsontableEditorClass(instance) { + this.isCellEdited = false; + this.instance = instance; + this.createElements(); + this.bindEvents(); +} + +Handsontable.helper.inherit(HandsontableHandsontableEditorClass, HandsontableTextEditorClass); + +HandsontableHandsontableEditorClass.prototype.createElements = function () { + HandsontableTextEditorClass.prototype.createElements.call(this); + + var DIV = document.createElement('DIV'); + DIV.className = 'handsontableEditor'; + this.TEXTAREA_PARENT.appendChild(DIV); + + this.$htContainer = $(DIV); +}; + +HandsontableHandsontableEditorClass.prototype.bindTemporaryEvents = function (td, row, col, prop, value, cellProperties) { + var parent = this; + + var options = { + colHeaders: true, + cells: function () { + return { + readOnly: true + } + }, + fillHandle: false, + width: 2000, + //width: 'auto', + afterOnCellMouseDown: function () { + var sel = this.getSelected(); + parent.TEXTAREA.value = this.getDataAtCell(sel[0], sel[1]); + parent.instance.destroyEditor(); + }, + beforeOnKeyDown: function (event) { + switch (event.keyCode) { + case 27: //esc + parent.instance.destroyEditor(true); + break; + + case 13: //enter + var sel = this.getSelected(); + parent.TEXTAREA.value = this.getDataAtCell(sel[0], sel[1]); + parent.instance.destroyEditor(); + break; + } + } + }; + + if (cellProperties.handsontable) { + options = $.extend(options, cellProperties.handsontable); + } + + this.$htContainer.handsontable(options); + + HandsontableTextEditorClass.prototype.bindTemporaryEvents.call(this, td, row, col, prop, value, cellProperties); +}; + +HandsontableHandsontableEditorClass.prototype.beginEditing = function (row, col, prop, useOriginalValue, suffix) { + var onBeginEditing = this.instance.getSettings().onBeginEditing; + if (onBeginEditing && onBeginEditing() === false) { + return; + } + + HandsontableTextEditorClass.prototype.beginEditing.call(this, row, col, prop, useOriginalValue, suffix); + + this.$htContainer.handsontable('render'); + this.$htContainer.handsontable('selectCell', 0, 0); +}; + +HandsontableHandsontableEditorClass.prototype.finishEditing = function (isCancelled, ctrlDown) { + if (Handsontable.helper.isDescendant(this.instance.rootElement[0], document.activeElement)) { + var that = this; + setTimeout(function () { + that.instance.listen(); //return the focus to the cell must be done after destroyer to work in IE7-9 + }, 0); + that.instance.listen(); //return the focus to the cell + } + this.$htContainer.handsontable('destroy'); + HandsontableTextEditorClass.prototype.finishEditing.call(this, isCancelled, ctrlDown); +}; + +HandsontableHandsontableEditorClass.prototype.isMenuExpanded = function () { + if (this.typeahead.$menu.is(":visible")) { + return this.typeahead; + } + else { + return false; + } +}; + +/** + * Handsontable editor + * @param {Object} instance Handsontable instance + * @param {Element} td Table cell where to render + * @param {Number} row + * @param {Number} col + * @param {String|Number} prop Row object property name + * @param value Original value (remember to escape unsafe HTML before inserting to DOM!) + * @param {Object} cellProperties Cell properites (shared by cell renderer and editor) + */ +Handsontable.HandsontableEditor = function (instance, td, row, col, prop, value, cellProperties) { + if (!instance.handsontableEditor) { + instance.handsontableEditor = new HandsontableHandsontableEditorClass(instance); + } + instance.handsontableEditor.bindTemporaryEvents(td, row, col, prop, value, cellProperties); + + instance.registerEditor = instance.handsontableEditor; + + return function (isCancelled) { + instance.handsontableEditor.finishEditing(isCancelled); + } +}; +/** + * Cell type is just a shortcut for setting bunch of cellProperties (used in getCellMeta) + */ + +Handsontable.AutocompleteCell = { + renderer: Handsontable.AutocompleteRenderer, + editor: Handsontable.AutocompleteEditor +}; + +Handsontable.CheckboxCell = { + renderer: Handsontable.CheckboxRenderer, + editor: Handsontable.CheckboxEditor +}; + +Handsontable.TextCell = { + renderer: Handsontable.TextRenderer, + editor: Handsontable.TextEditor +}; + +Handsontable.NumericCell = { + renderer: Handsontable.NumericRenderer, + editor: Handsontable.TextEditor, + dataType: 'number' +}; + +Handsontable.DateCell = { + renderer: Handsontable.AutocompleteRenderer, //displays small gray arrow on right side of the cell + editor: Handsontable.DateEditor +}; + +Handsontable.HandsontableCell = { + renderer: Handsontable.AutocompleteRenderer, //displays small gray arrow on right side of the cell + editor: Handsontable.HandsontableEditor +}; + +//here setup the friendly aliases that are used by cellProperties.type +Handsontable.cellTypes = { + autocomplete: Handsontable.AutocompleteCell, + checkbox: Handsontable.CheckboxCell, + text: Handsontable.TextCell, + numeric: Handsontable.NumericCell, + date: Handsontable.DateCell, + handsontable: Handsontable.HandsontableCell +}; + +//here setup the friendly aliases that are used by cellProperties.renderer and cellProperties.editor +Handsontable.cellLookup = { + renderer: { + autocomplete: Handsontable.AutocompleteRenderer, + checkbox: Handsontable.CheckboxRenderer, + text: Handsontable.TextRenderer, + numeric: Handsontable.NumericRenderer + }, + editor: { + autocomplete: Handsontable.AutocompleteEditor, + checkbox: Handsontable.CheckboxEditor, + text: Handsontable.TextEditor, + date: Handsontable.DateEditor, + handsontable: Handsontable.HandsontableEditor + } +}; +Handsontable.PluginHookClass = (function () { + + var legacy = { + onBeforeChange: "beforeChange", + onChange: "afterChange", + onCreateRow: "afterCreateRow", + onCreateCol: "afterCreateCol", + onSelection: "afterSelection", + onCopyLimit: "afterCopyLimit", + onSelectionEnd: "afterSelectionEnd", + onSelectionByProp: "afterSelectionByProp", + onSelectionEndByProp: "afterSelectionEndByProp" + }; + + function PluginHookClass () { + + this.hooks = { + // Hooks + beforeInitWalkontable : [], + + beforeInit : [], + beforeRender : [], + beforeChange : [], + beforeGet : [], + beforeSet : [], + beforeGetCellMeta : [], + beforeAutofill : [], + beforeKeyDown : [], + + afterInit : [], + afterLoadData : [], + afterRender : [], + afterChange : [], + afterGetCellMeta : [], + afterGetColHeader : [], + afterGetColWidth : [], + afterDestroy : [], + afterRemoveRow : [], + afterCreateRow : [], + afterRemoveCol : [], + afterCreateCol : [], + afterColumnResize : [], + afterColumnMove : [], + afterDeselect : [], + afterSelection : [], + afterSelectionByProp : [], + afterSelectionEnd : [], + afterSelectionEndByProp : [], + afterCopyLimit : [], + + // Modifiers + modifyCol : [] + }; + + this.legacy = legacy; + + } + + PluginHookClass.prototype.add = function (key, fn) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + if (typeof this.hooks[key] === "undefined") { + this.hooks[key] = []; + } + + if (fn instanceof Array) { + for (var i = 0, len = fn.length; i < len; i++) { + this.hooks[key].push(fn[i]); + } + } else { + this.hooks[key].push(fn); + } + + return this; + }; + + PluginHookClass.prototype.once = function (key, fn) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + var instance = this + , _remove = this.remove + , wrapper = function () { + _remove.call(instance, key, wrapper); + + return fn.apply(instance, arguments); + }; + + return this.add(key, wrapper); + }; + + PluginHookClass.prototype.remove = function (key, fn) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + for (var i = 0, len = this.hooks[key].length; i < len; i++) { + if (this.hooks[key][i] == fn) { + this.hooks[key].splice(i, 1); + return true; + } + } + return false; + } + + PluginHookClass.prototype.run = function (instance, key, p1, p2, p3, p4, p5) { + + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture + if (typeof this.hooks[key] !== 'undefined') { + for (var i = 0, len = this.hooks[key].length; i < len; i++) { + this.hooks[key][i].call(instance, p1, p2, p3, p4, p5); + } + } + } + + PluginHookClass.prototype.execute = function (instance, key, p1, p2, p3, p4, p5) { + // provide support for old versions of HOT + if (key in legacy) { + key = legacy[key]; + } + + //performance considerations - http://jsperf.com/call-vs-apply-for-a-plugin-architecture + if (typeof this.hooks[key] !== 'undefined') { + for (var i = 0, len = this.hooks[key].length; i < len; i++) { + p1 = this.hooks[key][i].call(instance, p1, p2, p3, p4, p5); + } + } + + return p1; + } + + return PluginHookClass; + +})(); + +Handsontable.PluginHooks = new Handsontable.PluginHookClass(); +function HandsontableAutoColumnSize() { + var that = this + , instance + , sampleCount = 5; //number of samples to take of each value length + + this.beforeInit = function () { + this.autoColumnWidths = []; + this.autoColumnSizeTmp = { + thead: null, + theadTh: null, + theadStyle: null, + tbody: null, + tbodyTd: null, + noRenderer: null, + noRendererTd: null, + renderer: null, + rendererTd: null, + container: null, + containerStyle: null, + $container: null, + $noRenderer: null, + $renderer: null + }; + }; + + this.determineColumnWidth = function (col) { + var tmp = instance.autoColumnSizeTmp + , d; + + if (!tmp.container) { + d = document; + + tmp.thead = d.createElement('table'); + tmp.thead.appendChild(d.createElement('thead')).appendChild(d.createElement('tr')).appendChild(d.createElement('th')); + tmp.theadTh = tmp.thead.getElementsByTagName('th')[0]; + + tmp.thead.className = 'htTable'; + tmp.theadStyle = tmp.thead.style; + tmp.theadStyle.tableLayout = 'auto'; + tmp.theadStyle.width = 'auto'; + + tmp.tbody = tmp.thead.cloneNode(false); + tmp.tbody.appendChild(d.createElement('tbody')).appendChild(d.createElement('tr')).appendChild(d.createElement('td')); + tmp.tbodyTd = tmp.tbody.getElementsByTagName('td')[0]; + + tmp.noRenderer = tmp.tbody.cloneNode(true); + tmp.noRendererTd = tmp.noRenderer.getElementsByTagName('td')[0]; + + tmp.renderer = tmp.tbody.cloneNode(true); + tmp.rendererTd = tmp.renderer.getElementsByTagName('td')[0]; + + tmp.container = d.createElement('div'); + tmp.container.className = instance.rootElement[0].className + ' hidden'; + tmp.containerStyle = tmp.container.style; + + tmp.container.appendChild(tmp.thead); + tmp.container.appendChild(tmp.tbody); + tmp.container.appendChild(tmp.noRenderer); + tmp.container.appendChild(tmp.renderer); + + tmp.$container = $(tmp.container); + tmp.$noRenderer = $(tmp.noRenderer); + tmp.$renderer = $(tmp.renderer); + + instance.rootElement[0].parentNode.appendChild(tmp.container); + } + + tmp.container.className = instance.rootElement[0].className + ' hidden'; + + var rows = instance.countRows(); + var samples = {}; + var maxLen = 0; + for (var r = 0; r < rows; r++) { + var value = Handsontable.helper.stringify(instance.getDataAtCell(r, col)); + var len = value.length; + if (len > maxLen) { + maxLen = len; + } + if (!samples[len]) { + samples[len] = { + needed: sampleCount, + strings: [] + }; + } + if (samples[len].needed) { + samples[len].strings.push(value); + samples[len].needed--; + } + } + + var settings = instance.getSettings(); + if (settings.colHeaders) { + instance.view.appendColHeader(col, tmp.theadTh); //TH innerHTML + } + + var txt = ''; + for (var i in samples) { + if (samples.hasOwnProperty(i)) { + for (var j = 0, jlen = samples[i].strings.length; j < jlen; j++) { + txt += samples[i].strings[j] + '
    '; + } + } + } + tmp.tbodyTd.innerHTML = txt; //TD innerHTML + + instance.view.wt.wtDom.empty(tmp.rendererTd); + instance.view.wt.wtDom.empty(tmp.noRendererTd); + + tmp.containerStyle.display = 'block'; + + var width = tmp.$container.outerWidth(); + + var cellProperties = instance.getCellMeta(0, col); + if (cellProperties.renderer) { + var str = 9999999999; + + tmp.noRendererTd.appendChild(document.createTextNode(str)); + var renderer = Handsontable.helper.getCellMethod('renderer', cellProperties.renderer); + renderer(instance, tmp.rendererTd, 0, col, instance.colToProp(col), str, cellProperties); + + width += tmp.$renderer.width() - tmp.$noRenderer.width(); //add renderer overhead to the calculated width + } + + tmp.containerStyle.display = 'none'; + + return width; + }; + + this.determineColumnsWidth = function () { + instance = this; + var settings = this.getSettings(); + if (settings.autoColumnSize || !settings.colWidths) { + var cols = this.countCols(); + for (var c = 0; c < cols; c++) { + this.autoColumnWidths[c] = that.determineColumnWidth(c); + } + } + }; + + this.getColWidth = function (col, response) { + if (this.autoColumnWidths[col] && this.autoColumnWidths[col] > response.width) { + response.width = this.autoColumnWidths[col]; + } + }; +} +var htAutoColumnSize = new HandsontableAutoColumnSize(); + +Handsontable.PluginHooks.add('beforeInit', htAutoColumnSize.beforeInit); +Handsontable.PluginHooks.add('beforeRender', htAutoColumnSize.determineColumnsWidth); +Handsontable.PluginHooks.add('afterGetColWidth', htAutoColumnSize.getColWidth); + +/** + * This plugin sorts the view by a column (but does not sort the data source!) + * @constructor + */ +function HandsontableColumnSorting() { + var plugin = this; + var sortingEnabled; + + this.afterInit = function () { + var instance = this; + if (this.getSettings().columnSorting) { + this.sortIndex = []; + this.rootElement.on('click.handsontable', '.columnSorting', function (e) { + var $target = $(e.target); + if ($target.is('.columnSorting')) { + var col = $target.closest('th').index(); + if (instance.getSettings().rowHeaders) { + col--; + } + if (instance.sortColumn === col) { + instance.sortOrder = !instance.sortOrder; + } + else { + instance.sortColumn = col; + instance.sortOrder = true; + } + plugin.sort.call(instance); + instance.render(); + } + }); + } + }; + + this.sort = function () { + sortingEnabled = false; + var instance = this; + this.sortIndex.length = 0; + //var data = this.getData(); + for (var i = 0, ilen = this.countRows(); i < ilen; i++) { + //this.sortIndex.push([i, data[i][this.sortColumn]]); + this.sortIndex.push([i, instance.getDataAtCell(i, this.sortColumn)]); + } + this.sortIndex.sort(function (a, b) { + if (a[1] === b[1]) { + return 0; + } + if (a[1] === null) { + return 1; + } + if (b[1] === null) { + return -1; + } + if (a[1] < b[1]) return instance.sortOrder ? -1 : 1; + if (a[1] > b[1]) return instance.sortOrder ? 1 : -1; + return 0; + }); + sortingEnabled = true; + }; + + this.translateRow = function (getVars) { + if (sortingEnabled && this.sortIndex && this.sortIndex.length) { + getVars.row = this.sortIndex[getVars.row][0]; + } + }; + + this.getColHeader = function (col, TH) { + if (this.getSettings().columnSorting) { + $(TH).find('span.colHeader')[0].className += ' columnSorting'; + } + }; +} +var htSortColumn = new HandsontableColumnSorting(); + +Handsontable.PluginHooks.add('afterInit', htSortColumn.afterInit); +Handsontable.PluginHooks.add('beforeGet', htSortColumn.translateRow); +Handsontable.PluginHooks.add('beforeSet', htSortColumn.translateRow); +Handsontable.PluginHooks.add('afterGetColHeader', htSortColumn.getColHeader); +function createContextMenu() { + var instance = this + , selectorId = instance.rootElement[0].id + , allItems = { + "row_above": {name: "Insert row above", disabled: isDisabled}, + "row_below": {name: "Insert row below", disabled: isDisabled}, + "hsep1": "---------", + "col_left": {name: "Insert column on the left", disabled: isDisabled}, + "col_right": {name: "Insert column on the right", disabled: isDisabled}, + "hsep2": "---------", + "remove_row": {name: "Remove row", disabled: isDisabled}, + "remove_col": {name: "Remove column", disabled: isDisabled}, + "hsep3": "---------", + "undo": {name: "Undo", disabled: function () { + return !instance.isUndoAvailable(); + }}, + "redo": {name: "Redo", disabled: function () { + return !instance.isRedoAvailable(); + }} + } + , defaultOptions = { + selector : "#" + selectorId + ' table, #' + selectorId + ' div', + trigger : 'right', + callback : onContextClick + } + , options = {} + , i + , ilen + , settings = instance.getSettings(); + + function onContextClick(key) { + var corners = instance.getSelected(); //[top left row, top left col, bottom right row, bottom right col] + + if (!corners) { + return; //needed when there are 2 grids on a page + } + + switch (key) { + case "row_above": + instance.alter("insert_row", corners[0]); + break; + + case "row_below": + instance.alter("insert_row", corners[2] + 1); + break; + + case "col_left": + instance.alter("insert_col", corners[1]); + break; + + case "col_right": + instance.alter("insert_col", corners[3] + 1); + break; + + case "remove_row": + instance.alter(key, corners[0], (corners[2] - corners[0]) + 1); + break; + + case "remove_col": + instance.alter(key, corners[1], (corners[3] - corners[1]) + 1); + break; + + case "undo": + instance.undo(); + break; + + case "redo": + instance.redo(); + break; + } + } + + function isDisabled(key) { + //TODO rewrite + /*if (instance.blockedCols.main.find('th.htRowHeader.active').length && (key === "remove_col" || key === "col_left" || key === "col_right")) { + return true; + } + else if (instance.blockedRows.main.find('th.htColHeader.active').length && (key === "remove_row" || key === "row_above" || key === "row_below")) { + return true; + } + else*/ + if (instance.countRows() >= instance.getSettings().maxRows && (key === "row_above" || key === "row_below")) { + return true; + } + else if (instance.countCols() >= instance.getSettings().maxCols && (key === "col_left" || key === "col_right")) { + return true; + } + else { + return false; + } + } + + if (!settings.contextMenu) { + return; + } + else if (settings.contextMenu === true) { //contextMenu is true + options.items = allItems; + } + else if (Object.prototype.toString.apply(settings.contextMenu) === '[object Array]') { //contextMenu is an array + options.items = {}; + for (i = 0, ilen = settings.contextMenu.length; i < ilen; i++) { + var key = settings.contextMenu[i]; + if (typeof allItems[key] === 'undefined') { + throw new Error('Context menu key "' + key + '" is not recognised'); + } + options.items[key] = allItems[key]; + } + } + else if (Object.prototype.toString.apply(settings.contextMenu) === '[object Object]') { //contextMenu is an options object as defined in http://medialize.github.com/jQuery-contextMenu/docs.html + options = settings.contextMenu; + if (options.items) { + for (i in options.items) { + if (options.items.hasOwnProperty(i) && allItems[i]) { + if (typeof options.items[i] === 'string') { + options.items[i] = allItems[i]; + } + else { + options.items[i] = $.extend(true, allItems[i], options.items[i]); + } + } + } + } + else { + options.items = allItems; + } + + if (options.callback) { + var handsontableCallback = defaultOptions.callback; + var customCallback = options.callback; + options.callback = function (key, options) { + handsontableCallback(key, options); + customCallback(key, options); + } + } + } + + if (!selectorId) { + throw new Error("Handsontable container must have an id"); + } + + $.contextMenu($.extend(true, defaultOptions, options)); +} + +function destroyContextMenu() { + var id = this.rootElement[0].id; + $.contextMenu('destroy', "#" + id + ' table, #' + id + ' div'); +} + +Handsontable.PluginHooks.add('afterInit', createContextMenu); +Handsontable.PluginHooks.add('afterDestroy', destroyContextMenu); +/** + * This plugin adds support for legacy features, deprecated APIs, etc. + */ + +/** + * Support for old autocomplete syntax + * For old syntax, see: https://github.com/warpech/jquery-handsontable/blob/8c9e701d090ea4620fe08b6a1a048672fadf6c7e/README.md#defining-autocomplete + */ +Handsontable.PluginHooks.add('beforeGetCellMeta', function (row, col, cellProperties) { + var settings = this.getSettings(), data = this.getData(), i, ilen, a; + if (settings.autoComplete) { + for (i = 0, ilen = settings.autoComplete.length; i < ilen; i++) { + if (settings.autoComplete[i].match(row, col, data)) { + if (typeof cellProperties.type === 'undefined') { + cellProperties.type = Handsontable.AutocompleteCell; + } + else { + if (typeof cellProperties.type.renderer === 'undefined') { + cellProperties.type.renderer = Handsontable.AutocompleteCell.renderer; + } + if (typeof cellProperties.type.editor === 'undefined') { + cellProperties.type.editor = Handsontable.AutocompleteCell.editor; + } + } + for (a in settings.autoComplete[i]) { + if (settings.autoComplete[i].hasOwnProperty(a) && a !== 'match' && typeof cellProperties[i] === 'undefined') { + if (a === 'source') { + cellProperties[a] = settings.autoComplete[i][a](row, col); + } + else { + cellProperties[a] = settings.autoComplete[i][a]; + } + } + } + break; + } + } + } +}); +function HandsontableManualColumnMove() { + var instance + , pressed + , startCol + , endCol + , startX + , startOffset; + + var ghost = document.createElement('DIV') + , ghostStyle = ghost.style; + + ghost.className = 'ghost'; + ghostStyle.position = 'absolute'; + ghostStyle.top = '25px'; + ghostStyle.left = 0; + ghostStyle.width = '10px'; + ghostStyle.height = '10px'; + ghostStyle.backgroundColor = '#CCC'; + ghostStyle.opacity = 0.7; + + $(document).mousemove(function (e) { + if (pressed) { + ghostStyle.left = startOffset + e.pageX - startX + 6 + 'px'; + if (ghostStyle.display === 'none') { + ghostStyle.display = 'block'; + } + } + }); + + $(document).mouseup(function () { + if (pressed) { + if (startCol < endCol) { + endCol--; + } + if (instance.getSettings().rowHeaders) { + startCol--; + endCol--; + } + instance.manualColumnPositions.splice(endCol, 0, instance.manualColumnPositions.splice(startCol, 1)[0]); + $('.manualColumnMover.active').removeClass('active'); + pressed = false; + instance.forceFullRender = true; + instance.view.render(); //updates all + ghostStyle.display = 'none'; + instance.PluginHooks.run('afterColumnMove', startCol, endCol); + } + }); + + this.beforeInit = function () { + this.manualColumnPositions = []; + }; + + this.afterInit = function () { + if (this.getSettings().manualColumnMove) { + var that = this; + this.rootElement.on('mousedown.handsontable', '.manualColumnMover', function (e) { + instance = that; + + var $resizer = $(e.target); + var th = $resizer.closest('th'); + startCol = th.index(); + pressed = true; + startX = e.pageX; + + var $table = that.rootElement.find('.htCore'); + $table.parent()[0].appendChild(ghost); + ghostStyle.width = $resizer.parent().width() + 'px'; + ghostStyle.height = $table.height() + 'px'; + startOffset = parseInt(th.offset().left - $table.offset().left, 10); + ghostStyle.left = startOffset + 6 + 'px'; + }); + this.rootElement.on('mouseenter.handsontable', 'td, th', function () { + if (pressed) { + $('.manualColumnMover.active').removeClass('active'); + var $ths = that.rootElement.find('thead th'); + endCol = $(this).index(); + var $hover = $ths.eq(endCol).find('.manualColumnMover').addClass('active'); + $ths.not($hover).removeClass('active'); + } + }); + } + }; + + this.modifyCol = function (col) { + //TODO test performance: http://jsperf.com/object-wrapper-vs-primitive/2 + if (this.getSettings().manualColumnMove) { + if (typeof this.manualColumnPositions[col] === 'undefined') { + this.manualColumnPositions[col] = col; + } + return this.manualColumnPositions[col]; + } + return col; + }; + + this.getColHeader = function (col, TH) { + if (this.getSettings().manualColumnMove) { + var DIV = document.createElement('DIV'); + DIV.className = 'manualColumnMover'; + TH.firstChild.appendChild(DIV); + } + }; +} +var htManualColumnMove = new HandsontableManualColumnMove(); + +Handsontable.PluginHooks.add('beforeInit', htManualColumnMove.beforeInit); +Handsontable.PluginHooks.add('afterInit', htManualColumnMove.afterInit); +Handsontable.PluginHooks.add('afterGetColHeader', htManualColumnMove.getColHeader); +Handsontable.PluginHooks.add('modifyCol', htManualColumnMove.modifyCol); + +function HandsontableManualColumnResize() { + var pressed + , currentCol + , currentWidth + , autoresizeTimeout + , instance + , newSize + , start + , startX + , startWidth + , startOffset + , dblclick = 0 + , resizer = document.createElement('DIV') + , line = document.createElement('DIV') + , lineStyle = line.style; + + resizer.className = 'manualColumnResizer'; + + line.className = 'manualColumnResizerLine'; + lineStyle.position ='absolute'; + lineStyle.top = 0; + lineStyle.left = 0; + lineStyle.width = 0; + lineStyle.borderRight = '1px dashed #777'; + line.appendChild(resizer); + + $(document).mousemove(function (e) { + if (pressed) { + currentWidth = startWidth + (e.pageX - startX); + newSize = setManualSize(currentCol, currentWidth); //save col width + lineStyle.left = startOffset + currentWidth - 1 + 'px'; + if (lineStyle.display === 'none') { + lineStyle.display = 'block'; + } + } + }); + + $(document).mouseup(function () { + if (pressed) { + $('.manualColumnResizer.active').removeClass('active'); + pressed = false; + instance.forceFullRender = true; + instance.view.render(); //updates all + lineStyle.display = 'none'; + instance.PluginHooks.run('afterColumnResize', currentCol, newSize); + } + }); + + this.beforeInit = function () { + this.manualColumnWidths = []; + }; + + this.afterInit = function () { + if (this.getSettings().manualColumnResize) { + var that = this; + + this.rootElement.on('mousedown.handsontable', '.manualColumnResizer', function (e) { + if (autoresizeTimeout == null) { + autoresizeTimeout = setTimeout(function () { + if (dblclick >= 2) { + setManualSize(currentCol, htAutoColumnSize.determineColumnWidth.call(instance, currentCol)); + instance.PluginHooks.run('afterColumnResize', currentCol, newSize); + } + dblclick = 0; + autoresizeTimeout = null; + }, 500); + } + dblclick++; + }); + + this.rootElement.on('mousedown.handsontable', '.manualColumnResizer', function (e) { + var _resizer = e.target, + $table = that.rootElement.find('.htCore'), + $grandpa = $(_resizer.parentNode.parentNode); + + instance = that; + currentCol = _resizer.getAttribute('rel'); + start = $(that.rootElement[0].getElementsByTagName('col')[$grandpa.index()]); + pressed = true; + startX = e.pageX; + startWidth = start.width(); + currentWidth = startWidth; + + _resizer.className += ' active'; + + lineStyle.height = $table.height() + 'px'; + $table.parent()[0].appendChild(line); + startOffset = parseInt($grandpa.offset().left - $table.offset().left, 10); + lineStyle.left = startOffset + currentWidth - 1 + 'px'; + }); + } + }; + + var setManualSize = function (col, width) { + width = Math.max(width, 20); + width = Math.min(width, 500); + instance.manualColumnWidths[col] = width; + return width; + }; + + this.getColHeader = function (col, TH) { + if (this.getSettings().manualColumnResize) { + var DIV = document.createElement('DIV'); + DIV.className = 'manualColumnResizer'; + DIV.setAttribute('rel', col); + TH.firstChild.appendChild(DIV); + } + }; + + this.getColWidth = function (col, response) { + if (this.getSettings().manualColumnResize && this.manualColumnWidths[col]) { + response.width = this.manualColumnWidths[col]; + } + }; +} +var htManualColumnResize = new HandsontableManualColumnResize(); + +Handsontable.PluginHooks.add('beforeInit', htManualColumnResize.beforeInit); +Handsontable.PluginHooks.add('afterInit', htManualColumnResize.afterInit); +Handsontable.PluginHooks.add('afterGetColHeader', htManualColumnResize.getColHeader); +Handsontable.PluginHooks.add('afterGetColWidth', htManualColumnResize.getColWidth); + +function HandsontableObserveChanges() { + // begin shim code + // fragments from https://github.com/Starcounter-Jack/JSON-Patch/blob/master/src/json-patch-duplex.js + // + // json-patch.js 0.3 + // (c) 2013 Joachim Wester + // MIT license + var observeOps = { + 'new': function (patches, path) { + var patch = { + op: "add", + path: path + "/" + this.name, + value: this.object[this.name] + }; + patches.push(patch); + }, + deleted: function (patches, path) { + var patch = { + op: "remove", + path: path + "/" + this.name + }; + patches.push(patch); + }, + updated: function (patches, path) { + var patch = { + op: "replace", + path: path + "/" + this.name, + value: this.object[this.name] + }; + patches.push(patch); + } + }; + function markPaths(observer, node) { + for(var key in node) { + var kid = node[key]; + if(kid instanceof Object) { + Object.unobserve(kid, observer); + kid.____Path = node.____Path + "/" + key; + markPaths(observer, kid); + } + } + } + function clearPaths(observer, node) { + delete node.____Path; + Object.observe(node, observer); + for(var key in node) { + var kid = node[key]; + if(kid instanceof Object) { + clearPaths(observer, kid); + } + } + } + var beforeDict = []; + var callbacks = []; + function observe(obj, callback) { + var patches = []; + var root = obj; + if(Object.observe) { + var observer = function (arr) { + if(!root.___Path) { + Object.unobserve(root, observer); + root.____Path = ""; + markPaths(observer, root); + arr.forEach(function (elem) { + if(elem.name != "____Path") { + observeOps[elem.type].call(elem, patches, elem.object.____Path); + } + }); + clearPaths(observer, root); + } + if(callback) { + callback.call(patches); + } + }; + } else { + observer = { + }; + var mirror; + for(var i = 0, ilen = beforeDict.length; i < ilen; i++) { + if(beforeDict[i].obj === obj) { + mirror = beforeDict[i]; + break; + } + } + if(!mirror) { + mirror = { + obj: obj + }; + beforeDict.push(mirror); + } + mirror.value = JSON.parse(JSON.stringify(obj)); + if(callback) { + callbacks.push(callback); + var next; + var intervals = [ + 100 + ]; + var currentInterval = 0; + var dirtyCheck = function () { + var temp = generate(observer); + if(temp.length > 0) { + observer.patches = []; + callback.call(null, temp); + } + }; + var fastCheck = function (e) { + clearTimeout(next); + next = setTimeout(function () { + dirtyCheck(); + currentInterval = 0; + next = setTimeout(slowCheck, intervals[currentInterval++]); + }, 0); + }; + var slowCheck = function () { + dirtyCheck(); + if(currentInterval == intervals.length) { + currentInterval = intervals.length - 1; + } + next = setTimeout(slowCheck, intervals[currentInterval++]); + }; + [ + "mousedown", + "mouseup", + "keydown" + ].forEach(function (str) { + window.addEventListener(str, fastCheck); + }); + next = setTimeout(slowCheck, intervals[currentInterval++]); + } + } + observer.patches = patches; + observer.object = obj; + return _observe(observer, obj, patches); + } + + /// Listen to changes on an object tree, accumulate patches + function _observe(observer, obj, patches) { + if(Object.observe) { + Object.observe(obj, observer); + } + for(var key in obj) { + if(obj.hasOwnProperty(key)) { + var v = obj[key]; + if(v && typeof (v) === "object") { + _observe(observer, v, patches); + } + } + } + return observer; + } + function generate(observer) { + if(Object.observe) { + Object.deliverChangeRecords(observer); + } else { + var mirror; + for(var i = 0, ilen = beforeDict.length; i < ilen; i++) { + if(beforeDict[i].obj === observer.object) { + mirror = beforeDict[i]; + break; + } + } + _generate(mirror.value, observer.object, observer.patches, ""); + } + return observer.patches; + } + + function _generate(mirror, obj, patches, path) { + var newKeys = Object.keys(obj); + var oldKeys = Object.keys(mirror); + var changed = false; + var deleted = false; + var added = false; + for(var t = 0; t < oldKeys.length; t++) { + var key = oldKeys[t]; + var oldVal = mirror[key]; + if(obj.hasOwnProperty(key)) { + var newVal = obj[key]; + if(oldVal instanceof Object) { + _generate(oldVal, newVal, patches, path + "/" + key); + } else { + if(oldVal != newVal) { + changed = true; + patches.push({ + op: "replace", + path: path + "/" + key, + value: newVal + }); + mirror[key] = newVal; + } + } + } else { + patches.push({ + op: "remove", + path: path + "/" + key + }); + deleted = true; + } + } + if(!deleted && newKeys.length == oldKeys.length) { + return; + } + for(var t = 0; t < newKeys.length; t++) { + var key = newKeys[t]; + if(!mirror.hasOwnProperty(key)) { + patches.push({ + op: "add", + path: path + "/" + key, + value: obj[key] + }); + } + } + } + //end shim code + + + this.afterLoadData = function () { + if (!this.observer && this.getSettings().observeChanges) { + var that = this; + this.observer = observe(this.getData(), function () { + that.render(); + }); + } + }; +} +var htObserveChanges = new HandsontableObserveChanges(); + +Handsontable.PluginHooks.add('afterLoadData', htObserveChanges.afterLoadData); +/* + * jQuery.fn.autoResize 1.1+ + * -- + * https://github.com/warpech/jQuery.fn.autoResize + * + * This fork differs from others in a way that it autoresizes textarea in 2-dimensions (horizontally and vertically). + * It was originally forked from alexbardas's repo but maybe should be merged with dpashkevich's repo in future. + * + * originally forked from: + * https://github.com/jamespadolsey/jQuery.fn.autoResize + * which is now located here: + * https://github.com/alexbardas/jQuery.fn.autoResize + * though the mostly maintained for is here: + * https://github.com/dpashkevich/jQuery.fn.autoResize/network + * + * -- + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. */ + +(function($){ + + autoResize.defaults = { + onResize: function(){}, + animate: { + duration: 200, + complete: function(){} + }, + extraSpace: 50, + minHeight: 'original', + maxHeight: 500, + minWidth: 'original', + maxWidth: 500 + }; + + autoResize.cloneCSSProperties = [ + 'lineHeight', 'textDecoration', 'letterSpacing', + 'fontSize', 'fontFamily', 'fontStyle', 'fontWeight', + 'textTransform', 'textAlign', 'direction', 'wordSpacing', 'fontSizeAdjust', + 'padding' + ]; + + autoResize.cloneCSSValues = { + position: 'absolute', + top: -9999, + left: -9999, + opacity: 0, + overflow: 'hidden', + border: '1px solid black', + padding: '0.49em' //this must be about the width of caps W character + }; + + autoResize.resizableFilterSelector = 'textarea,input:not(input[type]),input[type=text],input[type=password]'; + + autoResize.AutoResizer = AutoResizer; + + $.fn.autoResize = autoResize; + + function autoResize(config) { + this.filter(autoResize.resizableFilterSelector).each(function(){ + new AutoResizer( $(this), config ); + }); + return this; + } + + function AutoResizer(el, config) { + + if(this.clones) return; + + this.config = $.extend({}, autoResize.defaults, config); + + this.el = el; + + this.nodeName = el[0].nodeName.toLowerCase(); + + this.previousScrollTop = null; + + if (config.maxWidth === 'original') config.maxWidth = el.width(); + if (config.minWidth === 'original') config.minWidth = el.width(); + if (config.maxHeight === 'original') config.maxHeight = el.height(); + if (config.minHeight === 'original') config.minHeight = el.height(); + + if (this.nodeName === 'textarea') { + el.css({ + resize: 'none', + overflowY: 'hidden' + }); + } + + el.data('AutoResizer', this); + + this.createClone(); + this.injectClone(); + this.bind(); + + } + + AutoResizer.prototype = { + + bind: function() { + + var check = $.proxy(function(){ + this.check(); + return true; + }, this); + + this.unbind(); + + this.el + .bind('keyup.autoResize', check) + //.bind('keydown.autoResize', check) + .bind('change.autoResize', check); + + this.check(null, true); + + }, + + unbind: function() { + this.el.unbind('.autoResize'); + }, + + createClone: function() { + + var el = this.el, + self = this, + config = this.config; + + this.clones = $(); + + if (config.minHeight !== 'original' || config.maxHeight !== 'original') { + this.hClone = el.clone().height('auto'); + this.clones = this.clones.add(this.hClone); + } + if (config.minWidth !== 'original' || config.maxWidth !== 'original') { + this.wClone = $('
    ').width('auto').css({ + whiteSpace: 'nowrap', + 'float': 'left' + }); + this.clones = this.clones.add(this.wClone); + } + + $.each(autoResize.cloneCSSProperties, function(i, p){ + self.clones.css(p, el.css(p)); + }); + + this.clones + .removeAttr('name') + .removeAttr('id') + .attr('tabIndex', -1) + .css(autoResize.cloneCSSValues); + + }, + + check: function(e, immediate) { + + var config = this.config, + wClone = this.wClone, + hClone = this.hClone, + el = this.el, + value = el.val(); + + if (wClone) { + + wClone.text(value); + + // Calculate new width + whether to change + var cloneWidth = wClone.outerWidth(), + newWidth = (cloneWidth + config.extraSpace) >= config.minWidth ? + cloneWidth + config.extraSpace : config.minWidth, + currentWidth = el.width(); + + newWidth = Math.min(newWidth, config.maxWidth); + + if ( + (newWidth < currentWidth && newWidth >= config.minWidth) || + (newWidth >= config.minWidth && newWidth <= config.maxWidth) + ) { + + config.onResize.call(el); + + el.scrollLeft(0); + + config.animate && !immediate ? + el.stop(1,1).animate({ + width: newWidth + }, config.animate) + : el.width(newWidth); + + } + + } + + if (hClone) { + + if (newWidth) { + hClone.width(newWidth); + } + + hClone.height(0).val(value).scrollTop(10000); + + var scrollTop = hClone[0].scrollTop + config.extraSpace; + + // Don't do anything if scrollTop hasen't changed: + if (this.previousScrollTop === scrollTop) { + return; + } + + this.previousScrollTop = scrollTop; + + if (scrollTop >= config.maxHeight) { + el.css('overflowY', ''); + return; + } + + el.css('overflowY', 'hidden'); + + if (scrollTop < config.minHeight) { + scrollTop = config.minHeight; + } + + config.onResize.call(el); + + // Either animate or directly apply height: + config.animate && !immediate ? + el.stop(1,1).animate({ + height: scrollTop + }, config.animate) + : el.height(scrollTop); + } + }, + + destroy: function() { + this.unbind(); + this.el.removeData('AutoResizer'); + this.clones.remove(); + delete this.el; + delete this.hClone; + delete this.wClone; + delete this.clones; + }, + + injectClone: function() { + ( + autoResize.cloneContainer || + (autoResize.cloneContainer = $('').appendTo('body')) + ).empty().append(this.clones); //this should be refactored so that a node is never cloned more than once + } + + }; + +})(jQuery); +/** + * SheetClip - Spreadsheet Clipboard Parser + * version 0.2 + * + * This tiny library transforms JavaScript arrays to strings that are pasteable by LibreOffice, OpenOffice, + * Google Docs and Microsoft Excel. + * + * Copyright 2012, Marcin Warpechowski + * Licensed under the MIT license. + * http://github.com/warpech/sheetclip/ + */ +/*jslint white: true*/ +(function (global) { + "use strict"; + + function countQuotes(str) { + return str.split('"').length - 1; + } + + global.SheetClip = { + parse: function (str) { + var r, rlen, rows, arr = [], a = 0, c, clen, multiline, last; + rows = str.split('\n'); + if (rows.length > 1 && rows[rows.length - 1] === '') { + rows.pop(); + } + for (r = 0, rlen = rows.length; r < rlen; r += 1) { + rows[r] = rows[r].split('\t'); + for (c = 0, clen = rows[r].length; c < clen; c += 1) { + if (!arr[a]) { + arr[a] = []; + } + if (multiline && c === 0) { + last = arr[a].length - 1; + arr[a][last] = arr[a][last] + '\n' + rows[r][0]; + if (multiline && (countQuotes(rows[r][0]) & 1)) { //& 1 is a bitwise way of performing mod 2 + multiline = false; + arr[a][last] = arr[a][last].substring(0, arr[a][last].length - 1).replace(/""/g, '"'); + } + } + else { + if (c === clen - 1 && rows[r][c].indexOf('"') === 0) { + arr[a].push(rows[r][c].substring(1).replace(/""/g, '"')); + multiline = true; + } + else { + arr[a].push(rows[r][c].replace(/""/g, '"')); + multiline = false; + } + } + } + if (!multiline) { + a += 1; + } + } + return arr; + }, + + stringify: function (arr) { + var r, rlen, c, clen, str = '', val; + for (r = 0, rlen = arr.length; r < rlen; r += 1) { + for (c = 0, clen = arr[r].length; c < clen; c += 1) { + if (c > 0) { + str += '\t'; + } + val = arr[r][c]; + if (typeof val === 'string') { + if (val.indexOf('\n') > -1) { + str += '"' + val.replace(/"/g, '""') + '"'; + } + else { + str += val; + } + } + else if (val === null || val === void 0) { //void 0 resolves to undefined + str += ''; + } + else { + str += val; + } + } + str += '\n'; + } + return str; + } + }; +}(window)); +/** + * CopyPaste.js + * Creates a textarea that stays hidden on the page and gets focused when user presses CTRL while not having a form input focused + * In future we may implement a better driver when better APIs are available + * @constructor + */ +function CopyPaste(listenerElement) { + var that = this + , style; + listenerElement = listenerElement || document.body; + + this.elDiv = document.createElement('DIV'); + style = this.elDiv.style; + style.position = 'fixed'; + style.top = 0; + style.left = 0; + listenerElement.appendChild(this.elDiv); + + this.elTextarea = document.createElement('TEXTAREA'); + this.elTextarea.className = 'copyPaste'; + style = this.elTextarea.style; + style.width = '1px'; + style.height = '1px'; + this.elDiv.appendChild(this.elTextarea); + + if (typeof style.opacity !== 'undefined') { + style.opacity = 0; + } + else { + /*@cc_on @if (@_jscript) + if(typeof style.filter === 'string') { + style.filter = 'alpha(opacity=0)'; + } + @end @*/ + } + + this._bindEvent(listenerElement, 'keydown', function (event) { + var isCtrlDown = false; + if (event.metaKey) { //mac + isCtrlDown = true; + } + else if (event.ctrlKey && navigator.userAgent.indexOf('Mac') === -1) { //pc + isCtrlDown = true; + } + + if (isCtrlDown) { + that.selectNodeText(that.elTextarea); + setTimeout(function () { + that.selectNodeText(that.elTextarea); + }, 0); + } + + /* 67 = c + * 86 = v + * 88 = x + */ + if (isCtrlDown && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88)) { + // that.selectNodeText(that.elTextarea); + + if (event.keyCode === 88) { //works in all browsers, incl. Opera < 12.12 + setTimeout(function () { + that.triggerCut(event); + }, 0); + } + else if (event.keyCode === 86) { + setTimeout(function () { + that.triggerPaste(event); + }, 0); + } + } + }); +} + +//http://jsperf.com/textara-selection +//http://stackoverflow.com/questions/1502385/how-can-i-make-this-code-work-in-ie +CopyPaste.prototype.selectNodeText = function (el) { + el.select(); +}; + +CopyPaste.prototype.copyable = function (str) { + if (typeof str !== 'string' && str.toString === void 0) { + throw new Error('copyable requires string parameter'); + } + this.elTextarea.value = str; +}; + +CopyPaste.prototype.onCopy = function (fn) { + this.copyCallback = fn; +}; + +CopyPaste.prototype.onCut = function (fn) { + this.cutCallback = fn; +}; + +CopyPaste.prototype.onPaste = function (fn) { + this.pasteCallback = fn; +}; + +CopyPaste.prototype.triggerCut = function (event) { + var that = this; + if (that.cutCallback) { + setTimeout(function () { + that.cutCallback(event); + }, 50); + } +}; + +CopyPaste.prototype.triggerPaste = function (event, str) { + var that = this; + if (that.pasteCallback) { + setTimeout(function () { + that.pasteCallback((str || that.elTextarea.value).replace(/\n$/, ''), event); //remove trailing newline + }, 50); + } +}; + +//http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/ +//http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization +CopyPaste.prototype._bindEvent = (function () { + if (document.addEventListener) { + return function (elem, type, cb) { + elem.addEventListener(type, cb, false); + }; + } + else { + return function (elem, type, cb) { + elem.attachEvent('on' + type, function () { + var e = window['event']; + e.target = e.srcElement; + e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement; + if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug + return cb.call(elem, e) + }); + }; + } +})(); +function WalkontableBorder(instance, settings) { + var style; + + //reference to instance + this.instance = instance; + this.settings = settings; + this.wtDom = this.instance.wtDom; + + this.main = document.createElement("div"); + style = this.main.style; + style.position = 'absolute'; + style.top = 0; + style.left = 0; + + for (var i = 0; i < 5; i++) { + var DIV = document.createElement('DIV'); + DIV.className = 'wtBorder ' + (settings.className || ''); + style = DIV.style; + style.backgroundColor = settings.border.color; + style.height = settings.border.width + 'px'; + style.width = settings.border.width + 'px'; + this.main.appendChild(DIV); + } + + this.top = this.main.childNodes[0]; + this.left = this.main.childNodes[1]; + this.bottom = this.main.childNodes[2]; + this.right = this.main.childNodes[3]; + + this.topStyle = this.top.style; + this.leftStyle = this.left.style; + this.bottomStyle = this.bottom.style; + this.rightStyle = this.right.style; + + this.corner = this.main.childNodes[4]; + this.corner.className += ' corner'; + this.cornerStyle = this.corner.style; + this.cornerStyle.width = '5px'; + this.cornerStyle.height = '5px'; + this.cornerStyle.border = '2px solid #FFF'; + + this.disappear(); + instance.wtTable.hider.appendChild(this.main); +} + +/** + * Show border around one or many cells + * @param {Array} corners + */ +WalkontableBorder.prototype.appear = function (corners) { + var isMultiple, fromTD, toTD, fromOffset, toOffset, containerOffset, top, minTop, left, minLeft, height, width; + if (this.disabled) { + return; + } + + var instance = this.instance + , fromRow + , fromColumn + , toRow + , toColumn + , hideTop = false + , hideLeft = false + , hideBottom = false + , hideRight = false + , i + , ilen + , s; + + if (!instance.wtTable.isRowInViewport(corners[0])) { + hideTop = true; + } + + if (!instance.wtTable.isRowInViewport(corners[2])) { + hideBottom = true; + } + + ilen = instance.wtTable.rowStrategy.countVisible(); + + for (i = 0; i < ilen; i++) { + s = instance.wtTable.rowFilter.visibleToSource(i); + if (s >= corners[0] && s <= corners[2]) { + fromRow = s; + break; + } + } + + for (i = ilen - 1; i >= 0; i--) { + s = instance.wtTable.rowFilter.visibleToSource(i); + if (s >= corners[0] && s <= corners[2]) { + toRow = s; + break; + } + } + + if (hideTop && hideBottom) { + hideLeft = true; + hideRight = true; + } + else { + if (!instance.wtTable.isColumnInViewport(corners[1])) { + hideLeft = true; + } + + if (!instance.wtTable.isColumnInViewport(corners[3])) { + hideRight = true; + } + + ilen = instance.wtTable.columnStrategy.countVisible(); + + for (i = 0; i < ilen; i++) { + s = instance.wtTable.columnFilter.visibleToSource(i); + if (s >= corners[1] && s <= corners[3]) { + fromColumn = s; + break; + } + } + + for (i = ilen - 1; i >= 0; i--) { + s = instance.wtTable.columnFilter.visibleToSource(i); + if (s >= corners[1] && s <= corners[3]) { + toColumn = s; + break; + } + } + } + + if (fromRow !== void 0 && fromColumn !== void 0) { + isMultiple = (fromRow !== toRow || fromColumn !== toColumn); + fromTD = instance.wtTable.getCell([fromRow, fromColumn]); + toTD = isMultiple ? instance.wtTable.getCell([toRow, toColumn]) : fromTD; + fromOffset = this.wtDom.offset(fromTD); + toOffset = isMultiple ? this.wtDom.offset(toTD) : fromOffset; + containerOffset = this.wtDom.offset(instance.wtTable.TABLE); + + minTop = fromOffset.top; + height = toOffset.top + this.wtDom.outerHeight(toTD) - minTop; + minLeft = fromOffset.left; + width = toOffset.left + this.wtDom.outerWidth(toTD) - minLeft; + + top = minTop - containerOffset.top - 1; + left = minLeft - containerOffset.left - 1; + + var style = this.wtDom.getComputedStyle(fromTD); + if (parseInt(style['borderTopWidth'], 10) > 0) { + top += 1; + height -= 1; + } + if (parseInt(style['borderLeftWidth'], 10) > 0) { + left += 1; + width -= 1; + } + } + else { + this.disappear(); + return; + } + + if (hideTop) { + this.topStyle.display = 'none'; + } + else { + this.topStyle.top = top + 'px'; + this.topStyle.left = left + 'px'; + this.topStyle.width = width + 'px'; + this.topStyle.display = 'block'; + } + + if (hideLeft) { + this.leftStyle.display = 'none'; + } + else { + this.leftStyle.top = top + 'px'; + this.leftStyle.left = left + 'px'; + this.leftStyle.height = height + 'px'; + this.leftStyle.display = 'block'; + } + + var delta = Math.floor(this.settings.border.width / 2); + + if (hideBottom) { + this.bottomStyle.display = 'none'; + } + else { + this.bottomStyle.top = top + height - delta + 'px'; + this.bottomStyle.left = left + 'px'; + this.bottomStyle.width = width + 'px'; + this.bottomStyle.display = 'block'; + } + + if (hideRight) { + this.rightStyle.display = 'none'; + } + else { + this.rightStyle.top = top + 'px'; + this.rightStyle.left = left + width - delta + 'px'; + this.rightStyle.height = height + 1 + 'px'; + this.rightStyle.display = 'block'; + } + + if (hideBottom || hideRight || !this.hasSetting(this.settings.border.cornerVisible)) { + this.cornerStyle.display = 'none'; + } + else { + this.cornerStyle.top = top + height - 4 + 'px'; + this.cornerStyle.left = left + width - 4 + 'px'; + this.cornerStyle.display = 'block'; + } +}; + +/** + * Hide border + */ +WalkontableBorder.prototype.disappear = function () { + this.topStyle.display = 'none'; + this.leftStyle.display = 'none'; + this.bottomStyle.display = 'none'; + this.rightStyle.display = 'none'; + this.cornerStyle.display = 'none'; +}; + +WalkontableBorder.prototype.hasSetting = function (setting) { + if (typeof setting === 'function') { + return setting(); + } + return !!setting; +}; +/** + * WalkontableCellFilter + * @constructor + */ +function WalkontableCellFilter() { + this.offset = 0; + this.total = 0; + this.fixedCount = 0; +} + +WalkontableCellFilter.prototype.source = function (n) { + return n; +}; + +WalkontableCellFilter.prototype.offsetted = function (n) { + return n + this.offset; +}; + +WalkontableCellFilter.prototype.unOffsetted = function (n) { + return n - this.offset; +}; + +WalkontableCellFilter.prototype.fixed = function (n) { + if (n < this.fixedCount) { + return n - this.offset; + } + else { + return n; + } +}; + +WalkontableCellFilter.prototype.unFixed = function (n) { + if (n < this.fixedCount) { + return n + this.offset; + } + else { + return n; + } +}; + +WalkontableCellFilter.prototype.visibleToSource = function (n) { + return this.source(this.offsetted(this.fixed(n))); +}; + +WalkontableCellFilter.prototype.sourceToVisible = function (n) { + return this.source(this.unOffsetted(this.unFixed(n))); +}; +/** + * WalkontableCellStrategy + * @constructor + */ +function WalkontableCellStrategy() { +} + +WalkontableCellStrategy.prototype.getSize = function (index) { + return this.cellSizes[index]; +}; + +WalkontableCellStrategy.prototype.getContainerSize = function (proposedSize) { + return typeof this.containerSizeFn === 'function' ? this.containerSizeFn(proposedSize) : this.containerSizeFn; +}; + +WalkontableCellStrategy.prototype.countVisible = function () { + return this.cellCount; +}; + +WalkontableCellStrategy.prototype.isLastIncomplete = function () { + return this.remainingSize > 0; +}; +/** + * WalkontableClassNameList + * @constructor + */ +function WalkontableClassNameCache() { + this.cache = []; +} + +WalkontableClassNameCache.prototype.add = function (r, c, cls) { + if (!this.cache[r]) { + this.cache[r] = []; + } + if (!this.cache[r][c]) { + this.cache[r][c] = []; + } + this.cache[r][c][cls] = true; +}; + +WalkontableClassNameCache.prototype.test = function (r, c, cls) { + return (this.cache[r] && this.cache[r][c] && this.cache[r][c][cls]); +}; +/** + * WalkontableColumnFilter + * @constructor + */ +function WalkontableColumnFilter() { + this.countTH = 0; +} + +WalkontableColumnFilter.prototype = new WalkontableCellFilter(); + +WalkontableColumnFilter.prototype.readSettings = function (instance) { + this.offset = instance.wtSettings.settings.offsetColumn; + this.total = instance.getSetting('totalColumns'); + this.fixedCount = instance.getSetting('fixedColumnsLeft'); + this.countTH = instance.getSetting('rowHeaders').length; +}; + +WalkontableColumnFilter.prototype.offsettedTH = function (n) { + return n - this.countTH; +}; + +WalkontableColumnFilter.prototype.unOffsettedTH = function (n) { + return n + this.countTH; +}; + +WalkontableColumnFilter.prototype.visibleRowHeadedColumnToSourceColumn = function (n) { + return this.visibleToSource(this.offsettedTH(n)); +}; + +WalkontableColumnFilter.prototype.sourceColumnToVisibleRowHeadedColumn = function (n) { + return this.unOffsettedTH(this.sourceToVisible(n)); +}; +/** + * WalkontableColumnStrategy + * @param containerSizeFn + * @param sizeAtIndex + * @param strategy - all, last, none + * @constructor + */ +function WalkontableColumnStrategy(containerSizeFn, sizeAtIndex, strategy) { + var size + , i = 0; + + this.containerSizeFn = containerSizeFn; + this.cellSizesSum = 0; + this.cellSizes = []; + this.cellStretch = []; + this.cellCount = 0; + this.remainingSize = 0; + this.strategy = strategy; + + //step 1 - determine cells that fit containerSize and cache their widths + while (true) { + size = sizeAtIndex(i); + if (size === void 0) { + break; //total columns exceeded + } + if (this.cellSizesSum >= this.getContainerSize(this.cellSizesSum + size)) { + break; //total width exceeded + } + this.cellSizes.push(size); + this.cellSizesSum += size; + this.cellCount++; + + i++; + } + + var containerSize = this.getContainerSize(this.cellSizesSum); + this.remainingSize = this.cellSizesSum - containerSize; + //negative value means the last cell is fully visible and there is some space left for stretching + //positive value means the last cell is not fully visible +} + +WalkontableColumnStrategy.prototype = new WalkontableCellStrategy(); + +WalkontableColumnStrategy.prototype.getSize = function (index) { + return this.cellSizes[index] + (this.cellStretch[index] || 0); +}; + +WalkontableColumnStrategy.prototype.stretch = function () { + //step 2 - apply stretching strategy + var containerSize = this.getContainerSize(this.cellSizesSum) + , i = 0; + this.remainingSize = this.cellSizesSum - containerSize; + + this.cellStretch.length = 0; //clear previous stretch + + if (this.strategy === 'all') { + if (this.remainingSize < 0) { + var ratio = containerSize / this.cellSizesSum; + var newSize; + + while (i < this.cellCount - 1) { //"i < this.cellCount - 1" is needed because last cellSize is adjusted after the loop + newSize = Math.floor(ratio * this.cellSizes[i]); + this.remainingSize += newSize - this.cellSizes[i]; + this.cellStretch[i] = newSize - this.cellSizes[i]; + i++; + } + this.cellStretch[this.cellCount - 1] = -this.remainingSize; + this.remainingSize = 0; + } + } + else if (this.strategy === 'last') { + if (this.remainingSize < 0) { + this.cellStretch[this.cellCount - 1] = -this.remainingSize; + this.remainingSize = 0; + } + } +}; +function Walkontable(settings) { + var that = this, + originalHeaders = []; + + //bootstrap from settings + this.wtSettings = new WalkontableSettings(this, settings); + this.wtDom = new WalkontableDom(); + this.wtTable = new WalkontableTable(this); + this.wtScroll = new WalkontableScroll(this); + this.wtScrollbars = new WalkontableScrollbars(this); + this.wtViewport = new WalkontableViewport(this); + this.wtWheel = new WalkontableWheel(this); + this.wtEvent = new WalkontableEvent(this); + + //find original headers + if (this.wtTable.THEAD.childNodes.length && this.wtTable.THEAD.childNodes[0].childNodes.length) { + for (var c = 0, clen = this.wtTable.THEAD.childNodes[0].childNodes.length; c < clen; c++) { + originalHeaders.push(this.wtTable.THEAD.childNodes[0].childNodes[c].innerHTML); + } + if (!this.getSetting('columnHeaders').length) { + this.update('columnHeaders', [function (column, TH) { + that.wtDom.avoidInnerHTML(TH, originalHeaders[column]); + }]); + } + } + + //initialize selections + this.selections = {}; + var selectionsSettings = this.getSetting('selections'); + if (selectionsSettings) { + for (var i in selectionsSettings) { + if (selectionsSettings.hasOwnProperty(i)) { + this.selections[i] = new WalkontableSelection(this, selectionsSettings[i]); + } + } + } + + this.drawn = false; + this.drawInterrupted = false; +} + +Walkontable.prototype.draw = function (selectionsOnly) { + this.drawInterrupted = false; + if (!selectionsOnly && !this.wtDom.isVisible(this.wtTable.TABLE)) { + this.drawInterrupted = true; //draw interrupted because TABLE is not visible + return; + } + + this.getSetting('beforeDraw', !selectionsOnly); + selectionsOnly = selectionsOnly && this.getSetting('offsetRow') === this.lastOffsetRow && this.getSetting('offsetColumn') === this.lastOffsetColumn; + if (this.drawn) { //fix offsets that might have changed + this.scrollVertical(0); + this.scrollHorizontal(0); + } + this.lastOffsetRow = this.getSetting('offsetRow'); + this.lastOffsetColumn = this.getSetting('offsetColumn'); + this.wtTable.draw(selectionsOnly); + this.getSetting('onDraw'); + return this; +}; + +Walkontable.prototype.update = function (settings, value) { + return this.wtSettings.update(settings, value); +}; + +Walkontable.prototype.scrollVertical = function (delta) { + return this.wtScroll.scrollVertical(delta); +}; + +Walkontable.prototype.scrollHorizontal = function (delta) { + return this.wtScroll.scrollHorizontal(delta); +}; + +Walkontable.prototype.scrollViewport = function (coords) { + this.wtScroll.scrollViewport(coords); + return this; +}; + +Walkontable.prototype.getViewport = function () { + return [ + this.wtTable.rowFilter.visibleToSource(0), + this.wtTable.columnFilter.visibleToSource(0), + this.wtTable.getLastVisibleRow(), + this.wtTable.getLastVisibleColumn() + ]; +}; + +Walkontable.prototype.getSetting = function (key, param1, param2, param3) { + return this.wtSettings.getSetting(key, param1, param2, param3); +}; + +Walkontable.prototype.hasSetting = function (key) { + return this.wtSettings.has(key); +}; + +Walkontable.prototype.destroy = function () { + this.wtScrollbars.destroy(); + clearTimeout(this.wheelTimeout); + clearTimeout(this.dblClickTimeout); +}; +function WalkontableDom() { +} + +//goes up the DOM tree (including given element) until it finds an element that matches the nodeName +WalkontableDom.prototype.closest = function (elem, nodeNames) { + while (elem != null) { + if (elem.nodeType === 1 && nodeNames.indexOf(elem.nodeName) > -1) { + return elem; + } + elem = elem.parentNode; + } + return null; +}; + +WalkontableDom.prototype.prevSiblings = function (elem) { + var out = []; + while ((elem = elem.previousSibling) != null) { + if (elem.nodeType === 1) { + out.push(elem); + } + } + return out; +}; + +if (document.documentElement.classList) { + // HTML5 classList API + WalkontableDom.prototype.hasClass = function (ele, cls) { + return ele.classList.contains(cls); + }; + + WalkontableDom.prototype.addClass = function (ele, cls) { + ele.classList.add(cls); + }; + + WalkontableDom.prototype.removeClass = function (ele, cls) { + ele.classList.remove(cls); + }; +} +else { + //http://snipplr.com/view/3561/addclass-removeclass-hasclass/ + WalkontableDom.prototype.hasClass = function (ele, cls) { + return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); + }; + + WalkontableDom.prototype.addClass = function (ele, cls) { + if (!this.hasClass(ele, cls)) ele.className += " " + cls; + }; + + WalkontableDom.prototype.removeClass = function (ele, cls) { + if (this.hasClass(ele, cls)) { //is this really needed? + var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); + ele.className = ele.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); //last 2 replaces do right trim (see http://blog.stevenlevithan.com/archives/faster-trim-javascript) + } + }; +} + +/*//http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/ + WalkontableDom.prototype.addEvent = (function () { + var that = this; + if (document.addEventListener) { + return function (elem, type, cb) { + if ((elem && !elem.length) || elem === window) { + elem.addEventListener(type, cb, false); + } + else if (elem && elem.length) { + var len = elem.length; + for (var i = 0; i < len; i++) { + that.addEvent(elem[i], type, cb); + } + } + }; + } + else { + return function (elem, type, cb) { + if ((elem && !elem.length) || elem === window) { + elem.attachEvent('on' + type, function () { + + //normalize + //http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization + var e = window['event']; + e.target = e.srcElement; + //e.offsetX = e.layerX; + //e.offsetY = e.layerY; + e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement; + if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug + + return cb.call(elem, e) + }); + } + else if (elem.length) { + var len = elem.length; + for (var i = 0; i < len; i++) { + that.addEvent(elem[i], type, cb); + } + } + }; + } + })(); + + WalkontableDom.prototype.triggerEvent = function (element, eventName, target) { + var event; + if (document.createEvent) { + event = document.createEvent("MouseEvents"); + event.initEvent(eventName, true, true); + } else { + event = document.createEventObject(); + event.eventType = eventName; + } + + event.eventName = eventName; + event.target = target; + + if (document.createEvent) { + target.dispatchEvent(event); + } else { + target.fireEvent("on" + event.eventType, event); + } + };*/ + +WalkontableDom.prototype.removeTextNodes = function (elem, parent) { + if (elem.nodeType === 3) { + parent.removeChild(elem); //bye text nodes! + } + else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(elem.nodeName) > -1) { + var childs = elem.childNodes; + for (var i = childs.length - 1; i >= 0; i--) { + this.removeTextNodes(childs[i], elem); + } + } +}; + +/** + * Remove childs function + * WARNING - this doesn't unload events and data attached by jQuery + * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9 + * @param element + * @returns {void} + */ +// +WalkontableDom.prototype.empty = function (element) { + var child; + while (child = element.lastChild) { + element.removeChild(child); + } +}; + +/** + * Insert content into element trying avoid innerHTML method. + * @return {void} + */ +WalkontableDom.prototype.avoidInnerHTML = function (element, content) { + if ((/(<(.*)>|&(.*);)/g).test(content)) { + element.innerHTML = content; + } else { + var child; + while (child = element.lastChild) { + element.removeChild(child); + } + + element.appendChild(document.createTextNode(content)); + } +}; + +/** + * Returns true if element is attached to the DOM and visible, false otherwise + * @param elem + * @returns {boolean} + */ +WalkontableDom.prototype.isVisible = function (elem) { + //fast method + try {//try/catch performance is not a problem here: http://jsperf.com/try-catch-performance-overhead/7 + if (!elem.offsetParent) { + return false; //fixes problem with UI Bootstrap directive + } + } + catch (e) { + return false; //IE7-8 throws "Unspecified error" when offsetParent is not found - we catch it here + } + +// if (elem.offsetWidth > 0 || (elem.parentNode && elem.parentNode.offsetWidth > 0)) { //IE10 was mistaken here + if (elem.offsetWidth > 0) { + return true; + } + + //slow method + var next = elem; + while (next !== document.documentElement) { //until reached + if (next === null) { //parent detached from DOM + return false; + } + else if (next.nodeType === 11) { + if (next.nodeName === '#document-fragment') { //Shadow DOM + return true; + } + else { //IE7 reports nodeType === 11 after detaching element from DOM + return false; + } + } + else if (next.style.display === 'none') { + return false; + } + /*else if (next !== elem && next.offsetWidth === 0) { + //this is the technique used by jQuery is(':visible') + //but in IE7, clientWidth & offsetWidth sometimes returns 0 when it shouldn't + return false; + + compare with jQuery :visible selector (search jquery.js for `reliableHiddenOffsets`) + }*/ + next = next.parentNode; + } + return true; +}; + +/** + * Returns elements top and left offset relative to the document. In our usage case compatible with jQuery but 2x faster + * @param {HTMLElement} elem + * @return {Object} + */ +WalkontableDom.prototype.offset = function (elem) { + var offsetLeft = elem.offsetLeft + , offsetTop = elem.offsetTop + , lastElem = elem; + + while (elem = elem.offsetParent) { + if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0 + break; + } + offsetLeft += elem.offsetLeft; + offsetTop += elem.offsetTop; + lastElem = elem; + } + + if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6 + //if(lastElem !== document.body) { //faster but does gives false positive in Firefox + offsetLeft += window.pageXOffset || document.documentElement.scrollLeft; + offsetTop += window.pageYOffset || document.documentElement.scrollTop; + } + + return { + left: offsetLeft, + top: offsetTop + }; +}; + +WalkontableDom.prototype.getComputedStyle = function (elem) { + return elem.currentStyle || document.defaultView.getComputedStyle(elem); +}; + +WalkontableDom.prototype.outerWidth = function (elem) { + return elem.offsetWidth; +}; + +WalkontableDom.prototype.outerHeight = function (elem) { + return elem.offsetHeight; +}; +function WalkontableEvent(instance) { + var that = this; + + //reference to instance + this.instance = instance; + + this.wtDom = this.instance.wtDom; + + var dblClickOrigin = [null, null, null, null]; + this.instance.dblClickTimeout = null; + + var onMouseDown = function (event) { + var cell = that.parentCell(event.target); + + if (cell.TD && cell.TD.nodeName === 'TD') { + if (that.instance.hasSetting('onCellMouseDown')) { + that.instance.getSetting('onCellMouseDown', event, cell.coords, cell.TD); + } + } + else if (that.wtDom.hasClass(event.target, 'corner')) { + that.instance.getSetting('onCellCornerMouseDown', event, event.target); + } + + if (event.button !== 2) { //if not right mouse button + if (cell.TD && cell.TD.nodeName === 'TD') { + dblClickOrigin.shift(); + dblClickOrigin.push(cell.TD); + } + else if (that.wtDom.hasClass(event.target, 'corner')) { + dblClickOrigin.shift(); + dblClickOrigin.push(event.target); + } + } + }; + + var lastMouseOver; + var onMouseOver = function (event) { + if (that.instance.hasSetting('onCellMouseOver')) { + var TD = that.wtDom.closest(event.target, ['TD', 'TH']); + if (TD && TD !== lastMouseOver) { + lastMouseOver = TD; + if (TD.nodeName === 'TD') { + that.instance.getSetting('onCellMouseOver', event, that.instance.wtTable.getCoords(TD), TD); + } + } + } + }; + + var onMouseUp = function (event) { + if (event.button !== 2) { //if not right mouse button + var cell = that.parentCell(event.target); + + if (cell.TD && cell.TD.nodeName === 'TD') { + dblClickOrigin.shift(); + dblClickOrigin.push(cell.TD); + } + else { + dblClickOrigin.shift(); + dblClickOrigin.push(event.target); + } + + if (dblClickOrigin[3] !== null && dblClickOrigin[3] === dblClickOrigin[2]) { + if (that.instance.dblClickTimeout && dblClickOrigin[2] === dblClickOrigin[1] && dblClickOrigin[1] === dblClickOrigin[0]) { + if (cell.TD) { + that.instance.getSetting('onCellDblClick', event, cell.coords, cell.TD); + } + else if (that.wtDom.hasClass(event.target, 'corner')) { + that.instance.getSetting('onCellCornerDblClick', event, cell.coords, cell.TD); + } + + clearTimeout(that.instance.dblClickTimeout); + that.instance.dblClickTimeout = null; + } + else { + clearTimeout(that.instance.dblClickTimeout); + that.instance.dblClickTimeout = setTimeout(function () { + that.instance.dblClickTimeout = null; + }, 500); + } + } + } + }; + + $(this.instance.wtTable.parent).on('mousedown', onMouseDown); + $(this.instance.wtTable.TABLE).on('mouseover', onMouseOver); + $(this.instance.wtTable.parent).on('mouseup', onMouseUp); +} + +WalkontableEvent.prototype.parentCell = function (elem) { + var cell = {}; + cell.TD = this.wtDom.closest(elem, ['TD', 'TH']); + if (cell.TD) { + cell.coords = this.instance.wtTable.getCoords(cell.TD); + } + else if (!cell.TD && this.wtDom.hasClass(elem, 'wtBorder') && this.wtDom.hasClass(elem, 'current') && !this.wtDom.hasClass(elem, 'corner')) { + cell.coords = this.instance.selections.current.selected[0]; + cell.TD = this.instance.wtTable.getCell(cell.coords); + } + return cell; +}; +function walkontableRangesIntersect() { + var from = arguments[0]; + var to = arguments[1]; + for (var i = 1, ilen = arguments.length / 2; i < ilen; i++) { + if (from <= arguments[2 * i + 1] && to >= arguments[2 * i]) { + return true; + } + } + return false; +} +//http://stackoverflow.com/questions/3629183/why-doesnt-indexof-work-on-an-array-ie8 +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (elt /*, from*/) { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + +/** + * http://notes.jetienne.com/2011/05/18/cancelRequestAnimFrame-for-paul-irish-requestAnimFrame.html + */ +window.requestAnimFrame = (function () { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (/* function */ callback, /* DOMElement */ element) { + return window.setTimeout(callback, 1000 / 60); + }; +})(); + +window.cancelRequestAnimFrame = (function () { + return window.cancelAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.mozCancelRequestAnimationFrame || + window.oCancelRequestAnimationFrame || + window.msCancelRequestAnimationFrame || + clearTimeout +})(); + +//http://snipplr.com/view/13523/ +//modified for speed +//http://jsperf.com/getcomputedstyle-vs-style-vs-css/8 +if (!window.getComputedStyle) { + (function () { + var elem; + + var styleObj = { + getPropertyValue: function getPropertyValue(prop) { + if (prop == 'float') prop = 'styleFloat'; + return elem.currentStyle[prop.toUpperCase()] || null; + } + } + + window.getComputedStyle = function (el) { + elem = el; + return styleObj; + } + })(); +} +/** + * WalkontableRowFilter + * @constructor + */ +function WalkontableRowFilter() { +} + +WalkontableRowFilter.prototype = new WalkontableCellFilter(); + +WalkontableRowFilter.prototype.readSettings = function (instance) { + this.offset = instance.wtSettings.settings.offsetRow; + this.total = instance.getSetting('totalRows'); + this.fixedCount = instance.getSetting('fixedRowsTop'); +}; +/** + * WalkontableRowStrategy + * @param containerSizeFn + * @param sizeAtIndex + * @constructor + */ +function WalkontableRowStrategy(containerSizeFn, sizeAtIndex) { + this.containerSizeFn = containerSizeFn; + this.sizeAtIndex = sizeAtIndex; + this.cellSizesSum = 0; + this.cellSizes = []; + this.cellCount = 0; + this.remainingSize = -Infinity; +} + +WalkontableRowStrategy.prototype = new WalkontableCellStrategy(); + +WalkontableRowStrategy.prototype.add = function (i, TD) { + if (this.remainingSize < 0) { + var size = this.sizeAtIndex(i, TD); + if (size === void 0) { + return; //total rows exceeded + } + var containerSize = this.getContainerSize(this.cellSizesSum + size); + this.cellSizes.push(size); + this.cellSizesSum += size; + this.cellCount++; + this.remainingSize = this.cellSizesSum - containerSize; + } +}; + +WalkontableRowStrategy.prototype.remove = function () { + var size = this.cellSizes.pop(); + this.cellSizesSum -= size; + this.cellCount--; + this.remainingSize += size; +}; +function WalkontableScroll(instance) { + this.instance = instance; +} + +WalkontableScroll.prototype.scrollVertical = function (delta) { + if (!this.instance.drawn) { + throw new Error('scrollVertical can only be called after table was drawn to DOM'); + } + + var instance = this.instance + , newOffset + , offset = instance.getSetting('offsetRow') + , fixedCount = instance.getSetting('fixedRowsTop') + , total = instance.getSetting('totalRows') + , maxSize = instance.wtViewport.getViewportHeight(); + + if (total > 0) { + newOffset = this.scrollLogicVertical(delta, offset, total, fixedCount, maxSize, function (row) { + if (row - offset < fixedCount && row - offset >= 0) { + return instance.getSetting('rowHeight', row - offset); + } + else { + return instance.getSetting('rowHeight', row); + } + }, function (isReverse) { + instance.wtTable.verticalRenderReverse = isReverse; + }); + } + else { + newOffset = 0; + } + + if (newOffset !== offset) { + this.instance.wtScrollbars.vertical.scrollTo(newOffset); + } + return instance; +}; + +WalkontableScroll.prototype.scrollHorizontal = function (delta) { + if (!this.instance.drawn) { + throw new Error('scrollHorizontal can only be called after table was drawn to DOM'); + } + + var instance = this.instance + , newOffset + , offset = instance.getSetting('offsetColumn') + , fixedCount = instance.getSetting('fixedColumnsLeft') + , total = instance.getSetting('totalColumns') + , maxSize = instance.wtViewport.getViewportWidth(); + + if (total > 0) { + newOffset = this.scrollLogicHorizontal(delta, offset, total, fixedCount, maxSize, function (col) { + if (col - offset < fixedCount && col - offset >= 0) { + return instance.getSetting('columnWidth', col - offset); + } + else { + return instance.getSetting('columnWidth', col); + } + }); + } + else { + newOffset = 0; + } + + if (newOffset !== offset) { + this.instance.wtScrollbars.horizontal.scrollTo(newOffset); + } + return instance; +}; + +WalkontableScroll.prototype.scrollLogicVertical = function (delta, offset, total, fixedCount, maxSize, cellSizeFn, setReverseRenderFn) { + var newOffset = offset + delta; + + if (newOffset >= total - fixedCount) { + newOffset = total - fixedCount - 1; + setReverseRenderFn(true); + } + else if (newOffset < 0) { + newOffset = 0; + } + + return newOffset; +}; + +WalkontableScroll.prototype.scrollLogicHorizontal = function (delta, offset, total, fixedCount, maxSize, cellSizeFn) { + var newOffset = offset + delta + , sum = 0 + , col; + + if (newOffset > fixedCount) { + if (newOffset >= total - fixedCount) { + newOffset = total - fixedCount - 1; + } + + col = newOffset; + while (sum < maxSize && col < total) { + sum += cellSizeFn(col); + col++; + } + + if (sum < maxSize) { + while (newOffset > 0) { + //if sum still less than available width, we cannot scroll that far (must move offset to the left) + sum += cellSizeFn(newOffset - 1); + if (sum < maxSize) { + newOffset--; + } + else { + break; + } + } + } + } + else if (newOffset < 0) { + newOffset = 0; + } + + return newOffset; +}; + +/** + * Scrolls viewport to a cell by minimum number of cells + */ +WalkontableScroll.prototype.scrollViewport = function (coords) { + var offsetRow = this.instance.getSetting('offsetRow') + , offsetColumn = this.instance.getSetting('offsetColumn') + , lastVisibleRow = this.instance.wtTable.getLastVisibleRow() + , lastVisibleColumn = this.instance.wtTable.getLastVisibleColumn() + , totalRows = this.instance.getSetting('totalRows') + , totalColumns = this.instance.getSetting('totalColumns') + , fixedRowsTop = this.instance.getSetting('fixedRowsTop') + , fixedColumnsLeft = this.instance.getSetting('fixedColumnsLeft'); + + if (coords[0] < 0 || coords[0] > totalRows - 1) { + throw new Error('row ' + coords[0] + ' does not exist'); + } + else if (coords[1] < 0 || coords[1] > totalColumns - 1) { + throw new Error('column ' + coords[1] + ' does not exist'); + } + + if (coords[0] > lastVisibleRow) { +// this.scrollVertical(coords[0] - lastVisibleRow + 1); + this.scrollVertical(coords[0] - fixedRowsTop - offsetRow); + this.instance.wtTable.verticalRenderReverse = true; + } + else if (coords[0] === lastVisibleRow && this.instance.wtTable.rowStrategy.isLastIncomplete()) { +// this.scrollVertical(coords[0] - lastVisibleRow + 1); + this.scrollVertical(coords[0] - fixedRowsTop - offsetRow); + this.instance.wtTable.verticalRenderReverse = true; + } + else if (coords[0] - fixedRowsTop < offsetRow) { + this.scrollVertical(coords[0] - fixedRowsTop - offsetRow); + } + else { + this.scrollVertical(0); //Craig's issue: remove row from the last scroll page should scroll viewport a row up if needed + } + + if (coords[1] > lastVisibleColumn) { + this.scrollHorizontal(coords[1] - lastVisibleColumn + 1); + } + else if (coords[1] === lastVisibleColumn && this.instance.wtTable.columnStrategy.isLastIncomplete()) { + this.scrollHorizontal(coords[1] - lastVisibleColumn + 1); + } + else if (coords[1] - fixedColumnsLeft < offsetColumn) { + this.scrollHorizontal(coords[1] - fixedColumnsLeft - offsetColumn); + } + else { + this.scrollHorizontal(0); //Craig's issue + } + + return this.instance; +}; + +function WalkontableScrollbar() { +} + +WalkontableScrollbar.prototype.init = function () { + var that = this; + + //reference to instance + this.$table = $(this.instance.wtTable.TABLE); + + //create elements + this.slider = document.createElement('DIV'); + this.sliderStyle = this.slider.style; + this.sliderStyle.position = 'absolute'; + this.sliderStyle.top = '0'; + this.sliderStyle.left = '0'; + this.sliderStyle.display = 'none'; + this.slider.className = 'dragdealer ' + this.type; + + this.handle = document.createElement('DIV'); + this.handleStyle = this.handle.style; + this.handle.className = 'handle'; + + this.slider.appendChild(this.handle); + this.container = this.instance.wtTable.parent; + this.container.appendChild(this.slider); + + var firstRun = true; + this.dragTimeout = null; + var dragDelta; + var dragRender = function () { + that.onScroll(dragDelta); + }; + + this.dragdealer = new Dragdealer(this.slider, { + vertical: (this.type === 'vertical'), + horizontal: (this.type === 'horizontal'), + slide: false, + speed: 100, + animationCallback: function (x, y) { + if (firstRun) { + firstRun = false; + return; + } + that.skipRefresh = true; + dragDelta = that.type === 'vertical' ? y : x; + if (that.dragTimeout === null) { + that.dragTimeout = setInterval(dragRender, 100); + dragRender(); + } + }, + callback: function (x, y) { + that.skipRefresh = false; + clearInterval(that.dragTimeout); + that.dragTimeout = null; + dragDelta = that.type === 'vertical' ? y : x; + that.onScroll(dragDelta); + } + }); + this.skipRefresh = false; +}; + +WalkontableScrollbar.prototype.onScroll = function (delta) { + if (this.instance.drawn) { + this.readSettings(); + if (this.total > this.visibleCount) { + var newOffset = Math.round(this.handlePosition * this.total / this.sliderSize); + + if (delta === 1) { + if (this.type === 'vertical') { + this.instance.scrollVertical(Infinity).draw(); + } + else { + this.instance.scrollHorizontal(Infinity).draw(); + } + } + else if (newOffset !== this.offset) { //is new offset different than old offset + if (this.type === 'vertical') { + this.instance.scrollVertical(newOffset - this.offset).draw(); + } + else { + this.instance.scrollHorizontal(newOffset - this.offset).draw(); + } + } + else { + this.refresh(); + } + } + } +}; + +/** + * Returns what part of the scroller should the handle take + * @param viewportCount {Number} number of visible rows or columns + * @param totalCount {Number} total number of rows or columns + * @return {Number} 0..1 + */ +WalkontableScrollbar.prototype.getHandleSizeRatio = function (viewportCount, totalCount) { + if (!totalCount || viewportCount > totalCount) { + return 1; + } + return viewportCount / totalCount; +}; + +WalkontableScrollbar.prototype.prepare = function () { + if (this.skipRefresh) { + return; + } + var ratio = this.getHandleSizeRatio(this.visibleCount, this.total); + if (((ratio === 1 || isNaN(ratio)) && this.scrollMode === 'auto') || this.scrollMode === 'none') { + //isNaN is needed because ratio equals NaN when totalRows/totalColumns equals 0 + this.visible = false; + } + else { + this.visible = true; + } +}; + +WalkontableScrollbar.prototype.refresh = function () { + if (this.skipRefresh) { + return; + } + else if (!this.visible) { + this.sliderStyle.display = 'none'; + return; + } + + var ratio + , sliderSize + , handleSize + , handlePosition + , visibleCount = this.visibleCount + , tableWidth = this.instance.wtViewport.getWorkspaceWidth() + , tableHeight = this.instance.wtViewport.getWorkspaceHeight(); + + if (tableWidth === Infinity) { + tableWidth = this.instance.wtViewport.getWorkspaceActualWidth(); + } + + if (tableHeight === Infinity) { + tableHeight = this.instance.wtViewport.getWorkspaceActualHeight(); + } + + if (this.type === 'vertical') { + if (this.instance.wtTable.rowStrategy.isLastIncomplete()) { + visibleCount--; + } + + sliderSize = tableHeight - 2; //2 is sliders border-width + + this.sliderStyle.top = this.instance.wtDom.offset(this.$table[0]).top - this.instance.wtDom.offset(this.container).top + 'px'; + this.sliderStyle.left = tableWidth - 1 + 'px'; //1 is sliders border-width + this.sliderStyle.height = Math.max(sliderSize, 0) + 'px'; + } + else { //horizontal + if (this.instance.wtTable.columnStrategy.isLastIncomplete()) { + visibleCount--; + } + + sliderSize = tableWidth - 2; //2 is sliders border-width + + this.sliderStyle.left = this.instance.wtDom.offset(this.$table[0]).left - this.instance.wtDom.offset(this.container).left + 'px'; + this.sliderStyle.top = tableHeight - 1 + 'px'; //1 is sliders border-width + this.sliderStyle.width = Math.max(sliderSize, 0) + 'px'; + } + + ratio = this.getHandleSizeRatio(visibleCount, this.total); + handleSize = Math.round(sliderSize * ratio); + if (handleSize < 10) { + handleSize = 15; + } + + handlePosition = Math.floor(sliderSize * (this.offset / this.total)); + if (handleSize + handlePosition > sliderSize) { + handlePosition = sliderSize - handleSize; + } + + if (this.type === 'vertical') { + this.handleStyle.height = handleSize + 'px'; + this.handleStyle.top = handlePosition + 'px'; + + } + else { //horizontal + this.handleStyle.width = handleSize + 'px'; + this.handleStyle.left = handlePosition + 'px'; + } + + this.sliderStyle.display = 'block'; +}; + +WalkontableScrollbar.prototype.destroy = function () { + clearInterval(this.dragdealer.interval); +}; + +/// + +var WalkontableVerticalScrollbar = function (instance) { + this.instance = instance; + this.type = 'vertical'; + this.init(); +}; + +WalkontableVerticalScrollbar.prototype = new WalkontableScrollbar(); + +WalkontableVerticalScrollbar.prototype.scrollTo = function (cell) { + this.instance.update('offsetRow', cell); +}; + +WalkontableVerticalScrollbar.prototype.readSettings = function () { + this.scrollMode = this.instance.getSetting('scrollV'); + this.offset = this.instance.getSetting('offsetRow'); + this.total = this.instance.getSetting('totalRows'); + this.visibleCount = this.instance.wtTable.rowStrategy.countVisible(); + if(this.visibleCount > 1 && this.instance.wtTable.rowStrategy.isLastIncomplete()) { + this.visibleCount--; + } + this.handlePosition = parseInt(this.handleStyle.top, 10); + this.sliderSize = parseInt(this.sliderStyle.height, 10); + this.fixedCount = this.instance.getSetting('fixedRowsTop'); +}; + +/// + +var WalkontableHorizontalScrollbar = function (instance) { + this.instance = instance; + this.type = 'horizontal'; + this.init(); +}; + +WalkontableHorizontalScrollbar.prototype = new WalkontableScrollbar(); + +WalkontableHorizontalScrollbar.prototype.scrollTo = function (cell) { + this.instance.update('offsetColumn', cell); +}; + +WalkontableHorizontalScrollbar.prototype.readSettings = function () { + this.scrollMode = this.instance.getSetting('scrollH'); + this.offset = this.instance.getSetting('offsetColumn'); + this.total = this.instance.getSetting('totalColumns'); + this.visibleCount = this.instance.wtTable.columnStrategy.countVisible(); + if(this.visibleCount > 1 && this.instance.wtTable.columnStrategy.isLastIncomplete()) { + this.visibleCount--; + } + this.handlePosition = parseInt(this.handleStyle.left, 10); + this.sliderSize = parseInt(this.sliderStyle.width, 10); + this.fixedCount = this.instance.getSetting('fixedColumnsLeft'); +}; +function WalkontableScrollbarNative() { + this.lastWindowScrollPosition = NaN; +} + +WalkontableScrollbarNative.prototype.init = function () { + this.fixedContainer = this.instance.wtTable.TABLE.parentNode.parentNode.parentNode; + this.fixed = this.instance.wtTable.TABLE.parentNode.parentNode; + this.TABLE = this.instance.wtTable.TABLE; + this.$scrollHandler = $(window); //in future remove jQuery from here + + var that = this; + this.$scrollHandler.on('scroll.walkontable', function () { + if (!that.instance.wtTable.parent.parentNode) { + //Walkontable was detached from DOM, but this handler was not removed + that.destroy(); + return; + } + + that.onScroll(); + }); + + this.readSettings(); +}; + +WalkontableScrollbarNative.prototype.onScroll = function () { + this.readSettings(); + if (this.windowScrollPosition === this.lastWindowScrollPosition) { + return; + } + this.lastWindowScrollPosition = this.windowScrollPosition; + + var scrollDelta; + var newOffset = 0; + + if (this.windowScrollPosition > this.tableParentOffset) { + scrollDelta = this.windowScrollPosition - this.tableParentOffset; + newOffset = Math.ceil(scrollDelta / 20, 10); + newOffset = Math.min(newOffset, this.total) + } + + this.instance.update('offsetRow', newOffset); + this.instance.draw(); +}; + +WalkontableScrollbarNative.prototype.prepare = function () { +}; + +WalkontableScrollbarNative.prototype.availableSize = function () { + var availableSize; + + //var last = this.getLastCell(); + if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table + if (this.instance.wtTable.getLastVisibleRow() === this.total - 1) { + availableSize = this.instance.wtDom.outerHeight(this.TABLE); + } + else { + availableSize = this.windowSize; + } + } + else { + availableSize = this.windowSize - (this.tableParentOffset - this.windowScrollPosition); + } + + return availableSize; +}; + +WalkontableScrollbarNative.prototype.refresh = function () { + var last = this.getLastCell(); + this.measureBefore = this.offset * this.cellSize; + this.measureInside = this.getTableSize(); + if (last === -1) { //last -1 means that viewport is scrolled behind the table + this.measureAfter = 0; + } + else { + this.measureAfter = (this.total - last - 1) * this.cellSize; + } + this.applyToDOM(); +}; + +WalkontableScrollbarNative.prototype.destroy = function () { + this.$scrollHandler.off('scroll.walkontable'); +}; + +/// + +var WalkontableVerticalScrollbarNative = function (instance) { + this.instance = instance; + this.type = 'vertical'; + this.cellSize = 20; + this.init(); +}; + +WalkontableVerticalScrollbarNative.prototype = new WalkontableScrollbarNative(); + +WalkontableVerticalScrollbarNative.prototype.getLastCell = function () { + return this.instance.wtTable.getLastVisibleRow(); +}; + +WalkontableVerticalScrollbarNative.prototype.getTableSize = function () { + return this.instance.wtDom.outerHeight(this.TABLE); +}; + +WalkontableVerticalScrollbarNative.prototype.applyToDOM = function () { + if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table + this.fixed.style.position = 'fixed'; + this.fixed.style.top = '0'; + this.fixed.style.left = this.tableParentOtherOffset; + } + else { + this.fixed.style.position = 'relative'; + } + + var debug = false; + if (debug) { + //this.fixedContainer.style.borderTop = this.measureBefore + 'px solid red'; + //this.fixedContainer.style.borderBottom = (this.tableSize + this.measureAfter) + 'px solid blue'; + } + else { + this.fixedContainer.style.paddingTop = this.measureBefore + 'px'; + this.fixedContainer.style.paddingBottom = (this.measureInside + this.measureAfter) + 'px'; + } +}; + +WalkontableVerticalScrollbarNative.prototype.scrollTo = function (cell) { + this.$scrollHandler.scrollTop(this.tableParentOffset + cell * this.cellSize); +}; + +WalkontableVerticalScrollbarNative.prototype.readSettings = function () { + var offset = this.instance.wtDom.offset(this.fixedContainer); + this.tableParentOffset = offset.top; + this.tableParentOtherOffset = offset.left; + this.windowSize = this.$scrollHandler.height(); + this.windowScrollPosition = this.$scrollHandler.scrollTop(); + this.offset = this.instance.getSetting('offsetRow'); + this.total = this.instance.getSetting('totalRows'); +}; + +/// + +var WalkontableHorizontalScrollbarNative = function (instance) { + this.instance = instance; + this.type = 'horizontal'; + this.cellSize = 50; + this.init(); +}; + +WalkontableHorizontalScrollbarNative.prototype = new WalkontableScrollbarNative(); + +WalkontableHorizontalScrollbarNative.prototype.getLastCell = function () { + return this.instance.wtTable.getLastVisibleColumn(); +}; + +WalkontableHorizontalScrollbarNative.prototype.getTableSize = function () { + return this.instance.wtDom.outerWidth(this.TABLE); +}; + +WalkontableHorizontalScrollbarNative.prototype.applyToDOM = function () { + if (this.windowScrollPosition > this.tableParentOffset /*&& last > -1*/) { //last -1 means that viewport is scrolled behind the table + this.fixed.style.position = 'fixed'; + this.fixed.style.left = '0'; + this.fixed.style.top = this.tableParentOtherOffset; + } + else { + this.fixed.style.position = 'relative'; + } + + var debug = false; + if (debug) { + //this.fixedContainer.style.borderLeft = this.measureBefore + 'px solid red'; + //this.fixedContainer.style.borderBottom = (this.tableSize + this.measureAfter) + 'px solid blue'; + } + else { + this.fixedContainer.style.paddingLeft = this.measureBefore + 'px'; + this.fixedContainer.style.paddingRight = (this.measureInside + this.measureAfter) + 'px'; + } +}; + +WalkontableHorizontalScrollbarNative.prototype.scrollTo = function (cell) { + this.$scrollHandler.scrollLeft(this.tableParentOffset + cell * this.cellSize); +}; + +WalkontableHorizontalScrollbarNative.prototype.readSettings = function () { + var offset = this.instance.wtDom.offset(this.fixedContainer); + this.tableParentOffset = offset.left; + this.tableParentOtherOffset = offset.top; + this.windowSize = this.$scrollHandler.width(); + this.windowScrollPosition = this.$scrollHandler.scrollLeft(); + this.offset = this.instance.getSetting('offsetColumn'); + this.total = this.instance.getSetting('totalColumns'); +}; +function WalkontableScrollbars(instance) { + switch (instance.getSetting('scrollbarModelV')) { + case 'dragdealer': + this.vertical = new WalkontableVerticalScrollbar(instance); + break; + + case 'native': + this.vertical = new WalkontableVerticalScrollbarNative(instance); + break; + } + + switch (instance.getSetting('scrollbarModelH')) { + case 'dragdealer': + this.horizontal = new WalkontableHorizontalScrollbar(instance); + break; + + case 'native': + this.horizontal = new WalkontableHorizontalScrollbarNative(instance); + break; + } +} + +WalkontableScrollbars.prototype.destroy = function () { + this.vertical.destroy(); + this.horizontal.destroy(); +}; + +WalkontableScrollbars.prototype.refresh = function () { + this.horizontal.readSettings(); + this.vertical.readSettings(); + this.horizontal.prepare(); + this.vertical.prepare(); + this.horizontal.refresh(); + this.vertical.refresh(); +}; +function WalkontableSelection(instance, settings) { + this.instance = instance; + this.settings = settings; + this.selected = []; + if (settings.border) { + this.border = new WalkontableBorder(instance, settings); + } +} + +WalkontableSelection.prototype.add = function (coords) { + this.selected.push(coords); +}; + +WalkontableSelection.prototype.clear = function () { + this.selected.length = 0; //http://jsperf.com/clear-arrayxxx +}; + +/** + * Returns the top left (TL) and bottom right (BR) selection coordinates + * @returns {Object} + */ +WalkontableSelection.prototype.getCorners = function () { + var minRow + , minColumn + , maxRow + , maxColumn + , i + , ilen = this.selected.length; + + if (ilen > 0) { + minRow = maxRow = this.selected[0][0]; + minColumn = maxColumn = this.selected[0][1]; + + if (ilen > 1) { + for (i = 1; i < ilen; i++) { + if (this.selected[i][0] < minRow) { + minRow = this.selected[i][0]; + } + else if (this.selected[i][0] > maxRow) { + maxRow = this.selected[i][0]; + } + + if (this.selected[i][1] < minColumn) { + minColumn = this.selected[i][1]; + } + else if (this.selected[i][1] > maxColumn) { + maxColumn = this.selected[i][1]; + } + } + } + } + + return [minRow, minColumn, maxRow, maxColumn]; +}; + +WalkontableSelection.prototype.draw = function () { + var corners, r, c, source_r, source_c; + + var visibleRows = this.instance.wtTable.rowStrategy.countVisible() + , visibleColumns = this.instance.wtTable.columnStrategy.countVisible(); + + if (this.selected.length) { + corners = this.getCorners(); + + for (r = 0; r < visibleRows; r++) { + for (c = 0; c < visibleColumns; c++) { + source_r = this.instance.wtTable.rowFilter.visibleToSource(r); + source_c = this.instance.wtTable.columnFilter.visibleToSource(c); + + if (source_r >= corners[0] && source_r <= corners[2] && source_c >= corners[1] && source_c <= corners[3]) { + //selected cell + this.instance.wtTable.currentCellCache.add(r, c, this.settings.className); + } + else if (source_r >= corners[0] && source_r <= corners[2]) { + //selection is in this row + this.instance.wtTable.currentCellCache.add(r, c, this.settings.highlightRowClassName); + } + else if (source_c >= corners[1] && source_c <= corners[3]) { + //selection is in this column + this.instance.wtTable.currentCellCache.add(r, c, this.settings.highlightColumnClassName); + } + } + } + + this.border && this.border.appear(corners); //warning! border.appear modifies corners! + } + else { + this.border && this.border.disappear(); + } +}; + +function WalkontableSettings(instance, settings) { + var that = this; + this.instance = instance; + + //default settings. void 0 means it is required, null means it can be empty + this.defaults = { + table: void 0, + + //presentation mode + scrollH: 'auto', //values: scroll (always show scrollbar), auto (show scrollbar if table does not fit in the container), none (never show scrollbar) + scrollV: 'auto', //values: see above + scrollbarModelH: 'dragdealer', //values: dragdealer, native + scrollbarModelV: 'dragdealer', //values: dragdealer, native + stretchH: 'hybrid', //values: hybrid, all, last, none + currentRowClassName: null, + currentColumnClassName: null, + + //data source + data: void 0, + offsetRow: 0, + offsetColumn: 0, + fixedColumnsLeft: 0, + fixedRowsTop: 0, + rowHeaders: [], //this must be array of functions: [function (row, TH) {}] + columnHeaders: [], //this must be array of functions: [function (column, TH) {}] + totalRows: void 0, + totalColumns: void 0, + width: null, + height: null, + cellRenderer: function (row, column, TD) { + var cellData = that.getSetting('data', row, column); + that.instance.wtDom.avoidInnerHTML(TD, cellData === void 0 || cellData === null ? '' : cellData); + }, + columnWidth: 50, + selections: null, + + //callbacks + onCellMouseDown: null, + onCellMouseOver: null, + onCellDblClick: null, + onCellCornerMouseDown: null, + onCellCornerDblClick: null, + beforeDraw: null, + onDraw: null, + + //constants + scrollbarWidth: 10, + scrollbarHeight: 10 + }; + + //reference to settings + this.settings = {}; + for (var i in this.defaults) { + if (this.defaults.hasOwnProperty(i)) { + if (settings[i] !== void 0) { + this.settings[i] = settings[i]; + } + else if (this.defaults[i] === void 0) { + throw new Error('A required setting "' + i + '" was not provided'); + } + else { + this.settings[i] = this.defaults[i]; + } + } + } +} + +/** + * generic methods + */ + +WalkontableSettings.prototype.update = function (settings, value) { + if (value === void 0) { //settings is object + for (var i in settings) { + if (settings.hasOwnProperty(i)) { + this.settings[i] = settings[i]; + } + } + } + else { //if value is defined then settings is the key + this.settings[settings] = value; + } + return this.instance; +}; + +WalkontableSettings.prototype.getSetting = function (key, param1, param2, param3) { + if (this[key]) { + return this[key](param1, param2, param3); + } + else { + return this._getSetting(key, param1, param2, param3); + } +}; + +WalkontableSettings.prototype._getSetting = function (key, param1, param2, param3) { + if (typeof this.settings[key] === 'function') { + return this.settings[key](param1, param2, param3); + } + else if (param1 !== void 0 && Object.prototype.toString.call(this.settings[key]) === '[object Array]' && param1 !== void 0) { + return this.settings[key][param1]; + } + else { + return this.settings[key]; + } +}; + +WalkontableSettings.prototype.has = function (key) { + return !!this.settings[key] +}; + +/** + * specific methods + */ + +WalkontableSettings.prototype.rowHeight = function (row) { + var visible_r = this.instance.wtTable.rowFilter.sourceToVisible(row); + var size = this.instance.wtTable.rowStrategy.getSize(visible_r); + if (size !== void 0) { + return size; + } + return 20; +}; + +WalkontableSettings.prototype.columnWidth = function (column) { + return Math.min(200, this._getSetting('columnWidth', column)); +}; +/*var FLAG_VISIBLE_HORIZONTAL = 0x1; // 000001 + var FLAG_VISIBLE_VERTICAL = 0x2; // 000010 + var FLAG_PARTIALLY_VISIBLE_HORIZONTAL = 0x4; // 000100 + var FLAG_PARTIALLY_VISIBLE_VERTICAL = 0x8; // 001000 + var FLAG_NOT_VISIBLE_HORIZONTAL = 0x10; // 010000 + var FLAG_NOT_VISIBLE_VERTICAL = 0x20; // 100000*/ + +function WalkontableTable(instance) { + //reference to instance + this.instance = instance; + this.TABLE = this.instance.getSetting('table'); + this.wtDom = this.instance.wtDom; + this.wtDom.removeTextNodes(this.TABLE); + + this.hasEmptyCellProblem = ($.browser.msie && (parseInt($.browser.version, 10) <= 7)); + this.hasCellSpacingProblem = ($.browser.msie && (parseInt($.browser.version, 10) <= 7)); + + if (this.hasCellSpacingProblem) { //IE7 + this.TABLE.cellSpacing = 0; + } + + //wtSpreader + var parent = this.TABLE.parentNode; + if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) { + var spreader = document.createElement('DIV'); + spreader.className = 'wtSpreader'; + if (parent) { + parent.insertBefore(spreader, this.TABLE); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it + } + spreader.appendChild(this.TABLE); + } + this.spreader = this.TABLE.parentNode; + + //wtHider + parent = this.spreader.parentNode; + if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) { + var hider = document.createElement('DIV'); + hider.className = 'wtHider'; + if (parent) { + parent.insertBefore(hider, this.spreader); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it + } + hider.appendChild(this.spreader); + } + this.hider = this.spreader.parentNode; + this.hiderStyle = this.hider.style; + this.hiderStyle.position = 'relative'; + + //wtHolder + parent = this.hider.parentNode; + if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) { + var holder = document.createElement('DIV'); + holder.style.position = 'relative'; + holder.className = 'wtHolder'; + if (parent) { + parent.insertBefore(holder, this.hider); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it + } + holder.appendChild(this.hider); + } + this.parent = this.hider.parentNode; + + //bootstrap from settings + this.TBODY = this.TABLE.getElementsByTagName('TBODY')[0]; + if (!this.TBODY) { + this.TBODY = document.createElement('TBODY'); + this.TABLE.appendChild(this.TBODY); + } + this.THEAD = this.TABLE.getElementsByTagName('THEAD')[0]; + if (!this.THEAD) { + this.THEAD = document.createElement('THEAD'); + this.TABLE.insertBefore(this.THEAD, this.TBODY); + } + this.COLGROUP = this.TABLE.getElementsByTagName('COLGROUP')[0]; + if (!this.COLGROUP) { + this.COLGROUP = document.createElement('COLGROUP'); + this.TABLE.insertBefore(this.COLGROUP, this.THEAD); + } + + if (this.instance.getSetting('columnHeaders').length) { + if (!this.THEAD.childNodes.length) { + var TR = document.createElement('TR'); + this.THEAD.appendChild(TR); + } + } + + this.colgroupChildrenLength = this.COLGROUP.childNodes.length; + this.theadChildrenLength = this.THEAD.firstChild ? this.THEAD.firstChild.childNodes.length : 0; + this.tbodyChildrenLength = this.TBODY.childNodes.length; + + this.oldCellCache = new WalkontableClassNameCache(); + this.currentCellCache = new WalkontableClassNameCache(); + + this.rowFilter = new WalkontableRowFilter(); + this.columnFilter = new WalkontableColumnFilter(); + + this.verticalRenderReverse = false; +} + +WalkontableTable.prototype.refreshHiderDimensions = function () { + var height = this.instance.wtViewport.getWorkspaceHeight(); + var width = this.instance.wtViewport.getWorkspaceWidth(); + + var spreaderStyle = this.spreader.style; + + if (height !== Infinity || width !== Infinity) { + this.hiderStyle.overflow = 'hidden'; + + spreaderStyle.position = 'absolute'; + spreaderStyle.top = '0'; + spreaderStyle.left = '0'; + + if (this.instance.getSetting('scrollbarModelV') === 'dragdealer') { + spreaderStyle.height = '4000px'; + } + + if (this.instance.getSetting('scrollbarModelH') === 'dragdealer') { + spreaderStyle.width = '4000px'; + } + + if (height === Infinity) { + height = this.instance.wtViewport.getWorkspaceActualHeight(); + } + this.hiderStyle.height = height + 'px'; + + if (width === Infinity) { + width = this.instance.wtViewport.getWorkspaceActualWidth(); + } + this.hiderStyle.width = width + 'px'; + } + else { + spreaderStyle.position = 'relative'; + spreaderStyle.width = 'auto'; + spreaderStyle.height = 'auto'; + } +}; + +WalkontableTable.prototype.refreshStretching = function () { + var instance = this.instance + , stretchH = instance.getSetting('stretchH') + , totalRows = instance.getSetting('totalRows') + , totalColumns = instance.getSetting('totalColumns') + , offsetColumn = instance.getSetting('offsetColumn'); + + var containerWidthFn = function (cacheWidth) { + return that.instance.wtViewport.getViewportWidth(cacheWidth); + }; + + var that = this; + + var columnWidthFn = function (i) { + var source_c = that.columnFilter.visibleToSource(i); + if (source_c < totalColumns) { + return instance.getSetting('columnWidth', source_c); + } + }; + + if (stretchH === 'hybrid') { + if (offsetColumn > 0) { + stretchH = 'last'; + } + else { + stretchH = 'none'; + } + } + + var containerHeightFn = function (cacheHeight) { + return that.instance.wtViewport.getViewportHeight(cacheHeight); + }; + + var rowHeightFn = function (i, TD) { + var source_r = that.rowFilter.visibleToSource(i); + if (source_r < totalRows) { + if (that.verticalRenderReverse && i === 0) { + return that.wtDom.outerHeight(TD) - 1; + } + else { + return that.wtDom.outerHeight(TD); + } + } + }; + + this.columnStrategy = new WalkontableColumnStrategy(containerWidthFn, columnWidthFn, stretchH); + this.rowStrategy = new WalkontableRowStrategy(containerHeightFn, rowHeightFn); +}; + +WalkontableTable.prototype.adjustAvailableNodes = function () { + var displayTds + , rowHeaders = this.instance.getSetting('rowHeaders') + , displayThs = rowHeaders.length + , columnHeaders = this.instance.getSetting('columnHeaders') + , TR + , TD + , c; + + //adjust COLGROUP + while (this.colgroupChildrenLength < displayThs) { + this.COLGROUP.appendChild(document.createElement('COL')); + this.colgroupChildrenLength++; + } + + this.refreshStretching(); + displayTds = this.columnStrategy.cellCount; + + //adjust COLGROUP + while (this.colgroupChildrenLength < displayTds + displayThs) { + this.COLGROUP.appendChild(document.createElement('COL')); + this.colgroupChildrenLength++; + } + while (this.colgroupChildrenLength > displayTds + displayThs) { + this.COLGROUP.removeChild(this.COLGROUP.lastChild); + this.colgroupChildrenLength--; + } + + //adjust THEAD + if (columnHeaders.length) { + TR = this.THEAD.firstChild; + while (this.theadChildrenLength < displayTds + displayThs) { + TR.appendChild(document.createElement('TH')); + this.theadChildrenLength++; + } + while (this.theadChildrenLength > displayTds + displayThs) { + TR.removeChild(TR.lastChild); + this.theadChildrenLength--; + } + } + + //draw COLGROUP + for (c = 0; c < this.colgroupChildrenLength; c++) { + if (c < displayThs) { + this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader'); + } + else { + this.wtDom.removeClass(this.COLGROUP.childNodes[c], 'rowHeader'); + } + } + + //draw THEAD + if (columnHeaders.length) { + TR = this.THEAD.firstChild; + if (displayThs) { + TD = TR.firstChild; //actually it is TH but let's reuse single variable + for (c = 0; c < displayThs; c++) { + rowHeaders[c](-displayThs + c, TD); + if (this.hasEmptyCellProblem) { //IE7 + TD.innerHTML = ' '; + } + TD = TD.nextSibling; + } + } + } + + for (c = 0; c < displayTds; c++) { + if (columnHeaders.length) { + columnHeaders[0](this.columnFilter.visibleToSource(c), TR.childNodes[displayThs + c]); + } + } +}; + +WalkontableTable.prototype.adjustColumns = function (TR, desiredCount) { + var count = TR.childNodes.length; + while (count < desiredCount) { + var TD = document.createElement('TD'); + TR.appendChild(TD); + count++; + } + while (count > desiredCount) { + TR.removeChild(TR.lastChild); + count--; + } +}; + +WalkontableTable.prototype.draw = function (selectionsOnly) { + this.rowFilter.readSettings(this.instance); + this.columnFilter.readSettings(this.instance); + + if (!selectionsOnly) { + this.tableOffset = this.wtDom.offset(this.TABLE); + this._doDraw(); + } + else { + this.instance.wtScrollbars.refresh(); + } + + this.refreshPositions(selectionsOnly); + + this.instance.drawn = true; + return this; +}; + +WalkontableTable.prototype._doDraw = function () { + var r = 0 + , source_r + , c + , source_c + , offsetRow = this.instance.getSetting('offsetRow') + , totalRows = this.instance.getSetting('totalRows') + , totalColumns = this.instance.getSetting('totalColumns') + , displayTds + , rowHeaders = this.instance.getSetting('rowHeaders') + , displayThs = rowHeaders.length + , TR + , TD + , adjusted = false + , workspaceWidth; + + this.instance.wtViewport.resetSettings(); + + var noPartial = false; + if (this.verticalRenderReverse) { + if (offsetRow === totalRows - this.rowFilter.fixedCount - 1) { + noPartial = true; + } + else { + this.instance.update('offsetRow', offsetRow + 1); //if we are scrolling reverse + this.rowFilter.readSettings(this.instance); + } + } + + //draw TBODY + if (totalColumns > 0) { + source_r = this.rowFilter.visibleToSource(r); + + var first = true; + + while (source_r < totalRows && source_r >= 0) { + if (r >= this.tbodyChildrenLength || (this.verticalRenderReverse && r >= this.rowFilter.fixedCount)) { + TR = document.createElement('TR'); + for (c = 0; c < displayThs; c++) { + TR.appendChild(document.createElement('TH')); + } + if (this.verticalRenderReverse && r >= this.rowFilter.fixedCount) { + this.TBODY.insertBefore(TR, this.TBODY.childNodes[this.rowFilter.fixedCount] || this.TBODY.firstChild); + } + else { + this.TBODY.appendChild(TR); + } + this.tbodyChildrenLength++; + } + else if (r === 0) { + TR = this.TBODY.firstChild; + } + else { + TR = TR.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes + } + + //TH + TD = TR.firstChild; + for (c = 0; c < displayThs; c++) { + rowHeaders[c](source_r, TD); //actually TH + TD = TD.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes + } + + if (first) { +// if (r === 0) { + first = false; + + this.adjustAvailableNodes(); + adjusted = true; + displayTds = this.columnStrategy.cellCount; + + //TD + this.adjustColumns(TR, displayTds + displayThs); + + workspaceWidth = this.instance.wtViewport.getWorkspaceWidth(); + this.columnStrategy.stretch(); + for (c = 0; c < displayTds; c++) { + this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px'; + } + } + else { + //TD + this.adjustColumns(TR, displayTds + displayThs); + } + + for (c = 0; c < displayTds; c++) { + source_c = this.columnFilter.visibleToSource(c); + if (c === 0) { + TD = TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(source_c)]; + } + else { + TD = TD.nextSibling; //http://jsperf.com/nextsibling-vs-indexed-childnodes + } + TD.className = ''; + TD.removeAttribute('style'); + this.instance.getSetting('cellRenderer', source_r, source_c, TD); + if (this.hasEmptyCellProblem && TD.innerHTML === '') { //IE7 + TD.innerHTML = ' '; + } + } + + offsetRow = this.instance.getSetting('offsetRow'); //refresh the value + + //after last column is rendered, check if last cell is fully displayed + if (this.verticalRenderReverse && noPartial) { + if (-this.wtDom.outerHeight(TR.firstChild) < this.rowStrategy.remainingSize) { + this.TBODY.removeChild(TR); + this.instance.update('offsetRow', offsetRow + 1); + this.tbodyChildrenLength--; + this.rowFilter.readSettings(this.instance); + break; + + } + else { + this.rowStrategy.add(r, TD); + } + } + else { + this.rowStrategy.add(r, TD); + + if (this.rowStrategy.isLastIncomplete()) { + break; + } + } + + if (this.verticalRenderReverse && r >= this.rowFilter.fixedCount) { + if (offsetRow === 0) { + break; + } + this.instance.update('offsetRow', offsetRow - 1); + this.rowFilter.readSettings(this.instance); + } + else { + r++; + } + + source_r = this.rowFilter.visibleToSource(r); + } + } + + if (!adjusted) { + this.adjustAvailableNodes(); + } + + r = this.rowStrategy.countVisible(); + while (this.tbodyChildrenLength > r) { + this.TBODY.removeChild(this.TBODY.lastChild); + this.tbodyChildrenLength--; + } + + this.instance.wtScrollbars.refresh(); + + if (workspaceWidth !== this.instance.wtViewport.getWorkspaceWidth()) { + //workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching + this.columnStrategy.stretch(); + for (c = 0; c < displayTds; c++) { + this.COLGROUP.childNodes[c + displayThs].style.width = this.columnStrategy.getSize(c) + 'px'; + } + } + + this.verticalRenderReverse = false; +} +; + +WalkontableTable.prototype.refreshPositions = function (selectionsOnly) { + this.refreshHiderDimensions(); + this.refreshSelections(selectionsOnly); +}; + +WalkontableTable.prototype.refreshSelections = function (selectionsOnly) { + var vr + , r + , vc + , c + , s + , slen + , classNames = [] + , visibleRows = this.rowStrategy.countVisible() + , visibleColumns = this.columnStrategy.countVisible(); + + this.oldCellCache = this.currentCellCache; + this.currentCellCache = new WalkontableClassNameCache(); + + if (this.instance.selections) { + for (r in this.instance.selections) { + if (this.instance.selections.hasOwnProperty(r)) { + this.instance.selections[r].draw(); + if (this.instance.selections[r].settings.className) { + classNames.push(this.instance.selections[r].settings.className); + } + if (this.instance.selections[r].settings.highlightRowClassName) { + classNames.push(this.instance.selections[r].settings.highlightRowClassName); + } + if (this.instance.selections[r].settings.highlightColumnClassName) { + classNames.push(this.instance.selections[r].settings.highlightColumnClassName); + } + } + } + } + + slen = classNames.length; + + for (vr = 0; vr < visibleRows; vr++) { + for (vc = 0; vc < visibleColumns; vc++) { + r = this.rowFilter.visibleToSource(vr); + c = this.columnFilter.visibleToSource(vc); + for (s = 0; s < slen; s++) { + if (this.currentCellCache.test(vr, vc, classNames[s])) { + this.wtDom.addClass(this.getCell([r, c]), classNames[s]); + } + else if (selectionsOnly && this.oldCellCache.test(vr, vc, classNames[s])) { + this.wtDom.removeClass(this.getCell([r, c]), classNames[s]); + } + } + } + } +}; + +/* this function is not used currently (was used in _doDraw) + WalkontableTable.prototype.isCellVisible = function (r, c) { + var out = 0; + + if (this.isRowInViewport(r)) { + if (this.getLastVisibleRow() === c && this.rowStrategy.remainingSize > 0) { + out |= FLAG_PARTIALLY_VISIBLE_VERTICAL; + } + else { + out |= FLAG_VISIBLE_VERTICAL; + } + } + else { + out |= FLAG_NOT_VISIBLE_VERTICAL; + } + + if (this.isColumnInViewport(c)) { + if (this.getLastVisibleColumn() === c && this.columnStrategy.remainingSize > 0) { + out |= FLAG_PARTIALLY_VISIBLE_HORIZONTAL; + } + else { + out |= FLAG_VISIBLE_HORIZONTAL; + } + } + else { + out |= FLAG_NOT_VISIBLE_HORIZONTAL; + } + + return out; + };*/ + +/** + * getCell + * @param {Array} coords + * @return {Object} HTMLElement on success or {Number} one of the exit codes on error: + * -1 row before viewport + * -2 row after viewport + * -3 column before viewport + * -4 column after viewport + * + */ +WalkontableTable.prototype.getCell = function (coords) { + if (this.isRowBeforeViewport(coords[0])) { + return -1; //row before viewport + } + else if (this.isRowAfterViewport(coords[0])) { + return -2; //row after viewport + } + else { + if (this.isColumnBeforeViewport(coords[1])) { + return -3; //column before viewport + } + else if (this.isColumnAfterViewport(coords[1])) { + return -4; //column after viewport + } + else { + return this.TBODY.childNodes[this.rowFilter.sourceToVisible(coords[0])].childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(coords[1])]; + } + } +}; + +WalkontableTable.prototype.getCoords = function (TD) { + return [ + this.rowFilter.visibleToSource(this.wtDom.prevSiblings(TD.parentNode).length), + this.columnFilter.visibleRowHeadedColumnToSourceColumn(TD.cellIndex) + ]; +}; + +//returns -1 if no row is visible +WalkontableTable.prototype.getLastVisibleRow = function () { + return this.rowFilter.visibleToSource(this.rowStrategy.cellCount - 1); +}; + +//returns -1 if no column is visible +WalkontableTable.prototype.getLastVisibleColumn = function () { + return this.columnFilter.visibleToSource(this.columnStrategy.cellCount - 1); +}; + +WalkontableTable.prototype.isRowBeforeViewport = function (r) { + return (this.rowFilter.sourceToVisible(r) < this.rowFilter.fixedCount && r >= this.rowFilter.fixedCount); +}; + +WalkontableTable.prototype.isRowAfterViewport = function (r) { + return (r > this.getLastVisibleRow()); +}; + +WalkontableTable.prototype.isColumnBeforeViewport = function (c) { + return (this.columnFilter.sourceToVisible(c) < this.columnFilter.fixedCount && c >= this.columnFilter.fixedCount); +}; + +WalkontableTable.prototype.isColumnAfterViewport = function (c) { + return (c > this.getLastVisibleColumn()); +}; + +WalkontableTable.prototype.isRowInViewport = function (r) { + return (!this.isRowBeforeViewport(r) && !this.isRowAfterViewport(r)); +}; + +WalkontableTable.prototype.isColumnInViewport = function (c) { + return (!this.isColumnBeforeViewport(c) && !this.isColumnAfterViewport(c)); +}; + +WalkontableTable.prototype.isLastRowFullyVisible = function () { + return (this.getLastVisibleRow() === this.instance.getSetting('totalRows') - 1 && !this.rowStrategy.isLastIncomplete()); +}; + +WalkontableTable.prototype.isLastColumnFullyVisible = function () { + return (this.getLastVisibleColumn() === this.instance.getSetting('totalColumns') - 1 && !this.columnStrategy.isLastIncomplete()); +}; +function WalkontableViewport(instance) { + this.instance = instance; + this.resetSettings(); +} + +/*WalkontableViewport.prototype.isInSightVertical = function () { + //is table outside viewport bottom edge + if (tableTop > windowHeight + scrollTop) { + return -1; + } + + //is table outside viewport top edge + else if (scrollTop > tableTop + tableFakeHeight) { + return -2; + } + + //table is in viewport but how much exactly? + else { + + } +};*/ + +//used by scrollbar +WalkontableViewport.prototype.getWorkspaceHeight = function (proposedHeight) { + var height = this.instance.getSetting('height'); + + if (height === Infinity || height === void 0 || height === null || height < 1) { + if (this.instance.wtScrollbars.vertical instanceof WalkontableScrollbarNative) { + height = this.instance.wtScrollbars.vertical.availableSize(); + } + else { + height = Infinity; + } + } + + if (height !== Infinity) { + if (proposedHeight >= height) { + height -= this.instance.getSetting('scrollbarHeight'); + } + else if (this.instance.wtScrollbars.horizontal.visible) { + height -= this.instance.getSetting('scrollbarHeight'); + } + } + + return height; +}; + +WalkontableViewport.prototype.getWorkspaceWidth = function (proposedWidth) { + var width = this.instance.getSetting('width'); + + if (width === Infinity || width === void 0 || width === null || width < 1) { + if (this.instance.wtScrollbars.horizontal instanceof WalkontableScrollbarNative) { + width = this.instance.wtScrollbars.horizontal.availableSize(); + } + else { + width = Infinity; + } + } + + if (width !== Infinity) { + if (proposedWidth >= width) { + width -= this.instance.getSetting('scrollbarWidth'); + } + else if (this.instance.wtScrollbars.vertical.visible) { + width -= this.instance.getSetting('scrollbarWidth'); + } + } + return width; +}; + +WalkontableViewport.prototype.getWorkspaceActualHeight = function () { + return this.instance.wtDom.outerHeight(this.instance.wtTable.TABLE); +}; + +WalkontableViewport.prototype.getWorkspaceActualWidth = function () { + return this.instance.wtDom.outerWidth(this.instance.wtTable.TABLE) || this.instance.wtDom.outerWidth(this.instance.wtTable.TBODY) || this.instance.wtDom.outerWidth(this.instance.wtTable.THEAD); //IE8 reports 0 as
    offsetWidth; +}; + +WalkontableViewport.prototype.getViewportHeight = function (proposedHeight) { + var containerHeight = this.getWorkspaceHeight(proposedHeight); + + if (containerHeight === Infinity) { + return containerHeight; + } + + if (isNaN(this.columnHeaderHeight)) { + var cellOffset = this.instance.wtDom.offset(this.instance.wtTable.TBODY) + , tableOffset = this.instance.wtTable.tableOffset; + this.columnHeaderHeight = cellOffset.top - tableOffset.top; + } + + if (this.columnHeaderHeight > 0) { + return containerHeight - this.columnHeaderHeight; + } + else { + return containerHeight; + } +}; + +WalkontableViewport.prototype.getViewportWidth = function (proposedWidth) { + var containerWidth = this.getWorkspaceWidth(proposedWidth); + + if (containerWidth === Infinity) { + return containerWidth; + } + + if (isNaN(this.rowHeaderWidth)) { + var TR = this.instance.wtTable.TBODY ? this.instance.wtTable.TBODY.firstChild : null; + if (TR) { + var TD = TR.firstChild; + this.rowHeaderWidth = 0; + while (TD && TD.nodeName === 'TH') { + this.rowHeaderWidth += this.instance.wtDom.outerWidth(TD); + TD = TD.nextSibling; + } + } + } + + if (this.rowHeaderWidth > 0) { + return containerWidth - this.rowHeaderWidth; + } + else { + return containerWidth; + } +}; + +WalkontableViewport.prototype.resetSettings = function () { + this.rowHeaderWidth = NaN; + this.columnHeaderHeight = NaN; +}; +function WalkontableWheel(instance) { + if (instance.getSetting('scrollbarModelV') === 'native' || instance.getSetting('scrollbarModelH') === 'native') { + return; + } + + $(instance.wtTable.TABLE).on('mousewheel', function (event, delta, deltaX, deltaY) { + if (deltaY > 0 && instance.getSetting('offsetRow') === 0) { + return; //attempt to scroll up when it's already showing first row + } + else if (deltaY < 0 && instance.wtTable.isLastRowFullyVisible()) { + return; //attempt to scroll down when it's already showing last row + } + else if (deltaX < 0 && instance.getSetting('offsetColumn') === 0) { + return; //attempt to scroll left when it's already showing first column + } + else if (deltaX > 0 && instance.wtTable.isLastColumnFullyVisible()) { + return; //attempt to scroll right when it's already showing last column + } + + //now we are sure we really want to scroll + clearTimeout(instance.wheelTimeout); + instance.wheelTimeout = setTimeout(function () { //timeout is needed because with fast-wheel scrolling mousewheel event comes dozen times per second + if (deltaY) { + //ceil is needed because jquery-mousewheel reports fractional mousewheel deltas on touchpad scroll + //see http://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers + if (instance.wtScrollbars.vertical.visible) { // if we see scrollbar + instance.scrollVertical(-Math.ceil(deltaY)).draw(); + } + } + else if (deltaX) { + if (instance.wtScrollbars.horizontal.visible) { // if we see scrollbar + instance.scrollHorizontal(Math.ceil(deltaX)).draw(); + } + } + }, 0); + + event.preventDefault(); + }); +} +/** + * Dragdealer JS v0.9.5 - patched by Walkontable at lines 66, 309-310, 339-340 + * http://code.ovidiu.ch/dragdealer-js + * + * Copyright (c) 2010, Ovidiu Chereches + * MIT License + * http://legal.ovidiu.ch/licenses/MIT + */ + +/* Cursor */ + +var Cursor = +{ + x: 0, y: 0, + init: function() + { + this.setEvent('mouse'); + this.setEvent('touch'); + }, + setEvent: function(type) + { + var moveHandler = document['on' + type + 'move'] || function(){}; + document['on' + type + 'move'] = function(e) + { + moveHandler(e); + Cursor.refresh(e); + } + }, + refresh: function(e) + { + if(!e) + { + e = window.event; + } + if(e.type == 'mousemove') + { + this.set(e); + } + else if(e.touches) + { + this.set(e.touches[0]); + } + }, + set: function(e) + { + if(e.pageX || e.pageY) + { + this.x = e.pageX; + this.y = e.pageY; + } + else if(e.clientX || e.clientY) + { + this.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + this.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + } +}; +Cursor.init(); + +/* Position */ + +var Position = +{ + get: function(obj) + { + var curtop = 0, curleft = 0; //Walkontable patch. Original (var curleft = curtop = 0;) created curtop in global scope + if(obj.offsetParent) + { + do + { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } + while((obj = obj.offsetParent)); + } + return [curleft, curtop]; + } +}; + +/* Dragdealer */ + +var Dragdealer = function(wrapper, options) +{ + if(typeof(wrapper) == 'string') + { + wrapper = document.getElementById(wrapper); + } + if(!wrapper) + { + return; + } + var handle = wrapper.getElementsByTagName('div')[0]; + if(!handle || handle.className.search(/(^|\s)handle(\s|$)/) == -1) + { + return; + } + this.init(wrapper, handle, options || {}); + this.setup(); +}; +Dragdealer.prototype = +{ + init: function(wrapper, handle, options) + { + this.wrapper = wrapper; + this.handle = handle; + this.options = options; + + this.disabled = this.getOption('disabled', false); + this.horizontal = this.getOption('horizontal', true); + this.vertical = this.getOption('vertical', false); + this.slide = this.getOption('slide', true); + this.steps = this.getOption('steps', 0); + this.snap = this.getOption('snap', false); + this.loose = this.getOption('loose', false); + this.speed = this.getOption('speed', 10) / 100; + this.xPrecision = this.getOption('xPrecision', 0); + this.yPrecision = this.getOption('yPrecision', 0); + + this.callback = options.callback || null; + this.animationCallback = options.animationCallback || null; + + this.bounds = { + left: options.left || 0, right: -(options.right || 0), + top: options.top || 0, bottom: -(options.bottom || 0), + x0: 0, x1: 0, xRange: 0, + y0: 0, y1: 0, yRange: 0 + }; + this.value = { + prev: [-1, -1], + current: [options.x || 0, options.y || 0], + target: [options.x || 0, options.y || 0] + }; + this.offset = { + wrapper: [0, 0], + mouse: [0, 0], + prev: [-999999, -999999], + current: [0, 0], + target: [0, 0] + }; + this.change = [0, 0]; + + this.activity = false; + this.dragging = false; + this.tapping = false; + }, + getOption: function(name, defaultValue) + { + return this.options[name] !== undefined ? this.options[name] : defaultValue; + }, + setup: function() + { + this.setWrapperOffset(); + this.setBoundsPadding(); + this.setBounds(); + this.setSteps(); + + this.addListeners(); + }, + setWrapperOffset: function() + { + this.offset.wrapper = Position.get(this.wrapper); + }, + setBoundsPadding: function() + { + if(!this.bounds.left && !this.bounds.right) + { + this.bounds.left = Position.get(this.handle)[0] - this.offset.wrapper[0]; + this.bounds.right = -this.bounds.left; + } + if(!this.bounds.top && !this.bounds.bottom) + { + this.bounds.top = Position.get(this.handle)[1] - this.offset.wrapper[1]; + this.bounds.bottom = -this.bounds.top; + } + }, + setBounds: function() + { + this.bounds.x0 = this.bounds.left; + this.bounds.x1 = this.wrapper.offsetWidth + this.bounds.right; + this.bounds.xRange = (this.bounds.x1 - this.bounds.x0) - this.handle.offsetWidth; + + this.bounds.y0 = this.bounds.top; + this.bounds.y1 = this.wrapper.offsetHeight + this.bounds.bottom; + this.bounds.yRange = (this.bounds.y1 - this.bounds.y0) - this.handle.offsetHeight; + + this.bounds.xStep = 1 / (this.xPrecision || Math.max(this.wrapper.offsetWidth, this.handle.offsetWidth)); + this.bounds.yStep = 1 / (this.yPrecision || Math.max(this.wrapper.offsetHeight, this.handle.offsetHeight)); + }, + setSteps: function() + { + if(this.steps > 1) + { + this.stepRatios = []; + for(var i = 0; i <= this.steps - 1; i++) + { + this.stepRatios[i] = i / (this.steps - 1); + } + } + }, + addListeners: function() + { + var self = this; + + this.wrapper.onselectstart = function() + { + return false; + } + this.handle.onmousedown = this.handle.ontouchstart = function(e) + { + self.handleDownHandler(e); + }; + this.wrapper.onmousedown = this.wrapper.ontouchstart = function(e) + { + self.wrapperDownHandler(e); + }; + var mouseUpHandler = document.onmouseup || function(){}; + document.onmouseup = function(e) + { + mouseUpHandler(e); + self.documentUpHandler(e); + }; + var touchEndHandler = document.ontouchend || function(){}; + document.ontouchend = function(e) + { + touchEndHandler(e); + self.documentUpHandler(e); + }; + var resizeHandler = window.onresize || function(){}; + window.onresize = function(e) + { + resizeHandler(e); + self.documentResizeHandler(e); + }; + this.wrapper.onmousemove = function(e) + { + self.activity = true; + } + this.wrapper.onclick = function(e) + { + return !self.activity; + } + + this.interval = setInterval(function(){ self.animate() }, 25); + self.animate(false, true); + }, + handleDownHandler: function(e) + { + this.activity = false; + Cursor.refresh(e); + + this.preventDefaults(e, true); + this.startDrag(); + this.cancelEvent(e); + }, + wrapperDownHandler: function(e) + { + Cursor.refresh(e); + + this.preventDefaults(e, true); + this.startTap(); + }, + documentUpHandler: function(e) + { + this.stopDrag(); + this.stopTap(); + //this.cancelEvent(e); + }, + documentResizeHandler: function(e) + { + this.setWrapperOffset(); + this.setBounds(); + + this.update(); + }, + enable: function() + { + this.disabled = false; + this.handle.className = this.handle.className.replace(/\s?disabled/g, ''); + }, + disable: function() + { + this.disabled = true; + this.handle.className += ' disabled'; + }, + setStep: function(x, y, snap) + { + this.setValue( + this.steps && x > 1 ? (x - 1) / (this.steps - 1) : 0, + this.steps && y > 1 ? (y - 1) / (this.steps - 1) : 0, + snap + ); + }, + setValue: function(x, y, snap) + { + this.setTargetValue([x, y || 0]); + if(snap) + { + this.groupCopy(this.value.current, this.value.target); + } + }, + startTap: function(target) + { + if(this.disabled) + { + return; + } + this.tapping = true; + + this.setWrapperOffset(); + this.setBounds(); + + if(target === undefined) + { + target = [ + Cursor.x - this.offset.wrapper[0] - (this.handle.offsetWidth / 2), + Cursor.y - this.offset.wrapper[1] - (this.handle.offsetHeight / 2) + ]; + } + this.setTargetOffset(target); + }, + stopTap: function() + { + if(this.disabled || !this.tapping) + { + return; + } + this.tapping = false; + + this.setTargetValue(this.value.current); + this.result(); + }, + startDrag: function() + { + if(this.disabled) + { + return; + } + + this.setWrapperOffset(); + this.setBounds(); + + this.offset.mouse = [ + Cursor.x - Position.get(this.handle)[0], + Cursor.y - Position.get(this.handle)[1] + ]; + + this.dragging = true; + }, + stopDrag: function() + { + if(this.disabled || !this.dragging) + { + return; + } + this.dragging = false; + + var target = this.groupClone(this.value.current); + if(this.slide) + { + var ratioChange = this.change; + target[0] += ratioChange[0] * 4; + target[1] += ratioChange[1] * 4; + } + this.setTargetValue(target); + this.result(); + }, + feedback: function() + { + var value = this.value.current; + if(this.snap && this.steps > 1) + { + value = this.getClosestSteps(value); + } + if(!this.groupCompare(value, this.value.prev)) + { + if(typeof(this.animationCallback) == 'function') + { + this.animationCallback(value[0], value[1]); + } + this.groupCopy(this.value.prev, value); + } + }, + result: function() + { + if(typeof(this.callback) == 'function') + { + this.callback(this.value.target[0], this.value.target[1]); + } + }, + animate: function(direct, first) + { + if(direct && !this.dragging) + { + return; + } + if(this.dragging) + { + var prevTarget = this.groupClone(this.value.target); + + var offset = [ + Cursor.x - this.offset.wrapper[0] - this.offset.mouse[0], + Cursor.y - this.offset.wrapper[1] - this.offset.mouse[1] + ]; + this.setTargetOffset(offset, this.loose); + + this.change = [ + this.value.target[0] - prevTarget[0], + this.value.target[1] - prevTarget[1] + ]; + } + if(this.dragging || first) + { + this.groupCopy(this.value.current, this.value.target); + } + if(this.dragging || this.glide() || first) + { + this.update(); + this.feedback(); + } + }, + glide: function() + { + var diff = [ + this.value.target[0] - this.value.current[0], + this.value.target[1] - this.value.current[1] + ]; + if(!diff[0] && !diff[1]) + { + return false; + } + if(Math.abs(diff[0]) > this.bounds.xStep || Math.abs(diff[1]) > this.bounds.yStep) + { + this.value.current[0] += diff[0] * this.speed; + this.value.current[1] += diff[1] * this.speed; + } + else + { + this.groupCopy(this.value.current, this.value.target); + } + return true; + }, + update: function() + { + if(!this.snap) + { + this.offset.current = this.getOffsetsByRatios(this.value.current); + } + else + { + this.offset.current = this.getOffsetsByRatios( + this.getClosestSteps(this.value.current) + ); + } + this.show(); + }, + show: function() + { + if(!this.groupCompare(this.offset.current, this.offset.prev)) + { + if(this.horizontal) + { + this.handle.style.left = String(this.offset.current[0]) + 'px'; + } + if(this.vertical) + { + this.handle.style.top = String(this.offset.current[1]) + 'px'; + } + this.groupCopy(this.offset.prev, this.offset.current); + } + }, + setTargetValue: function(value, loose) + { + var target = loose ? this.getLooseValue(value) : this.getProperValue(value); + + this.groupCopy(this.value.target, target); + this.offset.target = this.getOffsetsByRatios(target); + }, + setTargetOffset: function(offset, loose) + { + var value = this.getRatiosByOffsets(offset); + var target = loose ? this.getLooseValue(value) : this.getProperValue(value); + + this.groupCopy(this.value.target, target); + this.offset.target = this.getOffsetsByRatios(target); + }, + getLooseValue: function(value) + { + var proper = this.getProperValue(value); + return [ + proper[0] + ((value[0] - proper[0]) / 4), + proper[1] + ((value[1] - proper[1]) / 4) + ]; + }, + getProperValue: function(value) + { + var proper = this.groupClone(value); + + proper[0] = Math.max(proper[0], 0); + proper[1] = Math.max(proper[1], 0); + proper[0] = Math.min(proper[0], 1); + proper[1] = Math.min(proper[1], 1); + + if((!this.dragging && !this.tapping) || this.snap) + { + if(this.steps > 1) + { + proper = this.getClosestSteps(proper); + } + } + return proper; + }, + getRatiosByOffsets: function(group) + { + return [ + this.getRatioByOffset(group[0], this.bounds.xRange, this.bounds.x0), + this.getRatioByOffset(group[1], this.bounds.yRange, this.bounds.y0) + ]; + }, + getRatioByOffset: function(offset, range, padding) + { + return range ? (offset - padding) / range : 0; + }, + getOffsetsByRatios: function(group) + { + return [ + this.getOffsetByRatio(group[0], this.bounds.xRange, this.bounds.x0), + this.getOffsetByRatio(group[1], this.bounds.yRange, this.bounds.y0) + ]; + }, + getOffsetByRatio: function(ratio, range, padding) + { + return Math.round(ratio * range) + padding; + }, + getClosestSteps: function(group) + { + return [ + this.getClosestStep(group[0]), + this.getClosestStep(group[1]) + ]; + }, + getClosestStep: function(value) + { + var k = 0; + var min = 1; + for(var i = 0; i <= this.steps - 1; i++) + { + if(Math.abs(this.stepRatios[i] - value) < min) + { + min = Math.abs(this.stepRatios[i] - value); + k = i; + } + } + return this.stepRatios[k]; + }, + groupCompare: function(a, b) + { + return a[0] == b[0] && a[1] == b[1]; + }, + groupCopy: function(a, b) + { + a[0] = b[0]; + a[1] = b[1]; + }, + groupClone: function(a) + { + return [a[0], a[1]]; + }, + preventDefaults: function(e, selection) + { + if(!e) + { + e = window.event; + } + if(e.preventDefault) + { + e.preventDefault(); + } + e.returnValue = false; + + if(selection && document.selection) + { + document.selection.empty(); + } + }, + cancelEvent: function(e) + { + if(!e) + { + e = window.event; + } + if(e.stopPropagation) + { + e.stopPropagation(); + } + e.cancelBubble = true; + } +}; + +/** + * jQuery.browser shim that makes Walkontable working with jQuery 1.9+ + */ +if (!jQuery.browser) { + (function () { + var matched, browser; + + /* + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ + jQuery.uaMatch = function (ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + + matched = jQuery.uaMatch(navigator.userAgent); + browser = {}; + + if (matched.browser) { + browser[ matched.browser ] = true; + browser.version = matched.version; + } + + // Chrome is Webkit, but Webkit is also Safari. + if (browser.chrome) { + browser.webkit = true; + } + else if (browser.webkit) { + browser.safari = true; + } + + jQuery.browser = browser; + + })(); +} +/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) + * Licensed under the MIT License (LICENSE.txt). + * + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * Thanks to: Seamus Leahy for adding deltaX and deltaY + * + * Version: 3.0.6 + * + * Requires: 1.2.2+ + */ + +(function($) { + +var types = ['DOMMouseScroll', 'mousewheel']; + +if ($.event.fixHooks) { + for ( var i=types.length; i; ) { + $.event.fixHooks[ types[--i] ] = $.event.mouseHooks; + } +} + +$.event.special.mousewheel = { + setup: function() { + if ( this.addEventListener ) { + for ( var i=types.length; i; ) { + this.addEventListener( types[--i], handler, false ); + } + } else { + this.onmousewheel = handler; + } + }, + + teardown: function() { + if ( this.removeEventListener ) { + for ( var i=types.length; i; ) { + this.removeEventListener( types[--i], handler, false ); + } + } else { + this.onmousewheel = null; + } + } +}; + +$.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function(fn) { + return this.unbind("mousewheel", fn); + } +}); + + +function handler(event) { + var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; + event = $.event.fix(orgEvent); + event.type = "mousewheel"; + + // Old school scrollwheel delta + if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; } + if ( orgEvent.detail ) { delta = -orgEvent.detail/3; } + + // New school multidimensional scroll (touchpads) deltas + deltaY = delta; + + // Gecko + if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { + deltaY = 0; + deltaX = -1*delta; + } + + // Webkit + if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; } + if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; } + + // Add event and delta to the front of the arguments + args.unshift(event, delta, deltaX, deltaY); + + return ($.event.dispatch || $.event.handle).apply(this, args); +} + +})(jQuery); + +})(jQuery, window, Handsontable); \ No newline at end of file diff --git a/components/handsontable/dist_wc/README.md b/components/handsontable/dist_wc/README.md new file mode 100644 index 00000000..db4284ba --- /dev/null +++ b/components/handsontable/dist_wc/README.md @@ -0,0 +1,14 @@ +# Handsontable Web Components distribution + +As a future alternative to jQuery, we are experimenting with Web Components version of Handsontable (see demo page for more details). + +To use the current experimental version, just load the Toolkitchen Toolkit library and import `x-handsontable.html` component. + +```html + + + + + + +``` \ No newline at end of file diff --git a/components/handsontable/dist_wc/x-handsontable.html b/components/handsontable/dist_wc/x-handsontable.html new file mode 100644 index 00000000..9ab2de06 --- /dev/null +++ b/components/handsontable/dist_wc/x-handsontable.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + diff --git a/components/handsontable/dist_wc/x-handsontable/jquery-2.min.js b/components/handsontable/dist_wc/x-handsontable/jquery-2.min.js new file mode 100644 index 00000000..b18e05a9 --- /dev/null +++ b/components/handsontable/dist_wc/x-handsontable/jquery-2.min.js @@ -0,0 +1,6 @@ +/*! jQuery v2.0.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery.min.map +*/ +(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],f="2.0.0",p=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=f.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return p.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,f,p,h,d,g,m,y="sizzle"+-new Date,v=e.document,b={},w=0,T=0,C=ot(),k=ot(),N=ot(),E=!1,S=function(){return 0},j=typeof undefined,D=1<<31,A=[],L=A.pop,q=A.push,H=A.push,O=A.slice,F=A.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=M.replace("w","w#"),$="\\["+R+"*("+M+")"+R+"*(?:([*^$|!~]?=)"+R+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+R+"*\\]",B=":("+M+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",I=RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=RegExp("^"+R+"*,"+R+"*"),_=RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),X=RegExp(R+"*[+~]"),U=RegExp("="+R+"*([^\\]'\"]*)"+R+"*\\]","g"),Y=RegExp(B),V=RegExp("^"+W+"$"),G={ID:RegExp("^#("+M+")"),CLASS:RegExp("^\\.("+M+")"),TAG:RegExp("^("+M.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+B),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),"boolean":RegExp("^(?:"+P+")$","i"),needsContext:RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},J=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,et=/'|\\/g,tt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,nt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{H.apply(A=O.call(v.childNodes),v.childNodes),A[v.childNodes.length].nodeType}catch(rt){H={apply:A.length?function(e,t){q.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function it(e){return J.test(e+"")}function ot(){var e,t=[];return e=function(n,i){return t.push(n+=" ")>r.cacheLength&&delete e[t.shift()],e[n]=i}}function st(e){return e[y]=!0,e}function at(e){var t=c.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ut(e,t,n,r){var i,o,s,a,u,f,d,g,x,w;if((t?t.ownerDocument||t:v)!==c&&l(t),t=t||c,n=n||[],!e||"string"!=typeof e)return n;if(1!==(a=t.nodeType)&&9!==a)return[];if(p&&!r){if(i=Q.exec(e))if(s=i[1]){if(9===a){if(o=t.getElementById(s),!o||!o.parentNode)return n;if(o.id===s)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(s))&&m(t,o)&&o.id===s)return n.push(o),n}else{if(i[2])return H.apply(n,t.getElementsByTagName(e)),n;if((s=i[3])&&b.getElementsByClassName&&t.getElementsByClassName)return H.apply(n,t.getElementsByClassName(s)),n}if(b.qsa&&(!h||!h.test(e))){if(g=d=y,x=t,w=9===a&&e,1===a&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(d=t.getAttribute("id"))?g=d.replace(et,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=f.length;while(u--)f[u]=g+mt(f[u]);x=X.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return H.apply(n,x.querySelectorAll(w)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(I,"$1"),t,n,r)}o=ut.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},l=ut.setDocument=function(e){var t=e?e.ownerDocument||e:v;return t!==c&&9===t.nodeType&&t.documentElement?(c=t,f=t.documentElement,p=!o(t),b.getElementsByTagName=at(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),b.attributes=at(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByClassName=at(function(e){return e.innerHTML="
    ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),b.sortDetached=at(function(e){return 1&e.compareDocumentPosition(c.createElement("div"))}),b.getById=at(function(e){return f.appendChild(e).id=y,!t.getElementsByName||!t.getElementsByName(y).length}),b.getById?(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){return e.getAttribute("id")===t}}):(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n?n.id===e||typeof n.getAttributeNode!==j&&n.getAttributeNode("id").value===e?[n]:undefined:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=b.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=b.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&p?t.getElementsByClassName(e):undefined},d=[],h=[],(b.qsa=it(t.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+R+"*(?:value|"+P+")"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){var t=c.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&h.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(b.matchesSelector=it(g=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){b.disconnectedMatch=g.call(e,"div"),g.call(e,"[s!='']:x"),d.push("!=",B)}),h=h.length&&RegExp(h.join("|")),d=d.length&&RegExp(d.join("|")),m=it(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,n){if(e===n)return E=!0,0;var r=n.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(n);return r?1&r||!b.sortDetached&&n.compareDocumentPosition(e)===r?e===t||m(v,e)?-1:n===t||m(v,n)?1:u?F.call(u,e)-F.call(u,n):0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],l=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:u?F.call(u,e)-F.call(u,n):0;if(o===s)return lt(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)l.unshift(r);while(a[i]===l[i])i++;return i?lt(a[i],l[i]):a[i]===v?-1:l[i]===v?1:0},c):c},ut.matches=function(e,t){return ut(e,null,null,t)},ut.matchesSelector=function(e,t){if((e.ownerDocument||e)!==c&&l(e),t=t.replace(U,"='$1']"),!(!b.matchesSelector||!p||d&&d.test(t)||h&&h.test(t)))try{var n=g.call(e,t);if(n||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return ut(t,c,null,[e]).length>0},ut.contains=function(e,t){return(e.ownerDocument||e)!==c&&l(e),m(e,t)},ut.attr=function(e,t){(e.ownerDocument||e)!==c&&l(e);var n=r.attrHandle[t.toLowerCase()],i=n&&n(e,t,!p);return i===undefined?b.attributes||!p?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null:i},ut.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ut.uniqueSort=function(e){var t,n=[],r=0,i=0;if(E=!b.detectDuplicates,u=!b.sortStable&&e.slice(0),e.sort(S),E){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return e};function lt(e,t){var n=t&&e,r=n&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ct(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}function ft(e,t,n){var r;return n?undefined:r=e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ht(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function dt(e){return st(function(t){return t=+t,st(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}i=ut.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r];r++)n+=i(t);return n},r=ut.selectors={cacheLength:50,createPseudo:st,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(tt,nt),e[3]=(e[4]||e[5]||"").replace(tt,nt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ut.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ut.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return G.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&Y.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(tt,nt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ut.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){f=t;while(f=f[g])if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[y]||(m[y]={}),l=c[e]||[],h=l[0]===w&&l[1],p=l[0]===w&&l[2],f=h&&m.childNodes[h];while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if(1===f.nodeType&&++p&&f===t){c[e]=[w,h,p];break}}else if(x&&(l=(t[y]||(t[y]={}))[e])&&l[0]===w)p=l[1];else while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if((a?f.nodeName.toLowerCase()===v:1===f.nodeType)&&++p&&(x&&((f[y]||(f[y]={}))[e]=[w,p]),f===t))break;return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ut.error("unsupported pseudo: "+e);return i[y]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?st(function(e,n){var r,o=i(e,t),s=o.length;while(s--)r=F.call(e,o[s]),e[r]=!(n[r]=o[s])}):function(e){return i(e,0,n)}):i}},pseudos:{not:st(function(e){var t=[],n=[],r=s(e.replace(I,"$1"));return r[y]?st(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:st(function(e){return function(t){return ut(e,t).length>0}}),contains:st(function(e){return function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:st(function(e){return V.test(e||"")||ut.error("unsupported lang: "+e),e=e.replace(tt,nt).toLowerCase(),function(t){var n;do if(n=p?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===c.activeElement&&(!c.hasFocus||c.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Z.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:dt(function(){return[0]}),last:dt(function(e,t){return[t-1]}),eq:dt(function(e,t,n){return[0>n?n+t:n]}),even:dt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:dt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:dt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:dt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=ht(t);function gt(e,t){var n,i,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=r.preFilter;while(a){(!n||(i=z.exec(a)))&&(i&&(a=a.slice(i[0].length)||a),u.push(o=[])),n=!1,(i=_.exec(a))&&(n=i.shift(),o.push({value:n,type:i[0].replace(I," ")}),a=a.slice(n.length));for(s in r.filter)!(i=G[s].exec(a))||l[s]&&!(i=l[s](i))||(n=i.shift(),o.push({value:n,type:s,matches:i}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ut.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,r){var i=t.dir,o=r&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,r,a){var u,l,c,f=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,r,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[y]||(t[y]={}),(l=c[i])&&l[0]===f){if((u=l[1])===!0||u===n)return u===!0}else if(l=c[i]=[f],l[1]=e(t,r,a)||n,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[y]&&(r=bt(r)),i&&!i[y]&&(i=bt(i,o)),st(function(o,s,a,u){var l,c,f,p=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,p,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(f=l[c])&&(y[h[c]]=!(m[h[c]]=f))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(f=y[c])&&l.push(m[c]=f);i(null,y=[],l,u)}c=y.length;while(c--)(f=y[c])&&(l=i?F.call(o,f):p[c])>-1&&(o[l]=!(s[l]=f))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):H.apply(s,y)})}function wt(e){var t,n,i,o=e.length,s=r.relative[e[0].type],u=s||r.relative[" "],l=s?1:0,c=yt(function(e){return e===t},u,!0),f=yt(function(e){return F.call(t,e)>-1},u,!0),p=[function(e,n,r){return!s&&(r||n!==a)||((t=n).nodeType?c(e,n,r):f(e,n,r))}];for(;o>l;l++)if(n=r.relative[e[l].type])p=[yt(vt(p),n)];else{if(n=r.filter[e[l].type].apply(null,e[l].matches),n[y]){for(i=++l;o>i;i++)if(r.relative[e[i].type])break;return bt(l>1&&vt(p),l>1&&mt(e.slice(0,l-1)).replace(I,"$1"),n,i>l&&wt(e.slice(l,i)),o>i&&wt(e=e.slice(i)),o>i&&mt(e))}p.push(n)}return vt(p)}function Tt(e,t){var i=0,o=t.length>0,s=e.length>0,u=function(u,l,f,p,h){var d,g,m,y=[],v=0,x="0",b=u&&[],T=null!=h,C=a,k=u||s&&r.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(a=l!==c&&l,n=i);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,f)){p.push(d);break}T&&(w=N,n=++i)}o&&((d=!m&&d)&&v--,u&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,f);if(u){if(v>0)while(x--)b[x]||y[x]||(y[x]=L.call(p));y=xt(y)}H.apply(p,y),T&&!u&&y.length>0&&v+t.length>1&&ut.uniqueSort(p)}return T&&(w=N,a=C),b};return o?st(u):u}s=ut.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[y]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ut(e,t[r],n);return n}function kt(e,t,n,i){var o,a,u,l,c,f=gt(e);if(!i&&1===f.length){if(a=f[0]=f[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&p&&r.relative[a[1].type]){if(t=(r.find.ID(u.matches[0].replace(tt,nt),t)||[])[0],!t)return n;e=e.slice(a.shift().value.length)}o=G.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],r.relative[l=u.type])break;if((c=r.find[l])&&(i=c(u.matches[0].replace(tt,nt),X.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=i.length&&mt(a),!e)return H.apply(n,i),n;break}}}return s(e,f)(i,t,!p,n,X.test(e)),n}r.pseudos.nth=r.pseudos.eq;function Nt(){}Nt.prototype=r.filters=r.pseudos,r.setFilters=new Nt,b.sortStable=y.split("").sort(S).join("")===y,l(),[0,0].sort(S),b.detectDuplicates=E,at(function(e){if(e.innerHTML="","#"!==e.firstChild.getAttribute("href")){var t="type|href|height|width".split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ft}}),at(function(e){if(null!=e.getAttribute("disabled")){var t=P.split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ct}}),x.find=ut,x.expr=ut.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ut.uniqueSort,x.text=ut.getText,x.isXMLDoc=ut.isXML,x.contains=ut.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(f){for(t=e.memory&&f,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(f[0],f[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!a||n&&!u||(r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))this.cache[i]=t;else for(r in t)o[r]=t[r]},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){return t===undefined||t&&"string"==typeof t&&n===undefined?this.get(e,t):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i=this.key(e),o=this.cache[i];if(t===undefined)this.cache[i]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):t in o?r=[t]:(r=x.camelCase(t),r=r in o?[r]:r.match(w)||[]),n=r.length;while(n--)delete o[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){delete this.cache[this.key(e)]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.substring(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t); +x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,i="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,s=0,a=x(this),u=t,l=e.match(w)||[];while(o=l[s++])u=i?u:!a.hasClass(o),a[u?"addClass":"removeClass"](o)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i,o=x(this);1===this.nodeType&&(i=r?e.call(this,n,o.val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.boolean.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.boolean.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.boolean.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,f,p,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(p=x.event.special[d]||{},d=(o?p.delegateType:p.bindType)||d,p=x.event.special[d]||{},f=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,p.setup&&p.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,f):h.push(f),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,f,p,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){f=x.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,f,p,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),p=x.event.special[d]||{},i||!p.trigger||p.trigger.apply(r,n)!==!1)){if(!i&&!p.noBubble&&!x.isWindow(r)){for(l=p.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:p.bindType||d,f=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),f&&f.apply(a,n),f=c&&a[c],f&&x.acceptData(a)&&f.apply&&f.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||p._default&&p._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return 3===e.target.nodeType&&(e.target=e.target.parentNode),s.filter?s.filter(e,o):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=x.expr.match.needsContext,Q={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return t=this,this.pushStack(x(e).filter(function(){for(r=0;i>r;r++)if(x.contains(t[r],this))return!0}));for(n=[],r=0;i>r;r++)x.find(e,this[r],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(Z(this,e||[],!0))},filter:function(e){return this.pushStack(Z(this,e||[],!1))},is:function(e){return!!e&&("string"==typeof e?J.test(e)?x(e,this.context).index(this[0])>=0:x.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],s=J.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function K(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return K(e,"nextSibling")},prev:function(e){return K(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(Q[e]||x.unique(i),"p"===e[0]&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function Z(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var et=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,tt=/<([\w:]+)/,nt=/<|&#?\w+;/,rt=/<(?:script|style|link)/i,it=/^(?:checkbox|radio)$/i,ot=/checked\s*(?:[^=]|=\s*.checked.)/i,st=/^$|\/(?:java|ecma)script/i,at=/^true\/(.*)/,ut=/^\s*\s*$/g,lt={option:[1,""],thead:[1,"
    ","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};lt.optgroup=lt.option,lt.tbody=lt.tfoot=lt.colgroup=lt.caption=lt.col=lt.thead,lt.th=lt.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(gt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&ht(gt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(gt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!rt.test(e)&&!lt[(tt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(et,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(gt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=p.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,f=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&ot.test(d))return this.each(function(r){var i=f.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(gt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,gt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,pt),l=0;s>l;l++)a=o[l],st.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(ut,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=gt(a),o=gt(e),r=0,i=o.length;i>r;r++)mt(o[r],s[r]);if(t)if(n)for(o=o||gt(e),s=s||gt(a),r=0,i=o.length;i>r;r++)dt(o[r],s[r]);else dt(e,a);return s=gt(a,"script"),s.length>0&&ht(s,!u&>(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,f=e.length,p=t.createDocumentFragment(),h=[];for(;f>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(nt.test(i)){o=o||p.appendChild(t.createElement("div")),s=(tt.exec(i)||["",""])[1].toLowerCase(),a=lt[s]||lt._default,o.innerHTML=a[1]+i.replace(et,"<$1>")+a[2],l=a[0];while(l--)o=o.firstChild;x.merge(h,o.childNodes),o=p.firstChild,o.textContent=""}else h.push(t.createTextNode(i));p.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=gt(p.appendChild(i),"script"),u&&ht(o),n)){l=0;while(i=o[l++])st.test(i.type||"")&&n.push(i)}return p},cleanData:function(e){var t,n,r,i=e.length,o=0,s=x.event.special;for(;i>o;o++){if(n=e[o],x.acceptData(n)&&(t=q.access(n)))for(r in t.events)s[r]?x.event.remove(n,r):x.removeEvent(n,r,t.handle);L.discard(n),q.discard(n)}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"text",async:!1,global:!1,success:x.globalEval})}});function ct(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function pt(e){var t=at.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function ht(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function dt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=x.extend({},o),l=o.events,q.set(t,s),l)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function gt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function mt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&it.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var yt,vt,xt=/^(none|table(?!-c[ea]).+)/,bt=/^margin/,wt=RegExp("^("+b+")(.*)$","i"),Tt=RegExp("^("+b+")(?!px)[a-z%]+$","i"),Ct=RegExp("^([+-])=("+b+")","i"),kt={BODY:"block"},Nt={position:"absolute",visibility:"hidden",display:"block"},Et={letterSpacing:0,fontWeight:400},St=["Top","Right","Bottom","Left"],jt=["Webkit","O","Moz","ms"];function Dt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=jt.length;while(i--)if(t=jt[i]+n,t in e)return t;return r}function At(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function Lt(t){return e.getComputedStyle(t,null)}function qt(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&At(r)&&(o[s]=q.access(r,"olddisplay",Pt(r.nodeName)))):o[s]||(i=At(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=Lt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return qt(this,!0)},hide:function(){return qt(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:At(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=yt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=Dt(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=Ct.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=Dt(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=yt(e,t,r)),"normal"===i&&t in Et&&(i=Et[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),yt=function(e,t,n){var r,i,o,s=n||Lt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Tt.test(a)&&bt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ht(e,t,n){var r=wt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ot(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+St[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+St[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+St[o]+"Width",!0,i))):(s+=x.css(e,"padding"+St[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+St[o]+"Width",!0,i)));return s}function Ft(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Lt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=yt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Tt.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ot(e,t,n||(s?"border":"content"),r,o)+"px"}function Pt(e){var t=o,n=kt[e];return n||(n=Rt(e,t),"none"!==n&&n||(vt=(vt||x(" + + + + +
    a minimalistic Excel-like data grid editor + for HTML, JavaScript & jQuery +
    + +
    + This page has been moved to + http://handsontable.com/. Please update your bookmarks. +
    + +
    + This is a new version . It introduces more plugin hooks (for Events and Callbacks) and more flexible Options.
    It should fully compatible + with 0.8 API. If not, please take a look at the migration guide in Wiki or write us a note on GitHub Issues.
    For details on what's changed, see Changelog. +
    + +
    + + + +

    Examples and how-to's

    + + + +
    +

    Editing

    + +
    + + + +
    +

    GitHub

    + +
    + +
    + +

    Authors

    + +

    + Marcin Warpechowski / Nextgen.pl +

    + + + + + + + + \ No newline at end of file diff --git a/components/handsontable/lib/bootstrap-typeahead.js b/components/handsontable/lib/bootstrap-typeahead.js new file mode 100644 index 00000000..ce8cb608 --- /dev/null +++ b/components/handsontable/lib/bootstrap-typeahead.js @@ -0,0 +1,335 @@ +/* ============================================================= + * bootstrap-typeahead.js v2.3.1 + * http://twitter.github.com/bootstrap/javascript.html#typeahead + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function($){ + + "use strict"; // jshint ;_; + + + /* TYPEAHEAD PUBLIC CLASS DEFINITION + * ================================= */ + + var Typeahead = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.typeahead.defaults, options) + this.matcher = this.options.matcher || this.matcher + this.sorter = this.options.sorter || this.sorter + this.highlighter = this.options.highlighter || this.highlighter + this.updater = this.options.updater || this.updater + this.source = this.options.source + this.$menu = $(this.options.menu) + this.shown = false + this.listen() + } + + Typeahead.prototype = { + + constructor: Typeahead + + , select: function () { + var val = this.$menu.find('.active').attr('data-value') + this.$element + .val(this.updater(val)) + .change() + return this.hide() + } + + , updater: function (item) { + return item + } + + , show: function () { + var pos = $.extend({}, this.$element.position(), { + height: this.$element[0].offsetHeight + }) + + this.$menu + .insertAfter(this.$element) + .css({ + top: pos.top + pos.height + , left: pos.left + }) + .show() + + this.shown = true + return this + } + + , hide: function () { + this.$menu.hide() + this.shown = false + return this + } + + , lookup: function (event) { + var items + + this.query = this.$element.val() + + if (!this.query || this.query.length < this.options.minLength) { + return this.shown ? this.hide() : this + } + + items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source + + return items ? this.process(items) : this + } + + , process: function (items) { + var that = this + + items = $.grep(items, function (item) { + return that.matcher(item) + }) + + items = this.sorter(items) + + if (!items.length) { + return this.shown ? this.hide() : this + } + + return this.render(items.slice(0, this.options.items)).show() + } + + , matcher: function (item) { + return ~item.toLowerCase().indexOf(this.query.toLowerCase()) + } + + , sorter: function (items) { + var beginswith = [] + , caseSensitive = [] + , caseInsensitive = [] + , item + + while (item = items.shift()) { + if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) + else if (~item.indexOf(this.query)) caseSensitive.push(item) + else caseInsensitive.push(item) + } + + return beginswith.concat(caseSensitive, caseInsensitive) + } + + , highlighter: function (item) { + var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') + return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { + return '' + match + '' + }) + } + + , render: function (items) { + var that = this + + items = $(items).map(function (i, item) { + i = $(that.options.item).attr('data-value', item) + i.find('a').html(that.highlighter(item)) + return i[0] + }) + + items.first().addClass('active') + this.$menu.html(items) + return this + } + + , next: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , next = active.next() + + if (!next.length) { + next = $(this.$menu.find('li')[0]) + } + + next.addClass('active') + } + + , prev: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , prev = active.prev() + + if (!prev.length) { + prev = this.$menu.find('li').last() + } + + prev.addClass('active') + } + + , listen: function () { + this.$element + .on('focus', $.proxy(this.focus, this)) + .on('blur', $.proxy(this.blur, this)) + .on('keypress', $.proxy(this.keypress, this)) + .on('keyup', $.proxy(this.keyup, this)) + + if (this.eventSupported('keydown')) { + this.$element.on('keydown', $.proxy(this.keydown, this)) + } + + this.$menu + .on('click', $.proxy(this.click, this)) + .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) + .on('mouseleave', 'li', $.proxy(this.mouseleave, this)) + } + + , eventSupported: function(eventName) { + var isSupported = eventName in this.$element + if (!isSupported) { + this.$element.setAttribute(eventName, 'return;') + isSupported = typeof this.$element[eventName] === 'function' + } + return isSupported + } + + , move: function (e) { + if (!this.shown) return + + switch(e.keyCode) { + case 9: // tab + case 13: // enter + case 27: // escape + e.preventDefault() + break + + case 38: // up arrow + e.preventDefault() + this.prev() + break + + case 40: // down arrow + e.preventDefault() + this.next() + break + } + + e.stopPropagation() + } + + , keydown: function (e) { + this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]) + this.move(e) + } + + , keypress: function (e) { + if (this.suppressKeyPressRepeat) return + this.move(e) + } + + , keyup: function (e) { + switch(e.keyCode) { + case 40: // down arrow + case 38: // up arrow + case 16: // shift + case 17: // ctrl + case 18: // alt + break + + case 9: // tab + case 13: // enter + if (!this.shown) return + this.select() + break + + case 27: // escape + if (!this.shown) return + this.hide() + break + + default: + this.lookup() + } + + e.stopPropagation() + e.preventDefault() + } + + , focus: function (e) { + this.focused = true + } + + , blur: function (e) { + this.focused = false + if (!this.mousedover && this.shown) this.hide() + } + + , click: function (e) { + e.stopPropagation() + e.preventDefault() + this.select() + this.$element.focus() + } + + , mouseenter: function (e) { + this.mousedover = true + this.$menu.find('.active').removeClass('active') + $(e.currentTarget).addClass('active') + } + + , mouseleave: function (e) { + this.mousedover = false + if (!this.focused && this.shown) this.hide() + } + + } + + + /* TYPEAHEAD PLUGIN DEFINITION + * =========================== */ + + var old = $.fn.typeahead + + $.fn.typeahead = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('typeahead') + , options = typeof option == 'object' && option + if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.typeahead.defaults = { + source: [] + , items: 8 + , menu: '' + , item: '
  • ' + , minLength: 1 + } + + $.fn.typeahead.Constructor = Typeahead + + + /* TYPEAHEAD NO CONFLICT + * =================== */ + + $.fn.typeahead.noConflict = function () { + $.fn.typeahead = old + return this + } + + + /* TYPEAHEAD DATA-API + * ================== */ + + $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { + var $this = $(this) + if ($this.data('typeahead')) return + $this.typeahead($this.data()) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.css b/components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.css new file mode 100644 index 00000000..f3323b0a --- /dev/null +++ b/components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.css @@ -0,0 +1,142 @@ +/*! + * jQuery contextMenu - Plugin for simple contextMenu handling + * + * Version: 1.5.25 + * + * Authors: Rodney Rehm, Addy Osmani (patches for FF) + * Web: http://medialize.github.com/jQuery-contextMenu/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ + +.context-menu-list { + margin:0; + padding:0; + + min-width: 120px; + max-width: 250px; + display: inline-block; + position: absolute; + list-style-type: none; + + border: 1px solid #DDD; + background: #EEE; + + -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + -ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + -o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; +} + +.context-menu-item { + padding: 2px 2px 2px 24px; + background-color: #EEE; + position: relative; + -webkit-user-select: none; + -moz-user-select: -moz-none; + -ms-user-select: none; + user-select: none; +} + +.context-menu-separator { + padding-bottom:0; + border-bottom: 1px solid #DDD; +} + +.context-menu-item > label > input, +.context-menu-item > label > textarea { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.context-menu-item.hover { + cursor: pointer; + background-color: #39F; +} + +.context-menu-item.disabled { + color: #666; +} + +.context-menu-input.hover, +.context-menu-item.disabled.hover { + cursor: default; + background-color: #EEE; +} + +.context-menu-submenu:after { + content: ">"; + color: #666; + position: absolute; + top: 0; + right: 3px; + z-index: 1; +} + +/* icons + #protip: + In case you want to use sprites for icons (which I would suggest you do) have a look at + http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement + .context-menu-item.icon:before {} + */ +.context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; } +.context-menu-item.icon-edit { background-image: url(images/page_white_edit.png); } +.context-menu-item.icon-cut { background-image: url(images/cut.png); } +.context-menu-item.icon-copy { background-image: url(images/page_white_copy.png); } +.context-menu-item.icon-paste { background-image: url(images/page_white_paste.png); } +.context-menu-item.icon-delete { background-image: url(images/page_white_delete.png); } +.context-menu-item.icon-add { background-image: url(images/page_white_add.png); } +.context-menu-item.icon-quit { background-image: url(images/door.png); } + +/* vertically align inside labels */ +.context-menu-input > label > * { vertical-align: top; } + +/* position checkboxes and radios as icons */ +.context-menu-input > label > input[type="checkbox"], +.context-menu-input > label > input[type="radio"] { + margin-left: -17px; +} +.context-menu-input > label > span { + margin-left: 5px; +} + +.context-menu-input > label, +.context-menu-input > label > input[type="text"], +.context-menu-input > label > textarea, +.context-menu-input > label > select { + display: block; + width: 100%; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} + +.context-menu-input > label > textarea { + height: 100px; +} +.context-menu-item > .context-menu-list { + display: none; + /* re-positioned by js */ + right: -5px; + top: 5px; +} + +.context-menu-item.hover > .context-menu-list { + display: block; +} + +.context-menu-accesskey { + text-decoration: underline; +} diff --git a/components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.js b/components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.js new file mode 100644 index 00000000..c8c07097 --- /dev/null +++ b/components/handsontable/lib/jQuery-contextMenu/jquery.contextMenu.js @@ -0,0 +1,1585 @@ +/*! + * jQuery contextMenu - Plugin for simple contextMenu handling + * + * Version: 1.5.25 + * + * Authors: Rodney Rehm, Addy Osmani (patches for FF) + * Web: http://medialize.github.com/jQuery-contextMenu/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ + +(function($, undefined){ + + // TODO: - + // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio + // create structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative + +// determine html5 compatibility +$.support.htmlMenuitem = ('HTMLMenuItemElement' in window); +$.support.htmlCommand = ('HTMLCommandElement' in window); +$.support.eventSelectstart = ("onselectstart" in document.documentElement); +/* // should the need arise, test for css user-select +$.support.cssUserSelect = (function(){ + var t = false, + e = document.createElement('div'); + + $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) { + var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect', + prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select'; + + e.style.cssText = prop + ': text;'; + if (e.style[propCC] == 'text') { + t = true; + return false; + } + + return true; + }); + + return t; +})(); +*/ + +var // currently active contextMenu trigger + $currentTrigger = null, + // is contextMenu initialized with at least one menu? + initialized = false, + // window handle + $win = $(window), + // number of registered menus + counter = 0, + // mapping selector to namespace + namespaces = {}, + // mapping namespace to options + menus = {}, + // custom command type handlers + types = {}, + // default values + defaults = { + // selector of contextMenu trigger + selector: null, + // where to append the menu to + appendTo: null, + // method to trigger context menu ["right", "left", "hover"] + trigger: "right", + // hide menu when mouse leaves trigger / menu elements + autoHide: false, + // ms to wait before showing a hover-triggered context menu + delay: 200, + // determine position to show menu at + determinePosition: function($menu) { + // position to the lower middle of the trigger element + if ($.ui && $.ui.position) { + // .position() is provided as a jQuery UI utility + // (...and it won't work on hidden elements) + $menu.css('display', 'block').position({ + my: "center top", + at: "center bottom", + of: this, + offset: "0 5", + collision: "fit" + }).css('display', 'none'); + } else { + // determine contextMenu position + var offset = this.offset(); + offset.top += this.outerHeight(); + offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2; + $menu.css(offset); + } + }, + // position menu + position: function(opt, x, y) { + var $this = this, + offset; + // determine contextMenu position + if (!x && !y) { + opt.determinePosition.call(this, opt.$menu); + return; + } else if (x === "maintain" && y === "maintain") { + // x and y must not be changed (after re-show on command click) + offset = opt.$menu.position(); + } else { + // x and y are given (by mouse event) + var triggerIsFixed = opt.$trigger.parents().andSelf() + .filter(function() { + return $(this).css('position') == "fixed"; + }).length; + + if (triggerIsFixed) { + y -= $win.scrollTop(); + x -= $win.scrollLeft(); + } + offset = {top: y, left: x}; + } + + // correct offset if viewport demands it + var bottom = $win.scrollTop() + $win.height(), + right = $win.scrollLeft() + $win.width(), + height = opt.$menu.height(), + width = opt.$menu.width(); + + if (offset.top + height > bottom) { + offset.top -= height; + } + + if (offset.left + width > right) { + offset.left -= width; + } + + opt.$menu.css(offset); + }, + // position the sub-menu + positionSubmenu: function($menu) { + if ($.ui && $.ui.position) { + // .position() is provided as a jQuery UI utility + // (...and it won't work on hidden elements) + $menu.css('display', 'block').position({ + my: "left top", + at: "right top", + of: this, + collision: "fit" + }).css('display', ''); + } else { + // determine contextMenu position + var offset = { + top: 0, + left: this.outerWidth() + }; + $menu.css(offset); + } + }, + // offset to add to zIndex + zIndex: 1, + // show hide animation settings + animation: { + duration: 50, + show: 'slideDown', + hide: 'slideUp' + }, + // events + events: { + show: $.noop, + hide: $.noop + }, + // default callback + callback: null, + // list of contextMenu items + items: {} + }, + // mouse position for hover activation + hoveract = { + timer: null, + pageX: null, + pageY: null + }, + // determine zIndex + zindex = function($t) { + var zin = 0, + $tt = $t; + + while (true) { + zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); + $tt = $tt.parent(); + if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase()) > -1 ) { + break; + } + } + + return zin; + }, + // event handlers + handle = { + // abort anything + abortevent: function(e){ + e.preventDefault(); + e.stopImmediatePropagation(); + }, + + // contextmenu show dispatcher + contextmenu: function(e) { + var $this = $(this); + + // disable actual context-menu + e.preventDefault(); + e.stopImmediatePropagation(); + + // abort native-triggered events unless we're triggering on right click + if (e.data.trigger != 'right' && e.originalEvent) { + return; + } + + if (!$this.hasClass('context-menu-disabled')) { + // theoretically need to fire a show event at + // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus + // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); + // e.data.$menu.trigger(evt); + + $currentTrigger = $this; + if (e.data.build) { + var built = e.data.build($currentTrigger, e); + // abort if build() returned false + if (built === false) { + return; + } + + // dynamically build menu on invocation + e.data = $.extend(true, {}, defaults, e.data, built || {}); + + // abort if there are no items to display + if (!e.data.items || $.isEmptyObject(e.data.items)) { + // Note: jQuery captures and ignores errors from event handlers + if (window.console) { + (console.error || console.log)("No items specified to show in contextMenu"); + } + + throw new Error('No Items sepcified'); + } + + // backreference for custom command type creation + e.data.$trigger = $currentTrigger; + + op.create(e.data); + } + // show menu + op.show.call($this, e.data, e.pageX, e.pageY); + } + }, + // contextMenu left-click trigger + click: function(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + $(this).trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); + }, + // contextMenu right-click trigger + mousedown: function(e) { + // register mouse down + var $this = $(this); + + // hide any previous menus + if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) { + $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide'); + } + + // activate on right click + if (e.button == 2) { + $currentTrigger = $this.data('contextMenuActive', true); + } + }, + // contextMenu right-click trigger + mouseup: function(e) { + // show menu + var $this = $(this); + if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { + e.preventDefault(); + e.stopImmediatePropagation(); + $currentTrigger = $this; + $this.trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); + } + + $this.removeData('contextMenuActive'); + }, + // contextMenu hover trigger + mouseenter: function(e) { + var $this = $(this), + $related = $(e.relatedTarget), + $document = $(document); + + // abort if we're coming from a menu + if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { + return; + } + + // abort if a menu is shown + if ($currentTrigger && $currentTrigger.length) { + return; + } + + hoveract.pageX = e.pageX; + hoveract.pageY = e.pageY; + hoveract.data = e.data; + $document.on('mousemove.contextMenuShow', handle.mousemove); + hoveract.timer = setTimeout(function() { + hoveract.timer = null; + $document.off('mousemove.contextMenuShow'); + $currentTrigger = $this; + $this.trigger($.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY })); + }, e.data.delay ); + }, + // contextMenu hover trigger + mousemove: function(e) { + hoveract.pageX = e.pageX; + hoveract.pageY = e.pageY; + }, + // contextMenu hover trigger + mouseleave: function(e) { + // abort if we're leaving for a menu + var $related = $(e.relatedTarget); + if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { + return; + } + + try { + clearTimeout(hoveract.timer); + } catch(e) {} + + hoveract.timer = null; + }, + + // click on layer to hide contextMenu + layerClick: function(e) { + var $this = $(this), + root = $this.data('contextMenuRoot'), + mouseup = false, + button = e.button, + x = e.pageX, + y = e.pageY, + target, + offset, + selectors; + + e.preventDefault(); + e.stopImmediatePropagation(); + + // This hack looks about as ugly as it is + // Firefox 12 (at least) fires the contextmenu event directly "after" mousedown + // for some reason `root.$layer.hide(); document.elementFromPoint()` causes this + // contextmenu event to be triggered on the uncovered element instead of on the + // layer (where every other sane browser, including Firefox nightly at the time) + // triggers the event. This workaround might be obsolete by September 2012. + $this.on('mouseup', function() { + mouseup = true; + }); + setTimeout(function() { + var $window, hideshow; + // test if we need to reposition the menu + if ((root.trigger == 'left' && button == 0) || (root.trigger == 'right' && button == 2)) { + if (document.elementFromPoint) { + root.$layer.hide(); + target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop()); + root.$layer.show(); + + selectors = []; + for (var s in namespaces) { + selectors.push(s); + } + + target = $(target).closest(selectors.join(', ')); + + if (target.length) { + if (target.is(root.$trigger[0])) { + root.position.call(root.$trigger, root, x, y); + return; + } + } + } else { + offset = root.$trigger.offset(); + $window = $(window); + // while this looks kinda awful, it's the best way to avoid + // unnecessarily calculating any positions + offset.top += $window.scrollTop(); + if (offset.top <= e.pageY) { + offset.left += $window.scrollLeft(); + if (offset.left <= e.pageX) { + offset.bottom = offset.top + root.$trigger.outerHeight(); + if (offset.bottom >= e.pageY) { + offset.right = offset.left + root.$trigger.outerWidth(); + if (offset.right >= e.pageX) { + // reposition + root.position.call(root.$trigger, root, x, y); + return; + } + } + } + } + } + } + + hideshow = function(e) { + if (e) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + + root.$menu.trigger('contextmenu:hide'); + if (target && target.length) { + setTimeout(function() { + target.contextMenu({x: x, y: y}); + }, 50); + } + }; + + if (mouseup) { + // mouseup has already happened + hideshow(); + } else { + // remove only after mouseup has completed + $this.on('mouseup', hideshow); + } + }, 50); + }, + // key handled :hover + keyStop: function(e, opt) { + if (!opt.isInput) { + e.preventDefault(); + } + + e.stopPropagation(); + }, + key: function(e) { + var opt = $currentTrigger.data('contextMenu') || {}, + $children = opt.$menu.children(), + $round; + + switch (e.keyCode) { + case 9: + case 38: // up + handle.keyStop(e, opt); + // if keyCode is [38 (up)] or [9 (tab) with shift] + if (opt.isInput) { + if (e.keyCode == 9 && e.shiftKey) { + e.preventDefault(); + opt.$selected && opt.$selected.find('input, textarea, select').blur(); + opt.$menu.trigger('prevcommand'); + return; + } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') { + // checkboxes don't capture this key + e.preventDefault(); + return; + } + } else if (e.keyCode != 9 || e.shiftKey) { + opt.$menu.trigger('prevcommand'); + return; + } + // omitting break; + + // case 9: // tab - reached through omitted break; + case 40: // down + handle.keyStop(e, opt); + if (opt.isInput) { + if (e.keyCode == 9) { + e.preventDefault(); + opt.$selected && opt.$selected.find('input, textarea, select').blur(); + opt.$menu.trigger('nextcommand'); + return; + } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') { + // checkboxes don't capture this key + e.preventDefault(); + return; + } + } else { + opt.$menu.trigger('nextcommand'); + return; + } + break; + + case 37: // left + handle.keyStop(e, opt); + if (opt.isInput || !opt.$selected || !opt.$selected.length) { + break; + } + + if (!opt.$selected.parent().hasClass('context-menu-root')) { + var $parent = opt.$selected.parent().parent(); + opt.$selected.trigger('contextmenu:blur'); + opt.$selected = $parent; + return; + } + break; + + case 39: // right + handle.keyStop(e, opt); + if (opt.isInput || !opt.$selected || !opt.$selected.length) { + break; + } + + var itemdata = opt.$selected.data('contextMenu') || {}; + if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) { + opt.$selected = null; + itemdata.$selected = null; + itemdata.$menu.trigger('nextcommand'); + return; + } + break; + + case 35: // end + case 36: // home + if (opt.$selected && opt.$selected.find('input, textarea, select').length) { + return; + } else { + (opt.$selected && opt.$selected.parent() || opt.$menu) + .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']() + .trigger('contextmenu:focus'); + e.preventDefault(); + return; + } + break; + + case 13: // enter + handle.keyStop(e, opt); + if (opt.isInput) { + if (opt.$selected && !opt.$selected.is('textarea, select')) { + e.preventDefault(); + return; + } + break; + } + opt.$selected && opt.$selected.trigger('mouseup'); + return; + + case 32: // space + case 33: // page up + case 34: // page down + // prevent browser from scrolling down while menu is visible + handle.keyStop(e, opt); + return; + + case 27: // esc + handle.keyStop(e, opt); + opt.$menu.trigger('contextmenu:hide'); + return; + + default: // 0-9, a-z + var k = (String.fromCharCode(e.keyCode)).toUpperCase(); + if (opt.accesskeys[k]) { + // according to the specs accesskeys must be invoked immediately + opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu + ? 'contextmenu:focus' + : 'mouseup' + ); + return; + } + break; + } + // pass event to selected item, + // stop propagation to avoid endless recursion + e.stopPropagation(); + opt.$selected && opt.$selected.trigger(e); + }, + + // select previous possible command in menu + prevItem: function(e) { + e.stopPropagation(); + var opt = $(this).data('contextMenu') || {}; + + // obtain currently selected menu + if (opt.$selected) { + var $s = opt.$selected; + opt = opt.$selected.parent().data('contextMenu') || {}; + opt.$selected = $s; + } + + var $children = opt.$menu.children(), + $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(), + $round = $prev; + + // skip disabled + while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) { + if ($prev.prev().length) { + $prev = $prev.prev(); + } else { + $prev = $children.last(); + } + if ($prev.is($round)) { + // break endless loop + return; + } + } + + // leave current + if (opt.$selected) { + handle.itemMouseleave.call(opt.$selected.get(0), e); + } + + // activate next + handle.itemMouseenter.call($prev.get(0), e); + + // focus input + var $input = $prev.find('input, textarea, select'); + if ($input.length) { + $input.focus(); + } + }, + // select next possible command in menu + nextItem: function(e) { + e.stopPropagation(); + var opt = $(this).data('contextMenu') || {}; + + // obtain currently selected menu + if (opt.$selected) { + var $s = opt.$selected; + opt = opt.$selected.parent().data('contextMenu') || {}; + opt.$selected = $s; + } + + var $children = opt.$menu.children(), + $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(), + $round = $next; + + // skip disabled + while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) { + if ($next.next().length) { + $next = $next.next(); + } else { + $next = $children.first(); + } + if ($next.is($round)) { + // break endless loop + return; + } + } + + // leave current + if (opt.$selected) { + handle.itemMouseleave.call(opt.$selected.get(0), e); + } + + // activate next + handle.itemMouseenter.call($next.get(0), e); + + // focus input + var $input = $next.find('input, textarea, select'); + if ($input.length) { + $input.focus(); + } + }, + + // flag that we're inside an input so the key handler can act accordingly + focusInput: function(e) { + var $this = $(this).closest('.context-menu-item'), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.$selected = opt.$selected = $this; + root.isInput = opt.isInput = true; + }, + // flag that we're inside an input so the key handler can act accordingly + blurInput: function(e) { + var $this = $(this).closest('.context-menu-item'), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.isInput = opt.isInput = false; + }, + + // :hover on menu + menuMouseenter: function(e) { + var root = $(this).data().contextMenuRoot; + root.hovering = true; + }, + // :hover on menu + menuMouseleave: function(e) { + var root = $(this).data().contextMenuRoot; + if (root.$layer && root.$layer.is(e.relatedTarget)) { + root.hovering = false; + } + }, + + // :hover done manually so key handling is possible + itemMouseenter: function(e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.hovering = true; + + // abort if we're re-entering + if (e && root.$layer && root.$layer.is(e.relatedTarget)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + + // make sure only one item is selected + (opt.$menu ? opt : root).$menu + .children('.hover').trigger('contextmenu:blur'); + + if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) { + opt.$selected = null; + return; + } + + $this.trigger('contextmenu:focus'); + }, + // :hover done manually so key handling is possible + itemMouseleave: function(e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) { + root.$selected && root.$selected.trigger('contextmenu:blur'); + e.preventDefault(); + e.stopImmediatePropagation(); + root.$selected = opt.$selected = opt.$node; + return; + } + + $this.trigger('contextmenu:blur'); + }, + // contextMenu item click + itemClick: function(e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot, + key = data.contextMenuKey, + callback; + + // abort if the key is unknown or disabled or is a menu + if (!opt.items[key] || $this.hasClass('disabled') || $this.hasClass('context-menu-submenu')) { + return; + } + + e.preventDefault(); + e.stopImmediatePropagation(); + + if ($.isFunction(root.callbacks[key])) { + // item-specific callback + callback = root.callbacks[key]; + } else if ($.isFunction(root.callback)) { + // default callback + callback = root.callback; + } else { + // no callback, no action + return; + } + + // hide menu if callback doesn't stop that + if (callback.call(root.$trigger, key, root) !== false) { + root.$menu.trigger('contextmenu:hide'); + } else if (root.$menu.parent().length) { + op.update.call(root.$trigger, root); + } + }, + // ignore click events on input elements + inputClick: function(e) { + e.stopImmediatePropagation(); + }, + + // hide + hideMenu: function(e, data) { + var root = $(this).data('contextMenuRoot'); + op.hide.call(root.$trigger, root, data && data.force); + }, + // focus + focusItem: function(e) { + e.stopPropagation(); + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + $this.addClass('hover') + .siblings('.hover').trigger('contextmenu:blur'); + + // remember selected + opt.$selected = root.$selected = $this; + + // position sub-menu - do after show so dumb $.ui.position can keep up + if (opt.$node) { + root.positionSubmenu.call(opt.$node, opt.$menu); + } + }, + // blur + blurItem: function(e) { + e.stopPropagation(); + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + $this.removeClass('hover'); + opt.$selected = null; + } + }, + // operations + op = { + show: function(opt, x, y) { + var $this = $(this), + offset, + css = {}; + + // hide any open menus + $('#context-menu-layer').trigger('mousedown'); + + // backreference for callbacks + opt.$trigger = $this; + + // show event + if (opt.events.show.call($this, opt) === false) { + $currentTrigger = null; + return; + } + + // create or update context menu + op.update.call($this, opt); + + // position menu + opt.position.call($this, opt, x, y); + + // make sure we're in front + if (opt.zIndex) { + css.zIndex = zindex($this) + opt.zIndex; + } + + // add layer + op.layer.call(opt.$menu, opt, css.zIndex); + + // adjust sub-menu zIndexes + opt.$menu.find('ul').css('zIndex', css.zIndex + 1); + + // position and show context menu + opt.$menu.css( css )[opt.animation.show](opt.animation.duration); + // make options available + $this.data('contextMenu', opt); + // register key handler + $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key); + // register autoHide handler + if (opt.autoHide) { + // trigger element coordinates + var pos = $this.position(); + pos.right = pos.left + $this.outerWidth(); + pos.bottom = pos.top + this.outerHeight(); + // mouse position handler + $(document).on('mousemove.contextMenuAutoHide', function(e) { + if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) { + // if mouse in menu... + opt.$menu.trigger('contextmenu:hide'); + } + }); + } + }, + hide: function(opt, force) { + var $this = $(this); + if (!opt) { + opt = $this.data('contextMenu') || {}; + } + + // hide event + if (!force && opt.events && opt.events.hide.call($this, opt) === false) { + return; + } + + if (opt.$layer) { + // keep layer for a bit so the contextmenu event can be aborted properly by opera + setTimeout((function($layer){ return function(){ + $layer.remove(); + }; + })(opt.$layer), 10); + + try { + delete opt.$layer; + } catch(e) { + opt.$layer = null; + } + } + + // remove handle + $currentTrigger = null; + // remove selected + opt.$menu.find('.hover').trigger('contextmenu:blur'); + opt.$selected = null; + // unregister key and mouse handlers + //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 + $(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); + // hide menu + opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function (){ + // tear down dynamically built menu after animation is completed. + if (opt.build) { + opt.$menu.remove(); + $.each(opt, function(key, value) { + switch (key) { + case 'ns': + case 'selector': + case 'build': + case 'trigger': + return true; + + default: + opt[key] = undefined; + try { + delete opt[key]; + } catch (e) {} + return true; + } + }); + } + }); + }, + create: function(opt, root) { + if (root === undefined) { + root = opt; + } + // create contextMenu + opt.$menu = $('
      ').data({ + 'contextMenu': opt, + 'contextMenuRoot': root + }); + + $.each(['callbacks', 'commands', 'inputs'], function(i,k){ + opt[k] = {}; + if (!root[k]) { + root[k] = {}; + } + }); + + root.accesskeys || (root.accesskeys = {}); + + // create contextMenu items + $.each(opt.items, function(key, item){ + var $t = $('
    • '), + $label = null, + $input = null; + + item.$node = $t.data({ + 'contextMenu': opt, + 'contextMenuRoot': root, + 'contextMenuKey': key + }); + + // register accesskey + // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that + if (item.accesskey) { + var aks = splitAccesskey(item.accesskey); + for (var i=0, ak; ak = aks[i]; i++) { + if (!root.accesskeys[ak]) { + root.accesskeys[ak] = item; + item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '$1'); + break; + } + } + } + + if (typeof item == "string") { + $t.addClass('context-menu-separator not-selectable'); + } else if (item.type && types[item.type]) { + // run custom type handler + types[item.type].call($t, item, opt, root); + // register commands + $.each([opt, root], function(i,k){ + k.commands[key] = item; + if ($.isFunction(item.callback)) { + k.callbacks[key] = item.callback; + } + }); + } else { + // add label for input + if (item.type == 'html') { + $t.addClass('context-menu-html not-selectable'); + } else if (item.type) { + $label = $('').appendTo($t); + $('').html(item._name || item.name).appendTo($label); + $t.addClass('context-menu-input'); + opt.hasTypes = true; + $.each([opt, root], function(i,k){ + k.commands[key] = item; + k.inputs[key] = item; + }); + } else if (item.items) { + item.type = 'sub'; + } + + switch (item.type) { + case 'text': + $input = $('') + .val(item.value || "").appendTo($label); + break; + + case 'textarea': + $input = $('') + .val(item.value || "").appendTo($label); + + if (item.height) { + $input.height(item.height); + } + break; + + case 'checkbox': + $input = $('') + .val(item.value || "").prop("checked", !!item.selected).prependTo($label); + break; + + case 'radio': + $input = $('') + .val(item.value || "").prop("checked", !!item.selected).prependTo($label); + break; + + case 'select': + $input = $(' + if (item.type && item.type != 'sub' && item.type != 'html') { + $input + .on('focus', handle.focusInput) + .on('blur', handle.blurInput); + + if (item.events) { + $input.on(item.events, opt); + } + } + + // add icons + if (item.icon) { + $t.addClass("icon icon-" + item.icon); + } + } + + // cache contained elements + item.$input = $input; + item.$label = $label; + + // attach item to menu + $t.appendTo(opt.$menu); + + // Disable text selection + if (!opt.hasTypes && $.support.eventSelectstart) { + // browsers support user-select: none, + // IE has a special event for text-selection + // browsers supporting neither will not be preventing text-selection + $t.on('selectstart.disableTextSelect', handle.abortevent); + } + }); + // attach contextMenu to (to bypass any possible overflow:hidden issues on parents of the trigger element) + if (!opt.$node) { + opt.$menu.css('display', 'none').addClass('context-menu-root'); + } + opt.$menu.appendTo(opt.appendTo || document.body); + }, + update: function(opt, root) { + var $this = this; + if (root === undefined) { + root = opt; + // determine widths of submenus, as CSS won't grow them automatically + // position:absolute > position:absolute; min-width:100; max-width:200; results in width: 100; + // kinda sucks hard... + opt.$menu.find('ul').andSelf().css({position: 'static', display: 'block'}).each(function(){ + var $this = $(this); + $this.width($this.css('position', 'absolute').width()) + .css('position', 'static'); + }).css({position: '', display: ''}); + } + // re-check disabled for each item + opt.$menu.children().each(function(){ + var $item = $(this), + key = $item.data('contextMenuKey'), + item = opt.items[key], + disabled = ($.isFunction(item.disabled) && item.disabled.call($this, key, root)) || item.disabled === true; + + // dis- / enable item + $item[disabled ? 'addClass' : 'removeClass']('disabled'); + + if (item.type) { + // dis- / enable input elements + $item.find('input, select, textarea').prop('disabled', disabled); + + // update input states + switch (item.type) { + case 'text': + case 'textarea': + item.$input.val(item.value || ""); + break; + + case 'checkbox': + case 'radio': + item.$input.val(item.value || "").prop('checked', !!item.selected); + break; + + case 'select': + item.$input.val(item.selected || ""); + break; + } + } + + if (item.$menu) { + // update sub-menu + op.update.call($this, item, root); + } + }); + }, + layer: function(opt, zIndex) { + // add transparent layer for click area + // filter and background for Internet Explorer, Issue #23 + var $layer = opt.$layer = $('
      ') + .css({height: $win.height(), width: $win.width(), display: 'block'}) + .data('contextMenuRoot', opt) + .insertBefore(this) + .on('contextmenu', handle.abortevent) + .on('mousedown', handle.layerClick); + + // IE6 doesn't know position:fixed; + if (!$.support.fixedPosition) { + $layer.css({ + 'position' : 'absolute', + 'height' : $(document).height() + }); + } + + return $layer; + } + }; + +// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key +function splitAccesskey(val) { + var t = val.split(/\s+/), + keys = []; + + for (var i=0, k; k = t[i]; i++) { + k = k[0].toUpperCase(); // first character only + // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. + // a map to look up already used access keys would be nice + keys.push(k); + } + + return keys; +} + +// handle contextMenu triggers +$.fn.contextMenu = function(operation) { + if (operation === undefined) { + this.first().trigger('contextmenu'); + } else if (operation.x && operation.y) { + this.first().trigger($.Event("contextmenu", {pageX: operation.x, pageY: operation.y})); + } else if (operation === "hide") { + var $menu = this.data('contextMenu').$menu; + $menu && $menu.trigger('contextmenu:hide'); + } else if (operation) { + this.removeClass('context-menu-disabled'); + } else if (!operation) { + this.addClass('context-menu-disabled'); + } + + return this; +}; + +// manage contextMenu instances +$.contextMenu = function(operation, options) { + if (typeof operation != 'string') { + options = operation; + operation = 'create'; + } + + if (typeof options == 'string') { + options = {selector: options}; + } else if (options === undefined) { + options = {}; + } + + // merge with default options + var o = $.extend(true, {}, defaults, options || {}), + $document = $(document); + + switch (operation) { + case 'create': + // no selector no joy + if (!o.selector) { + throw new Error('No selector specified'); + } + // make sure internal classes are not bound to + if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { + throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); + } + if (!o.build && (!o.items || $.isEmptyObject(o.items))) { + throw new Error('No Items sepcified'); + } + counter ++; + o.ns = '.contextMenu' + counter; + namespaces[o.selector] = o.ns; + menus[o.ns] = o; + + // default to right click + if (!o.trigger) { + o.trigger = 'right'; + } + + if (!initialized) { + // make sure item click is registered first + $document + .on({ + 'contextmenu:hide.contextMenu': handle.hideMenu, + 'prevcommand.contextMenu': handle.prevItem, + 'nextcommand.contextMenu': handle.nextItem, + 'contextmenu.contextMenu': handle.abortevent, + 'mouseenter.contextMenu': handle.menuMouseenter, + 'mouseleave.contextMenu': handle.menuMouseleave + }, '.context-menu-list') + .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) + .on({ + 'mouseup.contextMenu': handle.itemClick, + 'contextmenu:focus.contextMenu': handle.focusItem, + 'contextmenu:blur.contextMenu': handle.blurItem, + 'contextmenu.contextMenu': handle.abortevent, + 'mouseenter.contextMenu': handle.itemMouseenter, + 'mouseleave.contextMenu': handle.itemMouseleave + }, '.context-menu-item'); + + initialized = true; + } + + // engage native contextmenu event + $document + .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); + + switch (o.trigger) { + case 'hover': + $document + .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) + .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); + break; + + case 'left': + $document.on('click' + o.ns, o.selector, o, handle.click); + break; + /* + default: + // http://www.quirksmode.org/dom/events/contextmenu.html + $document + .on('mousedown' + o.ns, o.selector, o, handle.mousedown) + .on('mouseup' + o.ns, o.selector, o, handle.mouseup); + break; + */ + } + + // create menu + if (!o.build) { + op.create(o); + } + break; + + case 'destroy': + if (!o.selector) { + $document.off('.contextMenu .contextMenuAutoHide'); + $.each(namespaces, function(key, value) { + $document.off(value); + }); + + namespaces = {}; + menus = {}; + counter = 0; + initialized = false; + + $('#context-menu-layer, .context-menu-list').remove(); + } else if (namespaces[o.selector]) { + var $visibleMenu = $('.context-menu-list').filter(':visible'); + if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { + $visibleMenu.trigger('contextmenu:hide', {force: true}); + } + + try { + if (menus[namespaces[o.selector]].$menu) { + menus[namespaces[o.selector]].$menu.remove(); + } + + delete menus[namespaces[o.selector]]; + } catch(e) { + menus[namespaces[o.selector]] = null; + } + + $document.off(namespaces[o.selector]); + } + break; + + case 'html5': + // if or are not handled by the browser, + // or options was a bool true, + // initialize $.contextMenu for them + if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) { + $('menu[type="context"]').each(function() { + if (this.id) { + $.contextMenu({ + selector: '[contextmenu=' + this.id +']', + items: $.contextMenu.fromMenu(this) + }); + } + }).css('display', 'none'); + } + break; + + default: + throw new Error('Unknown operation "' + operation + '"'); + } + + return this; +}; + +// import values into commands +$.contextMenu.setInputValues = function(opt, data) { + if (data === undefined) { + data = {}; + } + + $.each(opt.inputs, function(key, item) { + switch (item.type) { + case 'text': + case 'textarea': + item.value = data[key] || ""; + break; + + case 'checkbox': + item.selected = data[key] ? true : false; + break; + + case 'radio': + item.selected = (data[item.radio] || "") == item.value ? true : false; + break; + + case 'select': + item.selected = data[key] || ""; + break; + } + }); +}; + +// export values from commands +$.contextMenu.getInputValues = function(opt, data) { + if (data === undefined) { + data = {}; + } + + $.each(opt.inputs, function(key, item) { + switch (item.type) { + case 'text': + case 'textarea': + case 'select': + data[key] = item.$input.val(); + break; + + case 'checkbox': + data[key] = item.$input.prop('checked'); + break; + + case 'radio': + if (item.$input.prop('checked')) { + data[item.radio] = item.value; + } + break; + } + }); + + return data; +}; + +// find