bootstrap-switch.coffee 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. do ($ = window.jQuery, window) ->
  2. "use strict"
  3. class BootstrapSwitch
  4. constructor: (element, options = {}) ->
  5. @$element = $ element
  6. @options = $.extend {}, $.fn.bootstrapSwitch.defaults,
  7. state: @$element.is ":checked"
  8. size: @$element.data "size"
  9. animate: @$element.data "animate"
  10. disabled: @$element.is ":disabled"
  11. readonly: @$element.is "[readonly]"
  12. indeterminate: @$element.data "indeterminate"
  13. inverse: @$element.data "inverse"
  14. radioAllOff: @$element.data "radio-all-off"
  15. onColor: @$element.data "on-color"
  16. offColor: @$element.data "off-color"
  17. onText: @$element.data "on-text"
  18. offText: @$element.data "off-text"
  19. labelText: @$element.data "label-text"
  20. handleWidth: @$element.data "handle-width"
  21. labelWidth: @$element.data "label-width"
  22. baseClass: @$element.data "base-class"
  23. wrapperClass: @$element.data "wrapper-class"
  24. , options
  25. @$wrapper = $ "<div>",
  26. class: do =>
  27. classes = ["#{@options.baseClass}"].concat @_getClasses @options.wrapperClass
  28. classes.push if @options.state then "#{@options.baseClass}-on" else "#{@options.baseClass}-off"
  29. classes.push "#{@options.baseClass}-#{@options.size}" if @options.size?
  30. classes.push "#{@options.baseClass}-disabled" if @options.disabled
  31. classes.push "#{@options.baseClass}-readonly" if @options.readonly
  32. classes.push "#{@options.baseClass}-indeterminate" if @options.indeterminate
  33. classes.push "#{@options.baseClass}-inverse" if @options.inverse
  34. classes.push "#{@options.baseClass}-id-#{@$element.attr("id")}" if @$element.attr "id"
  35. classes.join " "
  36. @$container = $ "<div>",
  37. class: "#{@options.baseClass}-container"
  38. @$on = $ "<span>",
  39. html: @options.onText,
  40. class: "#{@options.baseClass}-handle-on #{@options.baseClass}-#{@options.onColor}"
  41. @$off = $ "<span>",
  42. html: @options.offText,
  43. class: "#{@options.baseClass}-handle-off #{@options.baseClass}-#{@options.offColor}"
  44. @$label = $ "<span>",
  45. html: @options.labelText
  46. class: "#{@options.baseClass}-label"
  47. # set up events
  48. @$element.on "init.bootstrapSwitch", => @options.onInit.apply element, arguments
  49. @$element.on "switchChange.bootstrapSwitch", => @options.onSwitchChange.apply element, arguments
  50. # reassign elements after dom modification
  51. @$container = @$element.wrap(@$container).parent()
  52. @$wrapper = @$container.wrap(@$wrapper).parent()
  53. # insert handles and label and trigger event
  54. @$element
  55. .before(if @options.inverse then @$off else @$on)
  56. .before(@$label)
  57. .before(if @options.inverse then @$on else @$off)
  58. # indeterminate state
  59. @$element.prop "indeterminate", true if @options.indeterminate
  60. # normalize handles width and set container position
  61. initInterval = window.setInterval =>
  62. if @$wrapper.is ":visible"
  63. @_width()
  64. @_containerPosition null, =>
  65. @$wrapper.addClass "#{@options.baseClass}-animate" if @options.animate
  66. window.clearInterval initInterval
  67. , 50
  68. # initialise handlers
  69. @_elementHandlers()
  70. @_handleHandlers()
  71. @_labelHandlers()
  72. @_formHandler()
  73. @_externalLabelHandler()
  74. @$element.trigger "init.bootstrapSwitch"
  75. _constructor: BootstrapSwitch
  76. state: (value, skip) ->
  77. return @options.state if typeof value is "undefined"
  78. return @$element if @options.disabled or @options.readonly
  79. return @$element if @options.state and not @options.radioAllOff and @$element.is ":radio"
  80. # remove indeterminate
  81. @indeterminate false if @options.indeterminate
  82. value = not not value
  83. @$element.prop("checked", value).trigger "change.bootstrapSwitch", skip
  84. @$element
  85. toggleState: (skip) ->
  86. return @$element if @options.disabled or @options.readonly
  87. if @options.indeterminate
  88. @indeterminate false
  89. @state true
  90. else
  91. @$element.prop("checked", not @options.state).trigger "change.bootstrapSwitch", skip
  92. size: (value) ->
  93. return @options.size if typeof value is "undefined"
  94. @$wrapper.removeClass "#{@options.baseClass}-#{@options.size}" if @options.size?
  95. @$wrapper.addClass "#{@options.baseClass}-#{value}" if value
  96. @_width()
  97. @_containerPosition()
  98. @options.size = value
  99. @$element
  100. animate: (value) ->
  101. return @options.animate if typeof value is "undefined"
  102. value = not not value
  103. return @$element if value is @options.animate
  104. @toggleAnimate()
  105. toggleAnimate: ->
  106. @options.animate = not @options.animate
  107. @$wrapper.toggleClass "#{@options.baseClass}-animate"
  108. @$element
  109. disabled: (value) ->
  110. return @options.disabled if typeof value is "undefined"
  111. value = not not value
  112. return @$element if value is @options.disabled
  113. @toggleDisabled()
  114. toggleDisabled: ->
  115. @options.disabled = not @options.disabled
  116. @$element.prop "disabled", @options.disabled
  117. @$wrapper.toggleClass "#{@options.baseClass}-disabled"
  118. @$element
  119. readonly: (value) ->
  120. return @options.readonly if typeof value is "undefined"
  121. value = not not value
  122. return @$element if value is @options.readonly
  123. @toggleReadonly()
  124. toggleReadonly: ->
  125. @options.readonly = not @options.readonly
  126. @$element.prop "readonly", @options.readonly
  127. @$wrapper.toggleClass "#{@options.baseClass}-readonly"
  128. @$element
  129. indeterminate: (value) ->
  130. return @options.indeterminate if typeof value is "undefined"
  131. value = not not value
  132. return @$element if value is @options.indeterminate
  133. @toggleIndeterminate()
  134. toggleIndeterminate: ->
  135. @options.indeterminate = not @options.indeterminate
  136. @$element.prop "indeterminate", @options.indeterminate
  137. @$wrapper.toggleClass "#{@options.baseClass}-indeterminate"
  138. @_containerPosition()
  139. @$element
  140. inverse: (value) ->
  141. return @options.inverse if typeof value is "undefined"
  142. value = not not value
  143. return @$element if value is @options.inverse
  144. @toggleInverse()
  145. toggleInverse: ->
  146. @$wrapper.toggleClass "#{@options.baseClass}-inverse"
  147. $on = @$on.clone true
  148. $off = @$off.clone true
  149. @$on.replaceWith $off
  150. @$off.replaceWith $on
  151. @$on = $off
  152. @$off = $on
  153. @options.inverse = not @options.inverse
  154. @$element
  155. onColor: (value) ->
  156. color = @options.onColor
  157. return color if typeof value is "undefined"
  158. @$on.removeClass "#{@options.baseClass}-#{color}" if color?
  159. @$on.addClass "#{@options.baseClass}-#{value}"
  160. @options.onColor = value
  161. @$element
  162. offColor: (value) ->
  163. color = @options.offColor
  164. return color if typeof value is "undefined"
  165. @$off.removeClass "#{@options.baseClass}-#{color}" if color?
  166. @$off.addClass "#{@options.baseClass}-#{value}"
  167. @options.offColor = value
  168. @$element
  169. onText: (value) ->
  170. return @options.onText if typeof value is "undefined"
  171. @$on.html value
  172. @_width()
  173. @_containerPosition()
  174. @options.onText = value
  175. @$element
  176. offText: (value) ->
  177. return @options.offText if typeof value is "undefined"
  178. @$off.html value
  179. @_width()
  180. @_containerPosition()
  181. @options.offText = value
  182. @$element
  183. labelText: (value) ->
  184. return @options.labelText if typeof value is "undefined"
  185. @$label.html value
  186. @_width()
  187. @options.labelText = value
  188. @$element
  189. handleWidth: (value) ->
  190. return @options.handleWidth if typeof value is "undefined"
  191. @options.handleWidth = value
  192. @_width()
  193. @_containerPosition()
  194. @$element
  195. labelWidth: (value) ->
  196. return @options.labelWidth if typeof value is "undefined"
  197. @options.labelWidth = value
  198. @_width()
  199. @_containerPosition()
  200. @$element
  201. baseClass: (value) ->
  202. @options.baseClass
  203. wrapperClass: (value) ->
  204. return @options.wrapperClass if typeof value is "undefined"
  205. value = $.fn.bootstrapSwitch.defaults.wrapperClass unless value
  206. @$wrapper.removeClass @_getClasses(@options.wrapperClass).join " "
  207. @$wrapper.addClass @_getClasses(value).join " "
  208. @options.wrapperClass = value
  209. @$element
  210. radioAllOff: (value) ->
  211. return @options.radioAllOff if typeof value is "undefined"
  212. value = not not value
  213. return @$element if value is @options.radioAllOff
  214. @options.radioAllOff = value
  215. @$element
  216. onInit: (value) ->
  217. return @options.onInit if typeof value is "undefined"
  218. value = $.fn.bootstrapSwitch.defaults.onInit unless value
  219. @options.onInit = value
  220. @$element
  221. onSwitchChange: (value) ->
  222. return @options.onSwitchChange if typeof value is "undefined"
  223. value = $.fn.bootstrapSwitch.defaults.onSwitchChange unless value
  224. @options.onSwitchChange = value
  225. @$element
  226. destroy: ->
  227. $form = @$element.closest "form"
  228. $form.off("reset.bootstrapSwitch").removeData "bootstrap-switch" if $form.length
  229. @$container.children().not(@$element).remove()
  230. @$element.unwrap().unwrap().off(".bootstrapSwitch").removeData "bootstrap-switch"
  231. @$element
  232. _width: ->
  233. $handles = @$on.add(@$off)
  234. # remove width from inline style
  235. $handles.add(@$label).css("width", "")
  236. # save handleWidth for further label width calculation check
  237. handleWidth = if @options.handleWidth is "auto"
  238. then Math.max @$on.width(), @$off.width()
  239. else @options.handleWidth
  240. # set handles width
  241. $handles.width handleWidth
  242. # set label width
  243. @$label.width (index, width) =>
  244. return @options.labelWidth if @options.labelWidth isnt "auto"
  245. if width < handleWidth then handleWidth else width
  246. # get handle and label widths
  247. @_handleWidth = @$on.outerWidth()
  248. @_labelWidth = @$label.outerWidth()
  249. # set container and wrapper widths
  250. @$container.width (@_handleWidth * 2) + @_labelWidth
  251. @$wrapper.width @_handleWidth + @_labelWidth
  252. _containerPosition: (state = @options.state, callback) ->
  253. @$container
  254. .css "margin-left", =>
  255. values = [0, "-#{@_handleWidth}px"]
  256. return "-#{@_handleWidth / 2}px" if @options.indeterminate
  257. if state
  258. return if @options.inverse then values[1] else values[0]
  259. else
  260. return if @options.inverse then values[0] else values[1]
  261. return unless callback
  262. setTimeout ->
  263. callback()
  264. , 50
  265. _elementHandlers: ->
  266. @$element.on
  267. "change.bootstrapSwitch": (e, skip) =>
  268. e.preventDefault()
  269. e.stopImmediatePropagation()
  270. state = @$element.is ":checked"
  271. @_containerPosition state
  272. return if state is @options.state
  273. @options.state = state
  274. @$wrapper.toggleClass("#{@options.baseClass}-off").toggleClass "#{@options.baseClass}-on"
  275. unless skip
  276. if @$element.is ":radio"
  277. $("[name='#{@$element.attr('name')}']")
  278. .not(@$element)
  279. .prop("checked", false)
  280. .trigger "change.bootstrapSwitch", true
  281. @$element.trigger "switchChange.bootstrapSwitch", [state]
  282. "focus.bootstrapSwitch": (e) =>
  283. e.preventDefault()
  284. @$wrapper.addClass "#{@options.baseClass}-focused"
  285. "blur.bootstrapSwitch": (e) =>
  286. e.preventDefault()
  287. @$wrapper.removeClass "#{@options.baseClass}-focused"
  288. "keydown.bootstrapSwitch": (e) =>
  289. return if not e.which or @options.disabled or @options.readonly
  290. switch e.which
  291. when 37
  292. e.preventDefault()
  293. e.stopImmediatePropagation()
  294. @state false
  295. when 39
  296. e.preventDefault()
  297. e.stopImmediatePropagation()
  298. @state true
  299. _handleHandlers: ->
  300. @$on.on "click.bootstrapSwitch", (event) =>
  301. event.preventDefault()
  302. event.stopPropagation()
  303. @state false
  304. @$element.trigger "focus.bootstrapSwitch"
  305. @$off.on "click.bootstrapSwitch", (event) =>
  306. event.preventDefault()
  307. event.stopPropagation()
  308. @state true
  309. @$element.trigger "focus.bootstrapSwitch"
  310. _labelHandlers: ->
  311. @$label.on
  312. "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (e) =>
  313. return if @_dragStart or @options.disabled or @options.readonly
  314. e.preventDefault()
  315. e.stopPropagation()
  316. @_dragStart = (e.pageX or e.originalEvent.touches[0].pageX) - parseInt @$container.css("margin-left"), 10
  317. @$wrapper.removeClass "#{@options.baseClass}-animate" if @options.animate
  318. @$element.trigger "focus.bootstrapSwitch"
  319. "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (e) =>
  320. return unless @_dragStart?
  321. e.preventDefault()
  322. difference = (e.pageX or e.originalEvent.touches[0].pageX) - @_dragStart
  323. return if difference < -@_handleWidth or difference > 0
  324. @_dragEnd = difference
  325. @$container.css "margin-left", "#{@_dragEnd}px"
  326. "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (e) =>
  327. return unless @_dragStart
  328. e.preventDefault()
  329. @$wrapper.addClass "#{@options.baseClass}-animate" if @options.animate
  330. if @_dragEnd
  331. state = @_dragEnd > -(@_handleWidth / 2)
  332. @_dragEnd = false
  333. @state if @options.inverse then not state else state
  334. else
  335. @state not @options.state
  336. @_dragStart = false
  337. "mouseleave.bootstrapSwitch": (e) =>
  338. @$label.trigger "mouseup.bootstrapSwitch"
  339. _externalLabelHandler: ->
  340. $externalLabel = @$element.closest("label")
  341. $externalLabel.on "click", (event) =>
  342. event.preventDefault()
  343. event.stopImmediatePropagation()
  344. # reimplement toggle state on external label only if it is not the target
  345. @toggleState() if event.target is $externalLabel[0]
  346. _formHandler: ->
  347. $form = @$element.closest "form"
  348. return if $form.data "bootstrap-switch"
  349. $form
  350. .on "reset.bootstrapSwitch", ->
  351. window.setTimeout ->
  352. $form
  353. .find("input")
  354. .filter( -> $(@).data "bootstrap-switch")
  355. .each -> $(@).bootstrapSwitch "state", @checked
  356. , 1
  357. .data "bootstrap-switch", true
  358. _getClasses: (classes) ->
  359. return ["#{@options.baseClass}-#{classes}"] unless $.isArray classes
  360. cls = []
  361. for c in classes
  362. cls.push "#{@options.baseClass}-#{c}"
  363. cls
  364. $.fn.bootstrapSwitch = (option, args...) ->
  365. ret = @
  366. @each ->
  367. $this = $ @
  368. data = $this.data "bootstrap-switch"
  369. $this.data "bootstrap-switch", data = new BootstrapSwitch @, option unless data
  370. ret = data[option].apply data, args if typeof option is "string"
  371. ret
  372. $.fn.bootstrapSwitch.Constructor = BootstrapSwitch
  373. $.fn.bootstrapSwitch.defaults =
  374. state: true
  375. size: null
  376. animate: true
  377. disabled: false
  378. readonly: false
  379. indeterminate: false
  380. inverse: false
  381. radioAllOff: false
  382. onColor: "primary"
  383. offColor: "default"
  384. onText: "ON"
  385. offText: "OFF"
  386. labelText: "&nbsp;"
  387. handleWidth: "auto"
  388. labelWidth: "auto"
  389. baseClass: "bootstrap-switch"
  390. wrapperClass: "wrapper"
  391. onInit: ->
  392. onSwitchChange: ->