1/** 2 * Handle the interaction with the main navigation. 3 * 4 * @class MainNavigation 5 */ 6var MainNavigation = function(options){ 7 8 /* 9 * Variables accessible 10 * in the class 11 */ 12 var vars = { 13 element: {}, // The parent element holding the nav actions 14 active_category: {}, // The currently active category element 15 active_sub_category: {}, // The currently active sub category element 16 container: {}, // The element to contain the dynamic nav 17 seach_container: {}, // The element to contain the search nav 18 sub_prefix: {}, // The element precede the subnav response 19 cache: {}, // Store previously retrieved data so we dont keep request on each interaction 20 trigger_selector: '[category-main-nav-toggle]', 21 active_class: 'active', 22 sub_active_class: 'head', 23 container_selector: '#main-nav', 24 search_container_selector: '#search-nav', 25 header_selector: '#header-fixed-nav', 26 sub_prefix_selector: '#main-sub-prefix', 27 sub_level_selector: '[next-level]', 28 sub_item_selector: '[next-level-item]', 29 sub_content_selector: '[next-level-item]', 30 close_selector: '[category-main-nav-close]', 31 search_toggle_selector: '[search-nav-toggle]', 32 more_selector: '[category-main-nav-more]', 33 category_data_key: 'category', 34 sub_category_data_key: null, 35 main_route_prefix: 'index.php?route=product/category/navigation&hierarchy=0&category_id=', 36 sub_route_prefix: 'index.php?route=product/category/navigation&hierarchy=1&category_id=', 37 bind_events: true, 38 auto_activate_first_sub: true, 39 manage_header_class: true, 40 header_open_class: 'category-nav-open', 41 on_open: null, 42 on_close: null, 43 filter_sub_toggle: null 44 }; 45 46 /* 47 * Can access this.method 48 * inside other methods using 49 * root.method() 50 */ 51 var root = this; 52 53 /* 54 * Constructor 55 */ 56 this.construct = function(options){ 57 $.extend(vars , options); 58 59 vars.element = $(vars.trigger_selector).closest('nav'); 60 vars.container = $(vars.container_selector); 61 vars.search_container = $(vars.search_container_selector); 62 vars.header = $(vars.header_selector); 63 64 // Check if we're performing any actions on page load 65 if (window.location.hash) { 66 if (window.location.hash == '#search') { 67 root.searchToggle($(this)); 68 } 69 70 if (window.location.hash == '#search-multi') { 71 root.searchToggle($(this), true); 72 } 73 } 74 75 root.bindEvents(); 76 }; 77 78 this.bindEvents = function() { 79 if (!vars.bind_events) { 80 return; 81 } 82 83 // Display a main category nav level 84 $(document).on('click', vars.trigger_selector, function(){ 85 return root.toggle($(this)); 86 }); 87 88 // Display a secondary nav level 89 $(vars.container).on('click', vars.sub_level_selector, function(){ 90 return root.toggleSub($(this)); 91 }); 92 93 // Close on click 94 $(vars.container).on('click', vars.sub_item_selector + ' a', function(){ 95 root.close($(this)); 96 }); 97 98 // Hide the main nav 99 if (vars.close_selector) { 100 $(vars.container).on('click', vars.close_selector, function(){ 101 return root.close($(this)); 102 }); 103 104 if ($(vars.search_container).length) { 105 $(vars.search_container).on('click', vars.close_selector, function(){ 106 return root.close($(this)); 107 }); 108 } 109 } 110 111 // Show and hide the search nav 112 if (vars.search_toggle_selector) { 113 $(document).on('click', vars.search_toggle_selector, function(){ 114 return root.searchToggle($(this)); 115 }); 116 } 117 118 // Extend the main nav on "more" click 119 if (vars.more_selector) { 120 $(document).on('click', vars.more_selector, function(){ 121 return root.nextPage($(this)); 122 }); 123 } 124 }; 125 126 /** 127 * Activate the current nav. 128 * 129 * @return void 130 */ 131 this.activateCurrentNav = function(){ 132 var category_id = $(vars.active_category).data(vars.category_data_key); 133 var cache_key = 'h0_' + category_id; 134 135 // Check cache 136 if (typeof vars.cache[cache_key] != 'undefined') { 137 $(vars.container).html(vars.cache[cache_key]).show(); 138 139 // Find the new sub nav container on the dom 140 vars.sub_prefix = $(vars.container).find(vars.sub_prefix_selector); 141 142 // Activate click on second level 143 if (vars.auto_activate_first_sub) { 144 $(vars.container).find(vars.sub_level_selector + ':first').click(); 145 } 146 } else { 147 // Retrieve the html 148 LocalApi.call(vars.main_route_prefix + category_id, {}, 'GET', function(response){ 149 if (response.status == 'success') { 150 $(vars.container).html(response.data).show(); 151 vars.cache[cache_key] = response.data; 152 153 // Find the new sub nav container on the dom 154 vars.sub_prefix = $(vars.container).find(vars.sub_prefix_selector); 155 156 // Activate click on second level 157 if (vars.auto_activate_first_sub) { 158 $(vars.container).find(vars.sub_level_selector + ':first').click(); 159 } 160 } 161 }); 162 } 163 } 164 165 /** 166 * Activate a sub/second level nav. 167 * 168 * @return void 169 */ 170 this.activateSubNav = function(){ 171 var sub_data_key = vars.sub_category_data_key || vars.category_data_key; 172 var category_id = $(vars.active_sub_category).data(sub_data_key); 173 if (typeof category_id == 'undefined' || category_id === null || category_id === '') { 174 return; 175 } 176 var cache_key = 'h1_' + category_id; 177 178 // Check cache 179 if (typeof vars.cache[cache_key] != 'undefined') { 180 $(vars.sub_prefix).after(vars.cache[cache_key]).show(); 181 } else { 182 // Retrieve the html 183 LocalApi.call(vars.sub_route_prefix + category_id, {}, 'GET', function(response){ 184 if (response.status == 'success') { 185 $(vars.sub_prefix).after(response.data).show(); 186 vars.cache[cache_key] = response.data; 187 } 188 }); 189 } 190 } 191 192 /** 193 * Close the nave. 194 * 195 * @param object trigger 196 * 197 * @return object 198 */
199 this.close = function(trigger, callback){ 200 // Empty the container 201 if (window.location.hash === "#search" || window.location.hash === "#search-multi") { 202 window.history.back(); 203 } else { 204 root.resetContainer(); 205 } 206 vars.active_category = {}; 207 return false; 208 } 209 210 /** 211 * Show the next page of nav items. 212 * 213 * @param object trigger 214 * @return object 215 */ 216 this.nextPage = function(trigger){ 217 this.close(); 218 219 let initialMinKey = trigger.data('initial_min_key'); // The point we should paginate around 220 let minKey = trigger.data('min_key'); // The minimum key for the second half of results 221 let maxKey = trigger.data('max_key'); // The maximum key to display 222 let highestDisplayedKey = 0; 223 let highestKey = 0; 224 225 $('[category-main-nav-item]').each(function(key, item){ 226 if ($(item).data('key') >= minKey && $(item).data('key') <= maxKey) { 227 $(item).attr('style', 'display: block !important'); 228 highestDisplayedKey = $(item).data('key'); 229 } else { 230 $(item).attr('style', 'display: none !important'); 231 } 232 highestKey = $(item).data('key'); 233 }); 234 235 if (highestKey == highestDisplayedKey) { 236 trigger.data('min_key', 0); 237 trigger.data('max_key', (initialMinKey-1)); 238 trigger.html('« More'); 239 } else { 240 trigger.data('min_key', initialMinKey); 241 trigger.data('max_key', 999); 242 trigger.html('More »'); 243 } 244 } 245 246 /** 247 * Reset the containing element ready for new nav. 248 * 249 * @return void 250 */ 251 this.resetContainer = function(){ 252 // Close other nav 253 $('[content-main-nav-close]').each(function(key, item){ 254 item.click(); 255 }); 256 257 $(vars.trigger_selector).removeClass(vars.active_class); 258 $(vars.container).empty(); 259 $(vars.container).hide(); 260 if ($(vars.search_container).length) { 261 $(vars.search_container).hide(); 262 } 263 264 if (vars.manage_header_class && $(vars.header).length) { 265 $(vars.header).removeClass(vars.header_open_class); 266 var headerOffsetHeight = $(vars.header).height(); 267 if ($('#search-panel').length) { 268 $('#search-panel').css({ 269 'margin-top': headerOffsetHeight + 'px' 270 }); 271 } 272 } 273 274 if (typeof vars.on_close === 'function') { 275 vars.on_close.call(root); 276 } 277 } 278 279 /** 280 * Reset the sub-containing element ready for new nav. 281 * 282 * @return void 283 */ 284 this.resetSubContainer = function(){ 285 $(vars.container).find(vars.sub_content_selector).remove(); 286 } 287 288 /** 289 * Trigger a toggle of the search nav. 290 * 291 * @param object trigger 292 * 293 * @return object 294 */ 295 this.searchToggle = function(trigger, multi = false, callback){ 296 // Empty the container 297 root.resetContainer(); 298 299 // Disable any existing category that is open 300 vars.active_category = {}; 301 302 $(vars.search_container).show(); 303 $(vars.active_category).addClass(vars.active_class); 304 305 // Point the user to the correct input 306 $(vars.search_container).find('input[type="text"]').focus(); 307 308 // The fixed header will need styling updates to account for the growth of content 309 if (vars.manage_header_class && $(vars.header).length) { 310 $(vars.header).addClass(vars.header_open_class); 311 } 312 313 // Toggle to multi-part search if required 314 if (multi) { 315 $('.single-search').trigger('click'); 316 } 317 318 return false; 319 }; 320 321 /** 322 * Trigger a toggle on an element. 323 * 324 * @param object trigger 325 * 326 * @return object 327 */ 328 this.toggle = function(trigger, callback){ 329 // Empty the container 330 root.resetContainer(); 331 332 // Check if this is a switch to a new category 333 if ($(vars.active_category).data(vars.category_data_key) != $(trigger).data(vars.category_data_key)) { 334 vars.active_category = trigger; 335 root.activateCurrentNav(); 336 $(vars.active_category).addClass(vars.active_class); 337 338 // The fixed header will need styling updates to account for the growth of content 339 if (vars.manage_header_class && $(vars.header).length) { 340 $(vars.header).addClass(vars.header_open_class); 341 } 342 343 if (typeof vars.on_open === 'function') { 344 vars.on_open.call(root, trigger); 345 } 346 } else { 347 vars.active_category = {}; 348 } 349 350 return false;
351 }; 352 353 /** 354 * Trigger a toggle on an element for the sub/secondary level navigation. 355 * 356 * @param object trigger 357 * 358 * @return object 359 */ 360 this.toggleSub = function(trigger, callback){ 361 if (typeof vars.filter_sub_toggle === 'function') { 362 if (vars.filter_sub_toggle.call(root, trigger) === false) { 363 return false; 364 } 365 } 366 367 // Change the state of the item 368 $(vars.container).find(vars.sub_level_selector + '.' + vars.sub_active_class).removeClass(vars.sub_active_class); 369 $(trigger).addClass(vars.sub_active_class); 370 371 // Empty the container 372 root.resetSubContainer(); 373 vars.active_sub_category = trigger; 374 root.activateSubNav(); 375 376 return false; 377 }; 378 379 /* 380 * Pass options when class instantiated 381 */ 382 this.construct(options); 383 384}
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.