Giter Site home page Giter Site logo

base-element's Introduction

base-element

An element authoring library for creating standalone and performant elements.

build status NPM version experimental

Sauce Test Status

View this example List element in use with:

Or other examples:

example usage

You can construct your element API however you choose. A way that I prefer is by inheriting prototypes:

var BaseElement = require('base-element')

function Bear () {
  BaseElement.call(this)
}
Bear.prototype = Object.create(BaseElement.prototype)
// Or inherits(Bear, BaseElement)
// Or class Bear extends BaseElement

Then build your elements:

Bear.prototype.render = function (typeOfBear) {
  // Create a virtual DOM tree
  var vtree = this.html('div.bear', ['Im a ' + typeOfBear + '!'])
  // Call afterRender with your vtree when returning your vtree
  return this.afterRender(vtree)
}

Prefer just functions?

If you prefer just functions, an alternative interface is available:

var createElement = require('base-element')

// Create an element on a parent
var el = createElement(document.body)
el.render(function () {
  // Render a button upon clicked will alert
  return el.html('button', {
    onclick: function (e) {
      window.alert(e.target.innerText + ' button was clicked')
    }
  }, 'click me')
})

data down, events up

DOMs work best (in the opinion of myself and many) when data goes down and event (or actions) go up.

A simple example is a button element that changes when clicked. How it changes is up to the element but what it changes to is up to the user.

This is our Button element:

var BaseElement = require('base-element')

function Button () {
  BaseElement.call(this)
}
Button.prototype = Object.create(BaseElement.prototype)
// Or inherits(Button, BaseElement)
// Or class Button extends BaseElement

Button.prototype.render = function (label) {
  var self = this
  // The "label" data is coming down
  var vtree = this.html('button', {
    onclick: function (event) {
      // We send the "clicked" event up
      self.send('clicked', event.target)
    }
  }, label)
  return this.afterRender(vtree)
}

and this is the user's implementation, creates a button and on every click it changes to a random number:

var button = require('your-button')()
button.addEventListener('clicked', function (node) {
  button.render('button label ' + Math.random())
})

nested architecture

Elements created using base-element are intended on being shared and extended by others. Each element should not require an additional library/framework to run it or be injected into it in order to be ran. Elements should be standalone.

For example if you create an input-box element and published on npm:

var BaseElement = require('base-element')
function InputBox (el) {
  BaseElement.call(this, el)
}
InputBox.prototype = Object.create(BaseElement.prototype)
module.exports = InputBox

InputBox.prototype.render = function (value) {
  // Builds an <input value="{value}: />
  return this.afterRender(this.html('input', {
    onkeyup: function(e) {
      // When keys are typed in it we send the value up
      this.send('changed', e.target.value)
    }.bind(this),
    value: value || ''
  }))
}

Later yourself or another user can extend input-box to add functionality on top, such as email-input:

var InputBox = require('input-box')
function EmailInput (el) {
  InputBox.call(this, el)

  // When we receive a "changed" event from InputBox, handle it here
  this.addEventListener('changed', function (text) {
    /* Perform some email validation on text here,
       then render() if we need an update */
  })
}
EmailInput.prototype = Object.create(InputBox.prototype)
module.exports = EmailInput

EmailInput.prototype.render = function (data) {
  data = data || {}
  var vtree = this.html('div', [
    // Put a <label>Enter your email</label> inside this <div>
    this.html('label', data.label || 'Enter your email'),
    // Call the InputBox's render
    InputBox.prototype.render(data.value)
  ])
  // Return the virtual DOM tree
  return this.afterRender(vtree)
}

Both input-box and email-input can be ran on their own. When input-box updates over time, email-input can stay on a previous version until an upgrade can be made.

install

npm with browserify, webpack, etc

  • npm install base-element
  • var BaseElement = require('base-element')

standalone

  • copy/download/etc dist/base-element.js
  • <script src="base-element.js"></script>
  • <script>var element = new BaseElement()</script>

api

var element = new BaseElement([attachTo])

attachTo is a DOM element you want to append to such as document.body

By default, the element will not attach itself to a parent node. This is useful for handling the rendering on your own.

element.send(name[, params...])

Sends an event up with a given name and params.

element.addEventListener(name, function)

Register an event listener for a given name:

element.addEventListener('clicked', function (params) {})

element.afterRender([params...])

This method needs to be called when returning a constructed virtual tree. It will detect if we are at the top of the render tree and perform the DOM diff and patching.

