qtip2
Version:
Introducing... qTip2. The second generation of the advanced qTip plugin for the ever popular jQuery framework.
292 lines (237 loc) • 7.83 kB
JavaScript
function invalidOpt(a) {
return a === NULL || $.type(a) !== 'object';
}
function invalidContent(c) {
return !($.isFunction(c) ||
c && c.attr ||
c.length ||
$.type(c) === 'object' && (c.jquery || c.then));
}
// Option object sanitizer
function sanitizeOptions(opts) {
var content, text, ajax, once;
if(invalidOpt(opts)) { return FALSE; }
if(invalidOpt(opts.metadata)) {
opts.metadata = { type: opts.metadata };
}
if('content' in opts) {
content = opts.content;
if(invalidOpt(content) || content.jquery || content.done) {
text = invalidContent(content) ? FALSE : content;
content = opts.content = {
text: text
};
}
else { text = content.text; }
// DEPRECATED - Old content.ajax plugin functionality
// Converts it into the proper Deferred syntax
if('ajax' in content) {
ajax = content.ajax;
once = ajax && ajax.once !== FALSE;
delete content.ajax;
content.text = function(event, api) {
var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',
deferred = $.ajax(
$.extend({}, ajax, { context: api })
)
.then(ajax.success, NULL, ajax.error)
.then(function(newContent) {
if(newContent && once) { api.set('content.text', newContent); }
return newContent;
},
function(xhr, status, error) {
if(api.destroyed || xhr.status === 0) { return; }
api.set('content.text', status + ': ' + error);
});
return !once ? (api.set('content.text', loading), deferred) : loading;
};
}
if('title' in content) {
if($.isPlainObject(content.title)) {
content.button = content.title.button;
content.title = content.title.text;
}
if(invalidContent(content.title || FALSE)) {
content.title = FALSE;
}
}
}
if('position' in opts && invalidOpt(opts.position)) {
opts.position = { my: opts.position, at: opts.position };
}
if('show' in opts && invalidOpt(opts.show)) {
opts.show = opts.show.jquery ? { target: opts.show } :
opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
}
if('hide' in opts && invalidOpt(opts.hide)) {
opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
}
if('style' in opts && invalidOpt(opts.style)) {
opts.style = { classes: opts.style };
}
// Sanitize plugin options
$.each(PLUGINS, function() {
this.sanitize && this.sanitize(opts);
});
return opts;
}
// Setup builtin .set() option checks
CHECKS = PROTOTYPE.checks = {
builtin: {
// Core checks
'^id$': function(obj, o, v, prev) {
var id = v === TRUE ? QTIP.nextid : v,
newId = NAMESPACE + '-' + id;
if(id !== FALSE && id.length > 0 && !$('#'+newId).length) {
this._id = newId;
if(this.rendered) {
this.tooltip[0].id = this._id;
this.elements.content[0].id = this._id + '-content';
this.elements.title[0].id = this._id + '-title';
}
}
else { obj[o] = prev; }
},
'^prerender': function(obj, o, v) {
v && !this.rendered && this.render(this.options.show.ready);
},
// Content checks
'^content.text$': function(obj, o, v) {
this._updateContent(v);
},
'^content.attr$': function(obj, o, v, prev) {
if(this.options.content.text === this.target.attr(prev)) {
this._updateContent( this.target.attr(v) );
}
},
'^content.title$': function(obj, o, v) {
// Remove title if content is null
if(!v) { return this._removeTitle(); }
// If title isn't already created, create it now and update
v && !this.elements.title && this._createTitle();
this._updateTitle(v);
},
'^content.button$': function(obj, o, v) {
this._updateButton(v);
},
'^content.title.(text|button)$': function(obj, o, v) {
this.set('content.'+o, v); // Backwards title.text/button compat
},
// Position checks
'^position.(my|at)$': function(obj, o, v){
if('string' === typeof v) {
this.position[o] = obj[o] = new CORNER(v, o === 'at');
}
},
'^position.container$': function(obj, o, v){
this.rendered && this.tooltip.appendTo(v);
},
// Show checks
'^show.ready$': function(obj, o, v) {
v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
},
// Style checks
'^style.classes$': function(obj, o, v, p) {
this.rendered && this.tooltip.removeClass(p).addClass(v);
},
'^style.(width|height)': function(obj, o, v) {
this.rendered && this.tooltip.css(o, v);
},
'^style.widget|content.title': function() {
this.rendered && this._setWidget();
},
'^style.def': function(obj, o, v) {
this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
},
// Events check
'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
},
// Properties which require event reassignment
'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
if(!this.rendered) { return; }
// Set tracking flag
var posOptions = this.options.position;
this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
// Reassign events
this._unassignEvents();
this._assignEvents();
}
}
};
// Dot notation converter
function convertNotation(options, notation) {
var i = 0, obj, option = options,
// Split notation into array
levels = notation.split('.');
// Loop through
while(option = option[ levels[i++] ]) {
if(i < levels.length) { obj = option; }
}
return [obj || options, levels.pop()];
}
PROTOTYPE.get = function(notation) {
if(this.destroyed) { return this; }
var o = convertNotation(this.options, notation.toLowerCase()),
result = o[0][ o[1] ];
return result.precedance ? result.string() : result;
};
function setCallback(notation, args) {
var category, rule, match;
for(category in this.checks) {
if (!this.checks.hasOwnProperty(category)) { continue; }
for(rule in this.checks[category]) {
if (!this.checks[category].hasOwnProperty(rule)) { continue; }
if(match = (new RegExp(rule, 'i')).exec(notation)) {
args.push(match);
if(category === 'builtin' || this.plugins[category]) {
this.checks[category][rule].apply(
this.plugins[category] || this, args
);
}
}
}
}
}
var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
rrender = /^prerender|show\.ready/i;
PROTOTYPE.set = function(option, value) {
if(this.destroyed) { return this; }
var rendered = this.rendered,
reposition = FALSE,
options = this.options,
name;
// Convert singular option/value pair into object form
if('string' === typeof option) {
name = option; option = {}; option[name] = value;
}
else { option = $.extend({}, option); }
// Set all of the defined options to their new values
$.each(option, function(notation, val) {
if(rendered && rrender.test(notation)) {
delete option[notation]; return;
}
// Set new obj value
var obj = convertNotation(options, notation.toLowerCase()), previous;
previous = obj[0][ obj[1] ];
obj[0][ obj[1] ] = val && val.nodeType ? $(val) : val;
// Also check if we need to reposition
reposition = rmove.test(notation) || reposition;
// Set the new params for the callback
option[notation] = [obj[0], obj[1], val, previous];
});
// Re-sanitize options
sanitizeOptions(options);
/*
* Execute any valid callbacks for the set options
* Also set positioning flag so we don't get loads of redundant repositioning calls.
*/
this.positioning = TRUE;
$.each(option, $.proxy(setCallback, this));
this.positioning = FALSE;
// Update position if needed
if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
}
return this;
};