tourist-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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);