michael_hintbuble 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,572 @@
1
+ /**
2
+ * Michael Hintbuble creates pretty hint bubbles using prototype and
3
+ * scriptaculous. These functions work with ActionView helpers
4
+ * to provide hint bubble components using the syntax defined
5
+ * for rendering rails templates.
6
+ *
7
+ *
8
+ * Brought to you by the good folks at Coroutine. Hire us!
9
+ * http://coroutine.com
10
+ */
11
+ var MichaelHintbuble = {}
12
+
13
+
14
+ /**
15
+ * This property governs whether or not Michael bothers creating and
16
+ * managing a blocking iframe to accommodate ie6.
17
+ *
18
+ * Defaults to false, but override if you must.
19
+ */
20
+ MichaelHintbuble.SUPPORT_IE6_BULLSHIT = false;
21
+
22
+
23
+
24
+ //-----------------------------------------------------------------------------
25
+ // Bubble class
26
+ //-----------------------------------------------------------------------------
27
+
28
+ /**
29
+ * This function lets you come fly with Michael by defining
30
+ * the hint bubble class.
31
+ */
32
+ MichaelHintbuble.Bubble = function(target_id, content, options) {
33
+ this._target = $(target_id);
34
+ this._element = null;
35
+ this._positioner = null;
36
+ this._isShowing = null;
37
+
38
+ this._class = options["class"] || "container";
39
+ this._style = options["style"] || "";
40
+ this._eventNames = options["eventNames"] || ["mouseover","resize","scroll"]
41
+ this._position = options["position"] || "right";
42
+ this._beforeShow = options["beforeShow"] || Prototype.emptyFunction
43
+ this._afterShow = options["afterShow"] || Prototype.emptyFunction
44
+ this._beforeHide = options["beforeHide"] || Prototype.emptyFunction
45
+ this._afterHide = options["afterHide"] || Prototype.emptyFunction
46
+
47
+ this._makeBubble();
48
+ this._makePositioner();
49
+ this._attachObservers();
50
+ this.setContent(content);
51
+ this.setPosition();
52
+
53
+ if (MichaelHintbuble.SUPPORT_IE6_BULLSHIT) {
54
+ this._makeFrame();
55
+ }
56
+ };
57
+
58
+
59
+ /**
60
+ * This hash maps the bubble id to the bubble object itself. It allows the Rails
61
+ * code a way to specify the js object it wishes to invoke.
62
+ */
63
+ MichaelHintbuble.Bubble.instances = {};
64
+
65
+
66
+ /**
67
+ * This method destroys the bubble with the corresponding target id.
68
+ *
69
+ * @param {String} id The target id value of the bubble element (also the key
70
+ * in the instances hash.)
71
+ */
72
+ MichaelHintbuble.Bubble.destroy = function(id) {
73
+ var bubble = this.instances[id];
74
+ if (bubble) {
75
+ bubble.finalize();
76
+ }
77
+ this.instances[id] = null;
78
+ };
79
+
80
+
81
+ /**
82
+ * This method hides the bubble with the corresponding target id.
83
+ *
84
+ * @param {String} id The target id value of the bubble element (also the key
85
+ * in the instances hash.)
86
+ *
87
+ * @return {Object} an instance of MichaelHintbuble.Bubble
88
+ *
89
+ */
90
+ MichaelHintbuble.Bubble.hide = function(id) {
91
+ var bubble = this.instances[id];
92
+ if (bubble) {
93
+ bubble.hide();
94
+ }
95
+ return bubble;
96
+ };
97
+
98
+
99
+ /**
100
+ * This method returns a boolean indiciating whether or not the
101
+ * bubble with the corresponding target id is showing.
102
+ *
103
+ * @param {String} id The target id value of the bubble element (also the key
104
+ * in the instances hash.)
105
+ *
106
+ * @return {Boolean} Whether or not the bubble with the corresponding
107
+ * id is showing.
108
+ *
109
+ */
110
+ MichaelHintbuble.Bubble.isShowing = function(id) {
111
+ var bubble = this.instances[id];
112
+ if (!bubble) {
113
+ throw "No bubble cound be found for the supplied id.";
114
+ }
115
+ return bubble.isShowing();
116
+ };
117
+
118
+
119
+ /**
120
+ * This method shows the bubble with the corresponding target id.
121
+ *
122
+ * @param {String} id The target id value of the bubble element (also the key
123
+ * in the instances hash.)
124
+ *
125
+ * @return {Object} an instance of MichaelHintbuble.Bubble
126
+ *
127
+ */
128
+ MichaelHintbuble.Bubble.show = function(id) {
129
+ var bubble = this.instances[id];
130
+ if (bubble) {
131
+ bubble.show();
132
+ }
133
+ return bubble;
134
+ };
135
+
136
+
137
+ /**
138
+ * This function establishes all of the observations specified in the options.
139
+ */
140
+ MichaelHintbuble.Bubble.prototype._attachObservers = function() {
141
+ if (this._eventNames.indexOf("focus") > -1) {
142
+ this._target.observe("focus", function() {
143
+ this.show();
144
+ }.bind(this));
145
+ this._target.observe("blur", function() {
146
+ this.hide();
147
+ }.bind(this));
148
+ }
149
+ if (this._eventNames.indexOf("mouseover") > -1) {
150
+ this._target.observe("mouseover", function() {
151
+ this.show();
152
+ }.bind(this));
153
+ this._target.observe("mouseout", function() {
154
+ this.hide();
155
+ }.bind(this));
156
+ }
157
+ if (this._eventNames.indexOf("resize") > -1) {
158
+ Event.observe(window, "resize", function() {
159
+ if (this.isShowing()) {
160
+ this.setPosition();
161
+ }
162
+ }.bind(this));
163
+ }
164
+ if (this._eventNames.indexOf("scroll") > -1) {
165
+ Event.observe(window, "scroll", function() {
166
+ if (this.isShowing()) {
167
+ this.setPosition();
168
+ }
169
+ }.bind(this));
170
+ }
171
+ };
172
+
173
+
174
+ /**
175
+ * This function creates the bubble element and hides it by default.
176
+ */
177
+ MichaelHintbuble.Bubble.prototype._makeBubble = function() {
178
+ if (!this._element) {
179
+ this._container = new Element("DIV");
180
+ this._container.className = this._class;
181
+
182
+ this._element = new Element("DIV");
183
+ this._element.className = "michael_hintbuble_bubble";
184
+ this._element.writeAttribute("style", this._style);
185
+ this._element.update(this._container);
186
+ this._element.hide();
187
+ document.body.insert(this._element);
188
+ }
189
+ };
190
+
191
+
192
+ /**
193
+ * This function creates the blocking frame element and hides it by default.
194
+ */
195
+ MichaelHintbuble.Bubble.prototype._makeFrame = function() {
196
+ if (!this._frame) {
197
+ this._frame = new Element("IFRAME");
198
+ this._frame.className = "michael_hintbuble_bubble_frame";
199
+ this._frame.setAttribute("src", "about:blank");
200
+ this._frame.hide();
201
+ }
202
+ };
203
+
204
+
205
+ /**
206
+ * This function creates the bubble positioner object.
207
+ */
208
+ MichaelHintbuble.Bubble.prototype._makePositioner = function() {
209
+ if (!this._positioner) {
210
+ this._positioner = new MichaelHintbuble.BubblePositioner(this._target, this._element, this._position);
211
+ }
212
+ };
213
+
214
+
215
+ /**
216
+ * This method updates the container element by applying an additional style
217
+ * class representing the relative position of the bubble to the target.
218
+ */
219
+ MichaelHintbuble.Bubble.prototype._updateContainerClass = function() {
220
+ this._container.className = "container";
221
+ this._container.addClassName(this._positioner.styleClassForPosition());
222
+ };
223
+
224
+
225
+ /**
226
+ * This function allows the bubble object to be destroyed without
227
+ * creating memory leaks.
228
+ */
229
+ MichaelHintbuble.Bubble.prototype.finalize = function() {
230
+ this._positioner.finalize();
231
+ this._container.remove();
232
+ this._element.remove();
233
+
234
+ this._target = null;
235
+ this._element = null;
236
+ this._container = null;
237
+ this._positioner = null;
238
+
239
+ if (MichaelHintbuble.SUPPORT_IE6_BULLSHIT) {
240
+ this._frame.remove();
241
+ this._frame = null;
242
+ }
243
+ };
244
+
245
+
246
+ /**
247
+ * This function shows the hint bubble container (and the blocking frame, if
248
+ * required).
249
+ */
250
+ MichaelHintbuble.Bubble.prototype.hide = function() {
251
+ new Effect.Fade(this._element, {
252
+ duration: 0.2,
253
+ beforeStart: this._beforeHide,
254
+ afterFinish: function() {
255
+ this._isShowing = false;
256
+ this._afterHide();
257
+ }.bind(this)
258
+ });
259
+
260
+ if (this._frame) {
261
+ new Effect.Fade(this._frame, {
262
+ duration: 0.2
263
+ });
264
+ }
265
+ };
266
+
267
+
268
+ /**
269
+ * This function returns a boolean indicating whether or not the bubble is
270
+ * showing.
271
+ *
272
+ * @returns {Boolean} Whether or not the bubble is showing.
273
+ */
274
+ MichaelHintbuble.Bubble.prototype.isShowing = function() {
275
+ return this._isShowing;
276
+ };
277
+
278
+
279
+ /**
280
+ * This function sets the content of the hint bubble container.
281
+ *
282
+ * @param {String} content A string representation of the content to be added
283
+ * to the hint bubble container.
284
+ */
285
+ MichaelHintbuble.Bubble.prototype.setContent = function(content) {
286
+ var content_container = new Element("DIV");
287
+ content_container.className = "content";
288
+ content_container.update(content);
289
+
290
+ this._container.update(content_container);
291
+ };
292
+
293
+
294
+ /**
295
+ * This method sets the position of the hint bubble. It should be noted that the
296
+ * position simply states a preferred location for the bubble within the viewport.
297
+ * If the supplied position results in the bubble overrunning the viewport,
298
+ * the bubble will be repositioned to the opposite side to avoid viewport
299
+ * overrun.
300
+ *
301
+ * @param {String} position A string representation of the preferred position of
302
+ * the bubble element.
303
+ */
304
+ MichaelHintbuble.Bubble.prototype.setPosition = function(position) {
305
+ if (position) {
306
+ this._position = position.toLowerCase();
307
+ }
308
+ this._positioner.setPosition(this._position);
309
+ this._updateContainerClass();
310
+ };
311
+
312
+
313
+ /**
314
+ * This function shows the hint bubble container (and the blocking frame, if
315
+ * required).
316
+ */
317
+ MichaelHintbuble.Bubble.prototype.show = function() {
318
+ this.setPosition();
319
+
320
+ if (this._frame) {
321
+ var layout = new Element.Layout(this._element);
322
+ this._frame.style.top = layout.get("top") + "px";
323
+ this._frame.style.left = layout.get("left") + "px";
324
+ this._frame.style.width = layout.get("width") + "px";
325
+ this._frame.style.height = layout.get("height") + "px";
326
+
327
+ new Effect.Appear(this._frame, {
328
+ duration: 0.2
329
+ });
330
+ }
331
+
332
+ new Effect.Appear(this._element, {
333
+ duration: 0.2,
334
+ beforeStart: this._beforeShow,
335
+ afterFinish: function() {
336
+ this._isShowing = true;
337
+ this._afterShow();
338
+ }.bind(this)
339
+ });
340
+ };
341
+
342
+
343
+
344
+
345
+ //-----------------------------------------------------------------------------
346
+ // BubblePositioner class
347
+ //-----------------------------------------------------------------------------
348
+
349
+ /**
350
+ * This class encapsulates the positioning logic for bubble classes.
351
+ *
352
+ * @param {Element} target the dom element to which the bubble is anchored.
353
+ * @param {Element} element the bubble element itself.
354
+ */
355
+ MichaelHintbuble.BubblePositioner = function(target, element, position) {
356
+ this._target = target;
357
+ this._element = element;
358
+ this._position = position;
359
+ this._axis = null
360
+ };
361
+
362
+
363
+ /**
364
+ * These properties establish numeric values for the x and y axes.
365
+ */
366
+ MichaelHintbuble.BubblePositioner.X_AXIS = 1;
367
+ MichaelHintbuble.BubblePositioner.Y_AXIS = 2;
368
+
369
+
370
+ /**
371
+ * This property maps position values to one or the other axis.
372
+ */
373
+ MichaelHintbuble.BubblePositioner.AXIS_MAP = {
374
+ left: MichaelHintbuble.BubblePositioner.X_AXIS,
375
+ right: MichaelHintbuble.BubblePositioner.X_AXIS,
376
+ top: MichaelHintbuble.BubblePositioner.Y_AXIS,
377
+ bottom: MichaelHintbuble.BubblePositioner.Y_AXIS
378
+ };
379
+
380
+
381
+ /**
382
+ * This property maps position values to their opposite value.
383
+ */
384
+ MichaelHintbuble.BubblePositioner.COMPLEMENTS = {
385
+ left: "right",
386
+ right: "left",
387
+ top: "bottom",
388
+ bottom: "top"
389
+ };
390
+
391
+
392
+ /**
393
+ * This hash is a convenience that allows us to write slightly denser code when
394
+ * calculating the bubble's position.
395
+ */
396
+ MichaelHintbuble.BubblePositioner.POSITION_FN_MAP = {
397
+ left: "getWidth",
398
+ top: "getHeight"
399
+ };
400
+
401
+
402
+
403
+ /**
404
+ * This function positions the element below the target.
405
+ */
406
+ MichaelHintbuble.BubblePositioner.prototype._bottom = function() {
407
+ var to = this._targetAdjustedOffset();
408
+ var tl = new Element.Layout(this._target);
409
+
410
+ this._element.style.top = (to.top + tl.get("border-box-height")) + "px";
411
+ };
412
+
413
+
414
+ /**
415
+ * This function centers the positioning of the element for whichever
416
+ * axis it is on.
417
+ */
418
+ MichaelHintbuble.BubblePositioner.prototype._center = function() {
419
+ var to = this._targetAdjustedOffset();
420
+ var tl = new Element.Layout(this._target);
421
+ var el = new Element.Layout(this._element);
422
+
423
+ if (this._axis === MichaelHintbuble.BubblePositioner.X_AXIS) {
424
+ this._element.style.top = (to.top + Math.ceil(tl.get("border-box-height")/2) - Math.ceil(el.get("padding-box-height")/2)) + "px";
425
+ }
426
+ else if (this._axis === MichaelHintbuble.BubblePositioner.Y_AXIS) {
427
+ this._element.style.left = (to.left + Math.ceil(tl.get("border-box-width")/2) - Math.ceil(el.get("padding-box-width")/2)) + "px";
428
+ }
429
+ };
430
+
431
+
432
+ /**
433
+ * This function returns a boolean indicating whether or not the element is
434
+ * contained within the viewport.
435
+ *
436
+ * @returns {Boolean} whether or not the element is contained within the viewport.
437
+ */
438
+ MichaelHintbuble.BubblePositioner.prototype._isElementWithinViewport = function() {
439
+ var isWithinViewport = true;
440
+ var fnMap = MichaelHintbuble.BubblePositioner.POSITION_FN_MAP;
441
+ var method = null;
442
+ var viewPortMinEdge = null;
443
+ var viewPortMaxEdge = null;
444
+ var elementMinEdge = null;
445
+ var elementMaxEdge = null;
446
+
447
+ for (var prop in fnMap) {
448
+ method = fnMap[prop];
449
+ viewportMinEdge = document.viewport.getScrollOffsets()[prop];
450
+ viewportMaxEdge = viewportMinEdge + document.viewport[method]();
451
+ elementMinEdge = parseInt(this._element.style[prop] || 0);
452
+ elementMaxEdge = elementMinEdge + this._element[method]();
453
+
454
+ if ((elementMaxEdge > viewportMaxEdge) || (elementMinEdge < viewportMinEdge)) {
455
+ isWithinViewport = false;
456
+ break;
457
+ }
458
+ }
459
+
460
+ return isWithinViewport;
461
+ };
462
+
463
+
464
+ /**
465
+ * This function positions the element to the left of the target.
466
+ */
467
+ MichaelHintbuble.BubblePositioner.prototype._left = function() {
468
+ var to = this._targetAdjustedOffset();
469
+ var el = new Element.Layout(this._element);
470
+
471
+ this._element.style.left = (to.left - el.get("padding-box-width")) + "px";
472
+ };
473
+
474
+
475
+ /**
476
+ * This function positions the element to the right of the target.
477
+ */
478
+ MichaelHintbuble.BubblePositioner.prototype._right = function() {
479
+ var to = this._targetAdjustedOffset();
480
+ var tl = new Element.Layout(this._target);
481
+
482
+ this._element.style.left = (to.left + tl.get("border-box-width")) + "px";
483
+ };
484
+
485
+
486
+ /**
487
+ * This function positions the element relative to the target according to the
488
+ * position value supplied. Because this function is private, it assumes a
489
+ * safe position value.
490
+ *
491
+ * @param {String} position the desired relative position of the element to the
492
+ * target.
493
+ */
494
+ MichaelHintbuble.BubblePositioner.prototype._setPosition = function(position) {
495
+ this._axis = MichaelHintbuble.BubblePositioner.AXIS_MAP[position];
496
+ this._position = position;
497
+ this["_" + position]();
498
+ this._center();
499
+ };
500
+
501
+
502
+ /**
503
+ * This function returns a hash with the adjusted offset positions for the target
504
+ * element.
505
+ */
506
+ MichaelHintbuble.BubblePositioner.prototype._targetAdjustedOffset = function() {
507
+ var bs = $$("body").first().cumulativeScrollOffset();
508
+ var to = this._target.cumulativeOffset();
509
+ var ts = this._target.cumulativeScrollOffset();
510
+
511
+ return {
512
+ "top": to.top - ts.top + bs.top,
513
+ "left": to.left - ts.left + bs.left
514
+ }
515
+ };
516
+
517
+
518
+ /**
519
+ * This function positions the element above the target.
520
+ */
521
+ MichaelHintbuble.BubblePositioner.prototype._top = function() {
522
+ var to = this._targetAdjustedOffset();
523
+ var el = new Element.Layout(this._element);
524
+
525
+ this._element.style.top = (to.top - el.get("padding-box-height")) + "px";
526
+ };
527
+
528
+
529
+ /**
530
+ * This function allows the bubble positioner object to be destroyed without
531
+ * creating memory leaks.
532
+ */
533
+ MichaelHintbuble.BubblePositioner.prototype.finalize = function() {
534
+ this._target = null;
535
+ this._element = null;
536
+ this._axis = null;
537
+ this._position = null;
538
+ };
539
+
540
+
541
+ /**
542
+ * This function positions the element relative to the target according to the
543
+ * position value supplied. Invalid position values are ignored. If the new
544
+ * position runs off the viewport, the complement is tried. If that fails too,
545
+ * it gives up and does what was asked.
546
+ *
547
+ * @param {String} position the desired relative position of the element to the
548
+ * target.
549
+ */
550
+ MichaelHintbuble.BubblePositioner.prototype.setPosition = function(position) {
551
+ var axis = MichaelHintbuble.BubblePositioner.AXIS_MAP[position];
552
+ if (axis) {
553
+ this._setPosition(position);
554
+ if (!this._isElementWithinViewport()) {
555
+ this._setPosition(MichaelHintbuble.BubblePositioner.COMPLEMENTS[position]);
556
+ if (!this._isElementWithinViewport()) {
557
+ this._setPosition(position);
558
+ }
559
+ }
560
+ }
561
+ };
562
+
563
+
564
+ /**
565
+ * This function returns a string representation of the current logical positioning that
566
+ * can be used as a stylesheet class for physical positioning.
567
+ *
568
+ * @returns {String} a styleclass name appropriate for the current position.
569
+ */
570
+ MichaelHintbuble.BubblePositioner.prototype.styleClassForPosition = function() {
571
+ return this._position.toLowerCase();
572
+ };
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init.rb"
@@ -0,0 +1,22 @@
1
+ require "rails/generators"
2
+
3
+
4
+ class MichaelHintbubleGenerator < Rails::Generators::Base
5
+
6
+ # This call establishes the path to the templates directory.
7
+ #
8
+ def self.source_root
9
+ File.join(File.dirname(__FILE__), "templates")
10
+ end
11
+
12
+
13
+ # This method copies stylesheet and javascript files to the
14
+ # corresponding public directories.
15
+ #
16
+ def generate_assets
17
+ copy_file "michael_hintbuble_pointer.png", "public/images/michael_hintbuble_pointer.png"
18
+ copy_file "michael_hintbuble.css", "public/stylesheets/michael_hintbuble.css"
19
+ copy_file "michael_hintbuble.js", "public/javascripts/michael_hintbuble.js"
20
+ end
21
+
22
+ end
@@ -0,0 +1,48 @@
1
+ /**
2
+ * These styles are provided as examples. Feel free to change them however you like.
3
+ * Go ahead, see if we fucking care.
4
+ */
5
+
6
+ .michael_hintbuble_bubble_frame {
7
+ position: absolute;
8
+ border: none;
9
+ z-index: 1;
10
+ filter: alpha(opacity=0); /* really only needed if ie6 suport is enabled */
11
+ }
12
+
13
+ .michael_hintbuble_bubble {
14
+ position: absolute;
15
+ top: 0;
16
+ left: 0;
17
+ z-index: 2;
18
+ filter: alpha(opacity=100); /* really only needed if ie6 suport is enabled */
19
+ width: 240px;
20
+ }
21
+ .michael_hintbuble_bubble .container {
22
+ margin: 2px;
23
+ padding: 8px;
24
+ font-size: .85em;
25
+ }
26
+ .michael_hintbuble_bubble .container .content {
27
+ padding: 8px;
28
+ background: #333;
29
+ color: #FFF;
30
+ border-radius: 2px;
31
+ -moz-border-radius: 2px;
32
+ -webkit-border-radius: 2px;
33
+ }
34
+ .michael_hintbuble_bubble .bottom,
35
+ .michael_hintbuble_bubble .left,
36
+ .michael_hintbuble_bubble .right,
37
+ .michael_hintbuble_bubble .top {
38
+ background: url('../images/michael_hintbuble_pointer.png') no-repeat bottom;
39
+ }
40
+ .michael_hintbuble_bubble .bottom {
41
+ background-position: top;
42
+ }
43
+ .michael_hintbuble_bubble .left {
44
+ background-position: right;
45
+ }
46
+ .michael_hintbuble_bubble .right {
47
+ background-position: left;
48
+ }