Browse Source

increased readability of post conditions, reworked container position logic, implemented variable handles and label widths, fixed chrome opacity

Emanuele Marchi 10 năm trước cách đây
mục cha
commit
02c8d9f18e

+ 72 - 101
dist/css/bootstrap3/bootstrap-switch.css

@@ -34,108 +34,11 @@
   -ms-user-select: none;
   user-select: none;
   vertical-align: middle;
-  min-width: 100px;
   -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
   transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
 }
-.bootstrap-switch.bootstrap-switch-mini {
-  min-width: 71px;
-}
-.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label {
-  padding-bottom: 4px;
-  padding-top: 4px;
-  font-size: 10px;
-  line-height: 9px;
-}
-.bootstrap-switch.bootstrap-switch-small {
-  min-width: 79px;
-}
-.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label {
-  padding-bottom: 3px;
-  padding-top: 3px;
-  font-size: 12px;
-  line-height: 18px;
-}
-.bootstrap-switch.bootstrap-switch-large {
-  min-width: 120px;
-}
-.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label {
-  padding-bottom: 9px;
-  padding-top: 9px;
-  font-size: 16px;
-  line-height: normal;
-}
-.bootstrap-switch.bootstrap-switch-disabled,
-.bootstrap-switch.bootstrap-switch-readonly,
-.bootstrap-switch.bootstrap-switch-indeterminate {
-  opacity: 0.5;
-  filter: alpha(opacity=50);
-  cursor: default !important;
-}
-.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label {
-  cursor: default !important;
-}
-.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container {
-  -webkit-transition: margin-left 0.5s;
-  transition: margin-left 0.5s;
-}
-.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on {
-  border-bottom-left-radius: 0;
-  border-top-left-radius: 0;
-  border-bottom-right-radius: 3px;
-  border-top-right-radius: 3px;
-}
-.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off {
-  border-bottom-right-radius: 0;
-  border-top-right-radius: 0;
-  border-bottom-left-radius: 3px;
-  border-top-left-radius: 3px;
-}
-.bootstrap-switch.bootstrap-switch-focused {
-  border-color: #66afe9;
-  outline: 0;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
-  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
-}
-.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-container,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-container {
-  margin-left: 0%;
-}
-.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label {
-  border-bottom-right-radius: 3px;
-  border-top-right-radius: 3px;
-}
-.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-container,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-container {
-  margin-left: -50%;
-}
-.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label {
-  border-bottom-left-radius: 3px;
-  border-top-left-radius: 3px;
-}
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-container,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-indeterminate .bootstrap-switch-container {
-  margin-left: -25%;
-}
 .bootstrap-switch .bootstrap-switch-container {
   display: inline-block;
-  width: 150%;
   top: 0;
   border-radius: 4px;
   -webkit-transform: translate3d(0, 0, 0);
@@ -150,8 +53,7 @@
   cursor: pointer;
   display: inline-block !important;
   height: 100%;
-  padding-bottom: 4px;
-  padding-top: 4px;
+  padding: 6px 12px;
   font-size: 14px;
   line-height: 20px;
 }
@@ -159,7 +61,6 @@
 .bootstrap-switch .bootstrap-switch-handle-off {
   text-align: center;
   z-index: 1;
-  width: 33.333333333%;
 }
 .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,
 .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary {
@@ -196,7 +97,6 @@
   margin-top: -1px;
   margin-bottom: -1px;
   z-index: 100;
-  width: 33.333333333%;
   color: #333333;
   background: #ffffff;
 }
@@ -221,3 +121,74 @@
 .bootstrap-switch input[type='checkbox'].form-control {
   height: auto;
 }
+.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label {
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label {
+  padding: 6px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+}
+.bootstrap-switch.bootstrap-switch-disabled,
+.bootstrap-switch.bootstrap-switch-readonly,
+.bootstrap-switch.bootstrap-switch-indeterminate {
+  cursor: default !important;
+}
+.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+  cursor: default !important;
+}
+.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container {
+  -webkit-transition: margin-left 0.5s;
+  transition: margin-left 0.5s;
+}
+.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+  border-bottom-right-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off {
+  border-bottom-right-radius: 0;
+  border-top-right-radius: 0;
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+.bootstrap-switch.bootstrap-switch-focused {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label {
+  border-bottom-right-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/css/bootstrap3/bootstrap-switch.min.css


+ 139 - 53
dist/js/bootstrap-switch.js

@@ -45,6 +45,8 @@
           onText: this.$element.data("on-text"),
           offText: this.$element.data("off-text"),
           labelText: this.$element.data("label-text"),
+          handleWidth: this.$element.data("handle-width"),
+          labelWidth: this.$element.data("label-width"),
           baseClass: this.$element.data("base-class"),
           wrapperClass: this.$element.data("wrapper-class")
         }, options);
@@ -57,9 +59,6 @@
               if (_this.options.size != null) {
                 classes.push("" + _this.options.baseClass + "-" + _this.options.size);
               }
-              if (_this.options.animate) {
-                classes.push("" + _this.options.baseClass + "-animate");
-              }
               if (_this.options.disabled) {
                 classes.push("" + _this.options.baseClass + "-disabled");
               }
@@ -94,9 +93,6 @@
           html: this.options.labelText,
           "class": "" + this.options.baseClass + "-label"
         });
-        if (this.options.indeterminate) {
-          this.$element.prop("indeterminate", true);
-        }
         this.$element.on("init.bootstrapSwitch", (function(_this) {
           return function() {
             return _this.options.onInit.apply(element, arguments);
@@ -109,11 +105,23 @@
         })(this));
         this.$container = this.$element.wrap(this.$container).parent();
         this.$wrapper = this.$container.wrap(this.$wrapper).parent();
-        this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off).trigger("init.bootstrapSwitch");
+        this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off);
+        if (this.options.indeterminate) {
+          this.$element.prop("indeterminate", true);
+        }
+        this._width();
+        this._containerPosition(this.options.state, (function(_this) {
+          return function() {
+            if (_this.options.animate) {
+              return _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
+            }
+          };
+        })(this));
         this._elementHandlers();
         this._handleHandlers();
         this._labelHandlers();
         this._formHandler();
+        this.$element.trigger("init.bootstrapSwitch");
       }
 
       BootstrapSwitch.prototype._constructor = BootstrapSwitch;
@@ -160,6 +168,7 @@
         if (value) {
           this.$wrapper.addClass("" + this.options.baseClass + "-" + value);
         }
+        this._width();
         this.options.size = value;
         return this.$element;
       };
@@ -169,14 +178,14 @@
           return this.options.animate;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-animate");
         this.options.animate = value;
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-animate");
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleAnimate = function() {
-        this.$wrapper.toggleClass("" + this.options.baseClass + "-animate");
         this.options.animate = !this.options.animate;
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-animate");
         return this.$element;
       };
 
@@ -185,16 +194,16 @@
           return this.options.disabled;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-disabled");
-        this.$element.prop("disabled", value);
         this.options.disabled = value;
+        this.$element.prop("disabled", value);
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-disabled");
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleDisabled = function() {
-        this.$element.prop("disabled", !this.options.disabled);
-        this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled");
         this.options.disabled = !this.options.disabled;
+        this.$element.prop("disabled", this.options.disabled);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled");
         return this.$element;
       };
 
@@ -203,16 +212,16 @@
           return this.options.readonly;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-readonly");
-        this.$element.prop("readonly", value);
         this.options.readonly = value;
