Giter VIP home page Giter VIP logo

doom's Introduction

Doom

Doom is a Virtual Dom Library for Haxe. It is strictly typed (no Dynamics lurking around) and built to be easy to use.

Demos

Examples and demos with source code can be found here.

Tests

Make sure to have phantomjs installed.

Run the following commands from the root of the projects:

haxelib install hmm
hmm install
haxe build.hxml

VNode

A VNode (virtual node) is the basic rendering element in Doom. It can represent an element (like a DIV or an A), simple text or a Component.

Generating a VNode doesn't automatically render it. A VNode needs to be translated into browser DOM nodes. There are two ways to do that:

  • mount the VNode directly in the DOM
  • generate and return a VNode from a Component.render method.

To mount a VNode use Doom.render.mount():

import doom.html.Html.*;
import js.Browser.document as doc;

class Main {
  static function main()
    Doom.browser.mount(
      h1("I'm just a simple element"),
      doc.getElementById("main")
    );
}

VNode Types

The following VNode types exist:

  • Element(name: String, attributes: Map<String, AttributeValue>, children: VNodes)
  • Comment(comment: String)
  • Raw(code: String)
  • Text(text: String)
  • Comp<Props, El>(comp: Component<Props, El>)

These can be generated using the homonymous methods on doom.core.VNode, doom.html.Html and/or doom.html.Svg (the last two are convenient aliases). The methods are el, comp, comment, raw and text. doom.html.Html also contains shortcut methods like div or input to generate equivalent nodes.

VNodes

Most elements accept child elements, there are typed as VNodes which is an abstract on Array<VNode> with a few additional benefits (mostly implicit cast from common types).

AttributeValue

Elements expect a Map<String, AttributeValue> to set the node attributes and properties. AttributeValue is a convenient abstract that simplifies assigning the right values to the attributes.

AttributeValue has 3 constructors:

  • BoolAttribute(b : Bool)
  • StringAttribute(s : String)
  • EventAttribute<T : Event>(f : T -> Void)

Here are the types that are implicitly converted to AttributeValue:

  • String for attributes like id or class
  • Map<String, Bool> mainly to be used with class. It is convenient to turn on and off class names:
div([
  "class" => [
    "button" => true,
    "active" => props.active,
    style    => null != style
  ]
]);
  • Bool used with attributes like disabled or checked
  • Void -> Void an event handler that doesn't care about information related to the even itself. click is the perfect example for it.
  • (T : Event) -> Void when you want an event handler and have full control on the Event object.
  • (T : Element) -> Void, the handler receives the original element that triggered the event.
  • String -> Void, the handler receives the text content of the element that triggered the event. The text content is retrieved in different ways according to the type of element (input, textarea, select, ...).
  • Bool -> Void, the handler receives a flag value from the checked attribute.
  • Int -> Void, works like String -> Void but tries to convert the value into an Int. If that cannot happen the handler is not invoked.
  • Float -> Void, same as Int -> Void but for floats.

Note: All event handlers except for (T : Event) -> Void will automatically call event.preventDefault().

Components

A component is anything between a full UI application and a button. A component lives inside another component (as a VNode returned by the render method) or it can be mounted directly in the dom.

import doom.html.Component;
import doom.html.Html.*;
using thx.Objects;
import thx.Timer;

class Main {
  static function main() {
    var div = js.Browser.document.getElementById("main");
    Doom.browser.mount(new BannerComponent({
      messages : [ "Doom", "is", "Magic", "(but the good kind)" ],
      delay : 500,
      toDisplay : 0
    }), div);
  }
}

class BannerComponent extends Component<BannerProps> {
  override function render() {
    Timer.delay(function() {
      update(props.shallowMerge({
        toDisplay : (props.toDisplay + 1) % props.messages.length
      }));
    }, props.delay);
    return h1(props.messages[props.toDisplay]);
  }
}

typedef BannerProps = {
  messages : Array<String>,
  delay : Int,
  toDisplay : Int
}

API documentation

TODO

  • TODO describe update
  • TODO describe state

doom's People

Contributors

andywhite37 avatar fponticelli avatar mlms13 avatar sledorze avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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

doom's Issues

PatchChild index is wrong when the DOM is modified externally

Hello, I've debugged a bit the issue I had here https://groups.google.com/forum/#!topic/haxelang/5bViIOBmKkQ and I think I've found the problem. So the component structure is this one:

- header
  - div.row
     - ...
  - div.loader-active

After it is mounted the DOM changes to this:

- header
  - div.menu-button (added by the css/js framework)
  - div.row
     - ...
  - div.loader-active

And, here is the problem, when the component goes to render again it will override the div.row with div.loader-inactive so:

- header
  - div.menu-button
  - div.loader-inactive
     - ...
  - div.loader-active

I've checked the patches that get applied to the node and it shows that it will apply a PatchChild(1, oldLoader, newLoader)

PatchChild(0,  
  MigrateComponentToComponent(HeaderComponent(<header class="demo-header mdl-layout__...s--indeterminate is-active"/></header>),HeaderComponent(<header class="demo-header mdl-layout__...l-progress--indeterminate "/></header>))
  PatchChild(1,  
    MigrateComponentToComponent(LoadingComponent(<div id="loading-element" class="mdl-pr...l-progress--indeterminate is-active"/>),LoadingComponent(<div id="loading-element" class="mdl-pr...ogress mdl-progress--indeterminate "/>))
    SetAttribute(class,StringAttribute(mdl-progress mdl-js-progress mdl-progress--indeterminate ))))

