Tuesday, September 13, 2011

A super smart way to rotate graphics

Wanted to share a very usable way for rotating graphics that I found at zwibbler.com. I'll show you an example -



I placed the arrow and as is typical in drawing environments like this, I got a handle to use to rotate the arrow (this is the green G like thing you see on the canvas - highlighted by the red ellipse). The neat thing is you can drag the handle so that the further away you drag the handle (indicated by the thin blue line in the UI), the finer is the rotation so you can decide how much control you want over the rotation. Compare this to Powerpoint (which is a pretty good regular use graphics program for me) and the rotation is not as intuitive or easy and requires very careful mouse manipulation to rotate things right.

Very smart!

This does have a problem that you are limited by the canvas and in a rectangular canvas if your rotation arc (after you have extended it to almost the edge) passes over the edge, the rotation stops. But it seems to work well - give it at try.

Saturday, July 23, 2011

HoverPopup - a JQuery UI Plugin

Fiddling around with JQuery, JavaScript and assorted web development goodies in my spare time, I finally have a JQuery UI plugin I can call my own. This one mimics the behaviour you can find in the Gmail chat window. When you hover over a user, you'll get a panel that has additional details and actions for that context.


You can try out the plug-in here which is an easier way to understand what it does.

It wasn't all that hard and while I was mucking my application code with this UI logic, I figured I would write it as a JQueryUI plugin so it is re-usable and has the added advantage of keeping my code clean. It turned out to be a very fun and satisfying experience. Many thanks to this article for helping me understand what needed to be done - http://bililite.com/blog/understanding-jquery-ui-widgets-a-tutorial/

Disclaimer: My first JQueryUI plug-in and not too much after having learnt JavaScript, so use this at your own risk - I expect that an expert in either will be able to provide me good feedback on how things can be improved or written much better.

Here's the code for the plug-in. To use JQueryUI you need to follow the guidelines here. Thanks to the wonderful http://www.tohtml.com for syntax highlighting.

/*
* $(selector).hoverpopup({
leftMargin: [optional] - specify how much to the left of the trigger element the hover popup should appear
getTriggers: function that should return a list of elements to base the popup trigger on.
getPopupContents: function that is called to populate the popup
});
*
*/
var HoverPopup = {
options: {
leftMargin: 3,
},

_init: function() {
var _this = this;
var elem = this.element;
var triggers = this.options.getTriggers(elem);
_this.options.popup = $('#hover-popup-1');
if (_this.options.popup.length == 0) {
$('body').append("<div id='hover-popup-1' class='hover-popup'></div>");
_this.options.popup = $('#hover-popup-1');
_this.options.popup.hover(function(evt) {
clearTimeout(_this.options.t);
}, function(evt) {
});
_this.options.popup.hide();
$('html').click(_this.options.clickHandler = function(evt) {
if ($('#hover-popup-1:visible').length > 0) {
var o = _this.options.popup.offset();
var h = _this.options.popup.height();
var w = _this.options.popup.width();
if ((evt.pageX >= o.left && evt.pageX < o.left + w) &&
(evt.pageY >= o.top && evt.pageY < o.top + h)) {
// do nothing
} else {
_this.options.popup.fadeOut(_this.options.hideCallback);
}
}
});
}

for (var i = 0; i < triggers.length; i++) {
$(triggers[i]).hover(function(evt) {
_this.options.enter = setTimeout(function() {
var src = evt.currentTarget;
_this.options.popup.empty();
_this.options.popup.append(_this.options.getPopupContents(src));
clearTimeout(_this.options.t);
_this.options.popup.fadeIn(_this.options.showCallback);
_this.options.popup.offset(_this._calculateOffset(src));
}, 200);
}, function(evt) {
clearTimeout(_this.options.enter);
_this.options.t = setTimeout(function() {
_this.options.popup.fadeOut(_this.options.hideCallback);
}, 300);
});
}
},

destroy: function() {
$.Widget.prototype.destroy.apply(this, arguments);
_this.options.popup.unbind();
$('body').remove(_this.options.popup);
$('html').unbind(_this.options.clickHandler);

},

_calculateOffset: function(src) {
var w = this.options.popup.width();
var h = this.options.popup.height();
var jsrc = $(src);
var off = jsrc.offset();
var l = off.left - w - this.options.leftMargin;
if (l < 0) {
l = off.left + jsrc.width() + this.options.leftMargin;
}
var t = off.top;
var pageH = $(window).height();
if (t + h > pageH) {
t -= (t + h - pageH - 5);
}
return { "top" : t, "left" : l};
},
}

$.widget("ui.hoverpopup", HoverPopup);

The CSS code follows - which is not much really. You can style the contents of the popup which is your UI code. I suppose I could add this to the component itself and reduce the number of files.

#hover-popup-1 {
display: block;
position: absolute;
z-index: 10000;
}



Usage:
Apply hoverpopup on the jquery object - an instance will get instantiated for each element in the jquery object. The "getTriggers" function is called which gets the context element to work with. This function is expected to return a jquery object that has all the triggers in it. When the mouse hovers over one of the triggers, the popup is shown and its contents are populated by calling "getPopupContents" which can return anything that works in the JQuery "append" function.
leftMargin just controls where the popup is located - maybe this should be in a function of its own

Here's an example -

$("#div1").hoverpopup({
getTriggers: function(elem) {
return $(elem).find('.hp-test-tr');
},
getPopupContents: function(src) {
return '<div>' + $(src).find('.data').html() + '<div>';
},
leftMargin: 10,
});

Some more details
The reason I added in getTriggers, which is probably a deviation from the JQuery way of doing this - i.e. the pattern $(selector).hoverpopup would normally apply on the selector, was because I felt that instantiating a HoverPopup object for each trigger was excessive. Take a look at the demo page source to get an idea of how it was used.

Let me know what you think and I am open to suggestions on improvements and feedback on the code.