+        this.$element.prop("readonly", value);
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-readonly");
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleReadonly = function() {
-        this.$element.prop("readonly", !this.options.readonly);
-        this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly");
         this.options.readonly = !this.options.readonly;
+        this.$element.prop("readonly", this.options.readonly);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly");
         return this.$element;
       };
 
@@ -221,16 +230,18 @@
           return this.options.indeterminate;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-indeterminate");
-        this.$element.prop("indeterminate", value);
         this.options.indeterminate = value;
+        this.$element.prop("indeterminate", value);
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-indeterminate");
+        this._containerPosition();
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleIndeterminate = function() {
+        this.options.indeterminate = !this.options.indeterminate;
         this.$element.prop("indeterminate", !this.options.indeterminate);
         this.$wrapper.toggleClass("" + this.options.baseClass + "-indeterminate");
-        this.options.indeterminate = !this.options.indeterminate;
+        this._containerPosition();
         return this.$element;
       };
 
@@ -297,6 +308,8 @@
           return this.options.onText;
         }
         this.$on.html(value);
+        this._width();
+        this._containerPosition();
         this.options.onText = value;
         return this.$element;
       };
@@ -306,6 +319,8 @@
           return this.options.offText;
         }
         this.$off.html(value);
+        this._width();
+        this._containerPosition();
         this.options.offText = value;
         return this.$element;
       };
@@ -315,10 +330,29 @@
           return this.options.labelText;
         }
         this.$label.html(value);
+        this._width();
         this.options.labelText = value;
         return this.$element;
       };
 
+      BootstrapSwitch.prototype.handleWidth = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.handleWidth;
+        }
+        this.options.handleWidth = value;
+        this._width();
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.labelWidth = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.labelWidth;
+        }
+        this.options.labelWidth = value;
+        this._width();
+        return this.$element;
+      };
+
       BootstrapSwitch.prototype.baseClass = function(value) {
         return this.options.baseClass;
       };
@@ -377,24 +411,81 @@
         return this.$element;
       };
 
