Dynamic Tooltip Styling Based on Element States
The tooltip component applies standard theme framework classes by default, but oftan requires visual differentiation based on contextual states. Rather than manually specifying CSS classes for each instance, implement an automated inheritance mechanism that detects state classes from the target element.
Consider a scenario where interface elements indicate validation errors or highlight new features. The tooltip should mirror these states without explicit configuration:
<div class="control-group">
<button class="ui-state-highlight" title="New feature available">Feature</button>
<button class="ui-state-error" title="Connection failed">Retry</button>
<button title="Standard operation">Normal</button>
</div>
Extend the base tooltip widget to include state detection:
(function($) {
$.widget("app.smarttooltip", $.ui.tooltip, {
options: {
detectState: false,
stateClasses: ["ui-state-error", "ui-state-highlight", "ui-state-default"]
},
_initialize: function() {
if (this.options.detectState && !this.options.tooltipClass) {
this._inheritElementState();
}
this._super();
},
_inheritElementState: function() {
var element = this.element,
states = this.options.stateClasses;
$.each(states, function(index, stateClass) {
if (element.hasClass(stateClass)) {
element.data("tooltip-class", stateClass);
return false;
}
});
},
_createTooltip: function() {
this.options.tooltipClass = this.element.data("tooltip-class") || "";
this._super();
}
});
})(jQuery);
$(".control-group button").smarttooltip({ detectState: true });
This approach eliminates the need to manually synchronize tooltip appearance with element states, ensuring visual consistency across the interface.
Structured Content Architecture
Beyond simple text strings, tooltips frequently require complex layouts with headers, descriptive bodies, and footers. Rather than concatenating HTML strings within the content callback, implement a section-based configuration system that promotes maintainability.
<div id="product-grid">
<img src="item1.jpg" data-name="Wireless Mouse" data-desc="Ergonomic design..." data-price="$29.99">
<img src="item2.jpg" data-name="Mechanical Keyboard" data-desc="Cherry MX switches..." data-price="$89.99">
</div>
Define the enhanced tooltip widget with section support:
(function($) {
$.widget("app.structuredtooltip", $.ui.tooltip, {
options: {
header: null,
body: null,
footer: null
},
_init: function() {
if (this.options.header || this.options.body || this.options.footer) {
this.options.content = $.proxy(this._buildContent, this);
}
this._super();
},
_buildContent: function() {
var $wrapper = $("<div class='tooltip-wrapper'/>"),
sections = [
{ key: "header", class: "tooltip-header" },
{ key: "body", class: "tooltip-body" },
{ key: "footer", class: "tooltip-footer" }
];
$.each(sections, function(i, section) {
var content = this.options[section.key];
if (content) {
$("<div/>").addClass(section.class)
.html(content)
.appendTo($wrapper);
}
}.bind(this));
return $wrapper;
}
});
})(jQuery);
$("#product-grid img").each(function() {
var $img = $(this);
$img.structuredtooltip({
header: $img.data("name"),
body: $img.data("desc"),
footer: $img.data("price"),
items: "img"
});
});
Real-time Coordinate Tracking
During interface debugging, displaying cursor coordinates relative to specific elements proves invaluable. Implement a specialized tracker widget that extends tooltip functionality to display positional data dynamically.
.tracker-target {
width: 300px;
height: 200px;
border: 2px solid #ccc;
margin: 20px;
position: relative;
}
<div class="tracker-target" id="absolute-tracker">Page Coordinates</div>
<div class="tracker-target" id="relative-tracker">Local Coordinates</div>
(function($) {
$.widget("app.coordinatetracker", $.ui.tooltip, {
options: {
trackCursor: true,
relativeToElement: false,
targetClass: "tracking-active"
},
_create: function() {
this.element.addClass(this.options.targetClass);
this._super();
this._attachMouseHandler();
},
_attachMouseHandler: function() {
this._on(this.element, {
mousemove: this._updatePosition,
mouseenter: this._activate,
mouseleave: this._deactivate
});
},
_activate: function(event) {
this.options.content = this._generateContent.bind(this);
this.open(event);
},
_generateContent: function() {
var labelX = this.options.relativeToElement ? "Local X: " : "Global X: ";
var labelY = this.options.relativeToElement ? "Local Y: " : "Global Y: ";
return $("<div/>").append(
$("<div/>").append($("<strong/>").text(labelX))
.append($("<span class='coord-x'/>"))
).append(
$("<div/>").append($("<strong/>").text(labelY))
.append($("<span class='coord-y'/>"))
);
},
_updatePosition: function(event) {
var offsetX = 0, offsetY = 0;
if (this.options.relativeToElement) {
var offset = this.element.offset();
offsetX = offset.left;
offsetY = offset.top;
}
this.element.find(".coord-x").text(event.pageX - offsetX);
this.element.find(".coord-y").text(event.pageY - offsetY);
},
_deactivate: function() {
this.close();
}
});
})(jQuery);
$("#absolute-tracker").coordinatetracker();
$("#relative-tracker").coordinatetracker({ relativeToElement: true });
Sophisticated Animation Sequences
Control tooltip entrance and exit animations independently using the show and hide configuration objects. Different effects, durations, and delays create distinct user experiences for various interface elements.
<nav class="effect-demo">
<button data-effect="fade" title="Gradual opacity transition">Fade Effect</button>
<button data-effect="slide" title="Horizontal movement">Slide Effect</button>
<button data-effect="scale" title="Size transformation">Scale Effect</button>
</nav>
$(".effect-demo button").each(function() {
var $btn = $(this),
effectType = $btn.data("effect");
var animationConfig = {
fade: {
show: { effect: "fadeIn", duration: 400, delay: 100 },
hide: { effect: "fadeOut", duration: 250 }
},
slide: {
show: { effect: "slide", direction: "left", duration: 300 },
hide: { effect: "slide", direction: "right", duration: 200 }
},
scale: {
show: { effect: "scale", origin: ["middle", "center"], duration: 350 },
hide: { effect: "puff", percent: 150, duration: 400 }
}
};
$btn.tooltip(animationConfig[effectType]);
});
Contextual Glossary Implementation
Enable in-context help by allowing users to select text and receive definitions without leaving the current workflow. Implement a dictionary widget that operates in two modes: text selection and hover-based terminology highlighting.
<article class="documentation">
<p>The widget factory provides the foundation for creating stateful plugins...</p>
<p>Observables in the framework allow for loose coupling between components...</p>
</article>
(function($) {
$.widget("app.contextualhelp", {
options: {
glossary: [],
interactionMode: "selection",
highlightClass: "defined-term"
},
_init: function() {
if (this.options.interactionMode === "hover") {
this._parseAndMarkup();
} else {
this._setupSelectionHandler();
}
},
_parseAndMarkup: function() {
var html = this.element.html(),
terms = this.options.glossary;
$.each(terms, function(i, entry) {
var pattern = new RegExp("\\b(" + entry.word + ")\\b", "gi");
html = html.replace(pattern, '<span class="' + this.options.highlightClass + '" title="' + entry.definition + '">$1</span>');
}.bind(this));
this.element.html(html);
this.element.find("." + this.options.highlightClass).tooltip();
},
_setupSelectionHandler: function() {
this._on(this.element, {
mouseup: this._processSelection
});
},
_processSelection: function(event) {
var selection = window.getSelection().toString().trim().toLowerCase();
if (!selection) return;
var match = this._findTerm(selection);
if (match) {
this._displayDefinition(event, match);
} else {
this._clearDefinition();
}
},
_findTerm: function(text) {
return $.grep(this.options.glossary, function(item) {
return item.word === text || item.word + "s" === text;
})[0];
},
_displayDefinition: function(event, entry) {
this._clearDefinition();
this.element.attr("title", entry.definition);
this.element.tooltip({
position: { my: "left bottom", at: "right top", of: event },
open: function(event, ui) {
ui.tooltip.addClass("glossary-definition");
}
}).tooltip("open");
},
_clearDefinition: function() {
if (this.element.is(":ui-tooltip")) {
this.element.tooltip("destroy").removeAttr("title");
}
}
});
})(jQuery);
var terms = [
{ word: "widget", definition: "A self-contained UI component with state and methods" },
{ word: "observable", definition: "An object that notifies subscribers of state changes" }
];
$(".documentation").contextualhelp({
glossary: terms,
interactionMode: "selection"
});
Runtime Widget Transformation
Container widgets like accordions and tabs serve similar organizational purposes but offer different interaction paradigms. Implement bidirectional conversion capabilities allowing users to switch between collapsed and tabbed views dynamically.
<button class="morph-trigger" data-target="#content-container">Toggle View</button>
<div id="content-container">
<section>
<h3>Configuration</h3>
<div>Settings content...</div>
</section>
<section>
<h3>Advanced</h3>
<div>Advanced options...</div>
</section>
</div>
(function($) {
$.widget("app.morphable", $.ui.accordion, {
toTabs: function() {
this.destroy();
var $container = this.element,
$tabList = $("<ul/>").prependTo($container);
$container.children("section").each(function(index) {
var $section = $(this),
$header = $section.children("h3"),
$content = $section.children("div"),
tabId = "tab-" + index;
$("<li><a href='#" + tabId + "'>" + $header.text() + "</a></li>").appendTo($tabList);
$content.attr("id", tabId);
$header.remove();
});
return $container.tabs();
}
});
$.widget("app.morphabletabs", $.ui.tabs, {
toAccordion: function() {
this.destroy();
var $container = this.element,
$panels = $container.children("div");
$container.children("ul").children("li").each(function(index) {
var $link = $(this).children("a"),
headerText = $link.text(),
targetId = $link.attr("href");
$("<h3/>").text(headerText).insertBefore(targetId);
});
$container.children("ul").remove();
return $container.accordion();
}
});
})(jQuery);
$(".morph-trigger").on("click", function() {
var $target = $($(this).data("target")),
instance = $target.data("appMorphable") || $target.data("appMorphabletabs");
if ($target.is(":app-morphable")) {
$target.morphable("toTabs");
} else {
$target.morphabletabs("toAccordion");
}
});
$("#content-container").morphable();
Building Stateful Components from Scratch
The widget factory provides infrastructure for creating reusable, stateful components. When architecting custom widgets, prioritize single-responsibility principles and insure proper cleanup mechanisms.
Consider a task management widget with checkbox functionality and progress indication:
<div class="task-widget">
<div class="controls">
<button class="add-task">Add Task</button>
<button class="clear-completed">Clear Done</button>
</div>
<ul class="task-list">
<li>Review pull requests</li>
<li>Update documentation</li>
<li>Deploy to staging</li>
</ul>
<div class="progress-indicator"></div>
</div>
(function($) {
$.widget("app.taskmanager", {
options: {
itemSelector: "li",
completedClass: "task-complete",
onProgressChange: null
},
_create: function() {
this.taskItems = this.element.find(this.options.itemSelector);
this.progressBar = this.element.find(".progress-indicator").progressbar();
this._addStyles();
this._bindEvents();
this._updateProgress();
},
_addStyles: function() {
this.taskItems.addClass("ui-selectee").attr("role", "checkbox").attr("aria-checked", "false");
},
_bindEvents: function() {
this._on(this.taskItems, {
click: this._toggleTask
});
this._on(this.element.find(".add-task"), {
click: this._addNewTask
});
},
_toggleTask: function(event) {
var $task = $(event.currentTarget),
isComplete = $task.hasClass(this.options.completedClass);
$task.toggleClass(this.options.completedClass)
.attr("aria-checked", !isComplete);
this._updateProgress();
this._trigger("statusChange", event, { task: $task.text(), completed: !isComplete });
},
_updateProgress: function() {
var total = this.taskItems.length,
completed = this.taskItems.filter("." + this.options.completedClass).length,
percentage = total === 0 ? 0 : (completed / total) * 100;
this.progressBar.progressbar("value", percentage);
},
_addNewTask: function() {
var $newTask = $("<li>New Task</li>").appendTo(this.element.find(".task-list"));
this.taskItems = this.taskItems.add($newTask);
this._addStyles();
},
_destroy: function() {
this.taskItems.removeClass("ui-selectee " + this.options.completedClass)
.removeAttr("role aria-checked");
this.progressBar.progressbar("destroy");
}
});
})(jQuery);
$(".task-widget").taskmanager();
Application-Wide Event Monitoring
Debugging complex interfaces requires visibility into widget lifecycle events. Construct an observer mechanism that captures and logs events across multiple widget types without modifying individual component code.
(function($) {
$.widget("app.eventmonitor", {
options: {
watchedWidgets: ["accordion", "tabs", "dialog"],
logContainer: null
},
_create: function() {
this.logPanel = $(this.options.logContainer || "<div/>").appendTo("body");
this.eventLog = [];
this._setupGlobalListeners();
this._createLogInterface();
},
_setupGlobalListeners: function() {
var self = this;
$.each(this.options.watchedWidgets, function(i, widgetName) {
var fullName = "ui-" + widgetName,
proto = $.ui[widgetName].prototype;
if (proto && proto.widgetEventPrefix) {
$(document).on(proto.widgetEventPrefix + "create " +
proto.widgetEventPrefix + "activate", function(event) {
self._recordEvent(event);
});
}
});
},
_recordEvent: function(event) {
var entry = {
type: event.type,
timestamp: new Date().toLocaleTimeString(),
target: event.target.tagName
};
this.eventLog.unshift(entry);
this._renderLogEntry(entry);
},
_renderLogEntry: function(entry) {
$("<div/>").addClass("log-entry")
.append($("<span/>").addClass("timestamp").text(entry.timestamp))
.append($("<span/>").addClass("event-type").text(entry.type))
.prependTo(this.logPanel);
}
});
})(jQuery);
$(document).eventmonitor({ watchedWidgets: ["accordion", "tabs"] });
Framework Integration Patterns
When integrating jQuery UI widgets into structured application architectures like Backbone or similar MV* frameworks, treat widgets as view layer implementasions that respond to model changes.
// Within a Backbone View context
var SearchView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "change:query", this.updateSearch);
},
render: function() {
this.$el.html('<input class="search-field" placeholder="Search...">');
this.$input = this.$(".search-field");
// Initialize jQuery UI widget with Backbone-managed data
this.$input.autocomplete({
source: $.proxy(this.fetchResults, this),
select: $.proxy(this.onSelection, this)
});
return this;
},
fetchResults: function(request, response) {
// Interact with Backbone Collection
var matches = this.collection.filter(function(item) {
return item.get("name").indexOf(request.term) > -1;
});
response(matches.map(function(item) {
return { label: item.get("name"), value: item.id };
}));
},
onSelection: function(event, ui) {
this.model.set("selectedId", ui.item.value);
},
updateSearch: function() {
this.$input.val(this.model.get("query"));
}
});
This decoupled approach allows the widget to handle presentation concerns while the framework manages application state and data persistence.