webdev: (Default)
One of the things that was very important to us as we were building Google Web Designer was to build a tool that output code that was as clean as possible. Standards compliance wasn't enough--you can write fairly obfuscated code that is completely compliant with all relevant standards. We wanted to do better than just comply with the standards, we wanted clean, human-readable code that is as close as possible to what we might have written by hand.

In particular, several of the members of the GWD team--including myself--have a lot of practical and theoretical experience with CSS, and we wanted our tool to output well-structured terse CSS. As it turns out, that was more difficult than it sounds.

When an element is created in GWD, our styles controller gives it a base CSS class with a generated class name that follows the pattern "gwd-tagname-randomstring". Any CSS property changes applied to the element would go into that class. This pattern provides a couple of useful features right off:
  1. It namespaces all of our generated classes with a gwd prefix, so you can easily tell generated code from handwritten code.

  2. Including the tag name in the class gives some indication of what tag the class has been applied to. If we just used a completely random string, the class names would be a lot more opaque.
Useful class names are only the beginning, though. Any decent authoring tool has to have standard editing features like cut/copy and paste, and GWD is no exception. In terms of CSS, we determined that we wanted copied elements to share a single base class. So if you create an element on stage the code snippet looks something like this:
/* css */
.gwd-div-mak0 {
 width: 10px;
 height: 10px;
 background-color: rgb(0, 0 0);
 top: 63px;
 left: 100px;
}

/* markup */
<div class="gwd-div-mak0" id="original"></div>

(Here I have added an ID attribute to the tag to distinguish it from the duplicate we're about to make. Our paste routine is smart enough not to introduce duplicate IDs into the DOM.) If you selected that element on stage and hit Copy and then Paste, the result would be:
/* css */
.gwd-div-mak0 {
 width: 10px;
 height: 10px;
 background-color: rgb(0, 0 0);
 top: 63px;
 left: 100px;
}

/* markup */
<div class="gwd-div-mak0" id="original"></div>
<div class="gwd-div-mak0" id="original_1"></div>

Sharing classes among visually identical elements is CSS 101, so that's what we did.

Now here's the first question: what happens when you move the new element? When you do that, you change its top and left properties...but you don't want to affect the original element. So at this point, our Styles Controller is smart enough to know it needs to add a new class to the element that has been changed:
/* css */
.gwd-div-mak0 {
 width: 10px;
 height: 10px;
 background-color: rgb(0, 0 0);
 top: 63px;
 left: 100px;
}

.gwd-div-ghk3 {
 top: 100px;
 left: 200px;
}

/* markup */
<div class="gwd-div-mak0" id="original"></div>
<div class="gwd-div-mak0 gwd-div-ghk3" id="original_1"></div>

If you move the original element, it gets a new class too:
/* css */
.gwd-div-mak0 {
 width: 10px;
 height: 10px;
 background-color: rgb(0, 0 0);
 top: 63px;
 left: 100px;
}

.gwd-div-ghk3 {
 top: 100px;
 left: 200px;
}

.gwd-div-lj0n {
 top: 262px;
 left: 555px;
}

/* markup */
<div class="gwd-div-mak0 gwd-div-lj0n" id="original"></div>
<div class="gwd-div-mak0 gwd-div-ghk3" id="original_1"></div>

To bring us to our first edge case, what if you make changes to both elements background-color, width and height properties, resulting in this situation:
/* css */
.gwd-div-mak0 {
 width: 10px;
 height: 10px;
 background-color: rgb(0, 0 0);
 top: 63px;
 left: 100px;
}

.gwd-div-ghk3 {
 width: 15px;
 height: 15px;
 background-color: rgb(10, 10, 10);
 top: 100px;
 left: 200px;
}

.gwd-div-lj0n {
 width: 25px;
 height: 25px;
 background-color: rgb(100, 100, 100);
 top: 262px;
 left: 555px;
}

/* markup */
<div class="gwd-div-mak0 gwd-div-lj0n" id="original"></div>
<div class="gwd-div-mak0 gwd-div-ghk3" id="original_1"></div>

At this point the original shared class is no longer affecting either element; all of its properties are overridden by later classes for both elements. What should the tool do at this point? Should it remove the assignment from both elements? Should it delete the class from the style sheet? And if we do delete the class, what should we do if someone then hits Paste again?

This is just one of the situations we encountered, and is actually among the simplest. Other, more complex situations arise when you combine the desire to have terse CSS with features like cut/copy/paste, undo/redo, animation, and element inspection. Toss in platform inconsistencies and the occasional browser bug and you end up with a lot of edge cases to resolve.

To a significant extent we're still working on this as a team. Moving forward, we are enhancing our data models and changing some feature implementations, which should make it easier to resolve some of the edge cases. And we've received a lot of feedback from users about their expectations as well, which helps us refine our goals. This is a tough problem, but we're going to keep at it. The quality of our code output is very important to us.
webdev: (Default)
jQuery does a great job of providing a normalized event model across all browsers. It’s my go-to library for that particular task, plus it has several other useful features. One of the great features jQuery has for its event system is namespacing. You can namespace your event types as you attach, detach, and call them. For example:
// Attach a simple event handler to the element
$(“#targetEl”).on(“click”, function(event) {
  console.log(‘A click event happened!’);
});

