Object Oriented Css - Simplified

Object oriented coding can sometimes be hard to understand. When working with other people I usually use this metaphor to explain oocss.

We want to create a list of books, that sit on a shelf within a library. As html it would look something like this:



<ul>
    <li>Book 1</li>
    <li>Book 2</li>
    <li>Book 3</li>
</ul>

The wrong way
The common (but very wrong) way is to apply specific class names to each level, combining structural and visual styles together under a single class e.g.

.library {
    margin: 0px auto 0 auto;
    width: 980px;
    background-color: #ffcc00;
}

.shelf {
    padding: 20px;
    background-color: #333333;
}

.book {
    display: inline-block;
    width: 100px;
    margin: 0px 10px 0px 0px;
    background-color: #ccff00;
}

The object oriented way
Separate structure/layout from style/theme. So in our example that would be:

Layout/Structure:
1) library - the bricks and mortar of the building
2) shelves - the shelves attached with hinges to the brick, x amount
3) book - the items containers with content inside them, x amount

Style/Theme:
1) library - The decoration on the walls, how it looks and is painted
2) shelves - the wood effect, shadows borders, spacing
3) books - whether the books are big blue hardbacks, or small red paperbacks

Layout

We can identify the common structural elements and give them their own name-spaced class:

.layout .row {
    max-width: 980px;
    margin: 0 auto 10px auto;
    overflow: auto;
}

.layout .row .row {
    margin: 0 -10px;
}

