Giter Site home page Giter Site logo

coconut.vdom's Introduction

Coconut VDOM Renderer

Gitter

A pure Haxe virtual dom renderer for coconut.ui based on coconut.diffing.

Differences from coconut.react:

  • no JS dependency (moderate size saving)
  • allows putting DOM straight into VDOM (use with care)
  • slightly better performance (YMMV)

All in all coconut.vdom supports everything coconut.react does, except for the rendering of "pure" react components. You may find coconut.react + preact (+ preact-compat) a good middleground.

coconut.vdom's People

Contributors

back2dos avatar cedx avatar kevinresol avatar markknol avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

coconut.vdom's Issues

Function view does not update while class view does

Trying to implement something similar to react portals using Renderer.mount() and viewDidMount() I found some unexpected(?) behavior: function view does not update when parent state changes

import coconut.vdom.Renderer;
import coconut.vdom.View;

class TestBugView extends View {
    @:state var count:Int = 0;

    function viewDidMount() {
        Renderer.mount(js.Browser.document.getElementById('fragment'), <>Fragment: {count}</>);
        Renderer.mount(js.Browser.document.getElementById('element'), <span>Element: {count}</span>);
        Renderer.mount(js.Browser.document.getElementById('function'), <FunctionView count={count} />);
        Renderer.mount(js.Browser.document.getElementById('class'), <ClassView count={count}/>);
    }

    function render()
        return <button onclick={count += 1}>click me</button>;

    function FunctionView(attr:{ count:Int })
        return <span>Function: {count}</span>;
}

class ClassView extends View {
    @:attr var count:Int;
    function render()
        return <span>Class: {count}</span>;
}
other files

hxml

-cp src
-main TestBugMain
--library coconut.ui
--library coconut.vdom
-dce full
-D analyzer-optimize
-D js-es=6

-js bug\bundle.js

Main

class TestBugMain {
    static public function main() {

        Renderer.mount(js.Browser.document.body, <TestBugView />);
    }
}

html

<div id="fragment"></div>
<div id="element"></div>
<div id="function"></div>
<div id="class"></div>
<script src="bundle.js"></script>

Clicking the button updates only class view, while I expect updating function component as well.

Can't use vdom.Foreign in new version

package;

import tink.state.*;
import coconut.Ui.*;
import js.Browser.*;

class Main {
    static var key = {};
    static function main()
        hxx('<Editor key=${key} onChange=${onChange}/>');
    
    static function onChange(v:String)
        trace(v);
}


typedef Data = {
    @:optional var onChange(default, never):String->Void;
}

class Editor extends vdom.Foreign {
    
    public function new(data:Observable<Data>) {
        super(document.createDivElement());
    }
}

src/Main.hx:9: characters 15-21 : <Editor/> must have exactly one spread and no other attributes

Keys are messed up in views

It's really fishy how to deal with keys now to prevent recreation of view elements.
There should be some automatic key assignment and also maybe some explanation when manual keys are need.

Obscure bug about nullable function attribute (?)

class Main extends View {
	@:state var mystate:Int = 1;

	function render() '
		<div>
			<p>State: ${mystate}</p>
			<Menu onClick=${getClick(mystate)}/>
			<button onclick=${toggle}>Toggle</button>
		</div>
	';
	
	function toggle() {
		mystate++;
	}

	inline function getClick(state:Int):Void->Void {
		trace('compute ${state}');
		return state == 1 ? function() trace('clicked') : null;
	}
	
	static function main() {
		mount(document.getElementById('app'), hxx('<Main/>'));
	}
}

class Menu extends View {
	@:attr var onClick:Void->Void = null;
	function render() '
		<div>
			<Button onClick=${onClick}/>
		</div>
	';
}

class Button extends View {
	@:attr var onClick:Void->Void = null;
	function render() '
		<button onclick=${onClick}>Button</button>
	';
}

Expected behavior

  1. This should start with state = 1.
  2. When "Button" is clicked, it should trace "clicked"
  3. When "Toggle" is clicked, state will become 2
  4. When state is 2, getClick should produce a null callback and clicking "Button" should do nothing.

Actual behavior:
At step 4: getClick is never called with state = 2 and clicking "Button" will still trace "clicked"

Note:
I found that the <div> in Menu is critical. Removing it will make it behave as expected.

vdom version 35a8d8d
dependencies should be at latest git head as at time of writing

Raw component is one value behind

Hi,

The raw component seems to always be one value behind. At initialization its value is correct but then the first time the value changes the raw component does not change its rendered value.
Afterwards the displayed value is always one step behind.

See the following example

import coconut.vdom.View;
import js.Browser;
import coconut.ui.Renderer;

class Main {
	public static function main() {
    	Renderer.mount(Browser.document.getElementById('app'), "<App />");
	}
}

class App extends View {
    @:state var count=0;

    function handleClick() {
        count = count+1;
    }

