PageSourceSearch

https://shop.megalos.co.jp/lib/jquery.balloon.js

js megalos.co.jp collected 2026-10-02 04:27:36 UTC 15,268 bytes, 382 lines download raw bytes

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

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.