Welcome to PorterJS’s documentation!

Contents:

Introduction

Why PorterJS?

That’s a good question. There are plenty of other good packages out there. Why another?

To be light. To be fast. To be powerful. To be easy to use.

For example, PorterJS does not ship with a templating engine. Out of the box, it does not do client side rendering. HOWEVER, it can be used with your favorite templating engine. Handlebars and mustache.js both plug in nicely. My personal favorite is Markup.js.

Many JS frameworks out there require you to dive in and become an expert in their way of doing things. The goal for PorterJS is to alleviate that. It is meant to be developed to look and feel like plain old JavaScript and HTML, and to operate intuitively.

How can I help?

The project is still in active initial development with not all of its core features completed. However, enough is together to make it usable and operational. For any bugs, please let me know on the GitHub page.

If you are interested in helping to debug, write documentation, test, develop, or just sit down and have a been together, let me know.

Who are you?

Known as The Brewmaster, I am a coder, a lawyer, a father, an entrepreneur, and a lot of other things. Yes, I do like to brew beer, and yes, I do like drinking porters.

Public Components

Wrappers

PorterJS exposes several convenience wrappers:

  • p.one()
  • p.all()

These methods are essentially just wrappers for document.querySelector and document.querySelectorAll. However, they can be chained together in any combination, unlike document.querySelector and document.querySelectorAll.

p.one('#some_id').all('p')      >> OKAY
p.one('#some_id').one('p')      >> OKAY
p.all('.some_class').one('p')   >> OKAY
p.all('.some_class').all('p')   >> OKAY

You can call addEventListener() and removeEventListener on an entire group of nodes, unlike in stock JavaScript. So, for example, the following would work as expected.

p.all('.some_class').addEventListener('click', callSomeFunction)

Also, another difference from document.querySelectorAll and all is that you can run a forEach loop or a for-of loop.

var q = p.all('.some_class');

q.forEach( function (item) {
    ...
})

for (var item of q) {
    ...
}

Methods

In addition, it also exposes some additional methods that are used under the hood, but can be helpful in creating UI components:

  • p.trigger_call()
  • p.ready()
  • p.debounce()
  • p.load()

Objects

There are several classes that are used under the hood. These too (like the methods above) may be useful.

p.events

This is an instantiated instance of the Dispatcher class. Essentially, it is the holding place for all custom events. It contains several methods that can be used to add, remove, and trigger custom events.

  • p.events.add(<NAME OF EVENT>,<CALLBACK>):
    Used to record <NAME OF EVENT> as a potential event.
  • p.events.remove(<NAME OF EVENT>,<CALLBACK>):
    Used to remove <NAME OF EVENT> as a potential event.
  • p.events.trigger(<NAME OF EVENT>,<ARGS>):
    Used to trigger <NAME OF EVENT>.
var someFunction = function(arg1, arg2, arg3) {
    ...
}

p.events.add('myCustomEvent', someFunction)
p.events.trigger('myCustomEvent', arg1, arg2, arg3)
p.events.remove('myCustomEvent', someFunction)

p.stack

This is an instantiated instance of the DataStack class. It is used to hold state for the application. Data is kept inside the p.stack.storage variable. HOWEVER, it is strongly encouraged to NOT directly access the p.stack.storage object. Doing so will cause a lot of unexpected results.

Instead, you shoule interact with the stack by using the getter and setter methods.

  • p.stack.set(<NAME OF KEY>, <VALUE>, <OPTIONAL CALLBACK>):
    Used to store a key/value pair to state. If the optional callback is passed, it will be called after the key/value has been stored.
  • p.stack.get(<NAME OF KEY>, <OPTIONAL DEFAULT VALUE>):
    Used to retrive a key/value pair from state. If the second, optional parameter is passed, it will return this as a default value if the key is not in the state.
  • p.stack.push(<NAME OF KEY>, <VALUE>, <OPTIONAL CALLBACK>):
    Used to push a value to a an array in the state. If the optional callback is passed, it will be called after the value has been stored.
  • p.stack.update(<NAME OF KEY>, <NAME OF PROPERTY>, <VALUE>, <OPTIONAL CALLBACK>):
    Used to change a single property of an object already stored in the stack. If the optional callback is passed, it will be called after the value has been stored.