    function render() {
        return hxx('
        <div>
            <raw content=${'<b>${count}</b>'}/>
            <button onclick=${handleClick}>Click</button>
        </div>
        ');
    }
}

At init you will correctly see "0" displayed. The first time you click on the button no change will be seen. The second time you will see "1".

When wrapped div is left out, rendering is wrong

I am doodling with coconut vdom, but noticed a weird bug. Here is the full source. I have not taken time to track it down to smaller reproducible example.

Whats wrong:
In this example, If I remove <div class="leave-me-out"> (but keep its children) in this example, and click the tink_core button, the rendering is totally messed up.

  • You should see only the pages inside <if ${data.current == "tink_core"}>
  • But I see the content of some pages outside that <if>, but that's totally wrong because those visible attribute conditions are not even met.

I thought it was related to the <if> in relation to the wrapped div (because adding a wrapping div makes everything work) but it goes deeper; when I change <HtmlView content="${pageContent}"/> to <HtmlView content="${url}"/>, everything works. This maybe suggest something is wrong with @:loaded var pageContent (PageData) ??

There are probably better ways to set this thing up, but afaik I'm not doing super fancy stuff and thus should work with and without that wrapping <div class="leave-me-out">.

Here's the full code: https://github.com/markknol/coconut-readme-reader/blob/master/src/Main.hx
(Repo: https://github.com/markknol/coconut-readme-reader)

I am using

  • 4.0.0-preview.3
  • coconut.vdom=0.6.2
  • -lib markdown and -lib html-haxe-code-highlighter (latest from haxelib)
# @install: lix --silent download "gh://github.com/MVCoconut/coconut.vdom#99a5f04b9d6432ed0cfaaa513aeddec9bde7ce88" into coconut.vdom/0.6.2/github/99a5f04b9d6432ed0cfaaa513aeddec9bde7ce88

Null<tink.domspec.TagInfo> has no field domCt

Hi,
I'm trying to build the Mark Knoll's simple example ( https://gist.github.com/markknol/0de2d8d05e8f3d725946eb5515cc771b )
but compilation fails with this errors:

/Users/dinko/haxe/haxelib/coconut,vdom/git/src/coconut/vdom/macros/Setup.hx:18: characters 23-32 : Null<tink.domspec.TagInfo> has no field domCt
/Users/dinko/haxe/haxelib/coconut,vdom/git/src/coconut/vdom/macros/Setup.hx:34: characters 19-28 : Null<tink.domspec.TagInfo> has no field domCt

I have coconut.ui, coconut.data, coconunt.vdom and tink_hxx all
installed from git so they should be up to date.

I'm not using lix for library management but doing it simple via haxelib and haxelib git install command.

Here's the example project which fails to build.
coco_test.zip

Cheers,
Dinko

`<time>` tag: `datetime` attribute is stripped from the final rendering

When I add a datetime attribute to a <time> element, this attribute never appears in the final HTML.
View template:

<time datetime=${Date.now()}>Now!</time>

Rendered HTML:

<time>Now!</time>

When I query the element in JavaScript, its dateTime property is empty:

document.querySelector("time").dateTime.length; // => 0

Using coconut.ui 0.12.0 with coconut.vdom 0.10.0.

[diy] Lifecycle event doesn't run when the element rendered conditionally

Hey,

In DIY branch lifecycle events are not working when the element is rendered with condition:

    function render()
    {
        return 
        model.status == "hello" 
        ? @hxx '<HelloSubView />'
        : @hxx '<button onclick=${model.test()}>setHello</button>';
    }

The HelloSubView will have no lifecycle events, even if they got rendered.

Here is a minimalistic example of the issue:
https://github.com/grosmar/coconut-playground/blob/lifecycle-bug/src/HelloView.hx

I know DIY is discontinued but I would prefer to not go back to the master as GEN4 is coming, but that still seems not really finished...

Try event delegation.

When having a lot of event handlers, it does show up in the profiler. For bubbling events it might be advisable to use event delegation. Or it may just create a lot of bugs. Let's find out \o/

Remove xDOM dependency.

It should not be mandatory, but when present, it should be integrated. Refs should be wrapped in xdom.Wrapped accordingly.

`<raw />` component: `class` attribute should not be rendered when empty

When you don't provide a class attribute to a <raw /> component, the attribute is still rendered with an "undefined" value.

Source:

<raw content="Hello World!"/>

Rendered:

<span class="undefined">Hello World!</span>

Not a big issue... unless you already have a .undefined class defined by your stylesheet.
Using coconut.vdom 0.10.1.

Unspecified/null attributes not being set

For example, suppose we have an existing vnode <div class="foo"/>

and we are transiting to:
<div/> or <div class=${null}/>

The actual DOM will have the class=foo remained.

Current workaround is to specify <div class=""/> explicitly.

SVG class is not handled properly

Likely affects all SVG elements.

I can mock up a full repro if needed, but the basic steps are easy enough. Just make a view with <path class="test" />. It will compile as expected, but at runtime you'll get this:

Uncaught TypeError: Cannot assign to read only property 'className' of object '#<SVGPathElement>'

Per the spec, SVGElement's className is a read only SVGAnimatedString instance, and also deprecated. Spec suggests using classList instead, but setAttribute('class', ... ) would probably work if you're looking to avoid classList for other reasons.

Compile issue on newer haxe versions

When I compile a pretty basic vdom app with haxe 4.2.5, it compiles fine. If I update to 4.3.0 or newer, I get:

/src/coconut/vdom/RenderResult.hx:14: characters 12-37 : Void should be coconut.vdom.RenderResult

I can't figure out why. When I trace through it looks like all the code paths should be returning non-void but I might be confused by the macros...

Compiler server friendliness

With compiler server it gives the following error every other build:

--macro:1: character 0 : Type name vdom.macros.Loader is redefined from module vdom.macros.Loader

Element missing in rendered DOM

import tink.pure.List;
using tink.CoreApi;

class Main extends coconut.ui.View {
    static function main() {
        js.Browser.document.body.appendChild(new Main({}).toElement());
    }
    
    @:state var contents:List<Content> = [
        Text('foo'), 
        Expr(Multi([Text('bar')])),
    ];
    
    function render() '
        <div>
            ${renderContents(contents)}
        </div>
    ';
    
    function renderContents(contents:List<Content>) {
        return @hxx '
            <div>
                <for ${content in contents}>
                    <switch ${content}>
                        <case ${Text(v)}>
                            <TextWidget value=${v}/>
                        <case ${Expr(expr)}>
                            ${renderExpr(expr)}
                    </switch>
                </for>
            </div>
        ';
    }
    
    function renderExpr(expr:Expr) {
        return @hxx '
            <div>
                <switch ${expr}>
                    <case ${Multi(v)}>
						<let values=${Some(v)}>
							<switch ${values}>
								<case ${Some(c)}>
									${renderContents(c)}
								<case ${None}>
									${renderContents([])}
							</switch>	
						</let>
                </switch>
            </div>
        ';
    }
    
    override function afterInit(e) {
        haxe.Timer.delay(function() contents = [
            Text('foo2'),
            Text('bar2'),
        ], 1000);
    }
}

enum Content {
    Text(v:String);
    Expr(e:Expr);
}

@:pure
enum Expr {
    Multi(v:List<Content>);
}

class TextWidget extends coconut.ui.View {
	@:attr var value:String;
	
	function render() '
		<div>
			${value}
		</div>
	';
}

expected: show foo bar at startup, then show foo2 bar2 after 1s
actual after 1s shows only foo2

SVG support

(requires haxetink/tink_domspec#8)

import js.Browser.*;
import coconut.Ui.*;

using coconut.ui.Renderer;

class Main extends coconut.ui.View {
	static function main() {
		document.getElementById('app').mount(<Main/>);
	}
	
	function render() '
		<div>
			<svg width="100%" height="100%">
				<polygon points="100,10 40,198 190,78 10,78 160,198" style=${{fill:'lime',stroke:'purple',strokeWidth:'5',fillRule:'evenodd'}} />
				<circle cx="50" cy="50" r="40" stroke="black" strokeWidth="10" fill="red" />
			</svg>
		</div>
	'
	;
}

This code works in coconut.react-dom but not coconut.vdom. Perhaps the svg attributes need some special handling?

(in vdom, the elements are present but some of the attributes are missing)

root element is actually not ready in afterInit?

I am not very sure about this. But what is happening to me is like this:

function render() '<div class="ui dropdown">...</div>';
function afterInit(e) new JQuery(e).dropdown();

I am using semantic ui and this code doesn't work. When logging the e, it is actually the div. But the dropdown initialization doesn't work. However if I wrap the jquery call inside Callback.defer it works.

I reckon is problem happens if the view is newly rendered by its parent. So that inside the child view's afterInit, e is created but not yet mounted to the DOM tree by the parent view, causing this phenomenon.

"role" attributes are stripped from the final rendering

When I add a role attribute to an element, this attribute never appears in the final HTML.
View template:

<a href="#" role="button">Hello folks!</a>
<div class="alert" role="alert">Caution!</div>

Rendered HTML:

<a href="#">Hello folks!</a>
<div class="alert">Caution!</div>

Using coconut.ui 0.11.2 with coconut.vdom 0.8.1.

afterInit and beforeInit event called only once in case of rerender

To reproduce:
Create a view component and cause re-renders in any way where the component get destroyed and recreated.

The beforeInit and afterInit lifecycle events doesn't called only the first time.
For example:
beforeInit 1
render 1
afterInit 1
destroy 1
render 2
destroy 2
render 3
destroy 3
render 4
numbers representing the instance unique id for that is recreate

They should be called properly on every new creation of the object.

[gen4] key not working

class Main extends coconut.ui.View {
  static function main() {
    coconut.Ui.hxx('<Main/>').renderInto(js.Browser.document.body);
  }
  function render() '<div><Foo/></div>';
}

class Foo extends coconut.ui.View {
	@:state var key:String = '1';
	function render() '<div key=${key}/>';
	override function viewDidMount() key = '2';
}

See error in console:

 TypeError: Cannot read property 'insertBefore' of null

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.