Monday, August 5, 2013

building custom tags in html5 and javascript, part 2

Several months ago, I toyed around with the idea of building custom, JavaScript powered tags. My motivation for this was simple: It makes building the client-side portion of web application easy. But, it only handles one side of the equation elegantly: binding existing markup to JavaScript objects.

Ideally, I'd like to work the other way around too. I'd like to be able to create a new object and have the corresponding markup generated for me. And I'd like to drop the object right into the DOM without performing any object-to-node conversion.

We can start with a markup template. And we can plan on consuming the data-id attribute to identify nodes within the object [to avoid conflict with any existing id's in the DOM]. So, let's consider a simple "Blurb" ... like so:

<div class='blurb'>
  <h3 data-id="blurbTitle" style='text-decoration: italic;'>Title will go here</h3>
  <p data-id="blurbBody" style='color: silver;'></p>
</div>


And we want a JavaScript "class" definition that shows and hides the body when on click:

var Blurb = function(title, body) {

  this.init = function(title, body) {
    this.setTitle(title);
    this.setBody(body);
  }

  this.setTitle = function(t) {
    this.blurbTitle.innerHTML = t;
  }

  this.setBody = function(b) {
    this.blurbBody.innerHTML = b;
  }

  this.onclick = function() {
    if (this.blurbBody.style.display == 'none') {
      this.blurbBody.style.display = 'block';
    } else {
      this.blurbBody.style.display = 'none';
    }
  }


  this.init(title, body);

}


Using that template and class, we want to be able to throw a Blurb on the page by doing this:

var b = New(Blurb);
b.blurbTitle.innerHTML = "Some Title";
b.body.innerHTML = "Some body text";
document.body.appendChild(b);


Or this:

var b = New(Blurb);
b.setTitle("Some Title");
b.setBody("Some body text");
document.body.appendChild(b);


Or this:

var b = New(Blurb, "Some Title", "Some body text.");
document.body.appendChild(b);


All we're missing is a simple, easy-to-understand link between the template and the class definition. We need a call to something to make the Blurb constructor operate on a node based on the markup from the template, with the title and body nodes imported into the object as properties. (The outer div will be this.)

Sounds easy enough. But, it's a two-step process. Firstly, you'll notice I used a New() function instead of the new keyword, and that's for good reason. The new keyword will create a new this, which won't be our DOM node. And it's pretty tricky, if not impossible, to "consecrate" an existing object as a DOM node. So, by making a simple concession to use a New() function instead we make our job a lot easier.

So, let's write the fundamentals. Let's write a method that applies our Blurb constructor to a DOM node based on our template. (For now, let's assume we're attaching our template markup as text to class definitions themselves. I.e., Blurb.templateMarkup = "<div ... ")

function New(DomClass) {
  // create a container for the template markup
  var rv = document.createElement('div');

  // drop the markup in and grab the first child, which is what we're really after
  rv.innerHTML = DomClass.templateMarkup;
  rv = rv.firstChild;

  // and "bless" the return value with the class constructor, supplying any ~additional~
  // arguments that were supplied to this New() call
  DomClass.apply(rv, Array.prototype.slice.call(arguments, 1));

  return rv;
}


Simple enough. That gets us a DOM node that's "blessed" as an instance of the supplied object. The missing link: import data-id's from the template as object properties. And for now, we'll assume this must be done "pre-blessing" in simple, recursive manner. So, let's create a little helper function to do that work, which we can define and call in the context of New().

function New(DomClass) {
  // create and bless the object as before
  var rv = document.createElement('div');
  rv.innerHTML = DomClass.templateMarkup;
  rv = rv.firstChild;

  // before blessing it, import the properties
  importProperties(rv, rv.childNodes);

  DomClass.apply(rv, Array.prototype.slice.call(arguments, 1));
  return rv;


  // hoisted functions:

  // given a list of nodes, recursively attaches any with data-id's as properties of obj
  function importProperties(obj, nodes) {
    for (var i = 0; i < nodes.length; i++) {
      if (!nodes[i].getAttribute) continue;
      var id = nodes[i].getAttribute('data-id');
      if (id) {
        obj[id] = nodes[i];
        importProperties(obj, nodes[i].childNodes);
      }
    }
  }

}


And we're done. Our desired calls ...

var b = New(Blurb, "Some Title", "Some body text.");
document.body.appendChild(b);


... (etc.) will work as expected now. Give it a try.

Next time, we'll use what we've done here to improve our work from part 1 and bridge any gaps between the two. Hopefully, we'll end up with a robust system for more seamless, elegant, and straightforward interactions between our script and the DOM.

Tuesday, June 25, 2013

reminder: console.log() sometimes fails in IE

After hours of debugging a user's issue that I could only occasionally reproduce, I re-discovered that IE only provides a console object once the developer tools have been opened. So, a single debug line that works perfectly well in every other browser on the planet fails without a sound:

console.log("updating checkbox ", checkbox, v)

And it's almost by dumb luck alone that you'd know this line was the issue. Because how would you know? If you've opened the developer tools open, it won't fail. And if they haven't been opened, it fails, but has no console to log the error to ... Monstrously awesome, right?

So, let this be a reminder that IE lame, and it doesn't provide a console object to any of your site's users. So, don't blindly log to the console. Each time you want to log, make sure a console object with a log method exist:

if (window.console && typeof console.log == 'function') console.log("whatever");

Or, create a do-nothing console object once at the top of your page if none exists:

if (!window.console || !console.log) console = {log: function() {}};

/* lots of code ... */

console.log("this won't break your script now ... yay!");

/* lots of other code ... */

Tuesday, June 4, 2013

a JavaScript Color class

I was toying around with colors in JavaScript awhile back to facilitate some color blending in my little ASCII Monsters mini-game. The class is pretty small and simple, providing only a few basic, but very helpful operations. And having run into a question or two in some development forums, it occurred to me that some of these color operations might be helpful!

So, the Color class is here:

    http://www.thepointless.com/js/color.js

At the time of this posting, I don't have any hot-link protection in place. But, I'd strongly suggest grabbing a copy of the file for yourself anyway. I can't promise the link above will remain static!

Usage is pretty simple. When you instantiate a new Color, you can either provide RGB values or a CSS-like RGB, hex, or HSL string.

// full-on red
var red = new Color(255, 0, 0);

// full-on green
var green = new Color("#00ff00");

// full-on blue
var blue = new Color("rgb(0,0,255)");

// full-on yellow, I think
var yellow_i_think = new Color("hsl(60,100%,50%)");


And we can blend colors, like so:

// the orange halfway between red and yellow
var orange = red.getBlendedColor(yellow, 0.5);

// a yellow-leaning orange
var yellow_orange = red.getBlendedColor(yellow, 0.75);

// a red-leaning orange
var red_orange = red.getBlendedColor(yellow, 0.25);


And assign these colors to and from node styles with ease:

// the Color's toString() returns a CSS-style hex string
ugly_node.style.color = red;
ugly_node.style.backgroundColor = green;


We can also create colors from node styles with ease:

// lighten ugly-node a bit
var fg = new Color(ugly_node.style.color);
var bg = new Color(ugly_node.style.backgroundColor);
ugly_node.style.color = fg.getBlendedColor(new Color('#ffffff'), 0.5);
ugly_node.style.backgroundColor = bg.getBlendedColor(new Color('#ffffff'), 0.5);


And most importantly, of course, we can make pretty gradients without leveraging technologies that don't yet have "full" cross-browser support:

<div id='gradient' style='height: 40px; width: 500px;'></div>
<script type='text/javascript'>
  (function() {
    var out = document.getElementById('gradient');
    var left = new Color("#ff0000");
    var right = new Color(255, 255, 0);
    for (var pct = 0; pct <= 1; pct += 0.01) {
      var n = document.createElement('div');
      n.style.float = 'left';
      n.style.width = '5px';
      n.style.height = '40px';
      n.style.backgroundColor = left.getBlendedColor(right, pct);
      out.appendChild(n);
    }
  })();
</script>


Case in point:


Your comments and suggestions are welcome.

Monday, December 17, 2012

getters, setters, and redefining javascript

Given enough time, I'm sure every browser will support proper JavaScript getters and setters. Whether getters and setters are truly good is another matter. Let's assume they're good. And let's pretend we want to use them ... right now.

I'd posit there's a potential solution. And while I can't offer a full or fool-proof solution at this time, an intriguing thought struck me. That is, getters and setters are effectively nothing more than a compiler rewriting code that looks like this:

console.log(obj.property);
console.log(obj['property']);

Into something that looks more like this:

console.log(obj._get("property"));
console.log(obj._get("property"));


I'm not entirely sure why we feel better treating obj.property like any other property. Why don't we want to explicitly acknowledge the "work" that must be done each time we hit it? Perhaps it affords us some debatably necessary ignorance. Perhaps it allows for greater laziness. Perhaps we think the thinking or the characters we save by using the property notation all the time, rather than only when we're referring to actual properties, significantly outweighs the performance cost of preprocessing our code.

... awkward silence ...

Nevermind. Back to pretending getters and setters are unequivocally good ...

I've seen a lot of folks, including myself, trying to hack getters and setters (and even "dynamic" getters and setters) into JavaScript without fully recognizing that JavaScript is a dynamic language. It's compiled at runtime. And segments of every script can even be compiled mid-stream using fancy things like eval(). (Contrary to the scary stories you may have heard, eval() is not evil unless it is abused. And in my opinion, this is precisely the sort of rare case in which eval() is an acceptable solution.)

So, without spending a great deal of time trying to produce a perfect solution, let's just prove the concept with some basic getters and setters using some very simple self-improving script.

When an object property is referred to, we want our self-aware script to look for mechanisms to interact with that property, going from most specific to least specific. That is, when property obj.prop is referred to, we want to use the first available mechanism on our short, ordered list of possible mechanisms.

  1. obj.prop.(get|set(v)) - the property-specific getter or setter
  2. obj.prop - the property itself
  3. obj.(get(n)|set(n,v)) - the class-level getter or setter
  4. undefined - though not a getter or setter, explicitly acknowledge this as the default

Implemented as getter/setter wrappers, our logic looks like this:

Object.prototype._get = function(n) {
  // first, infinite recursion prevention
  this.__gotten = this.__gotten || {};
  if (this.__gotten[n]) {
    return this[n];  // even if it's undefined.
  } else {
    this.__gotten[n] = true;
  }

  // then, call getters
  var rv;
  if (this[n] == undefined) {
    if (this['get'] && typeof(this.get) == 'function') {
      rv = this.get(n);
    } else {
      rv = undefined;
    }
  } else {
    if (this[n]['get'] && typeof(this[n].get) == 'function') {
      rv = this[n].get(n);
    } else {
      rv = this[n];
    }
  }

  // unset the "gotten" flag so the getter works properly in
  // subsequent calls
  this.__gotten[n];
  return rv;
}

Object.prototype._set = function(n, v) {

  // first, infinite recursion prevention
  this.__setted = this.__setted || {};
  if (this.__setted[n]) {
    return (this[n] = v);  // even if it's undefined.
  } else {
    this.__setted[n] = true;
  }

  // then, call setters
  var rv;
  if (this[n] == undefined) {
    if (this['set'] && typeof(this.set) == 'function') {
      rv = this.set(n, v);
    } else {
      rv = undefined;
    }
  } else {
    if (this[n]['set'] && typeof(this[n].set) == 'function') {
      rv = this[n].set(n, v);
    } else {
      rv = (this[n] = v);
    }
  }

  // unset the "setted" flag so the getter works properly in
  // subsequent calls
  this.__setted[n];
  return rv;

}

With or without any fancy rewriting, we can we then build getter/setter endowed objects like this:

var o = {
  a: 1,
  b: {
    get: function() { return 2; },
    set: function() { /* do nothing */ }
  },
  get: function(n) {
    if (this[n] == undefined) {
      this[n] = 0;
      return this[n];
    }
  },
  set: function(n, v) {
    this[n] = v % 2;
  }
};

And we can interact with them like this:

console.log(o._get('a'));
console.log(o._get('b'));
console.log(o._get('c'));
o._set('d', 123);
console.log(o._get('d'));

And, we'll see precisely what we expect to see in our console. But, our goal is to see the same output by writing this:

console.log(o.a);
console.log(o.b);
console.log(o.c);
o.d = 123;
console.log(o.d);


Well alright. So, let's just have our script rewrite itself a little before it executes. And the best way to do that, I'd argue, is to wrap our code in a function that make some minor edits and eval()'s the result. So, here my first simple working implementation:

var F = function(f) {

  var _f = f.toString();
  _f = _f.replace(/\.([a-zA-Z0-9_])+\s*=\s*([^=].*)\s*;/gm, "._set('$1',$2);");
  _f = _f.replace(/\[(['"a-zA-Z0-9_]+)\]\s*=\s*([^=].*)\s*;/gm, "._set($1, $2);");
  _f = _f.replace(/\.([a-zA-Z0-9_])+(\.|\s|;|\n|\))/g, "._get('$1')$2");
  _f = _f.replace(/\[(['"a-zA-Z0-9_]+)\]/g, "._get($1)");

  eval("var rv = " + _f + "");
  rv._original = f; // for debugging, curiosity, etc.
  return rv;
} // F()

Using our modifications to the Object prototype, and our fancy F() function, our getter/setter endowed object works as intended like this:


F(function() {
  var o = {
    a: 1,
    b: {
      get: function() { return 2; },
      set: function() { /* not allowed */ }
    },
    get: function(n) {
      if (this[n] == undefined) {
        this[n] = "no.";
        return this[n];
      }
    },

    set: function(n, v) {
      this[n] = v % 2;
    }

  };


  console.log(o.a);
  console.log(o.b);
  console.log(o.c);
  o.d = 123;
  console.log(o.d);
})();


As expected, our console shows:

1
2
NaN
1

Remember, when pondering line 3 of our output, that our dynamic getter is returning the result of an assignment, which works through our dynamic setter, which expects the assigned value to be a number.

Also bear in mind, this little example is fairly simple, and our F() can tend to mangle more complex syntaxes. Our regular expressions are simple and inflexible. For instance, we can throw it off by hiding an assignment in conditional or by simply using the increment/decrement operators.

Consider this awkwardly written loop.

var app = F(function() {
  var o = {i:0};
  var rand = false;
  while (o.i = rand) {
    console.log(o);
    rand = Math.random();
  }
  o.i--;
  o.i += 5;
});

Now, we wouldn't expect to see such a silly loop in a real application. But, we should expect it to work nonetheless. And it doesn't. The F() rewritten function subverts some getter/setters and replaces others with invalid code. The rewritten, broken function looks like this:

function () {
  var o = {i:0};
  var rand = false;
  while (o._get('i') = rand) {
    console.log(o);
    rand = Math.random();
  }
  o.i--;
  o._get('i') += 5;
}

Multi-line assignments break too. This won't work:

var app = F(function() {
  var o = {};
  o.i = "This"
    + " assignment spans"
    + "multiple lines.";
});

So, F(), as I have defined it above, is limited. But, it illustrates the concept: JavaScript is a dynamic, compile-as-needed programming language. If you want getters, setters, operator overloading, a fancy short-hand syntax, or anything else, implement it!

Please feel free to suggest improvements. My latest getter-setter rewriter will be available here, and will include any suggestions I find significant and valuable. http://www.thepointless.com/js/accessors.js

Tuesday, October 16, 2012

building custom tags in html5 and javascript, part 1

If you've had the chance to work with Facebook's Social Plugins, you've likely come across the option to embed their widgets using XFBML. They offer a similar "HTML5 compatible" option, but I tend to think the custom, name-spaced tags option is neat. So, let's explore one way to do that.

As an example of the usefulness, I'll use my recently completed book of small potatoes (wisdom of the ancients) from thepointless.com. With each wise guy quote, we see markup in the following format, which renders a voting widget, with the help of some fancy JavaScript:

<tpdc:voter method='/ajax/pointlisting_vote' record_id='37' vote='1'
 fb_like_url='http://www.thepointless.com/pointlistings?id=37'>
  <tpdc:vote>vote<tpdc:vote>
  <tpdc:unvote>unvote<tpdc:unvote>
  +<tpdc:count>1<tpdc:count>
<tpdc:voter>


(Rather the post the full script that does the magic here, I'll be using some trivial simplified JavaScript here for conciseness. You can take a peek at it in action if you're interested.)

To prevent Internet Explorers 7 and 8 from giving us the silent treatment come script-time, our custom tags need to live in a namespace. (Other browsers, as far as I know, ignore the namespace altogether.) And the namespace must be defined in the opening html tag:

<html xmlns:tpdc="http://www.thepointless.com/ns/tpdc">

Now, as you might imagine, a little JavaScript magic is needed to work with these nodes gracefully. And although a touch hacking, it may not be as hackish as you'd expect! As seen on the thepointless.com, here is the function we use to grab these nodes and "import" their attributes correctly:

var getNodes = function(n, q) {
    try {
        // i think "r" stands for "return" here.

        // in any event, our first step is to try to find the nodes
        // by their full names -- that is, with the namespace.
        var r = n['getElementsByTagName'](q);
        if (r.length < 1) {

            // ... and if we don't find anything, omit the namespace,
            // if present.
            var p = q.split(":");
            if (p.length == 2) {
                r = n['getElementsByTagName'](p[1]);
            }
        }

        // import attributes, for each node
        for (var i = 0; i < r.length; i++) {


            // for each attribute in r[i]

            // first, make sure we're not attempting to RE-import attributes.
            var ri = r[i];

            if (!ri.__attributes_imported) {

                // so, we basically iterate through each attribute and
                // "re-attach" it to the node directly. seems silly, but
                // it allows our scripts to operate on these attributes
                // later much more naturally.
                for (var ai = 0; ai < ri.attributes.length; ai++) {
                    var att = ri.attributes[ai];
                    if (!ri[att.name]) {
                        ri[att.name] = att.value;
                    }
                }
                ri.__attributes_imported = true;
            }
        }

        // and finally, give that list of nodes back.
        return r;
    } catch (e) {
        return [];
    }
} // getNodes()


This getNodes() implementation works in recent versions of Chrome, Firefox, Safari, and IE7+ (not tested in IE6). One-off use of the function works similarly to (but not exactly like) the normal getElementByTagName() function. So, if we want to work with all the tpdc:voter nodes, we could do this:

// get an array of tpdc:voter nodes.
var voterNodes = getNodes(document, "tpcd:voter");

// for each one, let's add an onclick event that simply displays
// the method attribute.
for (var i = 0; i < voterNodes.length; i++) {

  // to be clear, the "this" here is the node itself
  voterNodes[i].onclick = function() { alert(this.method); }
}


Pretty simple. And working with child nodes is just as easy. Suppose we want to loop through all the tpdc:voter nodes and make each tpdc:count node with a value of 10 or more bold. Now, with this trivial example, we could do this globally, like so:

// get all the tpdc:count nodes.
var countNodes = getNodes(document, "tpdc:count");

// make each one with an innerHTML value >= 10 bold
for (var i = 0; i < countNodes.length; i++) {
  if (parseInt(countNodes[i].innerHTML) >= 10) {
    countNodes[i].style.fontWeight = 'bold';
  }
}


Or, we can work within the context of a single tpdc:voter node, as we might in a voter node's class method. A trivial example again, but this works as expected:

// assume we already have a tpdc:voter node, voterNode
var countChildren = getNodes(voterNode, 'tpdc:count');
// act on all tpdc:count nodes found.
for (var ci = 0; ci < countChildren.length; ci++) {
  if (parseInt(countChildren[ci].innerHTML) >= 10) {
    countChildren[ci].style.fontWeight = 'bold';
  }
}


Neat.

But, we're not done yet. What we really want is the ability to treat these custom tags like instances of a real class. And we can! We can define a namespace (empty object) with some classes (functions) and bind them semi-automagically to the DOM nodes.

Let's consider a TPDC namespace with two very simple classes:

// the namespace
var TPDC = {};


// class Simple
TPDC.Simple = function() {
  // we can do things at object "instantiation" time
  this.innerHTML = "Simple";
} // class TPDC.Simple()

// class LessSimple
TPDC.LessSimple = function() {

  // we can also define object methods
  this.makeSimple = function() {
    this.innerHTML = "Simple";
  } // TPDC.LessSimple.makeSimple()

  this.onClick = function() {
    this.makeSimple();
  } // TPDC.LessSimple.onClick()

  this.innerHTML = "Less Simple";
} // class TPDC.LessSimple()


Simple. And so is binding our whole TPDC "namespace" to the appropriate document nodes:

for (var k in TPDC) {
  var tpdc_nodes = getNodes(document, 'tpdc:' + k);
  for (var i = 0; i < tpdc_nodes.length; i++) {
    TPDC[k].apply(tpdc_nodes[i]);
  }
}


And, if we want to stuff our getNodes() function stuffed neatly away in a shared library, we can also add a convenient little binder method:

var BindNS = function(ns_name, parent) {
  // "find" the namespace within its parent, given its name
  var p = parent || this;
  var ns = p[ns_name];


  // apply the class constructors to the document nodes
  for (var k in ns) {
    var ns_nodes = getNodes(document, ns_name.toLowerCase() + ":" + k.toLowerCase());
    for (var i = 0; i < ns_nodes.length; i++) {
      ns[k].apply(ns_nodes[i]);
    }
  }
} // BindNS()


And then bind our simple TPDC class with one line:

BindNS('TPDC');

And at this point, any tags in our document that look like these will operate as though they're instances of our Simple and LessSimple classes:

<tpdc:simple>weeeeee...</tpdc:simple>
<tpdc:lesssimple>...eeeeee!</tpdc:lesssimple>


They also operate as generic DOM nodes too, of course. Your JavaScript classes are, in many respects, subclasses of the Node class.

Two things to remember though:

1. Most browsers ignore the namespace. So, this approach doesn't grant you an ability, so far as I know, to create namespaces with identical class names. You can work around this by including the namespace again as a prefix in your class names if you need to.

  and

2. It's invalid markup! It's served up with a text/html content type and an HTML5 doctype, which explicitly forbids tag namespaces. So, you might even call it grossly invalid! That said, of all the custom tag variations I tested, this syntax actually provided the most consistent behavior.

In any event, I like the way this works. I can simplify and minimize both my markup and my script using this approach. And I definitely plan on playing with it more. One thing I'm pondering is an elegant way to automagically attach custom child tags to custom-tag parents in a helpful and meaningful way.

So, if anyone has any thoughts on that (or any of this), I'd be interested in your feedback.

And of course, I encourage you to check out the working examples of this concept at thepointless.com, starting with the book of small potatoes!

Wee!

UPDATE: There's now a building custom tags in html5 and javascript, part 2!