.layout .col {
    float: left;
    padding: 0 10px;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

So how would our html look with the structural css class names?

<div class="layout">
    <ul class="row">
        <li class="col">Book 1</li>
        <li class="col">Book 2</li>
        <li class="col">Book 3</li>
    </ul>
</div>

Theme

Then identify the common visual/styling elements and give them their own name-space class:

.light {
    color: #333333;
    background-color: #ffffff;
}

.light .c1 {
    color: #333333;
}

.light .c2 {
    color: #2989D8;
}

.light .bg1 {
    background-color: #ffffff;
}

.light .bg2 {
    background-color: #eeeeee;
}

Now how would our html look with these style/theme classes?

<div class="layout">
    <ul class="row light">
        <li class="col c1 bg1">Book 1</li>
        <li class="col c1 bg1">Book 2</li>
        <li class="col c1 bg1">Book 3</li>
    </ul>
</div>

Now we can reuse our classes and even move the theme around to theme just a single row if we want or even add a second theme for that row of items:

<div class="layout">
    <ul class="row light">
        <li class="col c1 bg1">Book 1</li>
        <li class="col c2 bg1">Book 2</li>
        <li class="col c1 bg2">Book 3</li>
    </ul>
    <ul class="row dark">
        <li class="col c1 bg1">Book 1</li>
        <li class="col c2 bg1">Book 2</li>
        <li class="col c1 bg2">Book 3</li>
    </ul>
</div>

.dark {
    color: #ffffff;
    background-color: #111111;
}

.dark .c1 {
    color: #ffffff;
}

.dark .c2 {
    color: #2989D8;
}

.dark .bg1 {
    background-color: #111111;
}

.dark .bg1 {
    background-color: #333333;
}

To see a real-world example check out my simple responsive template on github:
https://github.com/kmturley/alley


Cross domain, cross browser ajax with jsonp

Previously I looked at ways to send ajax GET requests cross domain using CORS. This is a great solution and works well across the browsers normally supported IE8+. This requires the server to have allow access from all origins though and is not supported by older browsers.

JSONP is a variation of json which stands for json with padding. It is a method which allows you to wrap any json data with a function callback on the server feed. This means that when it is loaded your function is called and the json is passed back as a parameter.

Here is an example of a feed of JSON
https://gdata.youtube.com/feeds/api/videos?q=dogs&v=2&alt=jsonc&max-results=5

The first line looks something like this:


{apiVersion: "2.1"...



However if we pass through a callback parameter we get can see the server also supports JSONP
https://gdata.youtube.com/feeds/api/videos?q=dogs&v=2&alt=jsonc&max-results=5&callback=functionname

Now the first line of the data returned shows:
functionname({"apiVersion":"2.1"

This means a function called functionname() {} will be called on our page with the data passed back to us.

So how do we make this usable when sending ajax requests? We can let the browser do the request for us using the script tag. First we need to generate a new function name automatically from the browser timestamp


var timestamp = 'callback'+new Date().getTime();

Then we create the function dynamically from the function name:

window[timestamp] = function(e) { console.log(e); };

Now we just need to append the url to a script tag in the page dynamically:

var script = document.createElement('script');
script.src = url+'&callback='+timestamp;
document.getElementsByTagName('head')[0].appendChild(script);

After the script tag has loaded, your function will be called and the data returned!

Lets put that into a reusable function we can call multiple times:

function ajax(url, callback) {
    var timestamp = 'callback'+new Date().getTime();
    window[timestamp] = function(e) { callback(e); };
    var script = document.createElement('script');
    script.src = url+'&callback=window.'+timestamp;
    document.getElementsByTagName('head')[0].appendChild(script);
}

usage would look like this:

ajax('https://gdata.youtube.com/feeds/api/videos?q=dogs&v=2&alt=jsonc&max-results=5', function(e) {
    console.log(e);
}

Here is an example of my full ajax function supporting CORS and JSONP

function ajax(url, callback, filetype, type) {
        filetype = filetype ? filetype : 'json';
        type = type ? type : 'GET';
        var success = function(e) {
            var items = '';
            switch(filetype) {
                case 'csv': items = csv(e); break;
                case 'json': items = JSON.parse(e); break;
                default: items = e; break;
            }
            callback(items);
        }
        var error = function(e) {
            console.log('Please enabled CORS using access-control-allow-origin');
        }
        if (filetype == 'jsonp') {
            var timestamp = 'callback'+new Date().getTime();
            window[timestamp] = function(e) { success(e); };
            var script = document.createElement('script');
            script.src = url+'&callback='+timestamp;
            document.getElementsByTagName('head')[0].appendChild(script);
        }
        else {
            var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
            if (window.XDomainRequest && !sameOrigin(url)) { xhr = new XDomainRequest(); xhr.onload = function(){ success(xhr.responseText); }; }
            if (filetype == 'image' && xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=x-user-defined'); }
            xhr.onerror = error;
            xhr.onreadystatechange = function(e) { if (xhr.readyState == 4 && xhr.status == 200) { success(xhr.responseText); } }
            try {
                if ('withCredentials' in xhr) { xhr.open(type, url, true); }
                else { xhr.open(type, url); }
                xhr.send(null);
            }
            catch(e) { error(e); }
        }
}


function sameOrigin(url) {
        var split = url.split('/');
        if (split[0]+'//' == window.location.protocol+'//') { return split[2] != window.location.host ? false : true; }
        else { return true; }
}

And here is a link to my Utils module where I store my ajax and other helper functions:
http://www.kimturley.co.uk/js/modules/Utils.js




Cross browser cross domain ajax requests

When programming JavaScript you will eventually hit several cross browser inconsistencies. One of the most frustrating is the ajax request. This allows you to send and load data from other files or urls. A quick search will show that the modern browsers support a simple syntax:


var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(e) {
  console.log(e.target.responseText);
})
xhr.open('GET', 'http://echo.jsontest.com/key/value/one/two');
xhr.send();

This works in Chrome, Safari and other modern browsers. But what happens when we need to older browsers such as Internet Explorer and Firefox 3.6? Well a simple search returns almost every answer as "use jQuery".

The reason behind this is that there are many inconsistencies between browsers and whether requests are cross domain, when the server requires credentials and pre-flighted request that it becomes a nightmare to support.


What happens if we don't want to include a 30KB library just for ajax requests? Well it is fairly simple to support cross browser ajax requests if you know how.

The first step is to check if the browser is IE then use an ActiveXObject to support local requests (This can be used for cross domain requests but it is limited by browser security restrictions, so may not be ideal for some users e.g. inside company internal network). I've also defined success and error functions which we will reference later in several places.



var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
var success = function(e) { console.log(e); }
var error = function(e) { console.log(e); }
if (window.XDomainRequest && !this.sameOrigin(url)) { xhr = new XDomainRequest(); xhr.onload = success; }



Now we need to reference those complete and error functions which will be fired based on whether onload or on readystatechange functions are available. If the request is crossdomain use XDomainRequest for IE only.


if (window.XDomainRequest && !sameOrigin(url)) { xhr = new XDomainRequest(); xhr.onload = success; }
xhr.onerror = error;
xhr.onreadystatechange = function(e) { if (xhr.readyState == 4 && xhr.status == 200) { success(e); } }


If you would like to load images then you will need to override the mimetype to grab the data

if (file == 'image' && xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=x-user-defined'); }

Also if the request needs credentials then you need to send the value true through, we will surround this with a try catch block as IE throws an access denied error for request without CORS enabled. If you get the error message you need to ensure the feed header has 'access-control-allow-origin: *'


try {
    if ('withCredentials' in xhr) { xhr.open(type, url, true); }
    else { xhr.open(type, url); }
    xhr.send(null);
}
catch(e) { error(e); }


There you have a working cross browser ajax request. Great, but not exactly a good idea to duplicate this into every part for our app. Lets convert this into a reusable function which I store in my Utils module.

function ajax(url, callback, filetype, type) {


    filetype = filetype ? filetype : 'json';
    type = type ? type : 'GET';
    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
    var success = function(e) {
        var items = '';
        switch(filetype) {
            case 'csv': items = csv(xhr.responseText); break;
            case 'json': items = JSON.parse(xhr.responseText); break;
            default: items = xhr.responseText; break;
        }
        callback(items);
    }
    var error = function(e) {
        console.log('Please enabled CORS using  access-control-allow-origin');
    }
    if (window.XDomainRequest && !sameOrigin(url)) { xhr = new XDomainRequest(); xhr.onload = success; }
    if (filetype == 'image' && xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=x-user-defined'); }
    xhr.onerror = error;
    xhr.onreadystatechange = function(e) { if (xhr.readyState == 4 && xhr.status == 200) { success(e); } }
    try {
        if ('withCredentials' in xhr) { xhr.open(type, url, true); }
        else { xhr.open(type, url); }
        xhr.send(null);
    }
    catch(e) { error(e); }


}

The check for sameDomain matches the url against the current window location url:

function sameOrigin(url){
    var split = url.split('/');
    if (split[0]+'//' == window.location.protocol+'//') { return split[2] != window.location.host ? false : true; }
    else { return true; }
}


And to use it you would write:

ajax('http://echo.jsontest.com/key/value/one/two', function(e) {
    console.log(e);
});

I'm using this technique combined with a csv to json convertor script to load data into my site. My csv to json function:

function csv(string, divider) {
    divider = divider || ',';
    var regex = new RegExp(("(\\"+divider+"|\\r?\\n|\\r|^)"+"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +"([^\"\\"+divider+"\\r\\n]*))"), 'gi');
    var regex2 = new RegExp("\"\"", 'g');
    var items = [];
    var fields = [];
    var matches = null;
    while (matches = regex.exec(string)) {
        var match = matches[1];
        var value = '';
        if (match.length && match != divider) { items.push({}); }
        if (matches[2]) { value = matches[2].replace(regex2, '\"'); }
        else { value = matches[3]; }
        if (items.length == 0) { fields.push(value); }
        else {
            var index = Utils.size(items[items.length-1]);
            var name = fields[index];
            items[items.length-1][name] = value;
        }
    }
    return items;
}

If you want to test it working you can try these test feeds:


// cross domain json feed without CORS
var url = 'http://www.kimturley.co.uk/data/projects.json';

// cross domain json feed with CORS
var url = 'http://pipes.yahooapis.com/pipes/pipe.run?_id=giWz8Vc33BG6rQEQo_NLYQ&_render=json';

// same domain json feed
var url = '/echo/json/';

// google docs example
var url = 'https://docs.google.com/spreadsheet/pub?key=0AsnymCBa0S5PdGh4ZVF5Uktfc2hyYm5ibHduTHAzQ1E&output=csv';

Here is a working example with all of the code:
http://jsfiddle.net/kmturley/4RCBg/

Hope that helps some of you guys