PageSourceSearch

https://dancebug.com/js/baloon.js?1.024

js dancebug.com collected 2026-09-24 10:43:51 UTC 14,056 bytes, 344 lines download raw bytes

1/**
2 * Hover balloon on elements without css and images.
3 *
4 * Copyright (c) 2011 Hayato Takenaka
5 * Dual licensed under the MIT and GPL licenses:
6 * http://www.opensource.org/licenses/mit-license.php
7 * http://www.gnu.org/licenses/gpl.html
8 * @author: Hayato Takenaka (https://urin.github.io)
9 * @version: 1.0.3 - 2016/07/02
10**/
11(function($) {
12  //-----------------------------------------------------------------------------
13  // Private
14  //-----------------------------------------------------------------------------
15  // Helper for meta programming
16  const Meta = {
17    pos: $.extend(['top', 'bottom', 'left', 'right'], {camel: ['Top', 'Bottom', 'Left', 'Right']}),
18    size: $.extend(['height', 'width'], {camel: ['Height', 'Width']}),
19    getRelativeNames: function(position) {
20      const idx = {
21        pos: {
22          o: position,                                           // origin
23          f: (position % 2 === 0) ? position + 1 : position - 1, // faced
24          p1: (position % 2 === 0) ? position : position - 1,
25          p2: (position % 2 === 0) ? position + 1 : position,
26          c1: (position < 2) ? 2 : 0,
27          c2: (position < 2) ? 3 : 1
28        },
29        size: {
30          p: (position < 2) ? 0 : 1, // parallel
31          c: (position < 2) ? 1 : 0  // cross
32        }
33      };
34      const names = {};
35      for(var m1 in idx) {
36        if(!names[m1]) { names[m1] = {}; }
37        for(var m2 in idx[m1]) {
38          names[m1][m2] = Meta[m1][idx[m1][m2]];
39          if(!names.camel) { names.camel = {}; }
40          if(!names.camel[m1]) { names.camel[m1] = {}; }
41          names.camel[m1][m2] = Meta[m1].camel[idx[m1][m2]];
42        }
43      }
44      names.isTopLeft = (names.pos.o === names.pos.p1);
45      return names;
46    }
47  };
48
49  // Helper class to handle position and size as numerical pixels.
50  function NumericalBoxElement() { this.initialize.apply(this, arguments); }
51  (function() {
52    // Method factories
53    const Methods = {
54      setBorder: function(pos, isVertical) {
55        return function(value) {
56          this.$.css('border-' + pos.toLowerCase() + '-width', value + 'px');
57          this['border' + pos] = value;
58          return (this.isActive) ? digitalize(this, isVertical) : this;
59        }
60      },
61      setPosition: function(pos, isVertical) {
62        return function(value) {
63          this.$.css(pos.toLowerCase(), value + 'px');
64          this[pos.toLowerCase()] = value;
65          return (this.isActive) ? digitalize(this, isVertical) : this;
66        }
67      }
68    };
69
70    NumericalBoxElement.prototype = {
71      initialize: function($element) {
72        this.$ = $element;
73        $.extend(true, this, this.$.offset(), {center: {}, inner: {center: {}}});
74        for(var i = 0; i < Meta.pos.length; i++) {
75          this['border' + Meta.pos.camel[i]] = parseInt(this.$.css('border-' + Meta.pos[i] + '-width')) || 0;
76        }
77        this.active();
78      },
79      active: function() { this.isActive = true; digitalize(this); return this; },
80      inactive: function() { this.isActive = false; return this; }
81    };
82    for(var i = 0; i < Meta.pos.length; i++) {
83      NumericalBoxElement.prototype['setBorder' + Meta.pos.camel[i]] = Methods.setBorder(Meta.pos.camel[i], (i < 2));
84      if(i % 2 === 0) {
85        NumericalBoxElement.prototype['set' + Meta.pos.camel[i]] = Methods.setPosition(Meta.pos.camel[i], (i < 2));
86      }
87    }
88
89    function digitalize(box, isVertical) {
90      if(isVertical == null) { digitalize(box, true); return digitalize(box, false); }
91      const m = Meta.getRelativeNames((isVertical) ? 0 : 2);
92      box[m.size.p] = box.$['outer' + m.camel.size.p]();
93      box[m.pos.f] = box[m.pos.o] + box[m.size.p];
94      box.center[m.pos.o] = box[m.pos.o] + box[m.size.p] / 2;
95      box.inner[m.pos.o] = box[m.pos.o] + box['border' + m.camel.pos.o];
96      box.inner[m.size.p] = box.$['inner' + m.camel.size.p]();
97      box.inner[m.pos.f] = box.inner[m.pos.o] + box.inner[m.size.p];
98      box.inner.center[m.pos.o] = box.inner[m.pos.f] + box.inner[m.size.p] / 2;
99      return box;
100    }
101  })();
102
103  // Adjust position of balloon body
104  function makeupBalloon($target, $balloon, options) {
105    $balloon.stop(true, true);
106    var outerTip, innerTip;
107    const initTipStyle = {position: 'absolute', height: '0', width: '0', border: 'solid 0 transparent'},
108      target = new NumericalBoxElement($target),
109      balloon = new NumericalBoxElement($balloon);
110    balloon.setTop(-options.offsetY
111      + ((options.position && options.position.indexOf('top') >= 0) ? target.top - balloon.height
112      : ((options.position && options.position.indexOf('bottom') >= 0) ? target.bottom
113      : target.center.top - balloon.height / 2)));
114    balloon.setLeft(options.offsetX
115      + ((options.position && options.position.indexOf('left') >= 0) ? target.left - balloon.width
116      : ((options.position && options.position.indexOf('right') >= 0) ? target.right
117      : target.center.left - balloon.width / 2)));
118    if(options.tipSize > 0) {
119      // Add hidden balloon tips into balloon body.
120      if($balloon.data('outerTip')) { $balloon.data('outerTip').remove(); $balloon.removeData('outerTip'); }
121      if($balloon.data('innerTip')) { $balloon.data('innerTip').remove(); $balloon.removeData('innerTip'); }
122      outerTip = new NumericalBoxElement($('<div>').css(initTipStyle).appendTo($balloon));
123      innerTip = new NumericalBoxElement($('<div>').css(initTipStyle).appendTo($balloon));
124      // Make tip triangle, adjust position of tips.
125      var m;
126      for(var i = 0; i < Meta.pos.length; i++) {
127        m = Meta.getRelativeNames(i);
128        if(balloon.center[m.pos.c1] >= target[m.pos.c1] &&
129          balloon.center[m.pos.c1] <= target[m.pos.c2]) {
130          if(i % 2 === 0) {
131            if(balloon[m.pos.o] >= target[m.pos.o] && balloon[m.pos.f] >= target[m.pos.f]) { break; }
132          } else {
133            if(balloon[m.pos.o] <= target[m.pos.o] && balloon[m.pos.f] <= target[m.pos.f]) { break; }
134          }
135        }
136        m = null;
137      }
138      if(m) {
139        balloon['set' + m.camel.pos.p1]
140          (balloon[m.pos.p1] + ((m.isTopLeft) ? 1 : -1) * (options.tipSize - balloon['border' + m.camel.pos.o]));
141        makeTip(balloon, outerTip, m, options.tipSize, $balloon.css('border-' + m.pos.o + '-color'), options.tipPosition);
142        makeTip(balloon, innerTip, m, options.tipSize - 2 * balloon['border' + m.camel.pos.o], $balloon.css('background-color'), options.tipPosition);
143        $balloon.data('outerTip', outerTip.$).data('innerTip', innerTip.$);
144      } else {
145        $.each([outerTip.$, innerTip.$], function() { this.remove(); });
146      }
147    }
148    // Make up balloon tip.
149    function makeTip(balloon, tip, m, tipSize, color) {
150      const len = Math.round(tipSize / 1.7320508);
151      tip.inactive()
152        ['setBorder' + m.camel.pos.f](tipSize)
153        ['setBorder' + m.camel.pos.c1](len)
154        ['setBorder' + m.camel.pos.c2](len)
155        ['set' + m.camel.pos.p1]((m.isTopLeft) ? -tipSize : balloon.inner[m.size.p])
156        ['set' + m.camel.pos.c1](balloon.inner[m.size.c] / options.tipPosition - len)
157        .active()
158        .$.css('border-' + m.pos.f + '-color', color);
159    }
160  }
161
162  // True if the event comes from the target or balloon.
163  function isValidTargetEvent($target, e) {
164    const b = $target.data('balloon') && $target.data('balloon').get(0);
165    return !(b && (b === e.relatedTarget || $.contains(b, e.relatedTarget)));
166  }
167
168  //-----------------------------------------------------------------------------
169  // Public
170  //-----------------------------------------------------------------------------
171  $.fn.balloon = function(options) {
172    return this.one('mouseenter', function first(e) {
173      const $target = $(this), t = this
174      const $balloon = $target.on('mouseenter', function(e) {
175        isValidTargetEvent($target, e) && $target.showBalloon();
176      }).off('mouseenter', first).showBalloon(options).data('balloon');
177      if($balloon) {
178        $balloon.on('mouseleave', function(e) {
179          if(t === e.relatedTarget || $.contains(t, e.relatedTarget)) { return; }
180          $target.hideBalloon();
181        }).on('mouseenter', function(e) {
182          if(t === e.relatedTarget || $.contains(t, e.relatedTarget)) { return; }
183          $balloon.stop(true, true);
184          $target.showBalloon();
185        });
186      }
187    }).on('mouseleave', function(e) {
188      const $target = $(this);
189      isValidTargetEvent($target, e) && $target.hideBalloon();
190    });
191  };
192
193  $.fn.showBalloon = function(options) {
194    var $target, $balloon;
195    if(options || !this.data('options')) {
196      if($.balloon.defaults.css === null) { $.balloon.defaults.css = {}; }
197      this.data('options', $.extend(true, {}, $.balloon.defaults, options || {}));
198    }
199    options = this.data('options');
200    return this.each(function() {
201      var isNew;
202      $target = $(this);
203      isNew = !$target.data('balloon');
204      $balloon = $target.data('balloon') || $('<div>');
205      if(!isNew && $balloon.data('active')) { return; }
206      $balloon.data('active', true);
207      clearTimeout($balloon.data('minLifetime'));
208      const contents = $.isFunction(options.contents) ? options.contents.apply(this)
209        : (options.contents || $target.attr('title') || $target.attr('alt'));
210      $target.removeAttr('title');
211      if(!options.url && contents === '' || contents == null) { return; }
212      if(!$.isFunction(options.contents)) { options.contents = contents; }
213      if(options.url) {
214        if(!$balloon.data('ajaxDisabled')) {
215          if(contents !== '' && contents != null) {
216            if(options.html) {
217              $balloon.empty().append(contents);
218            } else {
219              $balloon.text(contents);
220            }
221          }
222          clearTimeout($balloon.data('ajaxDelay'));
223          $balloon.data('ajaxDelay',
224            setTimeout(function() {
225              $balloon.load(
226                $.isFunction(options.url) ? options.url.apply($target.get(0)) : options.url,
227                function(res, sts, xhr) {
228                  if(sts !== 'success' && sts !== 'notmodified') { return; }
229                  $balloon.data('ajaxDisabled', true);
230                  if(options.ajaxContentsMaxAge >= 0) {
231                    setTimeout(function() { $balloon.data('ajaxDisabled', false); }, options.ajaxContentsMaxAge);
232                  }
233                  if(options.ajaxComplete) { options.ajaxComplete(res, sts, xhr); }
234                  makeupBalloon($target, $balloon, options);
235                }
236              );
237            }, options.ajaxDelay)
238          );
239        }
240      } else {
241        if(options.html) {
242          $balloon.empty().append(contents);
243        } else {
244          $balloon.text(contents);
245        }
246      }
247      if(isNew) {
248        $balloon
249          .addClass(options.classname)
250          .css(options.css || {})
251          .css({ visibility: 'hidden', position: 'absolute' })
252          .appendTo('body');
253        $target.data('balloon', $balloon);
254        makeupBalloon($target, $balloon, options);
255        $balloon.hide().css('visibility', 'visible');
256      } else {
257        makeupBalloon($target, $balloon, options);
258      }
259      $balloon.data('delay', setTimeout(function() {
260        if(options.showAnimation) {
261          options.showAnimation.apply(
262            $balloon.stop(true, true), [
263              options.showDuration, function() {
264                options.showComplete && options.showComplete.apply($balloon);
265              }
266            ]
267          );
268        } else {
269          $balloon.show(options.showDuration, function() {
270            if(this.style.removeAttribute) { this.style.removeAttribute('filter'); }
271            options.showComplete && options.showComplete.apply($balloon);
272          });
273        }
274        if(options.maxLifetime) {
275          clearTimeout($balloon.data('maxLifetime'));
276          $balloon.data('maxLifetime',
277            setTimeout(function() { $target.hideBalloon(); }, options.maxLifetime)
278          );
279        }
280      }, options.delay));
281    });
282  };
283
284  $.fn.hideBalloon = function() {
285    const options = this.data('options');
286    if(!this.data('balloon')) { return this; }
287    return this.each(function() {
288      const $target = $(this), $balloon = $target.data('balloon');
289      clearTimeout($balloon.data('delay'));
290      clearTimeout($balloon.data('minLifetime'));
291      clearTimeout($balloon.data('ajaxDelay'));
292      $balloon.data('minLifetime', setTimeout(function() {
293        if(options.hideAnimation) {
294          options.hideAnimation.apply(
295            $balloon.stop(true, true),
296            [
297              options.hideDuration,
298              function(d) {
299                $(this).data('active', false);
300                options.hideComplete && options.hideComplete(d);
301              }
302            ]
303          );
304        } else {
305          $balloon.stop(true, true).hide(
306            options.hideDuration,
307            function(d) {
308              $(this).data('active', false);
309              options.hideComplete && options.hideComplete(d);
310            }
311          );
312        }
313      },
314      options.minLifetime));
315    });
316  };
317
318  $.balloon = {
319    defaults: {
320      contents: null, html: false, classname: null,
321      url: null, ajaxComplete: null, ajaxDelay: 500, ajaxContentsMaxAge: -1,
322      delay: 0, minLifetime: 200, maxLifetime: 0,
323      position: 'top', offsetX: 0, offsetY: 0, tipSize: 8, tipPosition: 2,
324      showDuration: 100, showAnimation: null,
325      hideDuration:  80, hideAnimation: function(d, c) { this.fadeOut(d, c); },
326      showComplete: null, hideComplete: null,
327      css: {
328        fontSize       : '.7rem',
329        minWidth       : '.7rem',
330        padding        : '.2rem .5rem',
331        border         : '1px solid rgba(212, 212, 212, .4)',
332        borderRadius   : '3px',
333        boxShadow      : '2px 2px 4px #555',
334        color          : '#eee',
335        backgroundColor: '#111',
336        opacity        : 0.85,
337        zIndex         : '32767',
338        textAlign      : 'left'
339      }
340    }
341  };
342})(jQuery);
343
344

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.