├── .gitignore ├── .idea ├── misc.xml ├── modules.xml ├── pyhandsontable.iml └── vcs.xml ├── .travis.yml ├── LICENSE ├── README.md ├── pyhandsontable ├── __init__.py ├── core.py ├── pagination.py └── templates │ ├── handsontable.full.min.css │ ├── handsontable.full.min.js │ ├── sheet.html │ └── sheet.js ├── pyproject.lock ├── pyproject.toml ├── screenshots ├── 0.png └── 1.png └── tests ├── __init__.py ├── output └── .gitignore └── test_generate.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/linux,python,pycharm,jupyternotebook 3 | 4 | ### JupyterNotebook ### 5 | .ipynb_checkpoints 6 | */.ipynb_checkpoints/* 7 | 8 | # Remove previous ipynb_checkpoints 9 | # git rm -r .ipynb_checkpoints/ 10 | # 11 | 12 | ### Linux ### 13 | *~ 14 | 15 | # temporary files which can be created if a process still has a handle open of a deleted file 16 | .fuse_hidden* 17 | 18 | # KDE directory preferences 19 | .directory 20 | 21 | # Linux trash folder which might appear on any partition or disk 22 | .Trash-* 23 | 24 | # .nfs files are created when an open file is removed but is still being accessed 25 | .nfs* 26 | 27 | ### PyCharm ### 28 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 29 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 30 | 31 | # User-specific stuff 32 | .idea/**/workspace.xml 33 | .idea/**/tasks.xml 34 | .idea/**/usage.statistics.xml 35 | .idea/**/dictionaries 36 | .idea/**/shelf 37 | 38 | # Sensitive or high-churn files 39 | .idea/**/dataSources/ 40 | .idea/**/dataSources.ids 41 | .idea/**/dataSources.local.xml 42 | .idea/**/sqlDataSources.xml 43 | .idea/**/dynamic.xml 44 | .idea/**/uiDesigner.xml 45 | .idea/**/dbnavigator.xml 46 | 47 | # Gradle 48 | .idea/**/gradle.xml 49 | .idea/**/libraries 50 | 51 | # Gradle and Maven with auto-import 52 | # When using Gradle or Maven with auto-import, you should exclude module files, 53 | # since they will be recreated, and may cause churn. Uncomment if using 54 | # auto-import. 55 | # .idea/modules.xml 56 | # .idea/*.iml 57 | # .idea/modules 58 | 59 | # CMake 60 | cmake-build-*/ 61 | 62 | # Mongo Explorer plugin 63 | .idea/**/mongoSettings.xml 64 | 65 | # File-based project format 66 | *.iws 67 | 68 | # IntelliJ 69 | out/ 70 | 71 | # mpeltonen/sbt-idea plugin 72 | .idea_modules/ 73 | 74 | # JIRA plugin 75 | atlassian-ide-plugin.xml 76 | 77 | # Cursive Clojure plugin 78 | .idea/replstate.xml 79 | 80 | # Crashlytics plugin (for Android Studio and IntelliJ) 81 | com_crashlytics_export_strings.xml 82 | crashlytics.properties 83 | crashlytics-build.properties 84 | fabric.properties 85 | 86 | # Editor-based Rest Client 87 | .idea/httpRequests 88 | 89 | ### PyCharm Patch ### 90 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 91 | 92 | # *.iml 93 | # modules.xml 94 | # .idea/misc.xml 95 | # *.ipr 96 | 97 | # Sonarlint plugin 98 | .idea/sonarlint 99 | 100 | ### Python ### 101 | # Byte-compiled / optimized / DLL files 102 | __pycache__/ 103 | *.py[cod] 104 | *$py.class 105 | 106 | # C extensions 107 | *.so 108 | 109 | # Distribution / packaging 110 | .Python 111 | build/ 112 | develop-eggs/ 113 | dist/ 114 | downloads/ 115 | eggs/ 116 | .eggs/ 117 | lib/ 118 | lib64/ 119 | parts/ 120 | sdist/ 121 | var/ 122 | wheels/ 123 | *.egg-info/ 124 | .installed.cfg 125 | *.egg 126 | MANIFEST 127 | 128 | # PyInstaller 129 | # Usually these files are written by a python script from a template 130 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 131 | *.manifest 132 | *.spec 133 | 134 | # Installer logs 135 | pip-log.txt 136 | pip-delete-this-directory.txt 137 | 138 | # Unit test / coverage reports 139 | htmlcov/ 140 | .tox/ 141 | .coverage 142 | .coverage.* 143 | .cache 144 | nosetests.xml 145 | coverage.xml 146 | *.cover 147 | .hypothesis/ 148 | .pytest_cache/ 149 | 150 | # Translations 151 | *.mo 152 | *.pot 153 | 154 | # Django stuff: 155 | *.log 156 | local_settings.py 157 | db.sqlite3 158 | 159 | # Flask stuff: 160 | instance/ 161 | .webassets-cache 162 | 163 | # Scrapy stuff: 164 | .scrapy 165 | 166 | # Sphinx documentation 167 | docs/_build/ 168 | 169 | # PyBuilder 170 | target/ 171 | 172 | # Jupyter Notebook 173 | 174 | # pyenv 175 | .python-version 176 | 177 | # celery beat schedule file 178 | celerybeat-schedule 179 | 180 | # SageMath parsed files 181 | *.sage.py 182 | 183 | # Environments 184 | .env 185 | .venv 186 | env/ 187 | venv/ 188 | ENV/ 189 | env.bak/ 190 | venv.bak/ 191 | 192 | # Spyder project settings 193 | .spyderproject 194 | .spyproject 195 | 196 | # Rope project settings 197 | .ropeproject 198 | 199 | # mkdocs documentation 200 | /site 201 | 202 | # mypy 203 | .mypy_cache/ 204 | 205 | ### Python Patch ### 206 | .venv/ 207 | 208 | 209 | # End of https://www.gitignore.io/api/linux,python,pycharm,jupyternotebook 210 | 211 | setup.py 212 | .idea/* 213 | .vscode/* 214 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/pyhandsontable.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | - "3.6" 5 | # - "3.7-dev" Currently running 3.7 on Ubuntu 6 | 7 | install: 8 | - "curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python" 9 | - "poetry install" 10 | 11 | script: 12 | - "pytest" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pacharapol Withayasakpunt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyhandsontable 2 | 3 | [![Build Status](https://travis-ci.org/patarapolw/pyhandsontable.svg?branch=master)](https://travis-ci.org/patarapolw/pyhandsontable) 4 | [![PyPI version shields.io](https://img.shields.io/pypi/v/pyhandsontable.svg)](https://pypi.python.org/pypi/pyhandsontable/) 5 | [![PyPI license](https://img.shields.io/pypi/l/pyhandsontable.svg)](https://pypi.python.org/pypi/pyhandsontable/) 6 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/pyhandsontable.svg)](https://pypi.python.org/pypi/pyhandsontable/) 7 | 8 | View a list of JSON-serializable dictionaries or a 2-D array, in HandsOnTable, in Jupyter Notebook. 9 | 10 | Nested JSON renderer is also supported and is default. Image and markdown renderers are possible, but has to be extended. 11 | 12 | ## Installation 13 | 14 | ```commandline 15 | pip install pyhandsontable 16 | ``` 17 | 18 | ## Usage 19 | 20 | In Jupyter Notebook, 21 | 22 | ```python 23 | >>> from pyhandsontable import PagedViewer 24 | >>> viewer = PagedViewer(data=data_matrix, **kwargs) 25 | >>> viewer 26 | 'A Handsontable is shown in Jupyter Notebook.' 27 | >>> viewer.view(-1) 28 | 'The last page is shown.' 29 | >>> viewer.previous() 30 | 'The previous page (i-1) is shown.' 31 | >>> viewer.next() 32 | 'The next page (i+1) is shown.' 33 | ``` 34 | 35 | Data matrix can be either a list of lists (2-D array) or a list of dictionaries. 36 | 37 | It is also possible to view all entries at once, but it could be bad, if there are too many rows. 38 | 39 | ```python 40 | >>> from pyhandsontable import view_table 41 | >>> view_table(data_matrix, **kwargs) 42 | ``` 43 | 44 | ## Acceptable kwargs 45 | 46 | - height: height of the window (default: 500) 47 | - width: width of the window (default: 1000) 48 | - title: title of the HTML file 49 | - maxColWidth: maximum column width. (Default: 200) 50 | - renderers: the renderers to use in generating the columns (see below.) 51 | - autodelete: whether the temporary HTML file should be autodeleted. (Default: True) 52 | - filename: filename of the temporary HTML file (default: 'temp.handsontable.html') 53 | - config: add additional config as defined in https://docs.handsontable.com/pro/5.0.0/tutorial-introduction.html 54 | - This will override the default config (per key basis) which are: 55 | 56 | ```javascript 57 | { 58 | data: data, 59 | rowHeaders: true, 60 | colHeaders: true, 61 | columns: columns, 62 | manualColumnResize: true, 63 | manualRowResize: true, 64 | renderAllRows: true, 65 | modifyColWidth: (width, col)=>{ 66 | if(width > maxColWidth) return maxColWidth; 67 | }, 68 | afterRenderer: (td, row, column, prop, value, cellProperties)=>{ 69 | td.innerHTML = '
' + td.innerHTML + '
'; 70 | } 71 | } 72 | ``` 73 | 74 | `renderers` example, if your data is a 2-D array: 75 | 76 | ```python 77 | { 78 | 1: 'html', 79 | 2: 'html' 80 | } 81 | ``` 82 | 83 | or if your data is list of dict: 84 | 85 | ```python 86 | { 87 | "front": 'html', 88 | "back": 'html' 89 | } 90 | ``` 91 | 92 | ## Enabling Image, HTML and Markdown renderer 93 | 94 | This can be done in Python side, by converting everything to HTML. Just use [any markdown for Python library](https://github.com/Python-Markdown/markdown). 95 | 96 | ```python 97 | from markdown import markdown 98 | import base64 99 | image_html = f'' 100 | image_html2 = f'' 101 | markdown_html = markdown(markdown_raw) 102 | ``` 103 | 104 | Any then, 105 | 106 | ```python 107 | PagedViewer(data=data_matrix, renderers={ 108 | "image_field": "html", 109 | "html_field": "html", 110 | "markdown_field": "html" 111 | }) 112 | ``` 113 | 114 | ## Screenshots 115 | 116 | ![1.png](/screenshots/1.png?raw=true) 117 | ![0.png](/screenshots/0.png?raw=true) 118 | 119 | ## Related projects 120 | 121 | - [htmlviewer](https://github.com/patarapolw/htmlviewer) - similar in concept to this project, but does not use HandsOnTable.js 122 | - [TinyDB-viewer](https://github.com/patarapolw/tinydb-viewer) - uses HandsOnTable.js and also allow editing in Jupyter Notebook. 123 | 124 | ## License 125 | 126 | This software includes [`handsontable.js`](https://github.com/handsontable/handsontable), which is MIT-licensed. 127 | -------------------------------------------------------------------------------- /pyhandsontable/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import generate_html, view_table 2 | from .pagination import PagedViewer 3 | -------------------------------------------------------------------------------- /pyhandsontable/core.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, PackageLoader 2 | from threading import Timer 3 | import os 4 | from collections import OrderedDict 5 | from IPython.display import IFrame 6 | 7 | env = Environment( 8 | loader=PackageLoader('pyhandsontable', 'templates') 9 | ) 10 | 11 | 12 | def generate_html(data, **kwargs): 13 | renderers = kwargs.pop('renderers', dict()) 14 | config = kwargs.pop('config', dict()) 15 | 16 | if isinstance(data[0], (dict, OrderedDict)): 17 | headers = sum((list(d.keys()) for d in data), list()) 18 | headers = [h for i, h in enumerate(headers) if h not in headers[:i]] 19 | config['colHeaders'] = list(headers) 20 | else: 21 | headers = range(len(data[0])) 22 | 23 | columns = [] 24 | for header in headers: 25 | columnData = { 26 | 'data': header, 27 | 'renderer': 'jsonRenderer' 28 | } 29 | if header in renderers.keys(): 30 | columnData['renderer'] = renderers.get(header) 31 | columns.append(columnData) 32 | 33 | template = env.get_template('sheet.html') 34 | 35 | return template.render(data=data, columns=columns, config=config, **kwargs) 36 | 37 | 38 | def view_table(data, width=1000, height=500, 39 | filename='temp.handsontable.html', autodelete=True, **kwargs): 40 | # A TemporaryFile does not work with Jupyter Notebook 41 | 42 | try: 43 | with open(filename, 'w') as f: 44 | f.write(generate_html(data=data, width=width, height=height, **kwargs)) 45 | 46 | return IFrame(filename, width=width, height=height) 47 | finally: 48 | if autodelete: 49 | Timer(5, os.unlink, args=[filename]).start() 50 | -------------------------------------------------------------------------------- /pyhandsontable/pagination.py: -------------------------------------------------------------------------------- 1 | from IPython.display import display 2 | import math 3 | 4 | from .core import view_table 5 | 6 | 7 | class PagedViewer: 8 | chunk_size = 10 9 | i = 0 10 | 11 | def __init__(self, 12 | records, 13 | chunk_size=10, 14 | **kwargs): 15 | self.records = list(records) 16 | self.chunk_size = chunk_size 17 | self.viewer_kwargs = kwargs 18 | self.viewer_kwargs.setdefault('config', dict()).setdefault('rowHeaders', False) 19 | 20 | @property 21 | def num_pages(self): 22 | return math.ceil(len(self.records) / self.chunk_size) 23 | 24 | @property 25 | def num_records(self): 26 | return len(self.records) 27 | 28 | def __len__(self): 29 | return self.num_records 30 | 31 | def _repr_html_(self): 32 | display(self.view()) 33 | return '' 34 | 35 | def view(self, page_number = None, start = None): 36 | """Choose a page number to view 37 | 38 | Keyword Arguments: 39 | page_number {int >= -1} -- Page number to view (default: {self.i}) 40 | start {int} -- Sequence of the record to start viewing (default: {None}) 41 | 42 | Returns: 43 | Viewer function object 44 | """ 45 | 46 | if page_number is None: 47 | page_number = self.i 48 | elif page_number == -1: 49 | page_number = self.num_pages -1 50 | 51 | self.i = page_number 52 | 53 | if start is None: 54 | start = page_number * self.chunk_size 55 | 56 | return view_table(self.records[start: start + self.chunk_size], **self.viewer_kwargs) 57 | 58 | def next(self): 59 | """Shows the next page 60 | 61 | Returns: 62 | Viewer function object 63 | """ 64 | 65 | if len(self.records) < (self.i + 1) * self.chunk_size: 66 | self.i = 0 67 | else: 68 | self.i += 1 69 | 70 | return self.view() 71 | 72 | def previous(self): 73 | """Show the previous page 74 | 75 | Returns: 76 | Viewer function object 77 | """ 78 | 79 | self.i -= 1 80 | if self.i < 0: 81 | self.i = self.num_pages - 1 82 | 83 | return self.view() 84 | 85 | def first(self): 86 | """Shows the first page 87 | 88 | Returns: 89 | Viewer function object 90 | """ 91 | 92 | return self.view(0) 93 | 94 | def last(self): 95 | """Shows the last page 96 | 97 | Returns: 98 | Viewer function object 99 | """ 100 | 101 | return self.view(-1) 102 | -------------------------------------------------------------------------------- /pyhandsontable/templates/handsontable.full.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*! 3 | * (The MIT License) 4 | * 5 | * Copyright (c) 2012-2014 Marcin Warpechowski 6 | * Copyright (c) 2015 Handsoncode sp. z o.o. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files (the 10 | * 'Software'), to deal in the Software without restriction, including 11 | * without limitation the rights to use, copy, modify, merge, publish, 12 | * distribute, sublicense, and/or sell copies of the Software, and to 13 | * permit persons to whom the Software is furnished to do so, subject to 14 | * the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | * 27 | * Version: 6.0.1 28 | * Release date: 02/10/2018 (built at 02/10/2018 11:30:48) 29 | */.handsontable .table td,.handsontable .table th{border-top:none}.handsontable tr{background:#fff}.handsontable td{background-color:inherit}.handsontable .table caption+thead tr:first-child td,.handsontable .table caption+thead tr:first-child th,.handsontable .table colgroup+thead tr:first-child td,.handsontable .table colgroup+thead tr:first-child th,.handsontable .table thead:first-child tr:first-child td,.handsontable .table thead:first-child tr:first-child th{border-top:1px solid #ccc}.handsontable .table-bordered{border:0;border-collapse:separate}.handsontable .table-bordered td,.handsontable .table-bordered th{border-left:none}.handsontable .table-bordered td:first-child,.handsontable .table-bordered th:first-child{border-left:1px solid #ccc}.handsontable .table>tbody>tr>td,.handsontable .table>tbody>tr>th,.handsontable .table>tfoot>tr>td,.handsontable .table>tfoot>tr>th,.handsontable .table>thead>tr>td,.handsontable .table>thead>tr>th{line-height:21px;padding:0 4px}.col-lg-1.handsontable,.col-lg-2.handsontable,.col-lg-3.handsontable,.col-lg-4.handsontable,.col-lg-5.handsontable,.col-lg-6.handsontable,.col-lg-7.handsontable,.col-lg-8.handsontable,.col-lg-9.handsontable,.col-lg-10.handsontable,.col-lg-11.handsontable,.col-lg-12.handsontable,.col-md-1.handsontable,.col-md-2.handsontable,.col-md-3.handsontable,.col-md-4.handsontable,.col-md-5.handsontable,.col-md-6.handsontable,.col-md-7.handsontable,.col-md-8.handsontable,.col-md-9.handsontable .col-sm-1.handsontable,.col-md-10.handsontable,.col-md-11.handsontable,.col-md-12.handsontable,.col-sm-2.handsontable,.col-sm-3.handsontable,.col-sm-4.handsontable,.col-sm-5.handsontable,.col-sm-6.handsontable,.col-sm-7.handsontable,.col-sm-8.handsontable,.col-sm-9.handsontable .col-xs-1.handsontable,.col-sm-10.handsontable,.col-sm-11.handsontable,.col-sm-12.handsontable,.col-xs-2.handsontable,.col-xs-3.handsontable,.col-xs-4.handsontable,.col-xs-5.handsontable,.col-xs-6.handsontable,.col-xs-7.handsontable,.col-xs-8.handsontable,.col-xs-9.handsontable,.col-xs-10.handsontable,.col-xs-11.handsontable,.col-xs-12.handsontable{padding-left:0;padding-right:0}.handsontable .table-striped>tbody>tr:nth-of-type(2n){background-color:#fff}.handsontable{position:relative}.handsontable .hide{display:none}.handsontable .relative{position:relative}.handsontable.htAutoSize{visibility:hidden;left:-99000px;position:absolute;top:-99000px}.handsontable .wtHider{width:0}.handsontable .wtSpreader{position:relative;width:0;height:auto}.handsontable div,.handsontable input,.handsontable table,.handsontable tbody,.handsontable td,.handsontable textarea,.handsontable th,.handsontable thead{box-sizing:content-box;-webkit-box-sizing:content-box;-moz-box-sizing:content-box}.handsontable input,.handsontable textarea{min-height:0}.handsontable table.htCore{border-collapse:separate;border-spacing:0;margin:0;border-width:0;table-layout:fixed;width:0;outline-width:0;cursor:default;max-width:none;max-height:none}.handsontable col,.handsontable col.rowHeader{width:50px}.handsontable td,.handsontable th{border-top-width:0;border-left-width:0;border-right:1px solid #ccc;border-bottom:1px solid #ccc;height:22px;empty-cells:show;line-height:21px;padding:0 4px;background-color:#fff;vertical-align:top;overflow:hidden;outline-width:0;white-space:pre-line;background-clip:padding-box}.handsontable td.htInvalid{background-color:#ff4c42!important}.handsontable td.htNoWrap{white-space:nowrap}.handsontable th:last-child{border-right:1px solid #ccc;border-bottom:1px solid #ccc}.handsontable th.htNoFrame,.handsontable th:first-child.htNoFrame,.handsontable tr:first-child th.htNoFrame{border-left-width:0;background-color:#fff;border-color:#fff}.handsontable .htNoFrame+td,.handsontable .htNoFrame+th,.handsontable.htRowHeaders thead tr th:nth-child(2),.handsontable td:first-of-type,.handsontable th:first-child,.handsontable th:nth-child(2){border-left:1px solid #ccc}.handsontable tr:first-child td,.handsontable tr:first-child th{border-top:1px solid #ccc}.ht_master:not(.innerBorderLeft):not(.emptyColumns)~.handsontable:not(.ht_clone_top) thead tr th:first-child,.ht_master:not(.innerBorderLeft):not(.emptyColumns)~.handsontable tbody tr th{border-right-width:0}.ht_master:not(.innerBorderTop) thead tr.lastChild th,.ht_master:not(.innerBorderTop) thead tr:last-child th,.ht_master:not(.innerBorderTop)~.handsontable thead tr.lastChild th,.ht_master:not(.innerBorderTop)~.handsontable thead tr:last-child th{border-bottom-width:0}.handsontable th{background-color:#f0f0f0;color:#222;text-align:center;font-weight:400;white-space:nowrap}.handsontable thead th{padding:0}.handsontable th.active{background-color:#ccc}.handsontable thead th .relative{padding:2px 4px}#hot-display-license-info{font-size:10px;color:#323232;padding:5px 0 3px;font-family:Helvetica,Arial,sans-serif;text-align:left}.handsontable .manualColumnResizer{position:fixed;top:0;cursor:col-resize;z-index:110;width:5px;height:25px}.handsontable .manualRowResizer{position:fixed;left:0;cursor:row-resize;z-index:110;height:5px;width:50px}.handsontable .manualColumnResizer.active,.handsontable .manualColumnResizer:hover,.handsontable .manualRowResizer.active,.handsontable .manualRowResizer:hover{background-color:#34a9db}.handsontable .manualColumnResizerGuide{position:fixed;right:0;top:0;background-color:#34a9db;display:none;width:0;border-right:1px dashed #777;margin-left:5px}.handsontable .manualRowResizerGuide{position:fixed;left:0;bottom:0;background-color:#34a9db;display:none;height:0;border-bottom:1px dashed #777;margin-top:5px}.handsontable .manualColumnResizerGuide.active,.handsontable .manualRowResizerGuide.active{display:block;z-index:199}.handsontable .columnSorting{position:relative}.handsontable .columnSorting.sortAction:hover{text-decoration:underline;cursor:pointer}.handsontable span.colHeader{display:inline-block;line-height:1.1}.handsontable span.colHeader.columnSorting:before{top:50%;margin-top:-6px;padding-left:8px;position:absolute;right:-9px;content:"";height:10px;width:5px;background-size:contain;background-repeat:no-repeat;background-position-x:right}.handsontable span.colHeader.columnSorting.ascending:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAMAAADJ7yrpAAAAKlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE86IAAAADXRSTlMABBEmRGprlJW72e77tTkTKwAAAFNJREFUeAHtzjkSgCAUBNHPgsoy97+ulGXRqJE5L+xkxoYt2UdsLb5bqFINz+aLuuLn5rIu2RkO3fZpWENimNgiw6iBYRTPMLJjGFxQZ1hxxb/xBI1qC8k39CdKAAAAAElFTkSuQmCC")}.handsontable span.colHeader.columnSorting.descending:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAMAAADJ7yrpAAAAKlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE86IAAAADXRSTlMABBEmRGprlJW72e77tTkTKwAAAFJJREFUeAHtzjkSgCAQRNFmQYUZ7n9dKUvru0TmvPAn3br0QfgdZ5xx6x+rQn23GqTYnq1FDcnuzZIO2WmedVqIRVxgGKEyjNgYRjKGkZ1hFIZ3I70LyM0VtU8AAAAASUVORK5CYII=")}.htGhostTable .htCore span.colHeader.columnSorting:not(.indicatorDisabled):after{content:"*";display:inline-block;position:relative;padding-right:20px}.handsontable .wtBorder{position:absolute;font-size:0}.handsontable .wtBorder.hidden{display:none!important}.handsontable .wtBorder.current{z-index:10}.handsontable .wtBorder.area{z-index:8}.handsontable .wtBorder.fill{z-index:6}.handsontable td.area,.handsontable td.area-1,.handsontable td.area-2,.handsontable td.area-3,.handsontable td.area-4,.handsontable td.area-5,.handsontable td.area-6,.handsontable td.area-7{position:relative}.handsontable td.area-1:before,.handsontable td.area-2:before,.handsontable td.area-3:before,.handsontable td.area-4:before,.handsontable td.area-5:before,.handsontable td.area-6:before,.handsontable td.area-7:before,.handsontable td.area:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;bottom:-100%\9;background:#005eff}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.handsontable td.area-1:before,.handsontable td.area-2:before,.handsontable td.area-3:before,.handsontable td.area-4:before,.handsontable td.area-5:before,.handsontable td.area-6:before,.handsontable td.area-7:before,.handsontable td.area:before{bottom:-100%}}.handsontable td.area:before{opacity:.1}.handsontable td.area-1:before{opacity:.2}.handsontable td.area-2:before{opacity:.27}.handsontable td.area-3:before{opacity:.35}.handsontable td.area-4:before{opacity:.41}.handsontable td.area-5:before{opacity:.47}.handsontable td.area-6:before{opacity:.54}.handsontable td.area-7:before{opacity:.58}.handsontable tbody th.ht__highlight,.handsontable thead th.ht__highlight{background-color:#dcdcdc}.handsontable tbody th.ht__active_highlight,.handsontable thead th.ht__active_highlight{background-color:#8eb0e7;color:#000}.handsontable .wtBorder.corner{font-size:0;cursor:crosshair}.handsontable .htBorder.htFillBorder{background:red;width:1px;height:1px}.handsontableInput{border:none;outline-width:0;margin:0;padding:1px 5px 0;font-family:inherit;line-height:21px;font-size:inherit;box-shadow:inset 0 0 0 2px #5292f7;resize:none;display:block;color:#000;border-radius:0;background-color:#fff}.handsontableInputHolder{position:absolute;top:0;left:0;z-index:104}.htSelectEditor{-webkit-appearance:menulist-button!important;position:absolute;width:auto}.handsontable .htDimmed{color:#777}.handsontable .htSubmenu{position:relative}.handsontable .htSubmenu :after{content:"\25B6";color:#777;position:absolute;right:5px;font-size:9px}.handsontable .htLeft{text-align:left}.handsontable .htCenter{text-align:center}.handsontable .htRight{text-align:right}.handsontable .htJustify{text-align:justify}.handsontable .htTop{vertical-align:top}.handsontable .htMiddle{vertical-align:middle}.handsontable .htBottom{vertical-align:bottom}.handsontable .htPlaceholder{color:#999}.handsontable .htAutocompleteArrow{float:right;font-size:10px;color:#eee;cursor:default;width:16px;text-align:center}.handsontable td .htAutocompleteArrow:hover{color:#777}.handsontable td.area .htAutocompleteArrow{color:#d3d3d3}.handsontable .htCheckboxRendererInput{display:inline-block;vertical-align:middle}.handsontable .htCheckboxRendererInput.noValue{opacity:.5}.handsontable .htCheckboxRendererLabel{cursor:pointer;display:inline-block;width:100%}.handsontable .handsontable.ht_clone_top .wtHider{padding:0 0 5px}.handsontable .autocompleteEditor.handsontable{padding-right:17px}.handsontable .autocompleteEditor.handsontable.htMacScroll{padding-right:15px}.handsontable.listbox{margin:0}.handsontable.listbox .ht_master table{border:1px solid #ccc;border-collapse:separate;background:#fff}.handsontable.listbox td,.handsontable.listbox th,.handsontable.listbox tr:first-child td,.handsontable.listbox tr:first-child th,.handsontable.listbox tr:last-child th{border-color:transparent}.handsontable.listbox td,.handsontable.listbox th{white-space:nowrap;text-overflow:ellipsis}.handsontable.listbox td.htDimmed{cursor:default;color:inherit;font-style:inherit}.handsontable.listbox .wtBorder{visibility:hidden}.handsontable.listbox tr:hover td,.handsontable.listbox tr td.current{background:#eee}.ht_clone_top{z-index:101}.ht_clone_left{z-index:102}.ht_clone_bottom_left_corner,.ht_clone_debug,.ht_clone_top_left_corner{z-index:103}.handsontable td.htSearchResult{background:#fcedd9;color:#583707}.htBordered{border-width:1px}.htBordered.htTopBorderSolid{border-top-style:solid;border-top-color:#000}.htBordered.htRightBorderSolid{border-right-style:solid;border-right-color:#000}.htBordered.htBottomBorderSolid{border-bottom-style:solid;border-bottom-color:#000}.htBordered.htLeftBorderSolid{border-left-style:solid;border-left-color:#000}.handsontable tbody tr th:nth-last-child(2){border-right:1px solid #ccc}.handsontable thead tr:nth-last-child(2) th.htGroupIndicatorContainer{border-bottom:1px solid #ccc;padding-bottom:5px}.ht_clone_top_left_corner thead tr th:nth-last-child(2){border-right:1px solid #ccc}.htCollapseButton{width:10px;height:10px;line-height:10px;text-align:center;border-radius:5px;border:1px solid #f3f3f3;box-shadow:1px 1px 3px rgba(0,0,0,.4);cursor:pointer;margin-bottom:3px;position:relative}.htCollapseButton:after{content:"";height:300%;width:1px;display:block;background:#ccc;margin-left:4px;position:absolute;bottom:10px}thead .htCollapseButton{right:5px;position:absolute;top:5px;background:#fff}thead .htCollapseButton:after{height:1px;width:700%;right:10px;top:4px}.handsontable tr th .htExpandButton{position:absolute;width:10px;height:10px;line-height:10px;text-align:center;border-radius:5px;border:1px solid #f3f3f3;box-shadow:1px 1px 3px rgba(0,0,0,.4);cursor:pointer;top:0;display:none}.handsontable thead tr th .htExpandButton{top:5px}.handsontable tr th .htExpandButton.clickable{display:block}.collapsibleIndicator{position:absolute;top:50%;transform:translateY(-50%);right:5px;border:1px solid #a6a6a6;line-height:10px;color:#222;border-radius:10px;font-size:10px;width:10px;height:10px;cursor:pointer;box-shadow:0 0 0 6px #eee;background:#eee}.handsontable col.hidden{width:0!important}.handsontable table tr th.lightRightBorder{border-right:1px solid #e6e6e6}.handsontable tr.hidden,.handsontable tr.hidden td,.handsontable tr.hidden th{display:none}.ht_clone_bottom,.ht_clone_left,.ht_clone_top,.ht_master{overflow:hidden}.ht_master .wtHolder{overflow:auto}.handsontable .ht_clone_left thead,.handsontable .ht_master thead,.handsontable .ht_master tr th{visibility:hidden}.ht_clone_bottom .wtHolder,.ht_clone_left .wtHolder,.ht_clone_top .wtHolder{overflow:hidden}.handsontable.mobile,.handsontable.mobile .wtHolder{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-overflow-scrolling:touch}.htMobileEditorContainer{display:none;position:absolute;top:0;width:70%;height:54pt;background:#f8f8f8;border-radius:20px;border:1px solid #ebebeb;z-index:999;box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-text-size-adjust:none}.topLeftSelectionHandle-HitArea:not(.ht_master .topLeftSelectionHandle-HitArea),.topLeftSelectionHandle:not(.ht_master .topLeftSelectionHandle){z-index:9999}.bottomRightSelectionHandle,.bottomRightSelectionHandle-HitArea,.topLeftSelectionHandle,.topLeftSelectionHandle-HitArea{left:-10000px;top:-10000px}.htMobileEditorContainer.active{display:block}.htMobileEditorContainer .inputs{position:absolute;right:210pt;bottom:10pt;top:10pt;left:14px;height:34pt}.htMobileEditorContainer .inputs textarea{font-size:13pt;border:1px solid #a1a1a1;-webkit-appearance:none;box-shadow:none;position:absolute;left:14px;right:14px;top:0;bottom:0;padding:7pt}.htMobileEditorContainer .cellPointer{position:absolute;top:-13pt;height:0;width:0;left:30px;border-left:13pt solid transparent;border-right:13pt solid transparent;border-bottom:13pt solid #ebebeb}.htMobileEditorContainer .cellPointer.hidden{display:none}.htMobileEditorContainer .cellPointer:before{content:"";display:block;position:absolute;top:2px;height:0;width:0;left:-13pt;border-left:13pt solid transparent;border-right:13pt solid transparent;border-bottom:13pt solid #f8f8f8}.htMobileEditorContainer .moveHandle{position:absolute;top:10pt;left:5px;width:30px;bottom:0;cursor:move;z-index:9999}.htMobileEditorContainer .moveHandle:after{content:"..\A..\A..\A..";white-space:pre;line-height:10px;font-size:20pt;display:inline-block;margin-top:-8px;color:#ebebeb}.htMobileEditorContainer .positionControls{width:205pt;position:absolute;right:5pt;top:0;bottom:0}.htMobileEditorContainer .positionControls>div{width:50pt;height:100%;float:left}.htMobileEditorContainer .positionControls>div:after{content:" ";display:block;width:15pt;height:15pt;text-align:center;line-height:50pt}.htMobileEditorContainer .downButton:after,.htMobileEditorContainer .leftButton:after,.htMobileEditorContainer .rightButton:after,.htMobileEditorContainer .upButton:after{transform-origin:5pt 5pt;-webkit-transform-origin:5pt 5pt;margin:21pt 0 0 21pt}.htMobileEditorContainer .leftButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(-45deg)}.htMobileEditorContainer .leftButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .rightButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(135deg)}.htMobileEditorContainer .rightButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .upButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(45deg)}.htMobileEditorContainer .upButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .downButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(225deg)}.htMobileEditorContainer .downButton:active:after{border-color:#cfcfcf}.handsontable.hide-tween{animation:opacity-hide .3s;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.handsontable.show-tween{animation:opacity-show .3s;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards} 30 | 31 | /*! 32 | * Pikaday 33 | * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/ 34 | */.pika-single{z-index:9999;display:block;position:relative;color:#333;background:#fff;border:1px solid #ccc;border-bottom-color:#bbb;font-family:Helvetica Neue,Helvetica,Arial,sans-serif}.pika-single:after,.pika-single:before{content:" ";display:table}.pika-single:after{clear:both}.pika-single{*zoom:1}.pika-single.is-hidden{display:none}.pika-single.is-bound{position:absolute;box-shadow:0 5px 15px -5px rgba(0,0,0,.5)}.pika-lendar{float:left;width:240px;margin:8px}.pika-title{position:relative;text-align:center}.pika-label{display:inline-block;*display:inline;position:relative;z-index:9999;overflow:hidden;margin:0;padding:5px 3px;font-size:14px;line-height:20px;font-weight:700;background-color:#fff}.pika-title select{cursor:pointer;position:absolute;z-index:9998;margin:0;left:0;top:5px;filter:alpha(opacity=0);opacity:0}.pika-next,.pika-prev{display:block;cursor:pointer;position:relative;outline:none;border:0;padding:0;width:20px;height:30px;text-indent:20px;white-space:nowrap;overflow:hidden;background-color:transparent;background-position:50%;background-repeat:no-repeat;background-size:75% 75%;opacity:.5;*position:absolute;*top:0}.pika-next:hover,.pika-prev:hover{opacity:1}.is-rtl .pika-next,.pika-prev{float:left;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==");*left:0}.is-rtl .pika-prev,.pika-next{float:right;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=");*right:0}.pika-next.is-disabled,.pika-prev.is-disabled{cursor:default;opacity:.2}.pika-select{display:inline-block;*display:inline}.pika-table{width:100%;border-collapse:collapse;border-spacing:0;border:0}.pika-table td,.pika-table th{width:14.285714285714286%;padding:0}.pika-table th{color:#999;font-size:12px;line-height:25px;font-weight:700;text-align:center}.pika-button{cursor:pointer;display:block;box-sizing:border-box;-moz-box-sizing:border-box;outline:none;border:0;margin:0;width:100%;padding:5px;color:#666;font-size:12px;line-height:15px;text-align:right;background:#f5f5f5}.pika-week{font-size:11px;color:#999}.is-today .pika-button{color:#3af;font-weight:700}.is-selected .pika-button{color:#fff;font-weight:700;background:#3af;box-shadow:inset 0 1px 3px #178fe5;border-radius:3px}.is-inrange .pika-button{background:#d5e9f7}.is-startrange .pika-button{color:#fff;background:#6cb31d;box-shadow:none;border-radius:3px}.is-endrange .pika-button{color:#fff;background:#3af;box-shadow:none;border-radius:3px}.is-disabled .pika-button,.is-outside-current-month .pika-button{pointer-events:none;cursor:default;color:#999;opacity:.3}.pika-button:hover{color:#fff;background:#ff8000;box-shadow:none;border-radius:3px}.pika-table abbr{border-bottom:none;cursor:help}.htCommentCell{position:relative}.htCommentCell:after{content:"";position:absolute;top:0;right:0;border-left:6px solid transparent;border-top:6px solid #000}.htComments{display:none;z-index:1059;position:absolute}.htCommentTextArea{box-shadow:0 1px 3px rgba(0,0,0,.117647),0 1px 2px rgba(0,0,0,.239216);box-sizing:border-box;border:none;border-left:3px solid #ccc;background-color:#fff;width:215px;height:90px;font-size:12px;padding:5px;outline:0!important;-webkit-appearance:none}.htCommentTextArea:focus{box-shadow:0 1px 3px rgba(0,0,0,.117647),0 1px 2px rgba(0,0,0,.239216),inset 0 0 0 1px #5292f7;border-left:3px solid #5292f7} 35 | /*! 36 | * Handsontable ContextMenu 37 | */.htContextMenu:not(.htGhostTable){display:none;position:absolute;z-index:1060}.htContextMenu .ht_clone_corner,.htContextMenu .ht_clone_debug,.htContextMenu .ht_clone_left,.htContextMenu .ht_clone_top{display:none}.htContextMenu table.htCore{border:1px solid #ccc;border-bottom-width:2px;border-right-width:2px}.htContextMenu .wtBorder{visibility:hidden}.htContextMenu table tbody tr td{background:#fff;border-width:0;padding:4px 6px 0;cursor:pointer;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.htContextMenu table tbody tr td:first-child{border:0}.htContextMenu table tbody tr td.htDimmed{font-style:normal;color:#323232}.htContextMenu table tbody tr td.current,.htContextMenu table tbody tr td.zeroclipboard-is-hover{background:#f3f3f3}.htContextMenu table tbody tr td.htSeparator{border-top:1px solid #e6e6e6;height:0;padding:0;cursor:default}.htContextMenu table tbody tr td.htDisabled{color:#999;cursor:default}.htContextMenu table tbody tr td.htDisabled:hover{background:#fff;color:#999;cursor:default}.htContextMenu table tbody tr.htHidden{display:none}.htContextMenu table tbody tr td .htItemWrapper{margin-left:10px;margin-right:6px}.htContextMenu table tbody tr td div span.selected{margin-top:-2px;position:absolute;left:4px}.htContextMenu .ht_master .wtHolder{overflow:hidden}textarea#HandsontableCopyPaste{position:fixed!important;top:0!important;right:100%!important;overflow:hidden;opacity:0;outline:0 none!important}.htRowHeaders .ht_master.innerBorderLeft~.ht_clone_left td:first-of-type,.htRowHeaders .ht_master.innerBorderLeft~.ht_clone_top_left_corner th:nth-child(2){border-left:0 none}.handsontable.ht__manualColumnMove.after-selection--columns thead th.ht__highlight{cursor:move;cursor:-webkit-grab;cursor:grab}.handsontable.ht__manualColumnMove.on-moving--columns,.handsontable.ht__manualColumnMove.on-moving--columns thead th.ht__highlight{cursor:move;cursor:-webkit-grabbing;cursor:grabbing}.handsontable.ht__manualColumnMove.on-moving--columns .manualColumnResizer{display:none}.handsontable .ht__manualColumnMove--backlight,.handsontable .ht__manualColumnMove--guideline{position:absolute;height:100%;display:none}.handsontable .ht__manualColumnMove--guideline{background:#757575;width:2px;top:0;margin-left:-1px;z-index:105}.handsontable .ht__manualColumnMove--backlight{background:#343434;background:rgba(52,52,52,.25);display:none;z-index:105;pointer-events:none}.handsontable.on-moving--columns .ht__manualColumnMove--backlight,.handsontable.on-moving--columns.show-ui .ht__manualColumnMove--guideline{display:block}.handsontable .wtHider{position:relative}.handsontable.ht__manualRowMove.after-selection--rows tbody th.ht__highlight{cursor:move;cursor:-webkit-grab;cursor:grab}.handsontable.ht__manualRowMove.on-moving--rows,.handsontable.ht__manualRowMove.on-moving--rows tbody th.ht__highlight{cursor:move;cursor:-webkit-grabbing;cursor:grabbing}.handsontable.ht__manualRowMove.on-moving--rows .manualRowResizer{display:none}.handsontable .ht__manualRowMove--backlight,.handsontable .ht__manualRowMove--guideline{position:absolute;width:100%;display:none}.handsontable .ht__manualRowMove--guideline{background:#757575;height:2px;left:0;margin-top:-1px;z-index:105}.handsontable .ht__manualRowMove--backlight{background:#343434;background:rgba(52,52,52,.25);display:none;z-index:105;pointer-events:none}.handsontable.on-moving--rows .ht__manualRowMove--backlight,.handsontable.on-moving--rows.show-ui .ht__manualRowMove--guideline{display:block}.handsontable tbody td[rowspan][class*=area][class*=highlight]:not([class*=fullySelectedMergedCell]):before{opacity:0}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-0]:before,.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-multiple]:before{opacity:.1}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-1]:before{opacity:.2}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-2]:before{opacity:.27}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-3]:before{opacity:.35}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-4]:before{opacity:.41}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-5]:before{opacity:.47}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-6]:before{opacity:.54}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-7]:before{opacity:.58} -------------------------------------------------------------------------------- /pyhandsontable/templates/sheet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 30 | 31 | {{ title }} 32 | 33 | 34 |
35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /pyhandsontable/templates/sheet.js: -------------------------------------------------------------------------------- 1 | function whatIsIt(object) { 2 | var stringConstructor = "test".constructor; 3 | var arrayConstructor = [].constructor; 4 | var objectConstructor = {}.constructor; 5 | 6 | if (object === null) { 7 | return "null"; 8 | } 9 | else if (object === undefined) { 10 | return "undefined"; 11 | } 12 | else if (object.constructor === stringConstructor) { 13 | return "String"; 14 | } 15 | else if (object.constructor === arrayConstructor) { 16 | return "Array"; 17 | } 18 | else if (object.constructor === objectConstructor) { 19 | return "Object"; 20 | } 21 | else { 22 | return "don't know"; 23 | } 24 | } 25 | 26 | function escapeHTML(unsafeText) { 27 | let div = document.createElement('div'); 28 | div.innerText = unsafeText; 29 | return div.innerHTML; 30 | } 31 | 32 | (function(Handsontable){ 33 | function customRenderer(hotInstance, td, row, column, prop, value, cellProperties) { 34 | let text; 35 | switch(whatIsIt(value)){ 36 | case "Object": 37 | text = '
' + JSON.stringify(value, null, 2) + '
'; 38 | break; 39 | case "Array": 40 | text = '
' + JSON.stringify(value) + '
'; 41 | break; 42 | default: 43 | text = '
' + escapeHTML(Handsontable.helper.stringify(value)) + '
'; 44 | } 45 | 46 | td.innerHTML = text; 47 | return td; 48 | } 49 | 50 | Handsontable.renderers.registerRenderer('jsonRenderer', customRenderer); 51 | })(Handsontable); 52 | 53 | var container = document.getElementById('hotArea'); 54 | var actualConfig = { 55 | data: data, 56 | rowHeaders: true, 57 | colHeaders: true, 58 | columns: columns, 59 | manualColumnResize: true, 60 | manualRowResize: true, 61 | renderAllRows: true, 62 | modifyColWidth: (width, col)=>{ 63 | if(width > maxColWidth) return maxColWidth; 64 | }, 65 | afterRenderer: (td, row, column, prop, value, cellProperties)=>{ 66 | td.innerHTML = '
' + td.innerHTML + '
'; 67 | } 68 | } 69 | Object.assign(actualConfig, config); 70 | actualConfig.colWidths = undefined; 71 | 72 | var hot = new Handsontable(container, actualConfig); 73 | 74 | let colWidths = []; 75 | [...Array(hot.countCols()).keys()].map(i => { 76 | colWidths.push(hot.getColWidth(i)); 77 | }); 78 | 79 | switch(whatIsIt(config.colWidths)){ 80 | case "Array": 81 | config.colWidths.forEach((item, index)=>{ 82 | colWidths[index] = item; 83 | }); 84 | break; 85 | case "Object": 86 | const colHeaders = hot.getColHeader(); 87 | Object.keys(config.colWidths).forEach((item, index)=>{ 88 | colWidths[colHeaders.indexOf(item)] = config.colWidths[item]; 89 | }); 90 | break; 91 | default: 92 | } 93 | 94 | hot.updateSettings({ 95 | colWidths: colWidths, 96 | modifyColWidth: ()=>{} 97 | }); -------------------------------------------------------------------------------- /pyproject.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "main" 3 | description = "Disable App Nap on OS X 10.9" 4 | name = "appnope" 5 | optional = false 6 | platform = "UNKNOWN" 7 | python-versions = "*" 8 | version = "0.1.0" 9 | 10 | [package.requirements] 11 | platform = "darwin" 12 | 13 | [[package]] 14 | category = "dev" 15 | description = "Atomic file writes." 16 | name = "atomicwrites" 17 | optional = false 18 | platform = "UNKNOWN" 19 | python-versions = "*" 20 | version = "1.1.5" 21 | 22 | [[package]] 23 | category = "dev" 24 | description = "Classes Without Boilerplate" 25 | name = "attrs" 26 | optional = false 27 | platform = "*" 28 | python-versions = "*" 29 | version = "18.1.0" 30 | 31 | [[package]] 32 | category = "main" 33 | description = "Specifications for callback functions passed in to an API" 34 | name = "backcall" 35 | optional = false 36 | platform = "UNKNOWN" 37 | python-versions = "*" 38 | version = "0.1.0" 39 | 40 | [[package]] 41 | category = "main" 42 | description = "An easy safelist-based HTML-sanitizing tool." 43 | name = "bleach" 44 | optional = false 45 | platform = "*" 46 | python-versions = "*" 47 | version = "2.1.3" 48 | 49 | [package.dependencies] 50 | html5lib = ">=0.99999999pre,<1.0b1 || >1.0b1,<1.0b2 || >1.0b2,<1.0b3 || >1.0b3,<1.0b4 || >1.0b4,<1.0b5 || >1.0b5,<1.0b6 || >1.0b6,<1.0b7 || >1.0b7,<1.0b8 || >1.0b8" 51 | six = "*" 52 | 53 | [[package]] 54 | category = "main" 55 | description = "Cross-platform colored terminal text." 56 | name = "colorama" 57 | optional = false 58 | platform = "UNKNOWN" 59 | python-versions = "*" 60 | version = "0.3.9" 61 | 62 | [package.requirements] 63 | platform = "win32" 64 | 65 | [[package]] 66 | category = "main" 67 | description = "Better living through Python with decorators" 68 | name = "decorator" 69 | optional = false 70 | platform = "All" 71 | python-versions = "*" 72 | version = "4.3.0" 73 | 74 | [[package]] 75 | category = "main" 76 | description = "Discover and load entry points from installed packages." 77 | name = "entrypoints" 78 | optional = false 79 | platform = "*" 80 | python-versions = ">=2.7" 81 | version = "0.2.3" 82 | 83 | [[package]] 84 | category = "main" 85 | description = "HTML parser based on the WHATWG HTML specification" 86 | name = "html5lib" 87 | optional = false 88 | platform = "*" 89 | python-versions = "*" 90 | version = "1.0.1" 91 | 92 | [package.dependencies] 93 | six = ">=1.9" 94 | webencodings = "*" 95 | 96 | [[package]] 97 | category = "main" 98 | description = "IPython Kernel for Jupyter" 99 | name = "ipykernel" 100 | optional = false 101 | platform = "Linux" 102 | python-versions = "*" 103 | version = "4.8.2" 104 | 105 | [package.dependencies] 106 | ipython = ">=4.0.0" 107 | jupyter-client = "*" 108 | tornado = ">=4.0" 109 | traitlets = ">=4.1.0" 110 | 111 | [[package]] 112 | category = "main" 113 | description = "IPython: Productive Interactive Computing" 114 | name = "ipython" 115 | optional = false 116 | platform = "Linux" 117 | python-versions = ">=3.3" 118 | version = "6.4.0" 119 | 120 | [package.dependencies] 121 | backcall = "*" 122 | decorator = "*" 123 | jedi = ">=0.10" 124 | pickleshare = "*" 125 | prompt-toolkit = ">=1.0.15,<2.0.0" 126 | pygments = "*" 127 | setuptools = ">=18.5" 128 | simplegeneric = ">0.8" 129 | traitlets = ">=4.2" 130 | 131 | [package.dependencies.appnope] 132 | platform = "darwin" 133 | version = "*" 134 | 135 | [package.dependencies.colorama] 136 | platform = "win32" 137 | version = "*" 138 | 139 | [package.dependencies.pexpect] 140 | platform = "!=win32" 141 | version = "*" 142 | 143 | [package.dependencies.win-unicode-console] 144 | platform = "win32" 145 | python = "<3.6" 146 | version = ">=0.5" 147 | 148 | [[package]] 149 | category = "main" 150 | description = "Vestigial utilities from IPython" 151 | name = "ipython-genutils" 152 | optional = false 153 | platform = "Linux" 154 | python-versions = "*" 155 | version = "0.2.0" 156 | 157 | [[package]] 158 | category = "main" 159 | description = "An autocompletion tool for Python that can be used for text editors." 160 | name = "jedi" 161 | optional = false 162 | platform = "any" 163 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 164 | version = "0.12.1" 165 | 166 | [package.dependencies] 167 | parso = ">=0.3.0" 168 | 169 | [[package]] 170 | category = "main" 171 | description = "A small but fast and easy to use stand-alone template engine written in pure python." 172 | name = "jinja2" 173 | optional = false 174 | platform = "*" 175 | python-versions = "*" 176 | version = "2.10" 177 | 178 | [package.dependencies] 179 | MarkupSafe = ">=0.23" 180 | 181 | [[package]] 182 | category = "main" 183 | description = "An implementation of JSON Schema validation for Python" 184 | name = "jsonschema" 185 | optional = false 186 | platform = "*" 187 | python-versions = "*" 188 | version = "2.6.0" 189 | 190 | [[package]] 191 | category = "main" 192 | description = "Jupyter metapackage. Install all the Jupyter components in one go." 193 | name = "jupyter" 194 | optional = false 195 | platform = "UNKNOWN" 196 | python-versions = "*" 197 | version = "1.0.0" 198 | 199 | [[package]] 200 | category = "main" 201 | description = "Jupyter protocol implementation and client libraries" 202 | name = "jupyter-client" 203 | optional = false 204 | platform = "Linux" 205 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 206 | version = "5.2.3" 207 | 208 | [package.dependencies] 209 | jupyter-core = "*" 210 | python-dateutil = ">=2.1" 211 | pyzmq = ">=13" 212 | tornado = ">=4.1" 213 | traitlets = "*" 214 | 215 | [[package]] 216 | category = "main" 217 | description = "Jupyter core package. A base package on which Jupyter projects rely." 218 | name = "jupyter-core" 219 | optional = false 220 | platform = "*" 221 | python-versions = "*" 222 | version = "4.4.0" 223 | 224 | [package.dependencies] 225 | traitlets = "*" 226 | 227 | [[package]] 228 | category = "main" 229 | description = "Implements a XML/HTML/XHTML Markup safe string for Python" 230 | name = "markupsafe" 231 | optional = false 232 | platform = "UNKNOWN" 233 | python-versions = "*" 234 | version = "1.0" 235 | 236 | [[package]] 237 | category = "main" 238 | description = "The fastest markdown parser in pure Python" 239 | name = "mistune" 240 | optional = false 241 | platform = "any" 242 | python-versions = "*" 243 | version = "0.8.3" 244 | 245 | [[package]] 246 | category = "dev" 247 | description = "More routines for operating on iterables, beyond itertools" 248 | name = "more-itertools" 249 | optional = false 250 | platform = "*" 251 | python-versions = "*" 252 | version = "4.2.0" 253 | 254 | [package.dependencies] 255 | six = ">=1.0.0,<2.0.0" 256 | 257 | [[package]] 258 | category = "main" 259 | description = "Converting Jupyter Notebooks" 260 | name = "nbconvert" 261 | optional = false 262 | platform = "Linux" 263 | python-versions = "*" 264 | version = "5.3.1" 265 | 266 | [package.dependencies] 267 | bleach = "*" 268 | entrypoints = ">=0.2.2" 269 | jinja2 = "*" 270 | jupyter-core = "*" 271 | mistune = ">=0.7.4" 272 | nbformat = ">=4.4" 273 | pandocfilters = ">=1.4.1" 274 | pygments = "*" 275 | testpath = "*" 276 | traitlets = ">=4.2" 277 | 278 | [[package]] 279 | category = "main" 280 | description = "The Jupyter Notebook format" 281 | name = "nbformat" 282 | optional = false 283 | platform = "Linux" 284 | python-versions = "*" 285 | version = "4.4.0" 286 | 287 | [package.dependencies] 288 | ipython-genutils = "*" 289 | jsonschema = ">=2.4,<2.5.0 || >2.5.0" 290 | jupyter-core = "*" 291 | traitlets = ">=4.1" 292 | 293 | [[package]] 294 | category = "main" 295 | description = "A web-based notebook environment for interactive computing" 296 | name = "notebook" 297 | optional = false 298 | platform = "Linux" 299 | python-versions = "*" 300 | version = "5.6.0" 301 | 302 | [package.dependencies] 303 | Send2Trash = "*" 304 | ipykernel = "*" 305 | ipython-genutils = "*" 306 | jinja2 = "*" 307 | jupyter-client = ">=5.2.0" 308 | jupyter-core = ">=4.4.0" 309 | nbconvert = "*" 310 | nbformat = "*" 311 | prometheus-client = "*" 312 | pyzmq = ">=17" 313 | terminado = ">=0.8.1" 314 | tornado = ">=4" 315 | traitlets = ">=4.2.1" 316 | 317 | [[package]] 318 | category = "main" 319 | description = "Utilities for writing pandoc filters in python" 320 | name = "pandocfilters" 321 | optional = false 322 | platform = "*" 323 | python-versions = "*" 324 | version = "1.4.2" 325 | 326 | [[package]] 327 | category = "main" 328 | description = "A Python Parser" 329 | name = "parso" 330 | optional = false 331 | platform = "any" 332 | python-versions = "*" 333 | version = "0.3.1" 334 | 335 | [[package]] 336 | category = "main" 337 | description = "Pexpect allows easy control of interactive console applications." 338 | name = "pexpect" 339 | optional = false 340 | platform = "UNIX" 341 | python-versions = "*" 342 | version = "4.6.0" 343 | 344 | [package.dependencies] 345 | ptyprocess = ">=0.5" 346 | 347 | [package.requirements] 348 | platform = "!=win32" 349 | 350 | [[package]] 351 | category = "main" 352 | description = "Tiny 'shelve'-like database with concurrency support" 353 | name = "pickleshare" 354 | optional = false 355 | platform = "*" 356 | python-versions = "*" 357 | version = "0.7.4" 358 | 359 | [[package]] 360 | category = "dev" 361 | description = "plugin and hook calling mechanisms for python" 362 | name = "pluggy" 363 | optional = false 364 | platform = "unix" 365 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 366 | version = "0.6.0" 367 | 368 | [[package]] 369 | category = "main" 370 | description = "Python client for the Prometheus monitoring system." 371 | name = "prometheus-client" 372 | optional = false 373 | platform = "*" 374 | python-versions = "*" 375 | version = "0.3.0" 376 | 377 | [[package]] 378 | category = "main" 379 | description = "Library for building powerful interactive command lines in Python" 380 | name = "prompt-toolkit" 381 | optional = false 382 | platform = "*" 383 | python-versions = "*" 384 | version = "1.0.15" 385 | 386 | [package.dependencies] 387 | six = ">=1.9.0" 388 | wcwidth = "*" 389 | 390 | [[package]] 391 | category = "main" 392 | description = "Run a subprocess in a pseudo terminal" 393 | name = "ptyprocess" 394 | optional = false 395 | platform = "*" 396 | python-versions = "*" 397 | version = "0.6.0" 398 | 399 | [[package]] 400 | category = "dev" 401 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 402 | name = "py" 403 | optional = false 404 | platform = "unix" 405 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 406 | version = "1.5.4" 407 | 408 | [[package]] 409 | category = "main" 410 | description = "Pygments is a syntax highlighting package written in Python." 411 | name = "pygments" 412 | optional = false 413 | platform = "any" 414 | python-versions = "*" 415 | version = "2.2.0" 416 | 417 | [[package]] 418 | category = "dev" 419 | description = "pytest: simple powerful testing with Python" 420 | name = "pytest" 421 | optional = false 422 | platform = "unix" 423 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 424 | version = "3.6.3" 425 | 426 | [package.dependencies] 427 | atomicwrites = ">=1.0" 428 | attrs = ">=17.4.0" 429 | more-itertools = ">=4.0.0" 430 | pluggy = ">=0.5,<0.7" 431 | py = ">=1.5.0" 432 | setuptools = "*" 433 | six = ">=1.10.0" 434 | 435 | [package.dependencies.colorama] 436 | platform = "win32" 437 | version = "*" 438 | 439 | [[package]] 440 | category = "main" 441 | description = "Extensions to the standard Python datetime module" 442 | name = "python-dateutil" 443 | optional = false 444 | platform = "*" 445 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 446 | version = "2.7.3" 447 | 448 | [package.dependencies] 449 | six = ">=1.5" 450 | 451 | [[package]] 452 | category = "main" 453 | description = "Python bindings for the winpty library" 454 | name = "pywinpty" 455 | optional = false 456 | platform = "*" 457 | python-versions = "*" 458 | version = "0.5.4" 459 | 460 | [[package]] 461 | category = "main" 462 | description = "Python bindings for 0MQ" 463 | name = "pyzmq" 464 | optional = false 465 | platform = "*" 466 | python-versions = ">=2.7,!=3.0*,!=3.1*,!=3.2*" 467 | version = "17.1.0" 468 | 469 | [[package]] 470 | category = "main" 471 | description = "Send file to trash natively under Mac OS X, Windows and Linux." 472 | name = "send2trash" 473 | optional = false 474 | platform = "*" 475 | python-versions = "*" 476 | version = "1.5.0" 477 | 478 | [[package]] 479 | category = "main" 480 | description = "Simple generic functions (similar to Python's own len(), pickle.dump(), etc.)" 481 | name = "simplegeneric" 482 | optional = false 483 | platform = "UNKNOWN" 484 | python-versions = "*" 485 | version = "0.8.1" 486 | 487 | [[package]] 488 | category = "main" 489 | description = "Python 2 and 3 compatibility utilities" 490 | name = "six" 491 | optional = false 492 | platform = "*" 493 | python-versions = "*" 494 | version = "1.11.0" 495 | 496 | [[package]] 497 | category = "main" 498 | description = "Terminals served to xterm.js using Tornado websockets" 499 | name = "terminado" 500 | optional = false 501 | platform = "*" 502 | python-versions = "*" 503 | version = "0.8.1" 504 | 505 | [package.dependencies] 506 | ptyprocess = "*" 507 | pywinpty = ">=0.5" 508 | tornado = ">=4" 509 | 510 | [[package]] 511 | category = "main" 512 | description = "Test utilities for code working with files and commands" 513 | name = "testpath" 514 | optional = false 515 | platform = "*" 516 | python-versions = "*" 517 | version = "0.3.1" 518 | 519 | [[package]] 520 | category = "main" 521 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 522 | name = "tornado" 523 | optional = false 524 | platform = "*" 525 | python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*" 526 | version = "5.1" 527 | 528 | [[package]] 529 | category = "main" 530 | description = "Traitlets Python config system" 531 | name = "traitlets" 532 | optional = false 533 | platform = "Linux,Mac OS X,Windows" 534 | python-versions = "*" 535 | version = "4.3.2" 536 | 537 | [package.dependencies] 538 | decorator = "*" 539 | ipython-genutils = "*" 540 | six = "*" 541 | 542 | [[package]] 543 | category = "main" 544 | description = "Measures number of Terminal column cells of wide-character codes" 545 | name = "wcwidth" 546 | optional = false 547 | platform = "UNKNOWN" 548 | python-versions = "*" 549 | version = "0.1.7" 550 | 551 | [[package]] 552 | category = "main" 553 | description = "Character encoding aliases for legacy web content" 554 | name = "webencodings" 555 | optional = false 556 | platform = "*" 557 | python-versions = "*" 558 | version = "0.5.1" 559 | 560 | [[package]] 561 | category = "main" 562 | description = "Enable Unicode input and display when running Python from Windows console." 563 | name = "win-unicode-console" 564 | optional = false 565 | platform = "UNKNOWN" 566 | python-versions = "*" 567 | version = "0.5" 568 | 569 | [package.requirements] 570 | platform = "win32" 571 | python = "<3.6" 572 | 573 | [metadata] 574 | content-hash = "160320ef4bea25f480846c0c0cdc0023c1403000a5e931df647ccf2cb44e2953" 575 | platform = "*" 576 | python-versions = ">=3.5" 577 | 578 | [metadata.hashes] 579 | appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] 580 | atomicwrites = ["240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", "a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"] 581 | attrs = ["4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", "e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"] 582 | backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"] 583 | bleach = ["b8fa79e91f96c2c2cd9fd1f9eda906efb1b88b483048978ba62fef680e962b34", "eb7386f632349d10d9ce9d4a838b134d4731571851149f9cc2c05a9a837a9a44"] 584 | colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"] 585 | decorator = ["2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", "c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"] 586 | entrypoints = ["10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b", "d2d587dde06f99545fb13a383d2cd336a8ff1f359c5839ce3a64c917d10c029f"] 587 | html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"] 588 | ipykernel = ["395f020610e33ffa0b0c9c0cd1a1d927d51ab9aa9f30a7ae36bb0c908a33e89c", "935941dba29d856eee34b8b5261d971bd5012547239ed73ddfff099143748c37", "c091449dd0fad7710ddd9c4a06e8b9e15277da306590bc07a3a1afa6b4453c8f"] 589 | ipython = ["a0c96853549b246991046f32d19db7140f5b1a644cc31f0dc1edc86713b7676f", "eca537aa61592aca2fef4adea12af8e42f5c335004dfa80c78caf80e8b525e5c"] 590 | ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"] 591 | jedi = ["b409ed0f6913a701ed474a614a3bb46e6953639033e31f769ca7581da5bd1ec1", "c254b135fb39ad76e78d4d8f92765ebc9bf92cbc76f49e97ade1d5f5121e1f6f"] 592 | jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"] 593 | jsonschema = ["000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", "6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"] 594 | jupyter = ["3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", "5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", "d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"] 595 | jupyter-client = ["27befcf0446b01e29853014d6a902dd101ad7d7f94e2252b1adca17c3466b761", "59e6d791e22a8002ad0e80b78c6fd6deecab4f9e1b1aa1a22f4213de271b29ea"] 596 | jupyter-core = ["927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970", "ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7"] 597 | markupsafe = ["a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"] 598 | mistune = ["b4c512ce2fc99e5a62eb95a4aba4b73e5f90264115c40b70a21e1f7d4e0eac91", "bc10c33bfdcaa4e749b779f62f60d6e12f8215c46a292d05e486b869ae306619"] 599 | more-itertools = ["2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8", "6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3", "a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"] 600 | nbconvert = ["12b1a4671d4463ab73af6e4cbcc965b62254e05d182cd54995dda0d0ef9e2db9", "260d390b989a647575b8ecae2cd06a9eaead10d396733d6e50185d5ebd08996e"] 601 | nbformat = ["b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", "f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"] 602 | notebook = ["66dd59e76e755584ae9450eb015c39f55d4bb1d8ec68f2c694d2b3cba7bf5c7e", "e2c8e931cc19db4f8c63e6a396efbc13a228b2cb5b2919df011b946f28239a08"] 603 | pandocfilters = ["b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"] 604 | parso = ["35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", "895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"] 605 | pexpect = ["2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", "3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b"] 606 | pickleshare = ["84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", "c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5"] 607 | pluggy = ["7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", "d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", "e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"] 608 | prometheus-client = ["69494dc1ac967c0f626c8193e439755c2b95dd4ed22ef31c277601778a50c7ff"] 609 | prompt-toolkit = ["1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", "3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", "858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917"] 610 | ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"] 611 | py = ["3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", "e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"] 612 | pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"] 613 | pytest = ["0453c8676c2bee6feb0434748b068d5510273a916295fd61d306c4f22fbfd752", "4b208614ae6d98195430ad6bde03641c78553acee7c83cec2e85d613c0cd383d"] 614 | python-dateutil = ["1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", "e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"] 615 | pywinpty = ["349eef36414b038426e65d96ecccfa581c437562cc164fb4faffe6f46963bc80", "4617637c38ae9099a99f73d8dbeb9c752743693bd1dca6ea3b1d520a7248ebf3", "4ee8193b19d77ab59097a000a2c52b36e768e92263812e0c0b40306be8927fb4", "4f6c850db79dd19b1d842d81a8c08fd7efad5e160a1effbba10ba738a5a35cb2", "4fd720b20bb69f1b7ca2060e84503ae843972fcb006ae6e8ddd6ab212fe8911c", "79f2b4584111e36826e587d33eb4e7416a12ae1d6c094cb554e873c5c162fa5f", "87ae1a2301fbce7a3005dac7cdf8ce8a4162f05130348234b87caef260771e96"] 616 | pyzmq = ["096b72e48dc4fb6afac4dc862626b103287176be7c0f49b52782689eac4bf376", "1c0d469837c54df4b3b8424c79f102a2a4ccc7e1e7f8816fcdcf39c1e6406b19", "2199f753a230e26aec5238b0518b036780708a4c887d4944519681a920b9dee4", "22402d2a55dcc5973b1356be12dbc4417350fcc85a6da3af2984271e769a28f4", "4166c2e45b5d43bc1da49a7f8518c2ced11227fb69b35405940442b92b0b0c34", "488ab43a9c4a710264987e7efe5d53384858281bd6540db0a47e7bb6c3bf7ec5", "8d757eaf5eb3b27ab449343e55b8e8f442a3de67ccdfca893cefe8964d44a147", "8e576262221c7fbee894cf105eb975f1385fc91988ce6120cdb2305195e7a9f6", "90b38559b33f544ebaa35fcb8d4091cb35c689a3cfe92e584e36c746f648c12b", "911f457a0b5c0e0c857a71bd2abbb96d15c695a11cf61caa43059143ed01bb6e", "941c9676bc2f3dbab528a4cd42a9dd80310156ed6014276f16288f25c9ff4da2", "965d50febc5402e7fb93c432155463b4184a79bec040cdeb3b006b278b3a544f", "98fe34bbbe5a516e75ac16f1ea585d9c1dda2067b6c9f4da174add6c952883b6", "9dc60c4a8299aa77082493231045d15b58c7875cb9f3dbee6e11a86356bd9e24", "a3d1c56387332063a5492c186fb504a7b6dfbdf6f0ce0a7388fed722998e3b0f", "a9660113ff4c52160f734704af8d351326cc6eabf1dbd135cf02c0ded02e1d9d", "b097f32a1ee00e0b2aa9776e0112a73655484d2e7c0634757c84c036c8be0706", "bcd81ab868e916fc22172667e528211b21e2dcab9af2e9fb3ee631d3eb946893", "bd1c9cbb6d1032a61a8ab0e77b1c83caa7b04e77a6736ff23d213de6bde92622", "cfe8d245bd62b3f87e120029f5a83f47055d3bb2d2b7f91566dd43487dedd6e3", "d74052450985befe7d983abb1daab6112b5cb78f1258731afe04c55dbb504739", "df65629511d6037045b2b4d87b17b0a984d7ae6af44e0d87c350c93e6685d746", "e724a5117044490cf0f55b5025bba4b909de0be4f47f6a48ad7c24d6a8e7e48c", "fafcb5be16c0012ada037b8eaf63d5b25d421680072eeb6df4a2d90599d962e6", "fdced6cf08aaa5b0dacfa4562e518ffb562bb09a90483f920cf6c4ecabd3f379"] 617 | send2trash = ["60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", "f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"] 618 | simplegeneric = ["dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"] 619 | six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"] 620 | terminado = ["55abf9ade563b8f9be1f34e4233c7b7bde726059947a593322e8a553cc4c067a", "65011551baff97f5414c67018e908110693143cfbaeb16831b743fe7cad8b927"] 621 | testpath = ["039fa6a6c9fd3488f8336d23aebbfead5fa602c4a47d49d83845f55a595ec1b4", "0d5337839c788da5900df70f8e01015aec141aa3fe7936cb0d0a2953f7ac7609"] 622 | tornado = ["1c0816fc32b7d31b98781bd8ebc7a9726d7dce67407dc353a2e66e697e138448", "4f66a2172cb947387193ca4c2c3e19131f1c70fa8be470ddbbd9317fd0801582", "5327ba1a6c694e0149e7d9126426b3704b1d9d520852a3e4aa9fc8fe989e4046", "6a7e8657618268bb007646b9eae7661d0b57f13efc94faa33cd2588eae5912c9", "a9b14804783a1d77c0bd6c66f7a9b1196cbddfbdf8bceb64683c5ae60bd1ec6f", "c58757e37c4a3172949c99099d4d5106e4d7b63aa0617f9bb24bfbff712c7866", "d8984742ce86c0855cccecd5c6f54a9f7532c983947cff06f3a0e2115b47f85c"] 623 | traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"] 624 | wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] 625 | webencodings = ["a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", "b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"] 626 | win-unicode-console = ["d4142d4d56d46f449d6f00536a73625a871cba040f0bc1a2e305a04578f07d1e"] 627 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pyhandsontable" 3 | version = "0.3" 4 | description = "View a list of dictionaries or a 2-D array, in HandsOnTable, in Jupyter Notebook." 5 | authors = ["Pacharapol Withayasakpunt "] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/patarapolw/pyhandsontable" 9 | homepage = "https://github.com/patarapolw/pyhandsontable" 10 | keywords = ['handsontable'] 11 | 12 | [tool.poetry.dependencies] 13 | python = ">=3.5" 14 | jinja2 = "^2.10" 15 | jupyter = "^1.0" 16 | notebook = "^5.6" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pytest = "^3.6" 20 | -------------------------------------------------------------------------------- /screenshots/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patarapolw/pyhandsontable/40a6886f3ca1eec9e7b54aebd354da152ab63940/screenshots/0.png -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patarapolw/pyhandsontable/40a6886f3ca1eec9e7b54aebd354da152ab63940/screenshots/1.png -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patarapolw/pyhandsontable/40a6886f3ca1eec9e7b54aebd354da152ab63940/tests/__init__.py -------------------------------------------------------------------------------- /tests/output/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /tests/test_generate.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pathlib import Path 3 | 4 | from pyhandsontable import generate_html 5 | 6 | 7 | @pytest.mark.parametrize('kwargs', [ 8 | {'data': [[1, 2, 3], [4, 5, 6]]}, 9 | {'data': [[False]]}, 10 | {'data': [[1, 2, 3], [4, 5, 6]], 'config': {'rowHeaders': False}}, 11 | {'data': [[1, 2, 3], [4, 5, 6]], 'config': {'colHeaders': False}} 12 | ]) 13 | def test_generate(kwargs, request): 14 | with Path('tests/output').joinpath(request.node.name + '.html').open('w') as f: 15 | f.write(generate_html(**kwargs)) 16 | --------------------------------------------------------------------------------