PageSourceSearch

https://gerolcardan.com/catalog/view/theme/luxshop/js/jquery.menu-aim.js?ls2v=1.0

js gerolcardan.com collected 2026-10-04 03:35:16 UTC 15,638 bytes, 391 lines download raw bytes

1;(function ( $, window, document, undefined ) {
2  var pluginName  = 'menuAim',
3      defaults    = {
4        triggerEvent:       "hover",  // A means of activating submenu - either 'hover' or 'click' or 'both
5        rowSelector:        "> li",   // Selector for identifying which elements in the menu are rows
6        handle:             "> a",    // Handle for triggering mouse clicks on menu item
7        submenuSelector:    "*",      // You may have some menu rows that aren't submenus and therefore
8                                      // shouldn't ever need to "activate." If so, filter submenu rows w/
9                                      // this selector. Defaults to "*" (all elements).
10        submenuDirection:   "right",  // Direction the submenu opens relative to the main menu. Can be
11										// left, right, above, or below. Defaults to "right".
12        openClassName:      "open",   // Class that will be applied to menu item when activated
13        tolerance:          75,       // Bigger = more tolerant for mouse movement precision when entering submenu
14        activationDelay:    0,      // Delay (ms) for first submenu opening
15        mouseLocsTracked:   3,        // Number of past mouse locations to track direction
16        defaultDelay:       700,      // Delay (ms) when user appears to be entering submenu
17        enterCallback:      $.noop,   // Function to call when mouse enters a menu row. Entering a row does not mean
18                                      // the row has been activated, as the user may be mousing over to a submenu.
19        activateCallback:   $.noop,   // Function to call when a row is purposefully activated. Use this
20                                      // to show a submenu's content for the activated row.
21        deactivateCallback: $.noop,   // Function to call when a row is deactivated.
22        exitCallback:       $.noop,   // Function to call when mouse exits a menu row.
23        exitMenuCallback:   $.noop,   // Function to call when mouse exits whole menu - for autoclosing submenu
24		exitMenuDelay: 		1e3,
25        exitMenuActiveLastRow:   $.noop    // Function to call when mouse exits whole menu - for autoclosing submenu
26      };
27  function Plugin( el, options ) {
28    this.el = el;
29    this.options = $.extend( {}, defaults, options);
30    this._defaults = defaults;
31    this._name = pluginName;
32    this.init();
33  }
34  Plugin.prototype = {
35    init: function () {
36      this.activeRow    = null,
37      this.mouseLocs    = [],
38      this.lastDelayLoc = null,
39      this.timeoutId    = null,
40      this.openDelayId  = null,
41      this.isOnClick    = $.inArray(this.options.triggerEvent, ['both', 'click']) > -1,
42      this.isOnHover    = $.inArray(this.options.triggerEvent, ['both', 'hover']) > -1;
43      /**
44       * Hook up initial menu events
45       */
46      if (this.isOnHover) {
47        this._hoverTriggerOn();
48      }
49      if (this.isOnClick) {
50        this._clickTriggerOn();
51      }
52    },
53    // Private methods start with underscore
54    /**
55     * Keep track of the last few locations of the mouse.
56     */
57    _mouseMoveDocument: function(e) {
58      obj = e.data.obj;
59      obj.mouseLocs.push({x: e.pageX, y: e.pageY});
60      if (obj.mouseLocs.length > obj.options.mouseLocsTracked) {
61        obj.mouseLocs.shift();
62      }
63    },
64    /**
65     * Cancel possible row activations when leaving the menu entirely
66     */
67    _mouseLeaveMenu: function(e) {
68      obj = e.data.obj;
69      if (obj.timeoutId) {
70        clearTimeout(obj.timeoutId);
71      }
72      if (obj.openDelayId) {
73        clearTimeout(obj.openDelayId);
74      }
75	  lastActiveRow = obj.activeRow;
76      obj._possiblyDeactivate(obj.activeRow);
77      obj.options.exitMenuCallback(this);
78      obj.options.exitMenuActiveLastRow(lastActiveRow);
79	  
80    },
81    /**
82     * Trigger a possible row activation whenever entering a new row.
83     */
84    _mouseEnterRow: function(e) {
85      obj = e.data.obj;
86      if (obj.timeoutId) {
87        // Cancel any previous activation delays
88        clearTimeout(obj.timeoutId);
89      }
90      obj.options.enterCallback(this);
91      obj._possiblyActivate(this);
92    },
93    _mouseLeaveRow: function(e) {
94      e.data.obj.options.exitCallback(this);
95    },
96    /*
97     * Immediately activate a row if the user clicks on it.
98     */
99    _clickRow: function(e) {
100      obj = e.data.obj;
101      obj._activate(this);
102      // bind close event when submenu content is rendered
103      $(obj.el)
104        .find(obj.options.rowSelector)
105          .find(obj.options.handle)
106            .on('click', { obj: obj }, obj._clickRowHandle);
107    },
108    /*
109     * Close already opened submenu
110     */
111    _clickRowHandle: function(e) {
112      obj = e.data.obj;
113      if ($(this).closest('li').hasClass(obj.options.openClassName)) {
114        obj._deactivate();
115        e.stopPropagation();
116      }
117    },
118    /**
119     * Activate a menu row with possible delay
120     */
121    _activate: function(row) {
122      var obj = this;
123      if (row == this.activeRow) {
124        return;
125      }
126      if (this.openDelayId) {
127        clearTimeout(this.openDelayId);
128      }
129      // check if delay should be placed before activation
130      if (parseInt(obj.options.activationDelay, 0) > 0 && obj.isOnHover) {
131        if (obj.activeRow) {
132          obj._activateWithoutDelay(row);
133        } else {
134          this.openDelayId = setTimeout(function() {
135            obj._activateWithoutDelay(row);
136          },
137          obj.options.activationDelay);
138        }
139      } else {
140        obj._activateWithoutDelay(row);
141      }
142    },
143    _activateWithoutDelay: function(row) {
144      if (this.activeRow) {
145        this.options.deactivateCallback(this.activeRow);
146      }
147      this.options.activateCallback(row);
148      this.activeRow = row;
149    },
150    _deactivate: function() {
151      if (this.openDelayId) {
152        clearTimeout(this.openDelayId);
153      }
154      if (this.activeRow) {
155        this.options.deactivateCallback(this.activeRow);
156        this.activeRow = null;
157      }
158    },
159    /**
160     * Possibly activate a menu row. If mouse movement indicates that we
161     * shouldn't activate yet because user may be trying to enter
162     * a submenu's content, then delay and check again later.
163     */
164    _possiblyActivate: function(row) {
165      var delay = this._activationDelay(),
166          that  = this;
167      if (delay) {
168        this.timeoutId = setTimeout(function() {
169          that._possiblyActivate(row);
170        }, delay);
171      } else {
172        this._activate(row);
173      }
174    },
175    _possiblyDeactivate: function(row) {
176      var delay = this._activationDelay(),
177          that  = this;
178      if (delay) {
179        this.timeoutId = setTimeout(function() {
180          that._possiblyDeactivate(row);
181        }, delay)
182      } else {
183        this.options.deactivateCallback(row);
184        this.activeRow = null;
185      }
186    },
187    /**
188     * Return the amount of time that should be used as a delay before the
189     * currently hovered row is activated.
190     *
191     * Returns 0 if the activation should happen immediately. Otherwise,
192     * returns the number of milliseconds that should be delayed before
193     * checking again to see if the row should be activated.
194     */
195    _activationDelay: function() {
196      if (!this.activeRow || !$(this.activeRow).is(this.options.submenuSelector)) {
197        // If there is no other submenu row already active, then
198        // go ahead and activate immediately.
199        return 0;
200      }
201      var offset = $(this.el).offset(),
202          upperLeft = {
203            x: offset.left,
204            y: offset.top - this.options.tolerance
205          },
206          upperRight = {
207            x: offset.left + $(this.el).outerWidth(),
208            y: upperLeft.y
209          },
210          lowerLeft = {
211            x: offset.left,
212            y: offset.top + $(this.el).outerHeight() + this.options.tolerance
213          },
214          lowerRight = {
215            x: offset.left + $(this.el).outerWidth(),
216            y: lowerLeft.y
217          },
218          loc = this.mouseLocs[this.mouseLocs.length - 1],
219          prevLoc = this.mouseLocs[0];
220      if (!loc) {
221        return 0;
222      }
223      if (!prevLoc) {
224        prevLoc = loc;
225      }
226      if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
227          prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
228        // If the previous mouse location was outside of the entire
229        // menu's bounds, immediately activate.
230        return 0;
231      }
232      if (this.lastDelayLoc && loc.x == this.lastDelayLoc.x && loc.y == this.lastDelayLoc.y) {
233        // If the mouse hasn't moved since the last time we checked
234        // for activation status, immediately activate.
235        return 0;
236      }
237      // Detect if the user is moving towards the currently activated
238      // submenu.
239      //
240      // If the mouse is heading relatively clearly towards
241      // the submenu's content, we should wait and give the user more
242      // time before activating a new row. If the mouse is heading
243      // elsewhere, we can immediately activate a new row.
244      //
245      // We detect this by calculating the slope formed between the
246      // current mouse location and the upper/lower right points of
247      // the menu. We do the same for the previous mouse location.
248      // If the current mouse location's slopes are
249      // increasing/decreasing appropriately compared to the
250      // previous's, we know the user is moving toward the submenu.
251      //
252      // Note that since the y-axis increases as the cursor moves
253      // down the screen, we are looking for the slope between the
254      // cursor and the upper right corner to decrease over time, not
255      // increase (somewhat counterintuitively).
256      function slope(a, b) {
257        return (b.y - a.y) / (b.x - a.x);
258      };
259      var decreasingCorner = upperRight,
260          increasingCorner = lowerRight;
261      // Our expectations for decreasing or increasing slope values
262      // depends on which direction the submenu opens relative to the
263      // main menu. By default, if the menu opens on the right, we
264      // expect the slope between the cursor and the upper right
265      // corner to decrease over time, as explained above. If the
266      // submenu opens in a different direction, we change our slope
267      // expectations.
268      if (this.options.submenuDirection == "left") {
269        decreasingCorner = lowerLeft;
270        increasingCorner = upperLeft;
271      } else if (this.options.submenuDirection == "below") {
272        decreasingCorner = lowerRight;
273        increasingCorner = lowerLeft;
274      } else if (this.options.submenuDirection == "above") {
275        decreasingCorner = upperLeft;
276        increasingCorner = upperRight;
277      }
278      var decreasingSlope = slope(loc, decreasingCorner),
279          increasingSlope = slope(loc, increasingCorner),
280          prevDecreasingSlope = slope(prevLoc, decreasingCorner),
281          prevIncreasingSlope = slope(prevLoc, increasingCorner);
282      if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
283        // Mouse is moving from previous location towards the
284        // currently activated submenu. Delay before activating a
285        // new menu row, because user may be moving into submenu.
286        this.lastDelayLoc = loc;
287        return this.options.defaultDelay;
288      }
289      this.lastDelayLoc = null;
290      return 0;
291    },
292    // if the target isn't the container nor a descendant of the container
293    _outsideMenuClick: function(e) {
294      var obj = e.data.obj;
295      if ($(obj.el).not(e.target) && $(obj.el).has(e.target).length === 0) {
296        obj.options.deactivateCallback(obj.activeRow);
297        obj.activeRow = null;
298      }
299    },
300    _hoverTriggerOn: function() {
301      $(this.el).on('mouseleave', { obj: this}, this._mouseLeaveMenu )
302        .find(this.options.rowSelector)
303          .on('mouseenter', { obj: this}, this._mouseEnterRow)
304          .on('mouseleave', { obj: this}, this._mouseLeaveRow);
305      $(window).on('blur', { obj: this }, this._mouseLeaveMenu);
306      $(document).on('mousemove', { obj: this }, this._mouseMoveDocument);
307    },
308    _hoverTriggerOff: function() {
309      $(this.el).off('mouseleave', this._mouseLeaveMenu)
310        .find(this.options.rowSelector)
311          .off('mouseenter', this._mouseEnterRow)
312          .off('mouseleave', this._mouseLeaveRow)
313      $(window).off('blur', this._mouseLeaveMenu);
314      $(document).off('mousemove', { obj: this }, this._mouseMoveDocument);
315    },
316    _clickTriggerOn: function() {
317      $(this.el).find(this.options.rowSelector)
318        .on('click', { obj: this }, this._clickRow);
319      // hide menu if clicked elsewere
320      $(document).on('click', { obj: this }, this._outsideMenuClick);
321    },
322    _clickTriggerOff: function() {
323      $(this.el)
324        .find(this.options.rowSelector)
325          .off('click', this._clickRow);
326      $(document).off('click', this._outsideMenuClick);
327    },
328    // Public methods
329    switchToHover: function() {
330      this._clickTriggerOff();
331      this._hoverTriggerOn();
332      this.isOnHover = true;
333      this.isOnClick = false;
vendor: 2,525 bytes, lines 334-391
334    },
335    switchToClick: function() {
336      this._hoverTriggerOff();
337      this._clickTriggerOn();
338      this.isOnHover = false;
339      this.isOnClick = true;
340    }
341  };
342  // A lightweight plugin wrapper around the constructor,
343  // preventing against multiple instantiations and allowing any
344  // public function (ie. a function whose name doesn't start
345  // with an underscore) to be called via the jQuery plugin,
346  // e.g. $(element).defaultPluginName('functionName', arg1, arg2)
347  $.fn[pluginName] = function ( options ) {
348    var args = arguments;
349    // Is the first parameter an object (options), or was omitted,
350    // instantiate a new instance of the plugin.
351    if (options === undefined || typeof options === 'object') {
352      return this.each(function () {
353        // Only allow the plugin to be instantiated once,
354        // so we check that the element has no plugin instantiation yet
355        if (!$.data(this, 'plugin_' + pluginName)) {
356          // if it has no instance, create a new one,
357          // pass options to our plugin constructor,
358          // and store the plugin instance
359          // in the elements jQuery data object.
360          $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
361        }
362      });
363    // If the first parameter is a string and it doesn't start
364    // with an underscore or "contains" the `init`-function,
365    // treat this as a call to a public method.
366    } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
367      // Cache the method call
368      // to make it possible
369      // to return a value
370      var returns;
371      this.each(function () {
372        var instance = $.data(this, 'plugin_' + pluginName);
373        // Tests that there's already a plugin-instance
374        // and checks that the requested public method exists
375        if (instance instanceof Plugin && typeof instance[options] === 'function') {
376          // Call the method of our plugin instance,
377          // and pass it the supplied arguments.
378          returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
379        }
380        // Allow instances to be destroyed via the 'destroy' method
381        if (options === 'destroy') {
382			$.data(this, 'plugin_' + pluginName, null);
383        }
384      });
385      // If the earlier cached method
386      // gives a value back return the value,
387      // otherwise return this to preserve chainability.
388      return returns !== undefined ? returns : this;
389    }
390  };
391}(jQuery, window, document));

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.