1// NOTES: 2/* 3Currently it's relyign ona settimeout in generateFilters(). 4 This is potentially an issue if the each loop and dom updates take more than 100ms, we'll need to look at a callback system to ensure this runs the parent callback only when it's actually done 5 6Memory management isn't great and there are potentially a fair few unused variables and checks, unfortunately time constraints mean I can't review it and rewrite portions of it. 7 8A lot of functions/logic should be in methods but aren't 9 10*/ 11if (!Array.prototype.indexOf) { 12 Array.prototype.indexOf = function(obj, start) { 13 for (var i = (start || 0), j = this.length; i < j; i++) { 14 if (this[i] === obj) { return i; } 15 } 16 return -1; 17 } 18} 19 20/** 21 * Declaring class Filters 22 * 23 */ 24var Filters = function() { 25 // constructor method 26 this.filterJSON = {}; // empty vars for JSON (GLOBALS) 27 this.tripsJSON = {}; // empty vars for JSON (GLOBALS) 28 this.loadedJSON = false; // loadedJSON BOOL 29 this.loadingJSON = false; 30 this.filtering = false; 31 this.parentClass = '.js-dynamic-filters'; 32 this.selects = this.parentClass + ' select'; 33 this.json = 'filters.json'; 34 35 this.tripProperties = []; // create empty array of trip properties that we'll be accessing (this will be populated on page load) 36 this.numFilters = 0; 37 38 39 this.firstFilterNames = {'subject': 'Subjects', 'country': 'Country', 'destination': 'Destination', 'schoolTypes': 'School/College type'}; 40 this.selectedOptions = {}; // create empty object to store selected options 41 this.numberSelected = 0; 42 43 // END CONSTRUCTOR 44}; 45 46 47/** 48 * Filters method preselected 49 * - make changes to the filters if they are pre selected 50 * 51 */ 52Filters.prototype.preselected = function() { 53 var self = this; 54 55 // store selected 56 var selected = []; 57 var values = []; 58 var numselected = 0; 59 60 // for each of the filters get the types 61 $(this.selects).each(function(){ 62 var type = $(this).attr('data-type'), // get type 63 val = $(this).val(); // get val 64 65 self.tripProperties.push(type); 66 self.selectedOptions[type] = ['', []]; // store empty strings as selected options along with empty array for trips 67 68 if (val !== "") { 69 selected.push(type); 70 values.push(val); 71 numselected++; 72 } 73 }); 74 75 this.numFilters = this.tripProperties.length; // store number of filters present for use later 76 77 78 var count = 0; 79 function process() { 80 if (selected[count] !== "") { 81 $('select[data-type="' + selected[count] + '"]').val(values[count]); 82 var $select = $('select[data-type="' + selected[count] + '"]'); 83 self.select($select, function() { 84 if (count < numselected) { 85 process(); 86 } 87 }); 88 } 89 count++; 90 } 91 if (numselected > 0) { 92 process(); 93 } 94}; 95 96/** 97 * Filters method listen 98 * - Listens for select changes, loads JSON then changes and generates relevant filters 99 * 100 */ 101Filters.prototype.listen = function() { 102 var self = this; // store instance of object 103 104 // Listen for select change event 105 $(self.selects).on('change', function(e){ 106 e.preventDefault; 107 108 self.select($(this), function(){ 109 110 }); 111 112 return false; 113 }); 114}; 115 116Filters.prototype.select = function($selected, callback) { 117 var self = this; 118 119 $(this.parentClass).addClass('loading'); 120 console.log('start'); 121 122 // store changedselect in object property 123 this.changedSelect = $selected; 124 //add js-filtered class to selected filter if not selected empty value 125 if (this.changedSelect.val() === '') { 126 this.changedSelect.removeClass('js-filtered'); 127 this.selectedOptions[this.changedSelect.attr('data-type')] = ['', []]; 128 } else { 129 this.changedSelect.addClass('js-filtered'); 130 } 131 132 this.numberSelected = $(this.selects + '.js-filtered').length; // recalc number of selected 133 134 // load the JSON ready for searching 135 this.loadJSON(function(){ 136 $(self.parentClass).removeClass('loading'); 137 if (typeof callback == "function") { 138 callback(); 139 } 140 });
141}; 142 143Filters.prototype.loadJSON = function(callback) { 144 // dynamically load json files then 145 if (!this.loadedJSON) { 146 var self = this; // store instance of object 147 148 this.loadingJSON = true; 149 150 // get JSON 151 $.getJSON( self.json, function(data) { 152 self.filterJSON = data["relationships"]; // store relationship JSON 153 self.tripsJSON = data["trips"]; // store trips JSON 154 self.allTrips = data["alltrips"]; // store array of all trips 155 self.loadedJSON = true; // set JSON variable to true 156 self.loadingJSON = false; 157 $(self.parentClass).removeClass('loading'); 158 }).done(function(){ 159 // do change function only after JSON has loaded 160 self.loadFilters(function(){ 161 callback(); 162 }); 163 }); 164 } else { 165 // do change function if the JSON is already loaded straight away 166 this.loadFilters(function(){ 167 if (typeof callback == "function") { 168 callback(); 169 } 170 }); 171 } 172}; 173 174 175/** 176 * Filters method loadFilters 177 * - Gets the relational filters and loads and generates them 178 * 179 */ 180Filters.prototype.loadFilters = function(callback) { 181 var self = this, 182 value = self.changedSelect.val(), // get value of changed select 183 filtertype = self.changedSelect.attr('data-type'); // get type of filter that was changed 184 185 // first get filters then when done, generate filters and load into dom, when complete callback 186 self.getFilters(filtertype, value, function(){ 187 self.generateFilters(filtertype, function(){ 188 if (typeof callback == "function") { 189 callback(); 190 } 191 }); 192 }); 193}; 194 195/** 196 * Method function to getFilters 197 * - specifically gets filters from the stored JSON and passes it to be generated 198 * 199 */ 200Filters.prototype.getFilters = function(filtertype, value, callback) { 201 var basetrips = this.filterJSON[filtertype][value] === undefined ? [] : this.filterJSON[filtertype][value], // get the trips associated with the selection 202 trips = basetrips, 203 self = this; 204 205 this.selectedOptions[filtertype] = [value, trips]; //store in the Filters selectedOptions property and the associated trips 206 207 // loop over the select types 208 this.filters = {}; 209 for (var i = 0; i < this.numFilters; i++) { 210 this.filters[this.tripProperties[i]] = []; // create empty arrays for storing new filter results in 211 212 // concatenates all the currently selected options arrays except for the just changed type 213 if (filtertype != this.tripProperties[i]) { 214 trips = trips.concat(this.selectedOptions[this.tripProperties[i]][1]); 215 } 216 } 217 218 // this will ensure that if the user has selected multuple options then it will only use the trips ID's that are available in all the options 219 var newTrips = trips; 220 if (this.numberSelected > 1) { 221 222 var newArr = trips, // create new array copying trips 223 newArrCount = newArr.length; // count array for looping 224 225 trips = []; // empty trips array 226 newTrips = []; 227 228 for (var i = 0; i < newArrCount; i++) { // loop through elements in new Arr (trips copy) 229 if (newArr[i] !== null) { 230 var tempItem = newArr[i]; // store ith value in a temp var 231 newArr[i] = null; // store value in array as null 232 233 234 var itemCount = 1; // init at 1 as there is already one instance 235 // loop through the number selected to work out how many times a value appears in array 236 for (var j = 0; j < this.numberSelected; j++) { 237 // if it exists 238 var index = newArr.indexOf(tempItem); 239 if (index >= 0) { 240 //up count and set to null 241 newArr[index] = null; 242 itemCount++; 243 } 244 } 245 // check if there is a duplicate in the concatenated array, if there is then add to new trips selected array 246 if (itemCount >= this.numberSelected - 1) { 247 trips.push(tempItem); 248 } 249 // check if there is a duplicate in the concatenated array, if there is then add to new trips selected array 250 if (itemCount == this.numberSelected) { 251 newTrips.push(tempItem); 252 } 253 } 254 } 255 } 256 257 258 // NEED TO OPTIMISE 259 // To sort out when nothing is selected 260 if (this.numberSelected < 2 && basetrips.length === 0) { 261 trips = this.allTrips; 262 } 263 if (this.numberSelected < 1 && basetrips.length === 0) { 264 newTrips = trips; 265 } 266 var tripsCount = trips.length; 267 268 269 generateFilterArray('selected', trips); 270 generateFilterArray('not-selected', newTrips); 271 272 273 //SORT THE NEW FILTERS 274 //iterate over each of the 4 arrays and sort 275 var count = 0; 276 for (var key in self.filters) { 277 self.sort(self.filters[key], function(){ 278 if (count >= self.numFilters - 1) { 279 if (typeof callback == "function") { 280 callback(); 281 } 282 } 283 count++; 284 }); 285 } 286 287 function generateFilterArray(type, trips) { 288 // loop through the trips for that selection 289 for (var i = 0, tripsCount = trips.length; i < tripsCount; i++) { 290 var trip = self.tripsJSON[trips[i]]; // get the trip properties for the current trip in array 291 292 // loop through the trip properties 293 for (var property in trip) { 294 // Check if the property we are on is in the trip properties we created on page load 295 if ($.inArray(property, self.tripProperties) >= 0) { 296 var operator = '==='; 297 if (type == 'selected') { 298 operator = '!=='; 299 } 300 console.log("self.selectedOptions[property][0] " + operator + " ''"); 301 // depending on type either update the ones that have already been selected or not 302 if (eval("self.selectedOptions[property][0] " + operator + " ''")) { 303 // check if the property is an array, if it is go in and get each value 304 if ($.isArray(trip[property])) { 305 for (var j = 0, propLen = trip[property].length; j < propLen; j++) { 306 self.addToArray(property, trip[property][j]); 307 } 308 } else { 309 self.addToArray(property, trip[property]); 310 } 311 } 312 } 313 }//end trip property loop 314 } //end trip id loop 315 } 316 317} // END GETFILTERS 318 319 320/** 321 * Filters method to sort an array passed in 322 * 323 */ 324Filters.prototype.sort = function(array, callback){ 325 if (!$.isArray(array)) { 326 return false; 327 } 328 array.sort(function(a, b){ 329 if(a < b) return -1; 330 if(a > b) return 1; 331 return 0; 332 });
333 if (typeof callback == "function") { 334 callback(); 335 } 336}; 337 338 339/** 340 * Filters method to addToArray 341 * - takes a key and property and then adds it to the filters array (the generated array of new options) 342 * 343 */ 344Filters.prototype.addToArray = function(key, property){ 345 //check if already exists in array 346 if (property !== '' && $.inArray(property, this.filters[key]) == -1) { 347 this.filters[key].push(property); // push property into array 348 } 349}; 350 351 352/** 353 * Filters method to generateFilters 354 * - specifically generates the new filters into the DOM 355 * 356 */ 357Filters.prototype.generateFilters = function(filtertype, callback){ 358 var self = this, 359 count = 0; 360 //iterate over the selects that weren't changed and update their contents 361 $(self.selects).each(function () { 362 var currentType = $(this).attr('data-type'); // get current type of select box 363 // console.log(filtertype, currentType); 364 if (currentType != filtertype) { 365 var newOptions = '<option value="">' + self.firstFilterNames[currentType] + '</option>'; // initial option 366 for (var i = 0, len = self.filters[currentType].length; i < len; i++) { 367 var val = self.filters[currentType][i], // get current value to use 368 selected = false; 369 370 // if the value is the currently selected one set selected to true 371 if (val == self.selectedOptions[currentType][0]) { 372 selected = true; 373 } 374 375 //create new option string 376 newOptions += '<option value="' + val + '"' + (selected ? ' selected': '') + '>' + val + '</option>'; 377 } 378 379 //empty the current select box and append the new options 380 $(this).empty().append(newOptions); 381 count++; 382 } 383 384 }); 385 386 var timer = window.setInterval(function(){ 387 if (count >= self.numFilters - 1) { 388 console.log('done!'); 389 window.clearInterval(timer); 390 if (typeof callback == "function") { 391 callback(); 392 } 393 } 394 },20); 395}; 396 397 398/** 399 * In main.js we run runFilters once the file has been loaded 400 * 401 */ 402function runFilters(){ 403 // don't do anything for IE8 and below 404 if (!$('html').hasClass('lt-ie9')) { 405 var filter = new Filters(); 406 407 //check if any preselected 408 filter.preselected(); 409 410 // listen for change 411 filter.listen(); 412 } 413}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.