Batteries Included View Framework
┌──────────────────────────────────────────────────┐
│ │
│ Module size: 3.42 KB, Gzipped size: 1.34 KB │
│ │
│ UMD size: 23.09 KB, Gzipped size: 7.83 KB │
│ │
└──────────────────────────────────────────────────┘
npm install tram-one --save
Tram-One is a view framework for developers who want to jump straight into building on the web. At its core, Tram-One is a collection of a few packages that gives you the tools to start working right away. That is to say... Batteries Included!
Tram-One is a project built to make exposing custom elements easy, and to have redux-like state management, and basic routing by default. It was created out of the frustration of having to scaffold the same dependencies over and over again.
Tram-One was also created to avoid a lot of the syntax that locks you into frameworks like Vue and React. The components written here can mimic the syntax you're already familiar, or help you create pure HTML friendly code.
While Tram-One comes with several packages to help you on your way, it does not include a way to bundle and run your code. The obvious answers are webpack, browserify, budo, etc...
If you want to quickly get started though, use tram-one express!
$ npm install -g tram-one-express
$ tram-one-express app-name
Tram-One Express builds a local project with everything you need to get started developing! It includes all the dependencies and some example code to help you get familiar with Tram-One.
Tram-One is a collection of excellent packages (and
some forks).
Here are the different package that make Tram-One possible...
For Rendering:
For Routing:
For State Management:
While not used in this project, Tram-One is heavily inspired by the choo view framework. Special thanks go out to the people on that project, and its creator, Yoshua Wuyts. If you like some of the things here, definitely go check out that project.
This video tutorial goes through the process of build a Tram-One web app from start to finish.
While it does not go into depth on all the things you can do with Tram-One, it's a great introduction to help you get started building a Tram-One app.
Link: https://youtu.be/mgHJbqls-wk
Here is a tiny example, the bare-minimum for creating an app.
Tram = require('tram-one') // pull in the library
const app = new Tram() // create an instance of Tram-One
// create the html function
const html = Tram.html()
// home page to load on the main route
const home = () => {
// we use html takes in a template literal of your standard HTML
return html`
<div>
🚋 Fun Times on Tram-One!
</div>
`
}
// add routes, by using path matchers with function components
app.addRoute('/', home)
// attach the app to an element with the class 'main'
app.start('.main')
Here is an example which makes use of the custom element, routing, and redux state management.
Tram = require('tram-one') // pull in the library
const app = new Tram() // create an instance of Tram-One
// create the html function, with no registry
const html = Tram.html()
// create a custom element to display a color option
const colorElement = (attrs, children) => {
return html`
<button onclick=${attrs.onclick}>${children}</button>
`
}
// create a new html function, with color-button
// the key can be any format, capitalize, kebab, whatever!
const cHtml = Tram.html({
'color-button': colorElement
})
// create a set of actions, that handles changing the color of the app
const colorActions = {
init: () => 'blue',
setColor: (currentColor, newColor) => newColor
}
// home page to load on the main route
const home = (store, actions) => {
// actionCreator that dispatches to the reducer
const onSetColor = (color) => () => {
actions.setColor(color)
}
// we use cHtml so that we have color-button available in the template
return cHtml`
<div>
I think the best color for this wall is... ${store.color}!
or maybe it's...
<color-button onclick=${onSetColor('blue')}>blue</color-button>
<color-button onclick=${onSetColor('red')}>red</color-button>
<color-button onclick=${onSetColor('green')}>green</color-button>
</div>
`
}
// add routes, by using path matchers with function components
app.addRoute('/', home)
app.addRoute('/404', noPage)
// add actions and save the state as `color` on the store
app.addActions({color: colorActions})
app.start('.main')
You can find more examples in the
examples directory.
You can run these examples by cloning the repo, and running
npm install
npm run example
Here are some most bodacious examples out in the real world
- Point-Cards - A planning poker web app that uses websockets (Tram-One v1.0.0)
- Hacktober & Tram-One - A static github pages app that pulls github issues for Hacktoberfest (Tram-One v2.0.0)
Tram-One has a simple interface to help build your web app.
Reference: hyperx, bel-create-element, rbel
Tram.html
returns a function that can be used to transform
template literals into Node DOM trees.
It can take in an optional registry
, which is a mapping of tag
names to functions that render your custom tags.
Because it is static, you call the function off of Tram-One, you do not need to have an instance of Tram-One to use this function.
Example:
/* pageWraper.js (custom element) */
const html = Tram.html()
module.exports = (attrs, children) => {
return html`
<div style=${attrs.style}>
<h1>Tram-One!</h1>
<div style='padding-left: 2em'>
${children}
</div>
</div>
`
}
/* index.js */
const pageWraper = require('./pageWraper')
const html = Tram.html({
// can map with kebab
'page-wraper': pageWraper,
// or with capitalization
'PageWraper': pageWraper,
// or whatever
'wrap': pageWraper
})
const home = () => {
return html`
<wrap>
This is my shiny app!
</wrap>
`
}
new Tram()
returns an instance of the Tram. The constructor
takes in an options
object, which can have a defaultRoute
.
defaultRoute
by default is /404
, but you can set it to whatever path
you want to load when path matching fails.
Example:
/* index.js */
// let's have all routes go to home
const app = new Tram({defaultRoute: '/'})
const html = Tram.html()
const home = (state) => {
return html`<div>This is my shiny app!</div>`
}
app.addRoute('/', home)
Reference: hover-engine
app.addActions
adds a set of actions that can be triggered in the instance of Tram-One.
It takes in one argument, an object where:
the keys are values that can be pulled in the view
the values are actions that can be triggered in the view
Example:
/* index.js */
const app = new Tram()
const html = Tram.html()
// in this example, `vote` is a number
// but in a larger app, this could be an object
// with multiple key-value pairs
const voteActions = {
init: () => 0,
up: (vote) => vote + 1,
down: (vote) => vote - 1
}
const home = (state, actions) => {
const upvote = () => {
actions.up()
}
const downvote = () => {
actions.down()
}
return html`
<div>
<h1> Votes: ${state.votes} </h1>
<button onclick=${upvote}>UPVOTE</button>
<button onclick=${downvote}>DOWNVOTE</button>
</div>
`
}
app.addActions({votes: voteActions})
Reference: rlite
app.addRoute
will associate a component with a route.
path
should be a matchable route for the application. Look up
rlite
to see all the possible options here.
page
should be a function that takes in a store
, actions
and params
.
The params
object passed into the page
function will have any path parameters and query params.
Example:
/* index.js */
const app = new Tram()
const html = Tram.html()
const homePage = () => {
return html`<div>This is my shiny app!</div>`
}
const colorPage = (store, actions, params) => {
const style = `
background: ${params.color};
width: 100px;
height: 100px;
`
return html`<div style=${style} />`
}
const noPage = () => {
return html`<div>Oh no! We couldn't find what you were looking for</div>`
}
app.addRoute('/', homePage)
app.addRoute('/:color', colorPage)
app.addRoute('/404', noPage)
app.start
will kick off the app. Once this is called the app is mounted onto the
selector
.
selector
can be a node or a css selector (which is fed into
document.querySelector
).
pathName
can be an initial path, if you don't want to check the browser's
current path.
This method only works on the client. If you are running Tram on a server, then
use .toString()
.
Note: setting pathName
is great for testing, but prevents other routes from
being reached on page reload.
Example:
/* index.html */
<html>
<head>
<title>Tram One</title>
</head>
<body>
<div class="main"></div>
<script src="/index.js"></script>
</body>
</html>
/* index.js */
const app = new Tram()
const html = Tram.html()
const homePage = (state) => {
return html`<div>This is my shiny app!</div>`
}
app.addRoute('/', homePage)
app.start('.main')
WARNING: INTENDED FOR INTERNAL USE ONLY
app.mount
matches a route from pathName
, passes in a store
and actions
object,
and either creates a child div, or updates a child div under selector
.
This was created to clean up the code in the library, but may be useful for testing.
YOU SHOULD NEVER CALL THIS DIRECTLY FOR YOUR APP
app.toNode
returns a HTMLNode of the app for a given route and store. The
function matches a route from pathName
, and either takes in a store
, or
uses the default store (that's been created by adding reducers).
While initially created to clean up the code in the library, this can be useful if you want to manually attach the HTMLNode that Tram-One builds to whatever.
app.toString
returns a string of the app for a given route and store. It has
the same interface at app.toNode
, and basically just calls .outerHTML
(or
toString
on the server) on the node.
This can be useful if you want to do server-sider rendering or testing.
If you decide to clone this repo, there are several commands included in the
package.json
to help you develop.
npm run lint
, runs eslint in the projectnpm run example
, kicks off one of the example apps in this reponpm run build
, builds the project and creates a distributablenpm run test-dev
, hosts the tests to be launched in a browsernpm run test
, runs tests against all available browsers on the machine
Check out our Issues on Github. PRs welcome!