~ubuntu-branches/ubuntu/vivid/horizon/vivid-updates

« back to all changes in this revision

Viewing changes to pkg/magic_search/data/magic_search.js

  • Committer: Package Import Robot
  • Author(s): Corey Bryant
  • Date: 2015-04-30 15:46:00 UTC
  • mto: (87.1.1 vivid-proposed) (0.15.1)
  • mto: This revision was merged to the branch mainline in revision 87.
  • Revision ID: package-import@ubuntu.com-20150430154600-1zms8pm8yccw8n1h
Tags: upstream-2015.1.0/xstatic
Import upstream version 2015.1.0, component xstatic

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * @fileOverview Magic Search JS
 
3
 * @requires AngularJS
 
4
 *
 
5
 */
 
6
 
 
7
// Allow the module to be pre-defined with additional dependencies
 
8
try{
 
9
    angular.module('MagicSearch');
 
10
} catch (exception) {
 
11
    angular.module('MagicSearch', []);    
 
12
}
 
13
 
 
14
angular.module('MagicSearch')
 
15
    .directive('magicSearch', function($compile) {
 
16
        return {
 
17
            restrict: 'E',
 
18
            scope: {
 
19
                facets_param: '@facets',
 
20
                filter_keys: '=filterKeys',
 
21
                strings: '=strings'
 
22
            },
 
23
            templateUrl: function (scope, elem) {
 
24
                return elem.template;
 
25
            },
 
26
            controller: function ($scope, $timeout) {
 
27
                $scope.promptString = $scope.strings['prompt'];
 
28
                $scope.currentSearch = [];
 
29
                $scope.initSearch = function() {
 
30
                    if (typeof $scope.facets_param === 'string') {
 
31
                        // Parse facets JSON and convert to a list of facets.
 
32
                        var tmp = $scope.facets_param.replace(/__apos__/g, "\'").replace(/__dquote__/g, '\\"').replace(/__bslash__/g, "\\");
 
33
                        $scope.facetsObj = JSON.parse(tmp);
 
34
                    }
 
35
                    else {
 
36
                        // Assume this is a usable javascript object
 
37
                        $scope.facetsObj = $scope.facets_param;
 
38
                    }
 
39
                    $scope.facetsSave = $scope.copyFacets($scope.facetsObj);
 
40
                    $scope.initFacets();
 
41
                };
 
42
                $scope.initFacets = function() {
 
43
                    // set facets selected and remove them from facetsObj
 
44
                    var initialFacets = window.location.search;
 
45
                    if (initialFacets.indexOf('?') === 0) {
 
46
                        initialFacets = initialFacets.slice(1);
 
47
                    }
 
48
                    initialFacets = initialFacets.split('&');
 
49
                    if (initialFacets.length > 1 || initialFacets[0].length > 0) {
 
50
                        $timeout(function() {
 
51
                            $scope.strings['prompt'] = '';
 
52
                        });
 
53
                    }
 
54
                    angular.forEach(initialFacets, function(facet, idx) {
 
55
                        var facetParts = facet.split('=');
 
56
                        angular.forEach($scope.facetsObj, function(value, idx) {
 
57
                            if (value.name == facetParts[0]) {
 
58
                                if (value.options === undefined) {
 
59
                                    $scope.currentSearch.push({'name':facet, 'label':[value.label, facetParts[1]]});
 
60
                                    // allow free-form facets to remain
 
61
                                }
 
62
                                else {
 
63
                                    angular.forEach(value.options, function(option, idx) {
 
64
                                        if (option.key == facetParts[1]) {
 
65
                                            $scope.currentSearch.push({'name':facet, 'label':[value.label, option.label]});
 
66
                                            if (value.singleton === true) {
 
67
                                                $scope.deleteFacetEntirely(facetParts);
 
68
                                            }
 
69
                                            else {
 
70
                                                $scope.deleteFacetSelection(facetParts);
 
71
                                            }
 
72
                                        }
 
73
                                    });
 
74
                                }
 
75
                            }
 
76
                        });
 
77
                    });
 
78
                    if ($scope.textSearch !== undefined) {
 
79
                        $scope.currentSearch.push({'name':'text='+$scope.textSearch, 'label':[$scope.strings['text'], $scope.textSearch]});
 
80
                    }
 
81
                    $scope.filteredObj = $scope.facetsObj;
 
82
                };
 
83
                $scope.copyFacets = function(facets) {
 
84
                    var ret = []
 
85
                    for (var i=0; i<facets.length; i++) {
 
86
                        var facet = Object.create(facets[i]);
 
87
                        if (facets[i].options !== undefined) {
 
88
                            facet.options = [];
 
89
                            for (var j=0; j<facets[i].options.length; j++) {
 
90
                                facet.options.push(Object.create(facets[i].options[j]));
 
91
                            }
 
92
                        }
 
93
                        ret.push(facet);
 
94
                    }
 
95
                    return ret;
 
96
                }
 
97
                // removes a facet from the menu
 
98
                $scope.deleteFacetSelection = function(facetParts) {
 
99
                    angular.forEach($scope.facetsObj.slice(), function(facet, idx) {
 
100
                        if (facet.name == facetParts[0]) {
 
101
                            if (facet.options === undefined) {
 
102
                                return;  // allow free-form facets to remain
 
103
                            }
 
104
                            for (var i=0; i<facet.options.length; i++) {
 
105
                                var option = facet.options[i];
 
106
                                if (option.key == facetParts[1]) {
 
107
                                    $scope.facetsObj[idx].options.splice($scope.facetsObj[idx].options.indexOf(option), 1);
 
108
                                }
 
109
                            }
 
110
                            if (facet.options.length === 0) {
 
111
                                $scope.facetsObj.splice($scope.facetsObj.indexOf(facet), 1);
 
112
                            }
 
113
                        }
 
114
                    });
 
115
                };
 
116
                $scope.deleteFacetEntirely = function(facetParts) {
 
117
                    // remove entire facet
 
118
                    angular.forEach($scope.facetsObj.slice(), function(facet, idx) {
 
119
                        if (facet.name == facetParts[0]) {
 
120
                            $scope.facetsObj.splice($scope.facetsObj.indexOf(facet), 1);
 
121
                        }
 
122
                    });
 
123
                };
 
124
                $('.search-input').on('keydown', function($event) {  // handle ctrl-char input
 
125
                    var key = $event.keyCode || $event.charCode;
 
126
                    if (key == 9) {  // prevent default when we can.
 
127
                        $event.preventDefault();
 
128
                    }
 
129
                });
 
130
                $('.search-input').on('keyup', function($event) {  // handle ctrl-char input
 
131
                    if ($event.metaKey == true) {
 
132
                        return;
 
133
                    }
 
134
                    var searchVal = $('.search-input').val();
 
135
                    var key = $event.keyCode || $event.charCode;
 
136
                    if (key == 9) {  // tab, so select facet if narrowed down to 1
 
137
                        if ($scope.facetSelected === undefined) {
 
138
                            if ($scope.filteredObj.length != 1) return;
 
139
                            $scope.facetClicked(0, '', $scope.filteredObj[0].name);
 
140
                        }
 
141
                        else {
 
142
                            if ($scope.filteredOptions === undefined || $scope.filteredOptions.length != 1) return;
 
143
                            $scope.optionClicked(0, '', $scope.filteredOptions[0].key);
 
144
                            $scope.resetState();
 
145
                        }
 
146
                        $timeout(function() {
 
147
                            $('.search-input').val('');
 
148
                        });
 
149
                        return;
 
150
                    }
 
151
                    if (key == 27) {  // esc, so cancel and reset everthing
 
152
                        $timeout(function() {
 
153
                            $scope.hideMenu();
 
154
                            $('.search-input').val('');
 
155
                        });
 
156
                        $scope.resetState();
 
157
                        var textFilter = $scope.textSearch;
 
158
                        if (textFilter === undefined) {
 
159
                            textFilter = '';
 
160
                        }
 
161
                        $scope.$emit('textSearch', textFilter, $scope.filter_keys);
 
162
                        return;
 
163
                    }
 
164
                    if (key == 13) {  // enter, so accept value
 
165
                        // if tag search, treat as regular facet
 
166
                        if ($scope.facetSelected && $scope.facetSelected.options === undefined) {
 
167
                            var curr = $scope.facetSelected;
 
168
                            curr.name = curr.name + '=' + searchVal;
 
169
                            curr.label[1] = searchVal;
 
170
                            $scope.currentSearch.push(curr);
 
171
                            $scope.resetState();
 
172
                            $scope.emitQuery();
 
173
                            $scope.showMenu();
 
174
                        }
 
175
                        // if text search treat as search
 
176
                        else {
 
177
                            for (i=0; i<$scope.currentSearch.length; i++) {
 
178
                                if ($scope.currentSearch[i].name.indexOf('text') === 0) {
 
179
                                    $scope.currentSearch.splice(i, 1);
 
180
                                }
 
181
                            }
 
182
                            $scope.currentSearch.push({'name':'text='+searchVal, 'label':[$scope.strings['text'], searchVal]});
 
183
                            $scope.$apply();
 
184
                            $scope.hideMenu();
 
185
                            $('.search-input').val('');
 
186
                            $scope.$emit('textSearch', searchVal, $scope.filter_keys);
 
187
                            $scope.textSearch = searchVal;
 
188
                        }
 
189
                        $scope.filteredObj = $scope.facetsObj;
 
190
                    }
 
191
                    else {
 
192
                        if (searchVal === '') {
 
193
                            $scope.filteredObj = $scope.facetsObj;
 
194
                            $scope.$emit('textSearch', '', $scope.filter_keys);
 
195
                        }
 
196
                        else {
 
197
                            $scope.filterFacets(searchVal);
 
198
                        }
 
199
                    }
 
200
                });
 
201
                $('.search-input').on('keypress', function($event) {  // handle character input
 
202
                    var searchVal = $('.search-input').val();
 
203
                    var key = $event.which || $event.keyCode || $event.charCode;
 
204
                    if (key != 8 && key != 46 && key != 13 && key != 9 && key != 27) {
 
205
                        searchVal = searchVal + String.fromCharCode(key).toLowerCase();
 
206
                    }
 
207
                    if (searchVal == ' ') {  // space and field is empty, show menu
 
208
                        $scope.showMenu();
 
209
                        $timeout(function() {
 
210
                            $('.search-input').val('');
 
211
                        });
 
212
                        return;
 
213
                    }
 
214
                    if (searchVal === '') {
 
215
                        $scope.filteredObj = $scope.facetsObj;
 
216
                        $scope.$emit('textSearch', '', $scope.filter_keys);
 
217
                        return;
 
218
                    }
 
219
                    if (key != 8 && key != 46) {
 
220
                        $scope.filterFacets(searchVal);
 
221
                    }
 
222
                });
 
223
                $scope.filterFacets = function(searchVal) {
 
224
                    // try filtering facets/options.. if no facets match, do text search
 
225
                    var i, idx, label;
 
226
                    var filtered = [];
 
227
                    if ($scope.facetSelected === undefined) {
 
228
                        $scope.filteredObj = $scope.facetsObj;
 
229
                        for (i=0; i<$scope.filteredObj.length; i++) {
 
230
                            var facet = $scope.filteredObj[i];
 
231
                            idx = facet.label.toLowerCase().indexOf(searchVal);
 
232
                            if (idx > -1) {
 
233
                                label = [facet.label.substring(0, idx), facet.label.substring(idx, idx + searchVal.length), facet.label.substring(idx + searchVal.length)];
 
234
                                filtered.push({'name':facet.name, 'label':label, 'options':facet.options});
 
235
                            }
 
236
                        }
 
237
                        if (filtered.length > 0) {
 
238
                            $scope.showMenu();
 
239
                            $timeout(function() {
 
240
                                $scope.filteredObj = filtered;
 
241
                            }, 0.1);
 
242
                        }
 
243
                        else {
 
244
                            $scope.$emit('textSearch', searchVal, $scope.filter_keys);
 
245
                            $scope.hideMenu();
 
246
                        }
 
247
                    }
 
248
                    else {  // assume option search
 
249
                        $scope.filteredOptions = $scope.facetOptions;
 
250
                        if ($scope.facetOptions === undefined) { // no options, assume free form text facet
 
251
                            return;
 
252
                        }
 
253
                        for (i=0; i<$scope.filteredOptions.length; i++) {
 
254
                            var option = $scope.filteredOptions[i];
 
255
                            idx = option.label.toLowerCase().indexOf(searchVal);
 
256
                            if (idx > -1) {
 
257
                                label = [option.label.substring(0, idx), option.label.substring(idx, idx + searchVal.length), option.label.substring(idx + searchVal.length)];
 
258
                                filtered.push({'key':option.key, 'label':label});
 
259
                            }
 
260
                        }
 
261
                        if (filtered.length > 0) {
 
262
                            $scope.showMenu();
 
263
                            $timeout(function() {
 
264
                                $scope.filteredOptions = filtered;
 
265
                            }, 0.1);
 
266
                        }
 
267
                    }
 
268
                };
 
269
                // enable text entry when mouse clicked anywhere in search box
 
270
                $('.search-main-area').on("click", function($event) {
 
271
                    $('.search-input').trigger("focus");
 
272
                    if ($scope.facetSelected === undefined) {
 
273
                        $scope.showMenu();
 
274
                    }
 
275
                });
 
276
                // when facet clicked, add 1st part of facet and set up options
 
277
                $scope.facetClicked = function($index, $event, name) {
 
278
                    $scope.hideMenu();
 
279
                    var facet = $scope.filteredObj[$index];
 
280
                    var label = facet.label;
 
281
                    if (Array.isArray(label)) {
 
282
                        label = label.join('');
 
283
                    }
 
284
                    $scope.facetSelected = {'name':facet.name, 'label':[label, '']};
 
285
                    if (facet.options !== undefined) {
 
286
                        $scope.filteredOptions = $scope.facetOptions = facet.options;
 
287
                        $scope.showMenu();
 
288
                    }
 
289
                    $timeout(function() {
 
290
                        $('.search-input').val('');
 
291
                    });
 
292
                    $scope.strings['prompt'] = '';
 
293
                    $timeout(function() {
 
294
                        $('.search-input').focus();
 
295
                    });
 
296
                };
 
297
                // when option clicked, complete facet and send event
 
298
                $scope.optionClicked = function($index, $event, name) {
 
299
                    $scope.hideMenu();
 
300
                    var curr = $scope.facetSelected;
 
301
                    curr.name = curr.name + '=' + name;
 
302
                    curr.label[1] = $scope.filteredOptions[$index].label;
 
303
                    if (Array.isArray(curr.label[1])) {
 
304
                        curr.label[1] = curr.label[1].join('');
 
305
                    }
 
306
                    $scope.currentSearch.push(curr);
 
307
                    $scope.resetState();
 
308
                    $scope.emitQuery();
 
309
                    $scope.showMenu();
 
310
                };
 
311
                // send event with new query string
 
312
                $scope.emitQuery = function(removed) {
 
313
                    var query = '';
 
314
                    for (var i=0; i<$scope.currentSearch.length; i++) {
 
315
                        if ($scope.currentSearch[i].name.indexOf('text') !== 0) {
 
316
                            if (query.length > 0) query = query + "&";
 
317
                            query = query + $scope.currentSearch[i].name;
 
318
                        }
 
319
                    }
 
320
                    if (removed !== undefined && removed.indexOf('text') === 0) {
 
321
                        $scope.$emit('textSearch', '', $scope.filter_keys);
 
322
                        $scope.textSearch = undefined
 
323
                    }
 
324
                    else {
 
325
                        $scope.$emit('searchUpdated', query);
 
326
                        if ($scope.currentSearch.length > 0) {
 
327
                            var newFacet = $scope.currentSearch[$scope.currentSearch.length-1].name;
 
328
                            var facetParts = newFacet.split('=');
 
329
                            angular.forEach($scope.facetsSave, function(facet, idx) {
 
330
                                if (facet.name == facetParts[0]) {
 
331
                                    if (facet.singleton === true) {
 
332
                                        $scope.deleteFacetEntirely(facetParts);
 
333
                                    }
 
334
                                    else {
 
335
                                        $scope.deleteFacetSelection(facetParts);
 
336
                                    }
 
337
                                }
 
338
                            });
 
339
                        }
 
340
                    }
 
341
                };
 
342
                // remove facet and either update filter or search
 
343
                $scope.removeFacet = function($index, $event) {
 
344
                    var removed = $scope.currentSearch[$index].name;
 
345
                    $scope.currentSearch.splice($index, 1);
 
346
                    if ($scope.facetSelected === undefined) {
 
347
                        $scope.emitQuery(removed);
 
348
                    }
 
349
                    else {
 
350
                        $scope.resetState();
 
351
                        $('.search-input').val('');
 
352
                    }
 
353
                    if ($scope.currentSearch.length == 0) {
 
354
                        $scope.strings['prompt'] = $scope.promptString;
 
355
                    }
 
356
                    // re-init to restore facets cleanly
 
357
                    $scope.facetsObj = $scope.copyFacets($scope.facetsSave);
 
358
                    $scope.currentSearch = [];
 
359
                    $scope.initFacets();
 
360
                };
 
361
                // clear entire searchbar
 
362
                $scope.clearSearch = function() {
 
363
                    if ($scope.currentSearch.length > 0) {
 
364
                        $scope.currentSearch = [];
 
365
                        $scope.facetsObj = $scope.copyFacets($scope.facetsSave);
 
366
                        $scope.resetState();
 
367
                        $scope.$emit('searchUpdated', '');
 
368
                        $scope.$emit('textSearch', '', $scope.filter_keys);
 
369
                        $scope.strings['prompt'] = $scope.promptString;
 
370
                    }
 
371
                };
 
372
                $scope.isMatchLabel = function(label) {
 
373
                    return Array.isArray(label);
 
374
                };
 
375
                $scope.resetState = function() {
 
376
                    $('.search-input').val('');
 
377
                    $scope.filteredObj = $scope.facetsObj;
 
378
                    $scope.facetSelected = undefined;
 
379
                    $scope.facetOptions = undefined;
 
380
                    $scope.filteredOptions = undefined
 
381
                };
 
382
                // showMenu and hideMenu depend on foundation's dropdown. They need
 
383
                // to be modified to work with another dropdown implemenation (i.e. bootstrap)
 
384
                $scope.showMenu = function() {
 
385
                    $timeout(function() {
 
386
                        if ($('#facet-drop').hasClass('open') === false) {
 
387
                            $('.search-input').trigger('click');
 
388
                        }
 
389
                    });
 
390
                };
 
391
                $scope.hideMenu = function() {
 
392
                    $(document).foundation('dropdown', 'closeall');
 
393
                };
 
394
                $scope.initSearch();
 
395
            }
 
396
        };
 
397
    })
 
398
;