123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345 |
- /**
- * tooltipster http://iamceege.github.io/tooltipster/
- * A rockin' custom tooltip jQuery plugin
- * Developed by Caleb Jacob and Louis Ameline
- * MIT license
- */
- (function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module unless amdModuleId is set
- define(["jquery"], function (a0) {
- return (factory(a0));
- });
- } else if (typeof exports === 'object') {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like environments that support module.exports,
- // like Node.
- module.exports = factory(require("jquery"));
- } else {
- factory(jQuery);
- }
- }(this, function ($) {
- // This file will be UMDified by a build task.
- var defaults = {
- animation: 'fade',
- animationDuration: 350,
- content: null,
- contentAsHTML: false,
- contentCloning: false,
- debug: true,
- delay: 300,
- delayTouch: [300, 500],
- functionInit: null,
- functionBefore: null,
- functionReady: null,
- functionAfter: null,
- functionFormat: null,
- IEmin: 6,
- interactive: false,
- multiple: false,
- // will default to document.body, or must be an element positioned at (0, 0)
- // in the document, typically like the very top views of an app.
- parent: null,
- plugins: ['sideTip'],
- repositionOnScroll: false,
- restoration: 'none',
- selfDestruction: true,
- theme: [],
- timer: 0,
- trackerInterval: 500,
- trackOrigin: false,
- trackTooltip: false,
- trigger: 'hover',
- triggerClose: {
- click: false,
- mouseleave: false,
- originClick: false,
- scroll: false,
- tap: false,
- touchleave: false
- },
- triggerOpen: {
- click: false,
- mouseenter: false,
- tap: false,
- touchstart: false
- },
- updateAnimation: 'rotate',
- zIndex: 9999999
- },
- // we'll avoid using the 'window' global as a good practice but npm's
- // jquery@<2.1.0 package actually requires a 'window' global, so not sure
- // it's useful at all
- win = (typeof window != 'undefined') ? window : null,
- // env will be proxied by the core for plugins to have access its properties
- env = {
- // detect if this device can trigger touch events. Better have a false
- // positive (unused listeners, that's ok) than a false negative.
- // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
- // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
- hasTouchCapability: !!(
- win
- && ( 'ontouchstart' in win
- || (win.DocumentTouch && win.document instanceof win.DocumentTouch)
- || win.navigator.maxTouchPoints
- )
- ),
- hasTransitions: transitionSupport(),
- IE: false,
- // don't set manually, it will be updated by a build task after the manifest
- semVer: '4.2.6',
- window: win
- },
- core = function() {
-
- // core variables
-
- // the core emitters
- this.__$emitterPrivate = $({});
- this.__$emitterPublic = $({});
- this.__instancesLatestArr = [];
- // collects plugin constructors
- this.__plugins = {};
- // proxy env variables for plugins who might use them
- this._env = env;
- };
- // core methods
- core.prototype = {
-
- /**
- * A function to proxy the public methods of an object onto another
- *
- * @param {object} constructor The constructor to bridge
- * @param {object} obj The object that will get new methods (an instance or the core)
- * @param {string} pluginName A plugin name for the console log message
- * @return {core}
- * @private
- */
- __bridge: function(constructor, obj, pluginName) {
-
- // if it's not already bridged
- if (!obj[pluginName]) {
-
- var fn = function() {};
- fn.prototype = constructor;
-
- var pluginInstance = new fn();
-
- // the _init method has to exist in instance constructors but might be missing
- // in core constructors
- if (pluginInstance.__init) {
- pluginInstance.__init(obj);
- }
-
- $.each(constructor, function(methodName, fn) {
-
- // don't proxy "private" methods, only "protected" and public ones
- if (methodName.indexOf('__') != 0) {
-
- // if the method does not exist yet
- if (!obj[methodName]) {
-
- obj[methodName] = function() {
- return pluginInstance[methodName].apply(pluginInstance, Array.prototype.slice.apply(arguments));
- };
-
- // remember to which plugin this method corresponds (several plugins may
- // have methods of the same name, we need to be sure)
- obj[methodName].bridged = pluginInstance;
- }
- else if (defaults.debug) {
-
- console.log('The '+ methodName +' method of the '+ pluginName
- +' plugin conflicts with another plugin or native methods');
- }
- }
- });
-
- obj[pluginName] = pluginInstance;
- }
-
- return this;
- },
-
- /**
- * For mockup in Node env if need be, for testing purposes
- *
- * @return {core}
- * @private
- */
- __setWindow: function(window) {
- env.window = window;
- return this;
- },
-
- /**
- * Returns a ruler, a tool to help measure the size of a tooltip under
- * various settings. Meant for plugins
- *
- * @see Ruler
- * @return {object} A Ruler instance
- * @protected
- */
- _getRuler: function($tooltip) {
- return new Ruler($tooltip);
- },
-
- /**
- * For internal use by plugins, if needed
- *
- * @return {core}
- * @protected
- */
- _off: function() {
- this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * For internal use by plugins, if needed
- *
- * @return {core}
- * @protected
- */
- _on: function() {
- this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * For internal use by plugins, if needed
- *
- * @return {core}
- * @protected
- */
- _one: function() {
- this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * Returns (getter) or adds (setter) a plugin
- *
- * @param {string|object} plugin Provide a string (in the full form
- * "namespace.name") to use as as getter, an object to use as a setter
- * @return {object|core}
- * @protected
- */
- _plugin: function(plugin) {
-
- var self = this;
-
- // getter
- if (typeof plugin == 'string') {
-
- var pluginName = plugin,
- p = null;
-
- // if the namespace is provided, it's easy to search
- if (pluginName.indexOf('.') > 0) {
- p = self.__plugins[pluginName];
- }
- // otherwise, return the first name that matches
- else {
- $.each(self.__plugins, function(i, plugin) {
-
- if (plugin.name.substring(plugin.name.length - pluginName.length - 1) == '.'+ pluginName) {
- p = plugin;
- return false;
- }
- });
- }
-
- return p;
- }
- // setter
- else {
-
- // force namespaces
- if (plugin.name.indexOf('.') < 0) {
- throw new Error('Plugins must be namespaced');
- }
-
- self.__plugins[plugin.name] = plugin;
-
- // if the plugin has core features
- if (plugin.core) {
-
- // bridge non-private methods onto the core to allow new core methods
- self.__bridge(plugin.core, self, plugin.name);
- }
-
- return this;
- }
- },
-
- /**
- * Trigger events on the core emitters
- *
- * @returns {core}
- * @protected
- */
- _trigger: function() {
-
- var args = Array.prototype.slice.apply(arguments);
-
- if (typeof args[0] == 'string') {
- args[0] = { type: args[0] };
- }
-
- // note: the order of emitters matters
- this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
- this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
-
- return this;
- },
-
- /**
- * Returns instances of all tooltips in the page or an a given element
- *
- * @param {string|HTML object collection} selector optional Use this
- * parameter to restrict the set of objects that will be inspected
- * for the retrieval of instances. By default, all instances in the
- * page are returned.
- * @return {array} An array of instance objects
- * @public
- */
- instances: function(selector) {
-
- var instances = [],
- sel = selector || '.tooltipstered';
-
- $(sel).each(function() {
-
- var $this = $(this),
- ns = $this.data('tooltipster-ns');
-
- if (ns) {
-
- $.each(ns, function(i, namespace) {
- instances.push($this.data(namespace));
- });
- }
- });
-
- return instances;
- },
-
- /**
- * Returns the Tooltipster objects generated by the last initializing call
- *
- * @return {array} An array of instance objects
- * @public
- */
- instancesLatest: function() {
- return this.__instancesLatestArr;
- },
-
- /**
- * For public use only, not to be used by plugins (use ::_off() instead)
- *
- * @return {core}
- * @public
- */
- off: function() {
- this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * For public use only, not to be used by plugins (use ::_on() instead)
- *
- * @return {core}
- * @public
- */
- on: function() {
- this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * For public use only, not to be used by plugins (use ::_one() instead)
- *
- * @return {core}
- * @public
- */
- one: function() {
- this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * Returns all HTML elements which have one or more tooltips
- *
- * @param {string} selector optional Use this to restrict the results
- * to the descendants of an element
- * @return {array} An array of HTML elements
- * @public
- */
- origins: function(selector) {
-
- var sel = selector ?
- selector +' ' :
- '';
-
- return $(sel +'.tooltipstered').toArray();
- },
-
- /**
- * Change default options for all future instances
- *
- * @param {object} d The options that should be made defaults
- * @return {core}
- * @public
- */
- setDefaults: function(d) {
- $.extend(defaults, d);
- return this;
- },
-
- /**
- * For users to trigger their handlers on the public emitter
- *
- * @returns {core}
- * @public
- */
- triggerHandler: function() {
- this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- return this;
- }
- };
- // $.tooltipster will be used to call core methods
- $.tooltipster = new core();
- // the Tooltipster instance class (mind the capital T)
- $.Tooltipster = function(element, options) {
-
- // list of instance variables
-
- // stack of custom callbacks provided as parameters to API methods
- this.__callbacks = {
- close: [],
- open: []
- };
- // the schedule time of DOM removal
- this.__closingTime;
- // this will be the user content shown in the tooltip. A capital "C" is used
- // because there is also a method called content()
- this.__Content;
- // for the size tracker
- this.__contentBcr;
- // to disable the tooltip after destruction
- this.__destroyed = false;
- // we can't emit directly on the instance because if a method with the same
- // name as the event exists, it will be called by jQuery. Se we use a plain
- // object as emitter. This emitter is for internal use by plugins,
- // if needed.
- this.__$emitterPrivate = $({});
- // this emitter is for the user to listen to events without risking to mess
- // with our internal listeners
- this.__$emitterPublic = $({});
- this.__enabled = true;
- // the reference to the gc interval
- this.__garbageCollector;
- // various position and size data recomputed before each repositioning
- this.__Geometry;
- // the tooltip position, saved after each repositioning by a plugin
- this.__lastPosition;
- // a unique namespace per instance
- this.__namespace = 'tooltipster-'+ Math.round(Math.random()*1000000);
- this.__options;
- // will be used to support origins in scrollable areas
- this.__$originParents;
- this.__pointerIsOverOrigin = false;
- // to remove themes if needed
- this.__previousThemes = [];
- // the state can be either: appearing, stable, disappearing, closed
- this.__state = 'closed';
- // timeout references
- this.__timeouts = {
- close: [],
- open: null
- };
- // store touch events to be able to detect emulated mouse events
- this.__touchEvents = [];
- // the reference to the tracker interval
- this.__tracker = null;
- // the element to which this tooltip is associated
- this._$origin;
- // this will be the tooltip element (jQuery wrapped HTML element).
- // It's the job of a plugin to create it and append it to the DOM
- this._$tooltip;
-
- // launch
- this.__init(element, options);
- };
- $.Tooltipster.prototype = {
-
- /**
- * @param origin
- * @param options
- * @private
- */
- __init: function(origin, options) {
-
- var self = this;
-
- self._$origin = $(origin);
- self.__options = $.extend(true, {}, defaults, options);
-
- // some options may need to be reformatted
- self.__optionsFormat();
-
- // don't run on old IE if asked no to
- if ( !env.IE
- || env.IE >= self.__options.IEmin
- ) {
-
- // note: the content is null (empty) by default and can stay that
- // way if the plugin remains initialized but not fed any content. The
- // tooltip will just not appear.
-
- // let's save the initial value of the title attribute for later
- // restoration if need be.
- var initialTitle = null;
-
- // it will already have been saved in case of multiple tooltips
- if (self._$origin.data('tooltipster-initialTitle') === undefined) {
-
- initialTitle = self._$origin.attr('title');
-
- // we do not want initialTitle to be "undefined" because
- // of how jQuery's .data() method works
- if (initialTitle === undefined) initialTitle = null;
-
- self._$origin.data('tooltipster-initialTitle', initialTitle);
- }
-
- // If content is provided in the options, it has precedence over the
- // title attribute.
- // Note: an empty string is considered content, only 'null' represents
- // the absence of content.
- // Also, an existing title="" attribute will result in an empty string
- // content
- if (self.__options.content !== null) {
- self.__contentSet(self.__options.content);
- }
- else {
-
- var selector = self._$origin.attr('data-tooltip-content'),
- $el;
-
- if (selector){
- $el = $(selector);
- }
-
- if ($el && $el[0]) {
- self.__contentSet($el.first());
- }
- else {
- self.__contentSet(initialTitle);
- }
- }
-
- self._$origin
- // strip the title off of the element to prevent the default tooltips
- // from popping up
- .removeAttr('title')
- // to be able to find all instances on the page later (upon window
- // events in particular)
- .addClass('tooltipstered');
-
- // set listeners on the origin
- self.__prepareOrigin();
-
- // set the garbage collector
- self.__prepareGC();
-
- // init plugins
- $.each(self.__options.plugins, function(i, pluginName) {
- self._plug(pluginName);
- });
-
- // to detect swiping
- if (env.hasTouchCapability) {
- $(env.window.document.body).on('touchmove.'+ self.__namespace +'-triggerOpen', function(event) {
- self._touchRecordEvent(event);
- });
- }
-
- self
- // prepare the tooltip when it gets created. This event must
- // be fired by a plugin
- ._on('created', function() {
- self.__prepareTooltip();
- })
- // save position information when it's sent by a plugin
- ._on('repositioned', function(e) {
- self.__lastPosition = e.position;
- });
- }
- else {
- self.__options.disabled = true;
- }
- },
-
- /**
- * Insert the content into the appropriate HTML element of the tooltip
- *
- * @returns {self}
- * @private
- */
- __contentInsert: function() {
-
- var self = this,
- $el = self._$tooltip.find('.tooltipster-content'),
- formattedContent = self.__Content,
- format = function(content) {
- formattedContent = content;
- };
-
- self._trigger({
- type: 'format',
- content: self.__Content,
- format: format
- });
-
- if (self.__options.functionFormat) {
-
- formattedContent = self.__options.functionFormat.call(
- self,
- self,
- { origin: self._$origin[0] },
- self.__Content
- );
- }
-
- if (typeof formattedContent === 'string' && !self.__options.contentAsHTML) {
- $el.text(formattedContent);
- }
- else {
- $el
- .empty()
- .append(formattedContent);
- }
-
- return self;
- },
-
- /**
- * Save the content, cloning it beforehand if need be
- *
- * @param content
- * @returns {self}
- * @private
- */
- __contentSet: function(content) {
-
- // clone if asked. Cloning the object makes sure that each instance has its
- // own version of the content (in case a same object were provided for several
- // instances)
- // reminder: typeof null === object
- if (content instanceof $ && this.__options.contentCloning) {
- content = content.clone(true);
- }
-
- this.__Content = content;
-
- this._trigger({
- type: 'updated',
- content: content
- });
-
- return this;
- },
-
- /**
- * Error message about a method call made after destruction
- *
- * @private
- */
- __destroyError: function() {
- throw new Error('This tooltip has been destroyed and cannot execute your method call.');
- },
-
- /**
- * Gather all information about dimensions and available space,
- * called before every repositioning
- *
- * @private
- * @returns {object}
- */
- __geometry: function() {
-
- var self = this,
- $target = self._$origin,
- originIsArea = self._$origin.is('area');
-
- // if this._$origin is a map area, the target we'll need
- // the dimensions of is actually the image using the map,
- // not the area itself
- if (originIsArea) {
-
- var mapName = self._$origin.parent().attr('name');
-
- $target = $('img[usemap="#'+ mapName +'"]');
- }
-
- var bcr = $target[0].getBoundingClientRect(),
- $document = $(env.window.document),
- $window = $(env.window),
- $parent = $target,
- // some useful properties of important elements
- geo = {
- // available space for the tooltip, see down below
- available: {
- document: null,
- window: null
- },
- document: {
- size: {
- height: $document.height(),
- width: $document.width()
- }
- },
- window: {
- scroll: {
- // the second ones are for IE compatibility
- left: env.window.scrollX || env.window.document.documentElement.scrollLeft,
- top: env.window.scrollY || env.window.document.documentElement.scrollTop
- },
- size: {
- height: $window.height(),
- width: $window.width()
- }
- },
- origin: {
- // the origin has a fixed lineage if itself or one of its
- // ancestors has a fixed position
- fixedLineage: false,
- // relative to the document
- offset: {},
- size: {
- height: bcr.bottom - bcr.top,
- width: bcr.right - bcr.left
- },
- usemapImage: originIsArea ? $target[0] : null,
- // relative to the window
- windowOffset: {
- bottom: bcr.bottom,
- left: bcr.left,
- right: bcr.right,
- top: bcr.top
- }
- }
- },
- geoFixed = false;
-
- // if the element is a map area, some properties may need
- // to be recalculated
- if (originIsArea) {
-
- var shape = self._$origin.attr('shape'),
- coords = self._$origin.attr('coords');
-
- if (coords) {
-
- coords = coords.split(',');
-
- $.map(coords, function(val, i) {
- coords[i] = parseInt(val);
- });
- }
-
- // if the image itself is the area, nothing more to do
- if (shape != 'default') {
-
- switch(shape) {
-
- case 'circle':
-
- var circleCenterLeft = coords[0],
- circleCenterTop = coords[1],
- circleRadius = coords[2],
- areaTopOffset = circleCenterTop - circleRadius,
- areaLeftOffset = circleCenterLeft - circleRadius;
-
- geo.origin.size.height = circleRadius * 2;
- geo.origin.size.width = geo.origin.size.height;
-
- geo.origin.windowOffset.left += areaLeftOffset;
- geo.origin.windowOffset.top += areaTopOffset;
-
- break;
-
- case 'rect':
-
- var areaLeft = coords[0],
- areaTop = coords[1],
- areaRight = coords[2],
- areaBottom = coords[3];
-
- geo.origin.size.height = areaBottom - areaTop;
- geo.origin.size.width = areaRight - areaLeft;
-
- geo.origin.windowOffset.left += areaLeft;
- geo.origin.windowOffset.top += areaTop;
-
- break;
-
- case 'poly':
-
- var areaSmallestX = 0,
- areaSmallestY = 0,
- areaGreatestX = 0,
- areaGreatestY = 0,
- arrayAlternate = 'even';
-
- for (var i = 0; i < coords.length; i++) {
-
- var areaNumber = coords[i];
-
- if (arrayAlternate == 'even') {
-
- if (areaNumber > areaGreatestX) {
-
- areaGreatestX = areaNumber;
-
- if (i === 0) {
- areaSmallestX = areaGreatestX;
- }
- }
-
- if (areaNumber < areaSmallestX) {
- areaSmallestX = areaNumber;
- }
-
- arrayAlternate = 'odd';
- }
- else {
- if (areaNumber > areaGreatestY) {
-
- areaGreatestY = areaNumber;
-
- if (i == 1) {
- areaSmallestY = areaGreatestY;
- }
- }
-
- if (areaNumber < areaSmallestY) {
- areaSmallestY = areaNumber;
- }
-
- arrayAlternate = 'even';
- }
- }
-
- geo.origin.size.height = areaGreatestY - areaSmallestY;
- geo.origin.size.width = areaGreatestX - areaSmallestX;
-
- geo.origin.windowOffset.left += areaSmallestX;
- geo.origin.windowOffset.top += areaSmallestY;
-
- break;
- }
- }
- }
-
- // user callback through an event
- var edit = function(r) {
- geo.origin.size.height = r.height,
- geo.origin.windowOffset.left = r.left,
- geo.origin.windowOffset.top = r.top,
- geo.origin.size.width = r.width
- };
-
- self._trigger({
- type: 'geometry',
- edit: edit,
- geometry: {
- height: geo.origin.size.height,
- left: geo.origin.windowOffset.left,
- top: geo.origin.windowOffset.top,
- width: geo.origin.size.width
- }
- });
-
- // calculate the remaining properties with what we got
-
- geo.origin.windowOffset.right = geo.origin.windowOffset.left + geo.origin.size.width;
- geo.origin.windowOffset.bottom = geo.origin.windowOffset.top + geo.origin.size.height;
-
- geo.origin.offset.left = geo.origin.windowOffset.left + geo.window.scroll.left;
- geo.origin.offset.top = geo.origin.windowOffset.top + geo.window.scroll.top;
- geo.origin.offset.bottom = geo.origin.offset.top + geo.origin.size.height;
- geo.origin.offset.right = geo.origin.offset.left + geo.origin.size.width;
-
- // the space that is available to display the tooltip relatively to the document
- geo.available.document = {
- bottom: {
- height: geo.document.size.height - geo.origin.offset.bottom,
- width: geo.document.size.width
- },
- left: {
- height: geo.document.size.height,
- width: geo.origin.offset.left
- },
- right: {
- height: geo.document.size.height,
- width: geo.document.size.width - geo.origin.offset.right
- },
- top: {
- height: geo.origin.offset.top,
- width: geo.document.size.width
- }
- };
-
- // the space that is available to display the tooltip relatively to the viewport
- // (the resulting values may be negative if the origin overflows the viewport)
- geo.available.window = {
- bottom: {
- // the inner max is here to make sure the available height is no bigger
- // than the viewport height (when the origin is off screen at the top).
- // The outer max just makes sure that the height is not negative (when
- // the origin overflows at the bottom).
- height: Math.max(geo.window.size.height - Math.max(geo.origin.windowOffset.bottom, 0), 0),
- width: geo.window.size.width
- },
- left: {
- height: geo.window.size.height,
- width: Math.max(geo.origin.windowOffset.left, 0)
- },
- right: {
- height: geo.window.size.height,
- width: Math.max(geo.window.size.width - Math.max(geo.origin.windowOffset.right, 0), 0)
- },
- top: {
- height: Math.max(geo.origin.windowOffset.top, 0),
- width: geo.window.size.width
- }
- };
-
- while ($parent[0].tagName.toLowerCase() != 'html') {
-
- if ($parent.css('position') == 'fixed') {
- geo.origin.fixedLineage = true;
- break;
- }
-
- $parent = $parent.parent();
- }
-
- return geo;
- },
-
- /**
- * Some options may need to be formated before being used
- *
- * @returns {self}
- * @private
- */
- __optionsFormat: function() {
-
- if (typeof this.__options.animationDuration == 'number') {
- this.__options.animationDuration = [this.__options.animationDuration, this.__options.animationDuration];
- }
-
- if (typeof this.__options.delay == 'number') {
- this.__options.delay = [this.__options.delay, this.__options.delay];
- }
-
- if (typeof this.__options.delayTouch == 'number') {
- this.__options.delayTouch = [this.__options.delayTouch, this.__options.delayTouch];
- }
-
- if (typeof this.__options.theme == 'string') {
- this.__options.theme = [this.__options.theme];
- }
-
- // determine the future parent
- if (this.__options.parent === null) {
- this.__options.parent = $(env.window.document.body);
- }
- else if (typeof this.__options.parent == 'string') {
- this.__options.parent = $(this.__options.parent);
- }
-
- if (this.__options.trigger == 'hover') {
-
- this.__options.triggerOpen = {
- mouseenter: true,
- touchstart: true
- };
-
- this.__options.triggerClose = {
- mouseleave: true,
- originClick: true,
- touchleave: true
- };
- }
- else if (this.__options.trigger == 'click') {
-
- this.__options.triggerOpen = {
- click: true,
- tap: true
- };
-
- this.__options.triggerClose = {
- click: true,
- tap: true
- };
- }
-
- // for the plugins
- this._trigger('options');
-
- return this;
- },
-
- /**
- * Schedules or cancels the garbage collector task
- *
- * @returns {self}
- * @private
- */
- __prepareGC: function() {
-
- var self = this;
-
- // in case the selfDestruction option has been changed by a method call
- if (self.__options.selfDestruction) {
-
- // the GC task
- self.__garbageCollector = setInterval(function() {
-
- var now = new Date().getTime();
-
- // forget the old events
- self.__touchEvents = $.grep(self.__touchEvents, function(event, i) {
- // 1 minute
- return now - event.time > 60000;
- });
-
- // auto-destruct if the origin is gone
- if (!bodyContains(self._$origin)) {
-
- self.close(function(){
- self.destroy();
- });
- }
- }, 20000);
- }
- else {
- clearInterval(self.__garbageCollector);
- }
-
- return self;
- },
-
- /**
- * Sets listeners on the origin if the open triggers require them.
- * Unlike the listeners set at opening time, these ones
- * remain even when the tooltip is closed. It has been made a
- * separate method so it can be called when the triggers are
- * changed in the options. Closing is handled in _open()
- * because of the bindings that may be needed on the tooltip
- * itself
- *
- * @returns {self}
- * @private
- */
- __prepareOrigin: function() {
-
- var self = this;
-
- // in case we're resetting the triggers
- self._$origin.off('.'+ self.__namespace +'-triggerOpen');
-
- // if the device is touch capable, even if only mouse triggers
- // are asked, we need to listen to touch events to know if the mouse
- // events are actually emulated (so we can ignore them)
- if (env.hasTouchCapability) {
-
- self._$origin.on(
- 'touchstart.'+ self.__namespace +'-triggerOpen ' +
- 'touchend.'+ self.__namespace +'-triggerOpen ' +
- 'touchcancel.'+ self.__namespace +'-triggerOpen',
- function(event){
- self._touchRecordEvent(event);
- }
- );
- }
-
- // mouse click and touch tap work the same way
- if ( self.__options.triggerOpen.click
- || (self.__options.triggerOpen.tap && env.hasTouchCapability)
- ) {
-
- var eventNames = '';
- if (self.__options.triggerOpen.click) {
- eventNames += 'click.'+ self.__namespace +'-triggerOpen ';
- }
- if (self.__options.triggerOpen.tap && env.hasTouchCapability) {
- eventNames += 'touchend.'+ self.__namespace +'-triggerOpen';
- }
-
- self._$origin.on(eventNames, function(event) {
- if (self._touchIsMeaningfulEvent(event)) {
- self._open(event);
- }
- });
- }
-
- // mouseenter and touch start work the same way
- if ( self.__options.triggerOpen.mouseenter
- || (self.__options.triggerOpen.touchstart && env.hasTouchCapability)
- ) {
-
- var eventNames = '';
- if (self.__options.triggerOpen.mouseenter) {
- eventNames += 'mouseenter.'+ self.__namespace +'-triggerOpen ';
- }
- if (self.__options.triggerOpen.touchstart && env.hasTouchCapability) {
- eventNames += 'touchstart.'+ self.__namespace +'-triggerOpen';
- }
-
- self._$origin.on(eventNames, function(event) {
- if ( self._touchIsTouchEvent(event)
- || !self._touchIsEmulatedEvent(event)
- ) {
- self.__pointerIsOverOrigin = true;
- self._openShortly(event);
- }
- });
- }
-
- // info for the mouseleave/touchleave close triggers when they use a delay
- if ( self.__options.triggerClose.mouseleave
- || (self.__options.triggerClose.touchleave && env.hasTouchCapability)
- ) {
-
- var eventNames = '';
- if (self.__options.triggerClose.mouseleave) {
- eventNames += 'mouseleave.'+ self.__namespace +'-triggerOpen ';
- }
- if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
- eventNames += 'touchend.'+ self.__namespace +'-triggerOpen touchcancel.'+ self.__namespace +'-triggerOpen';
- }
-
- self._$origin.on(eventNames, function(event) {
-
- if (self._touchIsMeaningfulEvent(event)) {
- self.__pointerIsOverOrigin = false;
- }
- });
- }
-
- return self;
- },
-
- /**
- * Do the things that need to be done only once after the tooltip
- * HTML element it has been created. It has been made a separate
- * method so it can be called when options are changed. Remember
- * that the tooltip may actually exist in the DOM before it is
- * opened, and present after it has been closed: it's the display
- * plugin that takes care of handling it.
- *
- * @returns {self}
- * @private
- */
- __prepareTooltip: function() {
-
- var self = this,
- p = self.__options.interactive ? 'auto' : '';
-
- // this will be useful to know quickly if the tooltip is in
- // the DOM or not
- self._$tooltip
- .attr('id', self.__namespace)
- .css({
- // pointer events
- 'pointer-events': p,
- zIndex: self.__options.zIndex
- });
-
- // themes
- // remove the old ones and add the new ones
- $.each(self.__previousThemes, function(i, theme) {
- self._$tooltip.removeClass(theme);
- });
- $.each(self.__options.theme, function(i, theme) {
- self._$tooltip.addClass(theme);
- });
-
- self.__previousThemes = $.merge([], self.__options.theme);
-
- return self;
- },
-
- /**
- * Handles the scroll on any of the parents of the origin (when the
- * tooltip is open)
- *
- * @param {object} event
- * @returns {self}
- * @private
- */
- __scrollHandler: function(event) {
-
- var self = this;
-
- if (self.__options.triggerClose.scroll) {
- self._close(event);
- }
- else {
-
- // if the origin or tooltip have been removed: do nothing, the tracker will
- // take care of it later
- if (bodyContains(self._$origin) && bodyContains(self._$tooltip)) {
-
- var geo = null;
-
- // if the scroll happened on the window
- if (event.target === env.window.document) {
-
- // if the origin has a fixed lineage, window scroll will have no
- // effect on its position nor on the position of the tooltip
- if (!self.__Geometry.origin.fixedLineage) {
-
- // we don't need to do anything unless repositionOnScroll is true
- // because the tooltip will already have moved with the window
- // (and of course with the origin)
- if (self.__options.repositionOnScroll) {
- self.reposition(event);
- }
- }
- }
- // if the scroll happened on another parent of the tooltip, it means
- // that it's in a scrollable area and now needs to have its position
- // adjusted or recomputed, depending ont the repositionOnScroll
- // option. Also, if the origin is partly hidden due to a parent that
- // hides its overflow, we'll just hide (not close) the tooltip.
- else {
-
- geo = self.__geometry();
-
- var overflows = false;
-
- // a fixed position origin is not affected by the overflow hiding
- // of a parent
- if (self._$origin.css('position') != 'fixed') {
-
- self.__$originParents.each(function(i, el) {
-
- var $el = $(el),
- overflowX = $el.css('overflow-x'),
- overflowY = $el.css('overflow-y');
-
- if (overflowX != 'visible' || overflowY != 'visible') {
-
- var bcr = el.getBoundingClientRect();
-
- if (overflowX != 'visible') {
-
- if ( geo.origin.windowOffset.left < bcr.left
- || geo.origin.windowOffset.right > bcr.right
- ) {
- overflows = true;
- return false;
- }
- }
-
- if (overflowY != 'visible') {
-
- if ( geo.origin.windowOffset.top < bcr.top
- || geo.origin.windowOffset.bottom > bcr.bottom
- ) {
- overflows = true;
- return false;
- }
- }
- }
-
- // no need to go further if fixed, for the same reason as above
- if ($el.css('position') == 'fixed') {
- return false;
- }
- });
- }
-
- if (overflows) {
- self._$tooltip.css('visibility', 'hidden');
- }
- else {
-
- self._$tooltip.css('visibility', 'visible');
-
- // reposition
- if (self.__options.repositionOnScroll) {
- self.reposition(event);
- }
- // or just adjust offset
- else {
-
- // we have to use offset and not windowOffset because this way,
- // only the scroll distance of the scrollable areas are taken into
- // account (the scrolltop value of the main window must be
- // ignored since the tooltip already moves with it)
- var offsetLeft = geo.origin.offset.left - self.__Geometry.origin.offset.left,
- offsetTop = geo.origin.offset.top - self.__Geometry.origin.offset.top;
-
- // add the offset to the position initially computed by the display plugin
- self._$tooltip.css({
- left: self.__lastPosition.coord.left + offsetLeft,
- top: self.__lastPosition.coord.top + offsetTop
- });
- }
- }
- }
-
- self._trigger({
- type: 'scroll',
- event: event,
- geo: geo
- });
- }
- }
-
- return self;
- },
-
- /**
- * Changes the state of the tooltip
- *
- * @param {string} state
- * @returns {self}
- * @private
- */
- __stateSet: function(state) {
-
- this.__state = state;
-
- this._trigger({
- type: 'state',
- state: state
- });
-
- return this;
- },
-
- /**
- * Clear appearance timeouts
- *
- * @returns {self}
- * @private
- */
- __timeoutsClear: function() {
-
- // there is only one possible open timeout: the delayed opening
- // when the mouseenter/touchstart open triggers are used
- clearTimeout(this.__timeouts.open);
- this.__timeouts.open = null;
-
- // ... but several close timeouts: the delayed closing when the
- // mouseleave close trigger is used and the timer option
- $.each(this.__timeouts.close, function(i, timeout) {
- clearTimeout(timeout);
- });
- this.__timeouts.close = [];
-
- return this;
- },
-
- /**
- * Start the tracker that will make checks at regular intervals
- *
- * @returns {self}
- * @private
- */
- __trackerStart: function() {
-
- var self = this,
- $content = self._$tooltip.find('.tooltipster-content');
-
- // get the initial content size
- if (self.__options.trackTooltip) {
- self.__contentBcr = $content[0].getBoundingClientRect();
- }
-
- self.__tracker = setInterval(function() {
-
- // if the origin or tooltip elements have been removed.
- // Note: we could destroy the instance now if the origin has
- // been removed but we'll leave that task to our garbage collector
- if (!bodyContains(self._$origin) || !bodyContains(self._$tooltip)) {
- self._close();
- }
- // if everything is alright
- else {
-
- // compare the former and current positions of the origin to reposition
- // the tooltip if need be
- if (self.__options.trackOrigin) {
-
- var g = self.__geometry(),
- identical = false;
-
- // compare size first (a change requires repositioning too)
- if (areEqual(g.origin.size, self.__Geometry.origin.size)) {
-
- // for elements that have a fixed lineage (see __geometry()), we track the
- // top and left properties (relative to window)
- if (self.__Geometry.origin.fixedLineage) {
- if (areEqual(g.origin.windowOffset, self.__Geometry.origin.windowOffset)) {
- identical = true;
- }
- }
- // otherwise, track total offset (relative to document)
- else {
- if (areEqual(g.origin.offset, self.__Geometry.origin.offset)) {
- identical = true;
- }
- }
- }
-
- if (!identical) {
-
- // close the tooltip when using the mouseleave close trigger
- // (see https://github.com/iamceege/tooltipster/pull/253)
- if (self.__options.triggerClose.mouseleave) {
- self._close();
- }
- else {
- self.reposition();
- }
- }
- }
-
- if (self.__options.trackTooltip) {
-
- var currentBcr = $content[0].getBoundingClientRect();
-
- if ( currentBcr.height !== self.__contentBcr.height
- || currentBcr.width !== self.__contentBcr.width
- ) {
- self.reposition();
- self.__contentBcr = currentBcr;
- }
- }
- }
- }, self.__options.trackerInterval);
-
- return self;
- },
-
- /**
- * Closes the tooltip (after the closing delay)
- *
- * @param event
- * @param callback
- * @param force Set to true to override a potential refusal of the user's function
- * @returns {self}
- * @protected
- */
- _close: function(event, callback, force) {
-
- var self = this,
- ok = true;
-
- self._trigger({
- type: 'close',
- event: event,
- stop: function() {
- ok = false;
- }
- });
-
- // a destroying tooltip (force == true) may not refuse to close
- if (ok || force) {
-
- // save the method custom callback and cancel any open method custom callbacks
- if (callback) self.__callbacks.close.push(callback);
- self.__callbacks.open = [];
-
- // clear open/close timeouts
- self.__timeoutsClear();
-
- var finishCallbacks = function() {
-
- // trigger any close method custom callbacks and reset them
- $.each(self.__callbacks.close, function(i,c) {
- c.call(self, self, {
- event: event,
- origin: self._$origin[0]
- });
- });
-
- self.__callbacks.close = [];
- };
-
- if (self.__state != 'closed') {
-
- var necessary = true,
- d = new Date(),
- now = d.getTime(),
- newClosingTime = now + self.__options.animationDuration[1];
-
- // the tooltip may already already be disappearing, but if a new
- // call to close() is made after the animationDuration was changed
- // to 0 (for example), we ought to actually close it sooner than
- // previously scheduled. In that case it should be noted that the
- // browser will not adapt the animation duration to the new
- // animationDuration that was set after the start of the closing
- // animation.
- // Note: the same thing could be considered at opening, but is not
- // really useful since the tooltip is actually opened immediately
- // upon a call to _open(). Since it would not make the opening
- // animation finish sooner, its sole impact would be to trigger the
- // state event and the open callbacks sooner than the actual end of
- // the opening animation, which is not great.
- if (self.__state == 'disappearing') {
-
- if ( newClosingTime > self.__closingTime
- // in case closing is actually overdue because the script
- // execution was suspended. See #679
- && self.__options.animationDuration[1] > 0
- ) {
- necessary = false;
- }
- }
-
- if (necessary) {
-
- self.__closingTime = newClosingTime;
-
- if (self.__state != 'disappearing') {
- self.__stateSet('disappearing');
- }
-
- var finish = function() {
-
- // stop the tracker
- clearInterval(self.__tracker);
-
- // a "beforeClose" option has been asked several times but would
- // probably useless since the content element is still accessible
- // via ::content(), and because people can always use listeners
- // inside their content to track what's going on. For the sake of
- // simplicity, this has been denied. Bur for the rare people who
- // really need the option (for old browsers or for the case where
- // detaching the content is actually destructive, for file or
- // password inputs for example), this event will do the work.
- self._trigger({
- type: 'closing',
- event: event
- });
-
- // unbind listeners which are no longer needed
-
- self._$tooltip
- .off('.'+ self.__namespace +'-triggerClose')
- .removeClass('tooltipster-dying');
-
- // orientationchange, scroll and resize listeners
- $(env.window).off('.'+ self.__namespace +'-triggerClose');
-
- // scroll listeners
- self.__$originParents.each(function(i, el) {
- $(el).off('scroll.'+ self.__namespace +'-triggerClose');
- });
- // clear the array to prevent memory leaks
- self.__$originParents = null;
-
- $(env.window.document.body).off('.'+ self.__namespace +'-triggerClose');
-
- self._$origin.off('.'+ self.__namespace +'-triggerClose');
-
- self._off('dismissable');
-
- // a plugin that would like to remove the tooltip from the
- // DOM when closed should bind on this
- self.__stateSet('closed');
-
- // trigger event
- self._trigger({
- type: 'after',
- event: event
- });
-
- // call our constructor custom callback function
- if (self.__options.functionAfter) {
- self.__options.functionAfter.call(self, self, {
- event: event,
- origin: self._$origin[0]
- });
- }
-
- // call our method custom callbacks functions
- finishCallbacks();
- };
-
- if (env.hasTransitions) {
-
- self._$tooltip.css({
- '-moz-animation-duration': self.__options.animationDuration[1] + 'ms',
- '-ms-animation-duration': self.__options.animationDuration[1] + 'ms',
- '-o-animation-duration': self.__options.animationDuration[1] + 'ms',
- '-webkit-animation-duration': self.__options.animationDuration[1] + 'ms',
- 'animation-duration': self.__options.animationDuration[1] + 'ms',
- 'transition-duration': self.__options.animationDuration[1] + 'ms'
- });
-
- self._$tooltip
- // clear both potential open and close tasks
- .clearQueue()
- .removeClass('tooltipster-show')
- // for transitions only
- .addClass('tooltipster-dying');
-
- if (self.__options.animationDuration[1] > 0) {
- self._$tooltip.delay(self.__options.animationDuration[1]);
- }
-
- self._$tooltip.queue(finish);
- }
- else {
-
- self._$tooltip
- .stop()
- .fadeOut(self.__options.animationDuration[1], finish);
- }
- }
- }
- // if the tooltip is already closed, we still need to trigger
- // the method custom callbacks
- else {
- finishCallbacks();
- }
- }
-
- return self;
- },
-
- /**
- * For internal use by plugins, if needed
- *
- * @returns {self}
- * @protected
- */
- _off: function() {
- this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * For internal use by plugins, if needed
- *
- * @returns {self}
- * @protected
- */
- _on: function() {
- this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * For internal use by plugins, if needed
- *
- * @returns {self}
- * @protected
- */
- _one: function() {
- this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
- return this;
- },
-
- /**
- * Opens the tooltip right away.
- *
- * @param event
- * @param callback Will be called when the opening animation is over
- * @returns {self}
- * @protected
- */
- _open: function(event, callback) {
-
- var self = this;
-
- // if the destruction process has not begun and if this was not
- // triggered by an unwanted emulated click event
- if (!self.__destroying) {
-
- // check that the origin is still in the DOM
- if ( bodyContains(self._$origin)
- // if the tooltip is enabled
- && self.__enabled
- ) {
-
- var ok = true;
-
- // if the tooltip is not open yet, we need to call functionBefore.
- // otherwise we can jst go on
- if (self.__state == 'closed') {
-
- // trigger an event. The event.stop function allows the callback
- // to prevent the opening of the tooltip
- self._trigger({
- type: 'before',
- event: event,
- stop: function() {
- ok = false;
- }
- });
-
- if (ok && self.__options.functionBefore) {
-
- // call our custom function before continuing
- ok = self.__options.functionBefore.call(self, self, {
- event: event,
- origin: self._$origin[0]
- });
- }
- }
-
- if (ok !== false) {
-
- // if there is some content
- if (self.__Content !== null) {
-
- // save the method callback and cancel close method callbacks
- if (callback) {
- self.__callbacks.open.push(callback);
- }
- self.__callbacks.close = [];
-
- // get rid of any appearance timeouts
- self.__timeoutsClear();
-
- var extraTime,
- finish = function() {
-
- if (self.__state != 'stable') {
- self.__stateSet('stable');
- }
-
- // trigger any open method custom callbacks and reset them
- $.each(self.__callbacks.open, function(i,c) {
- c.call(self, self, {
- origin: self._$origin[0],
- tooltip: self._$tooltip[0]
- });
- });
-
- self.__callbacks.open = [];
- };
-
- // if the tooltip is already open
- if (self.__state !== 'closed') {
-
- // the timer (if any) will start (or restart) right now
- extraTime = 0;
-
- // if it was disappearing, cancel that
- if (self.__state === 'disappearing') {
-
- self.__stateSet('appearing');
-
- if (env.hasTransitions) {
-
- self._$tooltip
- .clearQueue()
- .removeClass('tooltipster-dying')
- .addClass('tooltipster-show');
-
- if (self.__options.animationDuration[0] > 0) {
- self._$tooltip.delay(self.__options.animationDuration[0]);
- }
-
- self._$tooltip.queue(finish);
- }
- else {
- // in case the tooltip was currently fading out, bring it back
- // to life
- self._$tooltip
- .stop()
- .fadeIn(finish);
- }
- }
- // if the tooltip is already open, we still need to trigger the method
- // custom callback
- else if (self.__state == 'stable') {
- finish();
- }
- }
- // if the tooltip isn't already open, open it
- else {
-
- // a plugin must bind on this and store the tooltip in this._$tooltip
- self.__stateSet('appearing');
-
- // the timer (if any) will start when the tooltip has fully appeared
- // after its transition
- extraTime = self.__options.animationDuration[0];
-
- // insert the content inside the tooltip
- self.__contentInsert();
-
- // reposition the tooltip and attach to the DOM
- self.reposition(event, true);
-
- // animate in the tooltip. If the display plugin wants no css
- // animations, it may override the animation option with a
- // dummy value that will produce no effect
- if (env.hasTransitions) {
-
- // note: there seems to be an issue with start animations which
- // are randomly not played on fast devices in both Chrome and FF,
- // couldn't find a way to solve it yet. It seems that applying
- // the classes before appending to the DOM helps a little, but
- // it messes up some CSS transitions. The issue almost never
- // happens when delay[0]==0 though
- self._$tooltip
- .addClass('tooltipster-'+ self.__options.animation)
- .addClass('tooltipster-initial')
- .css({
- '-moz-animation-duration': self.__options.animationDuration[0] + 'ms',
- '-ms-animation-duration': self.__options.animationDuration[0] + 'ms',
- '-o-animation-duration': self.__options.animationDuration[0] + 'ms',
- '-webkit-animation-duration': self.__options.animationDuration[0] + 'ms',
- 'animation-duration': self.__options.animationDuration[0] + 'ms',
- 'transition-duration': self.__options.animationDuration[0] + 'ms'
- });
-
- setTimeout(
- function() {
-
- // a quick hover may have already triggered a mouseleave
- if (self.__state != 'closed') {
-
- self._$tooltip
- .addClass('tooltipster-show')
- .removeClass('tooltipster-initial');
-
- if (self.__options.animationDuration[0] > 0) {
- self._$tooltip.delay(self.__options.animationDuration[0]);
- }
-
- self._$tooltip.queue(finish);
- }
- },
- 0
- );
- }
- else {
-
- // old browsers will have to live with this
- self._$tooltip
- .css('display', 'none')
- .fadeIn(self.__options.animationDuration[0], finish);
- }
-
- // checks if the origin is removed while the tooltip is open
- self.__trackerStart();
-
- // NOTE: the listeners below have a '-triggerClose' namespace
- // because we'll remove them when the tooltip closes (unlike
- // the '-triggerOpen' listeners). So some of them are actually
- // not about close triggers, rather about positioning.
-
- $(env.window)
- // reposition on resize
- .on('resize.'+ self.__namespace +'-triggerClose', function(e) {
-
- var $ae = $(document.activeElement);
-
- // reposition only if the resize event was not triggered upon the opening
- // of a virtual keyboard due to an input field being focused within the tooltip
- // (otherwise the repositioning would lose the focus)
- if ( (!$ae.is('input') && !$ae.is('textarea'))
- || !$.contains(self._$tooltip[0], $ae[0])
- ) {
- self.reposition(e);
- }
- })
- // same as below for parents
- .on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
- self.__scrollHandler(e);
- });
-
- self.__$originParents = self._$origin.parents();
-
- // scrolling may require the tooltip to be moved or even
- // repositioned in some cases
- self.__$originParents.each(function(i, parent) {
-
- $(parent).on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
- self.__scrollHandler(e);
- });
- });
-
- if ( self.__options.triggerClose.mouseleave
- || (self.__options.triggerClose.touchleave && env.hasTouchCapability)
- ) {
-
- // we use an event to allow users/plugins to control when the mouseleave/touchleave
- // close triggers will come to action. It allows to have more triggering elements
- // than just the origin and the tooltip for example, or to cancel/delay the closing,
- // or to make the tooltip interactive even if it wasn't when it was open, etc.
- self._on('dismissable', function(event) {
-
- if (event.dismissable) {
-
- if (event.delay) {
-
- timeout = setTimeout(function() {
- // event.event may be undefined
- self._close(event.event);
- }, event.delay);
-
- self.__timeouts.close.push(timeout);
- }
- else {
- self._close(event);
- }
- }
- else {
- clearTimeout(timeout);
- }
- });
-
- // now set the listeners that will trigger 'dismissable' events
- var $elements = self._$origin,
- eventNamesIn = '',
- eventNamesOut = '',
- timeout = null;
-
- // if we have to allow interaction, bind on the tooltip too
- if (self.__options.interactive) {
- $elements = $elements.add(self._$tooltip);
- }
-
- if (self.__options.triggerClose.mouseleave) {
- eventNamesIn += 'mouseenter.'+ self.__namespace +'-triggerClose ';
- eventNamesOut += 'mouseleave.'+ self.__namespace +'-triggerClose ';
- }
- if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
- eventNamesIn += 'touchstart.'+ self.__namespace +'-triggerClose';
- eventNamesOut += 'touchend.'+ self.__namespace +'-triggerClose touchcancel.'+ self.__namespace +'-triggerClose';
- }
-
- $elements
- // close after some time spent outside of the elements
- .on(eventNamesOut, function(event) {
-
- // it's ok if the touch gesture ended up to be a swipe,
- // it's still a "touch leave" situation
- if ( self._touchIsTouchEvent(event)
- || !self._touchIsEmulatedEvent(event)
- ) {
-
- var delay = (event.type == 'mouseleave') ?
- self.__options.delay :
- self.__options.delayTouch;
-
- self._trigger({
- delay: delay[1],
- dismissable: true,
- event: event,
- type: 'dismissable'
- });
- }
- })
- // suspend the mouseleave timeout when the pointer comes back
- // over the elements
- .on(eventNamesIn, function(event) {
-
- // it's also ok if the touch event is a swipe gesture
- if ( self._touchIsTouchEvent(event)
- || !self._touchIsEmulatedEvent(event)
- ) {
- self._trigger({
- dismissable: false,
- event: event,
- type: 'dismissable'
- });
- }
- });
- }
-
- // close the tooltip when the origin gets a mouse click (common behavior of
- // native tooltips)
- if (self.__options.triggerClose.originClick) {
-
- self._$origin.on('click.'+ self.__namespace + '-triggerClose', function(event) {
-
- // we could actually let a tap trigger this but this feature just
- // does not make sense on touch devices
- if ( !self._touchIsTouchEvent(event)
- && !self._touchIsEmulatedEvent(event)
- ) {
- self._close(event);
- }
- });
- }
-
- // set the same bindings for click and touch on the body to close the tooltip
- if ( self.__options.triggerClose.click
- || (self.__options.triggerClose.tap && env.hasTouchCapability)
- ) {
-
- // don't set right away since the click/tap event which triggered this method
- // (if it was a click/tap) is going to bubble up to the body, we don't want it
- // to close the tooltip immediately after it opened
- setTimeout(function() {
-
- if (self.__state != 'closed') {
-
- var eventNames = '',
- $body = $(env.window.document.body);
-
- if (self.__options.triggerClose.click) {
- eventNames += 'click.'+ self.__namespace +'-triggerClose ';
- }
- if (self.__options.triggerClose.tap && env.hasTouchCapability) {
- eventNames += 'touchend.'+ self.__namespace +'-triggerClose';
- }
-
- $body.on(eventNames, function(event) {
-
- if (self._touchIsMeaningfulEvent(event)) {
-
- self._touchRecordEvent(event);
-
- if (!self.__options.interactive || !$.contains(self._$tooltip[0], event.target)) {
- self._close(event);
- }
- }
- });
-
- // needed to detect and ignore swiping
- if (self.__options.triggerClose.tap && env.hasTouchCapability) {
-
- $body.on('touchstart.'+ self.__namespace +'-triggerClose', function(event) {
- self._touchRecordEvent(event);
- });
- }
- }
- }, 0);
- }
-
- self._trigger('ready');
-
- // call our custom callback
- if (self.__options.functionReady) {
- self.__options.functionReady.call(self, self, {
- origin: self._$origin[0],
- tooltip: self._$tooltip[0]
- });
- }
- }
-
- // if we have a timer set, let the countdown begin
- if (self.__options.timer > 0) {
-
- var timeout = setTimeout(function() {
- self._close();
- }, self.__options.timer + extraTime);
-
- self.__timeouts.close.push(timeout);
- }
- }
- }
- }
- }
-
- return self;
- },
-
- /**
- * When using the mouseenter/touchstart open triggers, this function will
- * schedule the opening of the tooltip after the delay, if there is one
- *
- * @param event
- * @returns {self}
- * @protected
- */
- _openShortly: function(event) {
-
- var self = this,
- ok = true;
-
- if (self.__state != 'stable' && self.__state != 'appearing') {
-
- // if a timeout is not already running
- if (!self.__timeouts.open) {
-
- self._trigger({
- type: 'start',
- event: event,
- stop: function() {
- ok = false;
- }
- });
-
- if (ok) {
-
- var delay = (event.type.indexOf('touch') == 0) ?
- self.__options.delayTouch :
- self.__options.delay;
-
- if (delay[0]) {
-
- self.__timeouts.open = setTimeout(function() {
-
- self.__timeouts.open = null;
-
- // open only if the pointer (mouse or touch) is still over the origin.
- // The check on the "meaningful event" can only be made here, after some
- // time has passed (to know if the touch was a swipe or not)
- if (self.__pointerIsOverOrigin && self._touchIsMeaningfulEvent(event)) {
-
- // signal that we go on
- self._trigger('startend');
-
- self._open(event);
- }
- else {
- // signal that we cancel
- self._trigger('startcancel');
- }
- }, delay[0]);
- }
- else {
- // signal that we go on
- self._trigger('startend');
-
- self._open(event);
- }
- }
- }
- }
-
- return self;
- },
-
- /**
- * Meant for plugins to get their options
- *
- * @param {string} pluginName The name of the plugin that asks for its options
- * @param {object} defaultOptions The default options of the plugin
- * @returns {object} The options
- * @protected
- */
- _optionsExtract: function(pluginName, defaultOptions) {
-
- var self = this,
- options = $.extend(true, {}, defaultOptions);
-
- // if the plugin options were isolated in a property named after the
- // plugin, use them (prevents conflicts with other plugins)
- var pluginOptions = self.__options[pluginName];
-
- // if not, try to get them as regular options
- if (!pluginOptions){
-
- pluginOptions = {};
-
- $.each(defaultOptions, function(optionName, value) {
-
- var o = self.__options[optionName];
-
- if (o !== undefined) {
- pluginOptions[optionName] = o;
- }
- });
- }
-
- // let's merge the default options and the ones that were provided. We'd want
- // to do a deep copy but not let jQuery merge arrays, so we'll do a shallow
- // extend on two levels, that will be enough if options are not more than 1
- // level deep
- $.each(options, function(optionName, value) {
-
- if (pluginOptions[optionName] !== undefined) {
-
- if (( typeof value == 'object'
- && !(value instanceof Array)
- && value != null
- )
- &&
- ( typeof pluginOptions[optionName] == 'object'
- && !(pluginOptions[optionName] instanceof Array)
- && pluginOptions[optionName] != null
- )
- ) {
- $.extend(options[optionName], pluginOptions[optionName]);
- }
- else {
- options[optionName] = pluginOptions[optionName];
- }
- }
- });
-
- return options;
- },
-
- /**
- * Used at instantiation of the plugin, or afterwards by plugins that activate themselves
- * on existing instances
- *
- * @param {object} pluginName
- * @returns {self}
- * @protected
- */
- _plug: function(pluginName) {
-
- var plugin = $.tooltipster._plugin(pluginName);
-
- if (plugin) {
-
- // if there is a constructor for instances
- if (plugin.instance) {
-
- // proxy non-private methods on the instance to allow new instance methods
- $.tooltipster.__bridge(plugin.instance, this, plugin.name);
- }
- }
- else {
- throw new Error('The "'+ pluginName +'" plugin is not defined');
- }
-
- return this;
- },
-
- /**
- * This will return true if the event is a mouse event which was
- * emulated by the browser after a touch event. This allows us to
- * really dissociate mouse and touch triggers.
- *
- * There is a margin of error if a real mouse event is fired right
- * after (within the delay shown below) a touch event on the same
- * element, but hopefully it should not happen often.
- *
- * @returns {boolean}
- * @protected
- */
- _touchIsEmulatedEvent: function(event) {
-
- var isEmulated = false,
- now = new Date().getTime();
-
- for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
-
- var e = this.__touchEvents[i];
-
- // delay, in milliseconds. It's supposed to be 300ms in
- // most browsers (350ms on iOS) to allow a double tap but
- // can be less (check out FastClick for more info)
- if (now - e.time < 500) {
-
- if (e.target === event.target) {
- isEmulated = true;
- }
- }
- else {
- break;
- }
- }
-
- return isEmulated;
- },
-
- /**
- * Returns false if the event was an emulated mouse event or
- * a touch event involved in a swipe gesture.
- *
- * @param {object} event
- * @returns {boolean}
- * @protected
- */
- _touchIsMeaningfulEvent: function(event) {
- return (
- (this._touchIsTouchEvent(event) && !this._touchSwiped(event.target))
- || (!this._touchIsTouchEvent(event) && !this._touchIsEmulatedEvent(event))
- );
- },
-
- /**
- * Checks if an event is a touch event
- *
- * @param {object} event
- * @returns {boolean}
- * @protected
- */
- _touchIsTouchEvent: function(event){
- return event.type.indexOf('touch') == 0;
- },
-
- /**
- * Store touch events for a while to detect swiping and emulated mouse events
- *
- * @param {object} event
- * @returns {self}
- * @protected
- */
- _touchRecordEvent: function(event) {
-
- if (this._touchIsTouchEvent(event)) {
- event.time = new Date().getTime();
- this.__touchEvents.push(event);
- }
-
- return this;
- },
-
- /**
- * Returns true if a swipe happened after the last touchstart event fired on
- * event.target.
- *
- * We need to differentiate a swipe from a tap before we let the event open
- * or close the tooltip. A swipe is when a touchmove (scroll) event happens
- * on the body between the touchstart and the touchend events of an element.
- *
- * @param {object} target The HTML element that may have triggered the swipe
- * @returns {boolean}
- * @protected
- */
- _touchSwiped: function(target) {
-
- var swiped = false;
-
- for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
-
- var e = this.__touchEvents[i];
-
- if (e.type == 'touchmove') {
- swiped = true;
- break;
- }
- else if (
- e.type == 'touchstart'
- && target === e.target
- ) {
- break;
- }
- }
-
- return swiped;
- },
-
- /**
- * Triggers an event on the instance emitters
- *
- * @returns {self}
- * @protected
- */
- _trigger: function() {
-
- var args = Array.prototype.slice.apply(arguments);
-
- if (typeof args[0] == 'string') {
- args[0] = { type: args[0] };
- }
-
- // add properties to the event
- args[0].instance = this;
- args[0].origin = this._$origin ? this._$origin[0] : null;
- args[0].tooltip = this._$tooltip ? this._$tooltip[0] : null;
-
- // note: the order of emitters matters
- this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
- $.tooltipster._trigger.apply($.tooltipster, args);
- this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
-
- return this;
- },
-
- /**
- * Deactivate a plugin on this instance
- *
- * @returns {self}
- * @protected
- */
- _unplug: function(pluginName) {
-
- var self = this;
-
- // if the plugin has been activated on this instance
- if (self[pluginName]) {
-
- var plugin = $.tooltipster._plugin(pluginName);
-
- // if there is a constructor for instances
- if (plugin.instance) {
-
- // unbridge
- $.each(plugin.instance, function(methodName, fn) {
-
- // if the method exists (privates methods do not) and comes indeed from
- // this plugin (may be missing or come from a conflicting plugin).
- if ( self[methodName]
- && self[methodName].bridged === self[pluginName]
- ) {
- delete self[methodName];
- }
- });
- }
-
- // destroy the plugin
- if (self[pluginName].__destroy) {
- self[pluginName].__destroy();
- }
-
- // remove the reference to the plugin instance
- delete self[pluginName];
- }
-
- return self;
- },
-
- /**
- * @see self::_close
- * @returns {self}
- * @public
- */
- close: function(callback) {
-
- if (!this.__destroyed) {
- this._close(null, callback);
- }
- else {
- this.__destroyError();
- }
-
- return this;
- },
-
- /**
- * Sets or gets the content of the tooltip
- *
- * @returns {mixed|self}
- * @public
- */
- content: function(content) {
-
- var self = this;
-
- // getter method
- if (content === undefined) {
- return self.__Content;
- }
- // setter method
- else {
-
- if (!self.__destroyed) {
-
- // change the content
- self.__contentSet(content);
-
- if (self.__Content !== null) {
-
- // update the tooltip if it is open
- if (self.__state !== 'closed') {
-
- // reset the content in the tooltip
- self.__contentInsert();
-
- // reposition and resize the tooltip
- self.reposition();
-
- // if we want to play a little animation showing the content changed
- if (self.__options.updateAnimation) {
-
- if (env.hasTransitions) {
-
- // keep the reference in the local scope
- var animation = self.__options.updateAnimation;
-
- self._$tooltip.addClass('tooltipster-update-'+ animation);
-
- // remove the class after a while. The actual duration of the
- // update animation may be shorter, it's set in the CSS rules
- setTimeout(function() {
-
- if (self.__state != 'closed') {
-
- self._$tooltip.removeClass('tooltipster-update-'+ animation);
- }
- }, 1000);
- }
- else {
- self._$tooltip.fadeTo(200, 0.5, function() {
- if (self.__state != 'closed') {
- self._$tooltip.fadeTo(200, 1);
- }
- });
- }
- }
- }
- }
- else {
- self._close();
- }
- }
- else {
- self.__destroyError();
- }
-
- return self;
- }
- },
-
- /**
- * Destroys the tooltip
- *
- * @returns {self}
- * @public
- */
- destroy: function() {
-
- var self = this;
-
- if (!self.__destroyed) {
-
- if(self.__state != 'closed'){
-
- // no closing delay
- self.option('animationDuration', 0)
- // force closing
- ._close(null, null, true);
- }
- else {
- // there might be an open timeout still running
- self.__timeoutsClear();
- }
-
- // send event
- self._trigger('destroy');
-
- self.__destroyed = true;
-
- self._$origin
- .removeData(self.__namespace)
- // remove the open trigger listeners
- .off('.'+ self.__namespace +'-triggerOpen');
-
- // remove the touch listener
- $(env.window.document.body).off('.' + self.__namespace +'-triggerOpen');
-
- var ns = self._$origin.data('tooltipster-ns');
-
- // if the origin has been removed from DOM, its data may
- // well have been destroyed in the process and there would
- // be nothing to clean up or restore
- if (ns) {
-
- // if there are no more tooltips on this element
- if (ns.length === 1) {
-
- // optional restoration of a title attribute
- var title = null;
- if (self.__options.restoration == 'previous') {
- title = self._$origin.data('tooltipster-initialTitle');
- }
- else if (self.__options.restoration == 'current') {
-
- // old school technique to stringify when outerHTML is not supported
- title = (typeof self.__Content == 'string') ?
- self.__Content :
- $('<div></div>').append(self.__Content).html();
- }
-
- if (title) {
- self._$origin.attr('title', title);
- }
-
- // final cleaning
-
- self._$origin.removeClass('tooltipstered');
-
- self._$origin
- .removeData('tooltipster-ns')
- .removeData('tooltipster-initialTitle');
- }
- else {
- // remove the instance namespace from the list of namespaces of
- // tooltips present on the element
- ns = $.grep(ns, function(el, i) {
- return el !== self.__namespace;
- });
- self._$origin.data('tooltipster-ns', ns);
- }
- }
-
- // last event
- self._trigger('destroyed');
-
- // unbind private and public event listeners
- self._off();
- self.off();
-
- // remove external references, just in case
- self.__Content = null;
- self.__$emitterPrivate = null;
- self.__$emitterPublic = null;
- self.__options.parent = null;
- self._$origin = null;
- self._$tooltip = null;
-
- // make sure the object is no longer referenced in there to prevent
- // memory leaks
- $.tooltipster.__instancesLatestArr = $.grep($.tooltipster.__instancesLatestArr, function(el, i) {
- return self !== el;
- });
-
- clearInterval(self.__garbageCollector);
- }
- else {
- self.__destroyError();
- }
-
- // we return the scope rather than true so that the call to
- // .tooltipster('destroy') actually returns the matched elements
- // and applies to all of them
- return self;
- },
-
- /**
- * Disables the tooltip
- *
- * @returns {self}
- * @public
- */
- disable: function() {
-
- if (!this.__destroyed) {
-
- // close first, in case the tooltip would not disappear on
- // its own (no close trigger)
- this._close();
- this.__enabled = false;
-
- return this;
- }
- else {
- this.__destroyError();
- }
-
- return this;
- },
-
- /**
- * Returns the HTML element of the origin
- *
- * @returns {self}
- * @public
- */
- elementOrigin: function() {
-
- if (!this.__destroyed) {
- return this._$origin[0];
- }
- else {
- this.__destroyError();
- }
- },
-
- /**
- * Returns the HTML element of the tooltip
- *
- * @returns {self}
- * @public
- */
- elementTooltip: function() {
- return this._$tooltip ? this._$tooltip[0] : null;
- },
-
- /**
- * Enables the tooltip
- *
- * @returns {self}
- * @public
- */
- enable: function() {
- this.__enabled = true;
- return this;
- },
-
- /**
- * Alias, deprecated in 4.0.0
- *
- * @param {function} callback
- * @returns {self}
- * @public
- */
- hide: function(callback) {
- return this.close(callback);
- },
-
- /**
- * Returns the instance
- *
- * @returns {self}
- * @public
- */
- instance: function() {
- return this;
- },
-
- /**
- * For public use only, not to be used by plugins (use ::_off() instead)
- *
- * @returns {self}
- * @public
- */
- off: function() {
-
- if (!this.__destroyed) {
- this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- }
-
- return this;
- },
-
- /**
- * For public use only, not to be used by plugins (use ::_on() instead)
- *
- * @returns {self}
- * @public
- */
- on: function() {
-
- if (!this.__destroyed) {
- this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- }
- else {
- this.__destroyError();
- }
-
- return this;
- },
-
- /**
- * For public use only, not to be used by plugins
- *
- * @returns {self}
- * @public
- */
- one: function() {
-
- if (!this.__destroyed) {
- this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- }
- else {
- this.__destroyError();
- }
-
- return this;
- },
-
- /**
- * @see self::_open
- * @returns {self}
- * @public
- */
- open: function(callback) {
-
- if (!this.__destroyed) {
- this._open(null, callback);
- }
- else {
- this.__destroyError();
- }
-
- return this;
- },
-
- /**
- * Get or set options. For internal use and advanced users only.
- *
- * @param {string} o Option name
- * @param {mixed} val optional A new value for the option
- * @return {mixed|self} If val is omitted, the value of the option
- * is returned, otherwise the instance itself is returned
- * @public
- */
- option: function(o, val) {
-
- // getter
- if (val === undefined) {
- return this.__options[o];
- }
- // setter
- else {
-
- if (!this.__destroyed) {
-
- // change value
- this.__options[o] = val;
-
- // format
- this.__optionsFormat();
-
- // re-prepare the triggers if needed
- if ($.inArray(o, ['trigger', 'triggerClose', 'triggerOpen']) >= 0) {
- this.__prepareOrigin();
- }
-
- if (o === 'selfDestruction') {
- this.__prepareGC();
- }
- }
- else {
- this.__destroyError();
- }
-
- return this;
- }
- },
-
- /**
- * This method is in charge of setting the position and size properties of the tooltip.
- * All the hard work is delegated to the display plugin.
- * Note: The tooltip may be detached from the DOM at the moment the method is called
- * but must be attached by the end of the method call.
- *
- * @param {object} event For internal use only. Defined if an event such as
- * window resizing triggered the repositioning
- * @param {boolean} tooltipIsDetached For internal use only. Set this to true if you
- * know that the tooltip not being in the DOM is not an issue (typically when the
- * tooltip element has just been created but has not been added to the DOM yet).
- * @returns {self}
- * @public
- */
- reposition: function(event, tooltipIsDetached) {
-
- var self = this;
-
- if (!self.__destroyed) {
-
- // if the tooltip is still open and the origin is still in the DOM
- if (self.__state != 'closed' && bodyContains(self._$origin)) {
-
- // if the tooltip has not been removed from DOM manually (or if it
- // has been detached on purpose)
- if (tooltipIsDetached || bodyContains(self._$tooltip)) {
-
- if (!tooltipIsDetached) {
- // detach in case the tooltip overflows the window and adds
- // scrollbars to it, so __geometry can be accurate
- self._$tooltip.detach();
- }
-
- // refresh the geometry object before passing it as a helper
- self.__Geometry = self.__geometry();
-
- // let a plugin fo the rest
- self._trigger({
- type: 'reposition',
- event: event,
- helper: {
- geo: self.__Geometry
- }
- });
- }
- }
- }
- else {
- self.__destroyError();
- }
-
- return self;
- },
-
- /**
- * Alias, deprecated in 4.0.0
- *
- * @param callback
- * @returns {self}
- * @public
- */
- show: function(callback) {
- return this.open(callback);
- },
-
- /**
- * Returns some properties about the instance
- *
- * @returns {object}
- * @public
- */
- status: function() {
-
- return {
- destroyed: this.__destroyed,
- enabled: this.__enabled,
- open: this.__state !== 'closed',
- state: this.__state
- };
- },
-
- /**
- * For public use only, not to be used by plugins
- *
- * @returns {self}
- * @public
- */
- triggerHandler: function() {
-
- if (!this.__destroyed) {
- this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
- }
- else {
- this.__destroyError();
- }
-
- return this;
- }
- };
- $.fn.tooltipster = function() {
-
- // for using in closures
- var args = Array.prototype.slice.apply(arguments),
- // common mistake: an HTML element can't be in several tooltips at the same time
- contentCloningWarning = 'You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.';
-
- // this happens with $(sel).tooltipster(...) when $(sel) does not match anything
- if (this.length === 0) {
-
- // still chainable
- return this;
- }
- // this happens when calling $(sel).tooltipster('methodName or options')
- // where $(sel) matches one or more elements
- else {
-
- // method calls
- if (typeof args[0] === 'string') {
-
- var v = '#*$~&';
-
- this.each(function() {
-
- // retrieve the namepaces of the tooltip(s) that exist on that element.
- // We will interact with the first tooltip only.
- var ns = $(this).data('tooltipster-ns'),
- // self represents the instance of the first tooltipster plugin
- // associated to the current HTML object of the loop
- self = ns ? $(this).data(ns[0]) : null;
-
- // if the current element holds a tooltipster instance
- if (self) {
-
- if (typeof self[args[0]] === 'function') {
-
- if ( this.length > 1
- && args[0] == 'content'
- && ( args[1] instanceof $
- || (typeof args[1] == 'object' && args[1] != null && args[1].tagName)
- )
- && !self.__options.contentCloning
- && self.__options.debug
- ) {
- console.log(contentCloningWarning);
- }
-
- // note : args[1] and args[2] may not be defined
- var resp = self[args[0]](args[1], args[2]);
- }
- else {
- throw new Error('Unknown method "'+ args[0] +'"');
- }
-
- // if the function returned anything other than the instance
- // itself (which implies chaining, except for the `instance` method)
- if (resp !== self || args[0] === 'instance') {
-
- v = resp;
-
- // return false to stop .each iteration on the first element
- // matched by the selector
- return false;
- }
- }
- else {
- throw new Error('You called Tooltipster\'s "'+ args[0] +'" method on an uninitialized element');
- }
- });
-
- return (v !== '#*$~&') ? v : this;
- }
- // first argument is undefined or an object: the tooltip is initializing
- else {
-
- // reset the array of last initialized objects
- $.tooltipster.__instancesLatestArr = [];
-
- // is there a defined value for the multiple option in the options object ?
- var multipleIsSet = args[0] && args[0].multiple !== undefined,
- // if the multiple option is set to true, or if it's not defined but
- // set to true in the defaults
- multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
- // same for content
- contentIsSet = args[0] && args[0].content !== undefined,
- content = (contentIsSet && args[0].content) || (!contentIsSet && defaults.content),
- // same for contentCloning
- contentCloningIsSet = args[0] && args[0].contentCloning !== undefined,
- contentCloning =
- (contentCloningIsSet && args[0].contentCloning)
- || (!contentCloningIsSet && defaults.contentCloning),
- // same for debug
- debugIsSet = args[0] && args[0].debug !== undefined,
- debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
-
- if ( this.length > 1
- && ( content instanceof $
- || (typeof content == 'object' && content != null && content.tagName)
- )
- && !contentCloning
- && debug
- ) {
- console.log(contentCloningWarning);
- }
-
- // create a tooltipster instance for each element if it doesn't
- // already have one or if the multiple option is set, and attach the
- // object to it
- this.each(function() {
-
- var go = false,
- $this = $(this),
- ns = $this.data('tooltipster-ns'),
- obj = null;
-
- if (!ns) {
- go = true;
- }
- else if (multiple) {
- go = true;
- }
- else if (debug) {
- console.log('Tooltipster: one or more tooltips are already attached to the element below. Ignoring.');
- console.log(this);
- }
-
- if (go) {
- obj = new $.Tooltipster(this, args[0]);
-
- // save the reference of the new instance
- if (!ns) ns = [];
- ns.push(obj.__namespace);
- $this.data('tooltipster-ns', ns);
-
- // save the instance itself
- $this.data(obj.__namespace, obj);
-
- // call our constructor custom function.
- // we do this here and not in ::init() because we wanted
- // the object to be saved in $this.data before triggering
- // it
- if (obj.__options.functionInit) {
- obj.__options.functionInit.call(obj, obj, {
- origin: this
- });
- }
-
- // and now the event, for the plugins and core emitter
- obj._trigger('init');
- }
-
- $.tooltipster.__instancesLatestArr.push(obj);
- });
-
- return this;
- }
- }
- };
- // Utilities
- /**
- * A class to check if a tooltip can fit in given dimensions
- *
- * @param {object} $tooltip The jQuery wrapped tooltip element, or a clone of it
- */
- function Ruler($tooltip) {
-
- // list of instance variables
-
- this.$container;
- this.constraints = null;
- this.__$tooltip;
-
- this.__init($tooltip);
- }
- Ruler.prototype = {
-
- /**
- * Move the tooltip into an invisible div that does not allow overflow to make
- * size tests. Note: the tooltip may or may not be attached to the DOM at the
- * moment this method is called, it does not matter.
- *
- * @param {object} $tooltip The object to test. May be just a clone of the
- * actual tooltip.
- * @private
- */
- __init: function($tooltip) {
-
- this.__$tooltip = $tooltip;
-
- this.__$tooltip
- .css({
- // for some reason we have to specify top and left 0
- left: 0,
- // any overflow will be ignored while measuring
- overflow: 'hidden',
- // positions at (0,0) without the div using 100% of the available width
- position: 'absolute',
- top: 0
- })
- // overflow must be auto during the test. We re-set this in case
- // it were modified by the user
- .find('.tooltipster-content')
- .css('overflow', 'auto');
-
- this.$container = $('<div class="tooltipster-ruler"></div>')
- .append(this.__$tooltip)
- .appendTo(env.window.document.body);
- },
-
- /**
- * Force the browser to redraw (re-render) the tooltip immediately. This is required
- * when you changed some CSS properties and need to make something with it
- * immediately, without waiting for the browser to redraw at the end of instructions.
- *
- * @see http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
- * @private
- */
- __forceRedraw: function() {
-
- // note: this would work but for Webkit only
- //this.__$tooltip.close();
- //this.__$tooltip[0].offsetHeight;
- //this.__$tooltip.open();
-
- // works in FF too
- var $p = this.__$tooltip.parent();
- this.__$tooltip.detach();
- this.__$tooltip.appendTo($p);
- },
-
- /**
- * Set maximum dimensions for the tooltip. A call to ::measure afterwards
- * will tell us if the content overflows or if it's ok
- *
- * @param {int} width
- * @param {int} height
- * @return {Ruler}
- * @public
- */
- constrain: function(width, height) {
-
- this.constraints = {
- width: width,
- height: height
- };
-
- this.__$tooltip.css({
- // we disable display:flex, otherwise the content would overflow without
- // creating horizontal scrolling (which we need to detect).
- display: 'block',
- // reset any previous height
- height: '',
- // we'll check if horizontal scrolling occurs
- overflow: 'auto',
- // we'll set the width and see what height is generated and if there
- // is horizontal overflow
- width: width
- });
-
- return this;
- },
-
- /**
- * Reset the tooltip content overflow and remove the test container
- *
- * @returns {Ruler}
- * @public
- */
- destroy: function() {
-
- // in case the element was not a clone
- this.__$tooltip
- .detach()
- .find('.tooltipster-content')
- .css({
- // reset to CSS value
- display: '',
- overflow: ''
- });
-
- this.$container.remove();
- },
-
- /**
- * Removes any constraints
- *
- * @returns {Ruler}
- * @public
- */
- free: function() {
-
- this.constraints = null;
-
- // reset to natural size
- this.__$tooltip.css({
- display: '',
- height: '',
- overflow: 'visible',
- width: ''
- });
-
- return this;
- },
-
- /**
- * Returns the size of the tooltip. When constraints are applied, also returns
- * whether the tooltip fits in the provided dimensions.
- * The idea is to see if the new height is small enough and if the content does
- * not overflow horizontally.
- *
- * @param {int} width
- * @param {int} height
- * @returns {object} An object with a bool `fits` property and a `size` property
- * @public
- */
- measure: function() {
-
- this.__forceRedraw();
-
- var tooltipBcr = this.__$tooltip[0].getBoundingClientRect(),
- result = { size: {
- // bcr.width/height are not defined in IE8- but in this
- // case, bcr.right/bottom will have the same value
- // except in iOS 8+ where tooltipBcr.bottom/right are wrong
- // after scrolling for reasons yet to be determined.
- // tooltipBcr.top/left might not be 0, see issue #514
- height: tooltipBcr.height || (tooltipBcr.bottom - tooltipBcr.top),
- width: tooltipBcr.width || (tooltipBcr.right - tooltipBcr.left)
- }};
-
- if (this.constraints) {
-
- // note: we used to use offsetWidth instead of boundingRectClient but
- // it returned rounded values, causing issues with sub-pixel layouts.
-
- // note2: noticed that the bcrWidth of text content of a div was once
- // greater than the bcrWidth of its container by 1px, causing the final
- // tooltip box to be too small for its content. However, evaluating
- // their widths one against the other (below) surprisingly returned
- // equality. Happened only once in Chrome 48, was not able to reproduce
- // => just having fun with float position values...
-
- var $content = this.__$tooltip.find('.tooltipster-content'),
- height = this.__$tooltip.outerHeight(),
- contentBcr = $content[0].getBoundingClientRect(),
- fits = {
- height: height <= this.constraints.height,
- width: (
- // this condition accounts for min-width property that
- // may apply
- tooltipBcr.width <= this.constraints.width
- // the -1 is here because scrollWidth actually returns
- // a rounded value, and may be greater than bcr.width if
- // it was rounded up. This may cause an issue for contents
- // which actually really overflow by 1px or so, but that
- // should be rare. Not sure how to solve this efficiently.
- // See http://blogs.msdn.com/b/ie/archive/2012/02/17/sub-pixel-rendering-and-the-css-object-model.aspx
- && contentBcr.width >= $content[0].scrollWidth - 1
- )
- };
-
- result.fits = fits.height && fits.width;
- }
-
- // old versions of IE get the width wrong for some reason and it causes
- // the text to be broken to a new line, so we round it up. If the width
- // is the width of the screen though, we can assume it is accurate.
- if ( env.IE
- && env.IE <= 11
- && result.size.width !== env.window.document.documentElement.clientWidth
- ) {
- result.size.width = Math.ceil(result.size.width) + 1;
- }
-
- return result;
- }
- };
- // quick & dirty compare function, not bijective nor multidimensional
- function areEqual(a,b) {
- var same = true;
- $.each(a, function(i, _) {
- if (b[i] === undefined || a[i] !== b[i]) {
- same = false;
- return false;
- }
- });
- return same;
- }
- /**
- * A fast function to check if an element is still in the DOM. It
- * tries to use an id as ids are indexed by the browser, or falls
- * back to jQuery's `contains` method. May fail if two elements
- * have the same id, but so be it
- *
- * @param {object} $obj A jQuery-wrapped HTML element
- * @return {boolean}
- */
- function bodyContains($obj) {
- var id = $obj.attr('id'),
- el = id ? env.window.document.getElementById(id) : null;
- // must also check that the element with the id is the one we want
- return el ? el === $obj[0] : $.contains(env.window.document.body, $obj[0]);
- }
- // detect IE versions for dirty fixes
- var uA = navigator.userAgent.toLowerCase();
- if (uA.indexOf('msie') != -1) env.IE = parseInt(uA.split('msie')[1]);
- else if (uA.toLowerCase().indexOf('trident') !== -1 && uA.indexOf(' rv:11') !== -1) env.IE = 11;
- else if (uA.toLowerCase().indexOf('edge/') != -1) env.IE = parseInt(uA.toLowerCase().split('edge/')[1]);
- // detecting support for CSS transitions
- function transitionSupport() {
-
- // env.window is not defined yet when this is called
- if (!win) return false;
-
- var b = win.document.body || win.document.documentElement,
- s = b.style,
- p = 'transition',
- v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
-
- if (typeof s[p] == 'string') { return true; }
-
- p = p.charAt(0).toUpperCase() + p.substr(1);
- for (var i=0; i<v.length; i++) {
- if (typeof s[v[i] + p] == 'string') { return true; }
- }
- return false;
- }
- // we'll return jQuery for plugins not to have to declare it as a dependency,
- // but it's done by a build task since it should be included only once at the
- // end when we concatenate the main file with a pluginreturn $;
- }));
|