├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── LICENSE ├── README.md ├── bower.json ├── examples ├── bind-rows-with-headers.html ├── collapsing-columns.html ├── components │ ├── age-renderer.html │ ├── age-renderer.js │ ├── bold-renderer.html │ ├── bold-renderer.js │ ├── colored-renderer.html │ ├── colored-renderer.js │ ├── custom-google-map │ │ └── custom-google-map-search.html │ ├── dashboard-demo.html │ ├── dashboard-tile.html │ ├── language-editor.html │ └── language-editor.js ├── css │ └── main.css ├── dashboard.html ├── data-filtering.html ├── dynamically-generated-context-menu.html ├── editors.html ├── fixing-bottom-rows.html ├── js │ └── data.js ├── nested-tables.html ├── persistent-state.html ├── renderers.html └── resources │ └── flags │ ├── be.png │ ├── br.png │ ├── cz.png │ ├── de.png │ ├── dk.png │ ├── es.png │ ├── nl.png │ ├── no.png │ ├── pk.png │ ├── pl.png │ ├── ru.png │ ├── se.png │ ├── tj.png │ ├── tk.png │ └── us.png ├── gulpfile.js ├── hot-table.html ├── index.html ├── package.json ├── polymer.json ├── src ├── behavior │ └── public-methods.js ├── editor.js ├── hot-column.html ├── hot-column.js ├── hot-table-style.html ├── hot-table.html ├── hot-table.js ├── nested-tables.js └── settings-parser.js ├── test ├── SpecRunner.html ├── css │ └── SpecRunner.css ├── lib │ ├── jasmine-2.4.1 │ │ ├── boot.js │ │ ├── console.js │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ ├── jasmine.js │ │ └── jasmine_favicon.png │ ├── jquery.min.js │ └── jquery.simulate.js ├── spec │ ├── behavior │ │ └── public-methods.spec.js │ ├── hot-column.spec.js │ ├── hot-table.spec.js │ └── settings-parser.spec.js └── utils │ ├── my-custom-renderer.html │ ├── my-second-custom-renderer.html │ └── spec-helper.js └── wct.conf.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 2 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | .idea 4 | .bowerrc 5 | dev.html 6 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": false, 3 | "disallowSpacesInNamedFunctionExpression": { 4 | "beforeOpeningRoundBrace": true 5 | }, 6 | "disallowSpacesInFunctionExpression": { 7 | "beforeOpeningRoundBrace": true 8 | }, 9 | "disallowSpacesInAnonymousFunctionExpression": { 10 | "beforeOpeningRoundBrace": true 11 | }, 12 | "disallowSpacesInFunctionDeclaration": { 13 | "beforeOpeningRoundBrace": true 14 | }, 15 | "disallowEmptyBlocks": true, 16 | "disallowSpacesInCallExpression": true, 17 | "disallowSpacesInsideArrayBrackets": true, 18 | "disallowSpacesInsideParentheses": true, 19 | "disallowQuotedKeysInObjects": true, 20 | "disallowSpaceAfterObjectKeys": true, 21 | "disallowSpaceAfterPrefixUnaryOperators": true, 22 | "disallowSpaceBeforePostfixUnaryOperators": true, 23 | "disallowSpaceBeforeBinaryOperators": [ 24 | "," 25 | ], 26 | "disallowMixedSpacesAndTabs": true, 27 | "disallowTrailingWhitespace": true, 28 | "requireTrailingComma": false, 29 | "disallowYodaConditions": true, 30 | "disallowKeywords": [ "with" ], 31 | "disallowKeywordsOnNewLine": ["else"], 32 | "disallowMultipleLineBreaks": true, 33 | "disallowMultipleLineStrings": true, 34 | "disallowMultipleVarDecl": false, 35 | "disallowSpaceBeforeComma": true, 36 | "disallowSpaceBeforeSemicolon": true, 37 | "requireSpaceBeforeBlockStatements": true, 38 | "requireParenthesesAroundIIFE": true, 39 | "requireSpacesInConditionalExpression": true, 40 | "requireBlocksOnNewline": 1, 41 | "requireCommaBeforeLineBreak": true, 42 | "requireSpaceBeforeBinaryOperators": true, 43 | "requireSpaceAfterBinaryOperators": true, 44 | "requireCamelCaseOrUpperCaseIdentifiers": true, 45 | "requireLineFeedAtFileEnd": true, 46 | "requireCapitalizedConstructors": true, 47 | "requireDotNotation": true, 48 | "requireSpacesInForStatement": true, 49 | "requireSpaceBetweenArguments": true, 50 | "requireCurlyBraces": [ 51 | "if", 52 | "else", 53 | "for", 54 | "while", 55 | "do", 56 | "switch" 57 | ], 58 | "requireSpaceAfterKeywords": [ 59 | "if", 60 | "else", 61 | "for", 62 | "while", 63 | "do", 64 | "switch", 65 | "case", 66 | "return", 67 | "try", 68 | "catch", 69 | "typeof" 70 | ], 71 | "requirePaddingNewLinesBeforeLineComments": false, 72 | "requirePaddingNewLinesAfterBlocks": false, 73 | "requireSemicolons": true, 74 | "validateQuoteMarks": "'", 75 | "validateIndentation": 2, 76 | "disallowNotOperatorsInConditionals": true 77 | } 78 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": false, 3 | "browser": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "debug": false, 7 | "devel": false, 8 | "eqeqeq": true, 9 | "eqnull": false, 10 | "evil": false, 11 | "forin": true, 12 | "freeze": true, 13 | "funcscope": false, 14 | "immed": true, 15 | "indent": 2, 16 | "latedef": false, 17 | "laxbreak": false, 18 | "laxcomma": false, 19 | "loopfunc": false, 20 | "maxdepth": 5, 21 | "maxlen": 120, 22 | "maxparams": 7, 23 | "multistr": false, 24 | "newcap": false, 25 | "nonbsp": true, 26 | "nonew": true, 27 | "notypeof": false, 28 | "predef": [], 29 | "proto": false, 30 | "shadow": false, 31 | "sub": false, 32 | "supernew": false, 33 | "undef": false, 34 | "unused": false 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012-2014 Marcin Warpechowski 4 | Copyright (c) 2015 Handsoncode sp. z o.o. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # <hot-table> 2 | 3 | Polymer custom element wrapper for [Handsontable](http://handsontable.com/) data grid editor. Works with Polymer 1.x and Polymer 2.x (hybrid). 4 | 5 | :exclamation: some stuff does not work yet. Do not use this yet! But feel free to submit issues on GitHub 6 | 7 | ## Demo 8 | 9 | [Check it live!](http://handsontable.github.io/hot-table) 10 | 11 | ## Install 12 | 13 | Install the component using [Bower](http://bower.io/): 14 | 15 | ```sh 16 | $ bower install hot-table --save 17 | ``` 18 | 19 | Or [download as ZIP](https://github.com/handsontable/hot-table/archive/gh-pages.zip). 20 | 21 | ## Usage 22 | 23 | 1. Import Web Components' polyfill: 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | 2. Import Custom Element: 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | 3. Start using it! 36 | 37 | ```html 38 | 39 | ``` 40 | 41 | ## License 42 | 43 | [MIT License](http://opensource.org/licenses/MIT) 44 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hot-table", 3 | "description": "Polymer Element wrapper for Handsontable data grid editor", 4 | "license": "MIT", 5 | "main": "hot-table.html", 6 | "keywords": [ 7 | "polymer", 8 | "web-components", 9 | "datagrid", 10 | "grid", 11 | "table" 12 | ], 13 | "dependencies": { 14 | "polymer": "Polymer/polymer#1.9 - 2", 15 | "handsontable": "handsontable/handsontable#2 - 4" 16 | }, 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "examples", 22 | "..", 23 | "test", 24 | "tests" 25 | ], 26 | "devDependencies": { 27 | "iron-demo-helpers": "PolymerElements/iron-demo-helpers#1 - 2", 28 | "web-component-tester": "Polymer/web-component-tester#^6.0.0", 29 | "webcomponentsjs": "webcomponents/webcomponentsjs#^v1.0.0", 30 | "google-map": "GoogleWebComponents/google-map#^2.0.0" 31 | }, 32 | "variants": { 33 | "1.x": { 34 | "dependencies": { 35 | "polymer": "Polymer/polymer#^1.9" 36 | }, 37 | "devDependencies": { 38 | "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0.0", 39 | "web-component-tester": "^4.0.0", 40 | "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0", 41 | "google-map": "GoogleWebComponents/google-map#^2.0.0" 42 | }, 43 | "resolutions": { 44 | "webcomponentsjs": "^0.7" 45 | } 46 | } 47 | }, 48 | "resolutions": { 49 | "webcomponentsjs": "^1.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/bind-rows-with-headers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 |
33 |
34 |

<hot-table>

35 |

Binding rows with headers demo

36 |
37 | 38 |

Simple Example (strict mode)

39 | 40 |
41 | 42 | 56 | 57 |
58 | 59 |

60 | Try to add or remove rows using context menu. You will see that in strict mode all rows headers' keep to their initial rows. 61 |

62 | 63 |

Code

64 | 65 |

 66 | <dom-bind>
 67 |   <template>
 68 |     <hot-table width="420" height="280" datarows="{{ people }}" row-headers context-menu bind-rows-with-headers="strict">
 69 |       <hot-column width="50" read-only value="id" header="ID" type="numeric"></hot-column>
 70 |       <hot-column width="100" value="name.first" header="First Name"></hot-column>
 71 |       <hot-column width="100" value="name.last" header="Last Name">
 72 |         <template data-hot-role="renderer" is="dom-template">
 73 |           <bold-renderer value="{{ value }}"></bold-renderer>
 74 |         </template>
 75 |       </hot-column>
 76 |       <hot-column width="100" value="gender" type="dropdown" source="genderList" header="Gender">
 77 |         <template data-hot-role="renderer" is="dom-template">(<span>{{ value }}</span>)</template>
 78 |       </hot-column>
 79 |     </hot-table>
 80 |   </template>
 81 | </dom-bind>
 82 |   
83 | 84 | 85 | 118 | 119 |
120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /examples/collapsing-columns.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |

<hot-table>

30 |

Collapsing columns with nested headers demo

31 |
32 | 33 |

Simple Example

34 | 35 |
36 | 37 | 50 | 51 |
52 | 53 |

Code

54 | 55 |

 56 | <dom-bind>
 57 |   <template>
 58 |     <hot-table width="570" height="500" datarows="{{ people }}" row-headers context-menu hidden-columns
 59 |                nested-headers="{{ settings.nestedHeaders }}" collapsible-columns="{{ settings.collapsibleColumns }}">
 60 |       <hot-column width="125" value="name" header="Full Name"></hot-column>
 61 |       <hot-column width="125" value="address" header="Address"></hot-column>
 62 |       <hot-column width="125" value="registered" header="Registered" type="date" date-format="YYYY-MM-DD"></hot-column>
 63 |       <hot-column width="125" value="balance" header="Balance" type="numeric">
 64 |         <template data-hot-role="renderer" is="dom-template">
 65 |           <bold-renderer value="{{ value }}"></bold-renderer>
 66 |         </template>
 67 |       </hot-column>
 68 |     </hot-table>
 69 |   </template>
 70 | </dom-bind>
 71 |   