+      BootstrapSwitch.prototype._width = function() {
+        var $handles, handleWidth;
+        $handles = this.$on.add(this.$off);
+        $handles.add(this.$label).css("width", "");
+        handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth;
+        $handles.width(handleWidth);
+        this.$label.width((function(_this) {
+          return function(index, width) {
+            if (_this.options.labelWidth !== "auto") {
+              return _this.options.labelWidth;
+            }
+            if (width < handleWidth) {
+              return handleWidth;
+            } else {
+              return width;
+            }
+          };
+        })(this));
+        this._handleWidth = this.$on.outerWidth();
+        this._labelWidth = this.$label.outerWidth();
+        this.$container.width((this._handleWidth * 2) + this._labelWidth);
+        return this.$wrapper.width(this._handleWidth + this._labelWidth);
+      };
+
+      BootstrapSwitch.prototype._containerPosition = function(state, callback) {
+        if (state == null) {
+          state = this.options.state;
+        }
+        this.$container.css("margin-left", (function(_this) {
+          return function() {
+            var values;
+            values = [0, "-" + _this._handleWidth + "px"];
+            if (_this.options.indeterminate) {
+              return "-" + (_this._handleWidth / 2) + "px";
+            }
+            if (state) {
+              if (_this.options.inverse) {
+                return values[1];
+              } else {
+                return values[0];
+              }
+            } else {
+              if (_this.options.inverse) {
+                return values[0];
+              } else {
+                return values[1];
+              }
+            }
+          };
+        })(this));
+        if (!callback) {
+          return;
+        }
+        return this.$container.one($.support.transition.end, callback).emulateTransitionEnd(500);
+      };
+
       BootstrapSwitch.prototype._elementHandlers = function() {
         return this.$element.on({
           "change.bootstrapSwitch": (function(_this) {
             return function(e, skip) {
-              var checked;
+              var state;
               e.preventDefault();
               e.stopImmediatePropagation();
-              checked = _this.$element.is(":checked");
-              if (checked === _this.options.state) {
+              state = _this.$element.is(":checked");
+              _this._containerPosition(state);
+              if (state === _this.options.state) {
                 return;
               }
-              _this.options.state = checked;
-              _this.$wrapper.removeClass(checked ? "" + _this.options.baseClass + "-off" : "" + _this.options.baseClass + "-on").addClass(checked ? "" + _this.options.baseClass + "-on" : "" + _this.options.baseClass + "-off");
+              _this.options.state = state;
+              _this.$wrapper.toggleClass("" + _this.options.baseClass + "-off").toggleClass("" + _this.options.baseClass + "-on");
               if (!skip) {
                 if (_this.$element.is(":radio")) {
                   $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true);
                 }
-                return _this.$element.trigger("switchChange.bootstrapSwitch", [checked]);
+                return _this.$element.trigger("switchChange.bootstrapSwitch", [state]);
               }
             };
           })(this),
@@ -447,59 +538,52 @@
 
       BootstrapSwitch.prototype._labelHandlers = function() {
         return this.$label.on({
-          "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) {
+          "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) {
             return function(e) {
-              var left, pageX, percent, right;
-              if (!_this.isLabelDragging) {
+              if (_this._dragStart || _this.options.disabled || _this.options.readonly) {
                 return;
               }
               e.preventDefault();
-              _this.isLabelDragged = true;
-              pageX = e.pageX || e.originalEvent.touches[0].pageX;
-              percent = ((pageX - _this.$wrapper.offset().left) / _this.$wrapper.width()) * 100;
-              left = 25;
-              right = 75;
+              _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10);
               if (_this.options.animate) {
                 _this.$wrapper.removeClass("" + _this.options.baseClass + "-animate");
               }
-              if (percent < left) {
-                percent = left;
-              } else if (percent > right) {
-                percent = right;
-              }
-              _this.$container.css("margin-left", "" + (percent - right) + "%");
               return _this.$element.trigger("focus.bootstrapSwitch");
             };
           })(this),
-          "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) {
+          "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) {
             return function(e) {
-              if (_this.isLabelDragging || _this.options.disabled || _this.options.readonly) {
+              var difference;
+              if (_this._dragStart == null) {
                 return;
               }
               e.preventDefault();
-              _this.isLabelDragging = true;
-              return _this.$element.trigger("focus.bootstrapSwitch");
+              difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart;
+              if (difference < -_this._handleWidth || difference > 0) {
+                return;
+              }
+              _this._dragEnd = difference;
+              return _this.$container.css("margin-left", "" + _this._dragEnd + "px");
             };
           })(this),
           "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) {
             return function(e) {
               var state;
-              if (!_this.isLabelDragging) {
+              if (!_this._dragStart) {
                 return;
               }
               e.preventDefault();
-              if (_this.isLabelDragged) {
-                state = parseInt(_this.$container.css("margin-left"), 10) > -(_this.$container.width() / 6);
-                _this.isLabelDragged = false;
+              if (_this.options.animate) {
+                _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
+              }
+              if (_this._dragEnd) {
+                state = _this._dragEnd > -(_this._handleWidth / 2);
+                _this._dragEnd = false;
                 _this.state(_this.options.inverse ? !state : state);
-                if (_this.options.animate) {
-                  _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
-                }
-                _this.$container.css("margin-left", "");
               } else {
                 _this.state(!_this.options.state);
               }
-              return _this.isLabelDragging = false;
+              return _this._dragStart = false;
             };
           })(this),
           "mouseleave.bootstrapSwitch": (function(_this) {
@@ -575,6 +659,8 @@
       onText: "ON",
       offText: "OFF",
       labelText: "&nbsp;",
+      handleWidth: "auto",
+      labelWidth: "auto",
       baseClass: "bootstrap-switch",
       wrapperClass: "wrapper",
       onInit: function() {},

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/bootstrap-switch.min.js


+ 19 - 4
docs/js/main.js

@@ -27,7 +27,14 @@ $(function() {
   });
 
   // initialize all the inputs
-  $('input[type="checkbox"],[type="radio"]').not('#create-switch').not('#events-switch').bootstrapSwitch();
+  $('input[type="checkbox"],[type="radio"]')
+  .not('#create-switch')
+  .not('#events-switch')
+  .not('#switch-modal')
+  .one('switchChange.bootstrapSwitch', function(event, state) {
+    console.log(arguments);
+  })
+  .bootstrapSwitch();
 
   $('[data-get]').on("click", function() {
     var type = $(this).data('get');
@@ -47,15 +54,23 @@ $(function() {
     $('#switch-' + type).bootstrapSwitch('toggle' + type.charAt(0).toUpperCase() + type.slice(1));
   });
 
-  $('[data-set-text]').on('change', function(event) {
+  $('[data-set-value]').on('input', function(event) {
     event.preventDefault();
-    var type = $(this).data('set-text');
+    var type = $(this).data('set-value');
     var value = $.trim($(this).val());
 
-    if ( ! value) {
+    if ($(this).data('value') == value) {
       return;
     }
 
     $('#switch-' + type).bootstrapSwitch(type, value);
   });
+
+  $('#modal-switch')
+  .on("shown.bs.modal", function() {
+    $('#switch-modal').bootstrapSwitch();
+  })
+  .on("hidden.bs.modal", function() {
+    $('#switch-modal').bootstrapSwitch('destroy');
+  });
 });

+ 42 - 4
examples.html

@@ -64,7 +64,7 @@
         <div class="col-sm-6 col-lg-4">
           <h2 class="h4">State</h2>
           <p>
-            <input id="switch-state" type="checkbox" checked>
+            <input id="switch-state" type="checkbox">
           </p>
           <div class="btn-group">
             <button type="button" data-toggle="state" class="btn btn-default">Toggle</button>
@@ -181,7 +181,7 @@
           </p>
           <div class="row">
             <div class="col-sm-6">
-              <input type="text" data-set-text="onText" class="form-control">
+              <input type="text" data-set-value="onText" value="Yes" class="form-control">
             </div>
           </div>
         </div>
@@ -192,7 +192,7 @@
           </p>
           <div class="row">
             <div class="col-sm-6">
-              <input type="text" data-set-text="offText" class="form-control">
+              <input type="text" data-set-value="offText" value="No" class="form-control">
             </div>
           </div>
         </div>
@@ -203,7 +203,29 @@
           </p>
           <div class="row">
             <div class="col-sm-6">
-              <input type="text" data-set-text="labelText" class="form-control">
+              <input type="text" data-set-value="labelText" class="form-control">
+            </div>
+          </div>
+        </div>
+        <div class="col-sm-6 col-lg-4">
+          <h2 class="h4">Handle Width</h2>
+          <p>
+            <input id="switch-handleWidth" type="checkbox" data-handle-width="100">
+          </p>
+          <div class="row">
+            <div class="col-sm-6">
+              <input type="number" data-set-value="handleWidth" value="100" class="form-control">
+            </div>
+          </div>
+        </div>
+        <div class="col-sm-6 col-lg-4">
+          <h2 class="h4">Label Width</h2>
+          <p>
+            <input id="switch-labelWidth" type="checkbox" data-label-width="100">
+          </p>
+          <div class="row">
+            <div class="col-sm-6">
+              <input type="number" data-set-value="labelWidth" value="100" class="form-control">
             </div>
           </div>
         </div>
@@ -223,6 +245,22 @@
             <input type="radio" name="radio2" data-radio-all-off="true" class="switch-radio2">
             <input type="radio" name="radio2" data-radio-all-off="true" class="switch-radio2">
           </div>
+        </div><br>
+        <hr>
+        <h2 class="h4">Inside a Modal</h2>
+        <button data-toggle="modal" data-target="#modal-switch" class="btn btn-default">Open the modal</button>
+        <div id="modal-switch" tabindex="-1" role="dialog" aria-labelledby="modal-switch-label" class="modal fade">
+          <div class="modal-dialog">
+            <div class="modal-content">
+              <div class="modal-header">
+                <button type="button" data-dismiss="modal" class="close"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                <div id="modal-switch-label" class="modal-title">Modal title</div>
+                <div class="modal-body">
+                  <input id="switch-modal" type="checkbox" checked>
+                </div>
+              </div>
+            </div>
+          </div>
         </div>
       </div>
     </div>

+ 1 - 1
gulpfile.coffee

@@ -45,7 +45,7 @@ gulp.task 'coffee', ->
   .pipe $.changed "#{paths.dist}/js"
   .pipe $.coffeelint 'coffeelint.json'
   .pipe $.coffeelint.reporter()
-    .on 'error', $.util.log
+  .pipe $.coffeelint.reporter("fail")
   .pipe $.coffee()
     .on 'error', $.util.log
   .pipe $.header banner, pkg: pkg

+ 16 - 0
options-3.html

@@ -176,6 +176,22 @@
             <td>String</td>
             <td>'&amp;nbsp;'</td>
           </tr>
+          <tr>
+            <td>handleWidth</td>
+            <td>data-handle-width</td>
+            <td>String | Number</td>
+            <td>Width of the left and right sides in pixels</td>
+            <td>'auto' or Number</td>
+            <td>'auto'</td>
+          </tr>
+          <tr>
+            <td>labelWidth</td>
+            <td>data-label-width</td>
+            <td>String | Number</td>
+            <td>Width of the center handle in pixels</td>
+            <td>'auto' or Number</td>
+            <td>'auto'</td>
+          </tr>
           <tr>
             <td>baseClass</td>
             <td>data-base-class</td>

+ 148 - 76
src/coffee/bootstrap-switch.coffee

@@ -18,6 +18,8 @@ do ($ = window.jQuery, window) ->
         onText: @$element.data "on-text"
         offText: @$element.data "off-text"
         labelText: @$element.data "label-text"
+        handleWidth: @$element.data "handle-width"
+        labelWidth: @$element.data "label-width"
         baseClass: @$element.data "base-class"
         wrapperClass: @$element.data "wrapper-class"
       , options
@@ -27,7 +29,6 @@ do ($ = window.jQuery, window) ->
 
           classes.push if @options.state then "#{@options.baseClass}-on" else "#{@options.baseClass}-off"
           classes.push "#{@options.baseClass}-#{@options.size}" if @options.size?
-          classes.push "#{@options.baseClass}-animate" if @options.animate
           classes.push "#{@options.baseClass}-disabled" if @options.disabled
           classes.push "#{@options.baseClass}-readonly" if @options.readonly
           classes.push "#{@options.baseClass}-indeterminate" if @options.indeterminate
@@ -46,9 +47,6 @@ do ($ = window.jQuery, window) ->
         html: @options.labelText
         class: "#{@options.baseClass}-label"
 
-      # indeterminate state
-      @$element.prop "indeterminate", true if @options.indeterminate
-
       # set up events
       @$element.on "init.bootstrapSwitch", => @options.onInit.apply element, arguments
       @$element.on "switchChange.bootstrapSwitch", => @options.onSwitchChange.apply element, arguments
@@ -62,21 +60,31 @@ do ($ = window.jQuery, window) ->
       .before(if @options.inverse then @$off else @$on)
       .before(@$label)
       .before(if @options.inverse then @$on else @$off)
-      .trigger "init.bootstrapSwitch"
 
+      # indeterminate state
+      @$element.prop "indeterminate", true  if @options.indeterminate
+
+      # normalize handles width
+      @_width()
+
+      # set container position
+      @_containerPosition @options.state, =>
+        @$wrapper.addClass "#{@options.baseClass}-animate"  if @options.animate
+
+      # initialise handlers
       @_elementHandlers()
       @_handleHandlers()
       @_labelHandlers()
       @_formHandler()
 
-      # TODO: @$label.hasClass "label-change-switch" in toggleState
+      @$element.trigger "init.bootstrapSwitch"
 
     _constructor: BootstrapSwitch
 
     state: (value, skip) ->
-      return @options.state if typeof value is "undefined"
-      return @$element if @options.disabled or @options.readonly
-      return @$element if @options.state and not @options.radioAllOff and @$element.is ':radio'
+      return @options.state  if typeof value is "undefined"
+      return @$element  if @options.disabled or @options.readonly
+      return @$element  if @options.state and not @options.radioAllOff and @$element.is ':radio'
 
       if @options.indeterminate
         @indeterminate false
@@ -88,7 +96,7 @@ do ($ = window.jQuery, window) ->
       @$element
 
     toggleState: (skip) ->
-      return @$element if @options.disabled or @options.readonly
+      return @$element  if @options.disabled or @options.readonly
 
       if @options.indeterminate
         @indeterminate false
@@ -97,77 +105,84 @@ do ($ = window.jQuery, window) ->
         @$element.prop("checked", not @options.state).trigger "change.bootstrapSwitch", skip
 
     size: (value) ->
-      return @options.size if typeof value is "undefined"
+      return @options.size  if typeof value is "undefined"
 
       @$wrapper.removeClass "#{@options.baseClass}-#{@options.size}" if @options.size?
       @$wrapper.addClass "#{@options.baseClass}-#{value}" if value
+      @_width()
       @options.size = value
       @$element
 
     animate: (value) ->
-      return @options.animate if typeof value is "undefined"
+      return @options.animate  if typeof value is "undefined"
 
       value = not not value
+      @options.animate = value
 
       @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-animate")
-      @options.animate = value
       @$element
 
     toggleAnimate: ->
-      @$wrapper.toggleClass "#{@options.baseClass}-animate"
       @options.animate = not @options.animate
+
+      @$wrapper.toggleClass "#{@options.baseClass}-animate"
       @$element
 
     disabled: (value) ->
-      return @options.disabled if typeof value is "undefined"
+      return @options.disabled  if typeof value is "undefined"
 
       value = not not value
+      @options.disabled = value
 
-      @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-disabled")
       @$element.prop "disabled", value
-      @options.disabled = value
+      @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-disabled")
       @$element
 
     toggleDisabled: ->
-      @$element.prop "disabled", not @options.disabled
-      @$wrapper.toggleClass "#{@options.baseClass}-disabled"
       @options.disabled = not @options.disabled
+
+      @$element.prop "disabled", @options.disabled
+      @$wrapper.toggleClass "#{@options.baseClass}-disabled"
       @$element
 
     readonly: (value) ->
-      return @options.readonly if typeof value is "undefined"
+      return @options.readonly  if typeof value is "undefined"
 
       value = not not value
+      @options.readonly = value
 
-      @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-readonly")
       @$element.prop "readonly", value
-      @options.readonly = value
+      @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-readonly")
       @$element
 
     toggleReadonly: ->
-      @$element.prop "readonly", not @options.readonly
-      @$wrapper.toggleClass "#{@options.baseClass}-readonly"
       @options.readonly = not @options.readonly
+
+      @$element.prop "readonly", @options.readonly
+      @$wrapper.toggleClass "#{@options.baseClass}-readonly"
       @$element
 
     indeterminate: (value) ->
-      return @options.indeterminate if typeof value is "undefined"
+      return @options.indeterminate  if typeof value is "undefined"
 
       value = not not value
+      @options.indeterminate = value
 
-      @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-indeterminate")
       @$element.prop "indeterminate", value
-      @options.indeterminate = value
+      @$wrapper[if value then "addClass" else "removeClass"]("#{@options.baseClass}-indeterminate")
+      @_containerPosition()
       @$element
 
     toggleIndeterminate: ->
+      @options.indeterminate = not @options.indeterminate
+
       @$element.prop "indeterminate", not @options.indeterminate
       @$wrapper.toggleClass "#{@options.baseClass}-indeterminate"
-      @options.indeterminate = not @options.indeterminate
+      @_containerPosition()
       @$element
 
     inverse: (value) ->
-      return @options.inverse if typeof value is "undefined"
+      return @options.inverse  if typeof value is "undefined"
 
       value = not not value
 
@@ -195,7 +210,7 @@ do ($ = window.jQuery, window) ->
     onColor: (value) ->
       color = @options.onColor
 
-      return color if typeof value is "undefined"
+      return color  if typeof value is "undefined"
 
       @$on.removeClass "#{@options.baseClass}-#{color}" if color?
       @$on.addClass "#{@options.baseClass}-#{value}"
@@ -205,7 +220,7 @@ do ($ = window.jQuery, window) ->
     offColor: (value) ->
       color = @options.offColor
 
-      return color if typeof value is "undefined"
+      return color  if typeof value is "undefined"
 
       @$off.removeClass "#{@options.baseClass}-#{color}" if color?
       @$off.addClass "#{@options.baseClass}-#{value}"
@@ -213,31 +228,50 @@ do ($ = window.jQuery, window) ->
       @$element
 
     onText: (value) ->
-      return @options.onText if typeof value is "undefined"
+      return @options.onText  if typeof value is "undefined"
 
       @$on.html value
+      @_width()
+      @_containerPosition()
       @options.onText = value
       @$element
 
     offText: (value) ->
-      return @options.offText if typeof value is "undefined"
+      return @options.offText  if typeof value is "undefined"
 
       @$off.html value
+      @_width()
+      @_containerPosition()
       @options.offText = value
       @$element
 
     labelText: (value) ->
-      return @options.labelText if typeof value is "undefined"
+      return @options.labelText  if typeof value is "undefined"
 
       @$label.html value
+      @_width()
       @options.labelText = value
       @$element
 
+    handleWidth: (value) ->
+      return @options.handleWidth  if typeof value is "undefined"
+
+      @options.handleWidth = value
+      @_width()
+      @$element
+
+    labelWidth: (value) ->
+      return @options.labelWidth  if typeof value is "undefined"
+
+      @options.labelWidth = value
+      @_width()
+      @$element
+
     baseClass: (value) ->
       @options.baseClass
 
     wrapperClass: (value) ->
-      return @options.wrapperClass if typeof value is "undefined"
+      return @options.wrapperClass  if typeof value is "undefined"
 
       value = $.fn.bootstrapSwitch.defaults.wrapperClass unless value
 
@@ -247,13 +281,13 @@ do ($ = window.jQuery, window) ->
       @$element
 
     radioAllOff: (value) ->
-      return @options.radioAllOff if typeof value is "undefined"
+      return @options.radioAllOff  if typeof value is "undefined"
 
       @options.radioAllOff = value
       @$element
 
     onInit: (value) ->
-      return @options.onInit if typeof value is "undefined"
+      return @options.onInit  if typeof value is "undefined"
 
       value = $.fn.bootstrapSwitch.defaults.onInit unless value
 
@@ -261,7 +295,7 @@ do ($ = window.jQuery, window) ->
       @$element
 
     onSwitchChange: (value) ->
-      return @options.onSwitchChange if typeof value is "undefined"
+      return @options.onSwitchChange  if typeof value is "undefined"
 
       value = $.fn.bootstrapSwitch.defaults.onSwitchChange unless value
 
@@ -276,20 +310,64 @@ do ($ = window.jQuery, window) ->
       @$element.unwrap().unwrap().off(".bootstrapSwitch").removeData "bootstrap-switch"
       @$element
 
+    _width: ->
+      $handles = @$on.add(@$off)
+
+      # remove width from inline style
+      $handles.add(@$label).css("width", "")
+
+      # save handleWidth for further label width calculation check
+      handleWidth = if @options.handleWidth is "auto"
+      then Math.max @$on.width(), @$off.width()
+      else @options.handleWidth
+
+      # set handles width
+      $handles.width handleWidth
+
+      # set label width
+      @$label.width (index, width) =>
+        return @options.labelWidth  if @options.labelWidth isnt "auto"
+
+        if width < handleWidth then handleWidth else width
+
+      # get handle and label widths
+      @_handleWidth = @$on.outerWidth()
+      @_labelWidth = @$label.outerWidth()
+
+      # set container and wrapper widths
+      @$container.width (@_handleWidth * 2) + @_labelWidth
+      @$wrapper.width @_handleWidth + @_labelWidth
+
+    _containerPosition: (state = @options.state, callback) ->
+      @$container
+      .css "margin-left", =>
+        values = [0, "-#{@_handleWidth}px"]
+
+        return "-#{@_handleWidth / 2}px"  if @options.indeterminate
+
+        if state
+          return  if @options.inverse then values[1] else values[0]
+        else
+          return  if @options.inverse then values[0] else values[1]
+
+      return  unless callback
+
+      @$container
+      .one($.support.transition.end, callback)
+      .emulateTransitionEnd 500
+
     _elementHandlers: ->
       @$element.on
         "change.bootstrapSwitch": (e, skip) =>
           e.preventDefault()
           e.stopImmediatePropagation()
 
-          checked = @$element.is ":checked"
-
-          return if checked is @options.state
+          state = @$element.is ":checked"
+          @_containerPosition state
+          return  if state is @options.state
 
-          @options.state = checked
-          @$wrapper
-          .removeClass(if checked then "#{@options.baseClass}-off" else "#{@options.baseClass}-on")
-          .addClass if checked then "#{@options.baseClass}-on" else "#{@options.baseClass}-off"
+          @options.state = state
+          @$wrapper.toggleClass("#{@options.baseClass}-off").toggleClass "#{@options.baseClass}-on"
 
           unless skip
             if @$element.is ":radio"
@@ -297,7 +375,7 @@ do ($ = window.jQuery, window) ->
               .not(@$element)
               .prop("checked", false)
               .trigger "change.bootstrapSwitch", true
-            @$element.trigger "switchChange.bootstrapSwitch", [checked]
+            @$element.trigger "switchChange.bootstrapSwitch", [state]
 
         "focus.bootstrapSwitch": (e) =>
           e.preventDefault()
@@ -308,7 +386,7 @@ do ($ = window.jQuery, window) ->
           @$wrapper.removeClass "#{@options.baseClass}-focused"
 
         "keydown.bootstrapSwitch": (e) =>
-          return if not e.which or @options.disabled or @options.readonly
+          return  if not e.which or @options.disabled or @options.readonly
 
           switch e.which
             when 37
@@ -333,49 +411,41 @@ do ($ = window.jQuery, window) ->
 
     _labelHandlers: ->
       @$label.on
-        "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (e) =>
-          return unless @isLabelDragging
+        "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (e) =>
+          return  if @_dragStart or @options.disabled or @options.readonly
 
           e.preventDefault()
 
-          @isLabelDragged = true
-          pageX = e.pageX or e.originalEvent.touches[0].pageX
-          percent = ((pageX - @$wrapper.offset().left) / @$wrapper.width()) * 100
-          left = 25
-          right = 75
-
-          @$wrapper.removeClass "#{@options.baseClass}-animate" if @options.animate
-          if percent < left
-            percent = left
-          else if percent > right
-            percent = right
-
-          @$container.css "margin-left", "#{percent - right}%"
+          @_dragStart = (e.pageX or e.originalEvent.touches[0].pageX) - parseInt @$container.css("margin-left"), 10
+          @$wrapper.removeClass "#{@options.baseClass}-animate"  if @options.animate
           @$element.trigger "focus.bootstrapSwitch"
 
-        "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (e) =>
-          return if @isLabelDragging or @options.disabled or @options.readonly
+        "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (e) =>
+          return  unless @_dragStart?
 
           e.preventDefault()
 
-          @isLabelDragging = true
-          @$element.trigger "focus.bootstrapSwitch"
+          difference = (e.pageX or e.originalEvent.touches[0].pageX) - @_dragStart
+          return  if difference < -@_handleWidth or difference > 0
+
+          @_dragEnd = difference
+          @$container.css "margin-left", "#{@_dragEnd}px"
 
         "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (e) =>
-          return unless @isLabelDragging
+          return  unless @_dragStart
 
           e.preventDefault()
 
-          if @isLabelDragged
-            state = parseInt(@$container.css("margin-left"), 10) > -(@$container.width() / 6)
+          @$wrapper.addClass "#{@options.baseClass}-animate"  if @options.animate
+          if @_dragEnd
+            state = @_dragEnd > -(@_handleWidth / 2)
 
-            @isLabelDragged = false
+            @_dragEnd = false
             @state if @options.inverse then not state else state
-            @$wrapper.addClass "#{@options.baseClass}-animate" if @options.animate
-            @$container.css "margin-left", ""
           else
             @state not @options.state
-          @isLabelDragging = false
+
+          @_dragStart = false
 
         "mouseleave.bootstrapSwitch": (e) =>
           @$label.trigger "mouseup.bootstrapSwitch"
@@ -383,7 +453,7 @@ do ($ = window.jQuery, window) ->
     _formHandler: ->
       $form = @$element.closest "form"
 
-      return if $form.data "bootstrap-switch"
+      return  if $form.data "bootstrap-switch"
 
       $form
       .on "reset.bootstrapSwitch", ->
@@ -396,7 +466,7 @@ do ($ = window.jQuery, window) ->
       .data "bootstrap-switch", true
 
     _getClasses: (classes) ->
-      return ["#{@options.baseClass}-#{classes}"] unless $.isArray classes
+      return ["#{@options.baseClass}-#{classes}"]  unless $.isArray classes
 
       cls = []
       for c in classes
@@ -409,7 +479,7 @@ do ($ = window.jQuery, window) ->
       $this = $ @
       data = $this.data "bootstrap-switch"
 
-      $this.data "bootstrap-switch", data = new BootstrapSwitch @, option unless data
+      $this.data "bootstrap-switch", data = new BootstrapSwitch @, option  unless data
       ret = data[option].apply data, args if typeof option is "string"
     ret
 
@@ -428,6 +498,8 @@ do ($ = window.jQuery, window) ->
     onText: "ON"
     offText: "OFF"
     labelText: "&nbsp;"
+    handleWidth: "auto"
+    labelWidth: "auto"
     baseClass: "bootstrap-switch"
     wrapperClass: "wrapper"
     onInit: ->

+ 37 - 4
src/docs/examples.jade

@@ -7,7 +7,7 @@ block content
     .col-sm-6.col-lg-4
       h2.h4 State
       p
-        input#switch-state(type='checkbox', checked)
+        input#switch-state(type='checkbox',)
       .btn-group
         button.btn.btn-default(type='button' data-toggle='state') Toggle
         button.btn.btn-default(type='button', data-set='state', data-value='true') Set true
@@ -105,7 +105,7 @@ block content
         input#switch-onText(type='checkbox', checked, data-on-text='Yes')
       .row
         .col-sm-6
-          input.form-control(type='text', data-set-text='onText')
+          input.form-control(type='text', data-set-value='onText', value='Yes')
 
     .col-sm-6.col-lg-4
       h2.h4 Off Text
@@ -113,7 +113,7 @@ block content
         input#switch-offText(type='checkbox', data-off-text='No')
       .row
         .col-sm-6
-          input.form-control(type='text', data-set-text='offText')
+          input.form-control(type='text', data-set-value='offText', value='No')
 
     .col-sm-6.col-lg-4
       h2.h4 Label Text
@@ -121,7 +121,23 @@ block content
         input#switch-labelText(type='checkbox', data-label-text='Label')
       .row
         .col-sm-6
-          input.form-control(type='text', data-set-text='labelText')
+          input.form-control(type='text', data-set-value='labelText')
+
+    .col-sm-6.col-lg-4
+      h2.h4 Handle Width
+      p
+        input#switch-handleWidth(type='checkbox', data-handle-width='100')
+      .row
+        .col-sm-6
+          input.form-control(type='number', data-set-value='handleWidth', value='100')
+
+    .col-sm-6.col-lg-4
+      h2.h4 Label Width
+      p
+        input#switch-labelWidth(type='checkbox', data-label-width='100')
+      .row
+        .col-sm-6
+          input.form-control(type='number', data-set-value='labelWidth', value='100')
 
   br
   br
@@ -139,3 +155,20 @@ block content
         input.switch-radio2(type='radio', name='radio2', checked, data-radio-all-off='true')
         input.switch-radio2(type='radio', name='radio2', data-radio-all-off='true')
         input.switch-radio2(type='radio', name='radio2', data-radio-all-off='true')
+
+    br
+    hr
+
+    h2.h4 Inside a Modal
+    button.btn.btn-default(data-toggle='modal', data-target='#modal-switch') Open the modal
+    .modal.fade#modal-switch(tabindex='-1', role='dialog', aria-labelledby='modal-switch-label')
+      .modal-dialog
+        .modal-content
+          .modal-header
+            button.close(type='button', data-dismiss='modal')
+              span(aria-hidden='true') &times;
+              span.sr-only Close
+            .modal-title#modal-switch-label Modal title
+            .modal-body
+              input#switch-modal(type='checkbox', checked)
+

+ 14 - 0
src/docs/options-3.jade

@@ -106,6 +106,20 @@ block content
         td Text of the center handle of the switch
         td String
         td '&amp;nbsp;'
+      tr
+        td handleWidth
+        td data-handle-width
+        td String | Number
+        td Width of the left and right sides in pixels
+        td 'auto' or Number
+        td 'auto'
+      tr
+        td labelWidth
+        td data-label-width
+        td String | Number
+        td Width of the center handle in pixels
+        td 'auto' or Number
+        td 'auto'
       tr
         td baseClass
         td data-base-class

+ 93 - 119
src/less/bootstrap3/bootstrap-switch.less

@@ -12,123 +12,10 @@
   line-height: 8px;
   .user-select(none);
   vertical-align: middle;
-  min-width: 100px;
   .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s");
 
-  &.@{bootstrap-switch-base}-mini {
-    min-width: 71px;
-
-    .@{bootstrap-switch-base}-handle-on,
-    .@{bootstrap-switch-base}-handle-off,
-    .@{bootstrap-switch-base}-label {
-      padding-bottom: 4px;
-      padding-top: 4px;
-      font-size: 10px;
-      line-height: 9px;
-    }
-  }
-
-  &.@{bootstrap-switch-base}-small {
-    min-width: 79px;
-
-    .@{bootstrap-switch-base}-handle-on,
-    .@{bootstrap-switch-base}-handle-off,
-    .@{bootstrap-switch-base}-label {
-      padding-bottom: 3px;
-      padding-top: 3px;
-      font-size: 12px;
-      line-height: 18px;
-    }
-  }
-
-  &.@{bootstrap-switch-base}-large {
-    min-width: 120px;
-
-    .@{bootstrap-switch-base}-handle-on,
-    .@{bootstrap-switch-base}-handle-off,
-    .@{bootstrap-switch-base}-label {
-      padding-bottom: 9px;
-      padding-top: 9px;
-      font-size: 16px;
-      line-height: normal;
-    }
-  }
-
-  &.@{bootstrap-switch-base}-disabled,
-  &.@{bootstrap-switch-base}-readonly,
-  &.@{bootstrap-switch-base}-indeterminate {
-    .opacity(.5);
-    cursor: default !important;
-
-    .@{bootstrap-switch-base}-handle-on,
-    .@{bootstrap-switch-base}-handle-off,
-    .@{bootstrap-switch-base}-label {
-      cursor: default !important;
-    }
-  }
-
-  &.@{bootstrap-switch-base}-animate {
-
-    .@{bootstrap-switch-base}-container {
-      .transition(margin-left .5s);
-    }
-  }
-
-  &.@{bootstrap-switch-base}-inverse {
-
-    .@{bootstrap-switch-base}-handle-on {
-      .border-left-radius(0);
-      .border-right-radius(@border-radius-base - 1);
-    }
-
-    .@{bootstrap-switch-base}-handle-off {
-      .border-right-radius(0);
-      .border-left-radius(@border-radius-base - 1);
-    }
-  }
-
-  &.@{bootstrap-switch-base}-focused {
-    @color-rgba: rgba(red(@input-border-focus), green(@input-border-focus), blue(@input-border-focus), .6);
-    border-color: @input-border-focus;
-    outline: 0;
-    .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
-  }
-
-  &.@{bootstrap-switch-base}-on,
-  &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-off {
-
-    .@{bootstrap-switch-base}-container {
-      margin-left: 0%;
-    }
-
-    .@{bootstrap-switch-base}-label {
-      .border-right-radius(@border-radius-base - 1);
-    }
-  }
-
-  &.@{bootstrap-switch-base}-off,
-  &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-on {
-
-    .@{bootstrap-switch-base}-container {
-      margin-left: -50%;
-    }
-
-    .@{bootstrap-switch-base}-label {
-      .border-left-radius(@border-radius-base - 1);
-    }
-  }
-
-  &.@{bootstrap-switch-base}-indeterminate,
-  &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-indeterminate {
-
-    .@{bootstrap-switch-base}-container {
-      margin-left: -25%;
-    }
-  }
-
   .@{bootstrap-switch-base}-container {
     display: inline-block;
-    width: 150%;
     top: 0;
     border-radius: @border-radius-base;
     .translate3d(0, 0, 0);
@@ -141,17 +28,15 @@
     cursor: pointer;
     display: inline-block !important;
     height: 100%;
-    padding-bottom: 4px;
-    padding-top: 4px;
-    font-size: 14px;
-    line-height: 20px;
+    padding: @padding-base-vertical @padding-base-horizontal;
+    font-size: @font-size-base;
+    line-height: @line-height-computed;
   }
 
   .@{bootstrap-switch-base}-handle-on,
   .@{bootstrap-switch-base}-handle-off {
     text-align: center;
     z-index: 1;
-    width: 33.333333333%;
 
     &.@{bootstrap-switch-base}-primary {
       color: #fff;
@@ -189,7 +74,6 @@
     margin-top: -1px;
     margin-bottom: -1px;
     z-index: 100;
-    width: 33.333333333%;
     color: @btn-default-color;
     background: @btn-default-bg;
   }
@@ -214,4 +98,94 @@
       height: auto;
     }
   }
+
+  &.@{bootstrap-switch-base}-mini {
+
+    .@{bootstrap-switch-base}-handle-on,
+    .@{bootstrap-switch-base}-handle-off,
+    .@{bootstrap-switch-base}-label {
+      padding: @padding-xs-vertical @padding-xs-horizontal;
+      font-size: @font-size-small;
+      line-height: @line-height-small;
+    }
+  }
+
+  &.@{bootstrap-switch-base}-small {
+
+    .@{bootstrap-switch-base}-handle-on,
+    .@{bootstrap-switch-base}-handle-off,
+    .@{bootstrap-switch-base}-label {
+      padding: @padding-small-vertical @padding-small-horizontal;
+      font-size: @font-size-small;
+      line-height: @line-height-small;
+    }
+  }
+
+  &.@{bootstrap-switch-base}-large {
+
+    .@{bootstrap-switch-base}-handle-on,
+    .@{bootstrap-switch-base}-handle-off,
+    .@{bootstrap-switch-base}-label {
+      padding: @padding-base-vertical @padding-large-horizontal;
+      font-size: @font-size-large;
+      line-height: @line-height-large;
+    }
+  }
+
+  &.@{bootstrap-switch-base}-disabled,
+  &.@{bootstrap-switch-base}-readonly,
+  &.@{bootstrap-switch-base}-indeterminate {
+    cursor: default !important;
+
+    .@{bootstrap-switch-base}-handle-on,
+    .@{bootstrap-switch-base}-handle-off,
+    .@{bootstrap-switch-base}-label {
+      .opacity(.5);
+      cursor: default !important;
+    }
+  }
+
+  &.@{bootstrap-switch-base}-animate {
+
+    .@{bootstrap-switch-base}-container {
+      .transition(margin-left .5s);
+    }
+  }
+
+  &.@{bootstrap-switch-base}-inverse {
+
+    .@{bootstrap-switch-base}-handle-on {
+      .border-left-radius(0);
+      .border-right-radius(@border-radius-base - 1);
+    }
+
+    .@{bootstrap-switch-base}-handle-off {
+      .border-right-radius(0);
+      .border-left-radius(@border-radius-base - 1);
+    }
+  }
+
+  &.@{bootstrap-switch-base}-focused {
+    @color-rgba: rgba(red(@input-border-focus), green(@input-border-focus), blue(@input-border-focus), .6);
+    border-color: @input-border-focus;
+    outline: 0;
+    .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
+  }
+
+  &.@{bootstrap-switch-base}-on,
+  &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-off {
+
+    .@{bootstrap-switch-base}-label {
+      .border-right-radius(@border-radius-base - 1);
+    }
+  }
+
+  &.@{bootstrap-switch-base}-off,
+  &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-on {
+
+
+    .@{bootstrap-switch-base}-label {
+      .border-left-radius(@border-radius-base - 1);
+    }
+  }
 }

+ 139 - 53
test/bootstrap-switch.js

@@ -45,6 +45,8 @@
           onText: this.$element.data("on-text"),
           offText: this.$element.data("off-text"),
           labelText: this.$element.data("label-text"),
+          handleWidth: this.$element.data("handle-width"),
+          labelWidth: this.$element.data("label-width"),
           baseClass: this.$element.data("base-class"),
           wrapperClass: this.$element.data("wrapper-class")
         }, options);
@@ -57,9 +59,6 @@
               if (_this.options.size != null) {
                 classes.push("" + _this.options.baseClass + "-" + _this.options.size);
               }
-              if (_this.options.animate) {
-                classes.push("" + _this.options.baseClass + "-animate");
-              }
               if (_this.options.disabled) {
                 classes.push("" + _this.options.baseClass + "-disabled");
               }
@@ -94,9 +93,6 @@
           html: this.options.labelText,
           "class": "" + this.options.baseClass + "-label"
         });
-        if (this.options.indeterminate) {
-          this.$element.prop("indeterminate", true);
-        }
         this.$element.on("init.bootstrapSwitch", (function(_this) {
           return function() {
             return _this.options.onInit.apply(element, arguments);
@@ -109,11 +105,23 @@
         })(this));
         this.$container = this.$element.wrap(this.$container).parent();
         this.$wrapper = this.$container.wrap(this.$wrapper).parent();
-        this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off).trigger("init.bootstrapSwitch");
+        this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off);
+        if (this.options.indeterminate) {
+          this.$element.prop("indeterminate", true);
+        }
+        this._width();
+        this._containerPosition(this.options.state, (function(_this) {
+          return function() {
+            if (_this.options.animate) {
+              return _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
+            }
+          };
+        })(this));
         this._elementHandlers();
         this._handleHandlers();
         this._labelHandlers();
         this._formHandler();
+        this.$element.trigger("init.bootstrapSwitch");
       }
 
       BootstrapSwitch.prototype._constructor = BootstrapSwitch;