Regardless of the key, there will be an event emmitted when storing a key/value to the state. Its name will be key + 'StackChange'. Therefore, you can capture this by adding a custom event.

p.events.add('someKeyStackChange', function() {
    console.log('This event was triggered by pushing an item to the stack')
    console.log('The value of someKey is: ' + p.stack.get('someKey'))
})

p.stack.set('someKey', 'abcdefg')

This also works with the push() method.

p.events.add('someKeyStackChange', function() {
    var my_list = p.stack.get('someKey')
    console.log('This event was triggered by pushing an item to the stack')
    console.log('There are ' + my_list.length + ' items in somKey')
})

p.stack.push('someKey', 'abcdefg')

You can get all of the keys that have been set with the .keys() method.

for (var key of p.stack.keys()) {
    console.log(key)
}

Sometimes you do not want to override an entire object that is stored in the stack. What if you only want to update a single property? No problem, call .update().

var nested = {
    inner: {
        property: {
            found: {
                here: 'Hello, world.'
            }
        }
    }
}

p.stack.set('nested', nested)
p.stack.update('nested', 'inner.property.found.here', 123)

p.Request

This is an object used to make AJAX calls, and to return a response. To begin, it should be instantiated with a URL as its parameter.

var request = p.Request("http://example.com")

To actually make a call, you call either post() or get() on it. Both methods take data as its first argument.

The data variable can either be a url encoded string (foo=bar) or an object ({'foo': 'bar'}).

In addition, the post() method also takes an additional second parameter: csrftoken. This is a string that gets passed through to a X-CSRFToken header. Right now, I know this is limited functionality that is screaming of some need for further abstraction. But, PorterJS was developed first and foremost to run with a Django backend, hence the csrftoken in this form. Future releases will abstract away this logic, and also add better logic for adding headers.

To see this in action:

request.get().then(function (response) {
    console.log(response.responseText)
}).catch(function (error) {
    console.log(error)
})

Elements and Attributes

PorterJS tries to do all the dirty work for you in trying to figure out what you want to happen. One way it operates is to use data-* attributes on elements. Many other frameworks (we won’t mention which ones) spurn the HTML specifications and run custom attribute names. But not PorterJS. It is designed to work with HTML5 compliant source code. Here are all of the recognized attributes.

Attributes

data-bind

  • Purpose: To enable two-way binding between an <input> and the DataStack.
  • Value: A key in the DataStack, accessible as: p.stack.get('someKey').
  • Example:
<input data-bind="someKey">

data-class

See also .toggler below.

  • Purpose: To define the class that should be toggled.
  • Value: The class name to be toggled.
  • Example:
<a href="#" data-class="classToBeToggled" class="toggler" data-target="someId">toggle link</a>

data-hash

  • Purpose: To change the location hash on click.
  • Value: The hash to be assigned to the location.
  • Example:
<a data-hash="#go/to/here">Visit here</a>

data-method

  • Purpose: To change the HTTP request method for any element that triggers a call.
  • Default: GET, except on a form element that defaults to POST
  • Value: Can be: GET, POST, PATCH, PUT, DELETE
  • Example:
<a href="/some/path" data-method="PATCH">link to call a patch</a>

data-model

  • Purpose: To update the value or text if an element upon the change of a DataStack key.
  • Default:
  • Value:
  • Example:
<span data-model="someKey"></span>

or

<input data-model="someKey">

data-target (when used on a .toggler element)

See also .toggler below.

  • Purpose: To define the element id of the intended target.
  • Value: The id of the target.
  • Example:
