Package Exports
- hyper-element
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (hyper-element) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
hyper-element
A combining the best of hyperHTML and Custom Elements!
Your new custom-elements are built with hyperHTML and will be re-rendered on attribute and store change.
why hyper-element
- hyper-element is fast & small: under 5k (minify + gzip)
- With only 1 dependency: The super fast renderer hyperHTML
- With a completely stateless approach, setting and reseting the view is trivial
- Simple yet powerful Api
- Built in template system to define markup fragments
- Inline style objects supported (similar to React)
- First class support for data stores
Live Demo
Define a Custom Element
document.registerElement("my-elem", class extends hyperElement{
render(Html){
Html`hello ${this.attrs.who}`
}
})To use your element
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/hyperhtml@latest/min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hyper-element@latest/source/bundle.min.js"></script>
</head>
<body>
<my-elem who="world"></my-elem>
</body>
<html>Output
<my-elem who="world">
hello world
</my-elem>Api
Define your element
There are 2 functions. render is required and setup is optional
render
This is what will be displayed with in your element. Use the Html to define your content
render(Html,store){
Html`
<h1>
Lasted updated at ${new Date().toLocaleTimeString()}
</h1>
`
}setup
The setup function wires up an external data-source. This is done with the onNext argument that binds a data source to your renderer.
Connent a data source
Example to re-rendering when the mouse moves and pass current mouse values to render
// getMouseValues(){ ... }
setup(onNext){
// the getMouseValues function will be call before each render and pass to render
const next = onNext(getMouseValues)
// call next on every mouse event
onMouseMove(next)
// cleanup logic
return ()=>{ console.warn("On remove, do component cleanup here") }
}re-rendering with out a data source
Example of re-rendering every second
setup(onNext){
setInterval(onNext(), 1000);
}Set initial values to pass to every render
Example of attaching an object, that will be used on every render
setup(onNext){
onNext({max_levels:3})
}How to cleanup
Any logic you wish to run when the element is removed from the page should be returned as a function from the setup function
// listen to a WebSocket
setup(onNext){
const next = onNext();
const ws = new WebSocket("ws://127.0.0.1/data");
ws.onmessage = ({data}) => next(JSON.parse(data))
// Return way to unsubscribe
const teardown = ws.close.bind(ws);
return teardown
}
render(Html,incomingMessage){
// ...
}Returning a "teardown function" from setup address the problem of needing a reference to the resource you what to release. If the "teardown function" was a public function. We would need to store the reference to the resource some, that the teardown can access it when call.
With this approach there is no leaking of references.
✎ To subscribe to 2 events
setup(onNext){
const next = onNext(user);
mobx.autorun(next); // update when changed (real-time feedback)
setInterval(next, 1000); // update every second (update "the time is now ...")
}
this
- this.attrs : the attributes on the tage
<my-elem min="0" max="10" />={ min:0, max:10 }- Casting types supported:
Number
- Casting types supported:
- this.store : the value returned from the store function. !only update before each render
- this.wrappedContent : the text content embedded between your tag
<my-elem>Hi!</my-elem>="Hi!" - this.dataset: This allows reading and writing to all the custom data attributes
data-*set on the element.- Data will be parsed to try and cast them to Javascript types
- Casting types supported:
Object,Array,Number&Boolean - e.g.
<my-elem data-users="['ann','bob']"></my-elem>tothis.dataset.users // ["ann","bob"]
Templates
You can declare markup to be used as a template within the custom element
To enable templates:
- Add an attribute "templates" to your custom element
- Define the template markup within your element
In you document:
<my-list template data-json='[{"name":"ann","url":""},{"name":"bob","url":""}]' >
<div>
<a href="{url}">{name}</a>
</div>
</my-list>Implementation:
document.registerElement("my-list",class extends hyperElement{
render(Html){
Html`
${this.dataset.json.map(user => Html.template(user))}
`
}
})Output:
<my-list template data-json='[{"name":"ann","url":""},{"name":"bob","url":""}]' >
<div>
<a href="">ann</a>
</div>
<div>
<a href="">bob</a>
</div>
</my-list>Fragments
Fragments are pieces of content that can be loaded asynchronously.
You define one with a class property starting with a capital letter.
To use one with in you render. Pass an object with a property matching the fragment name and any value needed.
The fragment function should return an object with the following properties
- placeholder: the placeholder to show while resolving the fragment
- once: Only generate the fragment once.
- Default:
false. The fragment function will be run on every render!
- Default:
and one of the following as the fragment's result:
- text: An escaped string to output
- any: An type of content
- html: A html string to output,
document.registerElement("my-friends",class extends hyperElement{
FriendCount(user){
return {
once:true,
placeholder: "loading your number of friends",
text:fetch("/user/"+user.userId+"/friends")
.then(b => b.json())
.then(friends => {
if (friends) return `you have ${friends.count} friends`;
else return "problem loading friends";
})
}
}
render(Html){
const userId = this.attrs.myId
Html`<h2> ${{FriendCount:{userId}}} </h2>`
}
})Ouput:
<my-friends myId="1234">
<h2> loading your number of friends </h2>
</my-friends>then
<my-friends myId="1234">
<h2> you have 635 friends </h2>
</my-friends>Inline fragments
The fragment function will be run on every render!
Example of dynamically loading markup
document.registerElement("profile-widget",class extends hyperElement{
render(Html){
Html`${{
html: fetch('/widgets/profile/'+this.attrs.profileId)
.then(recivedMarkup => recivedMarkup.text())
placeholder: 'Loading your profile ...'
}}`
}
})Ouput:
<profile-widget profileId="1234">
Loading your profile ...
</profile-widget>then
<profile-widget profileId="1234">
<div>
<img>...</img>
...
</div>
</profile-widget>Styling
Supports an object as the style attribute. Compatible with React's implementation.
Example of centering an element
render(Html){
const style= {
position: "absolute",
top: "50%", left: "50%",
marginRight: "-50%",
transform: "translate(-50%, -50%)"
}
Html`<div style=${style}> center </div>`
}
Example of connecting to a data store
backbone
var user = new (Backbone.Model.extend({
defaults: {
name: 'Guest User',
}
}));
document.registerElement("my-profile", class extends hyperElement{
setup(onNext){
user.on("change",onNext(user.toJSON.bind(user)));
// OR user.on("change",onNext(()=>user.toJSON()));
}
render(Html,{name}){
Html`Profile: ${name}`
}
})mobx
const user = observable({
name: 'Guest User'
})
document.registerElement("my-profile", class extends hyperElement{
setup(onNext){
mobx.autorun(onNext(user));
}
render(Html,{name}){
Html`Profile: ${name}`
}
})redux
document.registerElement("my-profile", class extends hyperElement{
setup(onNext){
store.subcribe(onNext(store.getState)
}
render(Html,{user}){
Html`Profile: ${user.name}`
}
})