Point is a lightweight, client-side framework for building UI for the web. Its focus is on the view layer, and unlike many other frameworks, it uses plain HTML so it doesn't require you to learn a new syntax — which makes it extremely easy to pick up and use in your projects.
Point is a no-bloatware framework (only 4.7KB minified and gzipped), and it doesn't have a bunch of "features" you'll end up not using. It handles HTML rendering, data binding, and routing for Single-Page Applications.
Point is extensible, making it easy to create your own data modifiers to suits your specific needs. And, if you want to use other libraries, you can, as Point plays nice with other.
The best thing about Point? This page is all the documentation you'll need 😀
Start with creating an index.html
file and include Point.js with:
<!-- include Point.js -->
<script src="//cdn.jsdelivr.net/gh/ranaroussi/pointjs/dist/point.js"></script>
To render data to the DOM,
add an HTML component and tag it as a point-component
:
<div point-component="welcome">
{{message}}
</div>
Next, bind some data to the {{message}}
placeholder:
var data = {
message: "Hello from <strong>Point.js</strong>."
};
point.attach("welcome", data);
Point will render your code as:
We have just created our first app!
Note that data
is now "attached" to that DOM element,
so any changes made to the data
object
will be reflected in that DOM element.
To test this, open your browser's JavaScript console
and set data.message
to a different value.
You should see the rendered example above update.
To only render the data *without* linking it to the DOM element (rendered once), use:
point.render("welcome", data);
You don't always want (or need) to declare the data variable before passing it into Point. For example, the following code will produce the same result:
point.attach("welcome", {
message: "Hello from <strong>Point.js</strong>."
});
In this case, the data will be available via
point.$['welcome']
, and subject to modifying using:
point.$['welcome'].message = "New message";
point.attach("welcome", data).then((o) => {
if (!o.error) {
// run your *success callback* here
} else {
console.log("ERROR: "+ o.error)
}
});
It's easy to use conditional statements using Point. Let's say you wan't to let your visitors that you're available to chat. In that case, we can use something like this:
<div point-component="status">
{{if status == true}} We're Online! {{if:else}} We're Offline :( {{/if}}
</div>
point.attach("status", { status: true });
Point will render your code as:
And, as you might have guessed, the status can can updated by invoking:
point.$['status'].status = false;
It's worth mentioning that the
{{if:else}}
part is optional. Say you only want to display a "We're Online" message and noting otherwise. In that case, use:<div point-component="status"> {{if status == true}} We're Online! {{/if}} </div>
Looping over data is as simple as feeding Point an Array
.
<ol>
<li point-component="tasks">{{text}}</li>
</ol>
var tasks = [
{ text: 'Create an html file' },
{ text: 'Add Point.js' },
{ text: 'Rock on!' }
];
point.attach("tasks", tasks);
This will render as:
Sometimes, your data may have a nested structure. That's easy to handle with Point.
For example, the following code:
<ul>
<li point-component="contacts">{{name.first}} {{name.last}} - {{email}}</li>
</ul>
var contacts = [{
name: {
first: 'Tommy',
last: 'Shelton'
},
email: 'tommyshelton@gmail.com'
}, {
name: {
first: 'Mercedes',
last: 'Massey'
},
email: 'mercedesmassey@gmail.com'
}, {
name: {
first: 'Regina',
last: 'Curtis'
},
email: 'reginacurtis@gmail.com'
}];
point.attach("contacts", contacts);
...will render as:
So far, we saw how Point lets you render plain data. But it is likely you'd want to format this data upon rendering. That's where modifiers come in.
Modifiers are basically formatters for your raw data. For example,
{{blog_url|link}}
will converts blog_url
data into a link
automatically.
Herer's a quick example (modifiers are highlighted):
<table width="100%">
<thead>
<tr>
<th>VIP</th>
<th>Name</th>
<th>Email</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
<tr point-component="customers">
<td><input type="checkbox" {{vip|checked}}></td>
<td>{{name.first}} {{name.last}}</td>
<td>{{email|link}}</td>
<td>${{balance|format_number_as_word}}</td>
</tr>
</tbody>
</table>
var customers = [{
name: {
first: 'Tommy',
last: 'Shelton'
},
email: 'tommyshelton@gmail.com',
balance: 16253.12,
vip: 1
}, {
name: {
first: 'Mercedes',
last: 'Massey'
},
email: 'mercedesmassey@gmail.com',
balance: 180101192.03,
vip: 0
}, {
name: {
first: 'Regina',
last: 'Curtis'
},
email: 'reginacurtis@gmail.com',
balance: 8112.19,
vip: 0
}];
point.attach("customers", customers);
The above code will render as:
VIP | Name | Balance | |
---|---|---|---|
{{name.first}} {{name.last}} | {{email|link}} | ${{balance|format_number_as_word}} |
Modifier | Description |
---|---|
link | converts the data to a link |
capitalize | capitalize the data (title case) |
uppercase | converts the data to upper case |
lowercase | converts the data to lower case |
nl2br | converts line breaks into <br> |
nozero | render an empty string if the value is 0 |
clean_url | print out the url without http(s):// or www. |
unix2date | converts unix timestamps into Date |
format_number | format numbers (results in 1,000.00) |
format_decimal | rounds floats to maximum of 2 decimal points |
format_number_as_word | convers numbers to "words" (ie 5K, 1M, etc) |
format_int | add thousands separator to integers (results in 1,000) |
format_percent | format percentage (= format_number + '%') |
checked | adds checked="checked" if value is true or 1 (for radio/checkboxes) |
negative_checked | adds checked="checked" if value is false or 0 (for radio/checkboxes) |
selected | adds selected="selected" if value is true or 1 (for select boxes) |
negative_selected | adds selected="selected" if value is false or 0 (for select boxes) |
no_null | convert null values into "- " |
hidden_if_empty | converts empty values into hidden (usefull for css classes) |
You can create your own modifiers using the point.modifier()
method, like so:
point.modifier('NAME_OF_MODIFIER', (val) => {
return 'MODIFIED_VALUE';
});
For example, if you want to change the a "user status" flag from [-1, 0, 1]
to
[deleted, pending, active]
, you would write something like this:
point.modifier('status2word', (val) => {
return ['Deleted', 'Pending', 'Active'][parseInt(val) + 1];
});
...which you can later use in your template, like so:
<p>Your status: {{user_status|status2word}}</p>
<!-- will render as <p>Your status: Active</p> whenever "user_status" == 1 -->
Point also provides the
point-bind
attribute to make it easy to create
two-way binding between a form input and a javascript object:
<div point-component="greeting">
<p>{{message}}</p>
<p><input point-bind="message"></p>
</div>
let greeting = {
message: "Hello World"
};
point.attach("greeting", greeting);
Will result in:
{{message}}
So what if your data or HTML templates are located on a different URL than your page? Point can help you with that by offering a few methods to pull UI, data or both remotely.
First, make sure you have a container to store your remote UI:
<div id="container"></div>
Then, pull that UI and place it in the container element, ready to be rendered by Point:
// fetch & attach "people" to "person" template
point.attachHTML("/remote/templates/page.html", "container", data);
* template.html
should have relevant Point placeholders so data
can be rendered
there.
<h3>Online members:</h3>
<ol>
<li point-component="members">{{name.first}} {{name.last}}</li>
</ol>
point.attachJSON("/remote/api/members", "members");
point.attachRemote(
"/remote/templates/page.html",
"/remote/api/members",
"container");
To only render the data *without* linking it to the DOM element (have it rendered once), simply replaceattach*
withrender*
— for example, userenderJSON(...)
instead ofattachJSON(...)
.
Point comes bundled up with a router library for navigating Single Page Applications.
If you only need simple routing for your Single Page Applications, and do not wish to involve a full-featured router library, Point comes bundled up with a simple router library that allows you to:
To get started, include Point's router in your HTML and add the navigation container.
<!-- include Point.js router -->
<script src="//cdn.jsdelivr.net/gh/ranaroussi/pointjs/dist/router.js"></script>
<nav>
...
</nav>
<!-- navigation container -->
<div point-container="body">
Default site content
</div>
The following link will...
data
object to the customers
componentbody
containeryoutsite.com/customers
(on click)
<a href="/customers"
point-route-container="body"
point-route-component="customers"
point-route-data="customers"> All Customers </a>
The following link will...
/customers.json
customers
componentbody
containeryoutsite.com/customers
(on click)
<a href="/customers"
point-route-container="body"
point-route-component="customers"
point-route-json="/customers.json"> Customers </a>
The following link will...
/customers.html
data
object to that templatebody
containeryoutsite.com/customers
(on click)
<a href="/customers"
point-route-container="body"
point-route-html="/customers.html"
point-route-data="customers"> Customers </a>
The following link will...
/customers.json
/customers.html
body
containeryoutsite.com/customers
(on click)
<a href="/customers"
point-route-container="body"
point-route-html="/customers.html"
point-route-json="/customers.json"> Customers </a>
Single Page Applications have a limitation, whereas the users will get a
404
error if they accesshttp://oursite.com/user/id
. To fix the issue, we need to do is add a catch-all fallback route to our web server configuration. If the URL doesn't match any static assets, it should serve the sameindex.html
page that's our app.
# using mod_rewrite
<ifModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</ifModule>
# OR using FallbackResource
<Directory "/path/to/website/directory">
FallbackResource /index.html
</Directory>
location / {
try_files $uri $uri/ /index.html;
}
Now that that's done - the router will work as intended.