GithubHelp home page GithubHelp logo

davidjamesstone / superviews.js Goto Github PK

View Code? Open in Web Editor NEW
246.0 13.0 15.0 3.14 MB

Template engine targeting incremental-dom

Home Page: http://davidjamesstone.github.io/superviews.js/playground/

JavaScript 100.00%
incremental-dom virtual-dom template-language webcomponents customelements

superviews.js's Introduction

NEW! Try hyperviews instead. The same declarative template language as superviews.js but for any h(tag, props, children) compliant framework like React, hyperapp, preact....

superviews.js

On the server superviews.js is used as a template engine for google's incremental-dom.

It can also now be used in the browser to help build web applications based on Custom Elements V1

Try it out live in your browser

npm install superviews.js --save

API

tmplstr (required) - The template string.
name - The output function name (will be overridden with a <template> element).
argstr - The output function arguments (will be overridden with a <template> element).
mode - The output format. Can be one of ['es6', 'cjs', 'browser', 'amd'], if any other value is passed the function is exported as a variable with that name.

superviews(tmplstr, name, argstr, mode)

CLI

cat examples/test.html | superviews --mode=es6 --name=foo --argstr=bar > examples/test.js

Client

NEW! superviews can now be used in the browser.

Use it as a clientside library with a set of helpful classes and methods for building web applications based on the Web Components spec, specifically Custom Elements V1.

Example

Create a file called tmpl.html

<!--
If the outermost element is a `template` element and contains
an `args` attribute it will be used as the function definition.
A `name` attribute can also be supplied. These will be used to
define the enclosing function name and arguments in the incremental-dom output (see below).
-->
<template name="myWidget" args="data todos foo bar">

  <!--
  `script` tags that have no attributes are treated as literal javascript
  and will be simply inlined into the incremental-dom output.
  -->
  <script>
  function add (item) {
    todos.push(item)
  }

  function remove () {
    todos.pop()
  }
  </script>

  <!-- Attribute values can be set using javascript between curly braces {} -->
  <div class="{data.cssClass}">

    <!-- Attributes are omitted if their expression is null or undefined. Useful for `checked`, `disabled` -->
    <input type="text" disabled="{data.isDisabled}">

    <!-- Interpolation in attributes -->
    <a href="http://www.google.co.uk?q={data.query}"></a>

    <!-- Text Interpolation -->
    My name is {data.name} my age is {data.age}
    I live at {data.address}

    <!-- Any javascript can be used -->
    <div title="{JSON.stringify(data)}">Hover for json</div>

    <!-- 'on' event handlers. $event and $element are available to use in the handler. -->
    <button onclick="{alert(hi)}">Say hi</button>
    <input type="text" value="{data.val}" onchange="{data.val = this.value}">
    <a href="#" onclick="{$event.preventDefault(); model.doSomething();}">Some link</a>

    <!-- Use an `if` attribute for conditional rendering -->
    <p if="data.showMe">
      <span class="{data.bar + ' other-css'}">description</span>
    </p>

    <!-- An `if` tag can also be used for conditional
    rendering by adding a `condition` attribute. -->
    <if condition="data.showMe">
      I'm in an `if` block.
    </if>

    <!-- `elseif` and `else` tags can also be used -->
    <if condition="data.foo === 1">
      <span>1</span>
    <elseif condition="data.foo === 2">
      <span>2</span>
    <else>
      Default
    </if>

    <!-- Use a `skip` attribute for conditional patching of children -->
    <aside>
      <div skip="data.skipMe">
        <span id="{data.id}">
        </span>
      </div>
    </aside>

    <!-- The `style` attribute is special and can be set with an object. -->
    <span style="{ color: data.foo, backgroundColor: data.bar }">My style changes</span>

    <!-- The `each` attribute can be used to repeat over items.
    This includes iterating over keys on an Object or any object that has a
    forEach function e.g. an Array, Map, Set.
    Three variables are available for each iteration: $value, $item and $target.-->
    <ul>
      <li each="item in data.items">
        <span class="{ $item % 2 ? 'odd' : 'even' }">{$item}</span>
        <input value="{item.name}">
      </li>
    </ul>

    <!-- Looping over arrays -->
    <ul>
      <li each="item in data.arr">
        <span>{item.name}</span>
      </li>
    </ul>

    <!-- Looping over object keys -->
    <ul>
      <li each="key in data.obj">
        <span title="hello">{key} - {data.obj[key]}</span>
      </li>
    </ul>

    <!-- The `each` attribute also supports defining a `key` to use.
    For Arrays and Objects this is done automatically for you.
    
    If you are iterating a Map, this should be set to identify each item in the list. 
    This allow the diff patch in to keep track of each item in the list.
    See http://google.github.io/incremental-dom/#conditional-rendering/array-of-items.
    The key used here is `product.id`.
     -->
    <ul>
      <li each="product, product.id in data.products">
        {product.name}
      </li>
    </ul>

    <!-- Conditional iteration -->
    <ul>
      <li if="data.items.length" each="item, item.id in data.arr">
        {item.name}
      </li>
      <li if="!data.items.length" class="list-header">
        No items found
      </li>
    </ul>
  </div>