Notice the second PatchChild, it should be 2 because the new added div.menu-button by the css/js framework.

AutoComponent idea

This issue is to try to figure out how an AutoComponent macro thing could work in doom. I have some code in a branch to start this, so I can work on it and do a PR.

There would be an doom.AutoComponent interface with an @:autoBuild metadata to transform the implementing class, add the State and Api typedefs, generate getters for state/api properties, setup defaults, etc.

The idea is to turn something like this:

enum MyComponentStyle {
  Default;
  Primary;
  Other;
}

// could have @:noChildren metadata on the class to indicate that the component can't have children?
class MyComponent implements AutoComponent {
  @:state
  public var style : MyComponentStyle;

  @:state
  @:optional
  @:default(123)
  public var size : Int;

  @:api
  public var click : Void -> Void; // should this be an empty function rather than var? - I made it a var, so a property accessor could return api.click

  @:api
  @:optional
  public var hover : Void -> Void; // should this be an empty function?

  // user defined render function
  public override function render() : Node {
    var styleClass = switch style { /* some code */ };
    var sizeClass = /* some code */;

    return div([
      'class' => 'my-component $styleClass $sizeClass',
      'click' => click,
      'hover' => hover
    ], children);
  }
}

into this (via macros):

enum MyComponentStyle {
  Default;
  Primary;
  Other;
}

typedef MyComponentApi = {
  public function click() : Void;
  public function hover() : Void;
}

typedef MyComponentState = {
  style : MyComponentStyle,
  size : Int
}

class MyComponent extends Component<MyComponentApi, MyComponentState> {
  public var style(get, null) : MyComponentStyle;
  public var size(get, null) : Int;
  public var click(get, null) : Void -> Void;
  public var hover(get, null) : Void -> Void;

  // don't generate ctor - use super ctor

  // static convenience constructor function
  // should this call `render` on the component and return Node?
  public static function create(click : Void -> Void, ?optApi : { ?hover: Void -> Void }, style : MyComponentStyle, ?optState : { ?size : Int }, ?children : Nodes) : MyComponent {
    if (optApi == null) optApi = {};
    if (optApi.hover == null) optApi.hover = function() {}; // no-op - not sure how best to deal with this
    if (optState == null) optState = {};
    if (optState.size == null) optState.size = 123; // from @:default metadata
    return new MyComponent({
      click: click,
      hover: optApi.hover
    }, {
      style: style,
      size: optState.size
    }, children);
  }

  public override function render() : Node {
    // unchanged from above
  }

  public function get_style() : MyComponentStyle {
    return state.style;
  }

  public function get_size() : Int {
    return state.size;
  }

  public function get_click() : Void -> Void {
    return api.click;
  }

  public function get_hover() : Void -> Void {
    return api.hover;
  }

  // all other functions from above preserved here
}

EventListener doesn't get removed

I found out that an event listener is not removed in my case, i use bind to add a click listener, do you know if this is a known issue?

public static function render (state:State, api:Api) {
    return div(
	state.active ? button( "Enable") : button(["click" => api.removeState.bind()], "Disable");
    );
}

Test cases broken since yesterday's commit

haxe build.hxml
phantom.js: test errors
doom.html.TestComponent
testComponentIsRemovedFromDom: FAILURE FF
line: 194, expected 2 elements but they are 0
line: 200, expected 0 elements but they are 3
testComponentReplacedByElement: FAILURE .FF
line: 155, expected 2 elements but they are 0
line: 161, expected 0 elements but they are 3

Error: Command failed with error 1

Idea: support a selector-like syntax for creating elements with id, class, attributes, etc.

E.g. mithril has a syntax like m("div#my-id.my-class.another-class[placeholder=test][type=something]"

Maybe in Doom.hx, we could have a method like this:

The first arg would be a selector string that is parsed into attributes. You can still provide the other usual map of attributes (which would be merged with the selector attributes), and children/child.

// D for "Doom", like m for mithril ???
inline public static function D(selector : String, ?attributes : Map<String, AttributeValue>, ?children : Array<Node>, ?child : Node) {
  var attrs = parseSelector(selector); // returns Map<String, AttributeValue>
  attrs = attrs.merge(attributes); // merge map of attrs over the selector attrs?
  // not sure how to get the element name from attrs?
  return el(....);
}

Usage:

D("div#my-div.my-class.another-class[some-attr=some-value]", [ /* optional attrs */ ], [ /* optional children */ ]);

Leak in migrate

function migrate<Props>(src: doom.html.Component<Props>, dst: doom.html.Component<Props>) {

If src == dst, then there is a leak as the function proxy call stack grows with each migrate.

Possible way to embed styles for Components

Create an initialization macro function that would be invoked like: haxe --macro Doom.cssPath("../some/path/master.css"). This function should save the file path in a static variable for future macro use.

Each Component can then define a @:style("comp.css") meta (at the class level) to specify the path to a .css file for the component.

The AutoComponentBuild macro should load the .css file for each component (if specified), and append the contents in the master file specified by the Doom.cssPath init macro. This way, each component in the app can have a style sheet that is automatically combined with all other components. The @:style macro might need a way to specify or generate a unique class name for the component.

Not sure if this will work...

Make the selector parser function a macro

Parse the selector string format at compile time (for performance benefit and type-checking).

I can work on this at some point, or if you feel like doing it, free free.

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.