Backbone.js & Knockout (English version)

I had a lot of work and things to do, so I couldn’t write.

The last year my boss asked me to look up all the libraries like Backbone.js and Knockout. The idea was to check all the options and take the more convenient.

To accomplish that I began to read and look a lot of information. Besides, I took the opportunity to try each one and feel them. For that, I made an application in Backbone.js and then observed how it would be using Knockout.

I will show you the code and then give my opinion. That way you will have a better decision.

Backbone.js

It’s really powerful and used by a lot of people, but it have an hard learning curve. You have a model and a bunch of things to define. If you follow all the conventions, the code will be easy to mantain. Here you can see more information and details.

It is ideal for REST web servicies.

Creating this application took me one noon and part of the morning.

The html:

<div>
    <fieldset>
        <legend>Person List - Using Backbone</legend>
        <div id="person-container">
            <input type="button" value= "Create New" class="Add" id="btn-create-new-person" />
            <table id="person-list">
                <tr>
                    <th>
                        Name
                    </th>
                    <th>
                        Description
                    </th>
                    <th>
                    </th>
                </tr>
            </table>
        </div>
        <script id='person-template' type='text/template'>
            <td><%=ID%></td> <td><%=FirstName%></td> <td><%=LastName%></td> <td><%=Age%></td>
            <td><input type="button" value="Edit" class="person-edit" /> | <input type="button" value="Details" class="person-detail" /> | <input type="button" value="Delete" class="person-delete" /></td>
       </script>
    </fieldset>
</div>

Javascript:

$(function () {

    // Person Model
    var Person = Backbone.Model.extend({
        urlRoot: '/Person/List/',
        initialize: function () {
            console.log('Person initialize');
        },
        defaults: {
            ID: 0,
            FirstName: 'Unknown',
            LastName: 'Unknown',
            Age : 0
        }
    });

    // Person Collection
    var PersonCollection = Backbone.Collection.extend({
        model: Person,
        url: '/Person/List/'
     });

     // Person View - el returns the template enclosed within a tr
     var PersonView = Backbone.View.extend({
         template: _.template($('#person-template').html()),
         tagName: "tr",
         initialize: function () {
             console.log('PersonView initialize');
             this.model.bind('change', this.render, this);
             this.model.bind('remove', this.unrender, this);
         },
         render: function () {
             console.log('PersonView render');
             $(this.el).html(this.template(this.model.toJSON()));
             return this;
         },
         unrender: function () {
             console.log('PersonView unrender');
             $(this.el).remove();
             return this;
         },
         events: {
             "click .person-edit": 'EditPerson',
             "click .person-delete": 'DeletePerson'
         },
         EditPerson: function () {
             console.log('PersonView EditPerson');
             this.model.set({ FirstName: 'Unknown' });
             var self = this;
             this.model.save(this.model, { success: function () {
                 $("input:button", $(self.el)).button();
             }});
        },
        DeletePerson: function () {
            console.log('PersonView DeletePerson');
            this.model.destroy();
        }
    });

    // Actual App view
    var AppView = Backbone.View.extend({
        initialize: function () {
            console.log('AppView initialize');
            this.collection.bind('add', this.AppendPerson, this);
        },
        el: '#person-container',
        counter: 3,
        events: {
            "click #btn-create-new-person": "AddNewPerson"
        },
        AddNewPerson: function () {
            console.log('AppView AddNewPerson');
            this.counter++;
            var newPerson = new Person({ ID: this.counter, FirstName: 'Unknown ' + this.counter, LastName: 'Damn ' + this.counter, Age: this.counter });
            this.collection.add(newPerson);
            newPerson.save(newPerson, { success: function () {
                $("input:button", "#person-list").button();
            }});
        },
        AppendPerson: function (person) {
            console.log('AppView AppendPerson');
            var personView = new PersonView({ model: person });
            $(this.el).find('table').append(personView.render().el);
        },
        render: function () {
            console.log('AppView render');
            if (this.collection.length > 0) {
                this.collection.each(this.AppendPerson, this);
            }
            $("input:button", "#person-list").button();
        }
    });

    var persons = new PersonCollection();
    var view = new AppView({ collection: persons });

    persons.fetch({ success: function () {
        console.log('Fetch called');
        view.render();
    }});
});

