Search is important to most web apps. If you have a database with more than, say 100 records, you will want to consider some search, and possibly sort and filter, facility on your site. Since our app has no access to a real server, we will be using a Mock server that returns a random number of records generated with the help of a quote from Cicero, a Roman philosopher.
The page has two <script> templates. The search-results-template renders all search results and the search-result-template renders an individual record. Just as we did in the DataEntry example, the template token markers were changed from <% = %> to double braces {{ and }} using a simple _.templateSettings call.
The Search app code is below:
var Patterns = { // ** namespace pattern namespace: function (name) { // ** single var pattern var parts = name.split("."); var ns = this; // ** iterator pattern for (var i = 0, len = parts.length; i < len; i++) { // ** || idiom ns[parts[i]] = ns[parts[i]] || {}; ns = ns[parts[i]]; } return ns; } }; // ** namespace pattern // ** revealing module pattern // ** singleton pattern Patterns.namespace("InAction").Search = (function () { // change template token markers to use double curly braces (just like Mustache): _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g, evaluate: /\{\{(.+?)\}\}/g }; // ** extend pattern // ** option hash idiom var Book = Backbone.Model.extend({ }); // ** extend pattern // ** option hash idiom var Books = Backbone.Collection.extend({ model: Book, url: "search/:q" }); // ** extend pattern // ** option hash idiom var Views = Backbone.View.extend({ // ** chaining pattern template: _.template($('#search-results-template').html()), el: $("#searchresults"), // ** init pattern initialize: function (obj) { this.query = obj.query; this.books = new Books(); // ** observer pattern this.books.on('reset', this.render, this); // will trigger render this.books.fetch({ data: { q: this.query } }); }, render: function (eventName) { this.$el.empty(); // ** iterator pattern this.books.each(function (book) { this.$el.append(new View({ model: book }).render().el); }, this); return this; } }); // ** extend pattern // ** option hash idiom var View = Backbone.View.extend({ // ** chaining pattern template: _.template($('#search-result-template').html()), render: function (eventName) { this.$el.html(this.template(this.model.toJSON())); return this; } }); // Random text helpers var randomInt = function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // cicero: used in random string generator var cicero = "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"; var ciceroLen = cicero.length; // server Mock fauxServer.addRoute("searchBook", "search/:q", "GET", function (context) { var q = context.data.q; var count = randomInt(6, 35); var array = []; // ** iterator pattern for (var i = 0; i < count; i++) { var from = randomInt(1, ciceroLen - 31); var to = from + randomInt(10, 30); var append = cicero.substring(from, to).replace(/^\s+|\s+$/g, ''); // sub and trim array.push({ id: i, title: q + " " + append }) } context.data = array; return context.data; }); var start = function () { // focus on search text box and hook up enter keydown event // ** chaining pattern $("#search").focus().keydown(function (event) { if (event.keyCode == 13) { $("#searchbutton").click(); } }); // button click triggers search // ** observer pattern $("#searchbutton").on('click', function () { var q = $("#search").val(); if (q) { new Views({ query: q }); } else { alert("Please enter a search string"); $("#search").focus(); } }); // ** observer pattern $("#reset").on('mousedown', function () { // ** chaining pattern $("#search").val("").focus(); $("#searchresults").empty(); }); } return { start: start }; }()); $(function () { // ** facade pattern Patterns.InAction.Search.start(); });
The patterns and idioms used in this code are:
All of these patterns have already been described in the previous examples. This shows that the same patterns are commonly used over and over again. Once you develop the skills to write patterns they will become second nature, almost as if they were native to the JavaScript language.