<a href="#" data-target="someId" class="toggler" data-class="classToBeToggled">toggle link</a>

data-target (when used on an <input> element)

  • Purpose: To define the <form> element to be submitted on submit.
  • Value: The id of the target form.
  • Example:
<form id="someFormId">
    <input data-target="someFormId">
</form>

data-url

  • Purpose:
  • Default:
  • Value:
  • Example:
...

data-<EVENT NAME>

This can be used with any of the following events: click, keyup, keydown, focus, blur, change

  • Purpose: To trigger an event on the occurrence of some event.
  • Value: The name of the function to call.
  • Example:
<input data-keyup="someValidator">

<script>
    var someValidator = function (event) {
        ...
    }
</script>

Elements

<a></a> or [data-url]

By default, all <a></a> tags will be captured to send HTTP requests asynchronously. However, you can opt out of this behavior with one of the following:

  • Setting the element’s class as .exclude or .ignore-self
  • [target]

In addition, you can create a “fake” link by setting [data-url] on any element. This will attach a click even to it. This could be usefule, in the following example to make clickable table rows:

<table>
    <tr data-url="/go/to/element/1">
        ...
    </tr>
    <tr data-url="/go/to/element/2">
        ...
    </tr>
    <tr data-url="/go/to/element/3">
        ...
    </tr>
</table>

#content

...

.toggler

...

You can overide the default action by adding one of the following classes: ``ignore-self``, ``exclude``, or ``ignore-toggle``.

Requests

Web architecture is mainly centered around the request/response model. The browser sends a request to a server, that server returns a response to the browser. PorterJS is centered around this dynamic (and therefore is not currently built to handle websockets–COMING SOON!).

PorterJS expects a response from the browser to be in JSON format, and can accomodate any key/value structure it is given. However, to leverage its core functionality, there is a set of reserved key/value pairs that will trigger certain browser side actions.

callbacks

  • Purpose: To trigger execution of a callback function
  • Value(s): Should be an``{Object}`` in the following form
{
    'function': 'name_of_some_function',
    'arguments': [
        'list',
        'of',
        'some','
        'arguments'
    ]
}

errors

COMING SOON

html

  • Purpose: To push HTML code to elements in the DOM
  • Value(s): Can be a "string", {Object}, or [Array]
  1. If "string", then the contents of that string will be inserted into an element with an id="content".
  2. If {Object}, then each element with an id that equals key will have the corresponding value inserted.
  3. If [Array], then each item should itself be an {Object} with two keys: id and content.
// Scenario 1
{
    "html": "Hello, world."
}

// Scenario 2
{
    "html": {
        "foo": "bar"
    }
}

// Scenario 3
{
    "html": [
        {
            "id": "spam",
            "content": "eggs"
        }
    ]
}

notifications

COMING SOON

redirect

  • Purpose: To trigger a reload or change in the window.location
  • Value(s): A string being the intended location.

title

COMING SOON

Loaders

Loaders are the tools that turn HTML events into events and building blocks. The process is initiated by the load() method, which is also `publically available`_ <public.rst> as p.load(). Whenever HTML is being rendered, it should be followed by this method to capture any newly inserted DOM elements. This should be

Types

Events

There are two events that are emitted during the load(). You can hook into it with your own functionality by adding an event for preLoaders and postLoaders.

p.events.add('preLoaders', function () {
    console.log('Do something great./');
});

.. _router: http://
.. _elements: http://
.. _server side rendering: http://and `elements`_
.. _elements: elements.rst
.. _CONFIG: http://

Miscellaneous

Objects

Dotted property getter and setter

Given any object, you can get or set a property in by a dotted string notation.

var nested = {
    inner: {
        property: {
            found: {
                here: 'Hello, world.'
            }
        }
    }
}

console.log(nested.getProperty('inner.property.found.here'))

nested.setProperty('inner.property.found.here', 'Something else')

Indices and tables