xypts
v1.0.0-preA3
Published
Xyp - Minimalistic TypeScript framework
Downloads
2
Readme
Xyp - minimalistic TypeScript framework
Table of Contents
- Xyp - minimalistic TypeScript framework
Quick start
Install this package:
npm i -g xypts
Create new project:
xyp new quick-start
Start development server:
xyp serve
Edit src/components/app-root and open browser in http://localhost:9000
CLI
For CLI usage you can use command xyp
Reference
Components
You can create components with custom template and css.
xyp component my-awesome-component
Shorthand: xyp c my-awesome-component
New folder called my-awesome-component will be created. Now you can use it in app-root.html:
<my-awesome-component></my-awesome-component>
Attributes [directives]
(bind) - Two way binding
Example:
<h1 (bind)="name"></h1>
<input (bind)="name">
In this example h1 inner text depends on variable "name", which is binded to the input.
(listen) - One way binding
<h1 (listen)="name"></h1>
<input (bind)="name">
In this example header listens for changes in "name" variable. Works same as previous example.
(listen:<attribute>
), (bind:<attribute>
) - Data binding
<img (listen:src)="myphoto">
<input (listen:placeholder)="placeholder">
These attributes bind variable to defined attribute of calling element.
(model) - One way binding
<h1 (listen)="name"></h1>
<input (model)="name">
This example's input listens for changes and only modifies "name" variable.
(init) - Initialize value
<h1 (listen)="name"></h1>
<input (init)="name = 'hi there!'" (bind)="name">
In this example "name" varibale is initialized with value 'hi there!'.
Note: if variable was already initialized(e.g. is not undefined
), it won't be initialized
(for) - Iterate over array or object
Example:
<div (init)="favourites = ['snickers', 'milky way']" (for)="candy of favourites"><div>I love ${candy}!</div></div>
"favourites" variable is initalized with ['snickers', 'milky way'] array, for directive iterates over array's values.
Use "in" instead of "of" to iterate over object's keys, not values.
To access iterated value, use variable name in "${}" in html, e.g. ${candy}.
Notice that scope of "${}" value is isolated. That means you can access only this variable and json() function and i
, e.g. iterated index.
json - Convert object to JSON
Usage:
<div (init)="favourites = [{'snickers': true}, {'milky way': false}]" (for)="candy of favourites"><div>${json(candy)}</div></div>
Output:
{"snickers":true}
{"milky way":false}
(hide) - Show element conditionnally
Example 1:
<p (hide)="2 + 2 === 5">Wow, 2 + 2 is 5</p>
<p (hide)="2 + 2 === 4">As always, 2 + 2 is 4</p>
<p (init)="truth = true" (hide)="truth">Everything is truth</p>
<p (init)="lie = false" (hide)="!lie">Lie</p>
(hide) sets element's hidden attribute if expression is true. Example 2:
<p (hide)="2 + 2 === 4">2 + 2 is 4, so that element will be hidden</p>
You can use !
to hide element if value is false. Also you can bind directive to variable:
<p (init)="truth = true" (hide)="truth">Everything is truth</p>
<p (init)="lie = true" (hide)="!lie">Lie</p>
Advanced
Eval {{expression}} syntax
<p>This expression is precomputed: {{2 + 2}}</p>
<p (init)="x = 4 + 4">This expression is binded to "x" variable: {{x}}</p>
Xyp defines and uses
$data
$routes
$subscribes
$directives
$routeParameters
global variables. Make sure that your code is not overwriting them. Example:
<h1 (init)="title = 'Add more A: '">{{title}}</h1>
<button onclick="$data['title'] += 'A'">Add A here</button>
In this example $data['title'] is initialized with value 'Add more A: ' and then button click adds 'A' to the var.
Reactiveness
Xyp reactiveness mechanism is quite simple: item is initialized in $data variable with setter invoking all functions in corresponding $subscribes object. To subscribe on change of some variable in $data, push callback in $subscribes or use subscribe function. Example:
<!-- app-root.html -->
<h1 (init)="title = 'hello!'" (listen)="title"></h1>
component.ts:
/// <reference path="../../xypts/init.ts"/>
/// <reference path="../../xypts/component.ts"/>
@Component('app-root')
class AppRootComponent extends XypComponent {
constructor() {
super('app-root');
setTimeout(() => {
$data['title'] = 'hi there!';
}, 500);
subscribe('title', () => console.log('title has changed!'));
}
}
Routing
Xyp support routing. That means that Xyp can be used to create SPA(single-page application)s. Add this component where routing is needed:
<xyp-router></xyp-router>
Create some page components and prepend @Route decorator in component.ts. Example:
app-root.html
<a href="#/test">Test</a>
<xyp-router></xyp-router>
In new component called 'test-page':
component.ts:
/// <reference path="../../xypts/component.ts"/>
/// <reference path="../../xypts/routes.ts"/>
@Route({
selector: 'test-page',
route: '/test',
})
@Component('test-page')
class TestPageComponent extends XypComponent {
constructor() {
super('test-page');
}
}
If URL ends with #/test, there should be test-page works!
text.
Also you can pass parameters to the route like:
http://localhost:9000/#/test?foo=bar
Now, parameter will be available in $routeParameters['foo'], e.g $routeParameters['foo'] is 'bar'.
Functions
$http - send asynchronous HTTP request
interface HTTPRequest {
headers?: { [key: string]: string };
url: string;
method: string;
body?: any;
urlEncodeBody?: boolean;
parseJSON?: boolean;
}
function $http<T>(requestData: HTTPRequest): Promise<T>
$http sends HTTP request and returns promise with response body. If urlEncodeBody is true, request body is encoded as URL. If parseJSON is set, response body is parsed as JSON and returned in Promise as Object, otherwise string casted to T is returned.
listen - subscribe for object's property's changes
function listen(object: any, objname: string, callback: Function)
listen tries to inject setter and getter of object
's objname
proprety with defined callback. This function must NOT be called with $data object. Use bind function instead.
bind - inject reactive setter and getter of $data[objname]
function bind(objname: string, callback?: ReactiveListener)
Bind calls listen function and adds callback invoked on $data[objname]
's change, if present.
ReactiveListener
ReactiveListener has two fields: isDeletable
(checks whether this subscribe will update DOM), and callback
(subscribe). Use DefaultReactiveListener function to create ReactiveListener.
function DefaultReactiveListener(el: HTMLElement, callback: Function)
Comparation to other frameworks
Counter app
Xyp
<h1 (init)="count = 0">{{count}}</h1>
<button onclick="$data['count']--">-</button>
<button onclick="$data['count']++">+</button>
React
import React from "react";
import ReactDOM from "react-dom";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0};
}
down(value) {
this.setState(state => ({ count: state.count - value }));
}
up(value) {
this.setState(state => ({ count: state.count + value }));
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick = {() => this.down(1)}>-</button>
<button onClick = {() => this.up(1)}>+</button>
</div>
);
}
}
ReactDOM.render(<Counter />, document.querySelector("#app"));
Vue
import Vue from "vue";
new Vue({
data: { count: 0 },
methods: {
down: function(value) {
this.count -= value;
},
up: function(value) {
this.count += value;
}
},
render: function(h) {
return(
<div>
<h1>{this.count}</h1>
<button onClick={() => this.down(1)}>-</button>
<button onClick={() => this.up(1)}>+</button>
</div>
);
},
el: "#app"
});
Hyperapp
import { h, app } from "hyperapp";
const state = {
count: 0
};
const actions = {
down: value => state => ({ count: state.count - value}),
up: value => state => ({ count: state.count + value})
};
const view = (state, actions) => (
<div>
<h1>{state.count}</h1>
<button onclick={() => actions.down(1)}>-</button>
<button onclick={() => actions.up(1)}>+</button>
</div>
);
app(state, actions, view, document.querySelector("#app"));
Svelte
<div>
<h1>{count}</h1>
<button on:click="set({count: count - 1})">-</button>
<button on:click="set({count: count + 1})">+</button>
</div>
Asynchronous app
Xyp
app-root.html
<button onclick="getPosts()">Get posts</button>
<div (for)="post of posts">
<div id="${post.id}">
<h2><font color="#3AC1EF">${post.title}</font></h2>
<p>${post.body}</p>
</div>
</div>
component.ts
/// <reference path="../../xypts/init.ts"/>
/// <reference path="../../xypts/component.ts"/>
@Component('app-root')
class AppRootComponent extends XypComponent {
constructor() {
super('app-root');
$data['posts'] = [];
}
}
interface Post {
id: number;
userId: number;
title: string;
body: string;
}
function getPosts() {
$http<Post[]>({ url: 'https://jsonplaceholder.typicode.com/posts', method: 'GET', parseJSON: true }).then(data => {
$data['posts'] = data;
});
}
React
import React from "react";
import ReactDOM from "react-dom";
class PostViewer extends React.Component {
constructor(props) {
super(props);
this.state = { posts: [] };
}
getData() {
fetch(`https://jsonplaceholder.typicode.com/posts`)
.then(response => response.json())
.then(json => {
this.setState(state => ({ posts: json}));
});
}
render() {
return (
<div>
<button onClick={() => this.getData()}>Get posts</button>
{this.state.posts.map(post => (
<div key={post.id}>
<h2><font color="#3AC1EF">{post.title}</font></h2>
<p>{post.body}</p>
</div>
))}
</div>
);
}
}
ReactDOM.render(<PostViewer />, document.querySelector("#app"));
Vue
import Vue from "vue";
new Vue({
data: { posts: [] },
methods: {
getData: function(value) {
fetch(`https://jsonplaceholder.typicode.com/posts`)
.then(response => response.json())
.then(json => {
this.posts = json;
});
}
},
render: function(h) {
return (
<div>
<button onClick={() => this.getData()}>Get posts</button>
{this.posts.map(post => (
<div key={post.id}>
<h2><font color="#3AC1EF">{post.title}</font></h2>
<p>{post.body}</p>
</div>
))}
</div>
);
},
el: "#app"
});
Hyperapp
import { h, app } from "hyperapp";
const state = {
posts: []
};
const actions = {
getData: () => (state, actions) => {
fetch(`https://jsonplaceholder.typicode.com/posts`)
.then(response => response.json())
.then(json => {
actions.getDataComplete(json);
});
},
getDataComplete: data => state => ({ posts: data })
};
const view = (state, actions) => (
<div>
<button onclick={() => actions.getData()}>Get posts</button>
{state.posts.map(post => (
<div key={post.id}>
<h2><font color="#3AC1EF">{post.title}</font></h2>
<p>{post.body}</p>
</div>
))}
</div>
);
app(state, actions, view, document.querySelector("#app"));
Svelte
<div>
<button on:click="getData()">Get posts</button>
{#each posts as {title, body}}
<div>
<h2><font color="#3AC1EF">{title}</font></h2>
<p>{body}</p>
</div>
{/each}
</div>
<script>
export default {
methods: {
getData() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(posts => this.set({ posts }));
}
}
};
</script>