Button.prototype.render = function (data) {
  var vtree = this.html('button')
  return this.afterRender(vtree)
}

element.html(tag[, options], value)

A convenience wrapper for creating virtual-hyperscript nodes, i.e.:

var h = require('virtual-dom/h')
var vtree = h('div', 'Testing')

// is the same as
var vtree = this.html('div', 'Testing')

element.toString([data...])

For rendering your element as a string of HTML. data is any initial data passed to your render function.

element.element

The root DOM node the virtual tree resides on.

element.vtree

The current virtual DOM tree of the base element.

default events

load and unload events will be sent by default if your top level element registers this as it's properties:

var BaseElement = require('base-element')
function Button(el) {
  BaseElement.call(this, el)
  this.addEventListener('load', function (node) {
    console.log(node + ' has loaded!')
  })
  this.addEventListener('unload', function (node) {
    console.log(node + ' has unloaded!')
  })
}
Button.prototype.render = function (data) {
  // The top level element is provided with `this`, events will be fired
  return this.afterRender(this.html('button', this, 'click me'))
}

similar projects

  • vel create and render virtual-dom elements with ease

license

(c) 2015 Kyle Robinson Young. MIT License

base-element's People

Contributors

bendrucker avatar shama avatar yoshuawuyts 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  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

base-element's Issues

combine node.render() + this.afterRender() ?

From this.afterRender():

This method needs to be called when returning a constructed virtual tree.

If the method is mandatory, wouldn't it make sense to always run it internally? What are the use cases for not running this method?

const baseElement = require('base-element')
const html = require('virtual-dom')

const el = baseElement()

// I would prefer this:
el.render(data => html(`<div></div>`))

// over always typing this:
el.render(data => this.afterRender(html(`<div></div>`)))

I'm not sure how the internals are laid out, but passed in function is being bound it might simplify the code and possibly make the interface more resilient. Thanks!

replaceChild vs appendChild

I'm working on better mounting elements that were initially server rendered and looking for feedback

/cc @yoshuawuyts @bendrucker @chinedufn @sethvincent

Currently if a server renders <div class="app">Loading</div> and an element is loaded with createElement(document.querySelector('.app')) the resulting HTML is:

<div class="app">Loading<div class="app">Client Side Loaded</div></div> because the attach point always appends. We want to replaceChild instead to get the following HTML:

<div class="app">Client Side Loaded</div>

Which solution is preferred?

A) Leave attaching up to the user
This is what I currently do but it's a bit verbose

var el = createElement()
document.querySelector('.app').appendChild(el.element)
// or to mount
document.body.replaceChild(el.element, document.querySelector('.app'))

B) Detect if the attaching node is the same as rendered
The most simplest for the end user but a bit magical
We could check if the node has the same id or class and assume the user wants to replaceChild instead of appendChild:

<div class="app loading">Loading</div>
var el = createElement(document.querySelector('.app'))
// "el" is a div with the class "app" so we replace instead of append

C) Add methods to explicitly call
Adds more APIs to all elements which isn't desired

var el = createElement()
el.appendTo(document.body)
// or to mount
el.mountTo(document.querySelector('.app'))

Thanks!

.toString()?

When rendering on the server it can be neat to implement a .toString() function (or equivalent) to render components to html. I'm not sure what the tradeoffs are; but I think it's feasible to implement for every component. Thanks!

See Also

extract style > css / css examples

CSS can be attached to elements with: this.attachCSS('css src') which will localize the CSS to the element.

  • How does this fair with nested elements? Im thinking top level elements should rule the CSS of all nested elements
  • Extract into separate module and add a ton more tests
  • A dynamic className wont work well if written out to a file.css
  • Experiment more with overriding element CSS

Document setTimeout delay rationale

virtual-dom definitely requires you to wait until the next tick in hooks to be sure that DOM element has actually been inserted. base-element uses a timeout of 10 for load/unload events:

base-element/index.js

Lines 72 to 74 in 95f5ec2

setTimeout(function BaseElement_unhook_setTimeout () {
self.cb('unload', node)
}, 10)

Why 10ms via setTimeout instead of just nextTick?

Default __appendTo__ to false instead of document.body

Wanted to propose this for a future breaking version. I'm finding myself wanting to output a vtree from render without any DOM side effects far more often than I'm attaching components to the body. Super trivial to change the default in userland in the mean time.

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.