• Wall.js

    A multiple search built with Backbone and jQuery UI autocomplete
    LIVE DEMO Search for: java, php, coldfusion, coffeescript, javascript, asp, ruby
  • ¶

    Style: all private methods & objects start with underscore (_)

    (function () {
  • ¶

    Global namespace

    	var wall = window.wall = {
  • ¶

    This is the entry point to the application. It takes care of instantiating the views, models, controllers, etc.

    wall.start()
    		start: function() {
    			var searchCollection = new wall.Languages;
    			var view = new wall.View({collection: searchCollection});
    		},
  • ¶

    demo data

    		demoData: [ "java", "php", "coldfusion", "coffeescript", "javascript", "asp", "ruby" ]
    	};
  • ¶

    View that holds the text box and different brick views

    	wall.View = Backbone.View.extend({
    		inputId: 'wall-input',
    
    		initialize: function() {
    			this._bricks = new Backbone.Collection
    			this._input  = $("#" + this.inputId);
    			this._initContainer();
    			this._initAutocomplete();
    			this._bind();
    			var brickView = new wall.BrickView({collection: this._bricks});
    			this._input.focus();
    		},
  • ¶

    The container of the wall is a UL element. The container of a brick is a LI element, and this will be a separate view The input will be stored within its own brick The final markup should be similar to:

        <div class="wall">
            <ul class="wall-ul">
                <li class="wall-brick">
                    <span class="wall-brick-name">Name</span>
                    <a href="#remove" class="wall-brick-remove">x</a>
                </li>
                <li class="wall-brick">
                    <input type="text" class="wall-input" />
                </li>
            </ul>
        </div>
    		_initContainer: function() {
    			this._input
    				.addClass('wall-input')
    				.wrap(
    					'<div class="wall">' +
    						'<ul class="wall-ul ui-helper-clearfix">' + 
    							'<li class="wall-brick wall-brick-input"></li>' +
    						'</ul>' + 
    					'</div>'
    				);
  • ¶

    Store these in the global object so they can be accessed by other objects too

    			wall.container = this._input.parents('div.wall');
  • ¶

    Add a search icon on the left, for visual cue

    			wall.inputListItem = this._input.parent().before('<li class="wall-brick-search"></li>');
    		},
  • ¶

    Typical jQuery UI autocomplete options, apart from source which is delegated to the search collection; select is triggered when an item in the list selected; the position is shifted down as the container is padded

    		_initAutocomplete: function() {
    			var self = this;
    			var options = {
    				position	: { at: "left bottom+7" },
    				appendTo  : wall.container,
    				source		: function(request, response) { self._getData(request, response); },
    				focus			: function() { return false; },
    				select		: function(e, ui) { self._select(e, ui); }
    			};
    			this._input.autocomplete(options);
    		},
    
    		_getData: function(request, response) {
    			var languages = this.collection.fetch(request.term);
    			var models = languages.map(function(language) {
    				return {
    					id:    language.get('id'),
    					label: language.get('name'),
    					value: language.get('name')
    				};
    			});
    			response(models);
    		},
    
    		_select: function(e, ui) {
    			e.preventDefault();
    			this._bricks.add({
    				id: ui.item.id,
    				name: ui.item.label
    			});
    			this._input.val('').focus()
    			return false;
    		},
    
    		_bind: function() {
    			var self = this;
  • ¶

    This selects the text box anytime that the user clicks on the entire box

    			wall.container.click(function() { self._input.focus(); });
    		}
    	});
    
    	wall.BrickView = Backbone.View.extend({
    		_markup: '<li class="wall-brick" id="wall-brick-<%= id %>"><span class="wall-brick-name"><%= name %></span><a href="#remove" data-id="<%= id %>" class="wall-brick-remove"></a></li>',
    
    		initialize: function() {
    			var self = this;
    			this.collection.on('add', this.render, this);
    			this.collection.on('remove', this._remove, this);
    			wall.container.click(function(e) { self._onremove(e); });
    		},
    
    		render: function(model) {
    			if (!wall.inputListItem) return;
    			this._tmpl = this._tmpl || _.template(this._markup);
    			html = this._tmpl(model.toJSON());
    			wall.inputListItem.before(html);
    			return this;
    		},
    
    		_onremove: function(e) {
    			if (e.target.className !== 'wall-brick-remove') return;
    			e.preventDefault();
    			var id = $(e.target).data('id');
    			var model = this.collection.get(id);
    			this.collection.remove(model);
    		},
    
    		_remove: function(model) {
    			var li = wall.container.find('#wall-brick-' + model.get('id'));
    			if (li && li.length) {
    				li.fadeOut(function() {
    					li.remove();
    				});
    			}
    		}
    	});
    
    	wall.Languages = Backbone.Collection.extend({
    		model: wall.Language,
  • ¶

    Populate the collection with the dummy data

    		initialize: function() {
    			models = $.map(wall.demoData, function(language, i) {
    				return new wall.Language({
    					id: i + 1,
    					name: language
    				});
    			})
    			this.reset(models);
    		},
    
    		fetch: function(term) {
    			var reg = new RegExp(term, 'i');
    			return this.filter(function(model) {
    				return reg.test(model.get('name'));
    			});
    		}
    	});
    
    	wall.Bricks = Backbone.Collection.extend({
    		model: wall.Language
    	});
    
    	wall.Language = Backbone.Model.extend({
    	});
  • ¶

    start the app on document ready

    	$(function() {
    		wall.start()
    	});
    		
    
    }).call(this);