bootstrap_wizard_rails 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cfa55fbc433cdfaaca8f109390f57098faae2a74
4
+ data.tar.gz: 30fc59864b37f207583ccfed3666460804571e67
5
+ SHA512:
6
+ metadata.gz: 7e598d2220187f0480ffdf303d69818e0f2eb6db123fba360d8b68c849e3703420589af48a179c2f0df4f18787779cc82ec3050a4e7a2f5b87eff9080c5bd974
7
+ data.tar.gz: dc18b3ea0006c21d61ac541cc20630fb3b9697ed3bf907affcb984387ba1d28ce7968eed4dcea4dea1f2f943697dbd203654ba16acc59ea347731adf393891ba
@@ -0,0 +1,4 @@
1
+ module BootstrapWizardRails
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,1171 @@
1
+ /*
2
+ * Copyright (C) 2013 Panopta, Andrew Moffat
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ (function ($) {
23
+ $.fn.wizard = function(args) {
24
+ return new Wizard(this, args);
25
+ };
26
+
27
+ $.fn.wizard.logging = false;
28
+
29
+ var WizardCard = function(wizard, card, index, prev, next) {
30
+ this.wizard = wizard;
31
+ this.index = index;
32
+ this.prev = prev;
33
+ this.next = next;
34
+ this.el = card;
35
+ this.title = card.find("h3").first().text();
36
+ this.name = card.data("cardname") || this.title;
37
+
38
+ this.nav = this._createNavElement(this.title, index);
39
+
40
+ this._disabled = false;
41
+ this._loaded = false;
42
+ this._events = {};
43
+ };
44
+
45
+ WizardCard.prototype = {
46
+ select: function() {
47
+ this.log("selecting");
48
+ if (!this.isSelected()) {
49
+ this.nav.addClass("active");
50
+ this.el.show();
51
+
52
+ if (!this._loaded) {
53
+ this.trigger("loaded");
54
+ this.reload();
55
+ }
56
+
57
+ this.trigger("selected");
58
+ }
59
+
60
+
61
+ /*
62
+ * this is ugly, but we're handling the changing of the wizard's
63
+ * buttons here, in the WizardCard select. so when a card is
64
+ * selected, we're figuring out if we're the first card or the
65
+ * last card and changing the wizard's buttons via the guts of
66
+ * the wizard
67
+ *
68
+ * ideally this logic should be encapsulated by some wizard methods
69
+ * that we can call from here, instead of messing with the guts
70
+ */
71
+ var w = this.wizard;
72
+
73
+ // The back button is only disabled on this first card...
74
+ w.backButton.toggleClass("disabled", this.index == 0);
75
+
76
+ if (this.index >= w._cards.length-1) {
77
+ this.log("on last card, changing next button to submit");
78
+
79
+ w.changeNextButton(w.args.buttons.submitText, "btn-success");
80
+ w._readyToSubmit = true;
81
+ w.trigger("readySubmit");
82
+ }
83
+ else {
84
+ w._readyToSubmit = false;
85
+ w.changeNextButton(w.args.buttons.nextText, "btn-primary");
86
+ }
87
+
88
+ return this;
89
+ },
90
+
91
+ _createNavElement: function(name, i) {
92
+ var li = $('<li class="wizard-nav-item"></li>');
93
+ var a = $('<a class="wizard-nav-link"></a>');
94
+ a.data("navindex", i);
95
+ li.append(a);
96
+ a.append('<span class="glyphicon glyphicon-chevron-right"></span> ');
97
+ a.append(name);
98
+ return li;
99
+ },
100
+
101
+ markVisited: function() {
102
+ this.log("marking as visited");
103
+ this.nav.addClass("already-visited");
104
+ this.trigger("markVisited");
105
+ return this;
106
+ },
107
+
108
+ unmarkVisited: function() {
109
+ this.log("unmarking as visited");
110
+ this.nav.removeClass("already-visited");
111
+ this.trigger("unmarkVisited");
112
+ return this;
113
+ },
114
+
115
+ deselect: function() {
116
+ this.nav.removeClass("active");
117
+ this.el.hide();
118
+ this.trigger("deselect");
119
+ return this;
120
+ },
121
+
122
+ enable: function() {
123
+ this.log("enabling");
124
+
125
+ // Issue #38 Hiding navigation link when hide card
126
+ // Awaiting approval
127
+ //
128
+ // this.nav.removeClass('hide');
129
+
130
+ this.nav.addClass("active");
131
+ this._disabled = false;
132
+ this.trigger("enabled");
133
+ return this;
134
+ },
135
+
136
+ disable: function(hideCard) {
137
+ this.log("disabling");
138
+ this._disabled = true;
139
+ this.nav.removeClass("active already-visited");
140
+ if (hideCard) {
141
+ this.el.hide();
142
+ // Issue #38 Hiding navigation link when hide card
143
+ // Awaiting approval
144
+ //
145
+ // this.nav.addClass('hide');
146
+ }
147
+ this.trigger("disabled");
148
+ return this;
149
+ },
150
+
151
+ isDisabled: function() {
152
+ return this._disabled;
153
+ },
154
+
155
+ alreadyVisited: function() {
156
+ return this.nav.hasClass("already-visited");
157
+ },
158
+
159
+ isSelected: function() {
160
+ return this.nav.hasClass("active");
161
+ },
162
+
163
+ reload: function() {
164
+ this._loaded = true;
165
+ this.trigger("reload");
166
+ return this;
167
+ },
168
+
169
+ on: function() {
170
+ return this.wizard.on.apply(this, arguments);
171
+ },
172
+
173
+ trigger: function() {
174
+ this.callListener("on"+arguments[0]);
175
+ return this.wizard.trigger.apply(this, arguments);
176
+ },
177
+
178
+ /*
179
+ * displays an alert box on the current card
180
+ */
181
+ toggleAlert: function(msg, toggle) {
182
+ this.log("toggling alert to: " + toggle);
183
+
184
+ toggle = typeof(toggle) == "undefined" ? true : toggle;
185
+
186
+ if (toggle) {this.trigger("showAlert");}
187
+ else {this.trigger("hideAlert");}
188
+
189
+ var div;
190
+ var alert = this.el.children("h3").first().next("div.alert");
191
+
192
+ if (alert.length == 0) {
193
+ /*
194
+ * we're hiding anyways, so no need to create anything.
195
+ * we'll do that if we ever are actually showing the alert
196
+ */
197
+ if (!toggle) {return this;}
198
+
199
+ this.log("couldn't find existing alert div, creating one");
200
+ div = $("<div />");
201
+ div.addClass("alert");
202
+ div.addClass("hide");
203
+ div.insertAfter(this.el.find("h3").first());
204
+ }
205
+ else {
206
+ this.log("found existing alert div");
207
+ div = alert.first();
208
+ }
209
+
210
+ if (toggle) {
211
+ if (msg != null) {
212
+ this.log("setting alert msg to", msg);
213
+ div.html(msg);
214
+ }
215
+ div.show();
216
+ }
217
+ else {
218
+ div.hide();
219
+ }
220
+ return this;
221
+ },
222
+
223
+ /*
224
+ * this looks for event handlers embedded into the html of the
225
+ * wizard card itself, in the form of a data- attribute
226
+ */
227
+ callListener: function(name) {
228
+ // a bug(?) in jquery..can't access data-<name> if name is camelCase
229
+ name = name.toLowerCase();
230
+
231
+ this.log("looking for listener " + name);
232
+ var listener = window[this.el.data(name)];
233
+ if (listener) {
234
+ this.log("calling listener " + name);
235
+ var wizard = this.wizard;
236
+
237
+ try {
238
+ var vret = listener(this);
239
+ }
240
+ catch (e) {
241
+ this.log("exception calling listener " + name + ": ", e);
242
+ }
243
+ }
244
+ else {
245
+ this.log("didn't find listener " + name);
246
+ }
247
+ },
248
+
249
+ problem: function(toggle) {
250
+ this.nav.find("a").toggleClass("wizard-step-error", toggle);
251
+ },
252
+
253
+ validate: function() {
254
+ var failures = false;
255
+ var self = this;
256
+
257
+ /*
258
+ * run all the validators embedded on the inputs themselves
259
+ */
260
+ this.el.find("[data-validate]").each(function(i, el) {
261
+ self.log("validating individiual inputs");
262
+ el = $(el);
263
+
264
+ var v = el.data("validate");
265
+ if (!v) {return;}
266
+
267
+ var ret = {
268
+ status: true,
269
+ title: "Error",
270
+ msg: ""
271
+ };
272
+
273
+ var vret = window[v](el);
274
+ $.extend(ret, vret);
275
+
276
+ // Add-On
277
+ // This allows the use of a INPUT+BTN used as one according to boostrap layout
278
+ // for the wizard it is required to add an id with btn-(ID of Input)
279
+ // this will make sure the popover is drawn on the correct element
280
+ if ( $('#btn-' + el.attr('id')).length === 1 ) {
281
+ el = $('#btn-' + el.attr('id'));
282
+ }
283
+
284
+ if (!ret.status) {
285
+ failures = true;
286
+
287
+ // Updated to show error on correct form-group
288
+ el.parents("div.form-group").toggleClass("has-error", true);
289
+
290
+ // This allows the use of a INPUT+BTN used as one according to boostrap layout
291
+ // for the wizard it is required to add an id with btn-(ID of Input)
292
+ // this will make sure the popover is drawn on the correct element
293
+ if ( $('#btn-' + el.attr('id')).length === 1 ) {
294
+ el = $('#btn-' + el.attr('id'));
295
+ }
296
+
297
+ self.wizard.errorPopover(el, ret.msg);
298
+ } else {
299
+ el.parents("div.form-group").toggleClass("has-error", false);
300
+
301
+ // This allows the use of a INPUT+BTN used as one according to boostrap layout
302
+ // for the wizard it is required to add an id with btn-(ID of Input)
303
+ // this will make sure the popover is drawn on the correct element
304
+ if ( $('#btn-' + el.attr('id')).length === 1 ) {
305
+ el = $('#btn-' + el.attr('id'));
306
+ }
307
+
308
+ try {
309
+ el.popover("destroy");
310
+ }
311
+ /*
312
+ * older versions of bootstrap don't have a destroy call
313
+ * for popovers
314
+ */
315
+ catch (e) {
316
+ el.popover("hide");
317
+ }
318
+ }
319
+ });
320
+ this.log("after validating inputs, failures is", failures);
321
+
322
+ /*
323
+ * run the validator embedded in the card
324
+ */
325
+ var cardValidator = window[this.el.data("validate")];
326
+ if (cardValidator) {
327
+ this.log("running html-embedded card validator");
328
+ var cardValidated = cardValidator(this);
329
+ if (typeof(cardValidated) == "undefined" || cardValidated == null) {
330
+ cardValidated = true;
331
+ }
332
+ if (!cardValidated) failures = true;
333
+ this.log("after running html-embedded card validator, failures is", failures);
334
+ }
335
+
336
+ /*
337
+ * run the validate listener
338
+ */
339
+ this.log("running listener validator");
340
+ var listenerValidated = this.trigger("validate");
341
+ if (typeof(listenerValidated) == "undefined" || listenerValidated == null) {
342
+ listenerValidated = true;
343
+ }
344
+ if (!listenerValidated) failures = true;
345
+ this.log("after running listener validator, failures is", failures);
346
+
347
+ var validated = !failures;
348
+ if (validated) {
349
+ this.log("validated, calling listeners");
350
+ this.trigger("validated");
351
+ }
352
+ else {
353
+ this.log("invalid");
354
+ this.trigger("invalid");
355
+ }
356
+ return validated;
357
+ },
358
+
359
+ log: function() {
360
+ if (!window.console || !$.fn.wizard.logging) {return;}
361
+ var prepend = "card '"+this.name+"': ";
362
+ var args = [prepend];
363
+ args.push.apply(args, arguments);
364
+
365
+ console.log.apply(console, args);
366
+ },
367
+
368
+ isActive: function() {
369
+ return this.nav.hasClass("active");
370
+ }
371
+ };
372
+
373
+ Wizard = function(markup, args) {
374
+
375
+ /* TEMPLATE */
376
+ this.wizard_template = [
377
+ '<div class="modal fade wizard">',
378
+ '<div class="modal-dialog wizard-dialog">',
379
+ '<div class="modal-content wizard-content">',
380
+ '<div class="modal-header wizard-header">',
381
+ '<button type="button" class="close wizard-close" aria-hidden="true">&times;</button>',
382
+ '<h3 class="modal-title wizard-title"></h3>',
383
+ '<span class="wizard-subtitle"></span>',
384
+ '</div>',
385
+ '<div class="modal-body wizard-body">',
386
+ '<div class="pull-left wizard-steps">',
387
+ '<div class="wizard-nav-container">',
388
+ '<ul class="nav wizard-nav-list">',
389
+ '</ul>',
390
+ '</div>',
391
+ '<div class="wizard-progress-container">',
392
+ '<div class="progress progress-striped">',
393
+ '<div class="progress-bar" style="width: 0%;"></div>',
394
+ '</div>',
395
+ '</div>',
396
+ '</div>',
397
+ '<form>',
398
+ '<div class="wizard-cards">',
399
+ '<div class="wizard-card-container">',
400
+ '</div>',
401
+ '<div class="wizard-footer">',
402
+ '<div class="wizard-buttons-container">',
403
+ '<button class="btn wizard-cancel wizard-close" type="button">Cancel</button>',
404
+ '<div class="btn-group-single pull-right">',
405
+ '<button class="btn wizard-back" type="button">Back</button>',
406
+ '<button class="btn btn-primary wizard-next" type="button">Next</button>',
407
+ '</div>',
408
+ '</div>',
409
+ '</div>',
410
+ '</div>',
411
+ '</form>',
412
+ '</div>',
413
+
414
+ '</div>',
415
+ '</div>',
416
+ '</div>'
417
+ ];
418
+
419
+ this.args = {
420
+ keyboard: true,
421
+ backdrop: true,
422
+ show: false,
423
+ submitUrl: "",
424
+ showCancel: false,
425
+ showClose: true,
426
+ progressBarCurrent: false,
427
+ increaseHeight: 0,
428
+ contentHeight: 300,
429
+ contentWidth: 580,
430
+ buttons: {
431
+ cancelText: "Cancel",
432
+ nextText: "Next",
433
+ backText: "Back",
434
+ submitText: "Submit",
435
+ submittingText: "Submitting...",
436
+ },
437
+ formClass: "form-horizontal"
438
+ };
439
+
440
+ $.extend(this.args, args || {});
441
+
442
+ this._create(markup);
443
+ };
444
+
445
+ Wizard.prototype = {
446
+ log: function() {
447
+ if (!window.console || !$.fn.wizard.logging) {return;}
448
+ var prepend = "wizard "+this.el.id+": ";
449
+ var args = [prepend];
450
+ args.push.apply(args, arguments);
451
+ console.log.apply(console, args);
452
+ },
453
+
454
+ _create: function(markup) {
455
+ this.markup = $(markup);
456
+ this.title = this.markup.data('title');
457
+ this.submitCards = this.markup.find(".wizard-error,.wizard-failure,.wizard-success,.wizard-loading");
458
+ this.el = $(this.wizard_template.join('\n'));
459
+ $('body').append(this.el);
460
+
461
+ this.modal = this.el.modal({
462
+ keyboard: this.args.keyboard,
463
+ show: this.args.show,
464
+ backdrop: this.args.backdrop
465
+ });
466
+
467
+ this.dimensions = {
468
+ contentHeight: this.args.contentHeight,
469
+ contentWidth: this.args.contentWidth
470
+ };
471
+ this.dialog = this.modal.find('.wizard-dialog');
472
+ this.content = this.modal.find('.wizard-content');
473
+ this.header = this.modal.find('.wizard-header');
474
+ this.body = this.modal.find('.wizard-body');
475
+ this.wizardSteps = this.modal.find('.wizard-steps');
476
+ this.wizardCards = this.modal.find('.wizard-cards');
477
+ this.wizardCardContainer = this.modal.find('.wizard-card-container');
478
+ this.wizardCardContainer
479
+ .append(this.markup.find('.wizard-card'))
480
+ .append(this.submitCards);
481
+ this.navContainer = this.modal.find('.wizard-nav-container');
482
+ this.navList = this.modal.find('.wizard-nav-list');
483
+ this.progressContainer = this.modal.find('.wizard-progress-container');
484
+ this.progress = this.progressContainer.find('.progress-bar');
485
+ this.closeButton = this.modal.find('button.wizard-close.close');
486
+ this.cardsContainer = this.modal.find('wizard-cards-container');
487
+ this.form = this.modal.find('form');
488
+ this.footer = this.modal.find(".wizard-footer");
489
+ this.cancelButton = this.footer.find(".wizard-cancel");
490
+ this.backButton = this.footer.find(".wizard-back");
491
+ this.nextButton = this.footer.find(".wizard-next");
492
+
493
+ this._cards = [];
494
+ this.cards = {};
495
+ this._readyToSubmit = false;
496
+ this.percentComplete = 0;
497
+ this._submitting = false;
498
+ this._events = {};
499
+ this._firstShow = true;
500
+
501
+ this._createCards();
502
+
503
+ this.nextButton.click(this, this._handleNextClick);
504
+ this.backButton.click(this, this._handleBackClick);
505
+
506
+ this.cancelButton.text(this.args.buttons.cancelText);
507
+ this.backButton.text(this.args.buttons.backText);
508
+ this.nextButton.text(this.args.buttons.nextText);
509
+
510
+ // Apply Form Class(es)
511
+ this.form.addClass(this.args.formClass);
512
+
513
+ // Register Array Holder for popovers
514
+ this.popovers = [];
515
+
516
+ var self = this;
517
+ var _close = function() {
518
+ self.reset();
519
+ self.close();
520
+ self.trigger("closed");
521
+ };
522
+
523
+ // Register Close Button
524
+ this.closeButton.click(_close);
525
+ this.cancelButton.click(_close);
526
+
527
+ this.wizardSteps.on("click", "li.already-visited a.wizard-nav-link", this,
528
+ function(event) {
529
+ var index = parseInt($(event.target).data("navindex"));
530
+ event.data.setCard(index);
531
+ });
532
+
533
+ if ( this.title.length != 0 ) {
534
+ this.setTitle(this.title);
535
+ }
536
+
537
+ this.on("submit", this._defaultSubmit);
538
+
539
+ // Set Modal Dimensions
540
+ this.autoDimensions();
541
+ },
542
+
543
+ autoDimensions: function() {
544
+ // DO NOT REMOVE DISPLAY ; Temporary display is required for calculation
545
+ this.modal.css('display', 'block');
546
+
547
+ this.dimensions.header = this.header.outerHeight(true);
548
+
549
+ // Navigation Pane is dyanmic build on card content
550
+ // Navigation Pane === BASE Inner Content Height
551
+ this.dimensions.navigation = this.wizardSteps.outerHeight(true);
552
+ if ( this.dimensions.navigation < this.dimensions.contentHeight ) {
553
+ this.dimensions.navigation = this.dimensions.contentHeight;
554
+ this.navContainer.height( (this.dimensions.contentHeight-30) - this.progressContainer.outerHeight(true));
555
+ }
556
+
557
+ // Dimension Alias ( Body Height === (Navigation Height) )
558
+ this.dimensions.body = this.dimensions.navigation;
559
+
560
+ // Apply OuterHeight of navigation to it's parent wizardSteps
561
+ this.wizardSteps.height(this.dimensions.body);
562
+
563
+ // Modal Height === (Header + Content)
564
+ this.dimensions.modal = (this.dimensions.header + this.dimensions.navigation);
565
+ this.content.height(this.dimensions.modal + 'px');
566
+ this.dialog.width(this.dimensions.contentWidth);
567
+
568
+ this.body.height(this.dimensions.body + 'px');
569
+ this.wizardCards.height(this.dimensions.body + 'px');
570
+
571
+ // Footer Height
572
+ this.dimensions.footer = this.footer.outerHeight(true);
573
+
574
+ // Card Container === (Body - Footer)
575
+ this.dimensions.cardContainer = (this.dimensions.body - this.dimensions.footer);
576
+ this.wizardCardContainer.height(this.dimensions.cardContainer);
577
+
578
+ // Reposition
579
+ this.dimensions.offset = ($(window).height() - this.dialog.height()) / 2;
580
+ this.dialog.css({
581
+ 'margin-top': this.dimensions.offset + 'px',
582
+ 'padding-top': 0
583
+ });
584
+
585
+ // DO NOT REMOVE NEXT LINE
586
+ this.modal.css('display', '');
587
+ },
588
+
589
+ setTitle: function(title) {
590
+ this.log("setting title to", title);
591
+ this.modal.find(".wizard-title").first().text(title);
592
+ return this;
593
+ },
594
+
595
+ setSubtitle: function(title) {
596
+ this.log("setting subtitle to", title);
597
+ this.modal.find(".wizard-subtitle").first().text(title);
598
+ return this;
599
+ },
600
+
601
+ errorPopover: function(el, msg, allowHtml) {
602
+ this.log("launching popover on", el);
603
+ allowHtml = typeof allowHtml !== "undefined" ? allowHtml : false;
604
+ var popover = el.popover({
605
+ content: msg,
606
+ trigger: "manual",
607
+ html: allowHtml,
608
+ container: el.parents('.form-group')
609
+ }).addClass("error-popover").popover("show").next(".popover");
610
+
611
+ el.parents('.form-group').find('.popover').addClass("error-popover");
612
+
613
+ this.popovers.push(el);
614
+
615
+ return popover;
616
+ },
617
+
618
+ destroyPopover: function(pop) {
619
+ pop = $(pop);
620
+
621
+ /*
622
+ * this is the element that the popover was created for
623
+ */
624
+ try {
625
+ pop.popover("destroy");
626
+ }
627
+ /*
628
+ * older versions of bootstrap don't have a destroy call
629
+ * for popovers
630
+ */
631
+ catch (e) {
632
+ pop.popover("hide");
633
+ }
634
+ },
635
+
636
+ hidePopovers: function(el) {
637
+ this.log("hiding all popovers");
638
+ var self = this;
639
+
640
+ $.each(this.popovers, function(i, p) {
641
+ self.destroyPopover(p);
642
+ });
643
+
644
+ this.modal.find('.has-error').removeClass('has-error');
645
+ this.popovers = [];
646
+ },
647
+
648
+ eachCard: function(fn) {
649
+ $.each(this._cards, fn);
650
+ return this;
651
+ },
652
+
653
+ getActiveCard: function() {
654
+ this.log("getting active card");
655
+ var currentCard = null;
656
+
657
+ $.each(this._cards, function(i, card) {
658
+ if (card.isActive()) {
659
+ currentCard = card;
660
+ return false;
661
+ }
662
+ });
663
+ if (currentCard) {this.log("found active card", currentCard);}
664
+ else {this.log("couldn't find an active card");}
665
+ return currentCard;
666
+ },
667
+
668
+ changeNextButton: function(text, cls) {
669
+ this.log("changing next button, text: " + text, "class: " + cls);
670
+ if (typeof(cls) != "undefined") {
671
+ this.nextButton.removeClass("btn-success btn-primary");
672
+ }
673
+
674
+ if (cls) {
675
+ this.nextButton.addClass(cls);
676
+ }
677
+ this.nextButton.text(text);
678
+ return this;
679
+ },
680
+
681
+ hide: function() {
682
+ this.log("hiding");
683
+ this.modal.modal("hide");
684
+ return this;
685
+ },
686
+
687
+ close: function() {
688
+ this.log("closing");
689
+ this.modal.modal("hide");
690
+ return this;
691
+ },
692
+
693
+
694
+ show: function(modalOptions) {
695
+ this.log("showing");
696
+ if (this._firstShow) {
697
+ this.setCard(0);
698
+ this._firstShow = false;
699
+ }
700
+ if (this.args.showCancel) {
701
+ this.cancelButton.show();
702
+ } else {
703
+ this.cancelButton.hide();
704
+ }
705
+ if (this.args.showClose) { this.closeButton.show(); }
706
+ this.modal.modal('show');
707
+
708
+ return this;
709
+ },
710
+
711
+ on: function(name, fn) {
712
+ this.log("adding listener to event " + name);
713
+ this._events[name] = fn;
714
+ return this;
715
+ },
716
+
717
+ trigger: function() {
718
+ var name = arguments[0];
719
+ var args = Array.prototype.slice.call(arguments);
720
+ args.shift();
721
+ args.unshift(this);
722
+
723
+ this.log("firing event " + name);
724
+ var handler = this._events[name];
725
+ if (handler === undefined && this.wizard !== undefined) {
726
+ handler = this.wizard._events[name];
727
+ }
728
+ var ret = null;
729
+
730
+ if (typeof(handler) == "function") {
731
+ this.log("found event handler, calling " + name);
732
+ try {
733
+ ret = handler.apply(this, args);
734
+ }
735
+ catch (e) {
736
+ this.log("event handler " + name + " had an exception");
737
+ }
738
+ }
739
+ else {
740
+ this.log("couldn't find an event handler for " + name);
741
+ }
742
+ return ret;
743
+ },
744
+
745
+
746
+ reset: function() {
747
+ this.log("resetting");
748
+
749
+ this.updateProgressBar(0);
750
+ this.hideSubmitCards();
751
+
752
+ this.setCard(0);
753
+ this.lockCards();
754
+
755
+ this.enableNextButton();
756
+ this.showButtons();
757
+
758
+ this.hidePopovers();
759
+
760
+ this.trigger("reset");
761
+ return this;
762
+ },
763
+
764
+ /*
765
+ * this handles switching to the next card or previous card, taking
766
+ * care to skip over disabled cards
767
+ */
768
+ _abstractIncrementStep: function(direction, getNext) {
769
+ var current = this.getActiveCard();
770
+ var next;
771
+
772
+ if (current) {
773
+ /*
774
+ * loop until we find a card that isn't disabled
775
+ */
776
+ this.log("searching for valid next card");
777
+ while (true) {
778
+ next = getNext(current);
779
+ if (next) {
780
+ this.log("looking at card", next.index);
781
+ if (next.isDisabled()) {
782
+ this.log("card " + next.index + " is disabled/locked, continuing");
783
+ current = next;
784
+ continue;
785
+ }
786
+ else {
787
+ return this.setCard(current.index+direction);
788
+ }
789
+ }
790
+ else {
791
+ this.log("next card is not defined, breaking");
792
+ break;
793
+ }
794
+ }
795
+ }
796
+ else {
797
+ this.log("current card is undefined");
798
+ }
799
+ },
800
+
801
+
802
+ incrementCard: function() {
803
+ this.log("incrementing card");
804
+ var card = this._abstractIncrementStep(1, function(current){return current.next;});
805
+ this.trigger("incrementCard");
806
+ return card;
807
+ },
808
+
809
+ decrementCard: function() {
810
+ this.log("decrementing card");
811
+ var card = this._abstractIncrementStep(-1, function(current){return current.prev;});
812
+ this.trigger("decrementCard");
813
+ return card;
814
+ },
815
+
816
+ setCard: function(i) {
817
+ this.log("setting card to " + i);
818
+ this.hideSubmitCards();
819
+ var currentCard = this.getActiveCard();
820
+
821
+ if (this._submitting) {
822
+ this.log("we're submitting the wizard already, can't change cards");
823
+ return currentCard;
824
+ }
825
+
826
+ var newCard = this._cards[i];
827
+ if (newCard) {
828
+ if (newCard.isDisabled()) {
829
+ this.log("new card is currently disabled, returning");
830
+ return currentCard;
831
+ }
832
+
833
+ if (currentCard) {
834
+
835
+ /*
836
+ * here, we're only validating if we're going forward,
837
+ * not if we're going backwards in a step
838
+ */
839
+ if (i > currentCard.index) {
840
+ var cardToValidate = currentCard;
841
+ var ok = false;
842
+
843
+ /*
844
+ * we need to loop over every card between our current
845
+ * card and the card that we clicked, and re-validate
846
+ * them. if there's an error, we need to select the
847
+ * first card to have an error
848
+ */
849
+ while (cardToValidate.index != newCard.index) {
850
+ /*
851
+ * unless we're validating the card that we're
852
+ * leaving, we need to select the card, so that
853
+ * any validators that trigger errorPopovers can
854
+ * display correctly
855
+ */
856
+ if (cardToValidate.index != currentCard.index) {
857
+ cardToValidate.prev.deselect();
858
+ cardToValidate.prev.markVisited();
859
+ cardToValidate.select();
860
+ }
861
+ ok = cardToValidate.validate();
862
+ if (!ok) {
863
+ return cardToValidate;
864
+ }
865
+ cardToValidate = cardToValidate.next;
866
+ }
867
+
868
+ cardToValidate.prev.deselect();
869
+ cardToValidate.prev.markVisited();
870
+ }
871
+
872
+ currentCard.deselect();
873
+ currentCard.markVisited();
874
+ }
875
+
876
+ newCard.select();
877
+
878
+ if (this.args.progressBarCurrent) {
879
+ this.percentComplete = i * 100.0 / this._cards.length;
880
+ this.updateProgressBar(this.percentComplete);
881
+ }
882
+ else {
883
+ var lastPercent = this.percentComplete;
884
+ this.percentComplete = i * 100.0 / this._cards.length;
885
+ this.percentComplete = Math.max(lastPercent, this.percentComplete);
886
+ this.updateProgressBar(this.percentComplete);
887
+ }
888
+
889
+ return newCard;
890
+ }
891
+ else {
892
+ this.log("couldn't find card " + i);
893
+ }
894
+ },
895
+
896
+ updateProgressBar: function(percent) {
897
+ this.log("updating progress to " + percent + "%");
898
+ this.progress.css({width: percent + "%"});
899
+ this.percentComplete = percent;
900
+
901
+ this.trigger("progressBar", percent);
902
+
903
+ if (percent == 100) {
904
+ this.log("progress is 100, animating progress bar");
905
+ this.progressContainer.find('.progress').addClass("active");
906
+ }
907
+ else if (percent == 0) {
908
+ this.log("progress is 0, disabling animation");
909
+ this.progressContainer.find('.progress').removeClass("active");
910
+ }
911
+ },
912
+
913
+ getNextCard: function() {
914
+ var currentCard = this.getActiveCard();
915
+ if (currentCard) return currentCard.next;
916
+ },
917
+
918
+ lockCards: function() {
919
+ this.log("locking nav cards");
920
+ this.eachCard(function(i,card){card.unmarkVisited();});
921
+ return this;
922
+ },
923
+
924
+ disableCards: function() {
925
+ this.log("disabling all nav cards");
926
+ this.eachCard(function(i,card){card.disable();});
927
+ return this;
928
+ },
929
+
930
+ enableCards: function() {
931
+ this.log("enabling all nav cards");
932
+ this.eachCard(function(i,card){card.enable();});
933
+ return this;
934
+ },
935
+
936
+ hideCards: function() {
937
+ this.log("hiding cards");
938
+ this.eachCard(function(i,card){card.deselect();});
939
+ this.hideSubmitCards();
940
+ return this;
941
+ },
942
+
943
+ hideButtons: function() {
944
+ this.log("hiding buttons");
945
+ this.cancelButton.hide();
946
+ this.closeButton.hide();
947
+ this.nextButton.hide();
948
+ this.backButton.hide();
949
+ return this;
950
+ },
951
+
952
+ showButtons: function() {
953
+ this.log("showing buttons");
954
+ if (this.args.showCancel) {
955
+ this.cancelButton.show();
956
+ } else {
957
+ this.cancelButton.hide();
958
+ }
959
+ if (this.args.showClose) { this.closeButton.show(); };
960
+ this.nextButton.show();
961
+ this.backButton.show();
962
+ return this;
963
+ },
964
+
965
+ getCard: function(el) {
966
+ var cardDOMEl = $(el).parents(".wizard-card").first()[0];
967
+ if (cardDOMEl) {
968
+ var foundCard = null;
969
+ this.eachCard(function(i, card) {
970
+ if (cardDOMEl == card.el[0]) {
971
+ foundCard = card;
972
+ return false;
973
+ }
974
+ return true;
975
+ });
976
+ return foundCard;
977
+ }
978
+ else {
979
+ return null;
980
+ }
981
+ },
982
+
983
+ _createCards: function() {
984
+ var prev = null;
985
+ var next = null;
986
+ var currentCard = null;
987
+ var wizard = this;
988
+ var self = this;
989
+
990
+ self.log("Creating Cards");
991
+
992
+ var cards = this.modal.find(".wizard-cards .wizard-card");
993
+ $.each(cards, function(i, card) {
994
+ card = $(card);
995
+
996
+ prev = currentCard;
997
+ currentCard = new WizardCard(wizard, card, i, prev, next);
998
+ self._cards.push(currentCard);
999
+ if (currentCard.name) {
1000
+ self.cards[currentCard.name] = currentCard;
1001
+ }
1002
+ if (prev) {prev.next = currentCard;}
1003
+
1004
+ self.modal.find(".wizard-steps .wizard-nav-list").append(currentCard.nav);
1005
+ });
1006
+ },
1007
+
1008
+ showSubmitCard: function(name) {
1009
+ this.log("showing "+name+" submit card");
1010
+
1011
+ var card = this.el.find(".wizard-"+name);
1012
+ if (card.length) {
1013
+ this.hideCards();
1014
+ this.el.find(".wizard-"+name).show();
1015
+ }
1016
+ else {
1017
+ this.log("couldn't find submit card "+name);
1018
+ }
1019
+ },
1020
+
1021
+ hideSubmitCard: function(name) {
1022
+ this.log("hiding "+name+" submit card");
1023
+ this.el.find(".wizard-"+name).hide();
1024
+ },
1025
+
1026
+ hideSubmitCards: function() {
1027
+ var wizard = this;
1028
+ $.each(["success", "error", "failure", "loading"], function(i, name) {
1029
+ wizard.hideSubmitCard(name);
1030
+ });
1031
+ },
1032
+
1033
+ enableNextButton: function() {
1034
+ this.log("enabling next button");
1035
+ this.nextButton.removeAttr("disabled");
1036
+ return this;
1037
+ },
1038
+
1039
+ disableNextButton: function() {
1040
+ this.log("disabling next button");
1041
+ this.nextButton.attr("disabled", "disabled");
1042
+ return this;
1043
+ },
1044
+
1045
+ serializeArray: function() {
1046
+ var form = this.form.serializeArray();
1047
+ this.form.find('input[disabled][data-serialize="1"]').each(function() {
1048
+ formObj = {
1049
+ name: $(this).attr('name'),
1050
+ value: $(this).val()
1051
+ };
1052
+
1053
+ form.push(formObj);
1054
+ });
1055
+
1056
+ return form;
1057
+ },
1058
+
1059
+ serialize: function() {
1060
+ var form = this.form.serialize();
1061
+ this.form.find('input[disabled][data-serialize="1"]').each(function() {
1062
+ form = form + '&' + $(this).attr('name') + '=' + $(this).val();
1063
+ });
1064
+
1065
+ return form;
1066
+ },
1067
+
1068
+ find: function(selector) {
1069
+ return this.modal.find(selector);
1070
+ },
1071
+
1072
+
1073
+ /*
1074
+ * the next 3 functions are to be called by the custom submit event
1075
+ * handler. the idea is that after you make an ajax call to submit
1076
+ * your wizard data (or whatever it is you want to do at the end of
1077
+ * the wizard), you call one of these 3 handlers to display a specific
1078
+ * card for either success, failure, or error
1079
+ */
1080
+ submitSuccess: function() {
1081
+ this.log("submit success");
1082
+ this._submitting = false;
1083
+ this.showSubmitCard("success");
1084
+ this.trigger("submitSuccess");
1085
+ },
1086
+
1087
+ submitFailure: function() {
1088
+ this.log("submit failure");
1089
+ this._submitting = false;
1090
+ this.showSubmitCard("failure");
1091
+ this.trigger("submitFailure");
1092
+ },
1093
+
1094
+ submitError: function() {
1095
+ this.log("submit error");
1096
+ this._submitting = false;
1097
+ this.showSubmitCard("error");
1098
+ this.trigger("submitError");
1099
+ },
1100
+
1101
+
1102
+ _submit: function() {
1103
+ this.log("submitting wizard");
1104
+ this._submitting = true;
1105
+
1106
+ this.lockCards();
1107
+ this.cancelButton.hide();
1108
+ this.closeButton.hide();
1109
+ this.backButton.hide();
1110
+
1111
+ this.showSubmitCard("loading");
1112
+ this.updateProgressBar(100);
1113
+
1114
+ this.changeNextButton(this.args.buttons.submittingText, false);
1115
+ this.disableNextButton();
1116
+
1117
+ var ret = this.trigger("submit");
1118
+ this.trigger("loading");
1119
+ },
1120
+
1121
+ _onNextClick: function() {
1122
+ this.log("handling 'next' button click");
1123
+ var currentCard = this.getActiveCard();
1124
+ if (this._readyToSubmit && currentCard.validate()) {
1125
+ this._submit();
1126
+ }
1127
+ else {
1128
+ currentCard = this.incrementCard();
1129
+ }
1130
+ },
1131
+
1132
+ _onBackClick: function() {
1133
+ this.log("handling 'back' button click");
1134
+ var currentCard = this.decrementCard();
1135
+ },
1136
+
1137
+ _handleNextClick: function(event) {
1138
+ var wizard = event.data;
1139
+ wizard._onNextClick.call(wizard);
1140
+ },
1141
+
1142
+ _handleBackClick: function(event) {
1143
+ var wizard = event.data;
1144
+ wizard._onBackClick.call(wizard);
1145
+ },
1146
+
1147
+
1148
+ /*
1149
+ * this function is attached by default to the wizard's "submit" event.
1150
+ * if you choose to implement your own custom submit logic, you should
1151
+ * copy how this function works
1152
+ */
1153
+ _defaultSubmit: function(wizard) {
1154
+ $.ajax({
1155
+ type: "POST",
1156
+ url: wizard.args.submitUrl,
1157
+ data: wizard.serialize(),
1158
+ dataType: "json"
1159
+ }).done(function(response) {
1160
+ wizard.submitSuccess();
1161
+ wizard.hideButtons();
1162
+ wizard.updateProgressBar(0);
1163
+ }).fail(function() {
1164
+ wizard.submitFailure();
1165
+ wizard.hideButtons();
1166
+ });
1167
+ }
1168
+ };
1169
+
1170
+
1171
+ }(window.jQuery));