├── .gitbook.yaml ├── .gitignore ├── src ├── compiler │ ├── directives │ │ ├── index.js │ │ └── model.js │ └── index.js ├── runtime │ ├── modules │ │ ├── index.js │ │ ├── attrs.js │ │ └── events.js │ ├── elements │ │ ├── index.js │ │ ├── text.js │ │ ├── button.js │ │ ├── box.js │ │ ├── textinput.js │ │ ├── window.js │ │ ├── element.js │ │ └── widget.js │ ├── nodes │ │ ├── comment.js │ │ └── textnode.js │ ├── patch.js │ ├── index.js │ └── node-ops.js └── util │ └── index.js ├── docs ├── .gitbook │ └── assets │ │ ├── vuido-example.png │ │ ├── vuido-example (1).png │ │ └── vuido-screenshot.png ├── components │ ├── widgets │ │ ├── text.md │ │ ├── button.md │ │ └── textinput.md │ ├── containers │ │ └── box.md │ └── window.md ├── common-attributes.md ├── SUMMARY.md ├── installation │ ├── README.md │ └── manual-configuration.md ├── README.md └── usage.md ├── .editorconfig ├── example ├── main.js ├── build │ └── webpack.config.js └── components │ └── MainWindow.vue ├── .babelrc ├── packages └── vuido-template-compiler │ ├── README.md │ └── package.json ├── LICENSE ├── package.json └── README.md /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | dist/*.js 3 | example/dist/*.js 4 | node_modules 5 | packages/*/*.js 6 | -------------------------------------------------------------------------------- /src/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | import model from './model' 2 | 3 | export default { 4 | model 5 | } 6 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/vuido-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abdelaziz18003/vuido/master/docs/.gitbook/assets/vuido-example.png -------------------------------------------------------------------------------- /src/runtime/modules/index.js: -------------------------------------------------------------------------------- 1 | import attrs from './attrs' 2 | import events from './events' 3 | 4 | export default [ attrs, events ]; 5 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/vuido-example (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abdelaziz18003/vuido/master/docs/.gitbook/assets/vuido-example (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/vuido-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abdelaziz18003/vuido/master/docs/.gitbook/assets/vuido-screenshot.png -------------------------------------------------------------------------------- /src/runtime/elements/index.js: -------------------------------------------------------------------------------- 1 | export * from './window' 2 | export * from './box' 3 | export * from './text' 4 | export * from './textinput' 5 | export * from './button' 6 | -------------------------------------------------------------------------------- /docs/components/widgets/text.md: -------------------------------------------------------------------------------- 1 | # Text 2 | 3 | Static widget which displays simple text. 4 | 5 | ## Example 6 | 7 | ```markup 8 | Hello, world! 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /docs/components/widgets/button.md: -------------------------------------------------------------------------------- 1 | # Button 2 | 3 | A simple button widget. 4 | 5 | ## Events 6 | 7 | ### click 8 | 9 | Called when the button is clicked. 10 | 11 | ## Example 12 | 13 | ```markup 14 | 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import libui from 'libui-node' 2 | import Vue from 'vuido' 3 | 4 | import MainWindow from './components/MainWindow' 5 | 6 | const window = new Vue( { 7 | render( h ) { 8 | return h( MainWindow ); 9 | } 10 | } ); 11 | 12 | window.$mount(); 13 | 14 | libui.startLoop(); 15 | -------------------------------------------------------------------------------- /src/runtime/elements/text.js: -------------------------------------------------------------------------------- 1 | import libui from 'libui-node' 2 | 3 | import { Widget } from './widget' 4 | 5 | export class Text extends Widget { 6 | _createWidget() { 7 | this.widget = new libui.UiLabel(); 8 | } 9 | 10 | _setWidgetText( text ) { 11 | this.widget.text = text; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/common-attributes.md: -------------------------------------------------------------------------------- 1 | # Common attributes 2 | 3 | The following attributes are shared by all widgets and containers. 4 | 5 | ### visible 6 | 7 | type: Boolean 8 | 9 | Whether the widget is visible or hidden. 10 | 11 | ### enabled 12 | 13 | type: Boolean 14 | 15 | Whether the widget is enabled or disabled. 16 | 17 | -------------------------------------------------------------------------------- /src/runtime/nodes/comment.js: -------------------------------------------------------------------------------- 1 | export class Comment { 2 | constructor( text ) { 3 | this.parentNode = null; 4 | this.prevSibling = null; 5 | this.nextSibling = null; 6 | 7 | this.tagName = ''; 8 | 9 | this.text = text; 10 | } 11 | 12 | setText( text ) { 13 | this.text = text; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/runtime/patch.js: -------------------------------------------------------------------------------- 1 | import { createPatchFunction } from 'core/vdom/patch' 2 | import baseModules from 'core/vdom/modules/index' 3 | 4 | import platformModules from './modules/index' 5 | import * as nodeOps from './node-ops' 6 | 7 | const modules = platformModules.concat( baseModules ); 8 | 9 | export const patch = createPatchFunction( { nodeOps, modules } ); 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "env", { 4 | "targets": { 5 | "node": 8 6 | }, 7 | "modules": false, 8 | "useBuiltIns": true 9 | } ] 10 | ], 11 | "plugins": [ 12 | "transform-object-rest-spread", 13 | "transform-class-properties", 14 | "transform-flow-strip-types" 15 | ], 16 | "comments": false 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime/nodes/textnode.js: -------------------------------------------------------------------------------- 1 | export class TextNode { 2 | constructor( text ) { 3 | this.parentNode = null; 4 | this.prevSibling = null; 5 | this.nextSibling = null; 6 | 7 | this.tagName = ''; 8 | 9 | this.text = text; 10 | } 11 | 12 | setText( text ) { 13 | this.text = text; 14 | 15 | if ( this.parentNode != null ) 16 | this.parentNode._setContentText( text ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/vuido-template-compiler/README.md: -------------------------------------------------------------------------------- 1 | # vuido-template-compiler 2 | 3 | Template compiler for [Vuido](https://github.com/mimecorg/vuido). 4 | 5 | You must use at least version 15.0 of [vue-loader](https://github.com/vuejs/vue-loader) in order to be able to inject the Vuido template compiler. Otherwise it will use the standard Vue.js template compiler which is not compatible with Vuido. 6 | 7 | This package is auto-generated. Refer to the Vuido documentation for more information. 8 | -------------------------------------------------------------------------------- /src/runtime/elements/button.js: -------------------------------------------------------------------------------- 1 | import libui from 'libui-node' 2 | 3 | import { Widget } from './widget' 4 | 5 | export class Button extends Widget { 6 | _createWidget() { 7 | this.widget = new libui.UiButton(); 8 | } 9 | 10 | _setWidgetText( text ) { 11 | this.widget.text = text; 12 | } 13 | 14 | _setWidgetHandler( event, handler ) { 15 | if ( event == 'click' ) 16 | this.widget.onClicked( handler ); 17 | else 18 | super._setWidgetHandler( event, handler ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Vuido](README.md) 4 | * [Installation](installation/README.md) 5 | * [Manual configuration](installation/manual-configuration.md) 6 | * [Usage](usage.md) 7 | * [Common attributes](common-attributes.md) 8 | * Components 9 | * [Window](components/window.md) 10 | * Containers 11 | * [Box](components/containers/box.md) 12 | * Widgets 13 | * [Button](components/widgets/button.md) 14 | * [Text](components/widgets/text.md) 15 | * [TextInput](components/widgets/textinput.md) 16 | 17 | -------------------------------------------------------------------------------- /docs/components/widgets/textinput.md: -------------------------------------------------------------------------------- 1 | # TextInput 2 | 3 | An input widget for editing a single line of text. 4 | 5 | TextInput supports the `v-model` directive. 6 | 7 | ## Attributes 8 | 9 | ### text 10 | 11 | The current text displayed in the widget. 12 | 13 | ### readonly 14 | 15 | Whether the widget is read-only or editable. 16 | 17 | ## Events 18 | 19 | ### input 20 | 21 | Called when the text is changed. The current text is passed as an argument. 22 | 23 | ## Example 24 | 25 | ```markup 26 | 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | import { makeMap } from 'shared/util' 2 | 3 | export function isUnaryTag( el ) { 4 | return false; 5 | } 6 | 7 | export function canBeLeftOpenTag( el ) { 8 | return false; 9 | } 10 | 11 | export function mustUseProp( tag, type, name ) { 12 | return false; 13 | } 14 | 15 | export const isReservedTag = makeMap( 'template,script,style,box,button,text,textinput,window', true ); 16 | 17 | export function getTagNamespace( tag ) { 18 | } 19 | 20 | export function isUnknownElement( tag ) { 21 | return false; 22 | } 23 | 24 | export const isBooleanAttr = makeMap( 'visible,enabled,stretchy,margined,padded,horizontal,readonly' ); 25 | -------------------------------------------------------------------------------- /packages/vuido-template-compiler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuido-template-compiler", 3 | "version": "0.1.0", 4 | "description": "Template compiler for Vuido", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/mimecorg/vuido.git" 9 | }, 10 | "keywords": [ 11 | "vuido", 12 | "compiler" 13 | ], 14 | "author": "Michał Męciński", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/mimecorg/vuido/issues" 18 | }, 19 | "homepage": "https://github.com/mimecorg/vuido", 20 | "dependencies": { 21 | "de-indent": "^1.0.2", 22 | "he": "^1.1.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/compiler/index.js: -------------------------------------------------------------------------------- 1 | import { parseComponent } from 'sfc/parser' 2 | import { createCompiler } from 'compiler/index' 3 | import { genStaticKeys } from 'shared/util' 4 | 5 | import { isUnaryTag, canBeLeftOpenTag, mustUseProp, isReservedTag, getTagNamespace } from '../util' 6 | 7 | import directives from './directives' 8 | 9 | const modules = []; 10 | 11 | const baseOptions = { 12 | modules, 13 | directives, 14 | isUnaryTag, 15 | canBeLeftOpenTag, 16 | mustUseProp, 17 | isReservedTag, 18 | getTagNamespace, 19 | preserveWhitespace: false, 20 | staticKeys: genStaticKeys( modules ) 21 | }; 22 | 23 | const { compile, compileToFunctions } = createCompiler( baseOptions ); 24 | 25 | export { 26 | parseComponent, 27 | compile, 28 | compileToFunctions 29 | } 30 | -------------------------------------------------------------------------------- /docs/components/containers/box.md: -------------------------------------------------------------------------------- 1 | # Box 2 | 3 | A container which arranges all children vertically or horizontally. 4 | 5 | ## Attributes 6 | 7 | ### horizontal 8 | 9 | type: Boolean 10 | 11 | Whether the box arranges its children vertically or horizontally. 12 | 13 | ### padded 14 | 15 | type: Boolean 16 | 17 | Whether the box adds some padding between its children. 18 | 19 | ## Child attributes 20 | 21 | The following attributes can be added to child components to change the way they are handled by the box container. 22 | 23 | ### stretchy 24 | 25 | type: Boolean 26 | 27 | Whether the child stretches to fill available space. 28 | 29 | ## Example 30 | 31 | ```markup 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /src/compiler/directives/model.js: -------------------------------------------------------------------------------- 1 | import { addHandler, addAttr } from 'compiler/helpers' 2 | import { genComponentModel, genAssignmentCode } from 'compiler/directives/model' 3 | 4 | import { isReservedTag } from '../../util' 5 | 6 | export default function model( el, dir, _warn ) { 7 | if ( el.component != null || !isReservedTag( el.tag ) ) 8 | genComponentModel( el, dir.value, dir.modifiers ); 9 | else if ( el.tag == 'TextInput' ) 10 | genDefaultModel( el, dir.value, dir.modifiers ); 11 | else if ( process.env.NODE_ENV != 'production' ) 12 | _warn( el.tag + ' does not support v-model' ); 13 | } 14 | 15 | function genDefaultModel( el, value, modifiers ) { 16 | const { trim, number } = modifiers || {}; 17 | 18 | let valueExpression = '$event'; 19 | if ( trim ) 20 | valueExpression += '.trim()'; 21 | if ( number ) 22 | valueExpression = '_n(' + valueExpression + ')'; 23 | 24 | const code = genAssignmentCode( value, valueExpression ); 25 | 26 | addAttr( el, 'value', '(' + value + ')' ); 27 | addHandler( el, 'input', code, null, true ); 28 | } 29 | -------------------------------------------------------------------------------- /docs/components/window.md: -------------------------------------------------------------------------------- 1 | # Window 2 | 3 | The root of the Vue.js application must be a Window component which represents a single window. 4 | 5 | The Window component must contain exactly one child widget, typically a container. 6 | 7 | ## Attributes 8 | 9 | ### title 10 | 11 | type: String 12 | 13 | The title of the window. 14 | 15 | ### width 16 | 17 | type: Number 18 | 19 | The width of the window. 20 | 21 | ### height 22 | 23 | type: Number 24 | 25 | The height of the window 26 | 27 | ### margined 28 | 29 | type: Boolean 30 | 31 | Whether the window adds a margin around its children. 32 | 33 | ### menu 34 | 35 | type: Boolean 36 | 37 | Whether the window has a menu. 38 | 39 | ## Events 40 | 41 | ### close 42 | 43 | Called when the close button is clicked. To exit the application, call `libui.stopLoop()`. To close the window without exiting the application, call `this.$root.$destroy()`. 44 | 45 | ## Example 46 | 47 | ```markup 48 | 49 | 50 | ... 51 | 52 | 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /src/runtime/elements/box.js: -------------------------------------------------------------------------------- 1 | import libui from 'libui-node' 2 | 3 | import { Widget } from './widget' 4 | 5 | export class Box extends Widget { 6 | _getDefaultAttributes() { 7 | return { 8 | ...super._getDefaultAttributes(), 9 | horizontal: false, 10 | padded: false 11 | }; 12 | } 13 | 14 | _createWidget() { 15 | if ( this.attributes.horizontal ) 16 | this.widget = new libui.UiHorizontalBox(); 17 | else 18 | this.widget = new libui.UiVerticalBox(); 19 | } 20 | 21 | _appendWidget( childNode ) { 22 | this.widget.append( childNode.widget, childNode.attributes.stretchy ); 23 | } 24 | 25 | _removeWidget( childNode ) { 26 | this.widget.deleteAt( childNode.widgetIndex ); 27 | } 28 | 29 | _initializeWidgetAttributes() { 30 | super._initializeWidgetAttributes(); 31 | 32 | if ( this.attributes.padded ) 33 | this.widget.padded = true; 34 | } 35 | 36 | _setWidgetAttribute( key, value ) { 37 | if ( key == 'padded' ) 38 | this.widget.padded = value; 39 | else 40 | super._setWidgetAttribute( key, value ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2018 Michał Męciński 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/runtime/modules/attrs.js: -------------------------------------------------------------------------------- 1 | import { extend } from 'shared/util' 2 | import { isBooleanAttr } from '../../util' 3 | 4 | function updateAttrs( oldVnode, vnode ) { 5 | if ( oldVnode.data.attrs == null && vnode.data.attrs == null ) 6 | return; 7 | 8 | const elm = vnode.elm; 9 | const oldAttrs = oldVnode.data.attrs || {}; 10 | let attrs = vnode.data.attrs || {}; 11 | 12 | if ( attrs.__ob__ != null ) 13 | attrs = vnode.data.attrs = extend( {}, attrs ); 14 | 15 | for ( let key in attrs ) { 16 | let cur = attrs[ key ]; 17 | let old = oldAttrs[ key ]; 18 | if ( old !== cur ) 19 | setAttr( elm, key, cur ); 20 | } 21 | 22 | for ( let key in oldAttrs ) { 23 | if ( attrs[ key ] == null ) 24 | elm.setAttribute( key, null ); 25 | } 26 | } 27 | 28 | function setAttr( elm, key, value ) { 29 | if ( isBooleanAttr( key ) ) { 30 | if ( value == null || value === false ) 31 | elm.setAttribute( key, false ); 32 | else 33 | elm.setAttribute( key, true ); 34 | } else { 35 | elm.setAttribute( key, value ); 36 | } 37 | } 38 | 39 | export default { 40 | create: updateAttrs, 41 | update: updateAttrs 42 | } 43 | -------------------------------------------------------------------------------- /example/build/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const webpack = require( 'webpack' ); 3 | const VueLoaderPlugin = require( 'vue-loader/lib/plugin' ); 4 | 5 | const VuidoTemplateCompiler = require( '../../packages/vuido-template-compiler' ); 6 | 7 | module.exports = { 8 | entry: './example/main.js', 9 | output: { 10 | path: path.resolve( __dirname, '../dist' ), 11 | filename: 'main.js' 12 | }, 13 | target: 'node', 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.vue$/, 18 | loader: 'vue-loader', 19 | options: { 20 | compiler: VuidoTemplateCompiler 21 | } 22 | }, 23 | { 24 | test: /\.js$/, 25 | loader: 'babel-loader', 26 | exclude: /node_modules/ 27 | } 28 | ] 29 | }, 30 | resolve: { 31 | extensions: [ '.js', '.vue', '.json' ] 32 | }, 33 | plugins: [ 34 | new webpack.ExternalsPlugin( 'commonjs', [ 'libui-node', { 'vuido': '../../dist/vuido.js' } ] ), 35 | new VueLoaderPlugin() 36 | ], 37 | performance: { 38 | hints: false 39 | }, 40 | stats: { 41 | children: false, 42 | modules: false 43 | }, 44 | devtool: false 45 | }; 46 | -------------------------------------------------------------------------------- /src/runtime/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'core/instance/index' 2 | import { initGlobalAPI } from 'core/global-api/index' 3 | import { mountComponent } from 'core/instance/lifecycle' 4 | 5 | import { patch } from './patch' 6 | 7 | import { mustUseProp, isReservedTag, getTagNamespace, isUnknownElement } from '../util' 8 | 9 | initGlobalAPI( Vue ); 10 | 11 | Vue.version = __VUE_VERSION__; 12 | 13 | Vue.config.mustUseProp = mustUseProp; 14 | Vue.config.isReservedTag = isReservedTag; 15 | Vue.config.getTagNamespace = getTagNamespace; 16 | Vue.config.isUnknownElement = isUnknownElement; 17 | 18 | Vue.prototype.__patch__ = patch; 19 | 20 | Vue.prototype.$mount = function( el, hydrating ) { 21 | if ( el != null ) 22 | throw new Error( 'Mount element is not supported' ); 23 | 24 | mountComponent( this, el, hydrating ); 25 | 26 | if ( this.$parent == null && this.$el.widget == null ) { 27 | if ( this.$el.tagName != 'Window' ) 28 | throw new Error( 'Top level element must be a Window' ); 29 | 30 | this.$el._mountWindow(); 31 | 32 | this.$on( 'hook:destroyed', () => { 33 | this.$el._destroyWindow(); 34 | } ); 35 | } 36 | 37 | return this; 38 | }; 39 | 40 | export default Vue 41 | -------------------------------------------------------------------------------- /src/runtime/modules/events.js: -------------------------------------------------------------------------------- 1 | import { updateListeners } from 'core/vdom/helpers/update-listeners' 2 | 3 | let target = null; 4 | 5 | function add( event, handler, once, capture ) { 6 | if ( capture ) 7 | throw new Error( 'Bubble phase events are not supported' ); 8 | 9 | if ( once ) { 10 | const oldHandler = handler; 11 | const _target = target; 12 | 13 | handler = function( ev ) { 14 | const res = arguments.length === 1 ? oldHandler( ev ) : oldHandler.apply( null, arguments ); 15 | if ( res !== null ) 16 | remove( event, null, null, _target ); 17 | } 18 | } 19 | 20 | target.addEventListener( event, handler ); 21 | } 22 | 23 | function remove( event, handler, capture, _target ) { 24 | ( _target || target ).removeEventListener( event ); 25 | } 26 | 27 | function updateDOMListeners( oldVnode, vnode ) { 28 | if ( oldVnode.data.on == null && vnode.data.on == null ) 29 | return; 30 | 31 | const on = vnode.data.on || {}; 32 | const oldOn = oldVnode.data.on || {}; 33 | 34 | target = vnode.elm; 35 | updateListeners( on, oldOn, add, remove, vnode.context ); 36 | target = null; 37 | } 38 | 39 | export default { 40 | create: updateDOMListeners, 41 | update: updateDOMListeners 42 | } 43 | -------------------------------------------------------------------------------- /src/runtime/elements/textinput.js: -------------------------------------------------------------------------------- 1 | import libui from 'libui-node' 2 | 3 | import { Widget } from './widget' 4 | 5 | export class TextInput extends Widget { 6 | _getDefaultAttributes() { 7 | return { 8 | ...super._getDefaultAttributes(), 9 | value: '', 10 | readonly: false 11 | }; 12 | } 13 | 14 | _createWidget() { 15 | this.widget = new libui.UiEntry(); 16 | } 17 | 18 | _initializeWidgetAttributes() { 19 | super._initializeWidgetAttributes(); 20 | 21 | if ( this.attributes.value != '' ) 22 | this.widget.text = this.attributes.value; 23 | if ( this.attributes.readonly ) 24 | this.widget.readOnly = true; 25 | } 26 | 27 | _setWidgetAttribute( key, value ) { 28 | if ( key == 'value' ) { 29 | if ( this.widget.text != value ) 30 | this.widget.text = value; 31 | } else if ( key == 'readonly' ) { 32 | this.widget.readOnly = value; 33 | } else { 34 | super._setWidgetAttribute( key, value ); 35 | } 36 | } 37 | 38 | _setWidgetHandler( event, handler ) { 39 | if ( event == 'input' ) { 40 | this.widget.onChanged( () => { 41 | handler( this.widget.text ); 42 | } ); 43 | } else { 44 | super._setWidgetHandler( event, handler ); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/installation/README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Prerequisites 4 | 5 | The following prerequisites are needed to compile [libui-node](https://github.com/parro-it/libui-node), which is used by Vuido. 6 | 7 | ### Windows 8 | 9 | * [windows-build-tools](https://www.npmjs.com/package/windows-build-tools) or Visual Studio 2015 10 | * [Visual C++ Redistributable Package for Visual Studio 2013](https://www.microsoft.com/en-us/download/details.aspx?id=40784) 11 | 12 | ### Linux 13 | 14 | If they are not provided by default in your distribution: 15 | 16 | * [build-essential](https://packages.ubuntu.com/xenial/build-essential) 17 | * [GTK+ 3](https://packages.ubuntu.com/source/xenial/gtk+3.0) 18 | 19 | ### OS X 20 | 21 | * Xcode 22 | 23 | ## Quick setup 24 | 25 | The easiest way to start using Vuido is to use [vue-cli](https://www.npmjs.com/package/vue-cli) to create the scaffolding of the project. 26 | 27 | First make sure that vue-cli is installed globally: 28 | 29 | ```bash 30 | npm install -g vue-cli 31 | ``` 32 | 33 | Run the following command to create the project \(replace `my-project` with the name of your project\): 34 | 35 | ```bash 36 | vue init mimecorg/vuido-webpack-template my-project 37 | ``` 38 | 39 | Enter the directory created by vue-cli and install all dependencies: 40 | 41 | ```bash 42 | cd my-project 43 | npm install 44 | ``` 45 | 46 | Now you can build and run your application: 47 | 48 | ```bash 49 | npm run build 50 | npm start 51 | ``` 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/runtime/node-ops.js: -------------------------------------------------------------------------------- 1 | import { TextNode } from './nodes/textnode' 2 | import { Comment } from './nodes/comment' 3 | import * as elements from './elements' 4 | 5 | export function createElement( tagName, vnode ) { 6 | const element = elements[ tagName ]; 7 | if ( element == null ) 8 | throw new Error( 'Unknown element ' + tagName ); 9 | return new element( tagName ); 10 | } 11 | 12 | export function createElementNS( namespace, tagName ) { 13 | throw new Error( 'Namespaced elements are not supported' ); 14 | } 15 | 16 | export function createTextNode( text ) { 17 | return new TextNode( text ); 18 | } 19 | 20 | export function createComment( text ) { 21 | return new Comment( text ); 22 | } 23 | 24 | export function appendChild( node, child ) { 25 | node.appendChild( child ); 26 | } 27 | 28 | export function insertBefore( parentNode, newNode, referenceNode ) { 29 | parentNode.insertBefore( newNode, referenceNode ); 30 | } 31 | 32 | export function removeChild( node, child ) { 33 | node.removeChild( child ); 34 | } 35 | 36 | export function parentNode( node ) { 37 | return node.parentNode; 38 | } 39 | 40 | export function nextSibling( node ) { 41 | return node.nextSibling; 42 | } 43 | 44 | export function tagName( node ) { 45 | return node.tagName; 46 | } 47 | 48 | export function setTextContent( node, text ) { 49 | node.setText( text ); 50 | } 51 | 52 | export function setAttribute( node, key, value ) { 53 | node.setAttribute( key, value ); 54 | } 55 | 56 | export function setStyleScope( node, scopeId ) { 57 | throw new Error( 'Scoped styles are not supported' ); 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuido", 3 | "version": "0.1.0", 4 | "description": "Native desktop applications using Vue.js", 5 | "main": "dist/vuido.js", 6 | "files": [ 7 | "dist/*.js", 8 | "src" 9 | ], 10 | "scripts": { 11 | "build": "npm run build:runtime && npm run build:compiler && npm run build:example", 12 | "build:runtime": "webpack --config build/webpack.runtime.config.js", 13 | "build:compiler": "webpack --config build/webpack.compiler.config.js", 14 | "build:example": "webpack --config example/build/webpack.config.js", 15 | "example": "node ./example/dist/main.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/mimecorg/vuido.git" 20 | }, 21 | "keywords": [ 22 | "vuido", 23 | "vue", 24 | "libui", 25 | "desktop" 26 | ], 27 | "author": "Michał Męciński", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/mimecorg/vuido/issues" 31 | }, 32 | "homepage": "https://github.com/mimecorg/vuido", 33 | "dependencies": { 34 | "libui-node": "^0.2.0" 35 | }, 36 | "devDependencies": { 37 | "babel-core": "^6.26.3", 38 | "babel-loader": "^7.1.4", 39 | "babel-plugin-transform-class-properties": "^6.24.1", 40 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 41 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 42 | "babel-preset-env": "^1.7.0", 43 | "de-indent": "^1.0.2", 44 | "he": "^1.1.1", 45 | "vue": "^2.5.16", 46 | "vue-loader": "^15.0.11", 47 | "vue-template-compiler": "^2.5.16", 48 | "webpack": "^3.12.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Vuido 2 | 3 | Create native desktop applications for Windows, OS X and Linux using Vue.js. 4 | 5 | ![NPM module](https://img.shields.io/npm/v/vuido.svg) ![MIT License](https://img.shields.io/github/license/mimecorg/vuido.svg) 6 | 7 | ## Introduction 8 | 9 | Vuido makes it possible to create lightweight, native desktop applications using Vue.js. Applications using Vuido can run on Windows, OS X and Linux, using native GUI components, and don't require Electron. 10 | 11 | ![](.gitbook/assets/vuido-screenshot.png) 12 | 13 | Under the hood, Vuido uses the [libui](https://github.com/andlabs/libui) library which provides native GUI components for each desktop platform, and the [libui-node](https://github.com/parro-it/libui-node) bindings for Node.js. 14 | 15 | Vuido supports most of the standard Vue.js API and it's compatible with many Vue.js extensions, for example Vuex. Applications using Vuido can also use all standard Node.js modules and any packages compatible with Node.js. 16 | 17 | ## Development status 18 | 19 | At the moment Vuido is in a very early stage of development. The first goal is to implement all controls currently supported by libui and to write a documentation. 20 | 21 | ## Acknowledgements 22 | 23 | Vuido is largely based on Vue.js and shares most of its code, except for the platform specific code related to libui. 24 | 25 | Vuido was inspired by [Proton Native](https://github.com/kusti8/proton-native), an environment for creating native desktop applications using React. 26 | 27 | ## License 28 | 29 | Vuido is licensed under the MIT license 30 | 31 | Copyright \(C\) 2018 Michał Męciński 32 | 33 | -------------------------------------------------------------------------------- /example/components/MainWindow.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 60 | -------------------------------------------------------------------------------- /src/runtime/elements/window.js: -------------------------------------------------------------------------------- 1 | import libui from 'libui-node' 2 | 3 | import { Element } from './element' 4 | import { Widget } from './widget'; 5 | 6 | export class Window extends Element { 7 | constructor( tagName ) { 8 | super( tagName ); 9 | 10 | this.window = null; 11 | } 12 | 13 | appendChild( childNode ) { 14 | super.appendChild( childNode ); 15 | 16 | if ( !( childNode instanceof Widget ) ) 17 | throw new Error( 'Window can only contain child widgets' ); 18 | 19 | if ( this.childNodes.length > 1 ) 20 | throw new Error( 'Window can only contain one child element' ); 21 | 22 | if ( this.window != null ) 23 | throw new Error( 'Window child element cannot be inserted dynamically' ); 24 | } 25 | 26 | insertBefore( childNode, referenceNode ) { 27 | throw new Error( 'Window child element cannot be inserted dynamically' ); 28 | } 29 | 30 | removeChild( childNode ) { 31 | throw new Error( 'Window child element cannot be removed dynamically' ); 32 | } 33 | 34 | setAttribute( key, value ) { 35 | super.setAttribute( key, value ); 36 | 37 | if ( this.window != null ) 38 | this._setWindowAttribute( key, value ); 39 | } 40 | 41 | addEventListener( event, handler ) { 42 | super.addEventListener( event, handler ); 43 | 44 | if ( this.window != null ) 45 | this._setWindowHandler( event, handler ); 46 | } 47 | 48 | removeEventListener( event ) { 49 | super.removeEventListener( event ); 50 | 51 | if ( this.window != null ) 52 | this._setWindowHandler( event, null ); 53 | } 54 | 55 | _getDefaultAttributes() { 56 | return { 57 | title: 'Vuido', 58 | width: 400, 59 | height: 300, 60 | menu: false, 61 | margined: false 62 | }; 63 | } 64 | 65 | _mountWindow() { 66 | this.window = new libui.UiWindow( this.attributes.title, this.attributes.width, this.attributes.height, this.attributes.menu ); 67 | 68 | if ( this.attributes.margined ) 69 | this.window.margined = true; 70 | 71 | for ( let key in this.handlers ) 72 | this._setWindowHandler( key, this.handlers[ key ] ); 73 | 74 | if ( this.childNodes.length > 0 ) { 75 | this.childNodes[ 0 ]._mountWidget(); 76 | this.window.setChild( this.childNodes[ 0 ].widget ); 77 | } 78 | 79 | this.window.show(); 80 | } 81 | 82 | _destroyWindow() { 83 | this.window.close(); 84 | this.window = null; 85 | 86 | this.childNodes = []; 87 | } 88 | 89 | _setWindowAttribute( key, value ) { 90 | throw new Error( 'Window does not have attribute ' + key ); 91 | } 92 | 93 | _setWindowHandler( event, handler ) { 94 | if ( event == 'close' ) 95 | this.window.onClosing( handler ); 96 | else 97 | throw new Error( 'Window does not have event ' + event ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /docs/installation/manual-configuration.md: -------------------------------------------------------------------------------- 1 | # Manual configuration 2 | 3 | This information is intended for advanced users who wish to configure a Vuido application manually. In most cases the automatic [quick setup](./#quick-setup) should be enough. 4 | 5 | You need to install the [vuido](https://www.npmjs.com/package/vuido) package in order to use Vuido in you application: 6 | 7 | ```bash 8 | npm install --save vuido 9 | ``` 10 | 11 | In order to use single-file components, you will also need [webpack](https://webpack.js.org/) and [vue-loader](https://vue-loader.vuejs.org/). The configuration is similar to a web application using Vue.js, with some important differences: 12 | 13 | * The [vuido-template-compiler](https://www.npmjs.com/package/vuido-template-compiler) must be used instead of the standard vue-template-compiler which is used by vue-loader by default. Use the `compiler` option to pass the Vuido compiler to vue-loader. Note that this option requires vue-loader v15 or newer. 14 | * Set the target option to 'node' to ensure that the compiled script can be run correctly by Node.js. 15 | * Use webpack.ExternalsPlugin to exclude libui-node and other native modules from the bundle. 16 | 17 | Example of webpack configuration using Vuido: 18 | 19 | {% code-tabs %} 20 | {% code-tabs-item title="webpack.config.js" %} 21 | ```javascript 22 | const path = require('path'); 23 | const webpack = require('webpack'); 24 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 25 | const VuidoTemplateCompiler = require('vuido-template-compiler'); 26 | 27 | module.exports = { 28 | entry: './src/main.js', 29 | output: { 30 | path: path.resolve(__dirname, '../dist'), 31 | filename: 'bundle.js' 32 | } 33 | target: 'node', 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.vue$/, 38 | loader: 'vue-loader', 39 | options: { 40 | compiler: VuidoTemplateCompiler 41 | } 42 | }, 43 | { 44 | test: /\.js$/, 45 | loader: 'babel-loader', 46 | exclude: /node_modules/ 47 | } 48 | ] 49 | }, 50 | resolve: { 51 | extensions: ['.js', '.vue', '.json'] 52 | }, 53 | plugins: [ 54 | new webpack.ExternalsPlugin('commonjs', ['libui-node']), 55 | new VueLoaderPlugin() 56 | ] 57 | }; 58 | ``` 59 | {% endcode-tabs-item %} 60 | {% endcode-tabs %} 61 | 62 | You also need to install [babel-core](https://www.npmjs.com/package/babel-core), [babel-loader](https://github.com/babel/babel-loader) and [babel-preset-env](https://www.npmjs.com/package/babel-preset-env). 63 | 64 | The basic Babel configuration which compiles the scripts for Node.js v8 looks like this: 65 | 66 | {% code-tabs %} 67 | {% code-tabs-item title=".babelrc" %} 68 | ```javascript 69 | { 70 | "presets": [ 71 | [ "env", { 72 | "targets": { 73 | "node": 8 74 | }, 75 | "modules": false, 76 | "useBuiltIns": true 77 | } ] 78 | ], 79 | "comments": false 80 | } 81 | ``` 82 | {% endcode-tabs-item %} 83 | {% endcode-tabs %} 84 | 85 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | Vuido is simply a port of Vue.js for the desktop and it can be used just like Vue.js. Vuido supports most of the standard Vue.js API, including methods, components and directives. It's also compatible with many Vue.js extensions, for example Vuex. 4 | 5 | Applications using Vuido run in a Node.js environment, so they can use all standard Node.js modules, for example fs and http, and any packages compatible with Node.js. 6 | 7 | ## Creating a window component 8 | 9 | The easiest way to create a window component using Vuido is to use a single-file component with .vue extension. The component should include a template, where you can use Vuido components instead of HTML. It should also include a script which defines the component logic. 10 | 11 | Here's a basic window component using Vuido: 12 | 13 | {% code-tabs %} 14 | {% code-tabs-item title="MainWindow.vue" %} 15 | ```markup 16 | 24 | 25 | 44 | ``` 45 | {% endcode-tabs-item %} 46 | {% endcode-tabs %} 47 | 48 | ## The main script of the application 49 | 50 | A simple script with initializes the application looks as follows: 51 | 52 | {% code-tabs %} 53 | {% code-tabs-item title="main.js" %} 54 | ```javascript 55 | import libui from 'libui-node' 56 | import Vue from 'vuido' 57 | 58 | import MainWindow from './components/MainWindow' 59 | 60 | const window = new Vue({ 61 | render: h => h(MainWindow) 62 | }); 63 | 64 | window.$mount(); 65 | 66 | libui.startLoop(); 67 | ``` 68 | {% endcode-tabs-item %} 69 | {% endcode-tabs %} 70 | 71 | After creating the window component, call `$mount()` to show the window. 72 | 73 | {% hint style="warning" %} 74 | Note that `$mount()` should be called without any parameters. Also you should not pass the `el` option to the Vue instance. 75 | {% endhint %} 76 | 77 | Call `libui.startLoop()` to start the event loop of the application. To exit the application, call `libui.stopLoop()`. This is usually done in response to the close event of the window. 78 | 79 | {% hint style="info" %} 80 | Your application can create multiple windows by creating and mounting multiple root components. In order to close a window without exiting the application, call `$destroy()` on the root component. 81 | {% endhint %} 82 | 83 | ## Compiling and running the application 84 | 85 | You must build your application before running it: 86 | 87 | ```bash 88 | npm run build 89 | ``` 90 | 91 | Use the following command to run the application: 92 | 93 | ```bash 94 | npm start 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vuido 2 | 3 | Create native desktop applications for Windows, OS X and Linux using [Vue.js](https://vuejs.org/). 4 | 5 | [![NPM module](https://img.shields.io/npm/v/vuido.svg)](https://npmjs.org/package/vuido) 6 | [![MIT License](https://img.shields.io/github/license/mimecorg/vuido.svg)](https://github.com/mimecorg/vuido/blob/master/LICENSE) 7 | 8 | ## Introduction 9 | 10 | Vuido makes it possible to create lightweight, native desktop applications using Vue.js. Application using Vuido can run on Windows, OS X and Linux, using native GUI components, and don't require Electron. 11 | 12 | ![](docs/.gitbook/assets/vuido-screenshot.png) 13 | 14 | Under the hood, Vuido uses the [libui](https://github.com/andlabs/libui) library which provides native GUI components for each desktop platform, and the [libui-node](https://github.com/parro-it/libui-node) bindings for Node.js. 15 | 16 | Vuido supports most of the standard Vue.js API and it's compatible with many Vue.js extensions, for example Vuex. Applications using Vuido can also use all standard Node.js modules and any packages compatible with Node.js. 17 | 18 | ## Prerequisites 19 | 20 | The following prerequisites are needed to compile [libui-node](https://github.com/parro-it/libui-node), which is used by Vuido. 21 | 22 | ### Windows 23 | 24 | * [windows-build-tools](https://www.npmjs.com/package/windows-build-tools) or Visual Studio 2015 25 | * [Visual C++ Redistributable Package for Visual Studio 2013](https://www.microsoft.com/en-us/download/details.aspx?id=40784) 26 | 27 | ### Linux 28 | 29 | If they are not provided by default in your distribution: 30 | 31 | * [build-essential](https://packages.ubuntu.com/xenial/build-essential) 32 | * [GTK+ 3](https://packages.ubuntu.com/source/xenial/gtk+3.0) 33 | 34 | ### OS X 35 | 36 | * Xcode 37 | 38 | ## Quick setup 39 | 40 | The easiest way to start using Vuido is to use [vue-cli](https://www.npmjs.com/package/vue-cli) to create the scaffolding of the project. 41 | 42 | First make sure that vue-cli is installed globally: 43 | 44 | ```bash 45 | npm install -g vue-cli 46 | ``` 47 | 48 | Run the following command to create the project \(replace `my-project` with the name of your project\): 49 | 50 | ```bash 51 | vue init mimecorg/vuido-webpack-template my-project 52 | ``` 53 | 54 | Enter the directory created by vue-cli and install all dependencies: 55 | 56 | ```bash 57 | cd my-project 58 | npm install 59 | ``` 60 | 61 | Now you can build and run your application: 62 | 63 | ```bash 64 | npm run build 65 | npm start 66 | ``` 67 | 68 | ## Documentation 69 | 70 | You can find the full documentation of Vuido at [vuido.mimec.org](https://vuido.mimec.org/). 71 | 72 | 73 | ## Development status 74 | 75 | At the moment Vuido is in a very early stage of development. The main goal is to implement all remaining controls currently supported by libui. 76 | 77 | ## Acknowledgements 78 | 79 | Vuido is largely based on Vue.js and shares most of its code, except for the platform specific code related to libui. 80 | 81 | Vuido was inspired by [Proton Native](https://github.com/kusti8/proton-native), an environment for creating native desktop applications using React. 82 | 83 | ## License 84 | 85 | Vuido is licensed under the MIT license 86 | 87 | Copyright (C) 2018 Michał Męciński 88 | -------------------------------------------------------------------------------- /src/runtime/elements/element.js: -------------------------------------------------------------------------------- 1 | import { TextNode } from '../nodes/textnode' 2 | 3 | export class Element { 4 | constructor( tagName ) { 5 | this.parentNode = null; 6 | this.prevSibling = null; 7 | this.nextSibling = null; 8 | 9 | this.tagName = tagName; 10 | 11 | this.childNodes = []; 12 | 13 | this.attributes = this._getDefaultAttributes(); 14 | this.handlers = {}; 15 | } 16 | 17 | appendChild( childNode ) { 18 | if ( childNode == null ) 19 | throw new Error( 'Child node cannot be empty' ); 20 | 21 | if ( childNode.parentNode != null ) 22 | throw new Error( 'Child node already has a parent' ); 23 | 24 | if ( childNode instanceof TextNode && this.childNodes.length > 0 ) 25 | throw new Error( 'Element cannot contian multiple text nodes' ); 26 | 27 | childNode.parentNode = this; 28 | 29 | if ( this.childNodes.length > 0 ) { 30 | const lastChild = this.childNodes[ this.childNodes.length - 1 ]; 31 | childNode.prevSibling = lastChild; 32 | lastChild.nextSibling = childNode; 33 | } 34 | 35 | this.childNodes.push( childNode ); 36 | } 37 | 38 | insertBefore( childNode, referenceNode ) { 39 | if ( childNode == null ) 40 | throw new Error( 'Child node cannot be empty' ); 41 | 42 | if ( referenceNode != null && referenceNode.parentNode != this ) 43 | throw new Error( 'Reference node has invalid parent' ); 44 | 45 | if ( childNode.parentNode != null ) 46 | throw new Error( 'Child node already has a parent' ); 47 | 48 | if ( childNode instanceof TextNode ) 49 | throw new Error( 'Text node cannot be inserted dynamically' ); 50 | 51 | const index = this.childNodes.indexOf( referenceNode ); 52 | 53 | childNode.parentNode = this; 54 | childNode.nextSibling = referenceNode; 55 | childNode.prevSibling = this.childNodes[ index - 1 ]; 56 | 57 | referenceNode.prevSibling = childNode; 58 | this.childNodes.splice( index, 0, childNode ); 59 | 60 | return index; 61 | } 62 | 63 | removeChild( childNode ) { 64 | if ( childNode == null ) 65 | throw new Error( 'Child node cannot be empty' ); 66 | 67 | if ( childNode.parentNode != this ) 68 | throw new Error( 'Child node has invalid parent' ); 69 | 70 | if ( childNode instanceof TextNode ) 71 | throw new Error( 'Text node cannot be removed dynamically' ); 72 | 73 | childNode.parentNode = null; 74 | 75 | if ( childNode.prevSibling != null ) 76 | childNode.prevSibling.nextSibling = childNode.nextSibling; 77 | 78 | if ( childNode.nextSibling != null ) 79 | childNode.nextSibling.prevSibling = childNode.prevSibling; 80 | 81 | const index = this.childNodes.indexOf( childNode ); 82 | 83 | this.childNodes.splice( index, 1 ); 84 | } 85 | 86 | setAttribute( key, value ) { 87 | this.attributes[ key ] = value; 88 | } 89 | 90 | addEventListener( event, handler ) { 91 | this.handlers[ event ] = handler; 92 | } 93 | 94 | removeEventListener( event ) { 95 | delete this.handlers[ event ]; 96 | } 97 | 98 | _getDefaultAttributes() { 99 | return {}; 100 | } 101 | 102 | _setContentText( text ) { 103 | throw new Error( this.tagName + ' cannot contain text nodes' ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/runtime/elements/widget.js: -------------------------------------------------------------------------------- 1 | import { Element } from './element' 2 | import { TextNode } from '../nodes/textnode' 3 | 4 | export class Widget extends Element { 5 | constructor( tagName ) { 6 | super( tagName ); 7 | 8 | this.widget = null; 9 | this.widgetIndex = null; 10 | 11 | } 12 | 13 | appendChild( childNode ) { 14 | super.appendChild( childNode ); 15 | 16 | if ( this.widget != null ) { 17 | if ( childNode instanceof Element ) 18 | this._appendElement( childNode ); 19 | else if ( childNode instanceof TextNode ) 20 | this._setWidgetText( child.text ); 21 | } 22 | } 23 | 24 | insertBefore( childNode, referenceNode ) { 25 | const index = super.insertBefore( childNode, referenceNode ); 26 | 27 | if ( this.widget != null ) { 28 | if ( childNode instanceof Element ) 29 | this._insertElement( childNode, index ); 30 | } 31 | 32 | return index; 33 | } 34 | 35 | removeChild( childNode ) { 36 | super.removeChild( childNode ); 37 | 38 | if ( this.widget != null ) { 39 | if ( childNode instanceof Element ) 40 | this._removeElement( childNode ); 41 | } 42 | } 43 | 44 | setAttribute( key, value ) { 45 | super.setAttribute( key, value ); 46 | 47 | if ( this.widget != null ) 48 | this._setWidgetAttribute( key, value ); 49 | } 50 | 51 | addEventListener( event, handler ) { 52 | super.addEventListener( event, handler ); 53 | 54 | if ( this.widget != null ) 55 | this._setWidgetHandler( event, handler ); 56 | } 57 | 58 | removeEventListener( event ) { 59 | super.removeEventListener( event ); 60 | 61 | if ( this.widget != null ) 62 | this._setWidgetHandler( event, null ); 63 | } 64 | 65 | _mountWidget() { 66 | this._createWidget(); 67 | this._initializeWidgetAttributes(); 68 | 69 | for ( let key in this.handlers ) 70 | this._setWidgetHandler( key, this.handlers[ key ] ); 71 | 72 | for ( let i = 0; i < this.childNodes.length; i++ ) { 73 | const childNode = this.childNodes[ i ]; 74 | if ( childNode instanceof Element ) 75 | this._appendElement( childNode ); 76 | else if ( childNode instanceof TextNode ) 77 | this._setWidgetText( childNode.text ); 78 | } 79 | } 80 | 81 | _getDefaultAttributes() { 82 | return { 83 | visible: true, 84 | enabled: true, 85 | stretchy: false 86 | }; 87 | } 88 | 89 | _createWidget() { 90 | throw new Error( this.tagName + ' cannot be created' ); 91 | } 92 | 93 | _destroyWidget() { 94 | this.widget.destroy(); 95 | this.widget = null; 96 | 97 | this.childNodes = []; 98 | } 99 | 100 | _appendElement( childNode ) { 101 | if ( !( childNode instanceof Widget ) ) 102 | throw new Error( this.tagName + ' cannot contain ' + childNode.tagName + ' elements' ); 103 | 104 | childNode._mountWidget(); 105 | this._appendWidget( childNode ); 106 | 107 | childNode.widgetIndex = this.childNodes.indexOf( childNode ); 108 | } 109 | 110 | _insertElement( childNode, index ) { 111 | if ( !( childNode instanceof Widget ) ) 112 | throw new Error( this.tagName + ' cannot contain ' + childNode.tagName + ' elements' ); 113 | 114 | for ( let i = this.childNodes.length - 1; i > index; i-- ) { 115 | const tailNode = this.childNodes[ i ]; 116 | if ( tailNode instanceof Element ) 117 | this._removeWidget( tailNode ); 118 | } 119 | 120 | childNode._mountWidget(); 121 | this._appendWidget( childNode ); 122 | 123 | for ( let i = index + 1; i < this.childNodes.length; i++ ) { 124 | const tailNode = this.childNodes[ i ]; 125 | if ( tailNode instanceof Element ) 126 | this._appendWidget( tailNode ); 127 | } 128 | 129 | this._reindexChildWidgets(); 130 | } 131 | 132 | _removeElement( childNode ) { 133 | this._removeWidget( childNode ); 134 | 135 | childNode._destroyWidget(); 136 | 137 | this._reindexChildWidgets(); 138 | } 139 | 140 | _appendWidget( childNode ) { 141 | throw new Error( this.tagName + ' cannot contain child widgets' ); 142 | } 143 | 144 | _removeWidget( childNode ) { 145 | throw new Error( this.tagName + ' cannot contain child widgets' ); 146 | } 147 | 148 | _setContentText( text ) { 149 | if ( this.widget != null ) 150 | this._setWidgetText( text ); 151 | } 152 | 153 | _setWidgetText( text ) { 154 | throw new Error( this.tagName + ' cannot contain text nodes' ); 155 | } 156 | 157 | _initializeWidgetAttributes() { 158 | if ( !this.attributes.visible ) 159 | this.widget.visible = false; 160 | if ( !this.attributes.enabled ) 161 | this.widget.enabled = false; 162 | } 163 | 164 | _setWidgetAttribute( key, value ) { 165 | if ( key == 'visible' ) 166 | this.widget.visible = value; 167 | else if ( key == 'enabled' ) 168 | this.widget.enabled = value; 169 | else 170 | throw new Error( this.tagName + ' does not have attribute ' + key ); 171 | } 172 | 173 | _setWidgetHandler( event, handler ) { 174 | throw new Error( this.tagName + ' does not have event ' + event ); 175 | } 176 | 177 | _reindexChildWidgets() { 178 | let index = 0; 179 | for ( let i = 0; i < this.childNodes.length; i++ ) { 180 | const childNode = this.childNodes[ i ]; 181 | if ( childNode instanceof Widget ) 182 | childNode.widgetIndex = index++; 183 | } 184 | } 185 | } 186 | --------------------------------------------------------------------------------