</template>

cat tmpl.html | superviews > tmpl.js

Converts the template above to this incremental-dom code:

;(function () {
var hoisted1 = ["type", "text"]
var hoisted2 = ["type", "text"]
var hoisted3 = ["href", "#"]
var hoisted4 = ["title", "hello"]
var hoisted5 = ["class", "list-header"]
var __target

return function myWidget (data, todos, foo, bar) {
  function add (item) {
      todos.push(item)
    }

    function remove () {
      todos.pop()
    }
  elementOpen("div", null, null, "class", data.cssClass)
    elementOpen("input", "ba1d808c-0069-43bc-a345-89d8a60fa494", hoisted1, "disabled", data.isDisabled)
    elementClose("input")
    elementOpen("a", null, null, "href", "http://www.google.co.uk?q=" + (data.query) + "")
    elementClose("a")
    text(" \
        My name is " + (data.name) + " my age is " + (data.age) + " \
        I live at " + (data.address) + " \
     \
        ")
    elementOpen("div", null, null, "title", JSON.stringify(data))
      text("Hover for json")
    elementClose("div")
    elementOpen("button", null, null, "onclick", function ($event) {
      var $element = this;
    alert(hi)})
      text("Say hi")
    elementClose("button")
    elementOpen("input", "0887e662-2503-4669-b314-2d155cc72cad", hoisted2, "value", data.val, "onchange", function ($event) {
      var $element = this;
    data.val = this.value})
    elementClose("input")
    elementOpen("a", "4308eec1-f2dc-4247-a8d6-c07e81db0c3e", hoisted3, "onclick", function ($event) {
      var $element = this;
    $event.preventDefault(); model.doSomething();})
      text("Some link")
    elementClose("a")
    if (data.showMe) {
      elementOpen("p")
        elementOpen("span", null, null, "class", data.bar + ' other-css')
          text("description")
        elementClose("span")
      elementClose("p")
    }
    if (data.showMe) {
      text(" \
            I'm in an `if` block. \
          ")
    }
    if (data.foo === 1) {
      elementOpen("span")
        text("1")
      elementClose("span")
    } else if (data.foo === 2) {
      elementOpen("span")
        text("2")
      elementClose("span")
    } else {
      text(" \
            Default \
          ")
    }
    elementOpen("aside")
      elementOpen("div")
        if (data.skipMe) {
          skip()
        } else {
          elementOpen("span", null, null, "id", data.id)
          elementClose("span")
        }
      elementClose("div")
    elementClose("aside")
    elementOpen("span", null, null, "style", { color: data.foo, backgroundColor: data.bar })
      text("My style changes")
    elementClose("span")
    elementOpen("ul")
      __target = data.items
      if (__target) {
        ;(__target.forEach ? __target : Object.keys(__target)).forEach(function($value, $item, $target) {
          var item = $value
          var $key = "163c079d-6890-40f1-8983-b4119652d7ca_" + $item
          elementOpen("li", $key)
            elementOpen("span", null, null, "class",  $item % 2 ? 'odd' : 'even' )
              text("" + ($item) + "")
            elementClose("span")
            elementOpen("input", null, null, "value", item.name)
            elementClose("input")
          elementClose("li")
        }, this)
      }
    elementClose("ul")
    elementOpen("ul")
      __target = data.arr
      if (__target) {
        ;(__target.forEach ? __target : Object.keys(__target)).forEach(function($value, $item, $target) {
          var item = $value
          var $key = "9ee2a95c-ce40-4c43-9e1b-bb1e3771c72f_" + $item
          elementOpen("li", $key)
            elementOpen("span")
              text("" + (item.name) + "")
            elementClose("span")
          elementClose("li")
        }, this)
      }
    elementClose("ul")
    elementOpen("ul")
      __target = data.obj
      if (__target) {
        ;(__target.forEach ? __target : Object.keys(__target)).forEach(function($value, $item, $target) {
          var key = $value
          var $key = "07608362-dc5c-4fca-9f46-381ffc62a929_" + $item
          elementOpen("li", $key)
            elementOpen("span", "4bf05389-7b34-4184-9ae5-2f1371d46d05_" + $key, hoisted4)
              text("" + (key) + " - " + (data.obj[key]) + "")
            elementClose("span")
          elementClose("li")
        }, this)
      }
    elementClose("ul")
    elementOpen("ul")
      __target = data.products
      if (__target) {
        ;(__target.forEach ? __target : Object.keys(__target)).forEach(function($value, $item, $target) {
          var product = $value
          var $key = "494094aa-b914-405e-b489-31348c78a2f7_" + product.id
          elementOpen("li", $key)
            text(" \
                    " + (product.name) + " \
                  ")
          elementClose("li")
        }, this)
      }
    elementClose("ul")
    elementOpen("ul")
      if (data.items.length) {
        __target = data.arr
        if (__target) {
          ;(__target.forEach ? __target : Object.keys(__target)).forEach(function($value, $item, $target) {
            var item = $value
            var $key = "f53fcb3e-8035-4108-91bc-1d7661d41681_" + item.id
            elementOpen("li", $key)
              text(" \
                      " + (item.name) + " \
                    ")
            elementClose("li")
          }, this)
        }
      }
      if (!data.items.length) {
        elementOpen("li", "39dad44a-39c4-4d2d-bb31-7daf5bef8b73", hoisted5)
          text(" \
                  No items found \
                ")
        elementClose("li")
      }
    elementClose("ul")
  elementClose("div")
}
})()

