walterhiggins / ickyjs

icky.js - An 'icky' javascript library for rapid prototyping of Simple Apps.

Home Page:https://walterhiggins.github.io/ickyjs/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

icky.js - A small icky javascript library

icky.js is a tiny javascript library best used with ES6. It makes no claims for performance, legibility, maintenance, scalability or any of the other good things claimed by best-of-breed javascript libraries and frameworks. Use at your own peril.

Usage

index.html

<html>
  <body>
     <div id="ickyroot">
     </div>
  </body>
  <script src="icky.js" type="text/javascript"></script>
  <script src="app.js" type="text/javascript"></script>
</html>

app.js

(function(exports) {
  var todos = [
    { done: true, text: "Wake Up" }, 
    { done: true, text: "Fall out of Bed" },
    { done: false, text: "Drag a comb across my head" },
  ];
  // icky.map simplifies constructing a string from an array
  const todoView = () => `
  <ol>
    ${icky.map(todos, todoItem)}
  </ol>`;

  const todoItem = todo => {
    // icky.gnf assigns a unique global name to a function
    const onToggleStatus = icky.gnf(el => {
      // this event handler is bound to the todo object
      todo.done = el.checked;
    });
    return `
    <li>
      <input type="checkbox" 
             onchange="${onToggleStatus}(this)" 
             ${todo.done ? "checked" : ""} /> 
      ${todo.text}
    </li>`;
  };
  // icky.update populates an element with HTML
  exports.onload = () => icky.update("#ickyroot", todoView);
})(window);

Background

ES6 Template literals make writing HTML in Javascript easier. icky.js provides just 3 functions which help in the construction of HTML using template literals.

icky.gnf()

gnf is short for Globally-Name Function. Given a function as a parameter it returns a unique name so the function can be used as an event handler. For example:

let {gnf} = icky;
var shoppingCart = [];
// ...
function removeItemFromCart (item) {
  // ... 
}
// ...
function btnRemoveItem( item ){
  const onClick = gnf( btn => {
    removeItemFromCart( item );
    btn.disabled = true;
  });
  return `<button onclick="${onClick}(this)">Remove</button>`
}

This allows bindings between objects and DOM event handlers. Just like Angular's ng-click but without the pain of having to use Angular ;-p

For the curious: The function is assigned to a distinct new name in the icky.namespaces.functions namespace. In the above example, the generated HTML might look something like this:

<button onclick="icky.namespaces.functions.fn_15(this)">Remove</button>

As you can imagine, this could potentially be a source of memory leaks because the functions (and their closures) are kept around in memory indefinitely while a reference to them exists on the window object. This can be especially problematic if you use gnf() on a collection of objects:

// setup data
var numbers = [];
for (var i = 0;i < 100; i++){
  numbers.push('Number ' + i);
}

// template for a list of numbers
const tNumberList = () => {
  return `
  <ol>
    ${icky.map(numbers, tNumber)}
  </ol>`;
};

// template for a number 
const tNumber = (number) => {
  
  const onclick = gnf(btn => { 
    // the number is bound to the element's event handlers via a closure 
    console.log(number);
  });

  return `
  <li>
    <button onclick="${onclick}(this)">${number}</button>
  </li>`;
}

// update page
icky.update("#root", tNumberList);

If the #root element were updated frequently each call to tNumberList will generate new closures which will be kept around (not garbage collected). You can work around this by specifying a distinct namespace which will be initialized (existing references in the namespace will be wiped out). You can use the gnf function to specify a new namespace and return a function which will populate the namespace. This is best illustrated by example:

// setup data
var numbers = [];
for (var i = 0;i < 100; i++){
  numbers.push('Number ' + i);
}

// template for a list of numbers
const tNumberList = () => {
  // construct an initialise a namespace for this component's children
  let nf = gnf('tNumberList');
  return `
  <ol>
    ${icky.map(numbers, (number) => tNumber(nf, number))}
  </ol>`;
};

// template for a number 
const tNumber = (nf, number) => {
  
  const onclick = nf(btn => { 
    // the number is bound to the element's event handlers via a closure 
    console.log(number);
  });

  return `
  <li>
    <button onclick="${onclick}(this)">${number}</button>
  </li>`;
}

// update page
icky.update("#root", tNumberList);

icky.map()

Given an Array and a function, icky.map() will map() over the Array calling the function for each item in the array and then join() the array using "". Why might this be useful? Consider when using ES6 template literals to construct a list of items for display on a page in an ordered list:

<ol>
  <li>Butter</li>
  <li>Eggs</li>
  <li>Salt</li>
</ol>

Using ES6 template literals to construct such a list you might write code like this:

let shoppingCart = ['Butter','Eggs','Salt'];
//...
const orderedList = (list) => `<ol>${list.map( item => `<li>${item}</li>` )}</ol>
// example code - please don't @ me
document.querySelector('body').innerHTML = orderedList(shoppingCart);

...but the results would look something like this:

<ol>
  <li>Butter</li>,
  <li>Eggs</li>,
  <li>Salt</li>
</ol>

Each item has a , character following it because when an array is converted to a string it is join()ed using the default join character (,). The icky.map() function ensures no extraneous , characters will appear in the HTML. So when you rewrite the above function as:

const orderedList = (list) => `<ol>${icky.map(list, item => `<li>${item}</li>` )}</ol>
// example code - please don't @ me
document.querySelector('body').innerHTML = orderedList(shoppingCart);

...the output will be:

<ol>
  <li>Butter</li>
  <li>Eggs</li>
  <li>Salt</li>
</ol>

icky.update()

This is probably the ickiest part of icky.js . Given a query selector and a function reference, it will:

  1. Try to find a matching element and if it finds one
  2. Invoke the provided function which should return a string - a snippet of HTML
  3. Set the element's contents to the HTML returned by the function.

It's not big and it's not clever.

About the name

The name Icky came to mind as I watched this video on WebComponents and Polymer wherein the presenter talks about setting innerHTML as feeling icky.

On Frameworks

I'm not a fan of JS Frameworks and think that you can actually get a lot done using plain new ES6. Application code should look like application code. Breaking up your application code to fit the moulds enforced by the Framework-du-jour is not a good long-term bet.

icky.js is not a framework, at <100 lines of code it can barely be called a library. Its small set of functions are meant to shine a light on what's possible with ES6 Template Literals. If you want to see some examples, start by browsing through the examples directory.

About

icky.js - An 'icky' javascript library for rapid prototyping of Simple Apps.

https://walterhiggins.github.io/ickyjs/


Languages

Language:JavaScript 89.2%Language:HTML 6.0%Language:CSS 4.8%