From a0ea0add66d270acaf3d72cd8dd70de302ad1739 Mon Sep 17 00:00:00 2001 From: Craig Michael Thompson Date: Sun, 28 Jul 2013 10:27:08 +0100 Subject: [PATCH] Optimize initial rendering methods and related code. Fixes #566 --- src/core/class.js | 3 +- src/core/constants.js | 2 + src/core/events.js | 102 +++++++++++++++++++++++------- src/core/jquery_methods.js | 123 ++++++++++--------------------------- src/core/position.js | 2 +- src/tips/tips.js | 7 ++- 6 files changed, 120 insertions(+), 119 deletions(-) diff --git a/src/core/class.js b/src/core/class.js index 8c2587a6..9647d020 100644 --- a/src/core/class.js +++ b/src/core/class.js @@ -110,7 +110,8 @@ PROTOTYPE.render = function(show) { } }); - // Assign events + // Unassign initial events and assign proper events + this._unassignEvents(); this._assignEvents(); // When deferreds have completed diff --git a/src/core/constants.js b/src/core/constants.js index 38c2b98c..171912cc 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -40,6 +40,8 @@ replaceSuffix = '_replacedByqTip', oldtitle = 'oldtitle', trackingBound, +docBody, // set in jquery_methods.init + // Browser detection BROWSER = { /* diff --git a/src/core/events.js b/src/core/events.js index 53daae9f..37d02966 100644 --- a/src/core/events.js +++ b/src/core/events.js @@ -1,3 +1,13 @@ +function delay(callback, duration) { + // If tooltip has displayed, start hide timer + if(duration > 0) { + return setTimeout( + $.proxy(callback, this), duration + ); + } + else{ callback.call(this); } +} + function showMethod(event) { if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; } @@ -6,11 +16,10 @@ function showMethod(event) { clearTimeout(this.timers.hide); // Start show timer - var callback = $.proxy(function(){ this.toggle(TRUE, event); }, this); - if(this.options.show.delay > 0) { - this.timers.show = setTimeout(callback, this.options.show.delay); - } - else{ callback(); } + this.timers.show = delay.call(this, + function() { this.toggle(TRUE, event); }, + this.options.show.delay + ); } function hideMethod(event) { @@ -42,11 +51,11 @@ function hideMethod(event) { } // If tooltip has displayed, start hide timer - var callback = $.proxy(function(){ this.toggle(FALSE, event); }, this); - if(this.options.hide.delay > 0) { - this.timers.hide = setTimeout(callback, this.options.hide.delay); - } - else{ callback(); } + this.timers.hide = delay.call(this, + function() { this.toggle(FALSE, event); }, + this.options.hide.delay, + this + ); } function inactiveMethod(event) { @@ -54,8 +63,10 @@ function inactiveMethod(event) { // Clear timer clearTimeout(this.timers.inactive); - this.timers.inactive = setTimeout( - $.proxy(function(){ this.hide(event); }, this), this.options.hide.inactive + + this.timers.inactive = delay.call(this, + function(){ this.hide(event); }, + this.options.hide.inactive ); } @@ -89,7 +100,7 @@ PROTOTYPE._unbind = function(targets, suffix) { // Apply common event handlers using delegate (avoids excessive .bind calls!) var ns = '.'+NAMESPACE; function delegate(selector, events, method) { - $(document.body).delegate(selector, + docBody.delegate(selector, (events.split ? events : events.join(ns + ' ')) + ns, function() { var api = QTIP.api[ $.attr(this, ATTR_ID) ]; @@ -143,6 +154,59 @@ PROTOTYPE._trigger = function(type, args, event) { return !callback.isDefaultPrevented(); }; +PROTOTYPE._assignInitialEvents = function(event) { + var options = this.options, + showTarget = options.show.target, + hideTarget = options.hide.target, + showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], + hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; + + /* + * Make sure hoverIntent functions properly by using mouseleave as a hide event if + * mouseenter/mouseout is used for show.event, even if it isn't in the users options. + */ + if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) { + hideEvents.push('mouseleave'); + } + + /* + * Also make sure initial mouse targetting works correctly by caching mousemove coords + * on show targets before the tooltip has rendered. Also set onTarget when triggered to + * keep mouse tracking working. + */ + this._bind(showTarget, 'mousemove', function(event) { + this._storeMouse(event); + this.cache.onTarget = TRUE; + }); + + // Define hoverIntent function + function hoverIntent(event) { + // Only continue if tooltip isn't disabled + if(this.disabled) { return FALSE; } + + // Cache the event data + this.cache.event = $.extend({}, event); + this.cache.target = event ? $(event.target) : [undefined]; + + // Start the event sequence + clearTimeout(this.timers.show); + this.timers.show = delay.call(this, + function() { this.render(typeof event === 'object' || options.show.ready); }, + options.show.delay + ); + } + + // Bind events to target + this._bind(showTarget, showEvents, hoverIntent); + if(options.show.event !== options.hide.event) { + this._bind(hideTarget, hideEvents, function() { clearTimeout(this.timers.show); }); + } + + // Prerendering is enabled, create tooltip now + if(options.show.ready || options.prerender) { hoverIntent.call(this, event); } +}; + + // Event assignment method PROTOTYPE._assignEvents = function() { var self = this, @@ -299,14 +363,8 @@ PROTOTYPE._unassignEvents = function() { document ]; - // Check if tooltip is rendered - if(this.rendered) { - this._unbind($([]).pushStack( $.grep(targets, function(i) { - return typeof i === 'object'; - }))); - } - - // Tooltip isn't yet rendered, remove render event - else { $(targets[0]).unbind('.'+this._id+'-create'); } + this._unbind($([]).pushStack( $.grep(targets, function(i) { + return typeof i === 'object'; + }))); }; diff --git a/src/core/jquery_methods.js b/src/core/jquery_methods.js index 486de4db..b3cadda3 100644 --- a/src/core/jquery_methods.js +++ b/src/core/jquery_methods.js @@ -1,10 +1,9 @@ // Initialization method -function init(elem, id, opts) -{ - var obj, posOptions, attr, config, title, - +function init(elem, id, opts) { // Setup element references - docBody = $(document.body), + docBody = docBody || $(document.body); + + var obj, posOptions, attr, config, title, // Use document body instead of document element if needed newTarget = elem[0] === document ? docBody : elem, @@ -102,10 +101,8 @@ QTIP = $.fn.qtip = function(options, notation, newValue) } // Execute API command if present - else if('string' === typeof options) - { - this.each(function() - { + else if('string' === typeof options) { + this.each(function() { var api = $.data(this, NAMESPACE); if(!api) { return TRUE; } @@ -133,99 +130,41 @@ QTIP = $.fn.qtip = function(options, notation, newValue) } // No API commands. validate provided options and setup qTips - else if('object' === typeof options || !arguments.length) - { + else if('object' === typeof options || !arguments.length) { + // Sanitize options first opts = sanitizeOptions($.extend(TRUE, {}, options)); - // Bind the qTips - return QTIP.bind.call(this, opts, event); - } -}; + return this.each(function(i) { + var options, targets, events, namespace, api, id; -// $.fn.qtip Bind method -QTIP.bind = function(opts, event) -{ - return this.each(function(i) { - var options, targets, events, namespace, api, id; + // Find next available ID, or use custom ID if provided + id = $.isArray(opts.id) ? opts.id[i] : opts.id; + id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id; - // Find next available ID, or use custom ID if provided - id = $.isArray(opts.id) ? opts.id[i] : opts.id; - id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id; + // Setup events namespace + namespace = '.qtip-'+id+'-create'; - // Setup events namespace - namespace = '.qtip-'+id+'-create'; + // Initialize the qTip and re-grab newly sanitized options + api = init($(this), id, opts); + if(api === FALSE) { return TRUE; } + else { QTIP.api[id] = api; } + options = api.options; - // Initialize the qTip and re-grab newly sanitized options - api = init($(this), id, opts); - if(api === FALSE) { return TRUE; } - else { QTIP.api[id] = api; } - options = api.options; + // Initialize plugins + $.each(PLUGINS, function() { + if(this.initialize === 'initialize') { this(api); } + }); - // Initialize plugins - $.each(PLUGINS, function() { - if(this.initialize === 'initialize') { this(api); } + // Assign initial pre-render events + api._assignInitialEvents(event); }); + } +}; - // Determine hide and show targets - targets = { show: options.show.target, hide: options.hide.target }; - events = { - show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace, - hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace - }; - - /* - * Make sure hoverIntent functions properly by using mouseleave as a hide event if - * mouseenter/mouseout is used for show.event, even if it isn't in the users options. - */ - if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) { - events.hide += ' mouseleave' + namespace; - } - - /* - * Also make sure initial mouse targetting works correctly by caching mousemove coords - * on show targets before the tooltip has rendered. - * - * Also set onTarget when triggered to keep mouse tracking working - */ - targets.show.bind('mousemove'+namespace, function(event) { - api._storeMouse(event); - api.cache.onTarget = TRUE; - }); - - // Define hoverIntent function - function hoverIntent(event) { - function render() { - // Cache mouse coords,render and render the tooltip - api.render(typeof event === 'object' || options.show.ready); - - // Unbind show and hide events - targets.show.add(targets.hide).unbind(namespace); - } - - // Only continue if tooltip isn't disabled - if(api.disabled) { return FALSE; } - - // Cache the event data - api.cache.event = $.extend({}, event); - api.cache.target = event ? $(event.target) : [undefined]; - - // Start the event sequence - if(options.show.delay > 0) { - clearTimeout(api.timers.show); - api.timers.show = setTimeout(render, options.show.delay); - if(events.show !== events.hide) { - targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); }); - } - } - else { render(); } - } - - // Bind show events to target - targets.show.bind(events.show, hoverIntent); +// $.fn.qtip Bind method +QTIP.bind = function(opts, event) +{ - // Prerendering is enabled, create tooltip now - if(options.show.ready || options.prerender) { hoverIntent(event); } - }); }; // Populated in render method diff --git a/src/core/position.js b/src/core/position.js index 8121baa6..bdf9ed89 100644 --- a/src/core/position.js +++ b/src/core/position.js @@ -48,7 +48,7 @@ PROTOTYPE.reposition = function(event, effect) { // Calculate body and container offset and take them into account below if(type !== 'static') { position = container.offset(); } - if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { offset = $(doc.body).offset(); } + if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { offset = docBody.offset(); } // Use event coordinates for position position = { diff --git a/src/tips/tips.js b/src/tips/tips.js index 37045f64..35711008 100644 --- a/src/tips/tips.js +++ b/src/tips/tips.js @@ -81,7 +81,7 @@ $.extend(Tip.prototype, { // Setup constant parameters context.lineJoin = 'miter'; - context.miterLimit = 100; + context.miterLimit = 100000; context.save(); } else { @@ -333,7 +333,7 @@ $.extend(Tip.prototype, { // Grab canvas context and clear/save it context = inner[0].getContext('2d'); context.restore(); context.save(); - context.clearRect(0,0,3000,3000); + context.clearRect(0,0,6000,6000); // Set properties context.fillStyle = color[0]; @@ -343,6 +343,7 @@ $.extend(Tip.prototype, { // Draw the tip context.translate(translate[0], translate[1]); context.beginPath(); + context.moveTo(coords[0], coords[1]); context.lineTo(coords[2], coords[3]); context.lineTo(coords[4], coords[5]); @@ -588,7 +589,7 @@ CHECKS.tip = { }, '^style.tip.(height|width)$': function(obj) { // Re-set dimensions and redraw the tip - this.size = size = [ obj.width, obj.height ]; + this.size = [ obj.width, obj.height ]; this.update(); // Reposition the tooltip