browserify

Using browserify? There's the superviewify transform allowing you to simply require your templates and have them automatically compiled to incremental-dom javascript.

npm install superviewify --save

License

MIT

superviews.js's People

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

Watchers

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

superviews.js's Issues

Add support for skipNode

Add support for the skipNode method in addition to skip.
See: google/incremental-dom@ec0537b

This was only added a few weeks ago and has yet to even be rolled into a release yet, so this is a bit of a preemptive request for the time being.

Additional thought: Would it be useful to add support for skip and skipNode tags in addition to attributes?

option mode to export to amd like requirejs format

Support to amd format
format=amd

define(["exports",'incremental-dom'], function (exports,IncrementalDOM) {
    "use strict";
    var myWidget = (function () {
        function myWidget(data) {
            IncrementalDOM.elementOpen('div');
            IncrementalDOM.text('welcome, '+data.name);
            IncrementalDOM.elementClose('div');
        }
        return myWidget;
    }());
    exports.myWidget = myWidget;
});

Gulp integration

Hy, great job with superview, how i can compile it with gulp to virtual dom?

how to pass properties data?

how i can pass the "prop" values to the template:
I'm doing this without success

<template name="UsingCustomTag" args="$data">
    <div>
        <h5>USING A CUSTOM TAG</h5>
            <x-mytag text="test of test">       </x-mytag>
    </div>