@@ -160,6 +168,7 @@
         if (value) {
           this.$wrapper.addClass("" + this.options.baseClass + "-" + value);
         }
+        this._width();
         this.options.size = value;
         return this.$element;
       };
@@ -169,14 +178,14 @@
           return this.options.animate;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-animate");
         this.options.animate = value;
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-animate");
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleAnimate = function() {
-        this.$wrapper.toggleClass("" + this.options.baseClass + "-animate");
         this.options.animate = !this.options.animate;
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-animate");
         return this.$element;
       };
 
@@ -185,16 +194,16 @@
           return this.options.disabled;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-disabled");
-        this.$element.prop("disabled", value);
         this.options.disabled = value;
+        this.$element.prop("disabled", value);
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-disabled");
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleDisabled = function() {
-        this.$element.prop("disabled", !this.options.disabled);
-        this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled");
         this.options.disabled = !this.options.disabled;
+        this.$element.prop("disabled", this.options.disabled);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled");
         return this.$element;
       };
 
@@ -203,16 +212,16 @@
           return this.options.readonly;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-readonly");
-        this.$element.prop("readonly", value);
         this.options.readonly = value;
+        this.$element.prop("readonly", value);
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-readonly");
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleReadonly = function() {
-        this.$element.prop("readonly", !this.options.readonly);
-        this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly");
         this.options.readonly = !this.options.readonly;
+        this.$element.prop("readonly", this.options.readonly);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly");
         return this.$element;
       };
 