// Now attach a namespaced event handler to the element
$(‘#targetEl’).on(‘click.plugin’, function(event) {
  console.log(‘A click.plugin event happened!’);
});

Now, when you click on the target element, both event handlers will fire. If you manually trigger a click event you can add the namespace to it:
$(‘#targetEl’).trigger(‘click.plugin’);

This will trigger only the click.plugin event handler. This gives you an easy way of segregating your event handlers, and is particularly useful with custom events.

Event namespacing is a great feature, and I decided to think about how I might implement it from scratch, just as an exercise. The reason jQuery has namespaced events is because it has its own event handling system, so I started to think about what it would take to write a simple event manager that would provide a namespacing feature.

So here’s a first stab at a general event manager. The goal here isn’t to produce production-quality code, but rather to explore the desired features and get some code up that implements them, and if we like the direction we can later create something that’s more structured.

Long post is best post! Click to continue... )
webdev: (Default)
One of my favorite little-known features of the DOM is the EventListener interface. We all know how to attach an event listener to a DOM element using Element.addEventListener method, which takes three parameters:

  • eventType: a string that indicates the type of event

  • handler: a function to execute when the event happens

  • bubble: whether or not to execute the function during the bubble phase


What’s little known is that the DOM specifies you can use any object as the handler, as long as it implements the EventListener interface. According to the DOM Level 2 standard:

The EventListener interface is the primary method for handling events. Users implement the EventListener interface and register their listener on an EventTarget using the AddEventListener method. The users should also remove their EventListener from its EventTarget after they have completed using the listener.


An EventListener interface is simply a method on the object called handleEvent. Thus, the two event handlers can be used interchangeably:
function handleClickFunction(event) {
  alert(‘The element was clicked!’);
}

var handleClickObject = {
  handleEvent: function(event) {
    alert(‘The element was clicked!’);
  }
}

You register an event handler object using Element.addEventListener just like a function:
var myElement = document.getElementById(“targetElement”);
myElement.addEventListener(“click”, handleClickObject, true);

When a click event happens on the target element, handleClickObject.handleEvent will be invoked. Within that method, the this keyword will be a reference to handleClickObject, just like you’d expect.

One of the great things about this is it makes it easy to build a single object containing several event handlers, and then use the EventListener interface to delegate to the appropriate type. For example:
// Create an EventManager “class” that we can use as a base from which
// to instantiate event managers for elements as needed.
var EventManager = {
  handleEvent: function(event) {
    if (this[event.type] != null) {
      this[event.type].call(this, event);
    } else {
      console.log('no event handler for event of type ' + event.type);
    }
  }
};

// Create an event manager object from our base class, adding in
// methods for click and mousedown (but not mouseup)
var testElEventManager = Object.create(EventManager, {
  click: {
    value: function(event) {
      console.log('click event happened');
    }
  },
  mousedown: {
    value: function(event) {
      console.log('mousedown event happened');
    }
  }
});

// Bind the new event manager to testEl for the events click, 
// mousedown, and mouseup.
testEl.addEventListener('click', testElEventManager);
testEl.addEventListener('mousedown', testElEventManager);
testEl.addEventListener('mouseup', testElEventManager

In this example I’ve created a base object which implements the EventListener interface. All it does is check to see if a method exists on the object for a given event type, and if it does it calls it, and if it doesn’t it provides an error.

Then I create a new event manager from that base object using Object.create(). You could just as easily have created a constructor function; I prefer this technique because it allows me to add new methods as part of the creation step. In this example, I’m adding methods for click and mousedown events.

Then I bind the new event manager object to my test element for the three events click, mousedown, and mouseup.

When you click on the element, the mousedown-mouseup-click event sequence will fire, and the following output will result on the console:
mousedown event happened
no event handler for event of type mouseup
click event happened

This illustrates using a single object to handle multiple event types--pretty nifty.

Coming up: Using this method to create a single Event Manager object that serves as a one-stop shop for everything event-related: registration, delegation, etc. We can even include some nifty extra features like namespacing.

Profile

webdev: (Default)
Jon Reid

October 2013

S M T W T F S
  12 345
6789101112
13141516171819
20212223242526
272829 3031  

Links

Syndicate

RSS Atom