</template>

I wait get this:

elementOpen('x-mytag', index, null,null ,
    'props', {
        text:'test of test'
      });
elementClose('x-mytag');

as this exemple:

https://github.com/google/incremental-dom/blob/master/demo/customelement.html

but the result is this:

var hoisted1 = ["text", "test of test"]
var __target
exports.UsingCustomTag = (function () {
  return function UsingCustomTag ($data) {
  elementOpen("div")
    elementOpen("h5")
      text("USING A CUSTOM TAG")
    elementClose("h5")
    elementOpen("x-mytag", "9c91f494-b337-4f73-8499-3c9b79606f53", hoisted1)
    elementClose("x-mytag")
  elementClose("div")
}
})()

Feature Request: Option to create cjs/amd/ems output

It would be helpful to be able to create cjs, amd, and/or ems modules, as opposed to the default stand-alone that assumes certain globals exist.

For example, cjs output would be something like:

var IncrementalDOM = require('incremental-dom'),
    elementOpen = IncrementalDOM.elementOpen,
    elementClose = IncrementalDOM.elementClose,
    elementVoid = IncrementalDOM.elementVoid,
    text = IncrementalDOM.text;

// hoisted values

module.exports = function description (data) {
elementOpen("div")
// ...
elementClose("div")
}

Setting innerHTML

Hi,

Is there a way to set element's innerHTML through the templates? Does Incremental DOM even allow this? I'm currently putting placeholders and manually populating them with content after rendering. In some cases, re-thinking architecture helps to avoid using innerHTML, but sometimes it's unavoidable, like inlining parsed markdown (which is an HTML string) or other cases of user generated content.

add a global config

It makes sense to be able to set some global options. Two concrete examples that I encountered:

  1. arguments for the compiled idom render function

If the user prefers to use ctx as argument (for instance) instead of the default data, it is necessary to wrap the superviews template with <template>:

<template args="ctx">
    <div>
        hello {ctx.name}!
    </div>
</template>

If this was a config option, the user could simply do:

    <div>
        hello {ctx.name}!
    </div>
  1. Function prefix for the incremental dom calls

Currently superviews is calling the idom functions directly:

elementOpen("div");
elementClose("div");

If the user is including incremental dom via a browser script, it is easier to reference the functions from the "IncrementalDOM" object that is exposed globally

IncrementalDOM === window.IncrementalDOM; // true
IncrementalDOM.elementOpen("div");
IncrementalDOM.elementClose("div");

elementPlaceholder is deprecated

Incremental DOM v0.4.0 was released a few days ago:
https://github.com/google/incremental-dom/blob/master/CHANGELOG.md

The main changes seem to be:

  1. elementPlaceholder is now deprecated. More details:
    google/incremental-dom#206

The skip function should be used instead:
https://github.com/google/incremental-dom/blob/master/test/functional/skip.js

  1. there's a new patchOuter function ("patches an Element rather than an Element's children")

It would be great if superviews could be updated to work with the new version.

Adding attributes conditionally

Hi,

I'd like to add attributes depending on a condition, something like:

<input type="checkbox" { isChecked ? 'checked' : ''}>

which should add checked attribute to the input if isChecked is true:

<input type="checkbox" checked>

That doesn't seem to be possible with the current implementation. I'm wondering if there is another, declarative way to do it, and if not, whether it was left out because of some architectural constraints or simply was not previously considered?

Feature Request: Each tag

Proposal:

Add an <each> tag that functions similar to the dual <if>, which can be used as an attribute or as its own node. each would take a single attribute, condition.

Syntax:

<each condition="item in list">
    <span>{item}</span>
</each>

Reason:

<div each="item in list" if="true">{$value}</div> has two different potential meanings:

<each condition="item in list">
    <if condition="true">{$value}</if>
</each>

and

<if condition="true">
    <each condition="item in list">{$value}</each>
</if>

however, only the latter example is possible with the current syntax.