@@ -221,16 +230,18 @@
           return this.options.indeterminate;
         }
         value = !!value;
-        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-indeterminate");
-        this.$element.prop("indeterminate", value);
         this.options.indeterminate = value;
+        this.$element.prop("indeterminate", value);
+        this.$wrapper[value ? "addClass" : "removeClass"]("" + this.options.baseClass + "-indeterminate");
+        this._containerPosition();
         return this.$element;
       };
 
       BootstrapSwitch.prototype.toggleIndeterminate = function() {
+        this.options.indeterminate = !this.options.indeterminate;
         this.$element.prop("indeterminate", !this.options.indeterminate);
         this.$wrapper.toggleClass("" + this.options.baseClass + "-indeterminate");
-        this.options.indeterminate = !this.options.indeterminate;
+        this._containerPosition();
         return this.$element;
       };
 
@@ -297,6 +308,8 @@
           return this.options.onText;
         }
         this.$on.html(value);
+        this._width();
+        this._containerPosition();
         this.options.onText = value;
         return this.$element;
       };
@@ -306,6 +319,8 @@
           return this.options.offText;
         }
         this.$off.html(value);
+        this._width();
+        this._containerPosition();
         this.options.offText = value;
         return this.$element;
       };
@@ -315,10 +330,29 @@
           return this.options.labelText;
         }
         this.$label.html(value);