72 | 73 | 74 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/components/age-renderer.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/components/age-renderer.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | if (!Handsontable.helper.isWebComponentSupportedNatively()) { 4 | return; 5 | } 6 | 7 | var 8 | owner = (document._currentScript || document.currentScript).ownerDocument; 9 | 10 | function AgeRenderer() { 11 | 12 | } 13 | 14 | AgeRenderer.prototype = Object.create(HTMLElement.prototype, { 15 | constructor: { 16 | value: AgeRenderer, 17 | configurable: true 18 | } 19 | }); 20 | 21 | /** 22 | * On create custom element. 23 | */ 24 | AgeRenderer.prototype.createdCallback = function() { 25 | this.shadow = this.createShadowRoot(); 26 | this.shadow.appendChild(owner.querySelector("#template").content.cloneNode(true)); 27 | this.holder = this.shadowRoot.querySelector('#holder'); 28 | }; 29 | 30 | /** 31 | * On attached to the DOM custom element. 32 | */ 33 | AgeRenderer.prototype.attachedCallback = function() { 34 | this.updateAge(this.age); 35 | }; 36 | 37 | /** 38 | * Render element. 39 | */ 40 | AgeRenderer.prototype.updateAge = function(age) { 41 | var desc = this.holder.querySelector('#message'); 42 | 43 | this.age = parseInt(age, 10); 44 | 45 | if (isNaN(this.age)) { 46 | this.age = ''; 47 | } 48 | this.holder.querySelector('#age').textContent = this.age; 49 | 50 | if (this.age) { 51 | if (this.age < 18) { 52 | desc.classList.add('invalid'); 53 | desc.textContent = 'Too young!'; 54 | 55 | } else if (this.age >= 18 && this.age < 50) { 56 | desc.classList.remove('invalid'); 57 | desc.textContent = 'Perfect age!'; 58 | 59 | } else { 60 | desc.classList.add('invalid'); 61 | desc.textContent = 'Too old!'; 62 | } 63 | } else { 64 | desc.classList.remove('invalid'); 65 | desc.textContent = '???'; 66 | } 67 | }; 68 | 69 | AgeRenderer.prototype.attributeChangedCallback = function(attribute, oldVal, newVal) { 70 | if (attribute === 'age') { 71 | this.updateAge(newVal); 72 | } 73 | }; 74 | 75 | document.registerElement('age-renderer', AgeRenderer); 76 | }()); 77 | -------------------------------------------------------------------------------- /examples/components/bold-renderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/components/bold-renderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | Polymer({ 4 | is: 'bold-renderer', 5 | 6 | properties: { 7 | value: { 8 | type: String 9 | } 10 | } 11 | }); 12 | 13 | })(); 14 | -------------------------------------------------------------------------------- /examples/components/colored-renderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/components/colored-renderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | Polymer({ 4 | is: 'colored-renderer', 5 | 6 | properties: { 7 | value: { 8 | type: String, 9 | observer: '_onChanged' 10 | } 11 | }, 12 | 13 | /** 14 | * Split string into array 15 | */ 16 | splitValue: function (value) { 17 | this.splitedValue = typeof value === 'string' ? value.split('') : []; 18 | }, 19 | 20 | computeStyle: function(i) { 21 | return 'color: ' + (i % 2 ? 'red' : 'green'); 22 | }, 23 | 24 | _onChanged: function(value) { 25 | this.splitValue(value); 26 | } 27 | }); 28 | 29 | })(); 30 | -------------------------------------------------------------------------------- /examples/components/custom-google-map/custom-google-map-search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 | 223 | -------------------------------------------------------------------------------- /examples/components/dashboard-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 52 | 331 | 332 | -------------------------------------------------------------------------------- /examples/components/dashboard-tile.html: -------------------------------------------------------------------------------- 1 | 2 | 41 | 56 | 57 | -------------------------------------------------------------------------------- /examples/components/language-editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/components/language-editor.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var availableCountries = { 4 | be: 'Belgium', 5 | br: 'Brazil', 6 | cz: 'Czech Republic', 7 | de: 'Germany', 8 | dk: 'Denmark', 9 | es: 'Estonia', 10 | nl: 'Netherlands', 11 | no: 'Norway', 12 | pk: 'Pakistan', 13 | pl: 'Poland', 14 | ru: 'Russia', 15 | se: 'Sweden', 16 | tj: 'Tajikistan', 17 | tk: 'Tokelau', 18 | us: 'United States' 19 | }; 20 | 21 | Polymer({ 22 | is: 'language-editor', 23 | 24 | properties: { 25 | selected: { 26 | type: Array 27 | } 28 | }, 29 | 30 | observers: [ 31 | '_selectedChanged(selected)' 32 | ], 33 | 34 | /** 35 | * Listener for checkbox on-change event. 36 | * 37 | * @param {Event} event 38 | */ 39 | onChange: function(event) { 40 | var lang = event.model.lang; 41 | 42 | if (event.target.checked) { 43 | this.selected.push(lang.code); 44 | 45 | } else { 46 | if (this.selected.indexOf(lang.code) !== -1) { 47 | this.selected.splice(this.selected.indexOf(lang.code), 1); 48 | } 49 | } 50 | }, 51 | 52 | /** 53 | * Property observer for `selected` attribute. 54 | * 55 | * @param {Array} selected 56 | */ 57 | _selectedChanged: function(selected) { 58 | var languages = [], 59 | item, 60 | i; 61 | 62 | for (i in availableCountries) { 63 | if (availableCountries.hasOwnProperty(i)) { 64 | item = { 65 | code: i, 66 | name: availableCountries[i] 67 | }; 68 | item.checked = selected.indexOf(item.code) >= 0; 69 | 70 | languages.push(item); 71 | } 72 | } 73 | this.languages = languages; 74 | }, 75 | 76 | computeSrc: function(lang) { 77 | return './resources/flags/' + lang.code + '.png'; 78 | } 79 | }); 80 | 81 | })(); 82 | -------------------------------------------------------------------------------- /examples/css/main.css: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | HTML5 Boilerplate CSS: h5bp.com/css 3 | ========================================================================== */ 4 | 5 | article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { 6 | display: block; 7 | } 8 | 9 | audio, canvas, video { 10 | display: inline-block; 11 | *display: inline; 12 | *zoom: 1; 13 | } 14 | [hidden] { 15 | display: none; 16 | } 17 | 18 | html { 19 | font-size: 100%; 20 | -webkit-text-size-adjust: 100%; 21 | -ms-text-size-adjust: 100%; 22 | } 23 | 24 | html, button, input, select, textarea { 25 | font-family: sans-serif; 26 | color: #222; 27 | } 28 | 29 | body { 30 | margin: 0; 31 | font-family: Arial, Helvetica, sans-serif; 32 | font-size: 1em; 33 | line-height: 1.4; 34 | } 35 | 36 | ::-moz-selection { 37 | background: #20A6DB; 38 | color: #fff; 39 | text-shadow: none; 40 | } 41 | 42 | ::selection { 43 | background: #20A6DB; 44 | color: #fff; 45 | text-shadow: none; 46 | } 47 | 48 | a { 49 | color: #20A6DB; 50 | text-decoration: none; 51 | -webkit-transition: all .3s ease-in-out; 52 | -moz-transition: all .3s ease-in-out; 53 | -o-transition: all .3s ease-in-out; 54 | transition: all .3s ease-in-out; 55 | } 56 | 57 | a:visited { 58 | color: #20A6DB; 59 | } 60 | 61 | a:hover { 62 | color: #25BBF7; 63 | } 64 | 65 | a:focus { 66 | outline: none; 67 | } 68 | 69 | a:hover, a:active { 70 | outline: 0; 71 | } 72 | 73 | abbr[title] { 74 | border-bottom: 1px dotted; 75 | } 76 | 77 | b, strong { 78 | font-weight: bold; 79 | } 80 | 81 | blockquote { 82 | margin: 1em 40px; 83 | } 84 | 85 | dfn { 86 | font-style: italic; 87 | } 88 | 89 | hr { 90 | display: block; 91 | height: 1px; 92 | border: 0; 93 | border-top: 1px solid #ccc; 94 | margin: 1em 0; 95 | padding: 0; 96 | } 97 | 98 | ins { 99 | background: #ff9; 100 | color: #000; 101 | text-decoration: none; 102 | } 103 | 104 | mark { 105 | background: #ff0; 106 | color: #000; 107 | font-style: italic; 108 | font-weight: bold; 109 | } 110 | 111 | pre, code, kbd, samp { 112 | font-family: monospace, serif; 113 | _font-family: 'courier new', monospace; 114 | font-size: 1em; 115 | } 116 | 117 | pre { 118 | white-space: pre-wrap; 119 | white-space: pre; 120 | word-wrap: break-word; 121 | } 122 | 123 | q { 124 | quotes: none; 125 | } 126 | 127 | q:before, q:after { 128 | content: ""; 129 | content: none; 130 | } 131 | 132 | small { 133 | font-size: 85%; 134 | } 135 | 136 | sub, sup { 137 | font-size: 75%; 138 | line-height: 0; 139 | position: relative; 140 | vertical-align: baseline; 141 | } 142 | 143 | sup { 144 | top: -0.5em; 145 | } 146 | 147 | sub { 148 | bottom: -0.25em; 149 | } 150 | 151 | ul, ol { 152 | margin: 1em 0; 153 | padding: 0 0 0 40px; 154 | } 155 | 156 | dd { 157 | margin: 0 0 0 40px; 158 | } 159 | 160 | nav ul, nav ol { 161 | list-style: none; 162 | list-style-image: none; 163 | margin: 0; 164 | padding: 0; 165 | } 166 | 167 | img { 168 | border: 0; 169 | -ms-interpolation-mode: bicubic; 170 | vertical-align: middle; 171 | } 172 | 173 | svg:not(:root) { 174 | overflow: hidden; 175 | } 176 | 177 | figure { 178 | margin: 0; 179 | } 180 | 181 | form { 182 | margin: 0; 183 | } 184 | 185 | fieldset { 186 | border: 0; 187 | margin: 0; 188 | padding: 0; 189 | } 190 | 191 | label { 192 | cursor: pointer; 193 | } 194 | 195 | legend { 196 | border: 0; 197 | *margin-left: -7px; 198 | padding: 0; 199 | white-space: normal; 200 | } 201 | 202 | button, input, select, textarea { 203 | font-size: 100%; 204 | margin: 0; 205 | vertical-align: baseline; 206 | *vertical-align: middle; 207 | } 208 | 209 | button, input { 210 | line-height: normal; 211 | } 212 | 213 | button, input[type="button"], input[type="reset"], input[type="submit"] { 214 | cursor: pointer; 215 | -webkit-appearance: button; 216 | *overflow: visible; 217 | } 218 | 219 | button[disabled], input[disabled] { 220 | cursor: default; 221 | } 222 | 223 | input[type="checkbox"], input[type="radio"] { 224 | box-sizing: border-box; 225 | padding: 0; 226 | *width: 13px; 227 | *height: 13px; 228 | } 229 | 230 | input[type="search"] { 231 | -webkit-appearance: textfield; 232 | -moz-box-sizing: content-box; 233 | -webkit-box-sizing: content-box; 234 | box-sizing: content-box; 235 | } 236 | 237 | input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { 238 | -webkit-appearance: none; 239 | } 240 | 241 | button::-moz-focus-inner, input::-moz-focus-inner { 242 | border: 0; 243 | padding: 0; 244 | } 245 | 246 | textarea { 247 | overflow: auto; 248 | vertical-align: top; 249 | resize: vertical; 250 | } 251 | 252 | input:invalid, textarea:invalid { 253 | background-color: #f0dddd; 254 | } 255 | 256 | table { 257 | border-collapse: collapse; 258 | border-spacing: 0; 259 | margin-bottom: 30px; 260 | } 261 | 262 | td { 263 | vertical-align: top; 264 | } 265 | 266 | .chromeframe { 267 | margin: 0.2em 0; 268 | background: #ccc; 269 | color: black; 270 | padding: 0.2em 0; 271 | } 272 | 273 | /* Primary Styles */ 274 | 275 | body { 276 | color: #333; 277 | padding: 0 1.5em 3em; 278 | } 279 | 280 | header { 281 | text-align: center; 282 | padding-top: 10px; 283 | padding-bottom: 30px; 284 | } 285 | 286 | h1, h2, h3 { 287 | font-family: 'Open Sans Condensed', sans-serif; 288 | font-weight: 700; 289 | margin: 0; 290 | } 291 | 292 | h1 { 293 | font-size: 4.5em; 294 | } 295 | 296 | h2 { 297 | margin-top: 1em; 298 | } 299 | 300 | h3 { 301 | font-size: 1.1em; 302 | margin-bottom: 1em; 303 | } 304 | 305 | h2 span, 306 | h3 span { 307 | color: #AAA; 308 | } 309 | 310 | p, pre { 311 | margin: 0.5em 0 1.5em; 312 | } 313 | 314 | pre { 315 | font-size: .8em; 316 | color: #555; 317 | background: #EEE; 318 | padding: 0; 319 | -webkit-border-radius: 15px; 320 | border-radius: 15px; 321 | } 322 | 323 | #wrapper { 324 | overflow: hidden; 325 | width: 100%; 326 | max-width: 1000px; 327 | margin: 0 auto; 328 | } 329 | 330 | .logo-desc { 331 | margin-top: 0; 332 | } 333 | 334 | .warning { 335 | text-align: center; 336 | color: #AA0000; 337 | } 338 | 339 | .example { 340 | text-align: center; 341 | padding: 5px 30px 0 30px; 342 | } 343 | 344 | .sub { 345 | font-weight: bold; 346 | } 347 | 348 | .hidden { 349 | display: none !important; 350 | visibility: hidden; 351 | } 352 | 353 | .clearfix:before, .clearfix:after { 354 | content: ""; 355 | display: table; 356 | } 357 | 358 | .clearfix:after { 359 | clear: both; 360 | } 361 | 362 | .clearfix { 363 | *zoom: 1; 364 | } 365 | 366 | code.hljs { 367 | background-color: transparent; 368 | } 369 | 370 | .fork-me img { 371 | border: 0; 372 | height: 149px; 373 | position: absolute; 374 | right: 0; 375 | top: 0; 376 | width: 149px; 377 | } 378 | 379 | hot-table, .handsontable { 380 | font-size: 12px; 381 | } 382 | 383 | hot-table { 384 | display: inline-block; 385 | } 386 | 387 | hot-table .handsontableInputHolder { 388 | text-align: left !important; 389 | } 390 | -------------------------------------------------------------------------------- /examples/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

<hot-table>

26 |

Dashboard demo

27 |
28 | 29 |

Simple Example

30 | 31 |
32 | 33 |
34 | 35 |

Code

36 | 37 |

38 |     <dashboard-demo></dashboard-demo>
39 |   
40 | 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/data-filtering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |

<hot-table>

30 |

Filtering data demo

31 |
32 | 33 |

Simple Example

34 | 35 |
36 | 37 | 49 | 50 |
51 | 52 |

Code

53 | 54 |

55 | <dom-bind>
56 |   <template>
57 |     <hot-table width="570" height="280" datarows="{{ people }}" row-headers context-menu dropdown-menu filters>
58 |       <hot-column width="125" value="name" header="Full Name"></hot-column>
59 |       <hot-column width="125" value="address" header="Address"></hot-column>
60 |       <hot-column width="125" value="registered" header="Registered" type="date"></hot-column>
61 |       <hot-column width="125" value="balance" header="Balance" type="numeric">
62 |         <template data-hot-role="renderer" is="dom-template">
63 |           <bold-renderer value="{{ value }}"></bold-renderer>
64 |         </template>
65 |       </hot-column>
66 |     </hot-table>
67 |   </template>
68 | </dom-bind>
69 |   
70 | 71 | 72 | 83 | 84 | 85 |
86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /examples/dynamically-generated-context-menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

<hot-table>

26 |

Dynamically generated context menu demo

27 |
28 | 29 |

Simple Example

30 | 31 |
32 | 33 | 45 | 46 |
47 | 48 |

49 | Click the button to generate dynamically different context menu commands. 50 | Generate for or . 51 |

52 | 53 |

Code

54 | 55 |

 56 | <dom-bind>
 57 |   <template>
 58 |     <hot-table id="example" width="570" height="280" datarows="{{ people }}" row-headers context-menu="{{ contextMenu }}">
 59 |       <hot-column width="125" value="name" header="Full Name"></hot-column>
 60 |       <hot-column width="125" value="address" header="Address"></hot-column>
 61 |       <hot-column width="125" value="registered" header="Registered" type="date"></hot-column>
 62 |       <hot-column width="125" value="balance" header="Balance" type="numeric">
 63 |         <template data-hot-role="renderer" is="dom-template">
 64 |           <bold-renderer value="{{ value }}"></bold-renderer>
 65 |         </template>
 66 |       </hot-column>
 67 |     </hot-table>
 68 |   </template>
 69 | </dom-bind>
 70 |   
71 | 72 | 119 | 120 | 121 |
122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /examples/editors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 |
35 |
36 |

<hot-table>

37 |

Custom editors demo

38 |
39 | 40 |

Simple Example

41 | 42 |
43 | 44 | 85 | 86 |
87 | 88 |

Used editors:

89 | 90 | 94 | 95 | 96 |

Code

97 | 98 |

 99 | <dom-bind>
100 |   <template>
101 |     <hot-table width="790" height="490" datarows="{{ people }}" context-menu="true" max-rows="10">
102 |       <hot-column width="30" read-only value="id" header="ID"></hot-column>
103 |       <hot-column width="70" value="name.first" header="First Name"></hot-column>
104 |       <hot-column width="100" value="name.last" header="Last Name">
105 |         <template data-hot-role="renderer" is="dom-template">
106 |           <bold-renderer value="{{ value }}"></bold-renderer>
107 |         </template>
108 |       </hot-column>
109 |       <hot-column width="80" value="gender" type="dropdown" source="genderList" header="Gender">
110 |         <template data-hot-role="renderer" is="dom-template">(<span>{{ value }}</span>)</template>
111 |       </hot-column>
112 |       <hot-column width="100" value="skillLevel" header="Color skill level" class="no-padding color-editor">
113 |         <template data-hot-role="renderer" is="dom-template">
114 |           <div class$="{{ value }}"></div>
115 |         </template>
116 |         <template data-hot-role="editor" is="dom-template">
117 |           <select name="color" value="{{ value::input }}">
118 |             <option value="black">Black</option>
119 |             <option value="blue">Blue</option>
120 |             <option value="green">Green</option>
121 |             <option value="orange">Orange</option>
122 |             <option value="red">Red</option>
123 |             <option value="yellow">Yellow</option>
124 |           </select>
125 |         </template>
126 |       </hot-column>
127 |       <hot-column width="180" value="languages" header="Languages">
128 |         <template data-hot-role="renderer" is="dom-template">
129 |           <template is="dom-if" if="{{ value }}">
130 |             <template is="dom-repeat" items="{{ value }}" as="code">
131 |               <img src="{{ languagesComputedSrc(code) }}" alt$="{{ code }}">
132 |             </template>
133 |           </template>
134 |         </template>
135 |         <template data-hot-role="editor" is="dom-template">
136 |           <language-editor selected="[[ value ]]"></language-editor>
137 |         </template>
138 |       </hot-column>
139 |     </hot-table>
140 |   </template>
141 | </dom-bind>
142 |   
143 | 144 | 145 | 211 | 212 |
213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /examples/fixing-bottom-rows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |

<hot-table>

30 |

Fixing bottom rows demo

31 |
32 | 33 |

Simple Example

34 | 35 |
36 | 37 | 50 | 51 |
52 | 53 |

Code

54 | 55 |

56 | <dom-bind>
57 |   <template>
58 |     <hot-table width="570" height="280" datarows="{{ people }}" row-headers context-menu fixed-rows-bottom="2"
59 |                column-sorting sort-indicator>
60 |       <hot-column width="125" value="name" header="Full Name"></hot-column>
61 |       <hot-column width="125" value="address" header="Address"></hot-column>
62 |       <hot-column width="125" value="registered" header="Registered" type="date"></hot-column>
63 |       <hot-column width="125" value="balance" header="Balance" type="numeric">
64 |         <template data-hot-role="renderer" is="dom-template">
65 |           <bold-renderer value="{{ value }}"></bold-renderer>
66 |         </template>
67 |       </hot-column>
68 |     </hot-table>
69 |   </template>
70 | </dom-bind>
71 |   
72 | 73 | 81 | 82 | 83 |
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/nested-tables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 41 | 42 | 43 | 44 |
45 |
46 |

<hot-table>

47 |

Nested tables demo

48 |
49 | 50 |

Simple Example

51 | 52 |
53 | Comming soon (feature plan) 54 | 84 |
85 | 86 |

Code

87 | 88 |

 89 |   <hot-table dataRows="{{ cars }}" contextMenu="true">
 90 |     <hot-column width="30" readOnly value="id" header="ID" class="htCenter htMiddle"></hot-column>
 91 |     <hot-column width="100" value="brand" header="Brand" class="htCenter htMiddle"></hot-column>
 92 |     <hot-column width="700" value="cars" header="Cars" editor="false" class="no-padding no-header-shadow">
 93 |       <template data-hot-role="renderer">
 94 |         <hot-table width="700" stretchH="all" dataRows="{{ cars }}" contextMenu="true" class="nested" minSpareRows="1">
 95 |           <hot-column width="100" value="model" header="Model" class="htCenter htMiddle"></hot-column>
 96 |           <hot-column width="230" value="clients" header="Clients" editor="false" class="no-padding">
 97 |             <template data-hot-role="renderer">
 98 |               <hot-table width="230" stretchH="all" datarows="{{ clients }}" contextMenu="true"
 99 |                          class="nested" minSpareRows="1">
100 |                 <hot-column width="115" value="firstName" class="htLeft" header="First name"></hot-column>
101 |                 <hot-column width="115" value="lastName" class="htLeft" header="Last name"></hot-column>
102 |               </hot-table>
103 |             </template>
104 |           </hot-column>
105 |           <hot-column width="200" value="exportedTo" header="Exported to" editor="false" class="no-padding">
106 |             <template data-hot-role="renderer">
107 |               <hot-table width="200" stretchH="all" datarows="{{ exportedTo }}" contextMenu="true"
108 |                          class="nested" minSpareRows="1">
109 |                 <hot-column value="country" header="Country" class="htLeft"></hot-column>
110 |               </hot-table>
111 |             </template>
112 |           </hot-column>
113 |           <hot-column width="170" type="date" value="date.sold" header="Sold date" class="htCenter htMiddle"></hot-column>
114 |         </hot-table>
115 |       </template>
116 |     </hot-column>
117 |   </hot-table>
118 |   
119 | 120 |
121 | 122 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /examples/persistent-state.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

<hot-table>

26 |

Saving table state (persistent state) demo

27 |
28 | 29 |

Simple Example

30 | 31 |
32 | 33 | 46 | 47 |
48 | 49 |

50 |

Click the button to to the initial state.
51 |

52 | 53 |

Code

54 | 55 |

 56 | <dom-bind>
 57 |   <template>
 58 |     <hot-table id="example" width="570" height="280" datarows="{{ people }}" row-headers context-menu column-sorting sort-indicator
 59 |                persistent-state manual-column-resize manual-column-move manual-row-resize manual-row-move>
 60 |       <hot-column width="125" value="name" header="Full Name"></hot-column>
 61 |       <hot-column width="125" value="address" header="Address"></hot-column>
 62 |       <hot-column width="125" value="registered" header="Registered" type="date"></hot-column>
 63 |       <hot-column width="125" value="balance" header="Balance" type="numeric">
 64 |         <template data-hot-role="renderer" is="dom-template">
 65 |           <bold-renderer value="{{ value }}"></bold-renderer>
 66 |         </template>
 67 |       </hot-column>
 68 |     </hot-table>
 69 |   </template>
 70 | </dom-bind>
 71 |   
72 | 73 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/renderers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 |
34 |
35 |

<hot-table>

36 |

Custom renderers demo

37 |
38 | 39 |

Simple Example

40 | 41 |
42 | 43 | 79 | 80 |
81 | 82 |

Used renderers

83 | 84 | 91 | 92 |

Code

93 | 94 |

 95 | <dom-bind>
 96 |   <template>
 97 |     <hot-table width="830" height="1050" datarows="{{ people }}" max-rows="{{ people.length }}" context-menu>
 98 |       <hot-column width="30" read-only value="id" header="ID"></hot-column>
 99 |       <hot-column width="70" value="name.first" header="First Name">
100 |         <template data-hot-role="renderer" is="dom-template"><colored-renderer value="{{ value }}"></colored-renderer></template>
101 |       </hot-column>
102 |       <hot-column width="100" value="name.last" header="Last Name">
103 |         <template data-hot-role="renderer" is="dom-template"><bold-renderer value="{{ value }}"></bold-renderer></template>
104 |       </hot-column>
105 |       <hot-column width="80" value="gender" header="Gender">
106 |         <template data-hot-role="renderer" is="dom-template">(<span>{{ value }}</span>)</template>
107 |       </hot-column>
108 |       <hot-column width="120" value="age" header="Age">
109 |         <template data-hot-role="renderer" is="dom-template"><age-renderer age$="{{ value }}"></age-renderer></template>
110 |       </hot-column>
111 |       <hot-column width="200" height="100" value="address" header="Map" class="maps">
112 |         <template data-hot-role="renderer" is="dom-template">
113 |           <template is="dom-if" if="{{ value }}">
114 |             <custom-google-map-search map="[[ map ]]" query="{{ value }}" global-search results="{{ results }}"></custom-google-map-search>
115 |             <google-map disable-default-ui disable-zoom map="{{ map }}"
116 |                         latitude="{{ results.latitude }}" longitude="{{ results.longitude }}"></google-map>
117 |           </template>
118 |           <template is="dom-if" if="{{ !value }}">
119 |             <div class="google-map-empty">Double click me to provide address to show.</div>
120 |           </template>
121 |         </template>
122 |       </hot-column>
123 |       <hot-column type="checkbox" value="languages.english" header="English" checked-template="Yes"
124 |           unchecked-template="No"></hot-column>
125 |       <hot-column type="checkbox" value="languages.spanish" header="Spanish" checked-template="Yes"
126 |           unchecked-template="No"></hot-column>
127 |       <hot-column type="checkbox" value="languages.french" header="French" checked-template="Yes"
128 |           unchecked-template="No"></hot-column>
129 |       <hot-column type="numeric" value="salary" header="Salary" format="$ 0,0.00"></hot-column>
130 |     </hot-table>
131 |   </template>
132 | </dom-bind>
133 |   
134 | 135 | 196 | 197 |
198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /examples/resources/flags/be.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/be.png -------------------------------------------------------------------------------- /examples/resources/flags/br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/br.png -------------------------------------------------------------------------------- /examples/resources/flags/cz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/cz.png -------------------------------------------------------------------------------- /examples/resources/flags/de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/de.png -------------------------------------------------------------------------------- /examples/resources/flags/dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/dk.png -------------------------------------------------------------------------------- /examples/resources/flags/es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/es.png -------------------------------------------------------------------------------- /examples/resources/flags/nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/nl.png -------------------------------------------------------------------------------- /examples/resources/flags/no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/no.png -------------------------------------------------------------------------------- /examples/resources/flags/pk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/pk.png -------------------------------------------------------------------------------- /examples/resources/flags/pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/pl.png -------------------------------------------------------------------------------- /examples/resources/flags/ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/ru.png -------------------------------------------------------------------------------- /examples/resources/flags/se.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/se.png -------------------------------------------------------------------------------- /examples/resources/flags/tj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/tj.png -------------------------------------------------------------------------------- /examples/resources/flags/tk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/tk.png -------------------------------------------------------------------------------- /examples/resources/flags/us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/examples/resources/flags/us.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var jshint = require('gulp-jshint'); 3 | var stylish = require('jshint-stylish'); 4 | var vulcanize = require('gulp-vulcanize'); 5 | var jscs = require('gulp-jscs'); 6 | 7 | gulp.task('jshint', function() { 8 | return gulp.src('./src/*.js') 9 | .pipe(jshint({ 10 | lookup: true 11 | })) 12 | .pipe(jshint.reporter(stylish)); 13 | }); 14 | 15 | gulp.task('jscs', function() { 16 | return gulp.src('./src/*.js') 17 | .pipe(jscs()) 18 | .pipe(jscs.reporter()); 19 | }); 20 | 21 | //gulp.task('vulcanize', function () { 22 | // var DIST = 'dist'; 23 | // 24 | // return gulp.src('./src/hot-table.html') 25 | // .pipe(vulcanize({ 26 | // dest: DIST, 27 | // strip: true, 28 | // inline: true, 29 | // excludes: { 30 | // scripts: ['handsontable.*', 'polymer.js'], 31 | // styles: ['handsontable.*'] 32 | // } 33 | // })) 34 | // .pipe(gulp.dest(DIST)); 35 | //}); 36 | 37 | gulp.task('default', ['jscs', 'jshint']); 38 | -------------------------------------------------------------------------------- /hot-table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <hot-table> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |

<hot-table>

25 |

Polymer Element wrapper for Handsontable data grid editor

26 |
27 | 28 |

Some stuff does not work yet. For now it is not ready for production!
29 | But feel free to submit issues on GitHub.

30 | 31 |

Install

32 |
33 | Install the component using Bower: 34 |

 35 |     $ bower install hot-table --save
 36 |   
37 | Or download as ZIP. 38 | 39 |

Usage

40 |
41 | 1. Import Web Components' polyfill: 42 |

 43 |     <script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
 44 |   
45 | 2. Import Custom Element: 46 |

 47 |     <link rel="import" href="bower_components/hot-table/hot-table.html">
 48 |   
49 | 3. Start using it! 50 |

 51 |     <hot-table datarows="{{ itemsArray }}"></hot-table>
 52 |   
53 | 54 | 55 | Fork me on GitHub 56 | 57 | 58 |

Simple Example

59 | 60 |
61 | 62 | 85 | 86 |
87 | 88 |

Code

89 | 90 |

 91 | <dom-bind>
 92 |   <template>
 93 |     <p>
 94 |       Name of first person: <strong>{{ people.0.name.first }}</strong> | Highlighted row: <strong>{{ highlightedRow }}</strong>
 95 |     </p>
 96 |     <hot-table width="655" height="295" datarows="{{ people }}" context-menu max-rows="{{ people.length }}"
 97 |                highlighted-row="{{ highlightedRow }}" class="htLeft">
 98 |       <hot-column width="30" read-only value="id" header="ID"></hot-column>
 99 |       <hot-column value="name.first" header="First Name">
100 |         <template data-hot-role="renderer" is="dom-template"><span>{{ value }}</span>...</template>
101 |       </hot-column>
102 |       <hot-column width="100" value="name.last" header="Last Name"></hot-column>
103 |       <hot-column width="100" type="date" value="date" header="Birthday"></hot-column>
104 |       <hot-column value="gender" header="Gender" source="[[ genderSource ]]" type="dropdown"></hot-column>
105 |       <hot-column type="numeric" value="age" header="Age"></hot-column>
106 |       <hot-column type="checkbox" value="languages.english" header="English" checked-template="Yes"
107 |                   unchecked-template="No"></hot-column>
108 |       <hot-column type="checkbox" value="languages.spanish" header="Spanish" checked-template="Yes"
109 |                   unchecked-template="No"></hot-column>
110 |       <hot-column type="checkbox" value="languages.french" header="French" checked-template="Yes"
111 |                   unchecked-template="No"></hot-column>
112 |       <hot-column type="numeric" value="salary" header="Salary" format="$ 0,0.00"></hot-column>
113 |     </hot-table>
114 |   </template>
115 | </dom-bind>
116 |   
117 | 118 |

Demos

119 | 120 | 127 | 128 |

PRO features

129 |
130 |

131 | To install Handsontable PRO follow by this instructions. 132 |

133 | Install the component using Bower: 134 |

135 |     $ bower install hot-table --save
136 |   
137 | 138 | Overwrite handsontable version with handsontable PRO: 139 |

140 |     $ bower install handsontable=git@github.com:handsontable/handsontable-pro.git --save
141 |   
142 | If you don't have license for Pro yet, please click here for more info. 143 | 144 |

PRO features demos

145 | 146 | 151 | 152 | 184 | 185 |
186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "hot-table", 4 | "version": "1.0.0", 5 | "description": "Polymer Element wrapper for [Handsontable](http://handsontable.com/) data grid editor", 6 | "homepage": "https://github.com/handsontable/hot-table#readme", 7 | "author": "Handsoncode (https://handsoncode.net)", 8 | "license": "MIT", 9 | "keywords": [ 10 | "handsontable", 11 | "component", 12 | "data", 13 | "table", 14 | "grid", 15 | "data table", 16 | "data grid", 17 | "spreadsheet", 18 | "sheet", 19 | "excel", 20 | "pro", 21 | "enterprise", 22 | "sort", 23 | "formulas", 24 | "filter", 25 | "search", 26 | "conditional", 27 | "formatting", 28 | "csv", 29 | "polymer", 30 | "polymer component", 31 | "polymer grid", 32 | "wrapper" 33 | ], 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/handsontable/hot-table.git" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/handsontable/hot-table/issues" 40 | }, 41 | "dependencies": {}, 42 | "devDependencies": { 43 | "gulp": "^3.9.0", 44 | "gulp-jscs": "^3.0.2", 45 | "gulp-jshint": "^2.0.0", 46 | "gulp-vulcanize": "^6.1.0", 47 | "jshint": "^2.9.1", 48 | "jshint-stylish": "^1.0.2" 49 | }, 50 | "directories": { 51 | "example": "examples", 52 | "test": "test" 53 | }, 54 | "scripts": { 55 | "test": "echo \"Error: no test specified\" && exit 1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /polymer.json: -------------------------------------------------------------------------------- 1 | { 2 | "lint": { 3 | "rules": [ 4 | "polymer-2-hybrid" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/behavior/public-methods.js: -------------------------------------------------------------------------------- 1 | (function(w) { 2 | var 3 | publicMethods = [ 4 | 'addHook', 5 | 'addHookOnce', 6 | 'alter', 7 | 'clear', 8 | 'clearUndo', 9 | 'colOffset', 10 | 'colToProp', 11 | 'countCols', 12 | 'countEmptyCols', 13 | 'countEmptyRows', 14 | 'countRenderedCols', 15 | 'countRenderedRows', 16 | 'countRows', 17 | 'countSourceCols', 18 | 'countSourceRows', 19 | 'countVisibleCols', 20 | 'countVisibleRows', 21 | 'deselectCell', 22 | 'destroy', 23 | 'destroyEditor', 24 | 'emptySelectedCells', 25 | 'getActiveEditor', 26 | 'getCell', 27 | 'getCellEditor', 28 | 'getCellMeta', 29 | 'getCellMetaAtRow', 30 | 'getCellRenderer', 31 | 'getCellValidator', 32 | 'getCellsMeta', 33 | 'getColHeader', 34 | 'getColWidth', 35 | 'getCoords', 36 | 'getCopyableData', 37 | 'getCopyableText', 38 | 'getData', 39 | 'getDataAtCell', 40 | 'getDataAtCol', 41 | 'getDataAtProp', 42 | 'getDataAtRow', 43 | 'getDataAtRowProp', 44 | 'getDataType', 45 | 'getInstance', 46 | 'getPlugin', 47 | 'getRowHeader', 48 | 'getRowHeight', 49 | 'getSchema', 50 | 'getSelected', 51 | 'getSelectedLast', 52 | 'getSelectedRange', 53 | 'getSelectedRangeLast', 54 | 'getSettings', 55 | 'getSourceData', 56 | 'getSourceDataArray', 57 | 'getSourceDataAtCell', 58 | 'getSourceDataAtCol', 59 | 'getSourceDataAtRow', 60 | 'getTranslatedPhrase', 61 | 'getValue', 62 | 'hasColHeaders', 63 | 'hasHook', 64 | 'hasRowHeaders', 65 | // 'init', // Conflicted method 66 | 'isColumnModificationAllowed', 67 | 'isEmptyCol', 68 | 'isEmptyRow', 69 | 'isListening', 70 | 'isRedoAvailable', 71 | 'isUndoAvailable', 72 | 'listen', 73 | 'loadData', 74 | 'populateFromArray', 75 | 'propToCol', 76 | 'redo', 77 | 'removeCellMeta', 78 | 'removeHook', 79 | 'render', 80 | 'rowOffset', 81 | 'runHooks', 82 | 'scrollViewportTo', 83 | 'selectAll', 84 | 'selectCell', 85 | 'selectCellByProp', 86 | 'selectCells', 87 | 'selectColumns', 88 | 'selectRows', 89 | 'setCellMeta', 90 | 'setCellMetaObject', 91 | 'setDataAtCell', 92 | 'setDataAtRowProp', 93 | 'spliceCellsMeta', 94 | 'spliceCol', 95 | 'spliceRow', 96 | 'toPhysicalColumn', 97 | 'toPhysicalRow', 98 | 'toVisualColumn', 99 | 'toVisualRow', 100 | 'undo', 101 | 'unlisten', 102 | 'updateSettings', 103 | 'validateCell', 104 | 'validateCells', 105 | 'validateColumns', 106 | 'validateRows' 107 | ]; 108 | 109 | var PublicMethodsBehavior = {}; 110 | 111 | publicMethods.forEach(function(method) { 112 | PublicMethodsBehavior[method] = function() { 113 | return this.hot[method].apply(this.hot, arguments); 114 | }; 115 | }); 116 | 117 | w.HotTableUtils = w.HotTableUtils || {}; 118 | w.HotTableUtils.behaviors = w.HotTableUtils.behaviors || {}; 119 | w.HotTableUtils.behaviors.PublicMethodsBehavior = PublicMethodsBehavior; 120 | }(window)); 121 | -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | (function(w) { 2 | var BaseEditor = Handsontable.editors.BaseEditor; 3 | 4 | var EditorState = { 5 | VIRGIN: 'STATE_VIRGIN', 6 | EDITING: 'STATE_EDITING', 7 | WAITING: 'STATE_WAITING', 8 | FINISHED: 'STATE_FINISHED' 9 | }; 10 | 11 | function Editor(hotInstance) { 12 | BaseEditor.call(this, hotInstance); 13 | this.model = null; 14 | this.template = null; 15 | this.TemplateClass = null; 16 | 17 | if (Handsontable.eventManager) { 18 | this.eventManager = Handsontable.eventManager(this); 19 | } else { 20 | this.eventManager = new Handsontable.EventManager(this); 21 | } 22 | 23 | this.createContainerElement(); 24 | this.initEvents(); 25 | } 26 | 27 | Editor.prototype = Object.create(BaseEditor.prototype, { 28 | constructor: { 29 | value: BaseEditor, 30 | configurable: true 31 | } 32 | }); 33 | 34 | /** 35 | * Init editor instance 36 | */ 37 | Editor.prototype.init = function() { 38 | BaseEditor.prototype.init.call(this); 39 | }; 40 | 41 | /** 42 | * Init editor instance 43 | */ 44 | Editor.prototype.setTemplate = function(templateClass) { 45 | this.templateClass = templateClass; 46 | }; 47 | 48 | /** 49 | * Create and prepare editor container elements 50 | */ 51 | Editor.prototype.createContainerElement = function() { 52 | this.container = document.createElement('div'); 53 | Handsontable.dom.addClass(this.container, 'handsontableInputHolder'); 54 | 55 | this.containerStyle = this.container.style; 56 | this.containerStyle.display = 'none'; 57 | this.containerStyle.top = 0; 58 | this.containerStyle.left = 0; 59 | 60 | this.instance.rootElement.appendChild(this.container); 61 | }; 62 | 63 | /** 64 | * Create editor element from template 65 | */ 66 | Editor.prototype.createEditorElement = function() { 67 | if (!this.model) { 68 | if (Polymer.Element) { 69 | this.model = new this.templateClass(); 70 | } else { 71 | this.model = this.templateClass.stamp(); 72 | } 73 | Polymer.dom(this.container).appendChild(this.model.root); 74 | } 75 | }; 76 | 77 | /** 78 | * Fill all necessary cell variables 79 | * 80 | * @param {Number} row 81 | * @param {Number} col 82 | * @param {String} prop 83 | * @param {Element} td 84 | * @param {*} originalValue 85 | * @param {Object} cellProperties 86 | */ 87 | Editor.prototype.prepare = function(row, col, prop, td, originalValue, cellProperties) { 88 | BaseEditor.prototype.prepare.call(this, row, col, prop, td, originalValue, cellProperties); 89 | this.createEditorElement(); 90 | 91 | this.model.row = row; 92 | this.model.col = col; 93 | this.model.prop = prop; 94 | this.model.value = originalValue; 95 | this.model.cellProperties = cellProperties; 96 | this.model.editor = this; 97 | }; 98 | 99 | /** 100 | * Add all necessary events 101 | */ 102 | Editor.prototype.initEvents = function() { 103 | var _this = this; 104 | 105 | this.instance.addHook('afterScrollVertically', function() { 106 | _this.refreshDimensions(); 107 | }); 108 | 109 | this.instance.addHook('afterColumnResize', function() { 110 | _this.refreshDimensions(); 111 | _this.focus(); 112 | }); 113 | 114 | this.instance.addHook('afterRowResize', function() { 115 | _this.refreshDimensions(); 116 | _this.focus(); 117 | }); 118 | 119 | this.instance.addHook('afterDestroy', function() { 120 | _this.eventManager.destroy(); 121 | }); 122 | }; 123 | 124 | /** 125 | * Get section name where cell is edited 126 | * 127 | * @returns {String} 128 | */ 129 | Editor.prototype.getEditorSection = function() { 130 | var settings = this.instance.getSettings(), 131 | section; 132 | 133 | if (this.row < settings.fixedRowsTop) { 134 | if (this.col < settings.fixedColumnsLeft) { 135 | section = 'corner'; 136 | 137 | } else { 138 | section = 'vertical'; 139 | } 140 | } else { 141 | if (this.col < settings.fixedColumnsLeft) { 142 | section = 'horizontal'; 143 | } 144 | } 145 | 146 | return section; 147 | }; 148 | 149 | /** 150 | * Get edited cell element (TD) 151 | * 152 | * @returns {HTMLTableCellElement|undefined} 153 | */ 154 | Editor.prototype.getEditedCell = function() { 155 | var editorSection = this.getEditorSection(), editedCell; 156 | 157 | if (['top', 'left', 'corner'].indexOf(editorSection) > 0) { 158 | editedCell = this.instance.view.wt.wtScrollbars[editorSection].clone. 159 | wtTable.getCell({row: this.row, col: this.col}); 160 | 161 | } else { 162 | editedCell = this.instance.getCell(this.row, this.col); 163 | } 164 | 165 | return editedCell !== -1 && editedCell !== -2 ? editedCell : undefined; 166 | }; 167 | 168 | /** 169 | * Get css transform array offset 170 | * 171 | * @returns {Array|undefined} 172 | */ 173 | Editor.prototype.getSectionOffset = function() { 174 | var editorSection = this.getEditorSection(), offset; 175 | 176 | if (editorSection) { 177 | offset = Handsontable.dom.getCssTransform(this.instance.view.wt.wtScrollbars[editorSection].clone. 178 | wtTable.holder.parentNode); 179 | } 180 | 181 | return offset; 182 | }; 183 | 184 | /** 185 | * Refresh editor container dimension related with edited cell position 186 | */ 187 | Editor.prototype.refreshDimensions = function() { 188 | var width, height, rootOffset, tdOffset, cssTransformOffset; 189 | 190 | if (this.state !== EditorState.EDITING) { 191 | return; 192 | } 193 | this.TD = this.getEditedCell(); 194 | 195 | if (!this.TD) { 196 | return; 197 | } 198 | width = Handsontable.dom.outerWidth(this.TD); 199 | height = Handsontable.dom.outerHeight(this.TD); 200 | rootOffset = Handsontable.dom.offset(this.instance.rootElement); 201 | tdOffset = Handsontable.dom.offset(this.TD); 202 | cssTransformOffset = this.getSectionOffset(); 203 | 204 | if (cssTransformOffset && cssTransformOffset !== -1) { 205 | this.containerStyle[cssTransformOffset[0]] = cssTransformOffset[1]; 206 | 207 | } else { 208 | Handsontable.dom.resetCssTransform(this.container); 209 | } 210 | this.containerStyle.minWidth = width + 'px'; 211 | this.containerStyle.height = height + 'px'; 212 | this.containerStyle.top = tdOffset.top - rootOffset.top + 'px'; 213 | this.containerStyle.left = tdOffset.left - rootOffset.left + 'px'; 214 | this.containerStyle.display = ''; 215 | }; 216 | 217 | /** 218 | * Fired on begin editing 219 | * 220 | * @param {*} initialValue 221 | * @param {Event} event 222 | */ 223 | Editor.prototype.beginEditing = function(initialValue, event) { 224 | if (this.state !== EditorState.VIRGIN) { 225 | return; 226 | } 227 | this.instance.view.scrollViewport({row: this.row, col: this.col}); 228 | this.instance.view.render(); 229 | this.state = EditorState.EDITING; 230 | 231 | initialValue = typeof initialValue === 'string' ? initialValue : this.originalValue; 232 | this.setValue(initialValue); 233 | 234 | this.open(event); 235 | this._opened = true; 236 | this.focus(); 237 | 238 | this.instance.view.render(); 239 | }; 240 | 241 | /** 242 | * Fired on finish editing 243 | * 244 | * @param {Boolean} restoreOriginalValue 245 | * @param {Boolean} ctrlDown 246 | * @param {Function} callback 247 | */ 248 | Editor.prototype.finishEditing = function(restoreOriginalValue, ctrlDown, callback) { 249 | BaseEditor.prototype.finishEditing.call(this, restoreOriginalValue, ctrlDown, callback); 250 | }; 251 | 252 | /** 253 | * Fired on open editor 254 | */ 255 | Editor.prototype.open = function() { 256 | this.refreshDimensions(); 257 | this.model.showed = true; 258 | }; 259 | 260 | /** 261 | * Fired on close editor 262 | */ 263 | Editor.prototype.close = function() { 264 | this.containerStyle.display = 'none'; 265 | this.model.showed = false; 266 | }; 267 | 268 | /** 269 | * Fired after editor is closed 270 | * 271 | * @returns {*} 272 | */ 273 | Editor.prototype.getValue = function() { 274 | return this.model.value; 275 | }; 276 | 277 | /** 278 | * Fired before editor is opened 279 | * 280 | * @param {*} value 281 | */ 282 | Editor.prototype.setValue = function(value) { 283 | this.model.value = value; 284 | }; 285 | 286 | /** 287 | * Fired before editor is opened and before set value is triggered 288 | */ 289 | Editor.prototype.focus = function() { 290 | 291 | }; 292 | 293 | w.HotTableUtils = w.HotTableUtils || {}; 294 | w.HotTableUtils.Editor = Editor; 295 | }(window)); 296 | -------------------------------------------------------------------------------- /src/hot-column.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/hot-column.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var 3 | settingsParser = new HotTableUtils.SettingsParser(); 4 | 5 | function findRenderer(element) { 6 | return Polymer.dom(element).querySelector('template[data-hot-role=renderer]'); 7 | } 8 | function findEditor(element) { 9 | return Polymer.dom(element).querySelector('template[data-hot-role=editor]'); 10 | } 11 | 12 | Polymer({ 13 | is: 'hot-column', 14 | properties: settingsParser.getHotColumnProperties(), 15 | 16 | attached: function() { 17 | this.registerRenderer(findRenderer(this)); 18 | this.registerEditor(findEditor(this)); 19 | 20 | if (this.parentNode && this.parentNode.onMutation) { 21 | this.parentNode.onMutation(); 22 | } 23 | }, 24 | 25 | attributeChanged: function() { 26 | this._onChanged(); 27 | }, 28 | 29 | _onChanged: function() { 30 | if (this.parentNode && this.parentNode.onMutation) { 31 | this.parentNode.onMutation(); 32 | } 33 | }, 34 | 35 | /** 36 | * Register cell renderer. 37 | * 38 | * @param {Element} template Template element 39 | */ 40 | registerRenderer: function(template) { 41 | if (!template) { 42 | return; 43 | } 44 | var models = new WeakMap(); 45 | var Template; 46 | 47 | if (Polymer.Element) { // Polymer 2.0 48 | Template = Polymer.Templatize.templatize(template, this, { 49 | parentModel: false, 50 | forwardHostProp: function(prop, value) {} 51 | }); 52 | } else if (Polymer.Templatizer && Polymer.Templatizer.templatize) { // Polymer 1.7 - 1.9 53 | Polymer.Templatizer.templatize(template); 54 | } 55 | 56 | this.renderer = function(instance, TD, row, col, prop, value, cellProperties) { 57 | var model, hasModel; 58 | 59 | Handsontable.renderers.cellDecorator.apply(this, arguments); 60 | hasModel = models.has(TD); 61 | 62 | if (hasModel) { 63 | model = models.get(TD); 64 | 65 | } else { 66 | if (Polymer.Element) { 67 | model = new Template(); 68 | } else { 69 | // Don't copy parent properties. Stamp fresh template instance. 70 | template._parentProps = {}; 71 | model = template.stamp ? template.stamp({}) : Polymer.Templatizer.stamp({}); 72 | } 73 | models.set(TD, model); 74 | } 75 | model.row = row; 76 | model.col = col; 77 | model.prop = prop; 78 | model.cellProperties = cellProperties; 79 | model.value = value; 80 | 81 | TD.style.whiteSpace = 'normal'; 82 | 83 | if (!hasModel) { 84 | TD.textContent = ''; 85 | Polymer.dom(TD).appendChild(model.root); 86 | } 87 | }; 88 | }, 89 | 90 | /** 91 | * Register cell editor. 92 | * 93 | * @param {Element} template Template element 94 | */ 95 | registerEditor: function(template) { 96 | if (!template) { 97 | return; 98 | } 99 | this.editor = ProxyEditor; 100 | 101 | var Template; 102 | 103 | if (Polymer.Element) { 104 | Template = Polymer.Templatize.templatize(template, this, { 105 | parentModel: false, 106 | forwardHostProp: function(prop, value) {} 107 | }); 108 | } 109 | 110 | function ProxyEditor(hotInstance) { 111 | HotTableUtils.Editor.call(this, hotInstance); 112 | this.setTemplate(Polymer.Element ? Template : template); 113 | } 114 | 115 | ProxyEditor.prototype = Object.create(HotTableUtils.Editor.prototype, { 116 | constructor: { 117 | value: HotTableUtils.Editor, 118 | configurable: true 119 | } 120 | }); 121 | } 122 | }); 123 | }()); 124 | -------------------------------------------------------------------------------- /src/hot-table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/hot-table.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var 3 | settingsParser = new HotTableUtils.SettingsParser(), 4 | lastSelectedCellMeta; 5 | 6 | Polymer({ 7 | is: 'hot-table', 8 | properties: settingsParser.getHotTableProperties(), 9 | 10 | behaviors: [ 11 | HotTableUtils.behaviors.PublicMethodsBehavior 12 | ], 13 | 14 | observers: [ 15 | '_onDatarowsChanged(datarows.*)' 16 | ], 17 | 18 | /** 19 | * @property hot 20 | * @type Handsontable 21 | * @default null 22 | */ 23 | hot: null, 24 | 25 | /** 26 | * On create element but not attached to DOM 27 | */ 28 | created: function() { 29 | this.activeNestedTable = null; 30 | this.nestedTables = null; 31 | this.destroyed = false; 32 | this.initialized = false; 33 | this.hotRootElement = document.createElement('div'); 34 | this.hot = new Handsontable.Core(this.hotRootElement, {}); 35 | }, 36 | 37 | /** 38 | * On attached element to DOM 39 | */ 40 | attached: function() { 41 | this.$.htContainer.parentNode.replaceChild(this.hotRootElement, this.$.htContainer); 42 | 43 | if (this.id) { 44 | this.hotRootElement.id = this.id; 45 | } 46 | 47 | this.async(function() { 48 | if (!this.hot) { 49 | return; 50 | } 51 | // Fix detection of Polymer environment 52 | this.hot.isHotTableEnv = true; 53 | (Handsontable.eventManager || Handsontable.EventManager).isHotTableEnv = this.hot.isHotTableEnv; 54 | 55 | var settings = settingsParser.parse(this); 56 | 57 | if (settings.colHeaders !== false && Array.isArray(this.datarows) && this.datarows.length && 58 | this.colHeaders !== null) { 59 | if (typeof this.datarows[0] === 'object' && !Array.isArray(this.datarows[0])) { 60 | this.colHeaders = Object.keys(this.datarows[0]); 61 | } 62 | } 63 | this.hot.updateSettings(settings); 64 | this.hot.init(); 65 | this.initialized = true; 66 | 67 | this.collectNestedTables(); 68 | this.registerHooks(); 69 | }, 1); 70 | }, 71 | 72 | /** 73 | * Try to destroy handsontable instance if hadn't been destroyed 74 | */ 75 | detached: function() { 76 | if (this.hot && !this.destroyed) { 77 | this.hot.destroy(); 78 | this.hot = null; 79 | } 80 | }, 81 | 82 | /** 83 | * Register hooks 84 | */ 85 | registerHooks: function() { 86 | var _this = this; 87 | 88 | if (!Handsontable.dom.isChildOfWebComponentTable(this.parentNode)) { 89 | Handsontable.hooks.add('beforeOnCellMouseDown', function() { 90 | _this.onBeforeOnCellMouseDown.apply(_this, [this].concat(Array.prototype.slice.call(arguments))); 91 | }); 92 | Handsontable.hooks.add('afterOnCellMouseDown', function() { 93 | _this.onAfterOnCellMouseDown.apply(_this, [this].concat(Array.prototype.slice.call(arguments))); 94 | }); 95 | } 96 | this.hot.addHook('afterModifyTransformStart', this.onAfterModifyTransformStart.bind(this)); 97 | this.hot.addHook('beforeKeyDown', this.onBeforeKeyDown.bind(this)); 98 | this.hot.addHook('afterDeselect', function() { 99 | _this.highlightedRow = -1; 100 | _this.highlightedColumn = -1; 101 | }); 102 | this.hot.addHook('afterSelectionEnd', function() { 103 | var range = _this.hot.getSelectedRangeLast(); 104 | 105 | _this.highlightedRow = range.highlight.row; 106 | _this.highlightedColumn = range.highlight.col; 107 | }); 108 | this.hot.addHook('afterDestroy', function() { 109 | _this.destroyed = true; 110 | }); 111 | }, 112 | 113 | /** 114 | * Detect and collect all founded nested hot-table's 115 | */ 116 | collectNestedTables: function() { 117 | var parentTable = null, 118 | isNative = Handsontable.helper.isWebComponentSupportedNatively(); 119 | 120 | if (!Handsontable.dom.isChildOfWebComponentTable(this.parentNode)) { 121 | parentTable = this; 122 | } 123 | this.nestedTables = new HotTableUtils.NestedTable(this); 124 | this.nestedTables.setStrategy(isNative ? 'native' : 'emulation', parentTable); 125 | this.nestedTables.update(); 126 | }, 127 | 128 | /** 129 | * @param {Handsontable} hotInstance 130 | * @param {Event} event 131 | * @param {Object} coords 132 | * @param {HTMLElement} TD 133 | */ 134 | onBeforeOnCellMouseDown: function(hotInstance, event, coords, TD) { 135 | var cellMeta; 136 | 137 | if (!this.nestedTables.isNested(hotInstance) && hotInstance !== this.hot) { 138 | return; 139 | } 140 | cellMeta = hotInstance.getCellMeta(coords.row, coords.col); 141 | 142 | if (this.activeNestedTable) { 143 | cellMeta.disableVisualSelection = true; 144 | 145 | } else { 146 | cellMeta.disableVisualSelection = false; 147 | this.activeNestedTable = hotInstance; 148 | } 149 | // on last event set first table as listening 150 | if (hotInstance === this.hot) { 151 | this.activeNestedTable.listen(); 152 | this.activeNestedTable = null; 153 | } 154 | }, 155 | 156 | /** 157 | * @param {Handsontable} hotInstance 158 | * @param {Event} event 159 | * @param {Object} coords 160 | */ 161 | onAfterOnCellMouseDown: function(hotInstance, event, coords) { 162 | var cellMeta; 163 | 164 | if (!this.nestedTables.isNested(hotInstance) && hotInstance !== this.hot) { 165 | return; 166 | } 167 | cellMeta = hotInstance.getCellMeta(coords.row, coords.col); 168 | cellMeta.disableVisualSelection = false; 169 | }, 170 | 171 | /** 172 | * @param {WalkontableCellCoords} coords 173 | * @param {Number} rowTransform 174 | * @param {Number} colTransform 175 | */ 176 | onAfterModifyTransformStart: function(coords, rowTransform, colTransform) { 177 | var parent = this.nestedTables.getParent(), 178 | cellMeta, 179 | newCoords, 180 | selected; 181 | 182 | cellMeta = this.getCellMeta(coords.row, coords.col); 183 | cellMeta.disableVisualSelection = false; 184 | lastSelectedCellMeta = cellMeta; 185 | 186 | if (parent && (rowTransform !== 0 || colTransform !== 0)) { 187 | selected = parent.getSelected(); 188 | cellMeta.disableVisualSelection = true; 189 | newCoords = { 190 | row: selected[0] + rowTransform, 191 | col: selected[1] + colTransform 192 | }; 193 | 194 | if (newCoords.row < 0 || newCoords.row >= parent.countRows()) { 195 | newCoords.row -= rowTransform; 196 | } 197 | if (newCoords.col < 0 || newCoords.col >= parent.countCols()) { 198 | newCoords.col -= colTransform; 199 | } 200 | cellMeta = parent.getCellMeta(newCoords.row, newCoords.col); 201 | cellMeta.disableVisualSelection = false; 202 | lastSelectedCellMeta = cellMeta; 203 | 204 | parent.selectCell(newCoords.row, newCoords.col, undefined, undefined, true, false); 205 | parent.listen(); 206 | } 207 | }, 208 | 209 | /** 210 | * @param {Event} event 211 | */ 212 | onBeforeKeyDown: function(event) { 213 | var td, childTable, cellMeta; 214 | 215 | if (!this.isListening() || event.keyCode !== Handsontable.helper.KEY_CODES.ENTER) { 216 | return; 217 | } 218 | if (!lastSelectedCellMeta || lastSelectedCellMeta.editor !== false) { 219 | return; 220 | } 221 | td = this.getCell(lastSelectedCellMeta.row, lastSelectedCellMeta.col); 222 | 223 | if (td) { 224 | cellMeta = this.getCellMeta(lastSelectedCellMeta.row, lastSelectedCellMeta.col); 225 | cellMeta.disableVisualSelection = true; 226 | // Refresh cell border according to disableVisualSelection setting 227 | this.selectCell(lastSelectedCellMeta.row, lastSelectedCellMeta.col); 228 | 229 | childTable = td.querySelector(this.nodeName); 230 | cellMeta = childTable.getCellMeta(0, 0); 231 | cellMeta.disableVisualSelection = false; 232 | lastSelectedCellMeta = cellMeta; 233 | childTable.selectCell(0, 0, undefined, undefined, true, false); 234 | 235 | setTimeout(function() { 236 | childTable.listen(); 237 | }, 0); 238 | } 239 | }, 240 | 241 | onMutation: function() { 242 | var columns; 243 | 244 | if (this.hot && this.initialized) { 245 | columns = settingsParser.parseColumns(this); 246 | this.hot.updateSettings({columns: columns}); 247 | } 248 | }, 249 | 250 | attributeChanged: function() { 251 | this._onChanged(); 252 | }, 253 | 254 | _onChanged: function() { 255 | var settings; 256 | 257 | if (this.hot && this.initialized) { 258 | settings = settingsParser.parse(this); 259 | this.hot.updateSettings(settings); 260 | } 261 | }, 262 | 263 | _onDatarowsChanged: function() { 264 | if (this.hot && this.initialized) { 265 | this.hot.render(); 266 | } 267 | } 268 | }); 269 | }()); 270 | -------------------------------------------------------------------------------- /src/nested-tables.js: -------------------------------------------------------------------------------- 1 | (function(w) { 2 | /** 3 | * @param {HTMLElement} hotTable 4 | * @constructor 5 | */ 6 | function NestedTable(hotTable) { 7 | this.hotTable = hotTable; 8 | } 9 | 10 | NestedTable.strategies = {}; 11 | 12 | /** 13 | * @param {String} strategyName 14 | * @param {HTMLElement} rootHotTable 15 | */ 16 | NestedTable.prototype.setStrategy = function(strategyName, rootHotTable) { 17 | var strategy; 18 | 19 | if (NestedTable.strategies[strategyName]) { 20 | strategy = new NestedTable.strategies[strategyName](rootHotTable); 21 | 22 | } else { 23 | throw new Error('Strategy name (' + strategyName + ') is not supported'); 24 | } 25 | this.strategy = strategy; 26 | }; 27 | 28 | /** 29 | * Push nested table to collection 30 | * 31 | * @param {HTMLElement} hotTable 32 | */ 33 | NestedTable.prototype.push = function(hotTable) { 34 | if (this.strategy.tables.indexOf(hotTable) === -1) { 35 | this.strategy.tables.push(hotTable); 36 | } 37 | }; 38 | 39 | /** 40 | * Get child tables 41 | * 42 | * @returns {Array} Array of HTMLElements (hot-table) 43 | */ 44 | NestedTable.prototype.getChildren = function() { 45 | return this.strategy.tables; 46 | }; 47 | 48 | /** 49 | * Get parent table 50 | * 51 | * @returns {NestedTable} 52 | */ 53 | NestedTable.prototype.getParent = function() { 54 | return this.parent; 55 | }; 56 | 57 | /** 58 | * Set parent table 59 | * 60 | * @returns {NestedTable} 61 | */ 62 | NestedTable.prototype.setParent = function(parent) { 63 | this.parent = parent; 64 | }; 65 | 66 | /** 67 | * Collect tables 68 | */ 69 | NestedTable.prototype.update = function() { 70 | this.strategy.update(this.hotTable); 71 | }; 72 | 73 | /** 74 | * Checks if HOT instance belongs to the nested tables 75 | * 76 | * @param {Handsontable} hotInstance 77 | * @returns {Boolean} 78 | */ 79 | NestedTable.prototype.isNested = function(hotInstance) { 80 | function isNestedTable(nestedTable, hotInstance) { 81 | var result = false, 82 | tables = nestedTable.getChildren(); 83 | 84 | for (var i = 0, len = tables.length; i < len; i++) { 85 | if (wrap(tables[i]).hot === hotInstance) { 86 | result = true; 87 | break; 88 | } 89 | if (isNestedTable(wrap(tables[i]).nestedTables, hotInstance)) { 90 | result = true; 91 | break; 92 | } 93 | } 94 | 95 | return result; 96 | } 97 | 98 | return isNestedTable(this, hotInstance); 99 | }; 100 | 101 | w.HotTableUtils = w.HotTableUtils || {}; 102 | w.HotTableUtils.NestedTable = NestedTable; 103 | }(window)); 104 | 105 | (function(strategies) { 106 | strategies.emulation = EmulationSupport; 107 | 108 | /** 109 | * Strategy for browsers which not support web components natively (emulation from polymer). 110 | * Update is called by hot-table from child to parent. 111 | * 112 | * @param {HTMLElement} rootHotTable 113 | * @constructor 114 | */ 115 | function EmulationSupport(rootHotTable) { 116 | this.rootHotTable = rootHotTable; 117 | this.tables = []; 118 | } 119 | 120 | /** 121 | * @param {HTMLElement} hotTable 122 | */ 123 | EmulationSupport.prototype.update = function(hotTable) { 124 | var latestParent = null; 125 | 126 | if (this.rootHotTable && this.rootHotTable === hotTable) { 127 | hotTable.addEventListener('initialize', function(event) { 128 | var target, parent; 129 | 130 | event = unwrap(event); 131 | target = event.target; 132 | 133 | if (target === unwrap(hotTable)) { 134 | latestParent = target; 135 | 136 | return; 137 | } 138 | parent = Handsontable.dom.closest(target.parentNode, [target.nodeName]); 139 | 140 | if (parent === latestParent) { 141 | wrap(latestParent).nestedTables.push(wrap(target)); 142 | wrap(target).nestedTables.setParent(wrap(latestParent)); 143 | 144 | } else { 145 | latestParent = parent; 146 | wrap(latestParent).nestedTables.push(wrap(target)); 147 | wrap(target).nestedTables.setParent(wrap(latestParent)); 148 | } 149 | }); 150 | } 151 | hotTable.fire('initialize'); 152 | }; 153 | }(HotTableUtils.NestedTable.strategies)); 154 | 155 | (function(strategies) { 156 | strategies.native = NativeSupport; 157 | 158 | /** 159 | * Strategy for browsers which support web components natively. 160 | * Update is called by hot-table from parent to child. 161 | * 162 | * @param {HTMLElement} rootHotTable 163 | * @constructor 164 | */ 165 | function NativeSupport(rootHotTable) { 166 | this.rootHotTable = rootHotTable; 167 | this.tables = []; 168 | } 169 | 170 | /** 171 | * @param {HTMLElement} hotTable 172 | */ 173 | NativeSupport.prototype.update = function(hotTable) { 174 | var childHotTables = hotTable.hot.rootElement.querySelectorAll(hotTable.nodeName), 175 | index = childHotTables.length, 176 | parentTable; 177 | 178 | while (index--) { 179 | childHotTables[index].nestedTables.setParent(hotTable); 180 | this.tables.unshift(childHotTables[index]); 181 | } 182 | // On table new col/row insert update nested tables collection 183 | parentTable = Handsontable.dom.closest(hotTable.parentNode, [hotTable.nodeName]); 184 | 185 | if (parentTable && parentTable.nestedTables) { 186 | parentTable.nestedTables.push(hotTable); 187 | hotTable.nestedTables.setParent(parentTable); 188 | } 189 | }; 190 | }(HotTableUtils.NestedTable.strategies)); 191 | -------------------------------------------------------------------------------- /src/settings-parser.js: -------------------------------------------------------------------------------- 1 | (function(w) { 2 | var 3 | publicHooks = Handsontable.hooks.getRegistered(), 4 | publicOptions = Object.keys(Handsontable.DefaultSettings.prototype), 5 | publicProperties = [] 6 | ; 7 | 8 | publicProperties = publicProperties.concat(publicOptions, publicHooks); 9 | 10 | /** 11 | * @constructor 12 | */ 13 | function SettingsParser() {} 14 | 15 | /** 16 | * Get handsontable properties. 17 | * 18 | * @returns {Object} 19 | */ 20 | SettingsParser.prototype.getAvailableProperties = function() { 21 | var publish = {}; 22 | 23 | publicProperties.forEach(function(prop) { 24 | if (publish[prop]) { 25 | return; 26 | } 27 | var defaultValue = Handsontable.DefaultSettings.prototype[prop]; 28 | 29 | if (prop === 'data') { 30 | prop = 'datarows'; 31 | 32 | } else if (prop === 'className') { 33 | prop = 'class'; 34 | 35 | } else if (prop === 'title') { 36 | // rename 'title' attribute to 'header' because 'title' was causing 37 | // problems (https://groups.google.com/forum/#!topic/polymer-dev/RMMsV-D4HVw) 38 | prop = 'header'; 39 | } 40 | publish[prop] = { 41 | observer: '_onChanged' 42 | }; 43 | if (prop === 'settings' || prop === 'source' || prop === 'datarows') { 44 | publish[prop].type = Object; 45 | } 46 | 47 | if (typeof defaultValue === 'function') { 48 | publish[prop].value = function() { 49 | return function() { 50 | return defaultValue.apply(this.hot || this, arguments); 51 | }; 52 | }; 53 | } else if (defaultValue !== void 0) { 54 | publish[prop].value = defaultValue; 55 | } 56 | }); 57 | 58 | return publish; 59 | }; 60 | 61 | /** 62 | * Get handsontable properties. 63 | * 64 | * @returns {Object} 65 | */ 66 | SettingsParser.prototype.getHotTableProperties = function() { 67 | var props = this.getAvailableProperties(); 68 | 69 | props.highlightedRow = { 70 | type: Number, 71 | value: -1, 72 | notify: true, 73 | }; 74 | props.highlightedColumn = { 75 | type: Number, 76 | value: -1, 77 | notify: true, 78 | }; 79 | props.id = { 80 | type: String, 81 | value: '', 82 | notify: false, 83 | }; 84 | 85 | return props; 86 | }; 87 | 88 | /** 89 | * Get handsontable properties. 90 | * 91 | * @returns {Object} 92 | */ 93 | SettingsParser.prototype.getHotColumnProperties = function() { 94 | var props = this.getAvailableProperties(); 95 | 96 | delete props.datarows; 97 | props.value = { 98 | observer: '_onChanged' 99 | }; 100 | 101 | return props; 102 | }; 103 | 104 | /** 105 | * Parse hot-table to build handsontable settings object 106 | * 107 | * @param {HTMLElement} hotTable 108 | * @returns {Object} 109 | */ 110 | SettingsParser.prototype.parse = function(hotTable) { 111 | var columns = this.parseColumns(hotTable), 112 | options = {}, 113 | attrName, i, iLen; 114 | 115 | for (i = 0, iLen = publicProperties.length; i < iLen; i++) { 116 | attrName = publicProperties[i]; 117 | 118 | if (attrName === 'data') { 119 | attrName = 'datarows'; 120 | } 121 | options[publicProperties[i]] = this.readOption(hotTable, attrName, hotTable[attrName]); 122 | } 123 | 124 | if (hotTable.settings) { 125 | for (i in hotTable.settings) { 126 | if (hotTable.settings.hasOwnProperty(i)) { 127 | options[i] = hotTable.settings[i]; 128 | } 129 | } 130 | } 131 | if (columns.length) { 132 | options.columns = columns; 133 | } 134 | 135 | return options; 136 | }; 137 | 138 | /** 139 | * Parse hot-table columns (hot-column) to build handsontable columns settings object 140 | * 141 | * @param {HTMLElement} hotTable 142 | * @returns {Array} 143 | */ 144 | SettingsParser.prototype.parseColumns = function(hotTable) { 145 | var columns = [], 146 | i, iLen; 147 | 148 | for (i = 0, iLen = hotTable.childNodes.length; i < iLen; i++) { 149 | if (hotTable.childNodes[i].nodeName === 'HOT-COLUMN') { 150 | columns.push(this.parseColumn(hotTable, hotTable.childNodes[i])); 151 | } 152 | } 153 | 154 | return columns; 155 | }; 156 | 157 | /** 158 | * Parse hot-column to build handsontable column settings object 159 | * 160 | * @param {HTMLElement} hotTable 161 | * @param {HTMLElement} hotColumn 162 | * @returns {Object} 163 | */ 164 | SettingsParser.prototype.parseColumn = function(hotTable, hotColumn) { 165 | var object = {}, 166 | innerHotTable, 167 | attrName, 168 | len, 169 | val, 170 | i; 171 | 172 | for (i = 0, len = publicOptions.length; i < len; i++) { 173 | attrName = publicOptions[i]; 174 | 175 | if (attrName === 'data') { 176 | attrName = 'value'; 177 | 178 | } else if (attrName === 'title') { 179 | attrName = 'header'; 180 | 181 | } else if (attrName === 'className') { 182 | attrName = 'class'; 183 | } 184 | val = hotColumn[attrName]; 185 | 186 | if (val !== void 0 && val !== hotTable[attrName]) { 187 | object[publicOptions[i]] = this.readOption(hotColumn, attrName, val); 188 | } 189 | } 190 | innerHotTable = hotColumn.getElementsByTagName('hot-table'); 191 | 192 | if (innerHotTable.length) { 193 | object.handsontable = new Settings(innerHotTable[0]).parse(); 194 | } 195 | 196 | return object; 197 | }; 198 | 199 | /** 200 | * Read hot-table single option (attribute) 201 | * 202 | * @param {HTMLElement} hotTable 203 | * @param {String} key 204 | * @param {*} value 205 | * @returns {*} 206 | */ 207 | SettingsParser.prototype.readOption = function(hotTable, key, value) { 208 | if (key === 'datarows' || key === 'renderer' || key === 'source' || key === 'dataSchema' || key === 'className') { 209 | return value; 210 | } 211 | value = this.readBool(value); 212 | 213 | return value; 214 | }; 215 | 216 | /** 217 | * Try to read value as boolean if not return untouched value 218 | * 219 | * @param {*} value 220 | * @returns {*} 221 | */ 222 | SettingsParser.prototype.readBool = function(value) { 223 | if (value === 'false') { 224 | return false; 225 | 226 | } else if (value === '' || value === 'true') { 227 | return true; 228 | } 229 | 230 | return value; 231 | }; 232 | 233 | w.HotTableUtils = w.HotTableUtils || {}; 234 | w.HotTableUtils.SettingsParser = SettingsParser; 235 | 236 | }(window)); 237 | -------------------------------------------------------------------------------- /test/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/css/SpecRunner.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Currently, the test suite is designed to be run with the minimum screen width of 600px 3 | * This stylesheet makes sure that this condition is met when using PhantomJS 4 | */ 5 | 6 | html { 7 | font-family: Arial, Helvetica, sans-serif; 8 | font-size: 12px; 9 | min-width: 600px; 10 | } -------------------------------------------------------------------------------- /test/lib/jasmine-2.4.1/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = jasmineRequire.interface(jasmine, env); 36 | 37 | /** 38 | * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 39 | */ 40 | extend(window, jasmineInterface); 41 | 42 | /** 43 | * ## Runner Parameters 44 | * 45 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 46 | */ 47 | 48 | var queryString = new jasmine.QueryString({ 49 | getWindowLocation: function() { return window.location; } 50 | }); 51 | 52 | var catchingExceptions = queryString.getParam("catch"); 53 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 54 | 55 | var throwingExpectationFailures = queryString.getParam("throwFailures"); 56 | env.throwOnExpectationFailure(throwingExpectationFailures); 57 | 58 | var random = queryString.getParam("random"); 59 | env.randomizeTests(random); 60 | 61 | var seed = queryString.getParam("seed"); 62 | if (seed) { 63 | env.seed(seed); 64 | } 65 | 66 | /** 67 | * ## Reporters 68 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 69 | */ 70 | var htmlReporter = new jasmine.HtmlReporter({ 71 | env: env, 72 | onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, 73 | onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, 74 | onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, 75 | addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, 76 | getContainer: function() { return document.body; }, 77 | createElement: function() { return document.createElement.apply(document, arguments); }, 78 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 79 | timer: new jasmine.Timer() 80 | }); 81 | 82 | /** 83 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 84 | */ 85 | env.addReporter(jasmineInterface.jsApiReporter); 86 | env.addReporter(htmlReporter); 87 | 88 | /** 89 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 90 | */ 91 | var specFilter = new jasmine.HtmlSpecFilter({ 92 | filterString: function() { return queryString.getParam("spec"); } 93 | }); 94 | 95 | env.specFilter = function(spec) { 96 | return specFilter.matches(spec.getFullName()); 97 | }; 98 | 99 | /** 100 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. 101 | */ 102 | window.setTimeout = window.setTimeout; 103 | window.setInterval = window.setInterval; 104 | window.clearTimeout = window.clearTimeout; 105 | window.clearInterval = window.clearInterval; 106 | 107 | /** 108 | * ## Execution 109 | * 110 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. 111 | */ 112 | var currentWindowOnload = window.onload; 113 | 114 | window.onload = function() { 115 | if (currentWindowOnload) { 116 | currentWindowOnload(); 117 | } 118 | htmlReporter.initialize(); 119 | env.execute(); 120 | }; 121 | 122 | /** 123 | * Helper function for readability above. 124 | */ 125 | function extend(destination, source) { 126 | for (var property in source) destination[property] = source[property]; 127 | return destination; 128 | } 129 | 130 | }()); 131 | -------------------------------------------------------------------------------- /test/lib/jasmine-2.4.1/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2015 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | function getJasmineRequireObj() { 24 | if (typeof module !== 'undefined' && module.exports) { 25 | return exports; 26 | } else { 27 | window.jasmineRequire = window.jasmineRequire || {}; 28 | return window.jasmineRequire; 29 | } 30 | } 31 | 32 | getJasmineRequireObj().console = function(jRequire, j$) { 33 | j$.ConsoleReporter = jRequire.ConsoleReporter(); 34 | }; 35 | 36 | getJasmineRequireObj().ConsoleReporter = function() { 37 | 38 | var noopTimer = { 39 | start: function(){}, 40 | elapsed: function(){ return 0; } 41 | }; 42 | 43 | function ConsoleReporter(options) { 44 | var print = options.print, 45 | showColors = options.showColors || false, 46 | onComplete = options.onComplete || function() {}, 47 | timer = options.timer || noopTimer, 48 | specCount, 49 | failureCount, 50 | failedSpecs = [], 51 | pendingCount, 52 | ansi = { 53 | green: '\x1B[32m', 54 | red: '\x1B[31m', 55 | yellow: '\x1B[33m', 56 | none: '\x1B[0m' 57 | }, 58 | failedSuites = []; 59 | 60 | print('ConsoleReporter is deprecated and will be removed in a future version.'); 61 | 62 | this.jasmineStarted = function() { 63 | specCount = 0; 64 | failureCount = 0; 65 | pendingCount = 0; 66 | print('Started'); 67 | printNewline(); 68 | timer.start(); 69 | }; 70 | 71 | this.jasmineDone = function() { 72 | printNewline(); 73 | for (var i = 0; i < failedSpecs.length; i++) { 74 | specFailureDetails(failedSpecs[i]); 75 | } 76 | 77 | if(specCount > 0) { 78 | printNewline(); 79 | 80 | var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + 81 | failureCount + ' ' + plural('failure', failureCount); 82 | 83 | if (pendingCount) { 84 | specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); 85 | } 86 | 87 | print(specCounts); 88 | } else { 89 | print('No specs found'); 90 | } 91 | 92 | printNewline(); 93 | var seconds = timer.elapsed() / 1000; 94 | print('Finished in ' + seconds + ' ' + plural('second', seconds)); 95 | printNewline(); 96 | 97 | for(i = 0; i < failedSuites.length; i++) { 98 | suiteFailureDetails(failedSuites[i]); 99 | } 100 | 101 | onComplete(failureCount === 0); 102 | }; 103 | 104 | this.specDone = function(result) { 105 | specCount++; 106 | 107 | if (result.status == 'pending') { 108 | pendingCount++; 109 | print(colored('yellow', '*')); 110 | return; 111 | } 112 | 113 | if (result.status == 'passed') { 114 | print(colored('green', '.')); 115 | return; 116 | } 117 | 118 | if (result.status == 'failed') { 119 | failureCount++; 120 | failedSpecs.push(result); 121 | print(colored('red', 'F')); 122 | } 123 | }; 124 | 125 | this.suiteDone = function(result) { 126 | if (result.failedExpectations && result.failedExpectations.length > 0) { 127 | failureCount++; 128 | failedSuites.push(result); 129 | } 130 | }; 131 | 132 | return this; 133 | 134 | function printNewline() { 135 | print('\n'); 136 | } 137 | 138 | function colored(color, str) { 139 | return showColors ? (ansi[color] + str + ansi.none) : str; 140 | } 141 | 142 | function plural(str, count) { 143 | return count == 1 ? str : str + 's'; 144 | } 145 | 146 | function repeat(thing, times) { 147 | var arr = []; 148 | for (var i = 0; i < times; i++) { 149 | arr.push(thing); 150 | } 151 | return arr; 152 | } 153 | 154 | function indent(str, spaces) { 155 | var lines = (str || '').split('\n'); 156 | var newArr = []; 157 | for (var i = 0; i < lines.length; i++) { 158 | newArr.push(repeat(' ', spaces).join('') + lines[i]); 159 | } 160 | return newArr.join('\n'); 161 | } 162 | 163 | function specFailureDetails(result) { 164 | printNewline(); 165 | print(result.fullName); 166 | 167 | for (var i = 0; i < result.failedExpectations.length; i++) { 168 | var failedExpectation = result.failedExpectations[i]; 169 | printNewline(); 170 | print(indent(failedExpectation.message, 2)); 171 | print(indent(failedExpectation.stack, 2)); 172 | } 173 | 174 | printNewline(); 175 | } 176 | 177 | function suiteFailureDetails(result) { 178 | for (var i = 0; i < result.failedExpectations.length; i++) { 179 | printNewline(); 180 | print(colored('red', 'An error was thrown in an afterAll')); 181 | printNewline(); 182 | print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); 183 | 184 | } 185 | printNewline(); 186 | } 187 | } 188 | 189 | return ConsoleReporter; 190 | }; 191 | -------------------------------------------------------------------------------- /test/lib/jasmine-2.4.1/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsontable/hot-table/fe7f3692e005f3dd6d3dfe52b18a770e8c57abd3/test/lib/jasmine-2.4.1/jasmine_favicon.png -------------------------------------------------------------------------------- /test/lib/jquery.simulate.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Simulate v@VERSION - simulate browser mouse and keyboard events 3 | * https://github.com/jquery/jquery-simulate 4 | * 5 | * Copyright 2012 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * Date: @DATE 10 | */ 11 | 12 | ;(function( $, undefined ) { 13 | 14 | var rkeyEvent = /^key/, 15 | rmouseEvent = /^(?:mouse|contextmenu)|click/; 16 | 17 | $.fn.simulate = function( type, options ) { 18 | return this.each(function() { 19 | new $.simulate( this, type, options ); 20 | }); 21 | }; 22 | 23 | $.simulate = function( elem, type, options ) { 24 | var method = $.camelCase( "simulate-" + type ); 25 | 26 | this.target = elem; 27 | this.options = options; 28 | 29 | if ( this[ method ] ) { 30 | this[ method ](); 31 | } else { 32 | this.simulateEvent( elem, type, options ); 33 | } 34 | }; 35 | 36 | $.extend( $.simulate, { 37 | 38 | keyCode: { 39 | BACKSPACE: 8, 40 | COMMA: 188, 41 | DELETE: 46, 42 | DOWN: 40, 43 | END: 35, 44 | ENTER: 13, 45 | ESCAPE: 27, 46 | HOME: 36, 47 | LEFT: 37, 48 | NUMPAD_ADD: 107, 49 | NUMPAD_DECIMAL: 110, 50 | NUMPAD_DIVIDE: 111, 51 | NUMPAD_ENTER: 108, 52 | NUMPAD_MULTIPLY: 106, 53 | NUMPAD_SUBTRACT: 109, 54 | PAGE_DOWN: 34, 55 | PAGE_UP: 33, 56 | PERIOD: 190, 57 | RIGHT: 39, 58 | SPACE: 32, 59 | TAB: 9, 60 | UP: 38 61 | }, 62 | 63 | buttonCode: { 64 | LEFT: 0, 65 | MIDDLE: 1, 66 | RIGHT: 2 67 | } 68 | }); 69 | 70 | $.extend( $.simulate.prototype, { 71 | 72 | simulateEvent: function( elem, type, options ) { 73 | var event = this.createEvent( type, options ); 74 | this.dispatchEvent( elem, type, event, options ); 75 | }, 76 | 77 | createEvent: function( type, options ) { 78 | if ( rkeyEvent.test( type ) ) { 79 | return this.keyEvent( type, options ); 80 | } 81 | 82 | if ( rmouseEvent.test( type ) ) { 83 | return this.mouseEvent( type, options ); 84 | } 85 | }, 86 | 87 | mouseEvent: function( type, options ) { 88 | var event, eventDoc, doc, body; 89 | options = $.extend({ 90 | bubbles: true, 91 | cancelable: (type !== "mousemove"), 92 | view: window, 93 | detail: 0, 94 | screenX: 0, 95 | screenY: 0, 96 | clientX: 1, 97 | clientY: 1, 98 | ctrlKey: false, 99 | altKey: false, 100 | shiftKey: false, 101 | metaKey: false, 102 | button: 0, 103 | relatedTarget: undefined 104 | }, options ); 105 | 106 | if ( document.createEvent ) { 107 | event = document.createEvent( "MouseEvents" ); 108 | event.initMouseEvent( type, options.bubbles, options.cancelable, 109 | options.view, options.detail, 110 | options.screenX, options.screenY, options.clientX, options.clientY, 111 | options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 112 | options.button, wrap(options.relatedTarget || document.body.parentNode) ); 113 | 114 | // IE 9+ creates events with pageX and pageY set to 0. 115 | // Trying to modify the properties throws an error, 116 | // so we define getters to return the correct values. 117 | if ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) { 118 | eventDoc = event.relatedTarget.ownerDocument || document; 119 | doc = eventDoc.documentElement; 120 | body = eventDoc.body; 121 | 122 | Object.defineProperty( event, "pageX", { 123 | get: function() { 124 | return options.clientX + 125 | ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - 126 | ( doc && doc.clientLeft || body && body.clientLeft || 0 ); 127 | } 128 | }); 129 | Object.defineProperty( event, "pageY", { 130 | get: function() { 131 | return options.clientY + 132 | ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - 133 | ( doc && doc.clientTop || body && body.clientTop || 0 ); 134 | } 135 | }); 136 | } 137 | } else if ( document.createEventObject ) { 138 | try { 139 | event = document.createEventObject(options); 140 | } catch (e) { 141 | event = document.createEventObject(); 142 | $.extend( event, options ); 143 | } 144 | 145 | // standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx 146 | // old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx 147 | // so we actually need to map the standard back to oldIE 148 | event.button = { 149 | 0: 1, 150 | 1: 4, 151 | 2: 2 152 | }[ event.button ] || ( event.button === -1 ? 0 : event.button ); 153 | } 154 | 155 | return event; 156 | }, 157 | 158 | keyEvent: function( type, options ) { 159 | var event; 160 | options = $.extend({ 161 | bubbles: true, 162 | cancelable: true, 163 | view: window, 164 | ctrlKey: false, 165 | altKey: false, 166 | shiftKey: false, 167 | metaKey: false, 168 | keyCode: 0, 169 | charCode: undefined 170 | }, options ); 171 | 172 | if ( document.createEvent ) { 173 | try { 174 | event = document.createEvent( "KeyEvents" ); 175 | event.initKeyEvent( type, options.bubbles, options.cancelable, options.view, 176 | options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 177 | options.keyCode, options.charCode ); 178 | // initKeyEvent throws an exception in WebKit 179 | // see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution 180 | // and also https://bugs.webkit.org/show_bug.cgi?id=13368 181 | // fall back to a generic event until we decide to implement initKeyboardEvent 182 | } catch( err ) { 183 | event = document.createEvent( "Events" ); 184 | event.initEvent( type, options.bubbles, options.cancelable ); 185 | $.extend( event, { 186 | view: options.view, 187 | ctrlKey: options.ctrlKey, 188 | altKey: options.altKey, 189 | shiftKey: options.shiftKey, 190 | metaKey: options.metaKey, 191 | keyCode: options.keyCode, 192 | charCode: options.charCode 193 | }); 194 | } 195 | } else if ( document.createEventObject ) { 196 | event = document.createEventObject(); 197 | $.extend( event, options ); 198 | } 199 | 200 | if ( !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === "[object Opera]") ) { 201 | event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode; 202 | event.charCode = undefined; 203 | } 204 | 205 | return event; 206 | }, 207 | 208 | dispatchEvent: function( elem, type, event ) { 209 | if ( elem[ type ] ) { 210 | elem[ type ](); 211 | } else if ( elem.dispatchEvent ) { 212 | elem.dispatchEvent( event ); 213 | } else if ( elem.fireEvent ) { 214 | elem.fireEvent( "on" + type, event ); 215 | } 216 | }, 217 | 218 | simulateFocus: function() { 219 | var focusinEvent, 220 | triggered = false, 221 | element = $( this.target ); 222 | 223 | function trigger() { 224 | triggered = true; 225 | } 226 | 227 | element.bind( "focus", trigger ); 228 | element[ 0 ].focus(); 229 | 230 | if ( !triggered ) { 231 | focusinEvent = $.Event( "focusin" ); 232 | focusinEvent.preventDefault(); 233 | element.trigger( focusinEvent ); 234 | element.triggerHandler( "focus" ); 235 | } 236 | element.unbind( "focus", trigger ); 237 | }, 238 | 239 | simulateBlur: function() { 240 | var focusoutEvent, 241 | triggered = false, 242 | element = $( this.target ); 243 | 244 | function trigger() { 245 | triggered = true; 246 | } 247 | 248 | element.bind( "blur", trigger ); 249 | element[ 0 ].blur(); 250 | 251 | // blur events are async in IE 252 | setTimeout(function() { 253 | // IE won't let the blur occur if the window is inactive 254 | if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) { 255 | element[ 0 ].ownerDocument.body.focus(); 256 | } 257 | 258 | // Firefox won't trigger events if the window is inactive 259 | // IE doesn't trigger events if we had to manually focus the body 260 | if ( !triggered ) { 261 | focusoutEvent = $.Event( "focusout" ); 262 | focusoutEvent.preventDefault(); 263 | element.trigger( focusoutEvent ); 264 | element.triggerHandler( "blur" ); 265 | } 266 | element.unbind( "blur", trigger ); 267 | }, 1 ); 268 | } 269 | }); 270 | 271 | 272 | 273 | /** complex events **/ 274 | 275 | function findCenter( elem ) { 276 | var offset, 277 | document = $( elem.ownerDocument ); 278 | elem = $( elem ); 279 | offset = elem.offset(); 280 | 281 | return { 282 | x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(), 283 | y: offset.top + elem.outerHeight() / 2 - document.scrollTop() 284 | }; 285 | } 286 | 287 | function findCorner( elem ) { 288 | var offset, 289 | document = $( elem.ownerDocument ); 290 | elem = $( elem ); 291 | offset = elem.offset(); 292 | 293 | return { 294 | x: offset.left - document.scrollLeft(), 295 | y: offset.top - document.scrollTop() 296 | }; 297 | } 298 | 299 | $.extend( $.simulate.prototype, { 300 | simulateDrag: function() { 301 | var i = 0, 302 | target = this.target, 303 | options = this.options, 304 | center = options.handle === "corner" ? findCorner( target ) : findCenter( target ), 305 | x = Math.floor( center.x ), 306 | y = Math.floor( center.y ), 307 | coord = { clientX: x, clientY: y }, 308 | dx = options.dx || ( options.x !== undefined ? options.x - x : 0 ), 309 | dy = options.dy || ( options.y !== undefined ? options.y - y : 0 ), 310 | moves = options.moves || 3; 311 | 312 | this.simulateEvent( target, "mousedown", coord ); 313 | 314 | for ( ; i < moves ; i++ ) { 315 | x += dx / moves; 316 | y += dy / moves; 317 | 318 | coord = { 319 | clientX: Math.round( x ), 320 | clientY: Math.round( y ) 321 | }; 322 | 323 | this.simulateEvent( target.ownerDocument, "mousemove", coord ); 324 | } 325 | 326 | if ( $.contains( document, target ) ) { 327 | this.simulateEvent( target, "mouseup", coord ); 328 | this.simulateEvent( target, "click", coord ); 329 | } else { 330 | this.simulateEvent( document, "mouseup", coord ); 331 | } 332 | } 333 | }); 334 | 335 | })( jQuery ); 336 | -------------------------------------------------------------------------------- /test/spec/behavior/public-methods.spec.js: -------------------------------------------------------------------------------- 1 | describe('PublicMethodsBehavior', function() { 2 | 3 | it('should be defined', function() { 4 | expect(HotTableUtils.behaviors.PublicMethodsBehavior).toBeDefined(); 5 | }); 6 | 7 | it('should return object with all available methods', function() { 8 | var o = HotTableUtils.behaviors.PublicMethodsBehavior; 9 | 10 | expect(o.addHook).toBeFunction(); 11 | expect(o.addHookOnce).toBeFunction(); 12 | expect(o.alter).toBeFunction(); 13 | expect(o.clear).toBeFunction(); 14 | expect(o.clearUndo).toBeFunction(); 15 | expect(o.colOffset).toBeFunction(); 16 | expect(o.colToProp).toBeFunction(); 17 | expect(o.countCols).toBeFunction(); 18 | expect(o.countEmptyCols).toBeFunction(); 19 | expect(o.countEmptyRows).toBeFunction(); 20 | expect(o.countRenderedCols).toBeFunction(); 21 | expect(o.countRenderedRows).toBeFunction(); 22 | expect(o.countRows).toBeFunction(); 23 | expect(o.countVisibleCols).toBeFunction(); 24 | expect(o.countVisibleRows).toBeFunction(); 25 | expect(o.deselectCell).toBeFunction(); 26 | expect(o.destroy).toBeFunction(); 27 | expect(o.destroyEditor).toBeFunction(); 28 | expect(o.getCell).toBeFunction(); 29 | expect(o.getCellEditor).toBeFunction(); 30 | expect(o.getCellMeta).toBeFunction(); 31 | expect(o.getCellRenderer).toBeFunction(); 32 | expect(o.getCellValidator).toBeFunction(); 33 | expect(o.getColHeader).toBeFunction(); 34 | expect(o.getColWidth).toBeFunction(); 35 | expect(o.getCopyableData).toBeFunction(); 36 | expect(o.getData).toBeFunction(); 37 | expect(o.getDataAtCell).toBeFunction(); 38 | expect(o.getDataAtCol).toBeFunction(); 39 | expect(o.getDataAtProp).toBeFunction(); 40 | expect(o.getDataAtRow).toBeFunction(); 41 | expect(o.getDataAtRowProp).toBeFunction(); 42 | expect(o.getInstance).toBeFunction(); 43 | expect(o.getRowHeader).toBeFunction(); 44 | expect(o.getRowHeight).toBeFunction(); 45 | expect(o.getSchema).toBeFunction(); 46 | expect(o.getSelected).toBeFunction(); 47 | expect(o.getSelectedRange).toBeFunction(); 48 | expect(o.getSettings).toBeFunction(); 49 | expect(o.getSourceDataAtCol).toBeFunction(); 50 | expect(o.getSourceDataAtRow).toBeFunction(); 51 | expect(o.getValue).toBeFunction(); 52 | expect(o.hasColHeaders).toBeFunction(); 53 | expect(o.hasRowHeaders).toBeFunction(); 54 | expect(o.isListening).toBeFunction(); 55 | expect(o.isRedoAvailable).toBeFunction(); 56 | expect(o.isUndoAvailable).toBeFunction(); 57 | expect(o.listen).toBeFunction(); 58 | expect(o.loadData).toBeFunction(); 59 | expect(o.populateFromArray).toBeFunction(); 60 | expect(o.propToCol).toBeFunction(); 61 | expect(o.redo).toBeFunction(); 62 | expect(o.removeCellMeta).toBeFunction(); 63 | expect(o.removeHook).toBeFunction(); 64 | expect(o.render).toBeFunction(); 65 | expect(o.rowOffset).toBeFunction(); 66 | expect(o.runHooks).toBeFunction(); 67 | expect(o.selectCell).toBeFunction(); 68 | expect(o.selectCellByProp).toBeFunction(); 69 | expect(o.setCellMeta).toBeFunction(); 70 | expect(o.setCellMetaObject).toBeFunction(); 71 | expect(o.setDataAtCell).toBeFunction(); 72 | expect(o.setDataAtRowProp).toBeFunction(); 73 | expect(o.spliceCol).toBeFunction(); 74 | expect(o.spliceRow).toBeFunction(); 75 | expect(o.undo).toBeFunction(); 76 | expect(o.unlisten).toBeFunction(); 77 | expect(o.updateSettings).toBeFunction(); 78 | expect(o.validateCell).toBeFunction(); 79 | expect(o.validateCells).toBeFunction(); 80 | 81 | // fake Handsontable instance 82 | o.hot = {addHook: function() {return 'foo';}}; 83 | 84 | spyOn(o.hot, 'addHook').and.callThrough(); 85 | 86 | expect(o.addHook(1, 2, 3)).toBe('foo'); 87 | expect(o.hot.addHook).toHaveBeenCalledWith(1, 2, 3); 88 | }); 89 | }); 90 | 91 | -------------------------------------------------------------------------------- /test/spec/hot-table.spec.js: -------------------------------------------------------------------------------- 1 | describe('', function () { 2 | 3 | it('should create table', function(done) { 4 | var 5 | hot = createHotTable(); 6 | 7 | this.$container.append(hot); 8 | 9 | setTimeout(function() { 10 | expect(hot.getCell(0, 0).nodeName).toBe('TD'); 11 | done(); 12 | }, timeout); 13 | }); 14 | 15 | it('should possible to call public handsontable methods', function () { 16 | var 17 | hot = createHotTable(); 18 | 19 | this.$container.append(hot); 20 | 21 | expect(hot.addHook).toBeFunction(); 22 | expect(hot.addHookOnce).toBeFunction(); 23 | expect(hot.alter).toBeFunction(); 24 | expect(hot.clear).toBeFunction(); 25 | expect(hot.clearUndo).toBeFunction(); 26 | expect(hot.colOffset).toBeFunction(); 27 | expect(hot.colToProp).toBeFunction(); 28 | expect(hot.countCols).toBeFunction(); 29 | expect(hot.countEmptyCols).toBeFunction(); 30 | expect(hot.countEmptyRows).toBeFunction(); 31 | expect(hot.countRenderedCols).toBeFunction(); 32 | expect(hot.countRenderedRows).toBeFunction(); 33 | expect(hot.countRows).toBeFunction(); 34 | expect(hot.countSourceRows).toBeFunction(); 35 | expect(hot.countVisibleCols).toBeFunction(); 36 | expect(hot.countVisibleRows).toBeFunction(); 37 | expect(hot.deselectCell).toBeFunction(); 38 | expect(hot.destroy).toBeFunction(); 39 | expect(hot.destroyEditor).toBeFunction(); 40 | expect(hot.getActiveEditor).toBeFunction(); 41 | expect(hot.getCell).toBeFunction(); 42 | expect(hot.getCellEditor).toBeFunction(); 43 | expect(hot.getCellMeta).toBeFunction(); 44 | expect(hot.getCellRenderer).toBeFunction(); 45 | expect(hot.getCellValidator).toBeFunction(); 46 | expect(hot.getCellsMeta).toBeFunction(); 47 | expect(hot.getColHeader).toBeFunction(); 48 | expect(hot.getColWidth).toBeFunction(); 49 | expect(hot.getCoords).toBeFunction(); 50 | expect(hot.getCopyableData).toBeFunction(); 51 | expect(hot.getCopyableText).toBeFunction(); 52 | expect(hot.getData).toBeFunction(); 53 | expect(hot.getDataAtCell).toBeFunction(); 54 | expect(hot.getDataAtCol).toBeFunction(); 55 | expect(hot.getDataAtProp).toBeFunction(); 56 | expect(hot.getDataAtRow).toBeFunction(); 57 | expect(hot.getDataAtRowProp).toBeFunction(); 58 | expect(hot.getDataType).toBeFunction(); 59 | expect(hot.getInstance).toBeFunction(); 60 | expect(hot.getPlugin).toBeFunction(); 61 | expect(hot.getRowHeader).toBeFunction(); 62 | expect(hot.getRowHeight).toBeFunction(); 63 | expect(hot.getSchema).toBeFunction(); 64 | expect(hot.getSelected).toBeFunction(); 65 | expect(hot.getSelectedRange).toBeFunction(); 66 | expect(hot.getSettings).toBeFunction(); 67 | expect(hot.getSourceData).toBeFunction(); 68 | expect(hot.getSourceDataAtCell).toBeFunction(); 69 | expect(hot.getSourceDataAtCol).toBeFunction(); 70 | expect(hot.getSourceDataAtRow).toBeFunction(); 71 | expect(hot.getValue).toBeFunction(); 72 | expect(hot.hasColHeaders).toBeFunction(); 73 | expect(hot.hasRowHeaders).toBeFunction(); 74 | //expect(hot.init).toBeFunction(); 75 | //expect(hot.isEmptyCol).toBeFunction(); 76 | //expect(hot.isEmptyRow).toBeFunction(); 77 | expect(hot.isListening).toBeFunction(); 78 | expect(hot.isRedoAvailable).toBeFunction(); 79 | expect(hot.isUndoAvailable).toBeFunction(); 80 | expect(hot.listen).toBeFunction(); 81 | expect(hot.loadData).toBeFunction(); 82 | expect(hot.populateFromArray).toBeFunction(); 83 | expect(hot.propToCol).toBeFunction(); 84 | expect(hot.removeCellMeta).toBeFunction(); 85 | expect(hot.removeHook).toBeFunction(); 86 | expect(hot.render).toBeFunction(); 87 | expect(hot.rowOffset).toBeFunction(); 88 | expect(hot.runHooks).toBeFunction(); 89 | expect(hot.selectCell).toBeFunction(); 90 | expect(hot.selectCellByProp).toBeFunction(); 91 | expect(hot.setCellMeta).toBeFunction(); 92 | expect(hot.setCellMetaObject).toBeFunction(); 93 | expect(hot.setDataAtCell).toBeFunction(); 94 | expect(hot.setDataAtRowProp).toBeFunction(); 95 | expect(hot.spliceCol).toBeFunction(); 96 | expect(hot.spliceRow).toBeFunction(); 97 | expect(hot.unlisten).toBeFunction(); 98 | expect(hot.updateSettings).toBeFunction(); 99 | expect(hot.validateCell).toBeFunction(); 100 | expect(hot.validateCells).toBeFunction(); 101 | }); 102 | 103 | it('should detect that table is running in hot-table environment', function(done) { 104 | var 105 | hot = createHotTable(); 106 | 107 | this.$container.append(hot); 108 | 109 | setTimeout(function() { 110 | expect(hot.hot.isHotTableEnv).toBe(true); 111 | done(); 112 | }, timeout); 113 | }); 114 | 115 | it('undefined attribute should return default value', function(done) { 116 | var 117 | hot = createHotTable(); 118 | 119 | this.$container.append(hot); 120 | 121 | setTimeout(function() { 122 | // value of the property in element should be the same as property in handsontable settings object. 123 | expect(hot.allowInvalid).toBe(true); 124 | expect(hot.getSettings().allowInvalid).toBe(true); 125 | expect(hot.viewportColumnRenderingOffset).toBe('auto'); 126 | expect(hot.getSettings().viewportColumnRenderingOffset).toBe('auto'); 127 | done(); 128 | }, timeout); 129 | }); 130 | 131 | it('attribute should update settings', function(done) { 132 | var 133 | hot = createHotTable(); 134 | 135 | this.$container.append(hot); 136 | hot.setAttribute('viewport-column-rendering-offset', '100'); 137 | 138 | setTimeout(function() { 139 | // value of the property in element should be the same as property in handsontable settings object. 140 | expect(hot.viewportColumnRenderingOffset).toBe('100'); 141 | expect(hot.getSettings().viewportColumnRenderingOffset).toBe('100'); 142 | done(); 143 | }, timeout); 144 | }); 145 | 146 | it('settings attribute value should be parsed', function(done) { 147 | var 148 | hot = createHotTable(); 149 | 150 | hot.setAttribute('settings', '{"minSpareRows":3}'); 151 | this.$container.append(hot); 152 | 153 | setTimeout(function() { 154 | expect(hot.getSettings().minSpareRows).toBe(3); 155 | done(); 156 | }, timeout); 157 | }); 158 | 159 | it('should be able to pass the data object as attribute directly using template bindings', function(done) { 160 | var 161 | model = { 162 | data: [{ 163 | name: "Freddie" 164 | }], 165 | }, 166 | tpl, domBind; 167 | 168 | this.$container.append( 169 | createHtml('', model) 170 | ); 171 | 172 | setTimeout(function() { 173 | expect(getHotTable().getData()).toEqual([[model.data[0].name]]); 174 | expect(getHotTable().getSourceData()).toBe(model.data); 175 | done(); 176 | }, timeout); 177 | }); 178 | 179 | it('should be able to pass the settings object as attribute directly using template bindings', function(done) { 180 | var 181 | model = { 182 | settings: { 183 | colHeaders: ["First Name"] 184 | }, 185 | }, 186 | tpl, domBind; 187 | 188 | this.$container.append( 189 | createHtml('', model) 190 | ); 191 | 192 | setTimeout(function() { 193 | expect(getHotTable().getColHeader(0)).toBe('First Name'); 194 | done(); 195 | }, timeout); 196 | }); 197 | 198 | it('should parse empty property as boolean true', function(done) { 199 | var 200 | model = {}, 201 | tpl, domBind; 202 | 203 | this.$container.append( 204 | createHtml('', model) 205 | ); 206 | 207 | setTimeout(function() { 208 | expect(getHotTable().getSettings().colHeaders).toBe(true); 209 | done(); 210 | }, timeout); 211 | }); 212 | 213 | it('should parse string "true" as boolean true', function(done) { 214 | var 215 | model = {}, 216 | tpl, domBind; 217 | 218 | this.$container.append( 219 | createHtml('', model) 220 | ); 221 | 222 | setTimeout(function() { 223 | expect(getHotTable().getSettings().colHeaders).toBe(true); 224 | done(); 225 | }, timeout); 226 | }); 227 | 228 | it('should parse string "false" as boolean false', function(done) { 229 | var 230 | model = {}, 231 | tpl, domBind; 232 | 233 | this.$container.append( 234 | createHtml('', model) 235 | ); 236 | 237 | setTimeout(function() { 238 | expect(getHotTable().getSettings().colHeaders).toBe(false); 239 | done(); 240 | }, timeout); 241 | }); 242 | 243 | it('should parse function', function(done) { 244 | var 245 | model = { 246 | fn: myFunction, 247 | }, 248 | tpl, domBind; 249 | 250 | function myFunction(col) { 251 | return col; 252 | } 253 | 254 | this.$container.append( 255 | createHtml('', model) 256 | ); 257 | 258 | setTimeout(function() { 259 | expect(getHotTable().getSettings().colHeaders).toBe(myFunction); 260 | done(); 261 | }, timeout); 262 | }); 263 | 264 | it('should parse empty class as string (not boolean)', function() { 265 | var 266 | hot = createHotTable(); 267 | 268 | this.$container.append(hot); 269 | 270 | expect(Handsontable.dom.polymerWrap(getHotTable()).className).toBe(''); 271 | }); 272 | 273 | it('should parse class attribute and apply to table', function(done) { 274 | var 275 | hot = createHotTable(); 276 | 277 | this.$container.append(hot); 278 | hot.classList.add('my-table'); 279 | 280 | setTimeout(function() { 281 | expect(getHotTable().classList.contains('my-table')).toBe(true); 282 | done(); 283 | }, timeout); 284 | }); 285 | 286 | it('should observe Polymer notifications in data', function(done) { 287 | var 288 | afterRender = jasmine.createSpy('afterRender'), 289 | model = { 290 | settings: { 291 | afterRender: afterRender 292 | }, 293 | data: [{ 294 | name: "Freddie" 295 | }], 296 | }, 297 | lastCount, tpl, domBind; 298 | 299 | this.$container.append( 300 | createHtml('', model) 301 | ); 302 | 303 | lastCount = afterRender.calls.count(); 304 | getHotTable().set("datarows.0.name", "Frederik"); 305 | 306 | setTimeout(function() { 307 | expect(afterRender.calls.count()).toBeGreaterThan(lastCount); 308 | expect((getHotTable().shadowRoot || getHotTable()).querySelector('td').textContent).toBe('Frederik'); 309 | done(); 310 | }, timeout); 311 | }); 312 | 313 | it('should replace the dataset when assigned as a property', function(done) { 314 | var 315 | afterRender = jasmine.createSpy('afterRender'), 316 | model = { 317 | settings: { 318 | afterRender: afterRender 319 | }, 320 | data: [{ 321 | name: "Freddie" 322 | }], 323 | }, 324 | lastCount, tpl, domBind; 325 | 326 | this.$container.append( 327 | createHtml('', model) 328 | ); 329 | 330 | lastCount = afterRender.calls.count(); 331 | getHotTable().datarows = [{ 332 | name: "Mercury" 333 | }] 334 | 335 | setTimeout(function() { 336 | expect(afterRender.calls.count()).toBeGreaterThan(lastCount); 337 | expect((getHotTable().shadowRoot || getHotTable()).querySelector('td').textContent).toBe('Mercury'); 338 | done(); 339 | }, timeout); 340 | }); 341 | }); 342 | -------------------------------------------------------------------------------- /test/spec/settings-parser.spec.js: -------------------------------------------------------------------------------- 1 | describe('SettingsParser', function() { 2 | 3 | it('should be defined', function() { 4 | expect(HotTableUtils.SettingsParser).toBeDefined(); 5 | }); 6 | 7 | it('should return object with all available properties', function() { 8 | var parser = new HotTableUtils.SettingsParser(), 9 | prop = parser.getAvailableProperties(); 10 | 11 | expect(prop.afterCellMetaReset).toBeDefined(); 12 | expect(prop.afterChange).toBeDefined(); 13 | expect(prop.afterChangesObserved).toBeDefined(); 14 | expect(prop.afterColumnMove).toBeDefined(); 15 | expect(prop.afterColumnResize).toBeDefined(); 16 | expect(prop.afterContextMenuDefaultOptions).toBeDefined(); 17 | expect(prop.afterCopyLimit).toBeDefined(); 18 | expect(prop.afterCreateCol).toBeDefined(); 19 | expect(prop.afterCreateRow).toBeDefined(); 20 | expect(prop.afterDeselect).toBeDefined(); 21 | expect(prop.afterDestroy).toBeDefined(); 22 | expect(prop.afterDocumentKeyDown).toBeDefined(); 23 | expect(prop.afterGetCellMeta).toBeDefined(); 24 | expect(prop.afterGetColHeader).toBeDefined(); 25 | expect(prop.afterGetRowHeader).toBeDefined(); 26 | expect(prop.afterInit).toBeDefined(); 27 | expect(prop.afterLoadData).toBeDefined(); 28 | expect(prop.afterMomentumScroll).toBeDefined(); 29 | expect(prop.afterOnCellCornerMouseDown).toBeDefined(); 30 | expect(prop.afterOnCellMouseDown).toBeDefined(); 31 | expect(prop.afterOnCellMouseOver).toBeDefined(); 32 | expect(prop.afterRemoveCol).toBeDefined(); 33 | expect(prop.afterRemoveRow).toBeDefined(); 34 | expect(prop.afterRender).toBeDefined(); 35 | expect(prop.afterRenderer).toBeDefined(); 36 | expect(prop.afterRowMove).toBeDefined(); 37 | expect(prop.afterRowResize).toBeDefined(); 38 | expect(prop.afterScrollHorizontally).toBeDefined(); 39 | expect(prop.afterScrollVertically).toBeDefined(); 40 | expect(prop.afterSelection).toBeDefined(); 41 | expect(prop.afterSelectionByProp).toBeDefined(); 42 | expect(prop.afterSelectionEnd).toBeDefined(); 43 | expect(prop.afterSelectionEndByProp).toBeDefined(); 44 | expect(prop.afterSetCellMeta).toBeDefined(); 45 | expect(prop.afterUpdateSettings).toBeDefined(); 46 | expect(prop.afterValidate).toBeDefined(); 47 | expect(prop.allowInsertColumn).toBeDefined(); 48 | expect(prop.allowInsertRow).toBeDefined(); 49 | expect(prop.allowInvalid).toBeDefined(); 50 | expect(prop.allowRemoveColumn).toBeDefined(); 51 | expect(prop.allowRemoveRow).toBeDefined(); 52 | expect(prop.autoComplete).toBeDefined(); 53 | expect(prop.autoWrapCol).toBeDefined(); 54 | expect(prop.autoWrapRow).toBeDefined(); 55 | expect(prop.beforeAutofill).toBeDefined(); 56 | expect(prop.beforeCellAlignment).toBeDefined(); 57 | expect(prop.beforeChange).toBeDefined(); 58 | expect(prop.beforeChangeRender).toBeDefined(); 59 | expect(prop.beforeDrawBorders).toBeDefined(); 60 | expect(prop.beforeGetCellMeta).toBeDefined(); 61 | expect(prop.beforeInit).toBeDefined(); 62 | expect(prop.beforeInitWalkontable).toBeDefined(); 63 | expect(prop.beforeKeyDown).toBeDefined(); 64 | expect(prop.beforeOnCellMouseDown).toBeDefined(); 65 | expect(prop.beforeRemoveCol).toBeDefined(); 66 | expect(prop.beforeRemoveRow).toBeDefined(); 67 | expect(prop.beforeRender).toBeDefined(); 68 | expect(prop.beforeSetRangeEnd).toBeDefined(); 69 | expect(prop.beforeTouchScroll).toBeDefined(); 70 | expect(prop.beforeValidate).toBeDefined(); 71 | expect(prop.cell).toBeDefined(); 72 | expect(prop.cells).toBeDefined(); 73 | expect(prop.checkedTemplate).toBeDefined(); 74 | expect(prop.class).toBeDefined(); 75 | expect(prop.colHeaders).toBeDefined(); 76 | expect(prop.colWidths).toBeDefined(); 77 | expect(prop.columnSorting).toBeDefined(); 78 | expect(prop.columns).toBeDefined(); 79 | expect(prop.commentedCellClassName).toBeDefined(); 80 | expect(prop.comments).toBeDefined(); 81 | expect(prop.contextMenu).toBeDefined(); 82 | expect(prop.copyable).toBeDefined(); 83 | expect(prop.currentColClassName).toBeDefined(); 84 | expect(prop.currentRowClassName).toBeDefined(); 85 | expect(prop.customBorders).toBeDefined(); 86 | expect(prop.dataSchema).toBeDefined(); 87 | expect(prop.datarows).toBeDefined(); 88 | expect(prop.debug).toBeDefined(); 89 | expect(prop.disableVisualSelection).toBeDefined(); 90 | expect(prop.editor).toBeDefined(); 91 | expect(prop.enterBeginsEditing).toBeDefined(); 92 | expect(prop.enterMoves).toBeDefined(); 93 | expect(prop.fillHandle).toBeDefined(); 94 | expect(prop.fixedColumnsLeft).toBeDefined(); 95 | expect(prop.fragmentSelection).toBeDefined(); 96 | expect(prop.header).toBeDefined(); 97 | expect(prop.height).toBeDefined(); 98 | expect(prop.invalidCellClassName).toBeDefined(); 99 | expect(prop.isEmptyCol).toBeDefined(); 100 | expect(prop.isEmptyRow).toBeDefined(); 101 | expect(prop.manualColumnFreeze).toBeDefined(); 102 | expect(prop.manualColumnMove).toBeDefined(); 103 | expect(prop.manualColumnResize).toBeDefined(); 104 | expect(prop.manualRowMove).toBeDefined(); 105 | expect(prop.manualRowResize).toBeDefined(); 106 | expect(prop.maxCols).toBeDefined(); 107 | expect(prop.maxRows).toBeDefined(); 108 | expect(prop.mergeCells).toBeDefined(); 109 | expect(prop.minCols).toBeDefined(); 110 | expect(prop.minRows).toBeDefined(); 111 | expect(prop.minSpareCols).toBeDefined(); 112 | expect(prop.minSpareRows).toBeDefined(); 113 | expect(prop.modifyCol).toBeDefined(); 114 | expect(prop.modifyColWidth).toBeDefined(); 115 | expect(prop.modifyRow).toBeDefined(); 116 | expect(prop.modifyRowHeight).toBeDefined(); 117 | expect(prop.noWordWrapClassName).toBeDefined(); 118 | expect(prop.observeDOMVisibility).toBeDefined(); 119 | expect(prop.outsideClickDeselects).toBeDefined(); 120 | expect(prop.persistentState).toBeDefined(); 121 | expect(prop.persistentStateLoad).toBeDefined(); 122 | expect(prop.persistentStateReset).toBeDefined(); 123 | expect(prop.persistentStateSave).toBeDefined(); 124 | expect(prop.placeholder).toBeDefined(); 125 | expect(prop.placeholderCellClassName).toBeDefined(); 126 | expect(prop.readOnly).toBeDefined(); 127 | expect(prop.readOnlyCellClassName).toBeDefined(); 128 | expect(prop.renderer).toBeDefined(); 129 | expect(prop.rowHeaders).toBeDefined(); 130 | expect(prop.search).toBeDefined(); 131 | expect(prop.settings).toBeDefined(); 132 | expect(prop.startCols).toBeDefined(); 133 | expect(prop.startRows).toBeDefined(); 134 | expect(prop.stretchH).toBeDefined(); 135 | expect(prop.tabMoves).toBeDefined(); 136 | expect(prop.trimWhitespace).toBeDefined(); 137 | expect(prop.type).toBeDefined(); 138 | expect(prop.uncheckedTemplate).toBeDefined(); 139 | expect(prop.validator).toBeDefined(); 140 | expect(prop.viewportColumnRenderingOffset).toBeDefined(); 141 | expect(prop.viewportRowRenderingOffset).toBeDefined(); 142 | expect(prop.width).toBeDefined(); 143 | expect(prop.wordWrap).toBeDefined(); 144 | }); 145 | 146 | it('should return object with all available properties for ', function() { 147 | var parser = new HotTableUtils.SettingsParser(); 148 | 149 | spyOn(parser, 'getAvailableProperties').and.callThrough(); 150 | 151 | var prop = parser.getHotTableProperties(); 152 | 153 | expect(prop.highlightedRow).toBeDefined(); 154 | expect(prop.highlightedColumn).toBeDefined(); 155 | 156 | expect(parser.getAvailableProperties).toHaveBeenCalled(); 157 | }); 158 | 159 | it('should return object with all available properties for ', function() { 160 | var parser = new HotTableUtils.SettingsParser(); 161 | 162 | spyOn(parser, 'getAvailableProperties').and.callThrough(); 163 | 164 | var prop = parser.getHotColumnProperties(); 165 | 166 | expect(prop.value).toBeDefined(); 167 | expect(prop.datarows).not.toBeDefined(); 168 | 169 | expect(parser.getAvailableProperties).toHaveBeenCalled(); 170 | }); 171 | 172 | it('should parse attributes of himself', function() { 173 | var 174 | parser = new HotTableUtils.SettingsParser(), 175 | hot, settings; 176 | 177 | hot = createHotTable(); 178 | hot.setAttribute('allow-remove-row', 'false'); 179 | hot.enterMoves = {row: 1, col: 1}; 180 | hot.datarows = [{id: 1, name: 'foo'}]; 181 | hot.setAttribute('copyable', 'false'); 182 | hot.setAttribute('editor', 'false'); 183 | hot.setAttribute('min-cols', '10'); 184 | hot.setAttribute('width', '100'); 185 | 186 | settings = parser.parse(hot); 187 | 188 | expect(settings.allowRemoveRow).toBe(false); 189 | expect(settings.enterMoves).toBe(hot.enterMoves); 190 | expect(settings.copyable).toBe(false); 191 | expect(settings.data).toBe(hot.datarows); 192 | expect(settings.editor).toBe(false); 193 | expect(settings.minCols).toBe('10'); 194 | expect(settings.width).toBe('100'); 195 | }); 196 | 197 | it('should parse columns attributes', function() { 198 | var 199 | parser = new HotTableUtils.SettingsParser(), 200 | hot, hotColumn, columns; 201 | 202 | hot = createHotTable(); 203 | columns = parser.parseColumns(hot); 204 | 205 | expect(columns.length).toBe(0); 206 | 207 | hotColumn = document.createElement('hot-column'); 208 | hotColumn.setAttribute('class', 'custom-class second-class'); 209 | hotColumn.setAttribute('editor', 'false'); 210 | hotColumn.setAttribute('read-only', ''); 211 | hotColumn.setAttribute('read-only-cell-class-name', 'read-only'); 212 | hot.appendChild(hotColumn); 213 | columns = parser.parseColumns(hot); 214 | 215 | expect(columns.length).toBe(1); 216 | expect(columns[0].className).toBe('custom-class second-class'); 217 | expect(columns[0].editor).toBe(false); 218 | expect(columns[0].readOnly).toBe(true); 219 | expect(columns[0].readOnlyCellClassName).toBe('read-only'); 220 | }); 221 | 222 | it('should parse single column attributes', function() { 223 | var 224 | parser = new HotTableUtils.SettingsParser(), 225 | hot, hotColumn, columns; 226 | 227 | hot = createHotTable(); 228 | columns = parser.parseColumns(hot); 229 | 230 | expect(columns.length).toBe(0); 231 | 232 | hotColumn = document.createElement('hot-column'); 233 | hotColumn.setAttribute('class', 'custom-class second-class'); 234 | hotColumn.setAttribute('editor', 'false'); 235 | hotColumn.setAttribute('read-only', ''); 236 | hotColumn.setAttribute('read-only-cell-class-name', 'read-only'); 237 | hot.appendChild(hotColumn); 238 | columns = parser.parseColumn(hot, hotColumn); 239 | 240 | expect(columns.className).toBe('custom-class second-class'); 241 | expect(columns.editor).toBe(false); 242 | expect(columns.readOnly).toBe(true); 243 | expect(columns.readOnlyCellClassName).toBe('read-only'); 244 | }); 245 | 246 | it('should read table options in correct way', function() { 247 | var 248 | parser = new HotTableUtils.SettingsParser(), 249 | hot; 250 | 251 | hot = createHotTable(); 252 | spyOn(parser, 'readBool').and.callThrough(); 253 | 254 | expect(parser.readOption(hot, 'className', '')).toBe(''); 255 | expect(parser.readOption(hot, 'className', 'true')).toBe('true'); 256 | expect(parser.readOption(hot, 'someProperty', '')).toBe(true); 257 | expect(parser.readOption(hot, 'someProperty', 'true')).toBe(true); 258 | expect(parser.readOption(hot, 'someProperty', 'false')).toBe(false); 259 | expect(parser.readOption(hot, 'someProperty', void 0)).not.toBeDefined(); 260 | expect(parser.readBool.calls.count()).toEqual(4); 261 | 262 | parser.readOption(hot, 'renderer', 1); 263 | parser.readOption(hot, 'datarows', 2); 264 | parser.readOption(hot, 'source', 3); 265 | parser.readOption(hot, 'dataSchema', 4); 266 | }); 267 | 268 | it('should try to read as boolean if not it returns untouched value', function() { 269 | var 270 | parser = new HotTableUtils.SettingsParser(); 271 | 272 | expect(parser.readBool('')).toBe(true); 273 | expect(parser.readBool('true')).toBe(true); 274 | expect(parser.readBool('foo')).toBe('foo'); 275 | expect(parser.readBool('false')).toBe(false); 276 | expect(parser.readBool(null)).toBe(null); 277 | expect(parser.readBool(void 0)).toBe(void 0); 278 | expect(parser.readBool(12345)).toBe(12345); 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /test/utils/my-custom-renderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /test/utils/my-second-custom-renderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /test/utils/spec-helper.js: -------------------------------------------------------------------------------- 1 | 2 | var currentSpec = {}, 3 | ready = false, 4 | timeout = 100, 5 | _done; 6 | 7 | document.addEventListener('WebComponentsReady', function() { 8 | ready = true; 9 | 10 | if (_done) { 11 | _done(); 12 | } 13 | }); 14 | 15 | beforeEach(function(done) { 16 | currentSpec = this; 17 | this.$container = $('
').appendTo('body'); 18 | 19 | if (ready) { 20 | done(); 21 | } else { 22 | _done = done; 23 | } 24 | }); 25 | 26 | afterEach(function() { 27 | if (this.$container) { 28 | this.$container.remove(); 29 | } 30 | }); 31 | 32 | beforeEach(function() { 33 | jasmine.addMatchers({ 34 | toBeFunction: function() { 35 | return { 36 | compare: function(actual, expected) { 37 | var passed = typeof actual === 'function'; 38 | 39 | return { 40 | pass: passed, 41 | message: 'Expected ' + actual + (passed ? '' : ' not') + ' to equal ' + expected 42 | }; 43 | } 44 | }; 45 | } 46 | }); 47 | }); 48 | 49 | function spec() { 50 | return currentSpec; 51 | } 52 | 53 | function createHotTable() { 54 | var wrapper = document.createElement('div'); 55 | 56 | wrapper.innerHTML = ''; 57 | 58 | return wrapper.firstChild; 59 | }; 60 | 61 | function createHotColumn() { 62 | var wrapper = document.createElement('div'); 63 | 64 | wrapper.innerHTML = ''; 65 | 66 | return wrapper.firstChild; 67 | }; 68 | 69 | function createHtml(html, model) { 70 | var element; 71 | 72 | if (Polymer.Element) { 73 | element = document.createElement('dom-bind'); 74 | tpl = document.createElement('template', 'dom-bind'); 75 | element.appendChild(tpl); 76 | tpl.innerHTML = html; 77 | } else { 78 | element = document.createElement('template', 'dom-bind'); 79 | element.innerHTML = html; 80 | } 81 | 82 | Object.keys(model).forEach(function(key) { 83 | element[key] = model[key]; 84 | }) 85 | 86 | return element; 87 | } 88 | 89 | /** 90 | * @returns {HTMLElement} 91 | */ 92 | function getHotTable() { 93 | return spec().$container.find('hot-table')[0]; 94 | } 95 | 96 | /** 97 | * @returns {jQuery} 98 | */ 99 | function getHtCoreElement() { 100 | return $(getHotTable().instance.rootElement).find('.htCore').first(); 101 | } 102 | 103 | /** 104 | * @returns {jQuery} 105 | */ 106 | function getTopCloneElement() { 107 | return $(getHotTable().instance.rootElement).find('.ht_clone_top').first(); 108 | } 109 | 110 | /** 111 | * @returns {jQuery} 112 | */ 113 | function getLeftCloneElement() { 114 | return $(getHotTable().instance.rootElement).find('.ht_clone_left').first(); 115 | } 116 | 117 | 118 | function countTD() { 119 | return getHtCore().find('tbody td').length; 120 | } 121 | 122 | /** 123 | * Check if editor is visible or not 124 | * 125 | * @returns {Boolean} 126 | */ 127 | function isEditorVisible() { 128 | return !!(getEditorHolder().is(':visible') && !getEditorHolder().is('.htHidden')); 129 | } 130 | 131 | /** 132 | * Get editor holder element 133 | * 134 | * @returns {jQuery} 135 | */ 136 | function getEditorHolder() { 137 | return $(getHotTable().instance.rootElement).find('.handsontableInputHolder'); 138 | } 139 | 140 | /** 141 | * Returns a function that triggers a mouse event 142 | * 143 | * @param {String} type Event type 144 | * @param {Number} button 145 | * @returns {Function} 146 | */ 147 | function handsontableMouseTriggerFactory(type, button) { 148 | return function(element) { 149 | var ev = $.Event(type); 150 | 151 | if (!(element instanceof jQuery)) { 152 | element = $(element); 153 | } 154 | // left click by default 155 | ev.which = button || 1; 156 | element.simulate(type, ev); 157 | }; 158 | } 159 | 160 | var mouseDown = handsontableMouseTriggerFactory('mousedown'); 161 | var mouseUp = handsontableMouseTriggerFactory('mouseup'); 162 | var mouseRightDown = handsontableMouseTriggerFactory('mousedown', 3); 163 | var mouseRightUp = handsontableMouseTriggerFactory('mouseup', 3); 164 | 165 | var mouseDoubleClick = function(element) { 166 | mouseDown(element); 167 | mouseUp(element); 168 | mouseDown(element); 169 | mouseUp(element); 170 | }; 171 | 172 | /** 173 | * Returns a function that triggers a key event 174 | * 175 | * @param {String} type Event type 176 | * @returns {Function} 177 | */ 178 | function handsontableKeyTriggerFactory(type) { 179 | return function(key, extend) { 180 | var ev = {}; 181 | 182 | if (typeof key === 'string') { 183 | if (key.indexOf('shift+') > -1) { 184 | key = key.substring(6); 185 | ev.shiftKey = true; 186 | } 187 | 188 | if (key.indexOf('ctrl+') > -1) { 189 | key = key.substring(5); 190 | ev.ctrlKey = true; 191 | } 192 | 193 | switch (key) { 194 | case 'tab': 195 | ev.keyCode = 9; 196 | break; 197 | 198 | case 'enter': 199 | ev.keyCode = 13; 200 | break; 201 | 202 | case 'esc': 203 | ev.keyCode = 27; 204 | break; 205 | 206 | case 'f2': 207 | ev.keyCode = 113; 208 | break; 209 | 210 | case 'arrow_left': 211 | ev.keyCode = 37; 212 | break; 213 | 214 | case 'arrow_up': 215 | ev.keyCode = 38; 216 | break; 217 | 218 | case 'arrow_right': 219 | ev.keyCode = 39; 220 | break; 221 | 222 | case 'arrow_down': 223 | ev.keyCode = 40; 224 | break; 225 | 226 | case 'ctrl': 227 | ev.keyCode = 17; 228 | break; 229 | 230 | case 'shift': 231 | ev.keyCode = 16; 232 | break; 233 | 234 | case 'backspace': 235 | ev.keyCode = 8; 236 | break; 237 | 238 | case 'space': 239 | ev.keyCode = 32; 240 | break; 241 | 242 | default: 243 | throw new Error('Unrecognised key name: ' + key); 244 | } 245 | } else if (typeof key === 'number') { 246 | ev.keyCode = key; 247 | } 248 | $.extend(ev, extend); 249 | $(document.activeElement).simulate(type, ev); 250 | }; 251 | } 252 | 253 | var keyDown = handsontableKeyTriggerFactory('keydown'); 254 | var keyUp = handsontableKeyTriggerFactory('keyup'); 255 | 256 | /** 257 | * Presses keyDown, then keyUp 258 | * 259 | * @param {String} key 260 | * @param {*} extend 261 | */ 262 | function keyDownUp(key, extend) { 263 | if (typeof key === 'string' && key.indexOf('shift+') > -1) { 264 | keyDown('shift'); 265 | } 266 | keyDown(key, extend); 267 | keyUp(key, extend); 268 | 269 | if (typeof key === 'string' && key.indexOf('shift+') > -1) { 270 | keyUp('shift'); 271 | } 272 | } 273 | 274 | function serveImmediatePropagation(event) { 275 | if (event != null && event.isImmediatePropagationEnabled == null) { 276 | event.stopImmediatePropagation = function() { 277 | this.isImmediatePropagationEnabled = false; 278 | this.cancelBubble = true; 279 | }; 280 | event.isImmediatePropagationEnabled = true; 281 | event.isImmediatePropagationStopped = function() { 282 | return !this.isImmediatePropagationEnabled; 283 | }; 284 | } 285 | 286 | return event; 287 | } 288 | 289 | /** 290 | * Shows context menu 291 | * @param {HTMLElement} hot 292 | */ 293 | function contextMenu(hot) { 294 | hot = hot || getHotTable(); 295 | var selected = hot.getSelected(); 296 | 297 | if (!selected) { 298 | hot.selectCell(0, 0); 299 | selected = hot.getSelected(); 300 | } 301 | var cell = hot.getCell(selected[0], selected[1]); 302 | var cellOffset = $(cell).offset(); 303 | 304 | $(cell).simulate('contextmenu', { 305 | clientX: cellOffset.left, 306 | clientY: cellOffset.top 307 | }); 308 | } 309 | 310 | function closeContextMenu() { 311 | $(document).simulate('mousedown'); 312 | } 313 | -------------------------------------------------------------------------------- /wct.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "plugins": { 4 | "local": { 5 | "browsers": ["chrome"] 6 | } 7 | } 8 | } 9 | --------------------------------------------------------------------------------