Iterating over Sets/Maps

The documentation appears to suggest it's possible to iterate over Sets and Maps; however, that does not appear to currently be the case.

Iteration code is transformed into the following format:
(Array.isArray(model) ? model : Object.keys(model)).forEach(

Since a Set is not an array (Array.isArray(set) will return false), and Object.keys(set) will always return an empty array, no iteration occurs. The same is true for a Map.

I believe it is possible to instead detect if a variable has a forEach method, and default to that if it does. If it does not have one, then Object.keys(model).forEach could be used instead (or maybe we'll even find a way to sensibly add a forEach to objects...):

(typeof model.forEach === 'function' ? model : Object.keys(model)).forEach(.

Note: In the meantime, it's possible to use Array.from(set) in the template as a workaround.

Feature Request: Support for Conditional Attributes

http://google.github.io/incremental-dom/#conditional-rendering/logic-in-attributes

Currently there's no way to have an attribute applied conditionally beyond using duplicate if blocks. For example:

<p class="{value === true ? 'active' : ''}">

will be compiled as:

elementOpen("p", null, null, "class", "" + (value === true ? 'active' : '') + "")

Thus the value of the class is calculated prior to the script running and will always be the original value, rather than updating as the model changes.

It would be nice if attributes with logic would update on each patch to reflect changed states.

SVG <use> call being blocked by JS

I am trying to use a custom svg and am having mixed success or none at all when injecting it into the template. I have used these in other JS applications without issues.

<button class="o-dropdown-menu__menu-close">
  <svg role="img"
      aria-labelledby="r2"
      focusable="false"
      class="my-icon">
    <title id="r2">Close dropdown menu</title>
    <use xlink:href="#my-icon"></use>
  </svg>
</button>`

When using xlink:href the SVG does not render at all and after investigation it appears as if the use call is being blocked. If I removed the xlink: and solely use href the svg renders in every browser but Safari.

Hoping you might be able to provide some insight.

Inject HTML

This is kind of related to 34.

I'm looking for a way to inject safe HTML into an element, like:

angular 1:
angular 2: <div [innerHTML]="theHtmlString">

Include templates inside other templates

I find it an issue, where I want to include multiple templates inside a parent template using an HTML like syntax to create modular components.

Is there a possibility to extend the handler function (the one on line 139), therefore allowing us to create custom tags like <include> or <component> that would be compiled to js functions?
For example:

<template name="todoComponent" args="attrs innerHTML state">
  <span data-type="{attrs.type}" data-done="{state.done}">{innerHTML}</span>
</template>

<template name="todoListComponent" >
  <component name="todoComponent" type="work">Fix Bug01</component>
  <component name="todoComponent" type="work">Fix Bug02</component>
</template>

variable $index not found

After update to version 2.0.0, the variable $index of each has replaced by variable $item ????

And now how can i do this?

<div each="todo in todos"  onclick="{data.setIndex($item || $index)}">

</div>

I have found this manner

onclick="{()=>data.setIndex($item)}"

but when this compile to js the result is wrong if the browser don't suport arrow functions

Ability to import external modules

Wouldn't it be nice to have the ability to import external modules?
Possible solution:

<script mode='hoisted'>
import * as R from 'ramda'
import anotherTemplate from './anotherTemplate.js'
</script>

Make the interpolation symbols configurable

Style tags such as the following create errors in the generated javascript:

<style>.my-class { background: red; }</style>

Superviews tries to interpolate the stuff within the curly braces.

For my purposes, I've changed the interpolate function to

function interpolate (text) {
  text = text.replace(/\{%/g, '" + (')
  text = text.replace(/%\}/g, ') + "')
  text = text.replace(/\n/g, ' \\\n')
  return strify(text)
}

Which works great for me, but forces people to use {%variable%} instead of {variable}. Instead of introducing a breaking change, I suggest making the interpolation symbols configurable.

Repetitive blocks

If I have a repetitive block in my template (aka partial), is it possible to define it and reference it from multiple locations in the template?

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.