+        this._width();
         this.options.labelText = value;
         return this.$element;
       };
 
+      BootstrapSwitch.prototype.handleWidth = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.handleWidth;
+        }
+        this.options.handleWidth = value;
+        this._width();
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.labelWidth = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.labelWidth;
+        }
+        this.options.labelWidth = value;
+        this._width();
+        return this.$element;
+      };
+
       BootstrapSwitch.prototype.baseClass = function(value) {
         return this.options.baseClass;
       };
@@ -377,24 +411,81 @@
         return this.$element;
       };
 
+      BootstrapSwitch.prototype._width = function() {
+        var $handles, handleWidth;
+        $handles = this.$on.add(this.$off);
+        $handles.add(this.$label).css("width", "");
+        handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth;
+        $handles.width(handleWidth);
+        this.$label.width((function(_this) {
+          return function(index, width) {
+            if (_this.options.labelWidth !== "auto") {
+              return _this.options.labelWidth;
+            }
+            if (width < handleWidth) {
+              return handleWidth;
+            } else {
+              return width;
+            }
+          };
+        })(this));
+        this._handleWidth = this.$on.outerWidth();
+        this._labelWidth = this.$label.outerWidth();
+        this.$container.width((this._handleWidth * 2) + this._labelWidth);
+        return this.$wrapper.width(this._handleWidth + this._labelWidth);
+      };
+
+      BootstrapSwitch.prototype._containerPosition = function(state, callback) {
+        if (state == null) {
+          state = this.options.state;
+        }
+        this.$container.css("margin-left", (function(_this) {
+          return function() {
+            var values;
+            values = [0, "-" + _this._handleWidth + "px"];
+            if (_this.options.indeterminate) {
+              return "-" + (_this._handleWidth / 2) + "px";
+            }
+            if (state) {
+              if (_this.options.inverse) {
+                return values[1];
+              } else {
+                return values[0];
+              }
+            } else {
+              if (_this.options.inverse) {
+                return values[0];
+              } else {
+                return values[1];
+              }
+            }
+          };
+        })(this));
+        if (!callback) {
+          return;
+        }
+        return this.$container.one($.support.transition.end, callback).emulateTransitionEnd(500);
+      };
+
       BootstrapSwitch.prototype._elementHandlers = function() {
         return this.$element.on({
           "change.bootstrapSwitch": (function(_this) {
             return function(e, skip) {
-              var checked;
+              var state;
               e.preventDefault();
               e.stopImmediatePropagation();
-              checked = _this.$element.is(":checked");
-              if (checked === _this.options.state) {
+              state = _this.$element.is(":checked");
+              _this._containerPosition(state);
+              if (state === _this.options.state) {
                 return;
               }
-              _this.options.state = checked;
-              _this.$wrapper.removeClass(checked ? "" + _this.options.baseClass + "-off" : "" + _this.options.baseClass + "-on").addClass(checked ? "" + _this.options.baseClass + "-on" : "" + _this.options.baseClass + "-off");
+              _this.options.state = state;
+              _this.$wrapper.toggleClass("" + _this.options.baseClass + "-off").toggleClass("" + _this.options.baseClass + "-on");
               if (!skip) {
                 if (_this.$element.is(":radio")) {
                   $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true);
                 }
-                return _this.$element.trigger("switchChange.bootstrapSwitch", [checked]);
+                return _this.$element.trigger("switchChange.bootstrapSwitch", [state]);
               }
             };
           })(this),
@@ -447,59 +538,52 @@
 
       BootstrapSwitch.prototype._labelHandlers = function() {
         return this.$label.on({
-          "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) {
+          "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) {
             return function(e) {
-              var left, pageX, percent, right;
-              if (!_this.isLabelDragging) {
+              if (_this._dragStart || _this.options.disabled || _this.options.readonly) {
                 return;
               }
               e.preventDefault();
-              _this.isLabelDragged = true;
-              pageX = e.pageX || e.originalEvent.touches[0].pageX;
-              percent = ((pageX - _this.$wrapper.offset().left) / _this.$wrapper.width()) * 100;
-              left = 25;
-              right = 75;
+              _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10);
               if (_this.options.animate) {
                 _this.$wrapper.removeClass("" + _this.options.baseClass + "-animate");
               }
-              if (percent < left) {
-                percent = left;
-              } else if (percent > right) {
-                percent = right;
-              }
-              _this.$container.css("margin-left", "" + (percent - right) + "%");
               return _this.$element.trigger("focus.bootstrapSwitch");
             };
           })(this),
-          "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) {
+          "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) {
             return function(e) {
-              if (_this.isLabelDragging || _this.options.disabled || _this.options.readonly) {
+              var difference;
+              if (_this._dragStart == null) {
                 return;
               }
               e.preventDefault();
-              _this.isLabelDragging = true;
-              return _this.$element.trigger("focus.bootstrapSwitch");
+              difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart;
+              if (difference < -_this._handleWidth || difference > 0) {
+                return;
+              }
+              _this._dragEnd = difference;
+              return _this.$container.css("margin-left", "" + _this._dragEnd + "px");
             };
           })(this),
           "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) {
             return function(e) {
               var state;
-              if (!_this.isLabelDragging) {
+              if (!_this._dragStart) {
                 return;
               }
               e.preventDefault();
-              if (_this.isLabelDragged) {
-                state = parseInt(_this.$container.css("margin-left"), 10) > -(_this.$container.width() / 6);
-                _this.isLabelDragged = false;
+              if (_this.options.animate) {
+                _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
+              }
+              if (_this._dragEnd) {
+                state = _this._dragEnd > -(_this._handleWidth / 2);
+                _this._dragEnd = false;
                 _this.state(_this.options.inverse ? !state : state);
-                if (_this.options.animate) {
-                  _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
-                }
-                _this.$container.css("margin-left", "");
               } else {
                 _this.state(!_this.options.state);
               }
-              return _this.isLabelDragging = false;
+              return _this._dragStart = false;
             };
           })(this),
           "mouseleave.bootstrapSwitch": (function(_this) {
@@ -575,6 +659,8 @@
       onText: "ON",
       offText: "OFF",
       labelText: "&nbsp;",
+      handleWidth: "auto",
+      labelWidth: "auto",
       baseClass: "bootstrap-switch",
       wrapperClass: "wrapper",
       onInit: function() {},

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác