Nebulous Puffy Marshmallows

    TypeScript icon, indicating that this package has built-in type declarations

    0.0.8-alpha.3 • Public • Published



    Minimally viable DOM Document implementation for NativeScript


    Via npm:

    npm install dominative undom-ng

    Note: undom-ng is a peer dependency, you have to install it manually.




    import { Application } from '@nativescript/core'
    import { document } from 'dominative'
    const page = document.body
    const actionBar = document.createElement('ActionBar')
    actionBar.title = 'Hello World!'
    	create: () => document

    with ef.js



    	#title = Hello World!
    		#text = Button
    		.Welcome to the wonderland of ef.native!


    import { Application } from '@nativescript/core'
    import { domImpl, document } from 'dominative'
    import { setDOMImpl } from 'ef-core'
    import App from 'App.eft'
    const app = new App()
    app.$mount({target: document.body}){
    	create: () => document

    with SingUI



    import { Application } from '@nativescript/core'
    import { document } from 'dominative'
    import { browser, prop, setGlobalCtx, useTags, useElement, build } from 'singui'
    const tags = useTags(false)
    const app = (target) =>
    	build(({attach}) => {
    		const { ActionBar, NavigationButton, ActionItem, StackLayout, Label, Button } = tags
    		ActionBar(() => {
    			prop.title = 'Hello World!'
    		StackLayout(() => {
    			let count = 0
    			const {ret: updateText} = Label(() => {
    				return text().$textContent(
    					() => `You have tapped ${count} time${count === 1 ? '' : 's'}`
    			Button(() => {
    				prop.text = 'Tap me!'
    				on('tap', () => {
    					count += 1
    	create: () => {
    		return document

    with React + react-dom

    Playground - by Ammar Ahmed

    Note: This demo might have some issues with Chrome. Use Firefox if necessary.

    with Vue 3 + runtime-dom + DOMiNATIVE-Vue



    import { Application } from '@nativescript/core'
    import { createApp } from '@dominative/vue'
    import App from './App.vue'
    const app = createApp(App)

    with SolidJS + DOMiNATIVE-Solid



    import { Application } from "@nativescript/core"
    import { render } from "@dominative/solid"
    import { createSignal } from "solid-js"
    const App = () => {
    	const [count, setCount] = createSignal(0)
    	const increment = () => {
    		setCount(c => c + 1)
    	return <>
    	<actionbar title="Hello, SolidJS!"></actionbar>
    		<label>You have taapped {count()} time(s)</label>
    		<button class="-primary" on:tap={increment}>Tap me!</button>
    render(App, document.body)
    const create = () => document{ create })

    Prepare global environment

    Automatically register document, window and related variables globally:

    import { globalRegister } from 'dominative'

    Register Elements

    import { RadSideDrawer } from 'nativescript-ui-sidedrawer'
    import { RadListView } from 'nativescript-ui-listview'
    import { registerElement, makeListView } from 'dominative'
    // If you cannot determin what the component is based on, you can register it directly.
    registerElement('RadSideDrawer', RadSideDrawer)
    // Register with a specific type by using a pre-defined maker. Usually we check for inheritance, but with force we can make magic happen
    registerElement('RadListView', makeListView(RadListView, {force: true}))

    Pseudo Elements

    Pseudo elements are not real elements, but they appear as DOM elements to help organizing composition.


    Helper to put it's child/children to it's parent node's property by key


    key: String: RW The prop name to set on parent.

    type: <'array'|'key'>: RW Property type, could be an array prop or a single object prop. Once set, this prop couldn't be changed.

    value: any: RW Value to be set to parent. Usually children of this current node. Don't touch unless you know what you're doing.

    parent: Element: R Parent node of this node.

    class: String: RW Helper to set key and type, could be key:type or multi.level.key:type




    Prop but type already set to key.


    Prop but type already set to array.


    * Template was renamed to ItemTemplate to avoid conflict with HTML template tag.

    An ItemTemplate element holds a template to be replicated later, or can create views programmatically.


    Share mostly from Prop. Differences are listed below:

    key: String: RW Same form Prop, also serves the key name of a KeyedTemplate. Default to itemTemplate.

    type: 'single': R Should not be able to set type on a ItemTemplate.

    value: Function<T extends ViewBase>: R Same as createView.

    content: <T extends ViewBase>: RW The single child of this node. Don't touch unless you know what you're doing.

    patch: Function<T extends ViewBase>(PatchOptions): R Method to patch an existing clone.

    createView: Function<T extends ViewBase>: R Function to create view from this template.


    itemLoading: Triggered when patching and template has no content. Set event.view to change the view of this item. Additional props on event: view, index, item, data. This event's callback argument doesn't extend from NativeScript's data object.

    createView: Triggered when creating view from the template and template has no content. Set created view to event.view. If not set, view will be created by cloning the template. This event's callback argument doesn't extend from NativeScript's data object.


    ItemTemplate element could only have one element child. If you'd like to have multiple children in a template, just use a different type of view or layout as the only child and insert your other contents inside.


    By simpling putting ItemTemplates inside an array Prop we could set up a KeyedTemplate.


    <ListView itemTemplateSelector="$item % 2 ? 'odd' : 'even'">
    	<Prop key="itemTemplates" type="array">
    		<ItemTemplate key="odd">
    			<Label text="odd"/>
    		<ItemTemplate key="even">
    			<Label text="even"/>

    Template Handling for Custom Components

    There's a special maker caller makeTemplateReceiver, which you can use when a NativeScript component accepts templates.


    import { RadListView } from 'nativescript-ui-listview'
    import { registerElement, makeTemplateReceiver } from 'dominative'
    registerElement('RadListView', makeTemplateReceiver(RadListView, {
    	templateProps: ['itemTemplate'],
    	loadingEvents: ['itemLoading']

    templateProps: Array<String>: Props that accepts a template. Do not write keyed template props.

    loadingEvents: Array<String>: Events that will fire on the component when items loading.


    All elements added with registerElement is automatically extended with tweaking ability.

    Tweakable.defineEventOption(eventName: string, option: EventOption)

    Define how a event should be initialized. If an event is defined with bubbles: true or captures: true, they'll automatically be registered to native at element creation.

    Event option:

    	bubbles: boolean // should this event bubble, default false
    	captures: boolean // should this event have capture phase, default false
    	cancelable: boolean // should this event be cancelable, defalut true


    const ButtonElement = document.defaultView.Button
    ButtonElement.defineEventOption('tap', {
    	bubbles: true,
    	captures: true

    Tweakable.mapEvent(fromEvent: string, toEvent: string)

    See below

    Tweakable.mapProp(fromProp: string, toProp: string)

    See below

    Tree shaking

    Tree shaking is off by default, but if you want a smaller bundle size, you can enable it manually by setting __UI_USE_EXTERNAL_RENDERER__ global variable to true in your project's webpack config. For example:

    const { merge } = require('webpack-merge');
    module.exports = (env) => {
    	webpack.chainWebpack((config) => {
    		config.plugin('DefinePlugin').tap((args) => {
    			args[0] = merge(args[0], {
    				__UI_USE_EXTERNAL_RENDERER__: true, // Set true to enable tree shaking
    				__UI_USE_XML_PARSER__: false, // Usually XML parser isn't needed as well, so disabling it as well
    			return args;
    	return webpack.resolveConfig();

    But, PLEASE NOTICE, after tree shaking is enabled, you'll need to register {N} core components manually, otherwise they won't be available as elements. For example:

    import { AbsoluteLayout, StackLayout, Label, Button, registerElement } from 'dominative'
    registerElement('AbsoluteLayout', AbsoluteLayout)
    registerElement('StackLayout', StackLayout)
    registerElement('Label', Label)
    registerElement('Button', Button)

    or you can just register them all with registerAllElements, although it's pointless when tree shaking is enabled:

    import { registerAllElements } from 'dominative'

    Frame, Page and ContentView are registered by default.


    Hardcoding in Frameworks

    Frameworks are great, but they're not great when it comes to hardcoded things. We have to invest methods to counter the harm done by those hardcodings.

    Hardcoding is harmful, please do not hardcode.

    Always lowercased tag names

    Sometimes frameworks are just too thoughtful for you, they translate all your tag names to lowercase when compiling, which means your camelCase or PascalCase tag names won't work as intended.

    We can alias our tag names to lowercase if you like:

    import { aliasTagName } from 'dominative'
    const tagNameConverter = (PascalCaseName) => {
    	// ...whatever your transformation code here
    	// This is useful when your framework/renderer doesn't support document.createElement with uppercase letters.
    	const transformedName = PascalCaseName.toLowerCase()
    	return transformedName
    // Convert all built-in tag names

    Hardcoded events and props

    Some frameworks work like magic by providing lots of "just works" features that you don't even need to think about what's going on behind. They're actually done by heavily assuming you're on a specific platform - browser, and hardcoded these features to browser specific features.

    We have to mimic the events and props they hardcoded in order to make these frameworks happy:

    import { document } from 'dominative'
    const TextFieldElement = document.defaultView.TextField
    const ButtonElement = document.defaultView.Button
    TextFieldElement.mapEvent('input', 'inputChange') // This is redirecting event handler registering for 'input' to 'inputChange'
    TextFieldElement.mapProp('value', 'text') // This is redirecting access from 'value' prop to 'text' prop
    ButtonElement.mapEvent('click', 'tap') // Redirect 'click' event to 'tap'
    const input = document.createElement('TextField')
    input.addEventListener('input', (event) => { // This is actually registered to `inputChange`
    	console.log( // This is actually accessing ``

    Then the following code could work:

    <TextField v-model="userInput"/>
    <!-- 'v-model' hardcoded with `input` or `change` event and `value` or `checked` prop, so we have to provide it with a emulated `input` event and `value` prop -->
    <button onClick="onTapHandler"></button> // 'onTapHandler' is actually registered to 'tap', since some frameworks hardcoded "possible" event names so they can know it's an event handler




    npm i dominative

    DownloadsWeekly Downloads






    Unpacked Size

    101 kB

    Total Files


    Last publish


    • classicoldsong