rcswitcher.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. /**
  2. * JQuery rcSwitcher plugin
  3. *
  4. * rcSwitcher plugin referes to 'Radio Checkbox Switcher'
  5. * it let you transform radio and checkbox inputs into a nice switch button
  6. * without requirement of a specific html syntax, it simply takes your existed basic input
  7. * and do the magic with many customizations and supports 4 themes
  8. *
  9. *
  10. * @package rcSwitcher
  11. * @name rcSwitcher
  12. * @version 4.1 < 18 Feb 2017 >
  13. * @changes 4.1 - Add feature that input shold only have on switcher
  14. * - Add rcSwitcher Object on input as property
  15. * - Add attribute 'data-has-rcswitcher="1"' on input to mark that input has rcswitcher
  16. * - Enhance Auto Stick
  17. * @author ahmed saad <a7mad.sa3d.2014@gmail.com><ahmedfadlshaker@gmail.com>
  18. * @copyright ahmed saad april 2015
  19. * @link http://plus.google.com/+AhmedSaadGmail
  20. * @license http://choosealicense.com/licenses/gpl-3.0/ GNU GPL V3.0
  21. *
  22. */
  23. (function($){
  24. /**
  25. * Defining Static variables
  26. */
  27. var templates = {
  28. $switcher : $( '<span>', {'class': 'swraper'} ).hide(),
  29. $toggler: $( '<span>', {'class': 'stoggler' } ),
  30. $on: $( '<span>', {'class': 'slabel-on'} ),
  31. $off: $( '<span>', {'class': 'slabel-off'} ),
  32. $blob: $( '<span>', {'class': 'sblob'} ),
  33. // Allowed Themes
  34. themes: [ 'light', 'modern', 'dark', 'flat', 'yellowish-green' ],
  35. };
  36. /**
  37. * All Switcher methods goes here
  38. *
  39. * here all logic of switcher plugin
  40. */
  41. var switcherM = {
  42. /**
  43. * Prepare Switches
  44. *
  45. * @param {Object} switcherP switcher plugin properties
  46. * @param {Object} options switcher plugin options object
  47. * @return void
  48. */
  49. start: function( switcherP, options )
  50. {
  51. // Set Templates and one runtime calculations
  52. this.setTemplateStyle( options, switcherP );
  53. // prepare and create switches
  54. switcherP.$this.each( function( i, input )
  55. {
  56. // console.log( $input.type );
  57. if( input.hasOwnProperty( 'rcSwitcher' ) )
  58. {
  59. console.warn( 'already has instance' );
  60. return;
  61. }
  62. this.makeSwitcher( $( input ), switcherP, options );
  63. }.bind( this ) );
  64. // ADD EVENT BEHAVIOUR
  65. this.behaviour( switcherP );
  66. },
  67. /**
  68. * Make a switch
  69. *
  70. * @param {jQ Obj} $input input object to make switch for
  71. * @param {Object} switcherP switcher plugin propertirs
  72. * @param {Object} options switcher plugin options
  73. * @return void
  74. */
  75. makeSwitcher: function( $input, switcherP, options )
  76. {
  77. // console.log( $input.attr('name') )
  78. var inputName = $input.attr( 'name' ).replace( /(\[\])+$/, '' ), // remove input name array signs
  79. inputValue = $input.attr('value') || '',
  80. type = $input.attr('type'),
  81. cat = ( type == 'checkbox' ) ? switcherP.checkboxs : switcherP.radios ,
  82. data = { components: {}, '$input': $input };
  83. data.$switcher = templates.$switcher.clone().attr( { 'input-name': inputName, 'input-value': inputValue, 'input-type': type } );
  84. if( switcherP.cssValues.dir == 'rtl' ) data.$switcher.addClass( 'rtl' );
  85. data.components.$toggler = templates.$toggler.clone();
  86. data.components.$on = templates.$on.clone().html( $input.attr('data-ontext') || options.onText );
  87. data.components.$off = templates.$off.clone().html( $input.attr('data-offtext') || options.offText );
  88. data.components.$blob = templates.$blob.clone();
  89. // Build switch according to direction order
  90. if( options.reverse )
  91. data.components.$toggler.append( data.components.$off, data.components.$blob, data.components.$on )
  92. .appendTo( data.$switcher );
  93. else
  94. data.components.$toggler.append( data.components.$on, data.components.$blob, data.components.$off )
  95. .appendTo( data.$switcher );
  96. // Insert to DOM
  97. $input.before( data.$switcher );
  98. // Set On Off Style
  99. if( $input[ 0 ].checked )
  100. {
  101. data.switcherChecked = true;
  102. //|-> outside change
  103. //| //|-> style only
  104. //| //|
  105. this.turnOn( data, switcherP, false, true );
  106. }
  107. else
  108. {
  109. data.switcherChecked = false;
  110. this.turnOff( data, switcherP, false, true );
  111. }
  112. // Set Disabled Style
  113. inputDisabled = $input[ 0 ].disabled;
  114. if( inputDisabled )
  115. {
  116. data.switcherDisabled = true;
  117. data.$switcher.addClass( 'disabled' ).attr( 'title', 'switch is disabled!' );
  118. }
  119. else
  120. {
  121. data.switcherDisabled = false;
  122. }
  123. // Use family to be aliase for current radio name family
  124. // all radios with the same name has the same family
  125. // Check if family already exists
  126. if( family = cat.group[ inputName ] )
  127. {
  128. family.group[ inputValue ] = data;
  129. // update length.
  130. // represents how many input for the same radio name
  131. family.length++;
  132. }
  133. else
  134. {
  135. // create new family with radio name
  136. cat.group[ inputName ] = family = { group: {}, length: 1 }//, enabled: 0, disabled: 0, switchable: false };
  137. family.group[ inputValue ] = data;
  138. // Update groupLength property
  139. cat.groupLength++;
  140. // add radio specific properties
  141. if( type == 'radio' )
  142. {
  143. family.enabled = 0;
  144. family.disabled = 0;
  145. family.switchable = false;
  146. }
  147. }
  148. // Specifiuc radio properties
  149. if( type == 'radio' )
  150. {
  151. // Status
  152. if( inputDisabled )
  153. family.disabled++;
  154. else
  155. family.enabled++;
  156. // set current checked radio input
  157. if( $input[ 0 ].checked )
  158. family.checkedKey = inputValue;
  159. // Set switchable property
  160. family.switchable = ( family.enabled >= 2 ) ? true : false;
  161. }
  162. // Add to switchers collections
  163. switcherP.$switchers = switcherP.$switchers.add( data.$switcher );
  164. // Store In Element Input JS
  165. $input[0].rcSwitcher = data;
  166. // Add attribute to mark this input that it has rcswitcher
  167. $input.attr( 'data-has-rcswitcher', 1 );
  168. // Update input type length
  169. cat.length++;
  170. // show
  171. data.$switcher.show();
  172. // attach Outside Change Event
  173. $input.on( 'change', function(e)
  174. {
  175. this.trackChanges({
  176. type: type,
  177. name: inputName,
  178. value: inputValue,
  179. }, switcherP );
  180. }.bind(this));
  181. },
  182. /**
  183. * set plugin templates according to current set options
  184. *
  185. * @param {Object} options switcher plugin options object
  186. * @param {Object} switcherP switcher plugin properties
  187. * @return void
  188. */
  189. setTemplateStyle: function( options, switcherP ){
  190. // Switcher
  191. templates.$switcher.css( { 'width': [ options.width, 'px' ].join(''), 'line-height': [ options.height, 'px' ].join('') } )
  192. .removeClass( templates.themes.join(' ') ).addClass( options.theme );
  193. switcherP.cssValues.switcherWidth = options.width;
  194. switcherP.cssValues.switcherHeight = options.height;
  195. // Blob
  196. switcherP.cssValues.blobOffset = options.blobOffset;
  197. switcherP.cssValues.blobWidth = switcherP.cssValues.blobHeight = switcherP.cssValues.switcherHeight - options.blobOffset * 2;
  198. templates.$blob.css( { 'width': switcherP.cssValues.blobWidth, 'height': switcherP.cssValues.blobHeight } );
  199. // Set Transform Property EX, translateX( 10px )
  200. if( options.reverse )
  201. {
  202. switcherP.transformOff = ['translateX(', switcherP.transformDir , switcherP.cssValues.blobWidth / 2 + switcherP.cssValues.blobOffset, 'px)' ].join('');
  203. switcherP.transformOn = ['translateX(', switcherP.transformDir , switcherP.cssValues.switcherWidth - switcherP.cssValues.blobWidth / 2 - switcherP.cssValues.blobOffset, 'px)' ].join('');
  204. }
  205. else
  206. {
  207. switcherP.transformOn = ['translateX(', switcherP.transformDir , switcherP.cssValues.blobWidth / 2 + switcherP.cssValues.blobOffset, 'px)' ].join('');
  208. switcherP.transformOff = ['translateX(', switcherP.transformDir , switcherP.cssValues.switcherWidth - switcherP.cssValues.blobWidth / 2 - switcherP.cssValues.blobOffset, 'px)' ].join('');
  209. }
  210. switcherP.transformOn = {
  211. '-webkit-transform': switcherP.transformOn,
  212. '-moz-transform': switcherP.transformOn,
  213. '-o-transform': switcherP.transformOn,
  214. '-ms-transform': switcherP.transformOn,
  215. 'transform': switcherP.transformOn
  216. }
  217. switcherP.transformOff = {
  218. '-webkit-transform': switcherP.transformOff,
  219. '-moz-transform': switcherP.transformOff,
  220. '-o-transform': switcherP.transformOff,
  221. '-ms-transform': switcherP.transformOff,
  222. 'transform': switcherP.transformOff
  223. }
  224. // console.log( switcherP.transformOn )
  225. // Remove any applied font-size from previous initializaing
  226. templates.$toggler.css( 'font-size', '' );
  227. // Automatic fit font size in low heights or on autoFontSize
  228. if( options.height < 20 || options.autoFontSize ) templates.$toggler.css( { 'font-size': [ options.height / 2, 'px' ].join('') } );
  229. // console.log( 'calculated once' )
  230. // Auto Stick
  231. templates.$switcher.css( 'margin', '' );
  232. if( options.autoStick )
  233. {
  234. var $fInput = switcherP.$this.first(),
  235. $label = $fInput.prev('label'),
  236. parentAW;
  237. if( $label )
  238. {
  239. parentAW = $fInput.parent().width();
  240. // subtract input width if visible
  241. parentAW -= ( options.inputs ) ? $fInput.outerWidth( true ) : 0;
  242. // subtract label width
  243. parentAW -= $label.outerWidth( true );
  244. // subtract switch width
  245. var margin = parentAW - options.width;
  246. // remove border width if exists
  247. switch( options.theme )
  248. {
  249. case 'dark':
  250. margin -= 2;
  251. break;
  252. case 'yellowish-green':
  253. margin -= 4;
  254. break;
  255. }
  256. // Left OR Right margin
  257. if( switcherP.cssValues.dir == 'rtl' )
  258. templates.$switcher.css( 'margin-right', margin );
  259. else
  260. templates.$switcher.css( 'margin-left', margin );
  261. }
  262. }
  263. },
  264. /**
  265. * Toggle switch status
  266. *
  267. * @param {Object} info switcher information object
  268. * @param {Object} switcherP switcher plugin properties
  269. * @param {Boolean} outsideChange if toggle according to outside check change or not
  270. * @return void
  271. */
  272. toggle: function( info, switcherP, outsideChange ){
  273. // Aliases
  274. var family, data;
  275. if( info.type == 'checkbox' )
  276. {
  277. // Checkbox
  278. family = switcherP.checkboxs.group[ info.name ];
  279. data = family.group[ info.value ];
  280. if( !data.$input[ 0 ].disabled )
  281. {
  282. // console.log( 'clicked checkbox' )
  283. if( outsideChange )
  284. status = data.$input[ 0 ].checked ? 'turnOn' : 'turnOff';
  285. else
  286. status = data.$input[ 0 ].checked ? 'turnOff' : 'turnOn';
  287. this[ status ]( data, switcherP, outsideChange );
  288. }
  289. }
  290. else
  291. {
  292. // Radio
  293. family = switcherP.radios.group[ info.name ];
  294. // console.log( family );
  295. data = family.group[ info.value ] ;
  296. if( outsideChange && data.$input[0].checked == data.switcherChecked )
  297. return;
  298. // console.log( data );
  299. // if Outside request is to turnoff ( uncheck ), then don't do any thing and recheck radio
  300. if( outsideChange && !data.$input[ 0 ].checked )
  301. {
  302. // canot disable
  303. console.log( 'can not disable radio button, try to activate another sibiling one to deactivate current.' );
  304. data.$input.prop( 'checked', true );
  305. return;
  306. }
  307. if( !data.$input[ 0 ].disabled && family.switchable )
  308. {
  309. // change only if it is not currently selected
  310. if( family.checkedKey != info.name )
  311. {
  312. // turnon
  313. this.turnOff( family.group[ family.checkedKey ], switcherP, outsideChange );
  314. this.turnOn( data, switcherP, outsideChange );
  315. family.checkedKey = info.value;
  316. }
  317. }
  318. }
  319. },
  320. /**
  321. * Track Input Changes
  322. *
  323. * This Method Will Check And Apply Any Changes Happens To Original Input
  324. * Those Changes Are Checked And Disabled Status
  325. *
  326. * This Method Also Handles Disabled Changes Mechanism
  327. *
  328. * Fires : enable.rcSwitcher AND disable.rcSwitcher according To Change Status
  329. *
  330. * @param {Object Literal} info Input Name, Value, Type
  331. * @param {Switcher Properties Object} switcherP All Switchers Properties Object
  332. * @return {undefined} Doesnot Return Any Thing
  333. */
  334. trackChanges: function( info, switcherP )
  335. {
  336. // On Input Change Event
  337. var family, data;
  338. if( info.type == 'checkbox' )
  339. family = switcherP.checkboxs.group[ info.name ];
  340. else
  341. family = switcherP.radios.group[ info.name ];
  342. data = family.group[ info.value ];
  343. // Track Checked Status Changes
  344. if( data.$input[0].checked != data.switcherChecked )
  345. {
  346. if( data.switcherDisabled )
  347. {
  348. // Revert Changes
  349. data.$input.prop( 'checked', data.switcherChecked );
  350. if( info.type == 'radio' )
  351. family.group[ family.checkedKey ].$input.prop( 'checked', true );
  352. }
  353. else
  354. this.toggle( info, switcherP, true );
  355. }
  356. // Track Disabled Status Changes
  357. if( data.$input[0].disabled != data.switcherDisabled )
  358. {
  359. if( data.$input[0].disabled )
  360. {
  361. if( !data.$switcher.hasClass( 'disabled' ) )
  362. data.$switcher.addClass( 'disabled' );
  363. if( info.type == 'radio' )
  364. {
  365. family.disabled++;
  366. family.enabled--;
  367. // If Checked, Disable All Group
  368. if( data.$input[0].checked == true )
  369. family.switchable = false;
  370. // console.log( family );
  371. }
  372. data.switcherDisabled = true;
  373. // Fire Event and pass data object to event handler
  374. data.$input.trigger( 'disable.rcSwitcher', data );
  375. }
  376. else
  377. {
  378. data.$switcher.removeClass( 'disabled' );
  379. data.$input.trigger( 'enable.rcSwitcher', data );
  380. if( info.type == 'radio' )
  381. {
  382. --family.disabled;
  383. ++family.enabled;
  384. if( family.enabled - family.disabled >= 2 )
  385. family.switchable = true;
  386. // console.log( family );
  387. }
  388. data.switcherDisabled = false;
  389. }
  390. }
  391. },
  392. /**
  393. * Turn Off switch
  394. *
  395. * it also fire 'switcher.turnoff' custom event
  396. *
  397. * @param {Object} data data object represents switch entry in switcher properties
  398. * @param {Object} switcherP switcher plugin properties
  399. * @param {Boolean} styleOnly turnoff style only
  400. * @return void
  401. */
  402. turnOff: function( data, switcherP, outsideChange, styleOnly ){
  403. // console.log( 'calculated on eventOFF' )
  404. data.components.$toggler.css( switcherP.transformOff ).removeClass( 'on' ).addClass('off');
  405. data.switcherChecked = false;
  406. if( styleOnly ) return;
  407. // Set Input To Off
  408. if( !outsideChange )
  409. data.$input.prop( 'checked', false );
  410. // Fire Event and pass data object to event handler
  411. // Wait Transition 500ms Time
  412. setTimeout( function(){
  413. data.$input.trigger( 'turnoff.rcSwitcher', data );
  414. data.$input.trigger( 'toggle.rcSwitcher', [ data, 'turnoff' ] );
  415. }, 500 );
  416. },
  417. /**
  418. * Turn On switch
  419. *
  420. * it also fire 'switcher.turnon' custom event
  421. *
  422. * @param {Object} data data object represents switch entry in switcher properties
  423. * @param {Object} switcherP switcher plugin properties
  424. * @param {Boolean} styleOnly turnon style only
  425. * @return void
  426. */
  427. turnOn: function( data, switcherP, outsideChange, styleOnly ){
  428. // console.log( 'calculated on eventOn' )
  429. data.components.$toggler.css( switcherP.transformOn ).addClass( 'on' ).removeClass('off');
  430. data.switcherChecked = true;
  431. if( styleOnly ) return;
  432. // Set To ON and trigger original change event
  433. // auto set checked property doesnot fire toggle event, so we need to trigger event manually
  434. if( !outsideChange )
  435. data.$input.prop( 'checked', true );
  436. // Wait Transition 500ms Time
  437. setTimeout( function(){
  438. data.$input.trigger( 'turnon.rcSwitcher', data );
  439. data.$input.trigger( 'toggle.rcSwitcher', [ data, 'turnon' ] );
  440. }, 500 );
  441. },
  442. /**
  443. * Set Switcher Plugin Events
  444. *
  445. * @param {Object} switcherP plugin properties
  446. * @return void
  447. */
  448. behaviour: function( switcherP ){
  449. // behaviour: function( switcherP, $switcher ){
  450. // Disable click for input
  451. switcherP.$this.on( 'click', function( e )
  452. {
  453. e.preventDefault();
  454. e.stopPropagation();
  455. }.bind( this ) );
  456. // On Click on switchers
  457. switcherP.$switchers.on( 'click', function( e )
  458. {
  459. var obj = {};
  460. obj.type = e.currentTarget.getAttribute( 'input-type' );
  461. obj.name = e.currentTarget.getAttribute( 'input-name' );
  462. obj.value = e.currentTarget.getAttribute( 'input-value' );
  463. // Toggle
  464. this.toggle( obj, switcherP );
  465. e.preventDefault();
  466. e.stopPropagation();
  467. }.bind( this ) );
  468. },
  469. };
  470. /**
  471. * SWITCHER PLUGIN
  472. * @param {Object} options switcher options
  473. * @return {jQ Obj} this
  474. */
  475. $.fn.rcSwitcher = function( options ){
  476. // Defining Properties
  477. var switcherP = {
  478. checkboxs: {
  479. group:{},
  480. groupLength: 0,
  481. length: 0,
  482. },
  483. radios: {
  484. group:{},
  485. groupLength: 0,
  486. length: 0,
  487. },
  488. // $this: this,
  489. $switchers: $([]),
  490. cssValues:{},
  491. };
  492. // Filter this
  493. // Get Only Checkbox and Radio inputs only
  494. switcherP.$this = this.filter( 'input[type=checkbox], input[type=radio]' ).not( '[data-has-rcswitcher]' );
  495. // Stop if we havenot any checkboxs or radios
  496. if( switcherP.$this.length == 0 )
  497. return this;
  498. switcherP.cssValues.dir = window.getComputedStyle( switcherP.$this[0], null ).direction || 'ltr';
  499. // Detect Transform direction
  500. switcherP.transformDir = ( switcherP.cssValues.dir == 'rtl' ) ? '' : '-';
  501. // Set Options
  502. options = $.extend(
  503. {
  504. onText: 'ON',
  505. offText: 'OFF',
  506. reverse: false,
  507. inputs: false,
  508. width: 56,
  509. height: 20,
  510. blobOffset: 1,
  511. autoFontSize: false,
  512. theme: 'light',
  513. autoStick: false,
  514. },
  515. options || {} );
  516. // Hide Them ( selected inputs ) All
  517. if( !options.inputs ) switcherP.$this.css( 'display', 'none' );
  518. // Make Sure that Theme is Supported
  519. if( templates.themes.indexOf( options.theme ) == -1 ) options.theme = 'light';
  520. // Start
  521. switcherM.start( switcherP, options );
  522. // Return selected jquery object to allow chaining
  523. return this;
  524. };
  525. })(jQuery);
  526. // الحمد لله