bootstrap-application-wizard-rails 13.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ require 'bootstrap-application-wizard-rails/version'
2
+
3
+ module BootstrapApplicationWizard
4
+ module Rails
5
+ if ::Rails.version < '3.1'
6
+ require 'bootstrap-application-wizard-rails/railtie'
7
+ else
8
+ require 'bootstrap-application-wizard-rails/engine'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module BootstrapApplicationWizard
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module BootstrapApplicationWizard
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module BootstrapApplicationWizard
2
+ module Rails
3
+ VERSION = '13.3.3'
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ //= require bootstrap-wizard
@@ -0,0 +1,1047 @@
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
+
23
+ (function ($) {
24
+ $.fn.wizard = function(args) {
25
+ return new Wizard(this, args);
26
+ };
27
+
28
+ $.fn.wizard.logging = false;
29
+
30
+
31
+ WizardCard = function(wizard, card, index, prev, next) {
32
+ this.wizard = wizard;
33
+ this.index = index;
34
+ this.prev = prev;
35
+ this.next = next;
36
+ this.el = card;
37
+ this.title = card.find("h3").first().text();
38
+ this.name = card.data("cardname") || this.title;
39
+
40
+ this.nav = this._createNavElement(this.title, index);
41
+
42
+ this._disabled = false;
43
+ this._loaded = false;
44
+ this._events = {};
45
+ }
46
+
47
+ WizardCard.prototype = {
48
+
49
+ select: function() {
50
+ this.log("selecting");
51
+ if (!this.isSelected()) {
52
+ this.nav.addClass("active");
53
+ this.el.show();
54
+
55
+ if (!this._loaded) {
56
+ this.trigger("loaded");
57
+ this.reload();
58
+ }
59
+
60
+ this.trigger("selected");
61
+ }
62
+
63
+
64
+ /*
65
+ * this is ugly, but we're handling the changing of the wizard's
66
+ * buttons here, in the WizardCard select. so when a card is
67
+ * selected, we're figuring out if we're the first card or the
68
+ * last card and changing the wizard's buttons via the guts of
69
+ * the wizard
70
+ *
71
+ * ideally this logic should be encapsulated by some wizard methods
72
+ * that we can call from here, instead of messing with the guts
73
+ */
74
+ var w = this.wizard;
75
+ if (this.index >= w._cards.length-1) {
76
+ this.log("on last card, changing next button to submit");
77
+
78
+ w.changeNextButton(w.args.buttons.submitText, "btn-success");
79
+ w._readyToSubmit = true;
80
+ w.trigger("readySubmit");
81
+ }
82
+ else {
83
+ w._readyToSubmit = false;
84
+ if (this.index == 0) {
85
+ w.backButton.toggleClass("disabled", true);
86
+ }
87
+ else {
88
+ w.backButton.toggleClass("disabled", false);
89
+ }
90
+ w.changeNextButton(w.args.buttons.nextText, "btn-primary");
91
+ }
92
+
93
+ return this;
94
+ },
95
+
96
+ // <li><a href="#" data-navindex="0">
97
+ // <i class="icon-chevron-right"></i>
98
+ // Name &amp; FQDN
99
+ // </a></li>
100
+ _createNavElement: function(name, i) {
101
+ var li = $('<li class="wizard-nav-item"></li>');
102
+ var a = $('<a class="wizard-nav-link"></a>');
103
+ a.data("navindex", i);
104
+ li.append(a);
105
+ a.append('<i class="icon-chevron-right"></i>')
106
+ a.append(name);
107
+ return li;
108
+ },
109
+
110
+ markVisited: function() {
111
+ this.log("marking as visited");
112
+ this.nav.addClass("already-visited");
113
+ this.trigger("markVisited");
114
+ return this;
115
+ },
116
+
117
+ unmarkVisited: function() {
118
+ this.log("unmarking as visited");
119
+ this.nav.removeClass("already-visited");
120
+ this.trigger("unmarkVisited");
121
+ return this;
122
+ },
123
+
124
+ deselect: function() {
125
+ this.nav.removeClass("active");
126
+ this.el.hide();
127
+ this.trigger("deselect");
128
+ return this;
129
+ },
130
+
131
+ enable: function() {
132
+ this.log("enabling");
133
+ this.nav.addClass("active");
134
+ this._disabled = false;
135
+ this.trigger("enabled");
136
+ return this;
137
+ },
138
+
139
+ disable: function(hideCard) {
140
+ this.log("disabling");
141
+ this._disabled = true;
142
+ this.nav.removeClass("active already-visited");
143
+ if (hideCard) {this.el.hide();}
144
+ this.trigger("disabled");
145
+ return this;
146
+ },
147
+
148
+ isDisabled: function() {
149
+ return this._disabled;
150
+ },
151
+
152
+ alreadyVisited: function() {
153
+ return this.nav.hasClass("already-visited");
154
+ },
155
+
156
+ isSelected: function() {
157
+ return this.nav.hasClass("active");
158
+ },
159
+
160
+ reload: function() {
161
+ this._loaded = true;
162
+ this.trigger("reload");
163
+ return this;
164
+ },
165
+
166
+ on: function() {
167
+ return this.wizard.on.apply(this, arguments);
168
+ },
169
+
170
+ trigger: function() {
171
+ this.callListener("on"+arguments[0]);
172
+ return this.wizard.trigger.apply(this, arguments);
173
+ },
174
+
175
+ /*
176
+ * displays an alert box on the current card
177
+ */
178
+ toggleAlert: function(msg, toggle) {
179
+ this.log("toggling alert to: " + toggle);
180
+
181
+ toggle = typeof(toggle) == "undefined" ? true : toggle;
182
+
183
+ if (toggle) {this.trigger("showAlert");}
184
+ else {this.trigger("hideAlert");}
185
+
186
+ var div;
187
+ var alert = this.el.children("h3").first().next("div.alert");
188
+
189
+ if (alert.length == 0) {
190
+ /*
191
+ * we're hiding anyways, so no need to create anything.
192
+ * we'll do that if we ever are actually showing the alert
193
+ */
194
+ if (!toggle) {return this;}
195
+
196
+ this.log("couldn't find existing alert div, creating one");
197
+ div = $("<div />");
198
+ div.addClass("alert");
199
+ div.addClass("hide");
200
+ div.insertAfter(this.el.find("h3").first());
201
+ }
202
+ else {
203
+ this.log("found existing alert div");
204
+ div = alert.first();
205
+ }
206
+
207
+ if (toggle) {
208
+ if (msg != null) {
209
+ this.log("setting alert msg to", msg);
210
+ div.html(msg);
211
+ }
212
+ div.show();
213
+ }
214
+ else {
215
+ div.hide();
216
+ }
217
+ return this;
218
+ },
219
+
220
+ /*
221
+ * this looks for event handlers embedded into the html of the
222
+ * wizard card itself, in the form of a data- attribute
223
+ */
224
+ callListener: function(name) {
225
+ // a bug(?) in jquery..can't access data-<name> if name is camelCase
226
+ name = name.toLowerCase();
227
+
228
+ this.log("looking for listener " + name);
229
+ var listener = window[this.el.data(name)];
230
+ if (listener) {
231
+ this.log("calling listener " + name);
232
+ var wizard = this.wizard;
233
+
234
+ try {
235
+ var vret = listener(this);
236
+ }
237
+ catch (e) {
238
+ this.log("exception calling listener " + name + ": ", e);
239
+ }
240
+ }
241
+ else {
242
+ this.log("didn't find listener " + name);
243
+ }
244
+ },
245
+
246
+ problem: function(toggle) {
247
+ this.nav.find("a").toggleClass("wizard-step-error", toggle);
248
+ },
249
+
250
+ validate: function() {
251
+ var failures = false;
252
+ var self = this;
253
+
254
+ /*
255
+ * run all the validators embedded on the inputs themselves
256
+ */
257
+ this.el.find("[data-validate]").each(function(i, el) {
258
+ self.log("validating individiual inputs");
259
+ el = $(el);
260
+
261
+ var v = el.data("validate");
262
+ if (!v) {return;}
263
+
264
+ var ret = {
265
+ status: true,
266
+ title: "Error",
267
+ msg: ""
268
+ };
269
+
270
+ var vret = window[v](el);
271
+ $.extend(ret, vret);
272
+
273
+ if (!ret.status) {
274
+ failures = true;
275
+ el.parent(".control-group").toggleClass("error", true);
276
+ self.wizard.errorPopover(el, ret.msg);
277
+ }
278
+ else {
279
+ el.parent(".control-group").toggleClass("error", false);
280
+ try {
281
+ el.popover("destroy");
282
+ }
283
+ /*
284
+ * older versions of bootstrap don't have a destroy call
285
+ * for popovers
286
+ */
287
+ catch (e) {
288
+ el.popover("hide");
289
+ }
290
+ }
291
+ });
292
+ this.log("after validating inputs, failures is", failures);
293
+
294
+ /*
295
+ * run the validator embedded in the card
296
+ */
297
+ var cardValidator = window[this.el.data("validate")];
298
+ if (cardValidator) {
299
+ this.log("running html-embedded card validator");
300
+ var cardValidated = cardValidator(this);
301
+ if (typeof(cardValidated) == "undefined" || cardValidated == null) {
302
+ cardValidated = true;
303
+ }
304
+ if (!cardValidated) failures = true;
305
+ this.log("after running html-embedded card validator, failures\
306
+ is", failures);
307
+ }
308
+
309
+ /*
310
+ * run the validate listener
311
+ */
312
+ this.log("running listener validator");
313
+ var listenerValidated = this.trigger("validate");
314
+ if (typeof(listenerValidated) == "undefined" || listenerValidated == null) {
315
+ listenerValidated = true;
316
+ }
317
+ if (!listenerValidated) failures = true;
318
+ this.log("after running listener validator, failures is", failures);
319
+
320
+ var validated = !failures;
321
+ if (validated) {
322
+ this.log("validated, calling listeners");
323
+ this.trigger("validated");
324
+ }
325
+ else {
326
+ this.log("invalid");
327
+ this.trigger("invalid");
328
+ }
329
+ return validated;
330
+ },
331
+
332
+ log: function() {
333
+ if (!window.console || !$.fn.wizard.logging) {return;}
334
+ var prepend = "card '"+this.name+"': ";
335
+ var args = [prepend];
336
+ args.push.apply(args, arguments);
337
+
338
+ console.log.apply(console, args);
339
+ },
340
+
341
+ isActive: function() {
342
+ return this.nav.hasClass("active");
343
+ },
344
+ };
345
+
346
+
347
+ Wizard = function(markup, args) {
348
+ var wizard_template = [
349
+ '<div class="modal hide wizard-modal" role="dialog">',
350
+ '<div class="wizard-modal-header modal-header">',
351
+ '<button class="wizard-close close" type="button">x</button>',
352
+ '<h3 class="wizard-title"></h3>',
353
+ '<span class="wizard-subtitle"></span>',
354
+ '</div>',
355
+ '<div class="pull-left wizard-steps">',
356
+ '<div class="wizard-nav-container">',
357
+ '<ul class="nav nav-list" style="padding-bottom:30px;">',
358
+ '</ul>',
359
+ '</div>',
360
+
361
+ '<div class="wizard-progress-container">',,
362
+ '<div class="progress progress-striped">',
363
+ '<div class="bar"></div>',
364
+ '</div>',
365
+ '</div>',
366
+
367
+ '</div>',
368
+
369
+ '<form>',
370
+ '<div class="wizard-cards">',
371
+ '<div class="wizard-card-container">',
372
+ '</div>',
373
+ '<div class="wizard-modal-footer">',
374
+ '<div class="wizard-buttons-container">',
375
+ '<button class="btn wizard-back" type="button">Back</button>',
376
+ '<button class="btn btn-primary wizard-next" type="button">Next</button>',
377
+ '</div>',
378
+ '</div>',
379
+ '</div>',
380
+ '</form>',
381
+ '</div>'
382
+ ];
383
+
384
+ this.args = {
385
+ submitUrl: "",
386
+ width: 750,
387
+ progressBarCurrent: false,
388
+ increaseHeight: 0,
389
+ buttons: {
390
+ nextText: "Next",
391
+ backText: "Back",
392
+ submitText: "Submit",
393
+ submittingText: "Submitting...",
394
+ }
395
+ };
396
+ $.extend(this.args, args || {});
397
+
398
+ this.markup = $(markup);
399
+ this.submitCards = this.markup.find(".wizard-error,.wizard-failure,\
400
+ .wizard-success,.wizard-loading");
401
+ this.el = $(wizard_template.join("\n"));
402
+ this.el.find(".wizard-card-container")
403
+ .append(this.markup.find(".wizard-card"))
404
+ .append(this.submitCards);
405
+ $("body").append(this.el);
406
+
407
+ this.closeButton = this.el.find("button.wizard-close");
408
+ this.footer = this.el.find(".wizard-modal-footer");
409
+ this.backButton = this.footer.find(".wizard-back");
410
+ this.nextButton = this.footer.find(".wizard-next");
411
+ this.progress = this.el.find(".progress");
412
+ this._cards = [];
413
+ this.cards = {};
414
+ this._readyToSubmit = false;
415
+ this.percentComplete = 0;
416
+ this._submitting = false;
417
+ this._events = {};
418
+ this._firstShow = true;
419
+
420
+
421
+ // construction
422
+
423
+
424
+ this._createCards();
425
+
426
+ this.nextButton.click(this, this._handleNextClick);
427
+ this.backButton.click(this, this._handleBackClick);
428
+
429
+ this.backButton.text(this.args.buttons.backText);
430
+ this.nextButton.text(this.args.buttons.nextText);
431
+
432
+
433
+
434
+ /*
435
+ * adjust the height of the modal, and everything associated with
436
+ * adjusting the height
437
+ */
438
+ var baseHeight = 360;
439
+ var navHeight = baseHeight + this.args.increaseHeight;
440
+
441
+ this.el.find(".wizard-nav-container").css("height", navHeight);
442
+ this.el.find(".wizard-steps").css("height", (navHeight+65)+"px");
443
+ this.el.find(".wizard-card").css("height", (navHeight-60)+"px");
444
+ this.submitCards.css("height", (navHeight-60)+"px");
445
+
446
+ this.el.css("margin-top", -(this.el.height() / 2));
447
+
448
+
449
+ /*
450
+ * adjust the width of the modal
451
+ */
452
+ this.el.css("width", this.args.width);
453
+ this.el.css("margin-left", -(this.args.width / 2));
454
+
455
+
456
+
457
+ /*
458
+ * set up slimScroll for our nav, if slimScroll is installed
459
+ */
460
+ if ($.fn.slimScroll && false) {
461
+ var slimScrollArgs = {
462
+ position: "left",
463
+ height: "360px",
464
+ size: "8px",
465
+ distance: "5px",
466
+ railVisible: true,
467
+ disableFadeOut: true,
468
+ };
469
+ $.extend(slimScrollArgs, this.args.slimScroll || {});
470
+ this.el.find(".wizard-nav-container").slimScroll(slimScrollArgs);
471
+ }
472
+
473
+ /*
474
+ * if the close X is clicked, reset the wizard
475
+ */
476
+ var self = this;
477
+ this.closeButton.click(function() {
478
+ self.reset();
479
+ self.close();
480
+ })
481
+
482
+ this.el.find(".wizard-steps").on(
483
+ "click", "li.already-visited a.wizard-nav-link", this,
484
+ function(event) {
485
+ var index = parseInt($(event.target).data("navindex"));
486
+ event.data.setCard(index);
487
+ });
488
+
489
+ var title = this.markup.children("h1").first();
490
+ if (title.length) {this.setTitle(title.text());}
491
+
492
+ this.on("submit", this._defaultSubmit);
493
+ }
494
+
495
+ Wizard.prototype = {
496
+
497
+ errorPopover: function(el, msg) {
498
+ this.log("launching popover on", el);
499
+ var popover = el.popover({
500
+ content: msg,
501
+ trigger: "manual"
502
+ }).popover("show").next(".popover");
503
+
504
+ popover.addClass("error-popover");
505
+ return popover;
506
+ },
507
+
508
+ destroyPopover: function(pop) {
509
+ pop = $(pop);
510
+ pop.parent(".control-group").toggleClass("error", false);
511
+
512
+ /*
513
+ * this is the element that the popover was created for
514
+ */
515
+ var el = pop.prev();
516
+
517
+ try {
518
+ el.popover("destroy");
519
+ }
520
+ /*
521
+ * older versions of bootstrap don't have a destroy call
522
+ * for popovers
523
+ */
524
+ catch (e) {
525
+ el.popover("hide");
526
+ }
527
+ },
528
+
529
+ hidePopovers: function(el, msg) {
530
+ this.log("hiding all popovers");
531
+ var self = this;
532
+ this.el.find(".error-popover").each(function (i, popover) {
533
+ self.destroyPopover(popover);
534
+ });
535
+ },
536
+
537
+ eachCard: function(fn) {
538
+ $.each(this._cards, fn);
539
+ return this;
540
+ },
541
+
542
+ getActiveCard: function() {
543
+ this.log("getting active card");
544
+ var currentCard = null;
545
+
546
+ $.each(this._cards, function(i, card) {
547
+ if (card.isActive()) {
548
+ currentCard = card;
549
+ return false;
550
+ }
551
+ });
552
+ if (currentCard) {this.log("found active card", currentCard);}
553
+ else {this.log("couldn't find an active card");}
554
+ return currentCard;
555
+ },
556
+
557
+ setTitle: function(title) {
558
+ this.log("setting title to", title);
559
+ this.el.find(".wizard-title").first().text(title);
560
+ return this;
561
+ },
562
+
563
+ setSubtitle: function(title) {
564
+ this.log("setting subtitle to", title);
565
+ this.el.find(".wizard-subtitle").first().text(title);
566
+ return this;
567
+ },
568
+
569
+ changeNextButton: function(text, cls) {
570
+ this.log("changing next button, text: " + text, "class: " + cls);
571
+ if (typeof(cls) != "undefined") {
572
+ this.nextButton.removeClass("btn-success btn-primary");
573
+ }
574
+
575
+ if (cls) {
576
+ this.nextButton.addClass(cls);
577
+ }
578
+ this.nextButton.text(text);
579
+ return this;
580
+ },
581
+
582
+ hide: function() {
583
+ this.log("hiding");
584
+ this.el.modal("hide");
585
+ return this;
586
+ },
587
+
588
+ close: function() {
589
+ this.log("closing");
590
+ this.el.modal("hide");
591
+ return this;
592
+ },
593
+
594
+
595
+ show: function() {
596
+ this.log("showing");
597
+ if (this._firstShow) {
598
+ this.setCard(0);
599
+ this._firstShow = false;
600
+ }
601
+ this.el.modal();
602
+ return this;
603
+ },
604
+
605
+ on: function(name, fn) {
606
+ this.log("adding listener to event " + name);
607
+ this._events[name] = fn;
608
+ return this;
609
+ },
610
+
611
+ trigger: function() {
612
+ var name = arguments[0];
613
+ var args = Array.prototype.slice.call(arguments);
614
+ args.shift();
615
+ args.unshift(this);
616
+
617
+ this.log("firing event " + name);
618
+ var handler = this._events[name];
619
+ var ret = null;
620
+
621
+ if (typeof(handler) == "function") {
622
+ this.log("found event handler, calling " + name);
623
+ try {
624
+ ret = handler.apply(this, args);
625
+ }
626
+ catch (e) {
627
+ this.log("event handler " + name + " had an exception");
628
+ }
629
+ }
630
+ else {
631
+ this.log("couldn't find an event handler for " + name);
632
+ }
633
+ return ret;
634
+ },
635
+
636
+
637
+ reset: function() {
638
+ this.log("resetting");
639
+
640
+ this.updateProgressBar(0);
641
+ this.hideSubmitCards();
642
+
643
+ this.setCard(0);
644
+ this.lockCards();
645
+
646
+ this.enableNextButton();
647
+ this.showButtons();
648
+
649
+ this.hidePopovers();
650
+
651
+ this.trigger("reset");
652
+ return this;
653
+ },
654
+
655
+ log: function() {
656
+ if (!window.console || !$.fn.wizard.logging) {return;}
657
+ var prepend = "wizard "+this.el.id+": ";
658
+ var args = [prepend];
659
+ args.push.apply(args, arguments);
660
+ console.log.apply(console, args);
661
+ },
662
+
663
+
664
+ /*
665
+ * this handles switching to the next card or previous card, taking
666
+ * care to skip over disabled cards
667
+ */
668
+ _abstractIncrementStep: function(direction, getNext) {
669
+ var current = this.getActiveCard();
670
+ var next;
671
+
672
+ if (current) {
673
+ /*
674
+ * loop until we find a card that isn't disabled
675
+ */
676
+ this.log("searching for valid next card");
677
+ while (true) {
678
+ next = getNext(current);
679
+ if (next) {
680
+ this.log("looking at card", next.index);
681
+ if (next.isDisabled()) {
682
+ this.log("card " + next.index + " is disabled/locked, continuing");
683
+ current = next;
684
+ continue;
685
+ }
686
+ else {
687
+ return this.setCard(current.index+direction);
688
+ }
689
+ }
690
+ else {
691
+ this.log("next card is not defined, breaking");
692
+ break;
693
+ }
694
+ }
695
+ }
696
+ else {
697
+ this.log("current card is undefined");
698
+ }
699
+ },
700
+
701
+
702
+ incrementCard: function() {
703
+ this.log("incrementing card");
704
+ var card = this._abstractIncrementStep(1, function(current){return current.next;});
705
+ this.trigger("incrementCard");
706
+ return card;
707
+ },
708
+
709
+ decrementCard: function() {
710
+ this.log("decrementing card");
711
+ var card = this._abstractIncrementStep(-1, function(current){return current.prev;});
712
+ this.trigger("decrementCard");
713
+ return card;
714
+ },
715
+
716
+ setCard: function(i) {
717
+ this.log("setting card to " + i);
718
+ this.hideSubmitCards();
719
+ var currentCard = this.getActiveCard();
720
+
721
+ if (this._submitting) {
722
+ this.log("we're submitting the wizard already, can't change cards");
723
+ return currentCard;
724
+ }
725
+
726
+ var newCard = this._cards[i];
727
+ if (newCard) {
728
+ if (newCard.isDisabled()) {
729
+ this.log("new card is currently disabled, returning");
730
+ return currentCard;
731
+ }
732
+
733
+ if (currentCard) {
734
+
735
+ /*
736
+ * here, we're only validating if we're going forward,
737
+ * not if we're going backwards in a step
738
+ */
739
+ if (i > currentCard.index) {
740
+ var cardToValidate = currentCard;
741
+ var ok = false;
742
+
743
+ /*
744
+ * we need to loop over every card between our current
745
+ * card and the card that we clicked, and re-validate
746
+ * them. if there's an error, we need to select the
747
+ * first card to have an error
748
+ */
749
+ while (cardToValidate.index != newCard.index) {
750
+ /*
751
+ * unless we're validating the card that we're
752
+ * leaving, we need to select the card, so that
753
+ * any validators that trigger errorPopovers can
754
+ * display correctly
755
+ */
756
+ if (cardToValidate.index != currentCard.index) {
757
+ cardToValidate.prev.deselect();
758
+ cardToValidate.prev.markVisited();
759
+ cardToValidate.select();
760
+ }
761
+ ok = cardToValidate.validate();
762
+ if (!ok) {
763
+ return cardToValidate;
764
+ }
765
+ cardToValidate = cardToValidate.next;
766
+ }
767
+
768
+ cardToValidate.prev.deselect();
769
+ cardToValidate.prev.markVisited();
770
+ }
771
+
772
+ currentCard.deselect();
773
+ currentCard.markVisited();
774
+ }
775
+
776
+ newCard.select();
777
+
778
+ if (this.args.progressBarCurrent) {
779
+ var last_percent = this.percentComplete;
780
+ this.percentComplete = i * 100.0 / this._cards.length;
781
+ this.updateProgressBar(this.percentComplete);
782
+ }
783
+ else {
784
+ var last_percent = this.percentComplete;
785
+ this.percentComplete = i * 100.0 / this._cards.length;
786
+ this.percentComplete = Math.max(last_percent, this.percentComplete);
787
+ this.updateProgressBar(this.percentComplete);
788
+ }
789
+
790
+ return newCard;
791
+ }
792
+ else {
793
+ this.log("couldn't find card " + i);
794
+ }
795
+ },
796
+
797
+ updateProgressBar: function(percent) {
798
+ this.log("updating progress to " + percent + "%");
799
+ this.progress.find(".bar").css({width: percent + "%"});
800
+ this.percentComplete = percent;
801
+
802
+ this.trigger("progressBar", percent);
803
+
804
+ if (percent == 100) {
805
+ this.log("progress is 100, animating progress bar");
806
+ this.progress.addClass("active");
807
+ }
808
+ else if (percent == 0) {
809
+ this.log("progress is 0, disabling animation");
810
+ this.progress.removeClass("active");
811
+ }
812
+ },
813
+
814
+ getNextCard: function() {
815
+ var currentCard = this.getActiveCard();
816
+ if (currentCard) return currentCard.next;
817
+ },
818
+
819
+ lockCards: function() {
820
+ this.log("locking nav cards");
821
+ this.eachCard(function(i,card){card.unmarkVisited();});
822
+ return this;
823
+ },
824
+
825
+ disableCards: function() {
826
+ this.log("disabling all nav cards");
827
+ this.eachCard(function(i,card){card.disable();});
828
+ return this;
829
+ },
830
+
831
+ enableCards: function() {
832
+ this.log("enabling all nav cards");
833
+ this.eachCard(function(i,card){card.enable();});
834
+ return this;
835
+ },
836
+
837
+ hideCards: function() {
838
+ this.log("hiding cards");
839
+ this.eachCard(function(i,card){card.deselect();});
840
+ this.hideSubmitCards();
841
+ return this;
842
+ },
843
+
844
+ hideButtons: function() {
845
+ this.log("hiding buttons");
846
+ this.nextButton.hide();
847
+ this.backButton.hide();
848
+ return this;
849
+ },
850
+
851
+ showButtons: function() {
852
+ this.log("showing buttons");
853
+ this.nextButton.show();
854
+ this.backButton.show();
855
+ return this;
856
+ },
857
+
858
+ getCard: function(el) {
859
+ var cardDOMEl = $(el).parents(".wizard-card").first()[0];
860
+ if (cardDOMEl) {
861
+ var foundCard = null;
862
+ this.eachCard(function(i, card) {
863
+ if (cardDOMEl == card.el[0]) {
864
+ foundCard = card;
865
+ return false;
866
+ }
867
+ return true;
868
+ });
869
+ return foundCard;
870
+ }
871
+ else {
872
+ return null;
873
+ }
874
+ },
875
+
876
+ _createCards: function() {
877
+ var prev = null;
878
+ var next = null;
879
+ var current_i = 0;
880
+ var currentCard = null;
881
+ var wizard = this;
882
+ var self = this;
883
+
884
+ var cards = this.el.find(".wizard-cards .wizard-card");
885
+ $.each(cards, function(i, card) {
886
+ card = $(card);
887
+
888
+ prev = currentCard;
889
+ currentCard = new WizardCard(wizard, card, i, prev, next);
890
+ self._cards.push(currentCard);
891
+ if (currentCard.name) {
892
+ self.cards[currentCard.name] = currentCard;
893
+ }
894
+ if (prev) {prev.next = currentCard;}
895
+
896
+ self.el.find(".wizard-steps .nav-list").append(currentCard.nav);
897
+ });
898
+ },
899
+
900
+ showSubmitCard: function(name) {
901
+ this.log("showing "+name+" submit card");
902
+
903
+ var card = this.el.find(".wizard-"+name);
904
+ if (card.length) {
905
+ this.hideCards();
906
+ this.el.find(".wizard-"+name).show();
907
+ }
908
+ else {
909
+ this.log("couldn't find submit card "+name);
910
+ }
911
+ },
912
+
913
+ hideSubmitCard: function(name) {
914
+ this.log("hiding "+name+" submit card");
915
+ this.el.find(".wizard-"+name).hide();
916
+ },
917
+
918
+ hideSubmitCards: function() {
919
+ var wizard = this;
920
+ $.each(["success", "error", "failure", "loading"], function(i, name) {
921
+ wizard.hideSubmitCard(name);
922
+ });
923
+ },
924
+
925
+ enableNextButton: function() {
926
+ this.log("enabling next button");
927
+ this.nextButton.removeAttr("disabled");
928
+ return this;
929
+ },
930
+
931
+ disableNextButton: function() {
932
+ this.log("disabling next button");
933
+ this.nextButton.attr("disabled", "disabled");
934
+ return this;
935
+ },
936
+
937
+ serializeArray: function() {
938
+ var form = this.el.children("form").first();
939
+ return form.serializeArray();
940
+ },
941
+
942
+ serialize: function() {
943
+ var form = this.el.children("form").first();
944
+ return form.serialize();
945
+ },
946
+
947
+
948
+ /*
949
+ * the next 3 functions are to be called by the custom submit event
950
+ * handler. the idea is that after you make an ajax call to submit
951
+ * your wizard data (or whatever it is you want to do at the end of
952
+ * the wizard), you call one of these 3 handlers to display a specific
953
+ * card for either success, failure, or error
954
+ */
955
+ submitSuccess: function() {
956
+ this.log("submit success");
957
+ this._submitting = false;
958
+ this.showSubmitCard("success");
959
+ this.trigger("submitSuccess");
960
+ },
961
+
962
+ submitFailure: function() {
963
+ this.log("submit failure");
964
+ this._submitting = false;
965
+ this.showSubmitCard("failure");
966
+ this.trigger("submitFailure");
967
+ },
968
+
969
+ submitError: function() {
970
+ this.log("submit error");
971
+ this._submitting = false;
972
+ this.showSubmitCard("error");
973
+ this.trigger("submitError");
974
+ },
975
+
976
+
977
+ _submit: function() {
978
+ this.log("submitting wizard");
979
+ this._submitting = true;
980
+
981
+ this.lockCards();
982
+ this.backButton.hide();
983
+
984
+ this.showSubmitCard("loading");
985
+ this.updateProgressBar(100);
986
+
987
+ this.changeNextButton(this.args.buttons.submittingText, false);
988
+ this.disableNextButton();
989
+
990
+ var ret = this.trigger("submit");
991
+ this.trigger("loading");
992
+ },
993
+
994
+ _onNextClick: function() {
995
+ this.log("handling 'next' button click");
996
+ var currentCard = this.getActiveCard();
997
+ if (this._readyToSubmit && currentCard.validate()) {
998
+ this._submit();
999
+ }
1000
+ else {
1001
+ currentCard = this.incrementCard();
1002
+ }
1003
+ },
1004
+
1005
+ _onBackClick: function() {
1006
+ this.log("handling 'back' button click");
1007
+ var currentCard = this.decrementCard();
1008
+ },
1009
+
1010
+
1011
+ _handleNextClick: function(event) {
1012
+ var wizard = event.data;
1013
+ wizard._onNextClick.call(wizard);
1014
+ },
1015
+
1016
+ _handleBackClick: function(event) {
1017
+ var wizard = event.data;
1018
+ wizard._onBackClick.call(wizard);
1019
+ },
1020
+
1021
+
1022
+ /*
1023
+ * this function is attached by default to the wizard's "submit" event.
1024
+ * if you choose to implement your own custom submit logic, you should
1025
+ * copy how this function works
1026
+ */
1027
+ _defaultSubmit: function(wizard) {
1028
+ $.ajax({
1029
+ type: "POST",
1030
+ url: wizard.args.submitUrl,
1031
+ data: wizard.serialize(),
1032
+ dataType: "json",
1033
+ success: function(resp) {
1034
+ wizard.submitSuccess();
1035
+ wizard.hideButtons();
1036
+ wizard.updateProgressBar(0);
1037
+ },
1038
+ error: function() {
1039
+ wizard.submitFailure();
1040
+ wizard.hideButtons();
1041
+ },
1042
+ });
1043
+ }
1044
+
1045
+ };
1046
+
1047
+ }(window.jQuery));
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require bootstrap-wizard
3
+ */
@@ -0,0 +1,145 @@
1
+ .wizard {
2
+ display:none;
3
+ }
4
+ .wizard-modal form {
5
+ margin:0;
6
+ padding:0;
7
+ }
8
+ .wizard-modal.modal {
9
+ width:750px;
10
+ margin-left:-375px;
11
+ top:50%;
12
+ }
13
+ .wizard-modal-footer {
14
+ padding: 0px;
15
+ text-align:right;
16
+ }
17
+ .wizard-modal-header.modal-header h3 {
18
+ line-height:35px;
19
+ display:inline
20
+ }
21
+ .wizard-modal-header.modal-header {
22
+ border-bottom: 0;
23
+ }
24
+
25
+ .wizard-subtitle {
26
+ font-weight:bold;
27
+ color: #AFAFAF;
28
+ padding-left:20px;
29
+ }
30
+
31
+ .wizard-error,
32
+ .wizard-failure,
33
+ .wizard-success,
34
+ .wizard-loading,
35
+ .wizard-card {
36
+ position:relative;
37
+ padding:35px;
38
+ padding-top:20px;
39
+ overflow-y: auto;
40
+ height:300px;
41
+ display:none;
42
+ border-top: 1px solid #EEE;
43
+ margin-right: 5px;
44
+ }
45
+
46
+ .wizard-nav-link .icon-chevron-right {
47
+ float: right;
48
+ margin-top: 12px;
49
+ margin-right: -6px;
50
+ opacity: .25;
51
+ }
52
+
53
+ li.wizard-nav-item.active .icon-chevron-right {
54
+ opacity: 1;
55
+ }
56
+
57
+ li.wizard-nav-item {
58
+ line-height:40px;
59
+ }
60
+
61
+ .wizard-modal.modal .nav-list > li > a {
62
+ background-color:#f5f5f5;
63
+ padding: 3px 15px 3px 20px;
64
+ cursor: default;
65
+ color:#B4B4B4;
66
+ }
67
+
68
+ .wizard-modal.modal .nav-list li.active > a {
69
+ background-color:#08C;
70
+ }
71
+
72
+ .wizard-modal.modal .nav-list > li.already-visited > a.wizard-nav-link {
73
+ color: #08C;
74
+ cursor: pointer;
75
+ }
76
+
77
+ .wizard-modal.modal .nav-list > li.active > a.wizard-nav-link {
78
+ color:white;
79
+ }
80
+
81
+ .already-visited > a.wizard-nav-link:hover {
82
+ background-color: #E4E4E4;
83
+ }
84
+
85
+ .wizard-card > h3 {
86
+ margin-top: 0;
87
+ margin-bottom: 20px;
88
+ font-size: 21px;
89
+ line-height: 40px;
90
+ font-weight: normal;
91
+ }
92
+
93
+ .wizard-progress {
94
+ padding:15px;
95
+ bottom:0;
96
+ }
97
+ .wizard-progress-container {
98
+ padding:20px;
99
+ }
100
+
101
+ .wizard-steps {
102
+ width:28%;
103
+ height:425px;
104
+ background-color:#f5f5f5;
105
+ }
106
+
107
+ .wizard-nav-container {
108
+ height:360px;
109
+ }
110
+
111
+ .nav > li > a.wizard-step-error {
112
+ background-color:#F2DEDE;
113
+ color:#B94A48;
114
+ font-weight:bold;
115
+ }
116
+
117
+ .wizard-step-error .icon-chevron-right {
118
+ opacity: 0;
119
+ }
120
+
121
+ .wizard-input-section {
122
+ margin-bottom:20px;
123
+ }
124
+
125
+ .wizard-buttons-container {
126
+ padding:20px;
127
+ }
128
+
129
+ .wizard-modal .popover.error-popover {
130
+ background-color:#F2DEDE;
131
+ color:#B94A48;
132
+ border-color:#953B39;
133
+ }
134
+
135
+ .wizard-modal .popover.error-popover .arrow::after {
136
+ border-right-color:#F2DEDE;
137
+ }
138
+
139
+ .wizard-modal .popover.error-popover .popover-title {
140
+ display:none;
141
+ }
142
+
143
+ .wizard-modal .popover.error-popover .arrow {
144
+ border-right-color:#953B39;
145
+ }
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bootstrap-application-wizard-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 13.3.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thomas A. de Ruiter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: railties
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: coffee-rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 3.2.1
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.2.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rails
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ description: The Bootstrap Application Wizard is a Bootstrap addon that allows multi-step
79
+ forms to progress in a natural order while remaining flexible. This gem integrates
80
+ it with the Rails asset pipeline for easy of use.
81
+ email:
82
+ - thomas.deruiter@gimiscale.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - lib/bootstrap-application-wizard-rails.rb
88
+ - lib/bootstrap-application-wizard-rails/version.rb
89
+ - lib/bootstrap-application-wizard-rails/engine.rb
90
+ - lib/bootstrap-application-wizard-rails/railtie.rb
91
+ - vendor/assets/javascripts/bootstrap-wizard.js
92
+ - vendor/assets/stylesheets/bootstrap-wizard.css
93
+ - vendor/assets/javascripts/bootstrap-application-wizard-rails.js
94
+ - vendor/assets/stylesheets/bootstrap-application-wizard-rails.scss
95
+ homepage:
96
+ licenses: []
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 1.8.25
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Integrate Bootstrap Application Wizard with the Rails asset pipeline
119
+ test_files: []