In the server (my case asp.net MVC with C#):

public class PersonController : Controller
{
    List<Person> Persons = new List<Person> {
        new Person (1, "Federico", "Ponte" , 23),
        new Person (2, "Alejandro", "Drago" , 23),
    };

    // Get Person
    [AcceptVerbs(HttpVerbs.Get)]
    public JsonResult List(int? ID)
    {
        if (ID.HasValue)
        {
            Person PersonResult = Persons.Find(p => p.ID == ID);
            return Json(PersonResult, JsonRequestBehavior.AllowGet);
        }
        return Json(Persons, JsonRequestBehavior.AllowGet);
    }

    // Update Person
    [AcceptVerbs(HttpVerbs.Put)]
    public JsonResult List(int ID, Person UpdatedPerson)
    {
        Person PersonResult = Persons.Find(p => p.ID == ID);
        PersonResult.FirstName = UpdatedPerson.FirstName;
        PersonResult.LastName = UpdatedPerson.LastName;
        PersonResult.Age = UpdatedPerson.Age;
        return Json(PersonResult, JsonRequestBehavior.DenyGet);
    }

    //Add Person
    [AcceptVerbs(HttpVerbs.Post)]
    public JsonResult List(Person CreatePerson)
    {
        Persons.Add(CreatePerson);
        return Json(CreatePerson, JsonRequestBehavior.DenyGet);
    }

    //Delete Person
    [AcceptVerbs(HttpVerbs.Delete)]
    public JsonResult List(int ID)
    {
        Person PersonResult = Persons.Find(p => p.ID == ID);
        Persons.Remove(PersonResult);
        return Json(PersonResult, JsonRequestBehavior.DenyGet);
    }
}
public class Person
{
    public Person(int _ID, string _FirstName, string _LastName, int _Age)
    {
        ID = _ID;
        FirstName = _FirstName;
        LastName = _LastName;
        Age = _Age;
    }

    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

As you can see it is laborious, but it have advantages: highly configurable, it works on cellphones, you use web services. It is easy to scale with it.

It’s pretty tricky at the begging, but once you understand it, is simple.

Knockout

It use the pattern Model-View-View Model. It is quite easy to learn and pretty powerful. It comes with a complete template system and a lot of functions. You need to create a model and modify it directly in order to update the views. It have a pretty good documentation and it’s simple. To know more enter here.

One model would be:

function PersonModel () {
    var self = this;

    self.ID = ko.observable(0);
    self.FirstName = ko.observable('Unknown');
    self.LastName = ko.observable('Unknown');
    self.Age = ko.observable(0);
}

function PersonCollectionModel () {
    var self = this;

    self.Persons = ko.observableArray()
}

$(function (){
    ko.applyBindings(new PersonCollectionModel());
});

The functionality is pretty boring and it is not structurated. An example would be:

function PersonModel () {
    var self = this;

    self.ID = ko.observable(0);
    self.FirstName = ko.observable('Unknown');
    self.LastName = ko.observable('Unknown');
    self.Age = ko.observable(0);

    self.changeAndAlert = function (){
        self.FirstName('Hola');
        self.LastName('Chao');

        alert('Me cambie a ' + self.FirstName() + ' ' + self.LastName());
    }
}
<div>
    <fieldset>
        <legend>Person List - Using Knockout</legend>
        <div id="person-container">
            <input type="button" value= "Create New" class="Add" id="btn-create-new-person" />
            <table id="person-list">
                <tr>
                    <th>
                        Name
                    </th>
                    <th>
                        Description
                    </th>
                    <th>
                    </th>
                </tr>
                <!-- ko foreach : persons -->
                    <tr>
                        <td data-bind="text : FirstName"></td>
                        <td data-bind="text : LastName"></td>
                        <td><input type="button" data-bind="click : changeAndAlert" value="ChangeAndAlert" /></td>
                    </tr>
                <!-- /ko -->
            </table>
        </div>
    </fieldset>
</div>

The rest of the functionality in the case of Backbone is quite easy to do. With a good jQuery knowledge or another library, it just take some Ajax calls and modify the model.

Comparision

Backbone is quite tricky and hard to learn. It gives maintainability and scalability.

I have used more Knockout and it is pretty simple.

At this time I am writting this i don’t have any preferences as before. Both can do the same, the decision is on what you need.

Others

This library is sponsored by Google.

Angular JS

Advertisements

Backbone.js & Knockout

He tenido mucho trabajo y cosas que hacer, que no he podido escribir.

El año pasado mi jefe me pidió que viera las diversas librerías existentes estilo Backbone.js y Knockout. La idea era ver todas las opciones y tomar la más conveniente.

Para ello me puse a leer y buscar bastante información. Además, aproveche de probar cada una y ver como me sentía.  Para ello primero cree una aplicación en Backbone.js y luego observé que tal sería usar Knockout.

El resultado se los comento acá con el código. Así aprenden un poco y elijen el que más les guste.

Backbone.js

Es muy poderoso y usado por muchas personas, pero tiene una curva de aprendizaje un poco fuerte. Se tiene un modelo, con un montón de cosas que hay que definir. Luego, si se siguen las convenciones, al ser modificado, cambia de una vez las vistas. Acá pueden ver mucho mejor todos los detalles.

Lo ideal para trabajar con  él, es tener servicios web de tipo REST.

Crear la aplicación me quitó una tarde y parte de la mañana.

El html:

<div>
    <fieldset>
        <legend>Person List - Using Backbone</legend>
        <div id="person-container">
            <input type="button" value= "Create New" class="Add" id="btn-create-new-person" />
            <table id="person-list">
                <tr>
                    <th>
                        Name
                    </th>
                    <th>
                        Description
                    </th>
                    <th>
                    </th>
                </tr>
            </table>
        </div>
        <script id='person-template' type='text/template'>
            <td><%=ID%></td> <td><%=FirstName%></td> <td><%=LastName%></td> <td><%=Age%></td>
            <td><input type="button" value="Edit" class="person-edit" /> | <input type="button" value="Details" class="person-detail" /> | <input type="button" value="Delete" class="person-delete" /></td>
       </script>
    </fieldset>
</div>

Javascript:

$(function () {

    // Person Model
    var Person = Backbone.Model.extend({
        urlRoot: '/Person/List/',
        initialize: function () {
            console.log('Person initialize');
        },
        defaults: {
            ID: 0,
            FirstName: 'Unknown',
            LastName: 'Unknown',
            Age : 0
        }
    });

    // Person Collection
    var PersonCollection = Backbone.Collection.extend({
        model: Person,
        url: '/Person/List/'
     });

     // Person View - el returns the template enclosed within a tr
     var PersonView = Backbone.View.extend({
         template: _.template($('#person-template').html()),
         tagName: "tr",
         initialize: function () {
             console.log('PersonView initialize');
             this.model.bind('change', this.render, this);
             this.model.bind('remove', this.unrender, this);
         },
         render: function () {
             console.log('PersonView render');
             $(this.el).html(this.template(this.model.toJSON()));
             return this;
         },
         unrender: function () {
             console.log('PersonView unrender');
             $(this.el).remove();
             return this;
         },
         events: {
             "click .person-edit": 'EditPerson',
             "click .person-delete": 'DeletePerson'
         },
         EditPerson: function () {
             console.log('PersonView EditPerson');
             this.model.set({ FirstName: 'Unknown' });
             var self = this;
             this.model.save(this.model, { success: function () {
                 $("input:button", $(self.el)).button();
             }});
        },
        DeletePerson: function () {
            console.log('PersonView DeletePerson');
            this.model.destroy();
        }
    });

    // Actual App view
    var AppView = Backbone.View.extend({
        initialize: function () {
            console.log('AppView initialize');
            this.collection.bind('add', this.AppendPerson, this);
        },
        el: '#person-container',
        counter: 3,
        events: {
            "click #btn-create-new-person": "AddNewPerson"
        },
        AddNewPerson: function () {
            console.log('AppView AddNewPerson');
            this.counter++;
            var newPerson = new Person({ ID: this.counter, FirstName: 'Unknown ' + this.counter, LastName: 'Damn ' + this.counter, Age: this.counter });
            this.collection.add(newPerson);
            newPerson.save(newPerson, { success: function () {
                $("input:button", "#person-list").button();
            }});
        },
        AppendPerson: function (person) {
            console.log('AppView AppendPerson');
            var personView = new PersonView({ model: person });
            $(this.el).find('table').append(personView.render().el);
        },
        render: function () {
            console.log('AppView render');
            if (this.collection.length > 0) {
                this.collection.each(this.AppendPerson, this);
            }
            $("input:button", "#person-list").button();
        }
    });

    var persons = new PersonCollection();
    var view = new AppView({ collection: persons });

    persons.fetch({ success: function () {
        console.log('Fetch called');
        view.render();
    }});
});

Y en el servidor (en mi caso asp.net MVC 3 con C#):

public class PersonController : Controller
{
    List<Person> Persons = new List<Person> {
        new Person (1, "Federico", "Ponte" , 23),
        new Person (2, "Alejandro", "Drago" , 23),
    };

    // Get Person
    [AcceptVerbs(HttpVerbs.Get)]
    public JsonResult List(int? ID)
    {
        if (ID.HasValue)
        {
            Person PersonResult = Persons.Find(p => p.ID == ID);
            return Json(PersonResult, JsonRequestBehavior.AllowGet);
        }
        return Json(Persons, JsonRequestBehavior.AllowGet);
    }

    // Update Person
    [AcceptVerbs(HttpVerbs.Put)]
    public JsonResult List(int ID, Person UpdatedPerson)
    {
        Person PersonResult = Persons.Find(p => p.ID == ID);
        PersonResult.FirstName = UpdatedPerson.FirstName;
        PersonResult.LastName = UpdatedPerson.LastName;
        PersonResult.Age = UpdatedPerson.Age;
        return Json(PersonResult, JsonRequestBehavior.DenyGet);
    }

    //Add Person
    [AcceptVerbs(HttpVerbs.Post)]
    public JsonResult List(Person CreatePerson)
    {
        Persons.Add(CreatePerson);
        return Json(CreatePerson, JsonRequestBehavior.DenyGet);
    }

    //Delete Person
    [AcceptVerbs(HttpVerbs.Delete)]
    public JsonResult List(int ID)
    {
        Person PersonResult = Persons.Find(p => p.ID == ID);
        Persons.Remove(PersonResult);
        return Json(PersonResult, JsonRequestBehavior.DenyGet);
    }
}
public class Person
{
    public Person(int _ID, string _FirstName, string _LastName, int _Age)
    {
        ID = _ID;
        FirstName = _FirstName;
        LastName = _LastName;
        Age = _Age;
    }

    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

Como verán es trabajoso, pero trae sus ventajas: super configurable, sirve para celulares, se tienen los servicios web, etc. Si se quiere escalar, es muy fácil.

Es un poco complicado al comienzo, pero una vez que se entiende y se acostumbra es sencillo.

Knockout

Hace uso del patrón Model-View-View Model. Es muy fácil de aprender y es bastante poderoso. Ya viene con un sistema de templates bastante completo y muchas funciones hechas. Se crea el modelo y basta con modificarlo directamente para que las vistas se actualicen. Tiene buena documentación y no es nada complejo.  Para saber más ingresa aquí.

Un modelo sería:

function PersonModel () {
    var self = this;

    self.ID = ko.observable(0);
    self.FirstName = ko.observable('Unknown');
    self.LastName = ko.observable('Unknown');
    self.Age = ko.observable(0);
}

function PersonCollectionModel () {
    var self = this;

    self.Persons = ko.observableArray()
}

$(function (){
    ko.applyBindings(new PersonCollectionModel());
});

La fastidioso es hacer la funcionalidad: no es tan estructurada. Un ejemplo sería lo siguiente:

function PersonModel () {
    var self = this;

    self.ID = ko.observable(0);
    self.FirstName = ko.observable('Unknown');
    self.LastName = ko.observable('Unknown');
    self.Age = ko.observable(0);

    self.changeAndAlert = function (){
        self.FirstName('Hola');
        self.LastName('Chao');

        alert('Me cambie a ' + self.FirstName() + ' ' + self.LastName());
    }
}
</span></span></span>
<pre><pre><pre><div>
    <fieldset>
        <legend>Person List - Using Knockout</legend>
        <div id="person-container">
            <input type="button" value= "Create New" class="Add" id="btn-create-new-person" />
            <table id="person-list">
                <tr>
                    <th>
                        Name
                    </th>
                    <th>
                        Description
                    </th>
                    <th>
                    </th>
                </tr>
                <!-- ko foreach : persons -->
                    <tr>
                        <td data-bind="text : FirstName"></td>
                        <td data-bind="text : LastName"></td>
                        <td><input type="button" data-bind="click : changeAndAlert" value="ChangeAndAlert" /></td>
                    </tr>
                <!-- /ko -->
            </table>
        </div>
    </fieldset>
</div>

El resto de la funcionalidad implementada en el caso de Backbone.js es sencilla de hacer. Con saber usar bien jQuery o alguna otra librería basta con hacer las llamadas ajax correspondientes y modificar el modelo.

Comparación

Personalmente me gustó mucho Backbone.js. Me sentí más cómodo con él. La forma en que incita a uno a programar da una fácil mantenibilidad y pone de forma sencilla el poder escalar. Es un poco complejo de agarrar al comienzo  pero luego se hace fácil.

En cuanto Knockout es el que más he usado y es bastante sencillo. Se aprende fácilmente y es muy simple.

Ambos son igual de capaces, la decisión es por gusto.

Otras

Hace poco me conseguí me con esta librería patrocinada por Google.

Angular JS

Google Maps

Estos días estuve un rato largo lideando con Google Maps para aprender de él y ver sus posibilidades.

El ejemplo que hice es simular un mapa de los Estados Unidos donde se pueden ver diversas propiedades.

Antes que nada hay que obtener un API Key mediante una cuenta de google y de esta forma poder usarlo.

Otra cosa de las cosas usadas fue JQuery. Ya teniendo estás dos cosas se puede echar cógido.

Se puede usar Google Maps de dos formas: una llamada totalmente secuencial y otra asíncrona. El ejemplo usa está última:

En el Javascript:

function loadScript()
{
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "http://maps.googleapis.com/maps/api/js?key=APIKEYo&sensor=false&callback=initialize";
    document.body.appendChild(script);
}
function initialize()
{
    var myOptions = {
        zoom: 4,
        center: new google.maps.LatLng(38.68551, -99.22852),
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        minZoom: 4,
        maxZoom: 16
    };
    map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
}
$(document).ready(function () {
    loadScript();
});

En el HTML:

<div id="map_canvas"></div>

De acá uno puede empezar a complicar más la cosa. Lo siguiente sería agregar una imagen donde se hizo click en el map, que está al hacerle click salga una ventana de información y ademas debajo del mapa salga un div con un link para eliminarla.

Dentro de la función initialize se coloca lo siguiente:

    image = new google.maps.MarkerImage('path/image.png',
    new google.maps.Size(20, 32),
    new google.maps.Point(0, 0),
    new google.maps.Point(0, 32));
google.maps.event.addListener(map, 'click', function (event) {
    addMarker(event.latLng);
});

La función addMarker es la siguiente:

function addMarker(location, name)
{
    if (name == undefined) name = "default";
    if (markers[name] == undefined) markers[name] = [];
    var marker_number = markers[name].length;
    var contentString = '<div id="marker_info"><h2>Excelente localidad</h2></div>';
    var infowindow = new google.maps.InfoWindow({
       content: contentString,
       maxWidth: 150
    });
    var marker = new google.maps.Marker({
        position: location,
        map: map,
        icon: image,
        title: location.name
     });
    google.maps.event.addListener(marker, 'mouseover', function () {
        infowindow.open(map, marker);
    });
    google.maps.event.addListenerOnce(marker, 'click', function () {
        $('#marks').append('<a href="#" marker="' + marker_number + '">Eliminar- ' + marker_number + '</a>');
        $('[marker=' + marker_number + ']').click(function () {
             removeMarker("default", marker_number);
            $(this).hide();
        });
    });
    markers[name].push(marker);
}
function removeMarker(name, number)
{
    markers[name][number].setMap(null);
    markers[name][number] = null;
    swap(markers[name], number, markers[name].length - 1);
    markers[name].length -= 1;
}
function swap(array, a, b)
{
    var aux = array[a];
    array[a] = array[b];
    array[b] = aux;
}

Markers va a ser un diccionario que mapea a arreglos de marcadores.  Debajo del div donde está el mapa de google se coloca el siguiente:

<div id="marks">Haz click para eliminar </div>

Con lo siguiente se va a poder agregar marcadores en el mapa que muestran información y poder eliminarlos.

Vamos a complicar esto un poco más. Creemos una función para agregar polígonos y otra que simula búsquedas. Para ello agregamos las siguientes funciones:

En el Javascript:

function searchAddress(address, remove)
{
    var houses = searchResults[address];
    if (houses == undefined) {
        geocoder.geocode({ 'address': address }, function (results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                map.setCenter(results[0].geometry.location);
                alert(results[0].geometry.location.latitude + ', ' + results[0].geometry.location.longitude);
            }
            else
                alert("Geocode was not successful for the following reason: " + status);
        });
    }
    else if (houses.length == 0) return;
    else
    {
        var maxX = -360.0,
        maxY = -360.0,
        minX = 360.0,
        minY = 360.0;
        var coordinates = []
        if (remove)
        {
            for (name in searchResults)
            {
                removeMarkers(name);
                removePolygon(name);
            }
        }
        for (house in houses)
        {
            if (houses[house].longitude < minX) minX = houses[house].longitude;
            if (houses[house].longitude > maxX) maxX = houses[house].longitude;
            if (houses[house].latitude < minY) minY = houses[house].latitude;
            if (houses[house].latitude > maxY) maxY = houses[house].latitude;
            addHouseMarker(address, houses[house]);
            coordinates.push(new google.maps.LatLng(houses[house].latitude, houses[house].longitude));
         }
         addHousesPolygon(address, coordinates);
         var southWest = new google.maps.LatLng(minY, minX),
         northEast = new google.maps.LatLng(maxY, maxX);
         var bounds = new google.maps.LatLngBounds(southWest, northEast);
         map.fitBounds(bounds);
         var center_location = new google.maps.LatLng((minY + maxY) / 2, (minX + maxX) / 2);
         map.setCenter(center_location);
     }
}
function searchAllAddress()
{
    var maxX = -360.0,
    maxY = -360.0,
    minX = 360.0,
    minY = 360.0;
    for (address in searchResults)
    {
        var houses = searchResults[address];
        var coordinates = []
        for (house in houses)
        {
            if (houses[house].longitude < minX) minX = houses[house].longitude;
            if (houses[house].longitude > maxX) maxX = houses[house].longitude;
            if (houses[house].latitude < minY) minY = houses[house].latitude;
            if (houses[house].latitude > maxY) maxY = houses[house].latitude;
            addHouseMarker(address, houses[house]);
            coordinates.push(new google.maps.LatLng(houses[house].latitude, houses[house].longitude));
        }
        addHousesPolygon(address, coordinates);
    }
    var southWest = new google.maps.LatLng(minY, minX),
    northEast = new google.maps.LatLng(maxY, maxX);
    var bounds = new google.maps.LatLngBounds(southWest, northEast);
    map.fitBounds(bounds);
    var center_location = new google.maps.LatLng((minY + maxY) / 2, (minX + maxX) / 2);
    map.setCenter(center_location);
}
function removeMarker(name, number)
{
    markers[name][number].setMap(null);
    markers[name][number] = null;
    swap(markers[name], number, markers[name].length - 1);
    markers[name].length -= 1;
}
function removePolygon(name)
{
    if (polygons[name] == undefined) return;
    polygons[name].setMap(null);
    polygons[name] = null;
}

A la sección cuando el documento está listo:

$('#search_address_button').click(function () {
    var address = $('#search_address_text').val();
    searchAddress(address);
});
$('#search_address_text').click(function (event) {
    var keyDown = (event.keyCode ? event.keyCode : event.which);
    if (keyDown == 13)
        searchAddress();
});
$('.state_address').click(function () {
    var address = $(this).attr('address');
    if (address == "all")
        searchAllAddress();
    else
        searchAddress(address, true);
});

Y en el HTML:

<div id="address_selector">
    <div id="state_selector">
        <a class="state_address" address="california" href="#">California</a>
        <a class="state_address" address="washington" href="#">Washington DC</a>
        <a class="state_address" address="florida" href="#">Florida</a>
        <a class="state_address" address="ny" href="#">New York</a>
        <a class="state_address" address="utah" href="#">Utah</a>
        <a class="state_address" address="la" href="#">Los Angeles</a>
        <a class="state_address" address="default" href="#">Mines</a>
        <a class="state_address" address="all" href="#">All</a>
    </div>
    <div id="search_address">
        <input type="text" name="search_address_text" id="search_address_text"/> <input id="search_address_button" type="button" value="Search"/>
    </div>
</div>

searchResults es un arreglo que van a ver en los fuentes que publique con datos que yo mismo genere para probar.

Acá se me es algo complicado explicar el código, no encuentro como poner el código mejor y lo otro que sucede es que es mucho escribir y no tengo mucho tiempo.

Les colocó los dos archivos para que lo vean con más detenimiento y lo prueben si desean:

Javascript:

http://pastebin.com/mRu5H2kG

HTML:

http://pastebin.com/B3Z4GQNB