Point.js

A lightweight, client-side framework for building user interfaces

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 😀

 

Installation

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>

Basic Rendering

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:

{{message}}

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);

Using un-declared variables

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";

Using callbacks (via Promises):


point.attach("welcome", data).then((o) => {
    if (!o.error) {
        // run your *success callback* here
    } else {
        console.log("ERROR: "+ o.error)
    }
});

Advanced Renderings

Conditions

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:

{{if status == true}} We're Online! {{if:else}} We're Offline :( {{/if}}

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>

Loops

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:

  1. {{text}}

Nested data

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:


Modifiers

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 Email Balance
{{name.first}} {{name.last}} {{email|link}} ${{balance|format_number_as_word}}

Built-in modifiers:

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)

Creating custom modifiers:

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 -->

Handling user input

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}}


Using external data

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.


Using remote resources:

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.


Using remote data:


<h3>Online members:</h3>
<ol>
    <li point-component="members">{{name.first}} {{name.last}}</li>
</ol>

point.attachJSON("/remote/api/members", "members");

If you're using both external templates and JSON data, use:


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 replace attach* with render* — for example, use renderJSON(...) instead of attachJSON(...).

Routing

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:


Installation

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>

Using page-level components with local data

The following link will...

  1. Attach the data object to the customers component
  2. Place the rendered content in the body container
  3. Change the address bar URL to youtsite.com/customers (on click)

<a href="/customers"
    point-route-container="body"
    point-route-component="customers"
    point-route-data="customers"> All Customers </a>

Using page-level components with remote data

The following link will...

  1. Fetch data from /customers.json
  2. Attach it to the customers component
  3. Place the rendered content in the body container
  4. Change the address bar URL to youtsite.com/customers (on click)

<a href="/customers"
    point-route-container="body"
    point-route-component="customers"
    point-route-json="/customers.json"> Customers </a>

Using remote template with local data

The following link will...

  1. Fetch a template from /customers.html
  2. Attach the data object to that template
  3. Place the rendered content in the body container
  4. Change the address bar URL to youtsite.com/customers (on click)

<a href="/customers"
    point-route-container="body"
    point-route-html="/customers.html"
    point-route-data="customers"> Customers </a>

Using remote template with remote data

The following link will...

  1. Fetch data from /customers.json
  2. Fetch a template fetched from /customers.html
  3. Attach the downloaded data to the downloaded template
  4. Place the rendered content in the body container
  5. Change the address bar URL to youtsite.com/customers (on click)

<a href="/customers"
    point-route-container="body"
    point-route-html="/customers.html"
    point-route-json="/customers.json"> Customers </a>

Server configurations

Single Page Applications have a limitation, whereas the users will get a 404 error if they access http://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 same index.html page that's our app.

Apache


# 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>

NGINX


location / {
    try_files $uri $uri/ /index.html;
}

Now that that's done - the router will work as intended.