bootstrap-application-wizard-rails-sass 13.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ module BootstrapApplicationWizard
2
+ module Sass
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,1048 @@
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));
1048
+
@@ -0,0 +1,146 @@
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
+ }
146
+
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bootstrap-application-wizard-rails-sass
3
+ version: !ruby/object:Gem::Version
4
+ version: 13.3.5
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thomas A. de Ruiter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-25 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: sass-rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '3.2'
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'
46
+ - !ruby/object:Gem::Dependency
47
+ name: coffee-rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 3.2.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 3.2.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '1.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: '1.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rails
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '3.0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '3.0'
94
+ description: The Bootstrap Application Wizard is a Bootstrap addon that allows multi-step
95
+ forms to progress in a natural order while remaining flexible. This gem integrates
96
+ it with the Rails asset pipeline for easy of use.
97
+ email:
98
+ - thomas.deruiter@gimiscale.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - lib/bootstrap-application-wizard-rails.rb
104
+ - vendor/assets/javascripts/bootstrap-wizard.js
105
+ - vendor/assets/stylesheets/bootstrap-wizard.css.scss
106
+ homepage:
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.23
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Integrate Bootstrap Application Wizard with the Rails asset pipeline
130
+ test_files: []
131
+ has_rdoc: