tourist-rails 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f12911e169add2cf6c96f29de10eee9e627ba1ce
4
+ data.tar.gz: ac9d8bcd156633674780d1cbdba9eacab7189a83
5
+ SHA512:
6
+ metadata.gz: e9c0be85d2ee0b3f8100c5c563772696b0a93acf4488f713c96159874015ce12f494031968f7fbae7129441671e1f2066c0d626341922385628ec764d8d64e8d
7
+ data.tar.gz: 5f8db26bcff67f55c521dba36933cac8ba9683129463f872cc6719a8eac166868bc5678cb09ce9ba3bdd49bb96cd54d15c6cb4e08c808924230ec071723a5a4f
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tourist-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mathieu Gagné
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Tourist.js for Rails 3.x
2
+
3
+ > Tourist.js is a simple library for creating guided tours through your app. It's better suited to complex, single-page apps than websites. Our main requirement was the ability to control the interface for each step. For example, a step might need to open a window or menu, or wait for the user to complete a task. Tourist gives you hooks to do this.
4
+
5
+ ## Installation
6
+
7
+ ### Add the gem
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'tourist-rails'
12
+
13
+ And then execute:
14
+
15
+ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ gem install tourist-rails
20
+
21
+ ### Add Tourist.js to the asset pipeline like usual
22
+
23
+ #### application.js
24
+
25
+ require backbone
26
+ require tourist
27
+
28
+ #### application.css.scss (optional)
29
+
30
+ @import 'tourist';
31
+
32
+ ## Usage
33
+
34
+ Read more on their (github page)[http://easelinc.github.io/tourist/] and (repository)[https://github.com/easelinc/tourist]
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ module Tourist
2
+ module Rails
3
+ if defined?(Rails)
4
+ require 'tourist/rails/engine'
5
+ require 'tourist/rails/version'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module Tourist
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Tourist
2
+ module Rails
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tourist/rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tourist-rails"
8
+ spec.version = Tourist::Rails::VERSION
9
+ spec.authors = ["Mathieu Gagné"]
10
+ spec.email = ["gagne.mathieu@hotmail.com"]
11
+ spec.description = %q{Adds tourist.js to Rails 3.x asset pipeline}
12
+ spec.summary = %q{Adds tourist.js to Rails 3.x asset pipeline}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,1033 @@
1
+ //= require tourist/underscore
2
+ //= require tourist/backbone
3
+
4
+ (function() {
5
+ var _ref, _ref1, _ref2, _ref3,
6
+ __hasProp = {}.hasOwnProperty,
7
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
8
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
9
+
10
+ window.Tourist = window.Tourist || {};
11
+
12
+ /*
13
+ A model for the Tour. We'll only use the 'current_step' property.
14
+ */
15
+
16
+
17
+ Tourist.Model = (function(_super) {
18
+ __extends(Model, _super);
19
+
20
+ function Model() {
21
+ _ref = Model.__super__.constructor.apply(this, arguments);
22
+ return _ref;
23
+ }
24
+
25
+ Model.prototype._module = 'Tourist';
26
+
27
+ return Model;
28
+
29
+ })(Backbone.Model);
30
+
31
+ window.Tourist.Tip = window.Tourist.Tip || {};
32
+
33
+ /*
34
+ The flyout showing the content of each step.
35
+
36
+ This is the base class containing most of the logic. Can extend for different
37
+ tooltip implementations.
38
+ */
39
+
40
+
41
+ Tourist.Tip.Base = (function() {
42
+ Base.prototype._module = 'Tourist';
43
+
44
+ _.extend(Base.prototype, Backbone.Events);
45
+
46
+ Base.prototype.skipButtonTemplate = '<button class="btn btn-default btn-sm pull-right tour-next">Skip this step →</button>';
47
+
48
+ Base.prototype.nextButtonTemplate = '<button class="btn btn-primary btn-sm pull-right tour-next">Next step →</button>';
49
+
50
+ Base.prototype.finalButtonTemplate = '<button class="btn btn-primary btn-sm pull-right tour-next">Finish up</button>';
51
+
52
+ Base.prototype.closeButtonTemplate = '<a class="btn btn-close tour-close" href="#"><i class="icon icon-remove"></i></a>';
53
+
54
+ Base.prototype.okButtonTemplate = '<button class="btn btn-sm tour-close btn-primary">Okay</button>';
55
+
56
+ Base.prototype.actionLabelTemplate = _.template('<h4 class="action-label"><%= label %></h4>');
57
+
58
+ Base.prototype.actionLabels = ['Do this:', 'Then this:', 'Next this:'];
59
+
60
+ Base.prototype.highlightClass = 'tour-highlight';
61
+
62
+ Base.prototype.template = _.template('<div>\n <div class="tour-container">\n <%= close_button %>\n <%= content %>\n <p class="tour-counter <%= counter_class %>"><%= counter%></p>\n </div>\n <div class="tour-buttons">\n <%= buttons %>\n </div>\n</div>');
63
+
64
+ function Base(options) {
65
+ this.options = options != null ? options : {};
66
+ this.onClickNext = __bind(this.onClickNext, this);
67
+ this.onClickClose = __bind(this.onClickClose, this);
68
+ this.el = $('<div/>');
69
+ this.initialize(options);
70
+ this._bindClickEvents();
71
+ Tourist.Tip.Base._cacheTip(this);
72
+ }
73
+
74
+ Base.prototype.destroy = function() {
75
+ return this.el.remove();
76
+ };
77
+
78
+ Base.prototype.render = function(step) {
79
+ this.hide();
80
+ if (step) {
81
+ this._setTarget(step.target || false, step);
82
+ this._setZIndex('');
83
+ this._renderContent(step, this._buildContentElement(step));
84
+ if (step.target) {
85
+ this.show();
86
+ }
87
+ if (step.zIndex) {
88
+ this._setZIndex(step.zIndex, step);
89
+ }
90
+ }
91
+ return this;
92
+ };
93
+
94
+ Base.prototype.show = function() {};
95
+
96
+ Base.prototype.hide = function() {};
97
+
98
+ Base.prototype.setTarget = function(targetElement, step) {
99
+ return this._setTarget(targetElement, step);
100
+ };
101
+
102
+ Base.prototype.cleanupCurrentTarget = function() {
103
+ if (this.target && this.target.removeClass) {
104
+ this.target.removeClass(this.highlightClass);
105
+ }
106
+ return this.target = null;
107
+ };
108
+
109
+ /*
110
+ Event Handlers
111
+ */
112
+
113
+
114
+ Base.prototype.onClickClose = function(event) {
115
+ this.trigger('click:close', this, event);
116
+ return false;
117
+ };
118
+
119
+ Base.prototype.onClickNext = function(event) {
120
+ this.trigger('click:next', this, event);
121
+ return false;
122
+ };
123
+
124
+ /*
125
+ Private
126
+ */
127
+
128
+
129
+ Base.prototype._getTipElement = function() {};
130
+
131
+ Base.prototype._renderContent = function(step, contentElement) {};
132
+
133
+ Base.prototype._bindClickEvents = function() {
134
+ var el;
135
+ el = this._getTipElement();
136
+ el.delegate('.tour-close', 'click', this.onClickClose);
137
+ return el.delegate('.tour-next', 'click', this.onClickNext);
138
+ };
139
+
140
+ Base.prototype._setTarget = function(target, step) {
141
+ this.cleanupCurrentTarget();
142
+ if (target && step && step.highlightTarget) {
143
+ target.addClass(this.highlightClass);
144
+ }
145
+ return this.target = target;
146
+ };
147
+
148
+ Base.prototype._setZIndex = function(zIndex) {
149
+ var el;
150
+ el = this._getTipElement();
151
+ return el.css('z-index', zIndex || '');
152
+ };
153
+
154
+ Base.prototype._buildContentElement = function(step) {
155
+ var buttons, content;
156
+ buttons = this._buildButtons(step);
157
+ content = $($.parseHTML(this.template({
158
+ content: step.content,
159
+ buttons: buttons,
160
+ close_button: this._buildCloseButton(step),
161
+ counter: step.final ? '' : "step " + (step.index + 1) + " of " + step.total,
162
+ counter_class: step.final ? 'final' : ''
163
+ })));
164
+ if (!buttons) {
165
+ content.find('.tour-buttons').addClass('no-buttons');
166
+ }
167
+ this._renderActionLabels(content);
168
+ return content;
169
+ };
170
+
171
+ Base.prototype._buildButtons = function(step) {
172
+ var buttons;
173
+ buttons = '';
174
+ if (step.okButton) {
175
+ buttons += this.okButtonTemplate;
176
+ }
177
+ if (step.skipButton) {
178
+ buttons += this.skipButtonTemplate;
179
+ }
180
+ if (step.nextButton) {
181
+ buttons += step.final ? this.finalButtonTemplate : this.nextButtonTemplate;
182
+ }
183
+ return buttons;
184
+ };
185
+
186
+ Base.prototype._buildCloseButton = function(step) {
187
+ if (step.closeButton) {
188
+ return this.closeButtonTemplate;
189
+ } else {
190
+ return '';
191
+ }
192
+ };
193
+
194
+ Base.prototype._renderActionLabels = function(el) {
195
+ var action, actionIndex, actions, label, _i, _len, _results;
196
+ actions = el.find('.action');
197
+ actionIndex = 0;
198
+ _results = [];
199
+ for (_i = 0, _len = actions.length; _i < _len; _i++) {
200
+ action = actions[_i];
201
+ label = $($.parseHTML(this.actionLabelTemplate({
202
+ label: this.actionLabels[actionIndex]
203
+ })));
204
+ label.insertBefore(action);
205
+ _results.push(actionIndex++);
206
+ }
207
+ return _results;
208
+ };
209
+
210
+ Base._cacheTip = function(tip) {
211
+ if (!Tourist.Tip.Base._cachedTips) {
212
+ Tourist.Tip.Base._cachedTips = [];
213
+ }
214
+ return Tourist.Tip.Base._cachedTips.push(tip);
215
+ };
216
+
217
+ Base.destroy = function() {
218
+ var tip, _i, _len, _ref1;
219
+ if (!Tourist.Tip.Base._cachedTips) {
220
+ return;
221
+ }
222
+ _ref1 = Tourist.Tip.Base._cachedTips;
223
+ for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
224
+ tip = _ref1[_i];
225
+ tip.destroy();
226
+ }
227
+ return Tourist.Tip.Base._cachedTips = null;
228
+ };
229
+
230
+ return Base;
231
+
232
+ })();
233
+
234
+ /*
235
+ Bootstrap based tip implementation
236
+ */
237
+
238
+
239
+ Tourist.Tip.Bootstrap = (function(_super) {
240
+ __extends(Bootstrap, _super);
241
+
242
+ function Bootstrap() {
243
+ _ref1 = Bootstrap.__super__.constructor.apply(this, arguments);
244
+ return _ref1;
245
+ }
246
+
247
+ Bootstrap.prototype.initialize = function(options) {
248
+ var defs;
249
+ defs = {
250
+ showEffect: null,
251
+ hideEffect: null
252
+ };
253
+ this.options = _.extend(defs, options);
254
+ return this.tip = new Tourist.Tip.BootstrapTip();
255
+ };
256
+
257
+ Bootstrap.prototype.destroy = function() {
258
+ this.tip.destroy();
259
+ return Bootstrap.__super__.destroy.call(this);
260
+ };
261
+
262
+ Bootstrap.prototype.show = function() {
263
+ var fn;
264
+ if (this.options.showEffect) {
265
+ fn = Tourist.Tip.Bootstrap.effects[this.options.showEffect];
266
+ return fn.call(this, this.tip, this.tip.el);
267
+ } else {
268
+ return this.tip.show();
269
+ }
270
+ };
271
+
272
+ Bootstrap.prototype.hide = function() {
273
+ var fn;
274
+ if (this.options.hideEffect) {
275
+ fn = Tourist.Tip.Bootstrap.effects[this.options.hideEffect];
276
+ return fn.call(this, this.tip, this.tip.el);
277
+ } else {
278
+ return this.tip.hide();
279
+ }
280
+ };
281
+
282
+ /*
283
+ Private
284
+ */
285
+
286
+
287
+ Bootstrap.prototype._getTipElement = function() {
288
+ return this.tip.el;
289
+ };
290
+
291
+ Bootstrap.prototype._setTarget = function(target, step) {
292
+ Bootstrap.__super__._setTarget.call(this, target, step);
293
+ return this.tip.setTarget(target);
294
+ };
295
+
296
+ Bootstrap.prototype._renderContent = function(step, contentElement) {
297
+ var at, my;
298
+ my = step.my || 'left center';
299
+ at = step.at || 'right center';
300
+ this.tip.setContainer(step.container || $('body'));
301
+ this.tip.setContent(contentElement);
302
+ return this.tip.setPosition(step.target || false, my, at);
303
+ };
304
+
305
+ return Bootstrap;
306
+
307
+ })(Tourist.Tip.Base);
308
+
309
+ Tourist.Tip.Bootstrap.effects = {
310
+ slidein: function(tip, element) {
311
+ var OFFSETS, css, easing, easings, offset, side, value, _i, _len;
312
+ OFFSETS = {
313
+ top: 80,
314
+ left: 80,
315
+ right: -80,
316
+ bottom: -80
317
+ };
318
+ side = tip.my.split(' ')[0];
319
+ side = side || 'top';
320
+ offset = OFFSETS[side];
321
+ if (side === 'bottom') {
322
+ side = 'top';
323
+ }
324
+ if (side === 'right') {
325
+ side = 'left';
326
+ }
327
+ value = parseInt(element.css(side));
328
+ element.stop();
329
+ css = {};
330
+ css[side] = value + offset;
331
+ element.css(css);
332
+ element.show();
333
+ css[side] = value;
334
+ easings = ['easeOutCubic', 'swing', 'linear'];
335
+ for (_i = 0, _len = easings.length; _i < _len; _i++) {
336
+ easing = easings[_i];
337
+ if ($.easing[easing]) {
338
+ break;
339
+ }
340
+ }
341
+ element.animate(css, 300, easing);
342
+ return null;
343
+ }
344
+ };
345
+
346
+ /*
347
+ Simple implementation of tooltip with bootstrap markup.
348
+
349
+ Almost entirely deals with positioning. Uses the similar method for
350
+ positioning as qtip2:
351
+
352
+ my: 'top center'
353
+ at: 'bottom center'
354
+ */
355
+
356
+
357
+ Tourist.Tip.BootstrapTip = (function() {
358
+ BootstrapTip.prototype.template = '<div class="popover">\n <div class="arrow"></div>\n <div class="popover-content"></div>\n</div>';
359
+
360
+ BootstrapTip.prototype.FLIP_POSITION = {
361
+ bottom: 'top',
362
+ top: 'bottom',
363
+ left: 'right',
364
+ right: 'left'
365
+ };
366
+
367
+ function BootstrapTip(options) {
368
+ var defs;
369
+ defs = {
370
+ offset: 10,
371
+ tipOffset: 10
372
+ };
373
+ this.options = _.extend(defs, options);
374
+ this.el = $($.parseHTML(this.template));
375
+ this.hide();
376
+ }
377
+
378
+ BootstrapTip.prototype.destroy = function() {
379
+ return this.el.remove();
380
+ };
381
+
382
+ BootstrapTip.prototype.show = function() {
383
+ return this.el.show().addClass('visible');
384
+ };
385
+
386
+ BootstrapTip.prototype.hide = function() {
387
+ return this.el.hide().removeClass('visible');
388
+ };
389
+
390
+ BootstrapTip.prototype.setTarget = function(target) {
391
+ this.target = target;
392
+ return this._setPosition(this.target, this.my, this.at);
393
+ };
394
+
395
+ BootstrapTip.prototype.setPosition = function(target, my, at) {
396
+ this.target = target;
397
+ this.my = my;
398
+ this.at = at;
399
+ return this._setPosition(this.target, this.my, this.at);
400
+ };
401
+
402
+ BootstrapTip.prototype.setContainer = function(container) {
403
+ return container.append(this.el);
404
+ };
405
+
406
+ BootstrapTip.prototype.setContent = function(content) {
407
+ return this._getContentElement().html(content);
408
+ };
409
+
410
+ /*
411
+ Private
412
+ */
413
+
414
+
415
+ BootstrapTip.prototype._getContentElement = function() {
416
+ return this.el.find('.popover-content');
417
+ };
418
+
419
+ BootstrapTip.prototype._getTipElement = function() {
420
+ return this.el.find('.arrow');
421
+ };
422
+
423
+ BootstrapTip.prototype._setPosition = function(target, my, at) {
424
+ var clas, css, originalDisplay, position, shift, targetPosition, tip, tipOffset, tipPosition, _ref2;
425
+ if (my == null) {
426
+ my = 'left center';
427
+ }
428
+ if (at == null) {
429
+ at = 'right center';
430
+ }
431
+ if (!target) {
432
+ return;
433
+ }
434
+ _ref2 = my.split(' '), clas = _ref2[0], shift = _ref2[1];
435
+ originalDisplay = this.el.css('display');
436
+ this.el.css({
437
+ top: 0,
438
+ left: 0,
439
+ margin: 0,
440
+ display: 'block'
441
+ }).removeClass('top').removeClass('bottom').removeClass('left').removeClass('right').addClass(this.FLIP_POSITION[clas]);
442
+ if (!target) {
443
+ return;
444
+ }
445
+ tip = this._getTipElement().css({
446
+ left: '',
447
+ right: '',
448
+ top: '',
449
+ bottom: ''
450
+ });
451
+ if (shift !== 'center') {
452
+ tipOffset = {
453
+ left: tip[0].offsetWidth / 2,
454
+ right: 0,
455
+ top: tip[0].offsetHeight / 2,
456
+ bottom: 0
457
+ };
458
+ css = {};
459
+ css[shift] = tipOffset[shift] + this.options.tipOffset;
460
+ css[this.FLIP_POSITION[shift]] = 'auto';
461
+ tip.css(css);
462
+ }
463
+ targetPosition = this._caculateTargetPosition(at, target);
464
+ tipPosition = this._caculateTipPosition(my, targetPosition);
465
+ position = this._adjustForArrow(my, tipPosition);
466
+ this.el.css(position);
467
+ return this.el.css({
468
+ display: originalDisplay
469
+ });
470
+ };
471
+
472
+ BootstrapTip.prototype._caculateTargetPosition = function(atPosition, target) {
473
+ var bounds, pos;
474
+ if (Object.prototype.toString.call(target) === '[object Array]') {
475
+ return {
476
+ left: target[0],
477
+ top: target[1]
478
+ };
479
+ }
480
+ bounds = this._getTargetBounds(target);
481
+ pos = this._lookupPosition(atPosition, bounds.width, bounds.height);
482
+ return {
483
+ left: bounds.left + pos[0],
484
+ top: bounds.top + pos[1]
485
+ };
486
+ };
487
+
488
+ BootstrapTip.prototype._caculateTipPosition = function(myPosition, targetPosition) {
489
+ var height, pos, width;
490
+ width = this.el[0].offsetWidth;
491
+ height = this.el[0].offsetHeight;
492
+ pos = this._lookupPosition(myPosition, width, height);
493
+ return {
494
+ left: targetPosition.left - pos[0],
495
+ top: targetPosition.top - pos[1]
496
+ };
497
+ };
498
+
499
+ BootstrapTip.prototype._adjustForArrow = function(myPosition, tipPosition) {
500
+ var clas, height, position, shift, tip, width, _ref2;
501
+ _ref2 = myPosition.split(' '), clas = _ref2[0], shift = _ref2[1];
502
+ tip = this._getTipElement();
503
+ width = tip[0].offsetWidth;
504
+ height = tip[0].offsetHeight;
505
+ position = {
506
+ top: tipPosition.top,
507
+ left: tipPosition.left
508
+ };
509
+ switch (clas) {
510
+ case 'top':
511
+ position.top += height + this.options.offset;
512
+ break;
513
+ case 'bottom':
514
+ position.top -= height + this.options.offset;
515
+ break;
516
+ case 'left':
517
+ position.left += width + this.options.offset;
518
+ break;
519
+ case 'right':
520
+ position.left -= width + this.options.offset;
521
+ }
522
+ switch (shift) {
523
+ case 'left':
524
+ position.left -= width / 2 + this.options.tipOffset;
525
+ break;
526
+ case 'right':
527
+ position.left += width / 2 + this.options.tipOffset;
528
+ break;
529
+ case 'top':
530
+ position.top -= height / 2 + this.options.tipOffset;
531
+ break;
532
+ case 'bottom':
533
+ position.top += height / 2 + this.options.tipOffset;
534
+ }
535
+ return position;
536
+ };
537
+
538
+ BootstrapTip.prototype._lookupPosition = function(position, width, height) {
539
+ var height2, posLookup, width2;
540
+ width2 = width / 2;
541
+ height2 = height / 2;
542
+ posLookup = {
543
+ 'top left': [0, 0],
544
+ 'left top': [0, 0],
545
+ 'top right': [width, 0],
546
+ 'right top': [width, 0],
547
+ 'bottom left': [0, height],
548
+ 'left bottom': [0, height],
549
+ 'bottom right': [width, height],
550
+ 'right bottom': [width, height],
551
+ 'top center': [width2, 0],
552
+ 'left center': [0, height2],
553
+ 'right center': [width, height2],
554
+ 'bottom center': [width2, height]
555
+ };
556
+ return posLookup[position];
557
+ };
558
+
559
+ BootstrapTip.prototype._getTargetBounds = function(target) {
560
+ var el, size;
561
+ el = target[0];
562
+ if (typeof el.getBoundingClientRect === 'function') {
563
+ size = el.getBoundingClientRect();
564
+ } else {
565
+ size = {
566
+ width: el.offsetWidth,
567
+ height: el.offsetHeight
568
+ };
569
+ }
570
+ return $.extend({}, size, target.offset());
571
+ };
572
+
573
+ return BootstrapTip;
574
+
575
+ })();
576
+
577
+ /*
578
+ Qtip based tip implementation
579
+ */
580
+
581
+
582
+ Tourist.Tip.QTip = (function(_super) {
583
+ var ADJUST, OFFSETS, TIP_HEIGHT, TIP_WIDTH;
584
+
585
+ __extends(QTip, _super);
586
+
587
+ function QTip() {
588
+ this._renderTipBackground = __bind(this._renderTipBackground, this);
589
+ _ref2 = QTip.__super__.constructor.apply(this, arguments);
590
+ return _ref2;
591
+ }
592
+
593
+ TIP_WIDTH = 6;
594
+
595
+ TIP_HEIGHT = 14;
596
+
597
+ ADJUST = 10;
598
+
599
+ OFFSETS = {
600
+ top: 80,
601
+ left: 80,
602
+ right: -80,
603
+ bottom: -80
604
+ };
605
+
606
+ QTip.prototype.QTIP_DEFAULTS = {
607
+ content: {
608
+ text: ' '
609
+ },
610
+ show: {
611
+ ready: false,
612
+ delay: 0,
613
+ effect: function(qtip) {
614
+ var css, el, offset, side, value;
615
+ el = $(this);
616
+ side = qtip.options.position.my;
617
+ if (side) {
618
+ side = side[side.precedance];
619
+ }
620
+ side = side || 'top';
621
+ offset = OFFSETS[side];
622
+ if (side === 'bottom') {
623
+ side = 'top';
624
+ }
625
+ if (side === 'right') {
626
+ side = 'left';
627
+ }
628
+ value = parseInt(el.css(side));
629
+ css = {};
630
+ css[side] = value + offset;
631
+ el.css(css);
632
+ el.show();
633
+ css[side] = value;
634
+ el.animate(css, 300, 'easeOutCubic');
635
+ return null;
636
+ },
637
+ autofocus: false
638
+ },
639
+ hide: {
640
+ event: null,
641
+ delay: 0,
642
+ effect: false
643
+ },
644
+ position: {
645
+ adjust: {
646
+ method: 'shift shift',
647
+ scroll: false
648
+ }
649
+ },
650
+ style: {
651
+ classes: 'ui-tour-tip',
652
+ tip: {
653
+ height: TIP_WIDTH,
654
+ width: TIP_HEIGHT
655
+ }
656
+ },
657
+ events: {},
658
+ zindex: 2000
659
+ };
660
+
661
+ QTip.prototype.initialize = function(options) {
662
+ options = $.extend(true, {}, this.QTIP_DEFAULTS, options);
663
+ this.el.qtip(options);
664
+ this.qtip = this.el.qtip('api');
665
+ return this.qtip.render();
666
+ };
667
+
668
+ QTip.prototype.destroy = function() {
669
+ if (this.qtip) {
670
+ this.qtip.destroy();
671
+ }
672
+ return QTip.__super__.destroy.call(this);
673
+ };
674
+
675
+ QTip.prototype.show = function() {
676
+ return this.qtip.show();
677
+ };
678
+
679
+ QTip.prototype.hide = function() {
680
+ return this.qtip.hide();
681
+ };
682
+
683
+ /*
684
+ Private
685
+ */
686
+
687
+
688
+ QTip.prototype._getTipElement = function() {
689
+ return $('#qtip-' + this.qtip.id);
690
+ };
691
+
692
+ QTip.prototype._setTarget = function(targetElement, step) {
693
+ QTip.__super__._setTarget.call(this, targetElement, step);
694
+ return this.qtip.set('position.target', targetElement || false);
695
+ };
696
+
697
+ QTip.prototype._renderContent = function(step, contentElement) {
698
+ var at, my,
699
+ _this = this;
700
+ my = step.my || 'left center';
701
+ at = step.at || 'right center';
702
+ this._adjustPlacement(my, at);
703
+ this.qtip.set('content.text', contentElement);
704
+ this.qtip.set('position.container', step.container || $('body'));
705
+ this.qtip.set('position.my', my);
706
+ this.qtip.set('position.at', at);
707
+ this.qtip.set('position.viewport', step.viewport || false);
708
+ this.qtip.set('position.target', step.target || false);
709
+ return setTimeout(function() {
710
+ return _this._renderTipBackground(my.split(' ')[0]);
711
+ }, 10);
712
+ };
713
+
714
+ QTip.prototype._adjustPlacement = function(my, at) {
715
+ if (my.indexOf('top') === 0) {
716
+ return this._adjust(0, ADJUST);
717
+ } else if (my.indexOf('bottom') === 0) {
718
+ return this._adjust(0, -ADJUST);
719
+ } else if (my.indexOf('right') === 0) {
720
+ return this._adjust(-ADJUST, 0);
721
+ } else {
722
+ return this._adjust(ADJUST, 0);
723
+ }
724
+ };
725
+
726
+ QTip.prototype._adjust = function(adjustX, adjusty) {
727
+ this.qtip.set('position.adjust.x', adjustX);
728
+ return this.qtip.set('position.adjust.y', adjusty);
729
+ };
730
+
731
+ QTip.prototype._renderTipBackground = function(direction) {
732
+ var bg, el;
733
+ el = $('#qtip-' + this.qtip.id + ' .qtip-tip');
734
+ bg = el.find('.qtip-tip-bg');
735
+ if (!bg.length) {
736
+ bg = $('<div/>', {
737
+ 'class': 'icon icon-tip qtip-tip-bg'
738
+ });
739
+ el.append(bg);
740
+ }
741
+ bg.removeClass('top left right bottom');
742
+ return bg.addClass(direction);
743
+ };
744
+
745
+ return QTip;
746
+
747
+ })(Tourist.Tip.Base);
748
+
749
+ /*
750
+ Simplest implementation of a tooltip. Used in the tests. Useful as an example
751
+ as well.
752
+ */
753
+
754
+
755
+ Tourist.Tip.Simple = (function(_super) {
756
+ __extends(Simple, _super);
757
+
758
+ function Simple() {
759
+ _ref3 = Simple.__super__.constructor.apply(this, arguments);
760
+ return _ref3;
761
+ }
762
+
763
+ Simple.prototype.initialize = function(options) {
764
+ return $('body').append(this.el);
765
+ };
766
+
767
+ Simple.prototype.show = function() {
768
+ return this.el.show();
769
+ };
770
+
771
+ Simple.prototype.hide = function() {
772
+ return this.el.hide();
773
+ };
774
+
775
+ Simple.prototype._getTipElement = function() {
776
+ return this.el;
777
+ };
778
+
779
+ Simple.prototype._renderContent = function(step, contentElement) {
780
+ return this.el.html(contentElement);
781
+ };
782
+
783
+ return Simple;
784
+
785
+ })(Tourist.Tip.Base);
786
+
787
+ /*
788
+
789
+ A way to make a tour. Basically, you specify a series of steps which explain
790
+ elements to point at and what to say. This class manages moving between those
791
+ steps.
792
+
793
+ The 'step object' is a simple js obj that specifies how the step will behave.
794
+
795
+ A simple Example of a step object:
796
+ {
797
+ content: '<p>Welcome to my step</p>'
798
+ target: $('#something-to-point-at')
799
+ closeButton: true
800
+ highlightTarget: true
801
+ setup: (tour, options) ->
802
+ # do stuff in the interface/bind
803
+ teardown: (tour, options) ->
804
+ # remove stuff/unbind
805
+ }
806
+
807
+ Basic Step object options:
808
+
809
+ content - a string of html to put into the step.
810
+ target - jquery object or absolute point: [10, 30]
811
+ highlightTarget - optional bool, true will outline the target with a bright color.
812
+ container - optional jquery element that should contain the step flyout.
813
+ default: $('body')
814
+ viewport - optional jquery element that the step flyout should stay within.
815
+ $(window) is commonly used. default: false
816
+
817
+ my - string position of the pointer on the tip. default: 'left center'
818
+ at - string position on the element the tip points to. default: 'right center'
819
+ see http://craigsworks.com/projects/qtip2/docs/position/#basics
820
+
821
+ Step object button options:
822
+
823
+ okButton - optional bool, true will show a red ok button
824
+ closeButton - optional bool, true will show a grey close button
825
+ skipButton - optional bool, true will show a grey skip button
826
+ nextButton - optional bool, true will show a red next button
827
+
828
+ Step object function options:
829
+
830
+ All functions on the step will have the signature '(tour, options) ->'
831
+
832
+ tour - the Draw.Tour object. Handy to call tour.next()
833
+ options - the step options. An object passed into the tour when created.
834
+ It has the environment that the fns can use to manipulate the
835
+ interface, bind to events, etc. The same object is passed to all
836
+ of a step object's functions, so it is handy for passing data
837
+ between steps.
838
+
839
+ setup - called before step is shown. Use to scroll to your target, hide/show things, ...
840
+
841
+ 'this' is the step object itself.
842
+
843
+ MUST return an object. Properties in the returned object will override
844
+ properties in the step object.
845
+
846
+ i.e. the target might be dynamic so you would specify:
847
+
848
+ setup: (tour, options) ->
849
+ return { target: $('#point-to-me') }
850
+
851
+ teardown - function called right before hiding the step. Use to unbind from
852
+ things you bound to in setup().
853
+
854
+ 'this' is the step object itself.
855
+
856
+ Return nothing.
857
+
858
+ bind - an array of function names to bind. Use this for event handlers you use in setup().
859
+
860
+ Will bind functions to the step object as this, and the first 2 args as tour and options.
861
+
862
+ i.e.
863
+
864
+ bind: ['onChangeSomething']
865
+ setup: (tour, options) ->
866
+ options.document.bind('change:something', @onChangeSomething)
867
+ onChangeSomething: (tour, options, model, value) ->
868
+ tour.next()
869
+ teardown: (tour, options) ->
870
+ options.document.unbind('change:something', @onChangeSomething)
871
+ */
872
+
873
+
874
+ Tourist.Tour = (function() {
875
+ _.extend(Tour.prototype, Backbone.Events);
876
+
877
+ function Tour(options) {
878
+ var defs, tipOptions;
879
+ this.options = options != null ? options : {};
880
+ this.onChangeCurrentStep = __bind(this.onChangeCurrentStep, this);
881
+ this.next = __bind(this.next, this);
882
+ defs = {
883
+ tipClass: 'Bootstrap'
884
+ };
885
+ this.options = _.extend(defs, this.options);
886
+ this.model = new Tourist.Model({
887
+ current_step: null
888
+ });
889
+ tipOptions = _.extend({
890
+ model: this.model
891
+ }, this.options.tipOptions);
892
+ this.view = new Tourist.Tip[this.options.tipClass](tipOptions);
893
+ this.view.bind('click:close', _.bind(this.stop, this, true));
894
+ this.view.bind('click:next', this.next);
895
+ this.model.bind('change:current_step', this.onChangeCurrentStep);
896
+ }
897
+
898
+ /*
899
+ Public
900
+ */
901
+
902
+
903
+ Tour.prototype.start = function() {
904
+ this.trigger('start', this);
905
+ return this.next();
906
+ };
907
+
908
+ Tour.prototype.stop = function(doFinalStep) {
909
+ if (doFinalStep) {
910
+ return this._showCancelFinalStep();
911
+ } else {
912
+ return this._stop();
913
+ }
914
+ };
915
+
916
+ Tour.prototype.next = function() {
917
+ var currentStep, index;
918
+ currentStep = this._teardownCurrentStep();
919
+ index = 0;
920
+ if (currentStep) {
921
+ index = currentStep.index + 1;
922
+ }
923
+ if (index < this.options.steps.length) {
924
+ return this._showStep(this.options.steps[index], index);
925
+ } else if (index === this.options.steps.length) {
926
+ return this._showSuccessFinalStep();
927
+ } else {
928
+ return this._stop();
929
+ }
930
+ };
931
+
932
+ Tour.prototype.setStepOptions = function(stepOptions) {
933
+ return this.options.stepOptions = stepOptions;
934
+ };
935
+
936
+ /*
937
+ Handlers
938
+ */
939
+
940
+
941
+ Tour.prototype.onChangeCurrentStep = function(model, step) {
942
+ return this.view.render(step);
943
+ };
944
+
945
+ /*
946
+ Private
947
+ */
948
+
949
+
950
+ Tour.prototype._showCancelFinalStep = function() {
951
+ return this._showFinalStep(false);
952
+ };
953
+
954
+ Tour.prototype._showSuccessFinalStep = function() {
955
+ return this._showFinalStep(true);
956
+ };
957
+
958
+ Tour.prototype._teardownCurrentStep = function() {
959
+ var currentStep;
960
+ currentStep = this.model.get('current_step');
961
+ this._teardownStep(currentStep);
962
+ return currentStep;
963
+ };
964
+
965
+ Tour.prototype._stop = function() {
966
+ this._teardownCurrentStep();
967
+ this.model.set({
968
+ current_step: null
969
+ });
970
+ return this.trigger('stop', this);
971
+ };
972
+
973
+ Tour.prototype._showFinalStep = function(success) {
974
+ var currentStep, finalStep;
975
+ currentStep = this._teardownCurrentStep();
976
+ finalStep = success ? this.options.successStep : this.options.cancelStep;
977
+ if (_.isFunction(finalStep)) {
978
+ finalStep.call(this, this, this.options.stepOptions);
979
+ finalStep = null;
980
+ }
981
+ if (!finalStep) {
982
+ return this._stop();
983
+ }
984
+ if (currentStep && currentStep.final) {
985
+ return this._stop();
986
+ }
987
+ finalStep.final = true;
988
+ return this._showStep(finalStep, this.options.steps.length);
989
+ };
990
+
991
+ Tour.prototype._showStep = function(step, index) {
992
+ if (!step) {
993
+ return;
994
+ }
995
+ step = _.clone(step);
996
+ step.index = index;
997
+ step.total = this.options.steps.length;
998
+ if (!step.final) {
999
+ step.final = this.options.steps.length === index + 1 && !this.options.successStep;
1000
+ }
1001
+ step = _.extend(step, this._setupStep(step));
1002
+ return this.model.set({
1003
+ current_step: step
1004
+ });
1005
+ };
1006
+
1007
+ Tour.prototype._setupStep = function(step) {
1008
+ var fn, _i, _len, _ref4;
1009
+ if (!(step && step.setup)) {
1010
+ return {};
1011
+ }
1012
+ if (step.bind) {
1013
+ _ref4 = step.bind;
1014
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
1015
+ fn = _ref4[_i];
1016
+ step[fn] = _.bind(step[fn], step, this, this.options.stepOptions);
1017
+ }
1018
+ }
1019
+ return step.setup.call(step, this, this.options.stepOptions) || {};
1020
+ };
1021
+
1022
+ Tour.prototype._teardownStep = function(step) {
1023
+ if (step && step.teardown) {
1024
+ step.teardown.call(step, this, this.options.stepOptions);
1025
+ }
1026
+ return this.view.cleanupCurrentTarget();
1027
+ };
1028
+
1029
+ return Tour;
1030
+
1031
+ })();
1032
+
1033
+ }).call(this);