Project

General

Profile

Statistics
| Branch: | Revision:

colonymech / docs / www / colonyscout / internal / js / marumushi.widget.querybox.js @ f59acf11

History | View | Annotate | Download (11.9 KB)

1
/**
2
 * QueryBox 1.0
3
 * 
4
 * @author marcos weskamp, marcos@marumushi.com
5
 * @published Nov 30, 2008.
6
 * Copyright 2008, Marcos Weskamp marcos@marumushi.com
7
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License. 
8
 * http://creativecommons.org/licenses/by/3.0/
9
 *  
10
 * QueryBox allows you to easily create a Live Search Input form lets users refine 
11
 * a search query and continuously show all valid results as they type.
12
 * This widget is built on top of the Dojo javascript framework.
13
 */
14
dojo.declare("marumushi.widget.QueryBox", null, {
15
        
16
        //what should we display by default
17
        defaultMessage                                :'Search...',
18
        
19
        //displayed when no resuts are found                                
20
        noResultsMessage                        :'no results where found for ',
21
        
22
        //url to your results script
23
        queryURL                                        :'search?q=',
24
        
25
        //css class to use in this QueryBox        
26
        cssClass                                        :'QueryBox',
27
        
28
        // when focusing on the input field and hit enter where should we go. 
29
        // an empty string means go back to the same page we where at.                                        
30
        searchURL                                        :'',
31
        
32
        //(milis) time to wait to query backend script                                                                                
33
        keyDownTimeout                                :200, 
34
        
35
        // (milis) how long until we give up waiting for results to come back.                                                
36
        requestTimeout                                :5000, 
37
        
38
        //should we submit the query when user hasn't selected anything yet.                                                        
39
        submitOnInputBlur                        :true,
40
        
41
        //split results based on categories
42
        splitResultsOnCategories        :true,
43
        
44
        //private members you shouldn't need to change any of these.
45
        queryInputClass                                :'QueryInput',
46
        resultContainerClass                :'ResultBox',
47
        listItemClass                                :'ListItem',
48
        listItemSelectedClass                :'Selected',
49
        noResultsClass                                :'NoResults',
50
        inputFieldID                                :'lsquery_search_input',
51
        resultContainerID                        :'lsquery_result',
52
        selectedContainerID                 :'lsquery_selected',
53
        formID                                                :'lsquery_form',
54
        lastQuery                                        :"",
55
        list                                                :[],
56
        selectedCursor                                :-1,
57
        selectedItem                                :null,        
58
        timer                                                :null,
59
        activeRequest                                :null,
60
        currentQuery                                :null,
61
        queryBoxContainerID                        :null,
62
        
63
        // --------------------- PUBLIC METHODS ---------------------
64
        /**
65
         * overrite this method in the instance if you want to customize the results box markup
66
         * @param item the curren item data to render
67
         * @param lastQuery the lastQuery submited to the server.
68
         * */
69
        getItemMarkup:function(item,lastQuery){
70
                var title         = this.highlightString(item.title,lastQuery);
71
                var descr         = item.description!='' ? '<p>'+this.highlightString(item.description,lastQuery)+'</p>' : '';
72
                var line         = '<div class="'+this.listItemClass+'"><a href="'+item.link+'"><h2>'+title+'</h2>'+descr+'</a></div>';
73
                return line;
74
        },
75
        /**
76
         * overrite this method in the instance if you want to customize the results box markup
77
         * @param data is an array of item objects returned from the server
78
         */        
79
        getDropDownMarkup:function(data){
80
                var str = '';
81
                str+= '<div class="'+this.resultContainerClass+'">';
82
                str+= '<div class="Inside">';
83
                var categories={};
84
                var len = data.length;
85
                //split results by categories, depending on the type property
86
                if(this.splitResultsOnCategories){
87
                        for (var n=0;n<len;n++){
88
                                var item         = data[n];
89
                                var line        = this.getItemMarkup(item,this.lastQuery);
90
                                if(categories[item.category]){
91
                                        categories[item.category]+=line;
92
                                }else{
93
                                        categories[item.category]=line;
94
                                }
95
                                //str+=line;
96
                        }
97
                        //now build them nicely.
98
                        for (var category in categories){
99
                                str+='<div class="header"><h1>'+category+'</h1>'+categories[category]+'</div>';
100
                        }
101
                }else{
102
                        for (var n=0;n<len;n++){
103
                                var item         = data[n];
104
                                var line        = this.getItemMarkup(item,this.lastQuery);
105
                                str+=line;
106
                        }
107
                }
108
                str+='</div>'; //Inside
109
                str+='</div>'; //ResultBox
110
                return str;
111
        },
112
        /**
113
         * overrite this method in the instance if you want to customize the no results marukup
114
         * @param lastQuery is the last query submited.
115
         */
116
        getNoResultsMarkup:function(lastQuery){
117
                var str='';
118
                str+= '<div class="'+this.resultContainerClass+'">';
119
                str+= '<div class="Inside">';
120
                str+= '<div class="'+this.noResultsClass+'"><p>'+this.noResultsMessage+'<b>"'+lastQuery+'"</b></p></div>';
121
                str+= '<div>'; //Inside
122
                str+= '</div>'; //ResultBox
123
                return str;
124
        },
125
        
126
        // --------------------- PRIVATE METHODS ---------------------
127
        constructor:function(url,divID,displayQuery){
128
                if(url){
129
                        this.queryURL = url;
130
                }else{
131
                        str =         "ERROR instantiating QueryBox.\n"+
132
                                        "When instantiating QueryBox you must provide a URL to the script that will return the search results to QueryBox.\n"+
133
                                        "The URL should be the path to the script we'll use to query for results.\n" +
134
                                          "ex: var qbox = new marumushi.widget.LiveSearchQueryBox('http://domain.com/search.php?q=','my_div_id');\n";
135
                        console.error(str);
136
                }
137
                if(divID){
138
                        this.queryBoxContainerID = divID;
139
                }else{
140
                        str =         "ERROR instantiating QueryBox.\n"+
141
                                        "When instantiating QueryBox you must provide the ID where QueryBox will be rendered.\n"+
142
                                          "ex: var qbox = new marumushi.widget.LiveSearchQueryBox('http://domain.com/search.php?q=','my_div_id');\n";
143
                        console.error(str);
144
                }
145
                if(displayQuery){
146
                        this.currentQuery = displayQuery;
147
                }
148
                //make all these guys unique based on its own uniqueID
149
                var id = this.queryBoxContainerID+"_";
150
                this.inputFieldID                         = id+this.inputFieldID;
151
                this.resultContainerID                 = id+this.resultContainerID;
152
                this.selectedContainerID         = id+this.selectedContainerID;
153
                this.formID                                        = id+this.formID;
154
                var scope = this;
155
                //hang on to dojos onload event, then initialize.
156
                dojo.addOnLoad(function(){scope.init()});
157
        },
158

    
159
        init:function(){
160
                //console.log('init',this.queryBoxContainerID,dojo.byId(this.queryBoxContainerID));
161
                if(this.render()){
162
                        var input = dojo.byId(this.inputFieldID);
163
                        var scope = this;
164
                        if(input){
165
                                input.setAttribute("autocomplete","off");
166
                                dojo.connect(input, "onkeydown", this, this.onKeyPress );
167
                                dojo.connect(input, "focus", this, this.onTextInputFocus );
168
                                dojo.connect(input, "blur", this, this.onTextInputBlur );
169
                                input.value = this.currentQuery ? this.currentQuery : this.defaultMessage;
170
                                //override submit event, use our own.
171
                                dojo.connect( dojo.byId(this.formID), 'onsubmit',function(e){ dojo.stopEvent(e);scope.onFormSubmit(); } );                                
172
                        }else{
173
                                console.error('search div not found');
174
                        }
175
                }else{
176
                        console.error('oh no, a fatal error happened while trying to render this box!');
177
                }
178
        },
179

    
180
        render:function(){
181
                var e = dojo.byId(this.queryBoxContainerID);
182
                if(e){
183
                        e.innerHTML = this.getMarkup();
184
                        return true;
185
                }else{
186
                        console.error(this.queryBoxContainerID+' div not found! you need this div to render a QueryBox.');
187
                        return false;
188
                }
189
        },
190

    
191
        getMarkup:function(){
192
                var value = this.currentQuery ? value='value="'+this.currentQuery+'" ': '';
193
                var markup = 
194
                        '<div class="'+this.cssClass+'">'+
195
                                '<div class="'+this.queryInputClass+'">'+
196
                                   ' <form method="get" action="'+this.searchURL+'" id="'+this.formID+'" onsubmit="dojo.stopEvent(arguments[0]);return false;">'+
197
                                            '<div class="Input"><input type="text" name="q" id="'+this.inputFieldID+'" class="TextInput" '+value+'/></div>'+
198
                                            '<div class="Input Last"><input type="submit" value="Search" class="Button"/></div>'+
199
                                    '</form>'+
200
                            '</div>'+
201
                                '<div id="'+this.resultContainerID+'" style="display:none;"></div>'+
202
                        '</div>';
203
                return markup;
204
        },
205

    
206
        setQueryValue:function(query){
207
                var input = dojo.byId(this.inputFieldID);
208
                if(input){
209
                        input.value = query;
210
                }else{
211
                        console.error('querybox has not been initialized yet');
212
                }
213
        },
214

    
215
        onFormSubmit:function(){
216
                var selected = dojo.byId(this.selectedContainerID);
217
                if(selected){
218
                        //if you changed the item markup, you will need to change the way you get to the href here too.
219
                        var url = selected.firstChild.getAttribute("href");
220
                        window.location = url;
221
                        return false;
222
                }else{
223
                        //alert(this.searchURL);
224
                        if(this.submitOnInputBlur){
225
                                dojo.byId(this.formID).submit();
226
                        }else{
227
                                return false;
228
                        }
229
                }
230
        },
231

    
232
        onKeyPress:function(event){
233
                //console.log(event.keyCode);
234
                switch(event.keyCode){
235
                        //key down
236
                        case 40 : this.selectPrevious(event); break;
237
                        //key up
238
                        case 38 : this.selectNext(event); break;
239
                        //escape
240
                        case 27 : this.hide(); break;
241
                        //anything else
242
                        default : this.start(); break;
243
                }
244
        },
245

    
246
        selectNext:function(event){
247
                if (!this.selectedItem) {
248
                        this.selectedCursor = this.list.length-1;
249
                } else {
250
                        this.selectedCursor--;
251
                }
252
                this.selectIndex(this.selectedCursor);
253
                if (!dojo.isIE) { event.preventDefault(); }
254
        },
255
        
256
        selectPrevious:function(event){
257
                if (!this.selectedItem) {
258
                        this.selectedCursor = 0;
259
                } else {
260
                        this.selectedCursor++;
261
                }
262
                this.selectIndex(this.selectedCursor);
263
                if (!dojo.isIE) { event.preventDefault(); }
264
        },
265
        
266
        selectIndex:function(index){
267
                if (this.selectedItem) { 
268
                        this.selectedItem.removeAttribute("id");
269
                        this.selectedItem.className = this.listItemClass;
270
                }
271
                this.selectedItem = this.list[index];
272
                if (this.selectedItem) { 
273
                        this.selectedItem.setAttribute("id",this.selectedContainerID); 
274
                        this.selectedItem.className = this.listItemClass+' '+this.listItemSelectedClass;
275
                }
276
        },
277
        
278
        sendRequest:function(){
279
                var scope=this;
280
                var query = dojo.byId(this.inputFieldID).value;
281
                this.lastQuery = query;
282
                if(this.activeRequest){
283
                        this.activeRequest.cancel();
284
                }
285
                //nothing entered. hide the form
286
                if ( query == "" ) {
287
                        this.hide();
288
                        return false;
289
                }
290
                this.displayLoader(true);
291
                //send a new request
292
                //queryURL should be the path to the script we'll use to query for results.
293
                //ex: "http://domain.com/search.php?q="
294
                this.activeRequest = dojo.xhrGet( { 
295
                    url: this.queryURL +query, 
296
                    handleAs: "json",
297
                    timeout: this.requestTimeout, // millis
298
                    load: function(response, ioArgs) { 
299
                              scope.onRequestData(response.results);
300
                              return response;
301
                    },
302
                        error: function(response, ioArgs) {
303
                                console.error("HTTP status code: ", ioArgs.xhr.status); 
304
                                return response;
305
                        }
306
            });
307
        },
308
        /**
309
         * TODO:
310
         * probably want to switch visibility to on/off. loading is not good.
311
         */
312
        displayLoader:function(value){
313
                var path = '../images/querybox/';
314
                var imageURL = value ? 'ajax-loader.gif' : 'search-icon-light.gif';
315
                dojo.byId(this.inputFieldID).style.backgroundImage='url('+path+imageURL+')';
316
        },
317

    
318
        highlightString:function(str,query){
319
                /*
320
                        Grrr I hate regex!
321
                        this should be way more efficient with something like this:
322
                        var regex = new RegExp("(.+?)"+query+"(.+?)","g");  
323
                        str.replace(regex,'$1<b>$2</b>$3'); 
324
                        return str;
325
                        but I just can't get it going properlly. any ideas?
326
             */
327
            var n = str.indexOf(query);
328
                if(n>=0){
329
                        before = str.substr(0,n);
330
                        word   = str.substr(n,query.length);
331
                        after  = str.substr(n+query.length);
332
                        str    = before+'<b>'+word+'</b>'+after;
333
                        //console.log(str);
334
                }
335
                return str;
336
             
337
        },
338
        
339
        onRequestData:function(data){
340
                this.displayLoader(false);
341
                var len         = data.length;
342
                //console.log(len);
343
                var markup         = len == 0 ? this.getNoResultsMarkup(this.lastQuery) : this.getDropDownMarkup(data);
344
                var container = dojo.byId(this.resultContainerID);
345
                if(container){
346
                        container.innerHTML = markup;
347
                        //make the container visible
348
                        container.style.display = "block";
349
                        //create a list of all item divs so we can loop over them on keypress
350
                        var queryClass = '#'+this.resultContainerID+" ."+this.listItemClass;
351
                        this.list = dojo.query(queryClass);
352
                        this.selectedCursor = 0;
353
                }else{
354
                        console.error('container:'+this.resultContainerID+'was not found.')
355
                }
356
        },
357
        
358
        hide:function() {
359
                this.selectIndex(-1);
360
                dojo.byId(this.resultContainerID).style.display = "none";
361
        },
362
        
363
        onTextInputBlur:function(){
364
                var input = dojo.byId(this.inputFieldID);
365
                if(input.value == '' ){input.value=this.defaultMessage};
366
                var scope = this;
367
                window.setTimeout(function(){scope.hide();},400);
368
        },
369
        
370
        onTextInputFocus:function(){
371
                var input = dojo.byId(this.inputFieldID);
372
                if(input.value == this.defaultMessage ){
373
                        input.value='';
374
                }else{
375
                        input.select();
376
                }
377
        },
378
        
379
        start:function() {
380
                var scope = this;
381
                if (scope.timer) {
382
                        window.clearTimeout(scope.timer);
383
                }
384
                scope.timer = window.setTimeout(function(){scope.sendRequest();},scope.keyDownTimeout);
385
        }
386
});
387

    
388

    
389