├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __init__.py ├── core.py ├── ext.py ├── ignite.py ├── svg.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | __target__ 2 | __pycache__ 3 | *.pyc 4 | .idea 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file documents any relevant changes done to ViUR html5 since version 2. 4 | 5 | ## 3.0.0 [develop] 6 | 7 | - Feature: Changed LGPLv3 licensing terms into MIT 8 | - Feature: Ported framework to Python 3.7 using [Pyodide](https://github.com/iodide-project/pyodide), with a full source code and library cleanup 9 | - Feature: `html5.Widget.__init__()` now allows for parameters equal to `Widget.appendChild()` to directly stack widgets together. 10 | Additionally, the following parameters can be used: 11 | - `appendTo`: Directly append the newly created widget to another widget. 12 | - `style`: Provide class attributes for styling added to the new Widget, using `Widget.addClass()`. 13 | - Feature: `html5.Widget.appendChild()` and `html5.Widget.prependChild()` can handle arbitrary input now, including HTML, lists of widgets or just text, in any order. `html5.Widget.insertChild()` runs slightly different, but shares same features. This change mostly supersedes `html5.Widget.fromHTML()`. 14 | - Feature: New `replace`-parameter for `html5.Widget.appendChild()` and `html5.Widget.prependChild()` which clears the content. 15 | - Feature: `html5.ext.InputDialog` refactored & disables OK-Button when no value is present. 16 | - Feature: `html5.utils.doesEventHitWidgetOrChildren()` and `html5.utils.doesEventHitWidgetOrParent()` now return the Widget or None instead of a boolean, to avoid creating loops and directly work with the recognized Widget. 17 | - Feature: New function `html5.Widget.onBind()` enables widgets to react when bound to other widgets using the HTML parser. 18 | - Feature: Replace HTML-parsing-related `vars`-parameter generally by `**kwargs`, with backward-compatibility. 19 | - Feature: Splitting SVG-Widgets into separate module. 20 | - Feature: Replaced `_isVoid`-class by class-attribute `Widget._leafTag` for easier leaf-widget-/tag configuration outside of html5. 21 | - Feature: HTML-parser improvements 22 | - Direct attribute assignments: Store attributes which are not HTML-element attributes directly, e.g. `
` becomes `self.kind = "internal"` on the created `html5.Div()` instance 23 | - ':'-notation on attributes to transfer objects from binder to its children 24 | - Feature: Unified usage of "hidden" and "disabled" attributes on Widgets 25 | - Speed-improvement: Hold static `_WidgetClassWrapper` per `html5.Widget` instead of creating one each time on the fly. 26 | 27 | ## [2.5.1] Vesuv 28 | 29 | - Feature: Allow to bind multiple [name] attributes to one widget in HTML parser 30 | - Feature: `html5.utils.textToHtml()` extended to `clear`-parameter 31 | 32 | ## [2.5.0] Vesuv 33 | 34 | Release date: Jul 26, 2019 35 | 36 | - Bugfix: `Widget.Th()` now supporting full col-/rowspan getting and setting. 37 | - Bugfix: HTML-parser accepts tags in upper-/camel-case order now. 38 | - Bugfix: HTML-parser handles table tags with tbody/thead tags inside more gracefully. 39 | - Feature: Split HTML-parser into separate stages to compile and run; This allows to pre-compile HTML into a list/dict-structure and render it later on without parsing it again. `parseHTML()` is the new function, `fromHTML()` works like before and handles pre-compiled or raw HTML as parameter. 40 | - Feature: `fromHTML()` extended to `vars` parameter to replace key-value pairs in text-nodes and attribute values expressed as `{{key}}`. 41 | - Feature: HTML-parser dynamically reconizes void elements 42 | - Feature: `html5.registerTag()` can be used to define new or override existing HTML elements in the HTML parser by custom implementations based on `html5.Widget()` 43 | - Feature: New function `Widget.isVisible()` as counterpart for `Widget.isHidden()`. 44 | 45 | ## [2.4.0] Agung 46 | 47 | Release date: May 17, 2019 48 | 49 | - Bugfix: Fixed bug with disabling of input widgets. 50 | - Feature: Fully refactored the librarys source base into just two single files, to reduce number of required files to download and make the library easier to access. 51 | - Feature: New function `Widget.isHidden()` to check if a widget is currently shown. 52 | - Feature: Improved handling of key-events. 53 | - Feature: Allow to close popups by pressing `ESC`. 54 | - Feature: Improvements for SVG and TextNode. 55 | 56 | ## [2.3.0] Kilauea 57 | 58 | Release date: Oct 2, 2018 59 | 60 | - Refactored `html5.ext.SelectDialog` 61 | - Extended html parser to apply data-attributes 62 | - Switching event handling to newer JavaScript event listener API 63 | - Added `onFocusIn` and `onFocusOut` events 64 | 65 | ## [2.2.0] Etna 66 | 67 | Release date: Apr 23, 2018 68 | 69 | - Implemented `html5.Head()` to access the document's head object within the library. 70 | - Directly append text in construction of Li(). 71 | 72 | ## [2.1.0] 73 | 74 | Release date: Nov 2, 2017 75 | 76 | - Introduced a build-in HTML parser (`Widget.fromHTML()`) that is capable to compile HTML-code into DOM-objects of the html5 library, and an extra-feature to bind them to their root node for further access. This attempt makes it possible to create PyJS apps using the HTML5 library without creating every single element by hand. 77 | - A more distinct way for `Widget.hide()` and `Widget.show()` that cannot be overridden by styling. (setting "hidden" does not work when another display value is set). 78 | - Utility functions `Widget.enable() and `Widget.disable()`. 79 | - Directly append text in construction of Div() and Span(). 80 | - Allow for tuple and list processing in table cell assignments. 81 | - Adding `utils.parseFloat()` and `utils.parseInt()` utility functions. 82 | - Implemented `colspan` attribute for Th() 83 | - New README.md and CHANGELOG.md. 84 | 85 | ## 2.0 86 | 87 | Release date: Dec 22, 2016 88 | 89 | - v[2.0.1]: Directly append text in construction of Option(). 90 | - v[2.0.1]: Anything added to Widget.appendChild() or Widget.prependChild() which is not a widget is handled as text (TextNode() is automatically created). 91 | - New functions `Widget.prependChild()`, `Widget.insertBefore()`, `Widget.children()`, `Widget.removeAllChildren()`, 92 | `Widget.addClass()`, `Widget.removeClass()`, `Widget.toggleClass()` 93 | - Utility functions `utils.doesEventHitWidgetOrParents()`, `utils.doesEventHitWidgetOrChildren()` taken from vi77 94 | - Insert text blocks easier with `utils.textToHtml()` 95 | - Several bugfixes 96 | 97 | [develop]: https://github.com/viur-framework/html5/compare/v2.5.1...develop 98 | [2.5.1]: https://github.com/viur-framework/html5/compare/v2.5.0...v2.5.1 99 | [2.5.0]: https://github.com/viur-framework/html5/compare/v2.4.0...v2.5.0 100 | [2.4.0]: https://github.com/viur-framework/html5/compare/v2.3.0...v2.4.0 101 | [2.3.0]: https://github.com/viur-framework/html5/compare/v2.2.0...v2.3.0 102 | [2.2.0]: https://github.com/viur-framework/html5/compare/v2.1.0...v2.2.0 103 | [2.1.0]: https://github.com/viur-framework/html5/compare/v2.0.0...v2.1.0 104 | [2.0.1]: https://github.com/viur-framework/html5/compare/v2.0.0...v2.0.1 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014-2020 by Mausbrand Informationssysteme GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ViUR html5 2 | 3 | **html5** is a DOM-abstraction layer and API that is used to create client-side Web-Apps running in the browser and written in Python. 4 | 5 | ## About 6 | 7 | This API and framework is used to implement HTML5 web-apps using the Python programming language. The framework is an abstraction layer for a DOM running in [Pyodide](https://github.com/iodide-project/pyodide), a Python 3 interpreter compiled to web-assembly. 8 | 9 | It provides 10 | 11 | - class abstraction for all HTML5-DOM-elements, e.g. `html5.Div()` 12 | - a built-in HTML parser and executor to generate DOM objects from HTML-code 13 | - helpers for adding/removing classes, arrange children, handling events etc. 14 | 15 | The most prominent software completely established on this library is [ViUR-vi](https://github.com/viur-framework/viur-vi/), the visual administration interface for ViUR-based applications. 16 | 17 | Look [here](https://www.viur.dev/blog/html5-library) for a short introduction about features and usage. 18 | 19 | [flare](https://github.com/mausbrand/flare) is now available, which supersedes this library and provides a self-contained version of its core components, but with the aspect to provide a full web-app development framework. 20 | Both libraries, flare & html5, will be held synchronous, except the dialogs and ignite-related stuff. 21 | 22 | ## Contributing 23 | 24 | We take great interest in your opinion about ViUR. We appreciate your feedback and are looking forward to hear about your ideas. Share your vision or questions with us and participate in ongoing discussions. 25 | 26 | - [ViUR website](https://www.viur.dev) 27 | - [#ViUR on freenode IRC](https://webchat.freenode.net/?channels=viur) 28 | - [ViUR on GitHub](https://github.com/viur-framework) 29 | - [ViUR on Twitter](https://twitter.com/weloveViUR) 30 | 31 | ## Credits 32 | 33 | ViUR is developed and maintained by [Mausbrand Informationssysteme GmbH](https://www.mausbrand.de/en), from Dortmund in Germany. We are a software company consisting of young, enthusiastic software developers, designers and social media experts, working on exciting projects for different kinds of customers. All of our newer projects are implemented with ViUR, from tiny web-pages to huge company intranets with hundreds of users. 34 | 35 | Help of any kind to extend and improve or enhance this project in any kind or way is always appreciated. 36 | 37 | ## License 38 | 39 | Copyright (C) 2012-2020 by Mausbrand Informationssysteme GmbH. 40 | 41 | Mausbrand and ViUR are registered trademarks of Mausbrand Informationssysteme GmbH. 42 | 43 | You may use, modify and distribute this software under the terms and conditions of the MIT license. See the file LICENSE provided within this package for more information. 44 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | from .core import * 4 | from . import ext, ignite, svg, utils 5 | -------------------------------------------------------------------------------- /core.py: -------------------------------------------------------------------------------- 1 | import logging, string 2 | 3 | ######################################################################################################################## 4 | # DOM-access functions and variables 5 | ######################################################################################################################## 6 | 7 | try: 8 | # Pyodide 9 | from js import window, eval as jseval 10 | document = window.document 11 | 12 | except: 13 | print("Emulation mode") 14 | from xml.dom.minidom import parseString 15 | 16 | jseval = None 17 | window = None 18 | document = parseString("") 19 | 20 | 21 | def domCreateAttribute(tag, ns=None): 22 | """ 23 | Creates a new HTML/SVG/... attribute 24 | :param ns: the namespace. Default: HTML. Possible values: HTML, SVG, XBL, XUL 25 | """ 26 | uri = None 27 | 28 | if ns == "SVG": 29 | uri = "http://www.w3.org/2000/svg" 30 | elif ns == "XBL": 31 | uri = "http://www.mozilla.org/xbl" 32 | elif ns == "XUL": 33 | uri = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 34 | 35 | if uri: 36 | return document.createAttribute(uri, tag) 37 | 38 | return document.createAttribute(tag) 39 | 40 | 41 | def domCreateElement(tag, ns=None): 42 | """ 43 | Creates a new HTML/SVG/... tag 44 | :param ns: the namespace. Default: HTML. Possible values: HTML, SVG, XBL, XUL 45 | """ 46 | uri = None 47 | 48 | if ns == "SVG": 49 | uri = "http://www.w3.org/2000/svg" 50 | elif ns == "XBL": 51 | uri = "http://www.mozilla.org/xbl" 52 | elif ns == "XUL": 53 | uri = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 54 | 55 | if uri: 56 | return document.createElementNS(uri, tag) 57 | 58 | return document.createElement(tag) 59 | 60 | 61 | def domCreateTextNode(txt=""): 62 | return document.createTextNode(txt) 63 | 64 | 65 | def domGetElementById(idTag): 66 | return document.getElementById(idTag) 67 | 68 | 69 | def domElementFromPoint(x, y): 70 | return document.elementFromPoint(x, y) 71 | 72 | 73 | def domGetElementsByTagName(tag): 74 | items = document.getElementsByTagName(tag) 75 | return [items.item(i) for i in range(0, int(items.length))] #pyodide interprets items.length as float, so convert to int 76 | 77 | 78 | ######################################################################################################################## 79 | # HTML Widgets 80 | ######################################################################################################################## 81 | 82 | # TextNode ------------------------------------------------------------------------------------------------------------- 83 | 84 | class TextNode(object): 85 | """ 86 | Represents a piece of text inside the DOM. 87 | This is the *only* object not deriving from "Widget", as it does 88 | not support any of its properties. 89 | """ 90 | 91 | def __init__(self, txt=None, *args, **kwargs): 92 | super().__init__() 93 | self._parent = None 94 | self._children = [] 95 | self.element = domCreateTextNode(txt or "") 96 | self._isAttached = False 97 | 98 | def _setText(self, txt): 99 | self.element.data = txt 100 | 101 | def _getText(self): 102 | return self.element.data 103 | 104 | def __str__(self): 105 | return self.element.data 106 | 107 | def onAttach(self): 108 | self._isAttached = True 109 | 110 | def onDetach(self): 111 | self._isAttached = False 112 | 113 | def _setDisabled(self, disabled): 114 | return 115 | 116 | def _getDisabled(self): 117 | return False 118 | 119 | def children(self): 120 | return [] 121 | 122 | 123 | # _WidgetClassWrapper ------------------------------------------------------------------------------------------------- 124 | 125 | class _WidgetClassWrapper(list): 126 | 127 | def __init__(self, targetWidget): 128 | super().__init__() 129 | 130 | self.targetWidget = targetWidget 131 | 132 | # Initially read content of element into current wrappper 133 | value = targetWidget.element.getAttribute("class") 134 | if value: 135 | for c in value.split(" "): 136 | list.append(self, c) 137 | 138 | def set(self, value): 139 | if value is None: 140 | value = [] 141 | elif isinstance(value, str): 142 | value = value.split(" ") 143 | elif not isinstance(value, list): 144 | raise ValueError("Value must be a str, a List or None") 145 | 146 | list.clear(self) 147 | list.extend(self, value) 148 | self._updateElem() 149 | 150 | def _updateElem(self): 151 | if len(self) == 0: 152 | self.targetWidget.element.removeAttribute("class") 153 | else: 154 | self.targetWidget.element.setAttribute("class", " ".join(self)) 155 | 156 | def append(self, p_object): 157 | list.append(self, p_object) 158 | self._updateElem() 159 | 160 | def clear(self): 161 | list.clear(self) 162 | self._updateElem() 163 | 164 | def remove(self, value): 165 | try: 166 | list.remove(self, value) 167 | except: 168 | pass 169 | self._updateElem() 170 | 171 | def extend(self, iterable): 172 | list.extend(self, iterable) 173 | self._updateElem() 174 | 175 | def insert(self, index, p_object): 176 | list.insert(self, index, p_object) 177 | self._updateElem() 178 | 179 | def pop(self, index=None): 180 | list.pop(self, index) 181 | self._updateElem() 182 | 183 | 184 | # _WidgetDataWrapper --------------------------------------------------------------------------------------------------- 185 | 186 | class _WidgetDataWrapper(dict): 187 | 188 | def __init__(self, targetWidget): 189 | super().__init__() 190 | 191 | self.targetWidget = targetWidget 192 | alldata = targetWidget.element 193 | 194 | for data in dir(alldata.dataset): 195 | dict.__setitem__(self, data, getattr(alldata.dataset, data)) 196 | 197 | def __setitem__(self, key, value): 198 | dict.__setitem__(self, key, value) 199 | self.targetWidget.element.setAttribute(str("data-" + key), value) 200 | 201 | def update(self, E=None, **F): 202 | dict.update(self, E, **F) 203 | if E is not None and "keys" in dir(E): 204 | for key in E: 205 | self.targetWidget.element.setAttribute(str("data-" + key), E["data-" + key]) 206 | elif E: 207 | for (key, val) in E: 208 | self.targetWidget.element.setAttribute(str("data-" + key), "data-" + val) 209 | for key in F: 210 | self.targetWidget.element.setAttribute(str("data-" + key), F["data-" + key]) 211 | 212 | 213 | # _WidgetStyleWrapper -------------------------------------------------------------------------------------------------- 214 | 215 | class _WidgetStyleWrapper(dict): 216 | 217 | def __init__(self, targetWidget): 218 | super().__init__() 219 | 220 | self.targetWidget = targetWidget 221 | style = targetWidget.element.style 222 | 223 | for key in style.object_values(): 224 | self[key] = style.getPropertyValue(key) 225 | 226 | def __setitem__(self, key, value): 227 | dict.__setitem__(self, key, value) 228 | self.targetWidget.element.style.setProperty(key, value) 229 | 230 | def update(self, E=None, **F): 231 | dict.update(self, E, **F) 232 | if E is not None and "keys" in dir(E): 233 | for key in E: 234 | self.targetWidget.element.style.setProperty(key, E[key]) 235 | elif E: 236 | for (key, val) in E: 237 | self.targetWidget.element.style.setProperty(key, val) 238 | for key in F: 239 | self.targetWidget.element.style.setProperty(key, F[key]) 240 | 241 | 242 | # Widget --------------------------------------------------------------------------------------------------------------- 243 | 244 | class Widget(object): 245 | _tagName = None # Defines the tag-name that is used for DOM-Element construction 246 | _leafTag = False # Defines whether ths Widget may contain other Widgets (default) or is a leaf 247 | _namespace = None # Namespace 248 | _parserTagName = None # Alternative tag name under which this Widget is registered in HTML parser 249 | style = [] # CSS-classes to directly assign to this Widget at construction. 250 | 251 | def __init__(self, *args, appendTo=None, style=None, **kwargs): 252 | if "_wrapElem" in kwargs.keys(): 253 | self.element = kwargs["_wrapElem"] 254 | del kwargs["_wrapElem"] 255 | else: 256 | assert self._tagName is not None 257 | self.element = domCreateElement(self._tagName, ns=self._namespace) 258 | 259 | self._widgetClassWrapper = None 260 | 261 | super().__init__() 262 | 263 | self.addClass(self.style) 264 | 265 | if style: 266 | self.addClass(style) 267 | 268 | self._children = [] 269 | self._catchedEvents = {} 270 | self._disabledState = 0 271 | self._isAttached = False 272 | self._parent = None 273 | 274 | if args: 275 | self.appendChild(*args, **kwargs) 276 | 277 | if appendTo: 278 | appendTo.appendChild(self) 279 | 280 | def sinkEvent(self, *args): 281 | for event_attrName in args: 282 | event = event_attrName.lower() 283 | 284 | if event_attrName in self._catchedEvents or event in ["onattach", "ondetach"]: 285 | continue 286 | 287 | eventFn = getattr(self, event_attrName, None) 288 | assert eventFn and callable(eventFn), "{} must provide a {} method".format(str(self), event_attrName) 289 | 290 | self._catchedEvents[event_attrName] = eventFn 291 | 292 | if event.startswith("on"): 293 | event = event[2:] 294 | 295 | self.element.addEventListener(event, eventFn) 296 | 297 | def unsinkEvent(self, *args): 298 | for event_attrName in args: 299 | event = event_attrName.lower() 300 | 301 | if event_attrName not in self._catchedEvents: 302 | continue 303 | 304 | eventFn = self._catchedEvents[event_attrName] 305 | del self._catchedEvents[event_attrName] 306 | 307 | if event.startswith("on"): 308 | event = event[2:] 309 | 310 | self.element.removeEventListener(event, eventFn) 311 | 312 | def disable(self): 313 | """ 314 | Disables an element, in case it is not already disabled. 315 | 316 | On disabled elements, events are not triggered anymore. 317 | """ 318 | if not self["disabled"]: 319 | self["disabled"] = True 320 | 321 | def enable(self): 322 | """ 323 | Enables an element, in case it is not already enabled. 324 | """ 325 | if self["disabled"]: 326 | self["disabled"] = False 327 | 328 | def _getTargetfuncName(self, key, type): 329 | assert type in ["get", "set"] 330 | return "_{}{}{}".format(type, key[0].upper(), key[1:]) 331 | 332 | def __getitem__(self, key): 333 | funcName = self._getTargetfuncName(key, "get") 334 | 335 | func = getattr(self, funcName, None) 336 | if func: 337 | return func() 338 | 339 | return None 340 | 341 | def __setitem__(self, key, value): 342 | funcName = self._getTargetfuncName(key, "set") 343 | 344 | func = getattr(self, funcName, None) 345 | if func: 346 | return func(value) 347 | 348 | raise ValueError("{} is no valid attribute for {}".format(key, (self._tagName or str(self)))) 349 | 350 | def __str__(self): 351 | return str(self.__class__.__name__) 352 | 353 | def __iter__(self): 354 | return self._children.__iter__() 355 | 356 | def _getData(self): 357 | """ 358 | Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements. 359 | :param name: 360 | :returns: 361 | """ 362 | return _WidgetDataWrapper(self) 363 | 364 | def _getTranslate(self): 365 | """ 366 | Specifies whether an elements attribute values and contents of its children are to be translated when the page is localized, or whether to leave them unchanged. 367 | :returns: True | False 368 | """ 369 | return True if self.element.translate == "yes" else False 370 | 371 | def _setTranslate(self, val): 372 | """ 373 | Specifies whether an elements attribute values and contents of its children are to be translated when the page is localized, or whether to leave them unchanged. 374 | :param val: True | False 375 | """ 376 | self.element.translate = "yes" if val == True else "no" 377 | 378 | def _getTitle(self): 379 | """ 380 | Advisory information associated with the element. 381 | :returns: str 382 | """ 383 | return self.element.title 384 | 385 | def _setTitle(self, val): 386 | """ 387 | Advisory information associated with the element. 388 | :param val: str 389 | """ 390 | self.element.title = val 391 | 392 | def _getTabindex(self): 393 | """ 394 | Specifies whether the element represents an element that is is focusable (that is, an element which is part of the sequence of focusable elements in the document), and the relative order of the element in the sequence of focusable elements in the document. 395 | :returns: number 396 | """ 397 | return self.element.getAttribute("tabindex") 398 | 399 | def _setTabindex(self, val): 400 | """ 401 | Specifies whether the element represents an element that is is focusable (that is, an element which is part of the sequence of focusable elements in the document), and the relative order of the element in the sequence of focusable elements in the document. 402 | :param val: number 403 | """ 404 | self.element.setAttribute("tabindex", val) 405 | 406 | def _getSpellcheck(self): 407 | """ 408 | Specifies whether the element represents an element whose contents are subject to spell checking and grammar checking. 409 | :returns: True | False 410 | """ 411 | return True if self.element.spellcheck == "true" else False 412 | 413 | def _setSpellcheck(self, val): 414 | """ 415 | Specifies whether the element represents an element whose contents are subject to spell checking and grammar checking. 416 | :param val: True | False 417 | """ 418 | self.element.spellcheck = str(val).lower() 419 | 420 | def _getLang(self): 421 | """ 422 | Specifies the primary language for the contents of the element and for any of the elements attributes that contain text. 423 | :returns: language tag e.g. de|en|fr|es|it|ru| 424 | """ 425 | return self.element.lang 426 | 427 | def _setLang(self, val): 428 | """ 429 | Specifies the primary language for the contents of the element and for any of the elements attributes that contain text. 430 | :param val: language tag 431 | """ 432 | self.element.lang = val 433 | 434 | def _getHidden(self): 435 | """ 436 | Specifies that the element represents an element that is not yet, or is no longer, relevant. 437 | :returns: True | False 438 | """ 439 | return True if self.element.hasAttribute("hidden") else False 440 | 441 | def _setHidden(self, val): 442 | """ 443 | Specifies that the element represents an element that is not yet, or is no longer, relevant. 444 | :param val: True | False 445 | """ 446 | if val: 447 | self.element.setAttribute("hidden", "") 448 | self.addClass("is-hidden") 449 | else: 450 | self.element.removeAttribute("hidden") 451 | self.removeClass("is-hidden") 452 | 453 | def _getDisabled(self): 454 | return bool(self._disabledState) 455 | 456 | def _setDisabled(self, disable): 457 | for child in self._children: 458 | child._setDisabled(disable) 459 | 460 | if disable: 461 | self._disabledState += 1 462 | 463 | if isinstance(self, _attrDisabled) and self._disabledState == 1: 464 | self.element.disabled = True 465 | 466 | elif self._disabledState > 0: 467 | if isinstance(self, _attrDisabled) and self._disabledState == 1: 468 | self.element.disabled = False 469 | 470 | self._disabledState -= 1 471 | 472 | def _getDropzone(self): 473 | """ 474 | Specifies what types of content can be dropped on the element, and instructs the UA about which actions to take with content when it is dropped on the element. 475 | :returns: "copy" | "move" | "link" 476 | """ 477 | return self.element.dropzone 478 | 479 | def _setDropzone(self, val): 480 | """ 481 | Specifies what types of content can be dropped on the element, and instructs the UA about which actions to take with content when it is dropped on the element. 482 | :param val: "copy" | "move" | "link" 483 | """ 484 | self.element.dropzone = val 485 | 486 | def _getDraggable(self): 487 | """ 488 | Specifies whether the element is draggable. 489 | :returns: True | False | "auto" 490 | """ 491 | return (self.element.draggable if str(self.element.draggable) == "auto" else ( 492 | True if str(self.element.draggable).lower() == "true" else False)) 493 | 494 | def _setDraggable(self, val): 495 | """ 496 | Specifies whether the element is draggable. 497 | :param val: True | False | "auto" 498 | """ 499 | self.element.draggable = str(val).lower() 500 | 501 | def _getDir(self): 502 | """ 503 | Specifies the elements text directionality. 504 | :returns: ltr | rtl | auto 505 | """ 506 | return self.element.dir 507 | 508 | def _setDir(self, val): 509 | """ 510 | Specifies the elements text directionality. 511 | :param val: ltr | rtl | auto 512 | """ 513 | self.element.dir = val 514 | 515 | def _getContextmenu(self): 516 | """ 517 | The value of the id attribute on the menu with which to associate the element as a context menu. 518 | :returns: 519 | """ 520 | return self.element.contextmenu 521 | 522 | def _setContextmenu(self, val): 523 | """ 524 | The value of the id attribute on the menu with which to associate the element as a context menu. 525 | :param val: 526 | """ 527 | self.element.contextmenu = val 528 | 529 | def _getContenteditable(self): 530 | """ 531 | Specifies whether the contents of the element are editable. 532 | :returns: True | False 533 | """ 534 | v = self.element.getAttribute("contenteditable") 535 | return str(v).lower() == "true" 536 | 537 | def _setContenteditable(self, val): 538 | """ 539 | Specifies whether the contents of the element are editable. 540 | :param val: True | False 541 | """ 542 | self.element.setAttribute("contenteditable", str(val).lower()) 543 | 544 | def _getAccesskey(self): 545 | """ 546 | A key label or list of key labels with which to associate the element; each key label represents a keyboard shortcut which UAs can use to activate the element or give focus to the element. 547 | :param self: 548 | :returns: 549 | """ 550 | return self.element.accesskey 551 | 552 | def _setAccesskey(self, val): 553 | """ 554 | A key label or list of key labels with which to associate the element; each key label represents a keyboard shortcut which UAs can use to activate the element or give focus to the element. 555 | :param self: 556 | :param val: 557 | """ 558 | self.element.accesskey = val 559 | 560 | def _getId(self): 561 | """ 562 | Specifies a unique id for an element 563 | :param self: 564 | :returns: 565 | """ 566 | return self.element.id 567 | 568 | def _setId(self, val): 569 | """ 570 | Specifies a unique id for an element 571 | :param self: 572 | :param val: 573 | """ 574 | self.element.id = val 575 | 576 | def _getClass(self): 577 | """ 578 | The class attribute specifies one or more classnames for an element. 579 | :returns: 580 | """ 581 | if self._widgetClassWrapper is None: 582 | self._widgetClassWrapper = _WidgetClassWrapper(self) 583 | 584 | return self._widgetClassWrapper 585 | 586 | def _setClass(self, value): 587 | """ 588 | The class attribute specifies one or more classnames for an element. 589 | :param self: 590 | :param value: 591 | @raise ValueError: 592 | """ 593 | self._getClass().set(value) 594 | 595 | def _getStyle(self): 596 | """ 597 | The style attribute specifies an inline style for an element. 598 | :param self: 599 | :returns: 600 | """ 601 | return _WidgetStyleWrapper(self) 602 | 603 | def _getRole(self): 604 | """ 605 | Specifies a role for an element 606 | @param self: 607 | @return: 608 | """ 609 | return self.element.getAttribute("role") 610 | 611 | def _setRole(self, val): 612 | """ 613 | Specifies a role for an element 614 | @param self: 615 | @param val: 616 | """ 617 | self.element.setAttribute("role", val) 618 | 619 | def hide(self): 620 | """ 621 | Hide element, if shown. 622 | :return: 623 | """ 624 | if not self["hidden"]: 625 | self["hidden"] = True 626 | 627 | def show(self): 628 | """ 629 | Show element, if hidden. 630 | :return: 631 | """ 632 | if self["hidden"]: 633 | self["hidden"] = False 634 | 635 | def isHidden(self): 636 | """ 637 | Checks if a widget is hidden. 638 | :return: True if hidden, False otherwise. 639 | """ 640 | return self["hidden"] 641 | 642 | def isVisible(self): 643 | """ 644 | Checks if a widget is visible. 645 | :return: True if visible, False otherwise. 646 | """ 647 | return not self.isHidden() 648 | 649 | def onBind(self, widget, name): 650 | """ 651 | Event function that is called on the widget when it is bound to another widget with a name. 652 | This is only done by the HTML parser, a manual binding by the user is not triggered. 653 | """ 654 | return 655 | 656 | def onAttach(self): 657 | self._isAttached = True 658 | 659 | for c in self._children: 660 | c.onAttach() 661 | 662 | def onDetach(self): 663 | self._isAttached = False 664 | for c in self._children: 665 | c.onDetach() 666 | 667 | def __collectChildren(self, *args, **kwargs): 668 | if kwargs.get("bindTo") is None: 669 | kwargs["bindTo"] = self 670 | 671 | widgets = [] 672 | for arg in args: 673 | if isinstance(arg, (str, HtmlAst)): 674 | widgets.extend(fromHTML(arg, **kwargs)) 675 | 676 | elif isinstance(arg, (list, tuple)): 677 | for subarg in arg: 678 | widgets.extend(self.__collectChildren(subarg, **kwargs)) 679 | 680 | elif not isinstance(arg, (Widget, TextNode)): 681 | widgets.append(TextNode(str(arg))) 682 | 683 | else: 684 | widgets.append(arg) 685 | 686 | return widgets 687 | 688 | def insertBefore(self, insert, child, **kwargs): 689 | if not child: 690 | return self.appendChild(insert) 691 | 692 | assert child in self._children, "{} is not a child of {}".format(child, self) 693 | 694 | toInsert = self.__collectChildren(insert, **kwargs) 695 | 696 | for insert in toInsert: 697 | if insert._parent: 698 | insert._parent.removeChild(insert) 699 | 700 | self.element.insertBefore(insert.element, child.element) 701 | self._children.insert(self._children.index(child), insert) 702 | 703 | insert._parent = self 704 | if self._isAttached: 705 | insert.onAttach() 706 | 707 | return toInsert 708 | 709 | def prependChild(self, *args, **kwargs): 710 | if kwargs.get("replace", False): 711 | self.removeAllChildren() 712 | del kwargs["replace"] 713 | 714 | toPrepend = self.__collectChildren(*args, **kwargs) 715 | 716 | for child in toPrepend: 717 | if child._parent: 718 | child._parent._children.remove(child) 719 | child._parent = None 720 | 721 | if not self._children: 722 | self.appendChild(child) 723 | else: 724 | self.insertBefore(child, self.children(0)) 725 | 726 | return toPrepend 727 | 728 | def appendChild(self, *args, **kwargs): 729 | if kwargs.get("replace", False): 730 | self.removeAllChildren() 731 | del kwargs["replace"] 732 | 733 | toAppend = self.__collectChildren(*args, **kwargs) 734 | 735 | for child in toAppend: 736 | if isinstance(child, Template): 737 | return self.appendChild(child._children) 738 | 739 | if child._parent: 740 | child._parent._children.remove(child) 741 | 742 | self._children.append(child) 743 | self.element.appendChild(child.element) 744 | child._parent = self 745 | 746 | if self._isAttached: 747 | child.onAttach() 748 | 749 | return toAppend 750 | 751 | def removeChild(self, child): 752 | assert child in self._children, "{} is not a child of {}".format(child, self) 753 | 754 | if child._isAttached: 755 | child.onDetach() 756 | 757 | self.element.removeChild(child.element) 758 | self._children.remove(child) 759 | child._parent = None 760 | 761 | def removeAllChildren(self): 762 | """ 763 | Removes all child widgets of the current widget. 764 | """ 765 | for child in self._children[:]: 766 | self.removeChild(child) 767 | 768 | def isParentOf(self, widget): 769 | """ 770 | Checks if an object is the parent of widget. 771 | 772 | :type widget: Widget 773 | :param widget: The widget to check for. 774 | :return: True, if widget is a child of the object, else False. 775 | """ 776 | 777 | # You cannot be your own child! 778 | if self == widget: 779 | return False 780 | 781 | for child in self._children: 782 | if child == widget: 783 | return True 784 | 785 | if child.isParentOf(widget): 786 | return True 787 | 788 | return False 789 | 790 | def isChildOf(self, widget): 791 | """ 792 | Checks if an object is the child of widget. 793 | 794 | :type widget: Widget 795 | :param widget: The widget to check for. 796 | :return: True, if object is a child of widget, else False. 797 | """ 798 | 799 | # You cannot be your own parent! 800 | if self == widget: 801 | return False 802 | 803 | parent = self.parent() 804 | while parent: 805 | if parent == widget: 806 | return True 807 | 808 | parent = widget.parent() 809 | 810 | return False 811 | 812 | def hasClass(self, className): 813 | """ 814 | Determine whether the current widget is assigned the given class 815 | 816 | :param className: The class name to search for. 817 | :type className: str 818 | """ 819 | 820 | if isinstance(className, str) or isinstance(className, unicode): 821 | return className in self["class"] 822 | else: 823 | raise TypeError() 824 | 825 | def addClass(self, *args): 826 | """ 827 | Adds a class or a list of classes to the current widget. 828 | If the widget already has the class, it is ignored. 829 | 830 | :param args: A list of class names. This can also be a list. 831 | :type args: list of str | list of list of str 832 | """ 833 | 834 | for item in args: 835 | if isinstance(item, list): 836 | self.addClass(*item) 837 | 838 | elif isinstance(item, str): 839 | for sitem in item.split(" "): 840 | if not self.hasClass(sitem): 841 | self["class"].append(sitem) 842 | else: 843 | raise TypeError() 844 | 845 | def hasClass(self, name): 846 | """ 847 | Checks whether the widget has class name set or unset. 848 | 849 | :param name: The class-name to be checked. 850 | :type args: str 851 | """ 852 | return name in self["class"] 853 | 854 | def removeClass(self, *args): 855 | """ 856 | Removes a class or a list of classes from the current widget. 857 | 858 | :param args: A list of class names. This can also be a list. 859 | :type args: list of str | list of list of str 860 | """ 861 | 862 | for item in args: 863 | if isinstance(item, list): 864 | self.removeClass(item) 865 | 866 | elif isinstance(item, str): 867 | for sitem in item.split(" "): 868 | if self.hasClass(sitem): 869 | self["class"].remove(sitem) 870 | else: 871 | raise TypeError() 872 | 873 | def toggleClass(self, on, off=None): 874 | """ 875 | Toggles the class ``on``. 876 | 877 | If the widget contains a class ``on``, it is toggled by ``off``. 878 | ``off`` can either be a class name that is substituted, or nothing. 879 | 880 | :param on: Classname to test for. If ``on`` does not exist, but ``off``, ``off`` is replaced by ``on``. 881 | :type on: str 882 | 883 | :param off: Classname to replace if ``on`` existed. 884 | :type off: str 885 | 886 | :return: Returns True, if ``on`` was switched, else False. 887 | :rtype: bool 888 | """ 889 | if self.hasClass(on): 890 | self.removeClass(on) 891 | 892 | if off and not self.hasClass(off): 893 | self.addClass(off) 894 | 895 | return False 896 | 897 | if off and self.hasClass(off): 898 | self.removeClass(off) 899 | 900 | self.addClass(on) 901 | return True 902 | 903 | def onBlur(self, event): 904 | pass 905 | 906 | def onChange(self, event): 907 | pass 908 | 909 | def onContextMenu(self, event): 910 | pass 911 | 912 | def onFocus(self, event): 913 | pass 914 | 915 | def onFocusIn(self, event): 916 | pass 917 | 918 | def onFocusOut(self, event): 919 | pass 920 | 921 | def onFormChange(self, event): 922 | pass 923 | 924 | def onFormInput(self, event): 925 | pass 926 | 927 | def onInput(self, event): 928 | pass 929 | 930 | def onInvalid(self, event): 931 | pass 932 | 933 | def onReset(self, event): 934 | pass 935 | 936 | def onSelect(self, event): 937 | pass 938 | 939 | def onSubmit(self, event): 940 | pass 941 | 942 | def onKeyDown(self, event): 943 | pass 944 | 945 | def onKeyPress(self, event): 946 | pass 947 | 948 | def onKeyUp(self, event): 949 | pass 950 | 951 | def onClick(self, event): 952 | pass 953 | 954 | def onDblClick(self, event): 955 | pass 956 | 957 | def onDrag(self, event): 958 | pass 959 | 960 | def onDragEnd(self, event): 961 | pass 962 | 963 | def onDragEnter(self, event): 964 | pass 965 | 966 | def onDragLeave(self, event): 967 | pass 968 | 969 | def onDragOver(self, event): 970 | pass 971 | 972 | def onDragStart(self, event): 973 | pass 974 | 975 | def onDrop(self, event): 976 | pass 977 | 978 | def onMouseDown(self, event): 979 | pass 980 | 981 | def onMouseMove(self, event): 982 | pass 983 | 984 | def onMouseOut(self, event): 985 | pass 986 | 987 | def onMouseOver(self, event): 988 | pass 989 | 990 | def onMouseUp(self, event): 991 | pass 992 | 993 | def onMouseWheel(self, event): 994 | pass 995 | 996 | def onScroll(self, event): 997 | pass 998 | 999 | def onTouchStart(self, event): 1000 | pass 1001 | 1002 | def onTouchEnd(self, event): 1003 | pass 1004 | 1005 | def onTouchMove(self, event): 1006 | pass 1007 | 1008 | def onTouchCancel(self, event): 1009 | pass 1010 | 1011 | def focus(self): 1012 | self.element.focus() 1013 | 1014 | def blur(self): 1015 | self.element.blur() 1016 | 1017 | def parent(self): 1018 | return self._parent 1019 | 1020 | def children(self, n=None): 1021 | """ 1022 | Access children of widget. 1023 | 1024 | If ``n`` is ommitted, it returns a list of all child-widgets; 1025 | Else, it returns the N'th child, or None if its out of bounds. 1026 | 1027 | :param n: Optional offset of child widget to return. 1028 | :type n: int 1029 | 1030 | :return: Returns all children or only the requested one. 1031 | :rtype: list | Widget | None 1032 | """ 1033 | if n is None: 1034 | return self._children[:] 1035 | 1036 | try: 1037 | return self._children[n] 1038 | except IndexError: 1039 | return None 1040 | 1041 | def sortChildren(self, key): 1042 | """ 1043 | Sorts our direct children. They are rearranged on DOM level. 1044 | Key must be a function accepting one widget as parameter and must return 1045 | the key used to sort these widgets. 1046 | """ 1047 | self._children.sort(key=key) 1048 | tmpl = self._children[:] 1049 | tmpl.reverse() 1050 | for c in tmpl: 1051 | self.element.removeChild(c.element) 1052 | self.element.insertBefore(c.element, self.element.children.item(0)) 1053 | 1054 | def fromHTML(self, html, appendTo=None, bindTo=None, replace=False, vars=None, **kwargs): 1055 | """ 1056 | Parses html and constructs its elements as part of self. 1057 | 1058 | :param html: HTML code. 1059 | :param appendTo: The entity where the HTML code is constructed below. This defaults to self in usual case. 1060 | :param bindTo: The entity where the named objects are bound to. This defaults to self in usual case. 1061 | :param replace: Clear entire content of appendTo before appending. 1062 | :param vars: Deprecated; Same as kwargs. 1063 | :param **kwargs: Additional variables provided as a dict for {{placeholders}} inside the HTML 1064 | 1065 | :return: 1066 | """ 1067 | if appendTo is None: 1068 | appendTo = self 1069 | 1070 | if bindTo is None: 1071 | bindTo = self 1072 | 1073 | if replace: 1074 | appendTo.removeAllChildren() 1075 | 1076 | # use of vars is deprecated! 1077 | if isinstance(vars, dict): 1078 | kwargs.update(vars) 1079 | 1080 | return fromHTML(html, appendTo=appendTo, bindTo=bindTo, **kwargs) 1081 | 1082 | 1083 | ######################################################################################################################## 1084 | # Attribute Collectors 1085 | ######################################################################################################################## 1086 | 1087 | # _attrLabel --------------------------------------------------------------------------------------------------------------- 1088 | 1089 | class _attrLabel(object): 1090 | def _getLabel(self): 1091 | return self.element.getAttribute("label") 1092 | 1093 | def _setLabel(self, val): 1094 | self.element.setAttribute("label", val) 1095 | 1096 | 1097 | # _attrCharset -------------------------------------------------------------------------------------------------------------- 1098 | 1099 | class _attrCharset(object): 1100 | def _getCharset(self): 1101 | return self.element._attrCharset 1102 | 1103 | def _setCharset(self, val): 1104 | self.element._attrCharset = val 1105 | 1106 | 1107 | # _attrCite ----------------------------------------------------------------------------------------------------------------- 1108 | 1109 | class _attrCite(object): 1110 | def _getCite(self): 1111 | return self.element._attrCite 1112 | 1113 | def _setCite(self, val): 1114 | self.element._attrCite = val 1115 | 1116 | 1117 | class _attrDatetime(object): 1118 | def _getDatetime(self): 1119 | return self.element.datetime 1120 | 1121 | def _setDatetime(self, val): 1122 | self.element.datetime = val 1123 | 1124 | 1125 | # Form ----------------------------------------------------------------------------------------------------------------- 1126 | 1127 | class _attrForm(object): 1128 | def _getForm(self): 1129 | return self.element.form 1130 | 1131 | def _setForm(self, val): 1132 | self.element.form = val 1133 | 1134 | 1135 | class _attrAlt(object): 1136 | def _getAlt(self): 1137 | return self.element.alt 1138 | 1139 | def _setAlt(self, val): 1140 | self.element.alt = val 1141 | 1142 | 1143 | class _attrAutofocus(object): 1144 | def _getAutofocus(self): 1145 | return True if self.element.hasAttribute("autofocus") else False 1146 | 1147 | def _setAutofocus(self, val): 1148 | if val: 1149 | self.element.setAttribute("autofocus", "") 1150 | else: 1151 | self.element.removeAttribute("autofocus") 1152 | 1153 | 1154 | class _attrDisabled(object): 1155 | pass 1156 | 1157 | 1158 | class _attrChecked(object): 1159 | def _getChecked(self): 1160 | return self.element.checked 1161 | 1162 | def _setChecked(self, val): 1163 | self.element.checked = val 1164 | 1165 | 1166 | class _attrIndeterminate(object): 1167 | def _getIndeterminate(self): 1168 | return self.element.indeterminate 1169 | 1170 | def _setIndeterminate(self, val): 1171 | self.element.indeterminate = val 1172 | 1173 | 1174 | class _attrName(object): 1175 | def _getName(self): 1176 | return self.element.getAttribute("name") 1177 | 1178 | def _setName(self, val): 1179 | self.element.setAttribute("name", val) 1180 | 1181 | 1182 | class _attrValue(object): 1183 | def _getValue(self): 1184 | return self.element.value 1185 | 1186 | def _setValue(self, val): 1187 | self.element.value = val 1188 | 1189 | 1190 | class _attrAutocomplete(object): 1191 | def _getAutocomplete(self): 1192 | return True if self.element.autocomplete == "on" else False 1193 | 1194 | def _setAutocomplete(self, val): 1195 | self.element.autocomplete = "on" if val == True else "off" 1196 | 1197 | 1198 | class _attrRequired(object): 1199 | def _getRequired(self): 1200 | return True if self.element.hasAttribute("required") else False 1201 | 1202 | def _setRequired(self, val): 1203 | if val: 1204 | self.element.setAttribute("required", "") 1205 | else: 1206 | self.element.removeAttribute("required") 1207 | 1208 | 1209 | class _attrMultiple(object): 1210 | def _getMultiple(self): 1211 | return True if self.element.hasAttribute("multiple") else False 1212 | 1213 | def _setMultiple(self, val): 1214 | if val: 1215 | self.element.setAttribute("multiple", "") 1216 | else: 1217 | self.element.removeAttribute("multiple") 1218 | 1219 | 1220 | class _attrSize(object): 1221 | def _getSize(self): 1222 | return self.element.size 1223 | 1224 | def _setSize(self, val): 1225 | self.element.size = val 1226 | 1227 | 1228 | class _attrFor(object): 1229 | def _getFor(self): 1230 | return self.element.getAttribute("for") 1231 | 1232 | def _setFor(self, val): 1233 | self.element.setAttribute("for", val) 1234 | 1235 | 1236 | class _attrInputs(_attrRequired): 1237 | def _getMaxlength(self): 1238 | return self.element.maxLength 1239 | 1240 | def _setMaxlength(self, val): 1241 | self.element.maxLength = val 1242 | 1243 | def _getPlaceholder(self): 1244 | return self.element.placeholder 1245 | 1246 | def _setPlaceholder(self, val): 1247 | self.element.placeholder = val 1248 | 1249 | def _getReadonly(self): 1250 | return True if self.element.hasAttribute("readonly") else False 1251 | 1252 | def _setReadonly(self, val): 1253 | if val: 1254 | self.element.setAttribute("readonly", "") 1255 | else: 1256 | self.element.removeAttribute("readonly") 1257 | 1258 | 1259 | class _attrFormhead(object): 1260 | def _getFormaction(self): 1261 | return self.element.formaction 1262 | 1263 | def _setFormaction(self, val): 1264 | self.element.formaction = val 1265 | 1266 | def _getFormenctype(self): 1267 | return self.element.formenctype 1268 | 1269 | def _setFormenctype(self, val): 1270 | self.element.formenctype = val 1271 | 1272 | def _getFormmethod(self): 1273 | return self.element.formmethod 1274 | 1275 | def _setFormmethod(self, val): 1276 | self.element.formmethod = val 1277 | 1278 | def _getFormtarget(self): 1279 | return self.element.formtarget 1280 | 1281 | def _setFormtarget(self, val): 1282 | self.element.formtarget = val 1283 | 1284 | def _getFormnovalidate(self): 1285 | return True if self.element.hasAttribute("formnovalidate") else False 1286 | 1287 | def _setFormnovalidate(self, val): 1288 | if val: 1289 | self.element.setAttribute("formnovalidate", "") 1290 | else: 1291 | self.element.removeAttribute("formnovalidate") 1292 | 1293 | 1294 | # _attrHref ----------------------------------------------------------------------------------------------------------------- 1295 | 1296 | class _attrHref(object): 1297 | def _getHref(self): 1298 | """ 1299 | Url of a Page 1300 | :param self: 1301 | """ 1302 | return self.element.href 1303 | 1304 | def _setHref(self, val): 1305 | """ 1306 | Url of a Page 1307 | :param val: URL 1308 | """ 1309 | self.element.href = val 1310 | 1311 | def _getHreflang(self): 1312 | return self.element.hreflang 1313 | 1314 | def _setHreflang(self, val): 1315 | self.element.hreflang = val 1316 | 1317 | 1318 | class _attrTarget(object): 1319 | def _getTarget(self): 1320 | return self.element.target 1321 | 1322 | def _setTarget(self, val): 1323 | self.element.target = val 1324 | 1325 | 1326 | # _attrMedia ---------------------------------------------------------------------------------------------------------------- 1327 | 1328 | class _attrType(object): 1329 | def _getType(self): 1330 | return self.element.type 1331 | 1332 | def _setType(self, val): 1333 | self.element.type = val 1334 | 1335 | 1336 | class _attrMedia(_attrType): 1337 | def _getMedia(self): 1338 | return self.element.media 1339 | 1340 | def _setMedia(self, val): 1341 | self.element.media = val 1342 | 1343 | 1344 | class _attrDimensions(object): 1345 | def _getWidth(self): 1346 | return self.element.width 1347 | 1348 | def _setWidth(self, val): 1349 | self.element.width = val 1350 | 1351 | def _getHeight(self): 1352 | return self.element.height 1353 | 1354 | def _setHeight(self, val): 1355 | self.element.height = val 1356 | 1357 | 1358 | class _attrUsemap(object): 1359 | def _getUsemap(self): 1360 | return self.element.usemap 1361 | 1362 | def _setUsemap(self, val): 1363 | self.element.usemap = val 1364 | 1365 | 1366 | class _attrMultimedia(object): 1367 | def _getAutoplay(self): 1368 | return True if self.element.hasAttribute("autoplay") else False 1369 | 1370 | def _setAutoplay(self, val): 1371 | if val: 1372 | self.element.setAttribute("autoplay", "") 1373 | else: 1374 | self.element.removeAttribute("autoplay") 1375 | 1376 | def _getPlaysinline(self): 1377 | return True if self.element.hasAttribute("playsinline") else False 1378 | 1379 | def _setPlaysinline(self, val): 1380 | if val: 1381 | self.element.setAttribute("playsinline", "") 1382 | else: 1383 | self.element.removeAttribute("playsinline") 1384 | 1385 | def _getControls(self): 1386 | return True if self.element.hasAttribute("controls") else False 1387 | 1388 | def _setControls(self, val): 1389 | if val: 1390 | self.element.setAttribute("controls", "") 1391 | else: 1392 | self.element.removeAttribute("controls") 1393 | 1394 | def _getLoop(self): 1395 | return True if self.element.hasAttribute("loop") else False 1396 | 1397 | def _setLoop(self, val): 1398 | if val: 1399 | self.element.setAttribute("loop", "") 1400 | else: 1401 | self.element.removeAttribute("loop") 1402 | 1403 | def _getMuted(self): 1404 | return True if self.element.hasAttribute("muted") else False 1405 | 1406 | def _setMuted(self, val): 1407 | if val: 1408 | self.element.setAttribute("muted", "") 1409 | else: 1410 | self.element.removeAttribute("muted") 1411 | 1412 | def _getPreload(self): 1413 | return self.element.preload 1414 | 1415 | def _setPreload(self, val): 1416 | self.element.preload = val 1417 | 1418 | 1419 | # _attrRel ------------------------------------------------------------------------------------------------------------------ 1420 | 1421 | class _attrRel(object): 1422 | def _getRel(self): 1423 | return self.element.rel 1424 | 1425 | def _setRel(self, val): 1426 | self.element.rel = val 1427 | 1428 | 1429 | # _attrSrc ------------------------------------------------------------------------------------------------------------------ 1430 | 1431 | class _attrSrc(object): 1432 | def _getSrc(self): 1433 | return self.element.src 1434 | 1435 | def _setSrc(self, val): 1436 | self.element.src = val 1437 | 1438 | 1439 | ######################################################################################################################## 1440 | # HTML Elements 1441 | ######################################################################################################################## 1442 | 1443 | # A -------------------------------------------------------------------------------------------------------------------- 1444 | 1445 | class A(Widget, _attrHref, _attrTarget, _attrMedia, _attrRel, _attrName): 1446 | _tagName = "a" 1447 | 1448 | def _getDownload(self): 1449 | """ 1450 | The download attribute specifies the path to a download 1451 | :returns: filename 1452 | """ 1453 | return self.element.download 1454 | 1455 | def _setDownload(self, val): 1456 | """ 1457 | The download attribute specifies the path to a download 1458 | :param val: filename 1459 | """ 1460 | self.element.download = val 1461 | 1462 | 1463 | # Area ----------------------------------------------------------------------------------------------------------------- 1464 | 1465 | class Area(A, _attrAlt): 1466 | _tagName = "area" 1467 | _leafTag = True 1468 | 1469 | def _getCoords(self): 1470 | return self.element.coords 1471 | 1472 | def _setCoords(self, val): 1473 | self.element.coords = val 1474 | 1475 | def _getShape(self): 1476 | return self.element.shape 1477 | 1478 | def _setShape(self, val): 1479 | self.element.shape = val 1480 | 1481 | 1482 | # Audio ---------------------------------------------------------------------------------------------------------------- 1483 | 1484 | class Audio(Widget, _attrSrc, _attrMultimedia): 1485 | _tagName = "audio" 1486 | 1487 | class Bdo(Widget): 1488 | _tagName = "bdo" 1489 | 1490 | 1491 | # Blockquote ----------------------------------------------------------------------------------------------------------- 1492 | 1493 | class Blockquote(Widget): 1494 | _tagName = "blockquote" 1495 | 1496 | def _getBlockquote(self): 1497 | return self.element.blockquote 1498 | 1499 | def _setBlockquote(self, val): 1500 | self.element.blockquote = val 1501 | 1502 | 1503 | # Body ----------------------------------------------------------------------------------------------------------------- 1504 | 1505 | class BodyCls(Widget): 1506 | 1507 | def __init__(self, *args, **kwargs): 1508 | super().__init__(_wrapElem=domGetElementsByTagName("body")[0], *args, **kwargs) 1509 | self._isAttached = True 1510 | 1511 | 1512 | _body = None 1513 | 1514 | 1515 | def Body(): 1516 | global _body 1517 | 1518 | if _body is None: 1519 | _body = BodyCls() 1520 | 1521 | return _body 1522 | 1523 | 1524 | # Canvas --------------------------------------------------------------------------------------------------------------- 1525 | 1526 | class Canvas(Widget, _attrDimensions): 1527 | _tagName = "canvas" 1528 | 1529 | 1530 | # Command -------------------------------------------------------------------------------------------------------------- 1531 | 1532 | class Command(Widget, _attrLabel, _attrType, _attrDisabled, _attrChecked): 1533 | _tagName = "command" 1534 | 1535 | def _getIcon(self): 1536 | return self.element.icon 1537 | 1538 | def _setIcon(self, val): 1539 | self.element.icon = val 1540 | 1541 | def _getRadiogroup(self): 1542 | return self.element.radiogroup 1543 | 1544 | def _setRadiogroup(self, val): 1545 | self.element.radiogroup = val 1546 | 1547 | 1548 | # _Del ----------------------------------------------------------------------------------------------------------------- 1549 | 1550 | class _Del(Widget, _attrCite, _attrDatetime): 1551 | _tagName = "_del" 1552 | 1553 | 1554 | # Dialog -------------------------------------------------------------------------------------------------------------- 1555 | 1556 | class Dialog(Widget): 1557 | _tagName = "dialog" 1558 | 1559 | def _getOpen(self): 1560 | return True if self.element.hasAttribute("open") else False 1561 | 1562 | def _setOpen(self, val): 1563 | if val: 1564 | self.element.setAttribute("open", "") 1565 | else: 1566 | self.element.removeAttribute("open") 1567 | 1568 | # Elements ------------------------------------------------------------------------------------------------------------- 1569 | 1570 | class Abbr(Widget): 1571 | _tagName = "abbr" 1572 | 1573 | 1574 | class Address(Widget): 1575 | _tagName = "address" 1576 | 1577 | 1578 | class Article(Widget): 1579 | _tagName = "article" 1580 | 1581 | 1582 | class Aside(Widget): 1583 | _tagName = "aside" 1584 | 1585 | 1586 | class B(Widget): 1587 | _tagName = "b" 1588 | 1589 | 1590 | class Bdi(Widget): 1591 | _tagName = "bdi" 1592 | 1593 | 1594 | class Br(Widget): 1595 | _tagName = "br" 1596 | _leafTag = True 1597 | 1598 | 1599 | class Caption(Widget): 1600 | _tagName = "caption" 1601 | 1602 | 1603 | class Cite(Widget): 1604 | _tagName = "cite" 1605 | 1606 | 1607 | class Code(Widget): 1608 | _tagName = "code" 1609 | 1610 | 1611 | class Datalist(Widget): 1612 | _tagName = "datalist" 1613 | 1614 | 1615 | class Dfn(Widget): 1616 | _tagName = "dfn" 1617 | 1618 | 1619 | class Div(Widget): 1620 | _tagName = "div" 1621 | 1622 | 1623 | class Em(Widget): 1624 | _tagName = "em" 1625 | 1626 | 1627 | class Embed(Widget, _attrSrc, _attrType, _attrDimensions): 1628 | _tagName = "embed" 1629 | _leafTag = True 1630 | 1631 | 1632 | class Figcaption(Widget): 1633 | _tagName = "figcaption" 1634 | 1635 | 1636 | class Figure(Widget): 1637 | _tagName = "figure" 1638 | 1639 | 1640 | class Footer(Widget): 1641 | _tagName = "footer" 1642 | 1643 | 1644 | class Header(Widget): 1645 | _tagName = "header" 1646 | 1647 | 1648 | class H1(Widget): 1649 | _tagName = "h1" 1650 | 1651 | 1652 | class H2(Widget): 1653 | _tagName = "h2" 1654 | 1655 | 1656 | class H3(Widget): 1657 | _tagName = "h3" 1658 | 1659 | 1660 | class H4(Widget): 1661 | _tagName = "h4" 1662 | 1663 | 1664 | class H5(Widget): 1665 | _tagName = "h5" 1666 | 1667 | 1668 | class H6(Widget): 1669 | _tagName = "h6" 1670 | 1671 | 1672 | class Hr(Widget): 1673 | _tagName = "hr" 1674 | _leafTag = True 1675 | 1676 | 1677 | class I(Widget): 1678 | _tagName = "i" 1679 | 1680 | 1681 | class Kdb(Widget): 1682 | _tagName = "kdb" 1683 | 1684 | 1685 | class Legend(Widget): 1686 | _tagName = "legend" 1687 | 1688 | 1689 | class Mark(Widget): 1690 | _tagName = "mark" 1691 | 1692 | 1693 | class Noscript(Widget): 1694 | _tagName = "noscript" 1695 | 1696 | 1697 | class P(Widget): 1698 | _tagName = "p" 1699 | 1700 | 1701 | class Rq(Widget): 1702 | _tagName = "rq" 1703 | 1704 | 1705 | class Rt(Widget): 1706 | _tagName = "rt" 1707 | 1708 | 1709 | class Ruby(Widget): 1710 | _tagName = "ruby" 1711 | 1712 | 1713 | class S(Widget): 1714 | _tagName = "s" 1715 | 1716 | 1717 | class Samp(Widget): 1718 | _tagName = "samp" 1719 | 1720 | 1721 | class Section(Widget): 1722 | _tagName = "section" 1723 | 1724 | 1725 | class Small(Widget): 1726 | _tagName = "small" 1727 | 1728 | 1729 | class Strong(Widget): 1730 | _tagName = "strong" 1731 | 1732 | 1733 | class Sub(Widget): 1734 | _tagName = "sub" 1735 | 1736 | 1737 | class Summery(Widget): 1738 | _tagName = "summery" 1739 | 1740 | 1741 | class Sup(Widget): 1742 | _tagName = "sup" 1743 | 1744 | 1745 | class U(Widget): 1746 | _tagName = "u" 1747 | 1748 | 1749 | class Var(Widget): 1750 | _tagName = "var" 1751 | 1752 | 1753 | class Wbr(Widget): 1754 | _tagName = "wbr" 1755 | 1756 | 1757 | # Form ----------------------------------------------------------------------------------------------------------------- 1758 | 1759 | class Button(Widget, _attrDisabled, _attrType, _attrForm, _attrAutofocus, _attrName, _attrValue, _attrFormhead): 1760 | _tagName = "button" 1761 | 1762 | 1763 | class Fieldset(Widget, _attrDisabled, _attrForm, _attrName): 1764 | _tagName = "fieldset" 1765 | 1766 | 1767 | class Form(Widget, _attrDisabled, _attrName, _attrTarget, _attrAutocomplete): 1768 | _tagName = "form" 1769 | 1770 | def _getNovalidate(self): 1771 | return True if self.element.hasAttribute("novalidate") else False 1772 | 1773 | def _setNovalidate(self, val): 1774 | if val: 1775 | self.element.setAttribute("novalidate", "") 1776 | else: 1777 | self.element.removeAttribute("novalidate") 1778 | 1779 | def _getAction(self): 1780 | return self.element.action 1781 | 1782 | def _setAction(self, val): 1783 | self.element.action = val 1784 | 1785 | def _getMethod(self): 1786 | return self.element.method 1787 | 1788 | def _setMethod(self, val): 1789 | self.element.method = val 1790 | 1791 | def _getEnctype(self): 1792 | return self.element.enctype 1793 | 1794 | def _setEnctype(self, val): 1795 | self.element.enctype = val 1796 | 1797 | def _getAccept_attrCharset(self): 1798 | return getattr(self.element, "accept-charset") 1799 | 1800 | def _setAccept_attrCharset(self, val): 1801 | self.element.setAttribute("accept-charset", val) 1802 | 1803 | 1804 | class Input(Widget, _attrDisabled, _attrType, _attrForm, _attrAlt, _attrAutofocus, _attrChecked, 1805 | _attrIndeterminate, _attrName, _attrDimensions, _attrValue, _attrFormhead, 1806 | _attrAutocomplete, _attrInputs, _attrMultiple, _attrSize, _attrSrc): 1807 | _tagName = "input" 1808 | _leafTag = True 1809 | 1810 | def _getAccept(self): 1811 | return self.element.accept 1812 | 1813 | def _setAccept(self, val): 1814 | self.element.accept = val 1815 | 1816 | def _getList(self): 1817 | return self.element.list 1818 | 1819 | def _setList(self, val): 1820 | self.element.list = val 1821 | 1822 | def _getMax(self): 1823 | return self.element.max 1824 | 1825 | def _setMax(self, val): 1826 | self.element.max = val 1827 | 1828 | def _getMin(self): 1829 | return self.element.min 1830 | 1831 | def _setMin(self, val): 1832 | self.element.min = val 1833 | 1834 | def _getPattern(self): 1835 | return self.element.pattern 1836 | 1837 | def _setPattern(self, val): 1838 | self.element.pattern = val 1839 | 1840 | def _getStep(self): 1841 | return self.element.step 1842 | 1843 | def _setStep(self, val): 1844 | self.element.step = val 1845 | 1846 | 1847 | class Label(Widget, _attrForm, _attrFor): 1848 | _tagName = "label" 1849 | autoIdCounter = 0 1850 | 1851 | def __init__(self, *args, forElem=None, **kwargs): 1852 | super().__init__(*args, **kwargs) 1853 | 1854 | if forElem: 1855 | if not forElem["id"]: 1856 | idx = Label.autoIdCounter 1857 | Label.autoIdCounter += 1 1858 | forElem["id"] = "label-autoid-for-{}".format(idx) 1859 | 1860 | self["for"] = forElem["id"] 1861 | 1862 | 1863 | class Optgroup(Widget, _attrDisabled, _attrLabel): 1864 | _tagName = "optgroup" 1865 | 1866 | 1867 | class Option(Widget, _attrDisabled, _attrLabel, _attrValue): 1868 | _tagName = "option" 1869 | 1870 | def _getSelected(self): 1871 | return True if self.element.selected else False 1872 | 1873 | def _setSelected(self, val): 1874 | if val: 1875 | self.element.selected = True 1876 | else: 1877 | self.element.selected = False 1878 | 1879 | 1880 | class Output(Widget, _attrForm, _attrName, _attrFor): 1881 | _tagName = "output" 1882 | 1883 | 1884 | class Select(Widget, _attrDisabled, _attrForm, _attrAutofocus, _attrName, _attrRequired, _attrMultiple, _attrSize): 1885 | _tagName = "select" 1886 | 1887 | def _getSelectedIndex(self): 1888 | return self.element.selectedIndex 1889 | 1890 | def _getOptions(self): 1891 | return self.element.options 1892 | 1893 | 1894 | class Textarea(Widget, _attrDisabled, _attrForm, _attrAutofocus, _attrName, _attrInputs, _attrValue): 1895 | _tagName = "textarea" 1896 | 1897 | def _getCols(self): 1898 | return self.element.cols 1899 | 1900 | def _setCols(self, val): 1901 | self.element.cols = val 1902 | 1903 | def _getRows(self): 1904 | return self.element.rows 1905 | 1906 | def _setRows(self, val): 1907 | self.element.rows = val 1908 | 1909 | def _getWrap(self): 1910 | return self.element.wrap 1911 | 1912 | def _setWrap(self, val): 1913 | self.element.wrap = val 1914 | 1915 | 1916 | # Head ----------------------------------------------------------------------------------------------------------------- 1917 | 1918 | class HeadCls(Widget): 1919 | 1920 | def __init__(self, *args, **kwargs): 1921 | super().__init__(_wrapElem=domGetElementsByTagName("head")[0], *args, **kwargs) 1922 | self._isAttached = True 1923 | 1924 | 1925 | _head = None 1926 | 1927 | 1928 | def Head(): 1929 | global _head 1930 | if _head is None: 1931 | _head = HeadCls() 1932 | return _head 1933 | 1934 | 1935 | # Iframe --------------------------------------------------------------------------------------------------------------- 1936 | 1937 | class Iframe(Widget, _attrSrc, _attrName, _attrDimensions): 1938 | _tagName = "iframe" 1939 | 1940 | def _getSandbox(self): 1941 | return self.element.sandbox 1942 | 1943 | def _setSandbox(self, val): 1944 | self.element.sandbox = val 1945 | 1946 | def _getSrcdoc(self): 1947 | return self.element.src 1948 | 1949 | def _setSrcdoc(self, val): 1950 | self.element.src = val 1951 | 1952 | def _getSeamless(self): 1953 | return True if self.element.hasAttribute("seamless") else False 1954 | 1955 | def _setSeamless(self, val): 1956 | if val: 1957 | self.element.setAttribute("seamless", "") 1958 | else: 1959 | self.element.removeAttribute("seamless") 1960 | 1961 | 1962 | # Img ------------------------------------------------------------------------------------------------------------------ 1963 | 1964 | class Img(Widget, _attrSrc, _attrDimensions, _attrUsemap, _attrAlt): 1965 | _tagName = "img" 1966 | _leafTag = True 1967 | 1968 | def __init__(self, src=None, *args, **kwargs): 1969 | super().__init__() 1970 | if src: 1971 | self["src"] = src 1972 | 1973 | def _getCrossorigin(self): 1974 | return self.element.crossorigin 1975 | 1976 | def _setCrossorigin(self, val): 1977 | self.element.crossorigin = val 1978 | 1979 | def _getIsmap(self): 1980 | return self.element.ismap 1981 | 1982 | def _setIsmap(self, val): 1983 | self.element.ismap = val 1984 | 1985 | 1986 | # Ins ------------------------------------------------------------------------------------------------------------------ 1987 | 1988 | class Ins(Widget, _attrCite, _attrDatetime): 1989 | _tagName = "ins" 1990 | 1991 | 1992 | # Keygen --------------------------------------------------------------------------------------------------------------- 1993 | 1994 | class Keygen(Form, _attrAutofocus, _attrDisabled): 1995 | _tagName = "keygen" 1996 | 1997 | def _getChallenge(self): 1998 | return True if self.element.hasAttribute("challenge") else False 1999 | 2000 | def _setChallenge(self, val): 2001 | if val: 2002 | self.element.setAttribute("challenge", "") 2003 | else: 2004 | self.element.removeAttribute("challenge") 2005 | 2006 | def _getKeytype(self): 2007 | return self.element.keytype 2008 | 2009 | def _setKeytype(self, val): 2010 | self.element.keytype = val 2011 | 2012 | 2013 | # Link ----------------------------------------------------------------------------------------------------------------- 2014 | 2015 | class Link(Widget, _attrHref, _attrMedia, _attrRel): 2016 | _tagName = "link" 2017 | _leafTag = True 2018 | 2019 | def _getSizes(self): 2020 | return self.element.sizes 2021 | 2022 | def _setSizes(self, val): 2023 | self.element.sizes = val 2024 | 2025 | 2026 | # List ----------------------------------------------------------------------------------------------------------------- 2027 | 2028 | class Ul(Widget): 2029 | _tagName = "ul" 2030 | 2031 | 2032 | class Ol(Widget): 2033 | _tagName = "ol" 2034 | 2035 | 2036 | class Li(Widget): 2037 | _tagName = "li" 2038 | 2039 | 2040 | class Dl(Widget): 2041 | _tagName = "dl" 2042 | 2043 | 2044 | class Dt(Widget): 2045 | _tagName = "dt" 2046 | 2047 | 2048 | class Dd(Widget): 2049 | _tagName = "dd" 2050 | 2051 | 2052 | # Map ------------------------------------------------------------------------------------------------------------------ 2053 | 2054 | class Map(Label, _attrType): 2055 | _tagName = "map" 2056 | 2057 | 2058 | # Menu ----------------------------------------------------------------------------------------------------------------- 2059 | 2060 | class Menu(Widget): 2061 | _tagName = "menu" 2062 | 2063 | 2064 | # Meta ----------------------------------------------------------------------------------------------------------------- 2065 | 2066 | class Meta(Widget, _attrName, _attrCharset): 2067 | _tagName = "meta" 2068 | _leafTag = True 2069 | 2070 | def _getContent(self): 2071 | return self.element.content 2072 | 2073 | def _setContent(self, val): 2074 | self.element.content = val 2075 | 2076 | 2077 | # Meter ---------------------------------------------------------------------------------------------------------------- 2078 | 2079 | class Meter(Form, _attrValue): 2080 | _tagName = "meter" 2081 | 2082 | def _getHigh(self): 2083 | return self.element.high 2084 | 2085 | def _setHigh(self, val): 2086 | self.element.high = val 2087 | 2088 | def _getLow(self): 2089 | return self.element.low 2090 | 2091 | def _setLow(self, val): 2092 | self.element.low = val 2093 | 2094 | def _getMax(self): 2095 | return self.element.max 2096 | 2097 | def _setMax(self, val): 2098 | self.element.max = val 2099 | 2100 | def _getMin(self): 2101 | return self.element.min 2102 | 2103 | def _setMin(self, val): 2104 | self.element.min = val 2105 | 2106 | def _getOptimum(self): 2107 | return self.element.optimum 2108 | 2109 | def _setOptimum(self, val): 2110 | self.element.optimum = val 2111 | 2112 | 2113 | # Nav ------------------------------------------------------------------------------------------------------------------ 2114 | 2115 | class Nav(Widget): 2116 | _tagName = "nav" 2117 | 2118 | 2119 | # Object ----------------------------------------------------------------------------------------------------------------- 2120 | 2121 | class Object(Form, _attrType, _attrName, _attrDimensions, _attrUsemap): 2122 | _tagName = "object" 2123 | 2124 | 2125 | # Param ----------------------------------------------------------------------------------------------------------------- 2126 | 2127 | class Param(Widget, _attrName, _attrValue): 2128 | _tagName = "param" 2129 | _leafTag = True 2130 | 2131 | 2132 | # Progress ------------------------------------------------------------------------------------------------------------- 2133 | 2134 | class Progress(Widget, _attrValue): 2135 | _tagName = "progress" 2136 | 2137 | def _getMax(self): 2138 | return self.element.max 2139 | 2140 | def _setMax(self, val): 2141 | self.element.max = val 2142 | 2143 | 2144 | # Q -------------------------------------------------------------------------------------------------------------------- 2145 | 2146 | class Q(Widget, _attrCite): 2147 | _tagName = "q" 2148 | 2149 | 2150 | # Script ---------------------------------------------------------------------------------------------------------------- 2151 | 2152 | class Script(Widget, _attrSrc, _attrCharset): 2153 | _tagName = "script" 2154 | 2155 | def _getAsync(self): 2156 | return True if self.element.hasAttribute("async") else False 2157 | 2158 | def _setAsync(self, val): 2159 | if val: 2160 | self.element.setAttribute("async", "") 2161 | else: 2162 | self.element.removeAttribute("async") 2163 | 2164 | def _getDefer(self): 2165 | return True if self.element.hasAttribute("defer") else False 2166 | 2167 | def _setDefer(self, val): 2168 | if val: 2169 | self.element.setAttribute("defer", "") 2170 | else: 2171 | self.element.removeAttribute("defer") 2172 | 2173 | 2174 | # Source --------------------------------------------------------------------------------------------------------------- 2175 | 2176 | class Source(Widget, _attrMedia, _attrSrc): 2177 | _tagName = "source" 2178 | _leafTag = True 2179 | 2180 | 2181 | # Span ----------------------------------------------------------------------------------------------------------------- 2182 | 2183 | class Span(Widget): 2184 | _tagName = "span" 2185 | 2186 | 2187 | # Style ---------------------------------------------------------------------------------------------------------------- 2188 | 2189 | class Style(Widget, _attrMedia): 2190 | _tagName = "style" 2191 | 2192 | def _getScoped(self): 2193 | return True if self.element.hasAttribute("scoped") else False 2194 | 2195 | def _setScoped(self, val): 2196 | if val: 2197 | self.element.setAttribute("scoped", "") 2198 | else: 2199 | self.element.removeAttribute("scoped") 2200 | 2201 | 2202 | # Table ---------------------------------------------------------------------------------------------------------------- 2203 | 2204 | 2205 | class Tr(Widget): 2206 | _tagName = "tr" 2207 | 2208 | def _getRowspan(self): 2209 | span = self.element.getAttribute("rowspan") 2210 | return span if span else 1 2211 | 2212 | def _setRowspan(self, span): 2213 | assert span >= 1, "span may not be negative" 2214 | self.element.setAttribute("rowspan", span) 2215 | return self 2216 | 2217 | 2218 | class Td(Widget): 2219 | _tagName = "td" 2220 | 2221 | def _getColspan(self): 2222 | span = self.element.getAttribute("colspan") 2223 | return span if span else 1 2224 | 2225 | def _setColspan(self, span): 2226 | assert span >= 1, "span may not be negative" 2227 | self.element.setAttribute("colspan", span) 2228 | return self 2229 | 2230 | def _getRowspan(self): 2231 | span = self.element.getAttribute("rowspan") 2232 | return span if span else 1 2233 | 2234 | def _setRowspan(self, span): 2235 | assert span >= 1, "span may not be negative" 2236 | self.element.setAttribute("rowspan", span) 2237 | return self 2238 | 2239 | 2240 | class Th(Td): 2241 | _tagName = "th" 2242 | 2243 | 2244 | class Thead(Widget): 2245 | _tagName = "thead" 2246 | 2247 | 2248 | class Tbody(Widget): 2249 | _tagName = "tbody" 2250 | 2251 | 2252 | class ColWrapper(object): 2253 | def __init__(self, parentElem, *args, **kwargs): 2254 | super().__init__(*args, **kwargs) 2255 | self.parentElem = parentElem 2256 | 2257 | def __getitem__(self, item): 2258 | assert isinstance(item, int), "Invalid col-number. Expected int, got {}".format(str(type(item))) 2259 | if item < 0 or item > len(self.parentElem._children): 2260 | return None 2261 | 2262 | return self.parentElem._children[item] 2263 | 2264 | def __setitem__(self, key, value): 2265 | col = self[key] 2266 | assert col is not None, "Cannot assign widget to invalid column" 2267 | 2268 | col.removeAllChildren() 2269 | 2270 | if isinstance(value, list) or isinstance(value, tuple): 2271 | for el in value: 2272 | if isinstance(el, Widget) or isinstance(el, TextNode): 2273 | col.appendChild(value) 2274 | 2275 | elif isinstance(value, Widget) or isinstance(value, TextNode): 2276 | col.appendChild(value) 2277 | 2278 | 2279 | class RowWrapper(object): 2280 | def __init__(self, parentElem, *args, **kwargs): 2281 | super().__init__(*args, **kwargs) 2282 | self.parentElem = parentElem 2283 | 2284 | def __getitem__(self, item): 2285 | assert isinstance(item, int), "Invalid row-number. Expected int, got {}".format(str(type(item))) 2286 | if item < 0 or item > len(self.parentElem._children): 2287 | return None 2288 | 2289 | return ColWrapper(self.parentElem._children[item]) 2290 | 2291 | 2292 | class Table(Widget): 2293 | _tagName = "table" 2294 | 2295 | def __init__(self, *args, **kwargs): 2296 | super().__init__(*args, **kwargs) 2297 | self.head = Thead() 2298 | self.body = Tbody() 2299 | self.appendChild(self.head) 2300 | self.appendChild(self.body) 2301 | 2302 | def prepareRow(self, row): 2303 | assert row >= 0, "Cannot create rows with negative index" 2304 | 2305 | for child in self.body._children: 2306 | row -= child["rowspan"] 2307 | if row < 0: 2308 | return 2309 | 2310 | while row >= 0: 2311 | self.body.appendChild(Tr()) 2312 | row -= 1 2313 | 2314 | def prepareCol(self, row, col): 2315 | assert col >= 0, "Cannot create cols with negative index" 2316 | self.prepareRow(row) 2317 | 2318 | for rowChild in self.body._children: 2319 | row -= rowChild["rowspan"] 2320 | 2321 | if row < 0: 2322 | for colChild in rowChild._children: 2323 | col -= colChild["colspan"] 2324 | if col < 0: 2325 | return 2326 | 2327 | while col >= 0: 2328 | rowChild.appendChild(Td()) 2329 | col -= 1 2330 | 2331 | return 2332 | 2333 | def prepareGrid(self, rows, cols): 2334 | for row in range(self.getRowCount(), self.getRowCount() + rows): 2335 | self.prepareCol(row, cols) 2336 | 2337 | def clear(self): 2338 | for row in self.body._children[:]: 2339 | 2340 | for col in row._children[:]: 2341 | row.removeChild(col) 2342 | 2343 | self.body.removeChild(row) 2344 | 2345 | def _getCell(self): 2346 | return RowWrapper(self.body) 2347 | 2348 | def getRowCount(self): 2349 | cnt = 0 2350 | 2351 | for tr in self.body._children: 2352 | cnt += tr["rowspan"] 2353 | 2354 | return cnt 2355 | 2356 | 2357 | # Time ----------------------------------------------------------------------------------------------------------------- 2358 | 2359 | class Time(Widget, _attrDatetime): 2360 | _tagName = "time" 2361 | 2362 | 2363 | # Track ---------------------------------------------------------------------------------------------------------------- 2364 | 2365 | class Track(Label, _attrSrc): 2366 | _tagName = "track" 2367 | _leafTag = True 2368 | 2369 | def _getKind(self): 2370 | return self.element.kind 2371 | 2372 | def _setKind(self, val): 2373 | self.element.kind = val 2374 | 2375 | def _getSrclang(self): 2376 | return self.element.srclang 2377 | 2378 | def _setSrclang(self, val): 2379 | self.element.srclang = val 2380 | 2381 | def _getDefault(self): 2382 | return True if self.element.hasAttribute("default") else False 2383 | 2384 | def _setDefault(self, val): 2385 | if val: 2386 | self.element.setAttribute("default", "") 2387 | else: 2388 | self.element.removeAttribute("default") 2389 | 2390 | 2391 | # Video ---------------------------------------------------------------------------------------------------------------- 2392 | 2393 | class Video(Widget, _attrSrc, _attrDimensions, _attrMultimedia): 2394 | _tagName = "video" 2395 | 2396 | def _getPoster(self): 2397 | return self.element.poster 2398 | 2399 | def _setPoster(self, val): 2400 | self.element.poster = val 2401 | 2402 | 2403 | # Template ------------------------------------------------------------------------------------------------------------- 2404 | 2405 | class Template(Widget): 2406 | _tagName = "template" 2407 | 2408 | 2409 | ######################################################################################################################## 2410 | # Utilities 2411 | ######################################################################################################################## 2412 | 2413 | def unescape(val, maxLength=0): 2414 | """ 2415 | Unquotes several HTML-quoted characters in a string. 2416 | 2417 | :param val: The value to be unescaped. 2418 | :type val: str 2419 | 2420 | :param maxLength: Cut-off after maxLength characters. 2421 | A value of 0 means "unlimited". (default) 2422 | :type maxLength: int 2423 | 2424 | :returns: The unquoted string. 2425 | :rtype: str 2426 | """ 2427 | val = val \ 2428 | .replace("<", "<") \ 2429 | .replace(">", ">") \ 2430 | .replace(""", "\"") \ 2431 | .replace("'", "'") 2432 | 2433 | if maxLength > 0: 2434 | return val[0:maxLength] 2435 | 2436 | return val 2437 | 2438 | 2439 | def doesEventHitWidgetOrParents(event, widget): 2440 | """ 2441 | Test if event 'event' hits widget 'widget' (or *any* of its parents) 2442 | """ 2443 | while widget: 2444 | if event.target == widget.element: 2445 | return True 2446 | 2447 | widget = widget.parent() 2448 | 2449 | return False 2450 | 2451 | 2452 | def doesEventHitWidgetOrChildren(event, widget): 2453 | """ 2454 | Test if event 'event' hits widget 'widget' (or *any* of its children) 2455 | """ 2456 | if event.target == widget.element: 2457 | return True 2458 | 2459 | for child in widget._children: 2460 | if doesEventHitWidgetOrChildren(event, child): 2461 | return True 2462 | 2463 | return False 2464 | 2465 | 2466 | def textToHtml(node, text): 2467 | """ 2468 | Generates html nodes from text by splitting text into content and into 2469 | line breaks html5.Br. 2470 | 2471 | :param node: The node where the nodes are appended to. 2472 | :param text: The text to be inserted. 2473 | """ 2474 | 2475 | for (i, part) in enumerate(text.split("\n")): 2476 | if i > 0: 2477 | node.appendChild(Br()) 2478 | 2479 | node.appendChild(TextNode(part)) 2480 | 2481 | 2482 | def parseInt(s, ret=0): 2483 | """ 2484 | Parses a value as int 2485 | """ 2486 | if not isinstance(s, str): 2487 | return int(s) 2488 | elif s: 2489 | if s[0] in "+-": 2490 | ts = s[1:] 2491 | else: 2492 | ts = s 2493 | 2494 | if ts and all([_ in "0123456789" for _ in ts]): 2495 | return int(s) 2496 | 2497 | return ret 2498 | 2499 | 2500 | def parseFloat(s, ret=0.0): 2501 | """ 2502 | Parses a value as float. 2503 | """ 2504 | if not isinstance(s, str): 2505 | return float(s) 2506 | elif s: 2507 | if s[0] in "+-": 2508 | ts = s[1:] 2509 | else: 2510 | ts = s 2511 | 2512 | if ts and ts.count(".") <= 1 and all([_ in ".0123456789" for _ in ts]): 2513 | return float(s) 2514 | 2515 | return ret 2516 | 2517 | 2518 | ######################################################################################################################## 2519 | # Keycodes 2520 | ######################################################################################################################## 2521 | 2522 | def getKey(event): 2523 | """ 2524 | Returns the Key Identifier of the given event 2525 | 2526 | Available Codes: https://www.w3.org/TR/2006/WD-DOM-Level-3-Events-20060413/keyset.html#KeySet-Set 2527 | """ 2528 | if hasattr(event, "key"): 2529 | return event.key 2530 | 2531 | elif hasattr(event, "keyIdentifier"): 2532 | if event.keyIdentifier in ["Esc", "U+001B"]: 2533 | return "Escape" 2534 | else: 2535 | return event.keyIdentifier 2536 | 2537 | return None 2538 | 2539 | 2540 | def isArrowLeft(event): 2541 | return getKey(event) in ["ArrowLeft", "Left"] 2542 | 2543 | def isArrowUp(event): 2544 | return getKey(event) in ["ArrowUp", "Up"] 2545 | 2546 | def isArrowRight(event): 2547 | return getKey(event) in ["ArrowRight", "Right"] 2548 | 2549 | def isArrowDown(event): 2550 | return getKey(event) in ["ArrowDown", "Down"] 2551 | 2552 | def isEscape(event): 2553 | return getKey(event) == "Escape" 2554 | 2555 | def isReturn(event): 2556 | return getKey(event) == "Enter" 2557 | 2558 | def isControl(event): # The Control (Ctrl) key. 2559 | return getKey(event) == "Control" 2560 | 2561 | def isShift(event): 2562 | return getKey(event) == "Shift" 2563 | 2564 | 2565 | ######################################################################################################################## 2566 | # HTML parser 2567 | ######################################################################################################################## 2568 | 2569 | # Global variables required by HTML parser 2570 | __tags = None 2571 | __domParser = None 2572 | 2573 | 2574 | def registerTag(tagName, widgetClass, override=True): 2575 | assert issubclass(widgetClass, Widget), "widgetClass must be a sub-class of Widget!" 2576 | global __tags 2577 | 2578 | if __tags is None: 2579 | _buildTags() 2580 | 2581 | if not override and tagName.lower() in __tags: 2582 | return 2583 | 2584 | attr = [] 2585 | 2586 | for fname in dir(widgetClass): 2587 | if fname.startswith("_set"): 2588 | attr.append(fname[4:].lower()) 2589 | 2590 | __tags[tagName.lower()] = (widgetClass, attr) 2591 | 2592 | 2593 | def tag(cls): 2594 | assert issubclass(cls, Widget) 2595 | # This is a little bit ugly but works for the svg... 2596 | if str(cls.__module__).split(".")[-2] == "html5": 2597 | registerTag(cls._parserTagName or cls._tagName or cls.__name__, cls) 2598 | else: 2599 | registerTag(cls._parserTagName or cls.__name__, cls) # do NOT check for cls._tagName here!!! 2600 | 2601 | return cls 2602 | 2603 | 2604 | def _buildTags(debug=False): 2605 | """ 2606 | Generates a dictionary of all to the html5-library 2607 | known tags and their associated objects and attributes. 2608 | """ 2609 | global __tags 2610 | 2611 | if __tags is not None: 2612 | return 2613 | 2614 | if __tags is None: 2615 | __tags = {} 2616 | 2617 | for cname in globals().keys(): 2618 | if cname.startswith("_"): 2619 | continue 2620 | 2621 | cls = globals()[cname] 2622 | 2623 | try: 2624 | if not issubclass(cls, Widget): 2625 | continue 2626 | except: 2627 | continue 2628 | 2629 | registerTag(cls._parserTagName or cls._tagName or cls.__name__, cls, override=False) 2630 | 2631 | if debug: 2632 | for tag in sorted(__tags.keys()): 2633 | print("{}: {}".format(tag, ", ".join(sorted(__tags[tag][1])))) 2634 | 2635 | 2636 | class HtmlAst(list): 2637 | pass 2638 | 2639 | 2640 | def parseHTML(html, debug=False): 2641 | """ 2642 | Parses the provided HTML-code according to the objects defined in the html5-library. 2643 | """ 2644 | 2645 | def convertEncodedText(txt): 2646 | """ 2647 | Convert HTML-encoded text into decoded string. 2648 | 2649 | The reason for this function is the handling of HTML entities, which is not 2650 | properly supported by native JavaScript. 2651 | 2652 | We use the browser's DOM parser to to this, according to 2653 | https://stackoverflow.com/questions/3700326/decode-amp-back-to-in-javascript 2654 | 2655 | :param txt: The encoded text. 2656 | :return: The decoded text. 2657 | """ 2658 | global __domParser 2659 | 2660 | if jseval is None: 2661 | return txt 2662 | 2663 | if __domParser is None: 2664 | __domParser = jseval("new DOMParser") 2665 | 2666 | dom = __domParser.parseFromString("" + str(txt), "text/html") 2667 | return dom.body.textContent 2668 | 2669 | def scanWhite(l): 2670 | """ 2671 | Scan and return whitespace. 2672 | """ 2673 | 2674 | ret = "" 2675 | while l and l[0] in " \t\r\n": 2676 | ret += l.pop(0) 2677 | 2678 | return ret 2679 | 2680 | def scanWord(l): 2681 | """ 2682 | Scan and return a word. 2683 | """ 2684 | 2685 | ret = "" 2686 | while l and l[0] not in " \t\r\n" + "<>=\"'": 2687 | ret += l.pop(0) 2688 | 2689 | return ret 2690 | 2691 | stack = [] 2692 | 2693 | # Obtain tag descriptions, if not already done! 2694 | global __tags 2695 | 2696 | if __tags is None: 2697 | _buildTags(debug=debug) 2698 | 2699 | # Prepare stack and input 2700 | stack.append((None, None, HtmlAst())) 2701 | html = [ch for ch in html] 2702 | 2703 | # Parse 2704 | while html: 2705 | tag = None 2706 | text = "" 2707 | 2708 | # Auto-close leaf elements, e.g. like
,
, etc. 2709 | while stack and stack[-1][0] and __tags[stack[-1][0]][0]._leafTag: 2710 | stack.pop() 2711 | 2712 | if not stack: 2713 | break 2714 | 2715 | parent = stack[-1][2] 2716 | 2717 | while html: 2718 | ch = html.pop(0) 2719 | 2720 | # Comment 2721 | if html and ch == "<" and "".join(html[:3]) == "!--": 2722 | html = html[3:] 2723 | while html and "".join(html[:3]) != "-->": 2724 | html.pop(0) 2725 | 2726 | html = html[3:] 2727 | 2728 | # Opening tag 2729 | elif html and ch == "<" and html[0] != "/": 2730 | tag = scanWord(html) 2731 | if tag.lower() in __tags: 2732 | break 2733 | 2734 | text += ch + tag 2735 | 2736 | # Closing tag 2737 | elif html and stack[-1][0] and ch == "<" and html[0] == "/": 2738 | junk = ch 2739 | junk += html.pop(0) 2740 | 2741 | tag = scanWord(html) 2742 | junk += tag 2743 | 2744 | if stack[-1][0] == tag.lower(): 2745 | junk += scanWhite(html) 2746 | if html and html[0] == ">": 2747 | html.pop(0) 2748 | stack.pop() 2749 | tag = None 2750 | break 2751 | 2752 | text += junk 2753 | tag = None 2754 | 2755 | else: 2756 | text += ch 2757 | 2758 | # Append plain text (if not only whitespace) 2759 | if (text and ((len(text) == 1 and text in ["\t "]) 2760 | or not all([ch in " \t\r\n" for ch in text]))): 2761 | # print("text", text) 2762 | parent.append(convertEncodedText(text)) 2763 | 2764 | # Create tag 2765 | if tag: 2766 | tag = tag.lower() 2767 | # print("tag", tag) 2768 | 2769 | elem = (tag, {}, HtmlAst()) 2770 | 2771 | stack.append(elem) 2772 | parent.append(elem) 2773 | 2774 | while html: 2775 | scanWhite(html) 2776 | if not html: 2777 | break 2778 | 2779 | # End of tag > 2780 | if html[0] == ">": 2781 | html.pop(0) 2782 | break 2783 | 2784 | # Closing tag at end /> 2785 | elif html[0] == "/": 2786 | html.pop(0) 2787 | scanWhite(html) 2788 | 2789 | if html[0] == ">": 2790 | stack.pop() 2791 | html.pop(0) 2792 | break 2793 | 2794 | val = att = scanWord(html).lower() 2795 | 2796 | if not att: 2797 | html.pop(0) 2798 | continue 2799 | 2800 | scanWhite(html) 2801 | if html[0] == "=": 2802 | html.pop(0) 2803 | scanWhite(html) 2804 | 2805 | if html[0] in "\"'": 2806 | ch = html.pop(0) 2807 | 2808 | val = "" 2809 | while html and html[0] != ch: 2810 | val += html.pop(0) 2811 | 2812 | html.pop(0) 2813 | 2814 | if att not in elem[1]: 2815 | elem[1][att] = val 2816 | else: 2817 | elem[1][att] += " " + val 2818 | 2819 | continue 2820 | 2821 | while stack and stack[-1][0]: 2822 | stack.pop() 2823 | 2824 | return stack[0][2] 2825 | 2826 | def fromHTML(html, appendTo=None, bindTo=None, debug=False, vars=None, **kwargs): 2827 | """ 2828 | Parses the provided HTML code according to the objects defined in the html5-library. 2829 | html can also be pre-compiled by `parseHTML()` so that it executes faster. 2830 | 2831 | Constructs all objects as DOM nodes. The first level is chained into appendTo. 2832 | If no appendTo is provided, appendTo will be set to html5.Body(). 2833 | 2834 | If bindTo is provided, objects are bound to this widget. 2835 | 2836 | ```python 2837 | from vi import html5 2838 | 2839 | div = html5.Div() 2840 | html5.parse.fromHTML(''' 2841 |
Yeah! 2842 | 2843 | hah ala malla" bababtschga" 2844 | st 2845 | ahralla malla tralla da 2846 | lala 2847 |
''', div) 2848 | 2849 | div.myLink.appendChild("appended!") 2850 | ``` 2851 | """ 2852 | 2853 | # Handle defaults 2854 | if bindTo is None: 2855 | bindTo = appendTo 2856 | 2857 | if isinstance(html, str): 2858 | html = parseHTML(html, debug=debug) 2859 | 2860 | assert isinstance(html, HtmlAst) 2861 | 2862 | if isinstance(vars, dict): 2863 | kwargs.update(vars) 2864 | 2865 | def replaceVars(txt): 2866 | for var, val in kwargs.items(): 2867 | txt = txt.replace("{{%s}}" % var, str(val) if val is not None else "") 2868 | 2869 | return txt 2870 | 2871 | def interpret(parent, items): 2872 | ret = [] 2873 | 2874 | for item in items: 2875 | if isinstance(item, str): 2876 | txt = TextNode(replaceVars(item)) 2877 | 2878 | if parent: 2879 | parent.appendChild(txt) 2880 | 2881 | ret.append(txt) 2882 | continue 2883 | 2884 | tag = item[0] 2885 | atts = item[1] 2886 | children = item[2] 2887 | 2888 | # Special handling for tables: A "thead" and "tbody" are already part of table! 2889 | if tag in ["thead", "tbody"] and isinstance(parent, Table): 2890 | wdg = getattr(parent, tag[1:]) 2891 | 2892 | # Usual way: Construct new element and chain it into the parent. 2893 | else: 2894 | wdg = __tags[tag][0]() 2895 | 2896 | for att, val in atts.items(): 2897 | val = replaceVars(val) 2898 | 2899 | # The [name] attribute binds the current widget to bindTo under the provided name! 2900 | if att == "[name]": 2901 | # Allow disable binding! 2902 | if not bindTo: 2903 | logging.warning("html5: Unable to evaluate %r due unset bindTo", att) 2904 | continue 2905 | 2906 | if getattr(bindTo, val, None): 2907 | logging.warning("html5: Cannot assign name %r because it already exists in %r", val, bindTo) 2908 | 2909 | elif not (any([val.startswith(x) for x in string.ascii_letters + "_"]) 2910 | and all([x in string.ascii_letters + string.digits + "_" for x in val[1:]])): 2911 | logging.warning("html5: Cannot assign name %r because it contains invalid characters", val) 2912 | 2913 | else: 2914 | setattr(bindTo, val, wdg) 2915 | wdg.onBind(bindTo, val) 2916 | 2917 | if debug: #fixme: remove debug flag! 2918 | logging.debug("html5: %r assigned to %r", val, bindTo) 2919 | 2920 | # Class is handled via Widget.addClass() 2921 | elif att == "class": 2922 | # print(tag, att, val.split()) 2923 | wdg.addClass(*val.split()) 2924 | 2925 | elif att == "disabled": 2926 | # print(tag, att, val) 2927 | if val == "disabled": 2928 | wdg.disable() 2929 | 2930 | elif att == "hidden": 2931 | # print(tag, att, val) 2932 | if val == "hidden": 2933 | wdg.hide() 2934 | 2935 | # style-attributes must be split into its separate parts to be mapped into the dict. 2936 | elif att == "style": 2937 | for dfn in val.split(";"): 2938 | if ":" not in dfn: 2939 | continue 2940 | 2941 | att, val = dfn.split(":", 1) 2942 | 2943 | # print(tag, "style", att.strip(), val.strip()) 2944 | wdg["style"][att.strip()] = val.strip() 2945 | 2946 | # data attributes are mapped into a related dict. 2947 | elif att.startswith("data-"): 2948 | wdg["data"][att[5:]] = val 2949 | 2950 | # transfer attributes from the binder into current widget 2951 | elif att.startswith(":"): 2952 | if bindTo: 2953 | try: 2954 | setattr(wdg, att[1:], getattr(bindTo, val)) 2955 | except Exception as e: 2956 | logging.exception(e) 2957 | else: 2958 | logging.error("html5: bindTo is unset, can't use %r here", att) 2959 | 2960 | # add event listener on current widget to callbacks on the binder 2961 | elif att.startswith("@"): 2962 | if bindTo: 2963 | try: 2964 | callback = getattr(bindTo, val) 2965 | assert callable(callback), f"{callback} is not callable" 2966 | 2967 | except Exception as e: 2968 | print(e) 2969 | continue 2970 | 2971 | wdg.element.addEventListener(att[1:], callback) 2972 | 2973 | else: 2974 | print("html5: bindTo is unset, can't use %r here", att) 2975 | 2976 | # Otherwise, either store widget attribute or save value on widget. 2977 | else: 2978 | try: 2979 | wdg[att] = parseInt(val, val) 2980 | 2981 | except ValueError: 2982 | if att in dir(wdg): 2983 | logging.error("html5: Attribute %r already defined for %r", att, wdg) 2984 | else: 2985 | setattr(wdg, att, val) 2986 | 2987 | except Exception as e: 2988 | logging.exception(e) 2989 | 2990 | interpret(wdg, children) 2991 | 2992 | if parent and not wdg.parent(): 2993 | parent.appendChild(wdg) 2994 | 2995 | ret.append(wdg) 2996 | 2997 | return ret 2998 | 2999 | return interpret(appendTo, html) 3000 | 3001 | 3002 | if __name__ == '__main__': 3003 | print(globals()) 3004 | -------------------------------------------------------------------------------- /ext.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################ 4 | # NOTE: This part of the html5 library is superseded by flare. # 5 | # It is not improved anymore, and just remains here for # 6 | # existing projects. # 7 | # # 8 | # Visit https://github.com/mausbrand/flare for details. # 9 | ################################################################ 10 | 11 | from . import core as html5 12 | from . import utils 13 | 14 | class Button(html5.Button): 15 | 16 | def __init__(self, txt=None, callback=None, className=None, *args, **kwargs): 17 | super().__init__(*args, **kwargs) 18 | self["class"] = "btn" 19 | 20 | if className: 21 | self.addClass(className) 22 | 23 | self["type"] = "button" 24 | 25 | if txt is not None: 26 | self.setText(txt) 27 | 28 | self.callback = callback 29 | self.sinkEvent("onClick") 30 | 31 | def setText(self, txt): 32 | if txt is not None: 33 | self.element.innerHTML = txt 34 | self["title"] = txt 35 | else: 36 | self.element.innerHTML = "" 37 | self["title"] = "" 38 | 39 | def onClick(self, event): 40 | event.stopPropagation() 41 | event.preventDefault() 42 | if self.callback is not None: 43 | self.callback(self) 44 | 45 | 46 | class Input(html5.Input): 47 | def __init__(self, type="text", placeholder=None, callback=None, id=None, focusCallback=None, *args, **kwargs): 48 | """ 49 | 50 | :param type: Input type. Default: "text 51 | :param placeholder: Placeholder text. Default: None 52 | :param callback: Function to be called onChanged: callback(id, value) 53 | :param id: Optional id of the input element. Will be passed to callback 54 | :return: 55 | """ 56 | super().__init__(*args, **kwargs) 57 | self["class"] = "input" 58 | self["type"] = type 59 | if placeholder is not None: 60 | self["placeholder"] = placeholder 61 | 62 | self.callback = callback 63 | if id is not None: 64 | self["id"] = id 65 | self.sinkEvent("onChange") 66 | 67 | self.focusCallback = focusCallback 68 | if focusCallback: 69 | self.sinkEvent("onFocus") 70 | 71 | def onChange(self, event): 72 | event.stopPropagation() 73 | event.preventDefault() 74 | if self.callback is not None: 75 | self.callback(self, self["id"], self["value"]) 76 | 77 | def onFocus(self, event): 78 | event.stopPropagation() 79 | event.preventDefault() 80 | if self.focusCallback is not None: 81 | self.focusCallback(self, self["id"], self["value"]) 82 | 83 | def onDetach(self): 84 | super().onDetach() 85 | self.callback = None 86 | 87 | 88 | class Popup(html5.Div): 89 | def __init__(self, title=None, id=None, className=None, icon=None, enableShortcuts=True, closeable=True, *args, **kwargs): 90 | super().__init__(""" 91 |
92 |
93 |
94 |
95 | 96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | """) 106 | 107 | self.appendChild = self.popupBody.appendChild 108 | self.fromHTML = lambda *args, **kwargs: self.popupBody.fromHTML(*args, **kwargs) if kwargs.get("bindTo") else self.popupBody.fromHTML(bindTo=self, *args, **kwargs) 109 | 110 | self["class"] = "popup popup--center is-active" 111 | if className: 112 | self.addClass(className) 113 | 114 | if closeable: 115 | closeBtn = Button("×", self.close, className="item-action") 116 | closeBtn.removeClass("btn") 117 | self.popupHeadItem.appendChild(closeBtn) 118 | 119 | if title: 120 | self.popupHeadline.appendChild(title) 121 | 122 | if icon: 123 | self.popupIcon.appendChild(icon[0]) 124 | elif title: 125 | self.popupIcon.appendChild(title[0]) 126 | else: 127 | self.popupIcon.appendChild("Vi") #fixme!!! this _LIBRARY_ is not only used in the Vi... 128 | 129 | # id can be used to pass information to callbacks 130 | self.id = id 131 | 132 | #FIXME: Implement a global overlay! One popupOverlay next to a list of popups. 133 | self.popupOverlay = html5.Div() 134 | self.popupOverlay["class"] = "popup-overlay is-active" 135 | 136 | self.enableShortcuts = enableShortcuts 137 | self.onDocumentKeyDownMethod = None 138 | 139 | self.popupOverlay.appendChild(self) 140 | html5.Body().appendChild(self.popupOverlay) 141 | 142 | #FIXME: Close/Cancel every popup with click on popupCloseBtn without removing the global overlay. 143 | 144 | def onAttach(self): 145 | super(Popup, self).onAttach() 146 | 147 | if self.enableShortcuts: 148 | self.onDocumentKeyDownMethod = self.onDocumentKeyDown # safe reference to method 149 | html5.document.addEventListener("keydown", self.onDocumentKeyDownMethod) 150 | 151 | def onDetach(self): 152 | super(Popup, self).onDetach() 153 | 154 | if self.enableShortcuts: 155 | html5.document.removeEventListener("keydown", self.onDocumentKeyDownMethod) 156 | 157 | def onDocumentKeyDown(self, event): 158 | if html5.isEscape(event): 159 | self.close() 160 | 161 | def close(self, *args, **kwargs): 162 | html5.Body().removeChild(self.popupOverlay) 163 | self.popupOverlay = None 164 | 165 | 166 | 167 | class InputDialog(Popup): 168 | def __init__(self, text, value="", successHandler=None, abortHandler=None, 169 | successLbl="OK", abortLbl="Cancel", placeholder="", *args, **kwargs): 170 | 171 | super().__init__(*args, **kwargs) 172 | self.addClass("popup--inputdialog") 173 | 174 | self.sinkEvent("onKeyDown", "onKeyUp") 175 | 176 | self.successHandler = successHandler 177 | self.abortHandler = abortHandler 178 | 179 | self.fromHTML( 180 | """ 181 |
182 | 185 | 186 |
187 | """, 188 | vars={ 189 | "text": text, 190 | "value": value, 191 | "placeholder": placeholder 192 | } 193 | ) 194 | 195 | # Cancel 196 | self.popupFoot.appendChild(Button(abortLbl, self.onCancel, className="btn--cancel btn--danger")) 197 | 198 | # Okay 199 | self.okayBtn = Button(successLbl, self.onOkay, className="btn--okay btn--primary") 200 | if not value: 201 | self.okayBtn.disable() 202 | 203 | self.popupFoot.appendChild(self.okayBtn) 204 | 205 | self.inputElem.focus() 206 | 207 | def onKeyDown(self, event): 208 | if html5.isReturn(event) and self.inputElem["value"]: 209 | event.stopPropagation() 210 | event.preventDefault() 211 | self.onOkay() 212 | 213 | def onKeyUp(self, event): 214 | if self.inputElem["value"]: 215 | self.okayBtn.enable() 216 | else: 217 | self.okayBtn.disable() 218 | 219 | def onDocumentKeyDown(self, event): 220 | if html5.isEscape(event): 221 | event.stopPropagation() 222 | event.preventDefault() 223 | self.onCancel() 224 | 225 | def onOkay(self, *args, **kwargs): 226 | if self.successHandler: 227 | self.successHandler(self, self.inputElem["value"]) 228 | self.close() 229 | 230 | def onCancel(self, *args, **kwargs): 231 | if self.abortHandler: 232 | self.abortHandler(self, self.inputElem["value"]) 233 | self.close() 234 | 235 | 236 | class Alert(Popup): 237 | """ 238 | Just displaying an alerting message box with OK-button. 239 | """ 240 | 241 | def __init__(self, msg, title=None, className=None, okCallback=None, okLabel="OK", icon="!", closeable=True, *args, **kwargs): 242 | super().__init__(title, className=None, icon=icon, closeable=closeable, *args, **kwargs) 243 | self.addClass("popup--alert") 244 | 245 | if className: 246 | self.addClass(className) 247 | 248 | self.okCallback = okCallback 249 | 250 | message = html5.Span() 251 | message.addClass("alert-msg") 252 | self.popupBody.appendChild(message) 253 | 254 | if isinstance(msg, str): 255 | msg = msg.replace("\n", "
") 256 | 257 | message.appendChild(msg, bindTo=False) 258 | 259 | self.sinkEvent("onKeyDown") 260 | 261 | if closeable: 262 | okBtn = Button(okLabel, callback=self.onOkBtnClick) 263 | okBtn.addClass("btn--okay btn--primary") 264 | self.popupFoot.appendChild(okBtn) 265 | 266 | okBtn.focus() 267 | 268 | def drop(self): 269 | self.okCallback = None 270 | self.close() 271 | 272 | def onOkBtnClick(self, sender=None): 273 | if self.okCallback: 274 | self.okCallback(self) 275 | 276 | self.drop() 277 | 278 | def onKeyDown(self, event): 279 | if html5.isReturn(event): 280 | event.stopPropagation() 281 | event.preventDefault() 282 | self.onOkBtnClick() 283 | 284 | 285 | class YesNoDialog(Popup): 286 | def __init__(self, question, title=None, yesCallback=None, noCallback=None, 287 | yesLabel="Yes", noLabel="No", icon="?", 288 | closeable=False, *args, **kwargs): 289 | super().__init__(title, closeable=closeable, icon=icon, *args, **kwargs) 290 | self.addClass("popup--yesnodialog") 291 | 292 | self.yesCallback = yesCallback 293 | self.noCallback = noCallback 294 | 295 | lbl = html5.Span() 296 | lbl["class"].append("question") 297 | self.popupBody.appendChild(lbl) 298 | 299 | if isinstance(question, html5.Widget): 300 | lbl.appendChild(question) 301 | else: 302 | utils.textToHtml(lbl, question) 303 | 304 | if len(noLabel): 305 | btnNo = Button(noLabel, className="btn--no", callback=self.onNoClicked) 306 | #btnNo["class"].append("btn--no") 307 | self.popupFoot.appendChild(btnNo) 308 | 309 | btnYes = Button(yesLabel, callback=self.onYesClicked) 310 | btnYes["class"].append("btn--yes") 311 | self.popupFoot.appendChild(btnYes) 312 | 313 | self.sinkEvent("onKeyDown") 314 | btnYes.focus() 315 | 316 | def onKeyDown(self, event): 317 | if html5.isReturn(event): 318 | event.stopPropagation() 319 | event.preventDefault() 320 | self.onYesClicked() 321 | 322 | def onDocumentKeyDown(self, event): 323 | if html5.isEscape(event): 324 | event.stopPropagation() 325 | event.preventDefault() 326 | self.onNoClicked() 327 | 328 | def drop(self): 329 | self.yesCallback = None 330 | self.noCallback = None 331 | self.close() 332 | 333 | def onYesClicked(self, *args, **kwargs): 334 | if self.yesCallback: 335 | self.yesCallback(self) 336 | 337 | self.drop() 338 | 339 | def onNoClicked(self, *args, **kwargs): 340 | if self.noCallback: 341 | self.noCallback(self) 342 | 343 | self.drop() 344 | 345 | 346 | class SelectDialog(Popup): 347 | 348 | def __init__(self, prompt, items=None, title=None, okBtn="OK", cancelBtn="Cancel", forceSelect=False, 349 | callback=None, *args, **kwargs): 350 | super().__init__(title, *args, **kwargs) 351 | self["class"].append("popup--selectdialog") 352 | 353 | self.callback = callback 354 | self.items = items 355 | assert isinstance(self.items, list) 356 | 357 | # Prompt 358 | if prompt: 359 | lbl = html5.Span() 360 | lbl["class"].append("prompt") 361 | 362 | if isinstance(prompt, html5.Widget): 363 | lbl.appendChild(prompt) 364 | else: 365 | utils.textToHtml(lbl, prompt) 366 | 367 | self.popupBody.appendChild(lbl) 368 | 369 | # Items 370 | if not forceSelect and len(items) <= 3: 371 | for idx, item in enumerate(items): 372 | if isinstance(item, dict): 373 | title = item.get("title") 374 | cssc = item.get("class") 375 | elif isinstance(item, tuple): 376 | title = item[1] 377 | cssc = None 378 | else: 379 | title = item 380 | 381 | btn = Button(title, callback=self.onAnyBtnClick) 382 | btn.idx = idx 383 | 384 | if cssc: 385 | btn.addClass(cssc) 386 | 387 | self.popupBody.appendChild(btn) 388 | else: 389 | self.select = html5.Select() 390 | self.popupBody.appendChild(self.select) 391 | 392 | for idx, item in enumerate(items): 393 | if isinstance(item, dict): 394 | title = item.get("title") 395 | elif isinstance(item, tuple): 396 | title = item[1] 397 | else: 398 | title = item 399 | 400 | opt = html5.Option(title) 401 | opt["value"] = str(idx) 402 | 403 | self.select.appendChild(opt) 404 | 405 | if okBtn: 406 | self.popupFoot.appendChild(Button(okBtn, callback=self.onOkClick)) 407 | 408 | if cancelBtn: 409 | self.popupFoot.appendChild(Button(cancelBtn, callback=self.onCancelClick)) 410 | 411 | def onAnyBtnClick(self, sender): 412 | item = self.items[sender.idx] 413 | 414 | if isinstance(item, dict) and item.get("callback") and callable(item["callback"]): 415 | item["callback"](item) 416 | 417 | if self.callback: 418 | self.callback(item) 419 | 420 | self.items = None 421 | self.close() 422 | 423 | def onCancelClick(self, sender=None): 424 | self.close() 425 | 426 | def onOkClick(self, sender=None): 427 | assert self.select["selectedIndex"] >= 0 428 | item = self.items[int(self.select.children(self.select["selectedIndex"])["value"])] 429 | 430 | if isinstance(item, dict) and item.get("callback") and callable(item["callback"]): 431 | item["callback"](item) 432 | 433 | if self.callback: 434 | self.callback(item) 435 | 436 | self.items = None 437 | self.select = None 438 | self.close() 439 | 440 | 441 | class TextareaDialog(Popup): 442 | def __init__(self, text, value="", successHandler=None, abortHandler=None, successLbl="OK", abortLbl="Cancel", 443 | *args, **kwargs): 444 | super().__init__(*args, **kwargs) 445 | self["class"].append("popup--textareadialog") 446 | 447 | self.successHandler = successHandler 448 | self.abortHandler = abortHandler 449 | 450 | span = html5.Span() 451 | span.element.innerHTML = text 452 | self.popupBody.appendChild(span) 453 | 454 | self.inputElem = html5.Textarea() 455 | self.inputElem["value"] = value 456 | self.popupBody.appendChild(self.inputElem) 457 | 458 | okayBtn = Button(successLbl, self.onOkay) 459 | okayBtn["class"].append("btn--okay") 460 | self.popupFoot.appendChild(okayBtn) 461 | 462 | cancelBtn = Button(abortLbl, self.onCancel) 463 | cancelBtn["class"].append("btn--cancel") 464 | self.popupFoot.appendChild(cancelBtn) 465 | 466 | self.sinkEvent("onKeyDown") 467 | 468 | self.inputElem.focus() 469 | 470 | def onDocumentKeyDown(self, event): 471 | if html5.isEscape(event): 472 | event.stopPropagation() 473 | event.preventDefault() 474 | self.onCancel() 475 | 476 | def onOkay(self, *args, **kwargs): 477 | if self.successHandler: 478 | self.successHandler(self, self.inputElem["value"]) 479 | self.close() 480 | 481 | def onCancel(self, *args, **kwargs): 482 | if self.abortHandler: 483 | self.abortHandler(self, self.inputElem["value"]) 484 | self.close() 485 | -------------------------------------------------------------------------------- /ignite.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import core as html5 3 | 4 | ################################################################ 5 | # NOTE: This part of the html5 library is superseded by flare. # 6 | # It is not improved anymore, and just remains here for # 7 | # existing projects. # 8 | # # 9 | # Visit https://github.com/mausbrand/flare for details. # 10 | ################################################################ 11 | 12 | @html5.tag 13 | class Label(html5.Label): 14 | _parserTagName = "ignite-label" 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(Label, self).__init__(style="label ignt-label", *args, **kwargs) 18 | 19 | 20 | @html5.tag 21 | class Input(html5.Input): 22 | _parserTagName = "ignite-input" 23 | 24 | def __init__(self, *args, **kwargs): 25 | super(Input, self).__init__(style="input ignt-input", *args, **kwargs) 26 | 27 | 28 | @html5.tag 29 | class Switch(html5.Div): 30 | _parserTagName = "ignite-switch" 31 | 32 | def __init__(self, *args, **kwargs): 33 | super(Switch, self).__init__(style="switch ignt-switch", *args, **kwargs) 34 | 35 | self.input = html5.Input(style="switch-input") 36 | self.appendChild(self.input) 37 | self.input["type"] = "checkbox" 38 | 39 | switchLabel = html5.Label(forElem=self.input) 40 | switchLabel.addClass("switch-label") 41 | self.appendChild(switchLabel) 42 | 43 | def _setChecked(self, value): 44 | self.input["checked"] = bool(value) 45 | 46 | def _getChecked(self): 47 | return self.input["checked"] 48 | 49 | 50 | @html5.tag 51 | class Check(html5.Input): 52 | _parserTagName = "ignite-check" 53 | 54 | def __init__(self, *args, **kwargs): 55 | super(Check, self).__init__(style="check ignt-check", *args, **kwargs) 56 | 57 | checkInput = html5.Input() 58 | checkInput.addClass("check-input") 59 | checkInput["type"] = "checkbox" 60 | self.appendChild(checkInput) 61 | 62 | checkLabel = html5.Label(forElem=checkInput) 63 | checkLabel.addClass("check-label") 64 | self.appendChild(checkLabel) 65 | 66 | 67 | @html5.tag 68 | class Radio(html5.Div): 69 | _parserTagName = "ignite-radio" 70 | 71 | def __init__(self, *args, **kwargs): 72 | super(Radio, self).__init__(style="radio ignt-radio", *args, **kwargs) 73 | 74 | radioInput = html5.Input() 75 | radioInput.addClass("radio-input") 76 | radioInput["type"] = "radio" 77 | self.appendChild(radioInput) 78 | 79 | radioLabel = html5.Label(forElem=radioInput) 80 | radioLabel.addClass("radio-label") 81 | self.appendChild(radioLabel) 82 | 83 | 84 | @html5.tag 85 | class Select(html5.Select): 86 | _parserTagName = "ignite-select" 87 | 88 | def __init__(self, *args, **kwargs): 89 | super(Select, self).__init__(style="select ignt-select", *args, **kwargs) 90 | 91 | defaultOpt = html5.Option() 92 | defaultOpt["selected"] = True 93 | defaultOpt["disabled"] = True 94 | defaultOpt.element.innerHTML = "" 95 | self.appendChild(defaultOpt) 96 | 97 | 98 | @html5.tag 99 | class Textarea(html5.Textarea): 100 | _parserTagName = "ignite-textarea" 101 | 102 | def __init__(self, *args, **kwargs): 103 | super(Textarea, self).__init__(style="textarea ignt-textarea", *args, **kwargs) 104 | 105 | 106 | @html5.tag 107 | class Progress(html5.Progress): 108 | _parserTagName = "ignite-progress" 109 | 110 | def __init__(self, *args, **kwargs): 111 | super(Progress, self).__init__(style="progress ignt-progress", *args, **kwargs) 112 | 113 | 114 | @html5.tag 115 | class Item(html5.Div): 116 | _parserTagName = "ignite-item" 117 | 118 | def __init__(self, title=None, descr=None, className=None, *args, **kwargs): 119 | super(Item, self).__init__(style="item ignt-item", *args, **kwargs) 120 | if className: 121 | self.addClass(className) 122 | 123 | self.fromHTML(""" 124 |
125 |
126 |
127 |
128 |
129 |
130 | """) 131 | 132 | if title: 133 | self.itemHeadline.appendChild(html5.TextNode(title)) 134 | 135 | if descr: 136 | self.itemSubline = html5.Div() 137 | self.addClass("item-subline ignt-item-subline") 138 | self.itemSubline.appendChild(html5.TextNode(descr)) 139 | self.appendChild(self.itemSubline) 140 | 141 | 142 | @html5.tag 143 | class Table(html5.Table): 144 | _parserTagName = "ignite-table" 145 | 146 | def __init__(self, *args, **kwargs): 147 | super(Table, self).__init__(*args, **kwargs) 148 | self.head.addClass("ignt-table-head") 149 | self.body.addClass("ignt-table-body") 150 | 151 | def prepareRow(self, row): 152 | assert row >= 0, "Cannot create rows with negative index" 153 | 154 | for child in self.body._children: 155 | row -= child["rowspan"] 156 | if row < 0: 157 | return 158 | 159 | while row >= 0: 160 | tableRow = html5.Tr() 161 | tableRow.addClass("ignt-table-body-row") 162 | self.body.appendChild(tableRow) 163 | row -= 1 164 | 165 | def prepareCol(self, row, col): 166 | assert col >= 0, "Cannot create cols with negative index" 167 | self.prepareRow(row) 168 | 169 | for rowChild in self.body._children: 170 | row -= rowChild["rowspan"] 171 | 172 | if row < 0: 173 | for colChild in rowChild._children: 174 | col -= colChild["colspan"] 175 | if col < 0: 176 | return 177 | 178 | while col >= 0: 179 | tableCell = html5.Td() 180 | tableCell.addClass("ignt-table-body-cell") 181 | rowChild.appendChild(tableCell) 182 | col -= 1 183 | 184 | return 185 | def fastGrid( self, rows, cols, createHidden=False ): 186 | colsstr = "".join(['' for i in range(0, cols)]) 187 | tblstr = '' 188 | 189 | for r in range(0, rows): 190 | tblstr += '%s' %("is-hidden" if createHidden else "",colsstr) 191 | tblstr +="" 192 | 193 | self.fromHTML(tblstr) 194 | -------------------------------------------------------------------------------- /svg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import core as html5 3 | 4 | 5 | ######################################################################################################################## 6 | # Attribute Collectors 7 | ######################################################################################################################## 8 | 9 | class _attrSvgViewBox(object): 10 | 11 | def _getViewbox(self): 12 | viewBox = self.element.viewBox 13 | try: 14 | return " ".join( 15 | [str(x) for x in [viewBox.baseVal.x, viewBox.baseVal.y, viewBox.baseVal.width, viewBox.baseVal.height]]) 16 | except: 17 | return "" 18 | 19 | def _setViewbox(self, val): 20 | self.element.setAttribute("viewBox", val) 21 | 22 | def _getPreserveaspectratio(self): 23 | return self.element.preserveAspectRatio 24 | 25 | def _setPreserveaspectratio(self, val): 26 | self.element.setAttribute("preserveAspectRatio", val) 27 | 28 | 29 | class _attrSvgDimensions(object): 30 | 31 | def _getWidth(self): 32 | return self.element.width 33 | 34 | def _setWidth(self, val): 35 | self.element.setAttribute("width", val) 36 | 37 | def _getHeight(self): 38 | return self.element.height 39 | 40 | def _setHeight(self, val): 41 | self.element.setAttribute("height", val) 42 | 43 | def _getX(self): 44 | return self.element.x 45 | 46 | def _setX(self, val): 47 | self.element.setAttribute("x", val) 48 | 49 | def _getY(self): 50 | return self.element.y 51 | 52 | def _setY(self, val): 53 | self.element.setAttribute("y", val) 54 | 55 | def _getR(self): 56 | return self.element.r 57 | 58 | def _setR(self, val): 59 | self.element.setAttribute("r", val) 60 | 61 | def _getRx(self): 62 | return self.element.rx 63 | 64 | def _setRx(self, val): 65 | self.element.setAttribute("rx", val) 66 | 67 | def _getRy(self): 68 | return self.element.ry 69 | 70 | def _setRy(self, val): 71 | self.element.setAttribute("ry", val) 72 | 73 | def _getCx(self): 74 | return self.element.cx 75 | 76 | def _setCx(self, val): 77 | self.element.setAttribute("cx", val) 78 | 79 | def _getCy(self): 80 | return self.element.cy 81 | 82 | def _setCy(self, val): 83 | self.element.setAttribute("cy", val) 84 | 85 | 86 | class _attrSvgPoints(object): 87 | 88 | def _getPoints(self): 89 | return self.element.points 90 | 91 | def _setPoints(self, val): 92 | self.element.setAttribute("points", val) 93 | 94 | def _getX1(self): 95 | return self.element.x1 96 | 97 | def _setX1(self, val): 98 | self.element.setAttribute("x1", val) 99 | 100 | def _getY1(self): 101 | return self.element.y1 102 | 103 | def _setY1(self, val): 104 | self.element.setAttribute("y1", val) 105 | 106 | def _getX2(self): 107 | return self.element.x2 108 | 109 | def _setX2(self, val): 110 | self.element.setAttribute("x2", val) 111 | 112 | def _getY2(self): 113 | return self.element.y2 114 | 115 | def _setY2(self, val): 116 | self.element.setAttribute("y2", val) 117 | 118 | 119 | class _attrSvgTransform(object): 120 | 121 | def _getTransform(self): 122 | return self.element.transform 123 | 124 | def _setTransform(self, val): 125 | self.element.setAttribute("transform", val) 126 | 127 | 128 | class _attrSvgXlink(object): 129 | 130 | def _getXlinkhref(self): 131 | return self.element.getAttribute("xlink:href") 132 | 133 | def _setXlinkhref(self, val): 134 | self.element.setAttribute("xlink:href", val) 135 | 136 | 137 | class _attrSvgStyles(object): 138 | 139 | def _getFill(self): 140 | return self.element.fill 141 | 142 | def _setFill(self, val): 143 | self.element.setAttribute("fill", val) 144 | 145 | def _getStroke(self): 146 | return self.element.stroke 147 | 148 | def _setStroke(self, val): 149 | self.element.setAttribute("stroke", val) 150 | 151 | 152 | ######################################################################################################################## 153 | # SVG Widgets 154 | ######################################################################################################################## 155 | 156 | @html5.tag 157 | class SvgWidget(html5.Widget): 158 | _namespace = "SVG" 159 | 160 | 161 | @html5.tag 162 | class Svg(SvgWidget, _attrSvgViewBox, _attrSvgDimensions, _attrSvgTransform): 163 | _tagName = "svg" 164 | 165 | def _getVersion(self): 166 | return self.element.version 167 | 168 | def _setVersion(self, val): 169 | self.element.setAttribute("version", val) 170 | 171 | def _getXmlns(self): 172 | return self.element.xmlns 173 | 174 | def _setXmlns(self, val): 175 | self.element.setAttribute("xmlns", val) 176 | 177 | 178 | @html5.tag 179 | class SvgCircle(SvgWidget, _attrSvgTransform, _attrSvgDimensions): 180 | _tagName = "circle" 181 | 182 | 183 | @html5.tag 184 | class SvgEllipse(SvgWidget, _attrSvgTransform, _attrSvgDimensions): 185 | _tagName = "ellipse" 186 | 187 | 188 | @html5.tag 189 | class SvgG(SvgWidget, _attrSvgTransform, _attrSvgStyles): 190 | _tagName = "g" 191 | 192 | def _getSvgTransform(self): 193 | return self.element.transform 194 | 195 | def _setSvgTransform(self, val): 196 | self.element.setAttribute("transform", val) 197 | 198 | 199 | @html5.tag 200 | class SvgImage(SvgWidget, _attrSvgViewBox, _attrSvgDimensions, _attrSvgTransform, _attrSvgXlink): 201 | _tagName = "image" 202 | 203 | 204 | @html5.tag 205 | class SvgLine(SvgWidget, _attrSvgTransform, _attrSvgPoints): 206 | _tagName = "line" 207 | 208 | 209 | @html5.tag 210 | class SvgPath(SvgWidget, _attrSvgTransform): 211 | _tagName = "path" 212 | 213 | def _getD(self): 214 | return self.element.d 215 | 216 | def _setD(self, val): 217 | self.element.setAttribute("d", val) 218 | 219 | def _getPathLength(self): 220 | return self.element.pathLength 221 | 222 | def _setPathLength(self, val): 223 | self.element.setAttribute("pathLength", val) 224 | 225 | 226 | @html5.tag 227 | class SvgPolygon(SvgWidget, _attrSvgTransform, _attrSvgPoints): 228 | _tagName = "polygon" 229 | 230 | 231 | @html5.tag 232 | class SvgPolyline(SvgWidget, _attrSvgTransform, _attrSvgPoints): 233 | _tagName = "polyline" 234 | 235 | 236 | @html5.tag 237 | class SvgRect(SvgWidget, _attrSvgDimensions, _attrSvgTransform, _attrSvgStyles): 238 | _tagName = "rect" 239 | 240 | 241 | @html5.tag 242 | class SvgText(SvgWidget, _attrSvgDimensions, _attrSvgTransform, _attrSvgStyles): 243 | _tagName = "text" 244 | 245 | 246 | ''' later... 247 | 248 | class SvgDefs(SvgWidget): 249 | _tagName = "defs" 250 | 251 | 252 | class SvgClipPath(SvgWidget): 253 | _tagName = "clippath" 254 | 255 | 256 | class SvgLinearGradient(SvgWidget, _attrSvgPoints): 257 | _tagName = "lineargradient" 258 | 259 | def _setGradientunits(self, value): 260 | self.element.gradientUnits = value 261 | 262 | def _getGradientunits(self): 263 | return self.element.gradientUnits 264 | 265 | def _setGradienttransform(self, value): 266 | self.element.gradientTransform = value 267 | 268 | def _getGradienttransform(self): 269 | return self.element.gradientTransform 270 | 271 | 272 | class SvgStop(SvgWidget): 273 | _tagName = "stop" 274 | 275 | def _setOffset(self, value): 276 | self.element.offset = value 277 | 278 | def _getOffset(self): 279 | return self.element.offset 280 | 281 | def _setStopcolor(self, value): 282 | self.element.offset = value 283 | 284 | def _getStopcolor(self): 285 | return self.element.offset 286 | ''' 287 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import core as html5 3 | 4 | ################################################################ 5 | # NOTE: This part of the html5 library is superseded by flare. # 6 | # It is not improved anymore, and just remains here for # 7 | # existing projects. # 8 | # # 9 | # Visit https://github.com/mausbrand/flare for details. # 10 | ################################################################ 11 | 12 | def unescape(val, maxLength = 0): 13 | """ 14 | Unquotes several HTML-quoted characters in a string. 15 | 16 | :param val: The value to be unescaped. 17 | :type val: str 18 | 19 | :param maxLength: Cut-off after maxLength characters. 20 | A value of 0 means "unlimited". (default) 21 | :type maxLength: int 22 | 23 | :returns: The unquoted string. 24 | :rtype: str 25 | """ 26 | val = val \ 27 | .replace("<", "<") \ 28 | .replace(">", ">") \ 29 | .replace(""", "\"") \ 30 | .replace("'", "'") 31 | 32 | if maxLength > 0: 33 | return val[0:maxLength] 34 | 35 | return val 36 | 37 | def doesEventHitWidgetOrParents(event, widget): 38 | """ 39 | Test if event 'event' hits widget 'widget' (or *any* of its parents) 40 | """ 41 | while widget: 42 | if event.target == widget.element: 43 | return widget 44 | 45 | widget = widget.parent() 46 | 47 | return None 48 | 49 | def doesEventHitWidgetOrChildren(event, widget): 50 | """ 51 | Test if event 'event' hits widget 'widget' (or *any* of its children) 52 | """ 53 | if event.target == widget.element: 54 | return widget 55 | 56 | for child in widget.children(): 57 | if doesEventHitWidgetOrChildren(event, child): 58 | return child 59 | 60 | return None 61 | 62 | def textToHtml(node, text, clear=False): 63 | """ 64 | Generates html nodes from text by splitting text into content and into 65 | line breaks html5.Br. 66 | 67 | :param node: The node where the nodes are appended to. 68 | :param text: The text to be inserted. 69 | :param clear: Clear node before inserting text 70 | """ 71 | if clear: 72 | node.removeAllChildren() 73 | 74 | for (i, part) in enumerate(text.split("\n")): 75 | if i > 0: 76 | node.appendChild(html5.Br()) 77 | 78 | node.appendChild(html5.TextNode(part)) 79 | 80 | def parseInt(s, ret = 0): 81 | """ 82 | Parses a value as int 83 | """ 84 | if not isinstance(s, str): 85 | return int(s) 86 | elif s: 87 | if s[0] in "+-": 88 | ts = s[1:] 89 | else: 90 | ts = s 91 | 92 | if ts and all([_ in "0123456789" for _ in ts]): 93 | return int(s) 94 | 95 | return ret 96 | 97 | def parseFloat(s, ret = 0.0): 98 | """ 99 | Parses a value as float. 100 | """ 101 | if not isinstance(s, str): 102 | return float(s) 103 | elif s: 104 | if s[0] in "+-": 105 | ts = s[1:] 106 | else: 107 | ts = s 108 | 109 | if ts and ts.count(".") <= 1 and all([_ in ".0123456789" for _ in ts]): 110 | return float(s) 111 | 112 | return ret 113 | 114 | def importJS(*args, **kwargs): 115 | """ 116 | Dynamically imports the provided JavaScript files sequentially. 117 | 118 | This has to be done when multiple scripts are bootstrapped, and cause errors in case they're all loaded 119 | simultaneously. 120 | 121 | :param callback: Allows to specify a callback function to be called when all scripts have been loaded. 122 | :type callback: callable 123 | """ 124 | if not args: 125 | callback = kwargs.get("callback") 126 | if callable(callback): 127 | callback() 128 | 129 | return 130 | 131 | script = html5.Script() 132 | script["src"] = args[0] 133 | #print(script["src"]) 134 | script.onLoad = lambda *xargs, **xkwargs: importJS(*args[1:], **kwargs) 135 | script.sinkEvent("onLoad") 136 | 137 | html5.Head().appendChild(script) 138 | --------------------------------------------------------------------------------