/** * JQuery rcSwitcher plugin * * rcSwitcher plugin referes to 'Radio Checkbox Switcher' * it let you transform radio and checkbox inputs into a nice switch button * without requirement of a specific html syntax, it simply takes your existed basic input * and do the magic with many customizations and supports 4 themes * * * @package rcSwitcher * @name rcSwitcher * @version 4.1 < 18 Feb 2017 > * @changes 4.1 - Add feature that input shold only have on switcher * - Add rcSwitcher Object on input as property * - Add attribute 'data-has-rcswitcher="1"' on input to mark that input has rcswitcher * - Enhance Auto Stick * @author ahmed saad * @copyright ahmed saad april 2015 * @link http://plus.google.com/+AhmedSaadGmail * @license http://choosealicense.com/licenses/gpl-3.0/ GNU GPL V3.0 * */ (function($){ /** * Defining Static variables */ var templates = { $switcher : $( '', {'class': 'swraper'} ).hide(), $toggler: $( '', {'class': 'stoggler' } ), $on: $( '', {'class': 'slabel-on'} ), $off: $( '', {'class': 'slabel-off'} ), $blob: $( '', {'class': 'sblob'} ), // Allowed Themes themes: [ 'light', 'modern', 'dark', 'flat', 'yellowish-green' ], }; /** * All Switcher methods goes here * * here all logic of switcher plugin */ var switcherM = { /** * Prepare Switches * * @param {Object} switcherP switcher plugin properties * @param {Object} options switcher plugin options object * @return void */ start: function( switcherP, options ) { // Set Templates and one runtime calculations this.setTemplateStyle( options, switcherP ); // prepare and create switches switcherP.$this.each( function( i, input ) { // console.log( $input.type ); if( input.hasOwnProperty( 'rcSwitcher' ) ) { console.warn( 'already has instance' ); return; } this.makeSwitcher( $( input ), switcherP, options ); }.bind( this ) ); // ADD EVENT BEHAVIOUR this.behaviour( switcherP ); }, /** * Make a switch * * @param {jQ Obj} $input input object to make switch for * @param {Object} switcherP switcher plugin propertirs * @param {Object} options switcher plugin options * @return void */ makeSwitcher: function( $input, switcherP, options ) { // console.log( $input.attr('name') ) var inputName = $input.attr( 'name' ).replace( /(\[\])+$/, '' ), // remove input name array signs inputValue = $input.attr('value') || '', type = $input.attr('type'), cat = ( type == 'checkbox' ) ? switcherP.checkboxs : switcherP.radios , data = { components: {}, '$input': $input }; data.$switcher = templates.$switcher.clone().attr( { 'input-name': inputName, 'input-value': inputValue, 'input-type': type } ); if( switcherP.cssValues.dir == 'rtl' ) data.$switcher.addClass( 'rtl' ); data.components.$toggler = templates.$toggler.clone(); data.components.$on = templates.$on.clone().html( $input.attr('data-ontext') || options.onText ); data.components.$off = templates.$off.clone().html( $input.attr('data-offtext') || options.offText ); data.components.$blob = templates.$blob.clone(); // Build switch according to direction order if( options.reverse ) data.components.$toggler.append( data.components.$off, data.components.$blob, data.components.$on ) .appendTo( data.$switcher ); else data.components.$toggler.append( data.components.$on, data.components.$blob, data.components.$off ) .appendTo( data.$switcher ); // Insert to DOM $input.before( data.$switcher ); // Set On Off Style if( $input[ 0 ].checked ) { data.switcherChecked = true; //|-> outside change //| //|-> style only //| //| this.turnOn( data, switcherP, false, true ); } else { data.switcherChecked = false; this.turnOff( data, switcherP, false, true ); } // Set Disabled Style inputDisabled = $input[ 0 ].disabled; if( inputDisabled ) { data.switcherDisabled = true; data.$switcher.addClass( 'disabled' ).attr( 'title', 'switch is disabled!' ); } else { data.switcherDisabled = false; } // Use family to be aliase for current radio name family // all radios with the same name has the same family // Check if family already exists if( family = cat.group[ inputName ] ) { family.group[ inputValue ] = data; // update length. // represents how many input for the same radio name family.length++; } else { // create new family with radio name cat.group[ inputName ] = family = { group: {}, length: 1 }//, enabled: 0, disabled: 0, switchable: false }; family.group[ inputValue ] = data; // Update groupLength property cat.groupLength++; // add radio specific properties if( type == 'radio' ) { family.enabled = 0; family.disabled = 0; family.switchable = false; } } // Specifiuc radio properties if( type == 'radio' ) { // Status if( inputDisabled ) family.disabled++; else family.enabled++; // set current checked radio input if( $input[ 0 ].checked ) family.checkedKey = inputValue; // Set switchable property family.switchable = ( family.enabled >= 2 ) ? true : false; } // Add to switchers collections switcherP.$switchers = switcherP.$switchers.add( data.$switcher ); // Store In Element Input JS $input[0].rcSwitcher = data; // Add attribute to mark this input that it has rcswitcher $input.attr( 'data-has-rcswitcher', 1 ); // Update input type length cat.length++; // show data.$switcher.show(); // attach Outside Change Event $input.on( 'change', function(e) { this.trackChanges({ type: type, name: inputName, value: inputValue, }, switcherP ); }.bind(this)); }, /** * set plugin templates according to current set options * * @param {Object} options switcher plugin options object * @param {Object} switcherP switcher plugin properties * @return void */ setTemplateStyle: function( options, switcherP ){ // Switcher templates.$switcher.css( { 'width': [ options.width, 'px' ].join(''), 'line-height': [ options.height, 'px' ].join('') } ) .removeClass( templates.themes.join(' ') ).addClass( options.theme ); switcherP.cssValues.switcherWidth = options.width; switcherP.cssValues.switcherHeight = options.height; // Blob switcherP.cssValues.blobOffset = options.blobOffset; switcherP.cssValues.blobWidth = switcherP.cssValues.blobHeight = switcherP.cssValues.switcherHeight - options.blobOffset * 2; templates.$blob.css( { 'width': switcherP.cssValues.blobWidth, 'height': switcherP.cssValues.blobHeight } ); // Set Transform Property EX, translateX( 10px ) if( options.reverse ) { switcherP.transformOff = ['translateX(', switcherP.transformDir , switcherP.cssValues.blobWidth / 2 + switcherP.cssValues.blobOffset, 'px)' ].join(''); switcherP.transformOn = ['translateX(', switcherP.transformDir , switcherP.cssValues.switcherWidth - switcherP.cssValues.blobWidth / 2 - switcherP.cssValues.blobOffset, 'px)' ].join(''); } else { switcherP.transformOn = ['translateX(', switcherP.transformDir , switcherP.cssValues.blobWidth / 2 + switcherP.cssValues.blobOffset, 'px)' ].join(''); switcherP.transformOff = ['translateX(', switcherP.transformDir , switcherP.cssValues.switcherWidth - switcherP.cssValues.blobWidth / 2 - switcherP.cssValues.blobOffset, 'px)' ].join(''); } switcherP.transformOn = { '-webkit-transform': switcherP.transformOn, '-moz-transform': switcherP.transformOn, '-o-transform': switcherP.transformOn, '-ms-transform': switcherP.transformOn, 'transform': switcherP.transformOn } switcherP.transformOff = { '-webkit-transform': switcherP.transformOff, '-moz-transform': switcherP.transformOff, '-o-transform': switcherP.transformOff, '-ms-transform': switcherP.transformOff, 'transform': switcherP.transformOff } // console.log( switcherP.transformOn ) // Remove any applied font-size from previous initializaing templates.$toggler.css( 'font-size', '' ); // Automatic fit font size in low heights or on autoFontSize if( options.height < 20 || options.autoFontSize ) templates.$toggler.css( { 'font-size': [ options.height / 2, 'px' ].join('') } ); // console.log( 'calculated once' ) // Auto Stick templates.$switcher.css( 'margin', '' ); if( options.autoStick ) { var $fInput = switcherP.$this.first(), $label = $fInput.prev('label'), parentAW; if( $label ) { parentAW = $fInput.parent().width(); // subtract input width if visible parentAW -= ( options.inputs ) ? $fInput.outerWidth( true ) : 0; // subtract label width parentAW -= $label.outerWidth( true ); // subtract switch width var margin = parentAW - options.width; // remove border width if exists switch( options.theme ) { case 'dark': margin -= 2; break; case 'yellowish-green': margin -= 4; break; } // Left OR Right margin if( switcherP.cssValues.dir == 'rtl' ) templates.$switcher.css( 'margin-right', margin ); else templates.$switcher.css( 'margin-left', margin ); } } }, /** * Toggle switch status * * @param {Object} info switcher information object * @param {Object} switcherP switcher plugin properties * @param {Boolean} outsideChange if toggle according to outside check change or not * @return void */ toggle: function( info, switcherP, outsideChange ){ // Aliases var family, data; if( info.type == 'checkbox' ) { // Checkbox family = switcherP.checkboxs.group[ info.name ]; data = family.group[ info.value ]; if( !data.$input[ 0 ].disabled ) { // console.log( 'clicked checkbox' ) if( outsideChange ) status = data.$input[ 0 ].checked ? 'turnOn' : 'turnOff'; else status = data.$input[ 0 ].checked ? 'turnOff' : 'turnOn'; this[ status ]( data, switcherP, outsideChange ); } } else { // Radio family = switcherP.radios.group[ info.name ]; // console.log( family ); data = family.group[ info.value ] ; if( outsideChange && data.$input[0].checked == data.switcherChecked ) return; // console.log( data ); // if Outside request is to turnoff ( uncheck ), then don't do any thing and recheck radio if( outsideChange && !data.$input[ 0 ].checked ) { // canot disable console.log( 'can not disable radio button, try to activate another sibiling one to deactivate current.' ); data.$input.prop( 'checked', true ); return; } if( !data.$input[ 0 ].disabled && family.switchable ) { // change only if it is not currently selected if( family.checkedKey != info.name ) { // turnon this.turnOff( family.group[ family.checkedKey ], switcherP, outsideChange ); this.turnOn( data, switcherP, outsideChange ); family.checkedKey = info.value; } } } }, /** * Track Input Changes * * This Method Will Check And Apply Any Changes Happens To Original Input * Those Changes Are Checked And Disabled Status * * This Method Also Handles Disabled Changes Mechanism * * Fires : enable.rcSwitcher AND disable.rcSwitcher according To Change Status * * @param {Object Literal} info Input Name, Value, Type * @param {Switcher Properties Object} switcherP All Switchers Properties Object * @return {undefined} Doesnot Return Any Thing */ trackChanges: function( info, switcherP ) { // On Input Change Event var family, data; if( info.type == 'checkbox' ) family = switcherP.checkboxs.group[ info.name ]; else family = switcherP.radios.group[ info.name ]; data = family.group[ info.value ]; // Track Checked Status Changes if( data.$input[0].checked != data.switcherChecked ) { if( data.switcherDisabled ) { // Revert Changes data.$input.prop( 'checked', data.switcherChecked ); if( info.type == 'radio' ) family.group[ family.checkedKey ].$input.prop( 'checked', true ); } else this.toggle( info, switcherP, true ); } // Track Disabled Status Changes if( data.$input[0].disabled != data.switcherDisabled ) { if( data.$input[0].disabled ) { if( !data.$switcher.hasClass( 'disabled' ) ) data.$switcher.addClass( 'disabled' ); if( info.type == 'radio' ) { family.disabled++; family.enabled--; // If Checked, Disable All Group if( data.$input[0].checked == true ) family.switchable = false; // console.log( family ); } data.switcherDisabled = true; // Fire Event and pass data object to event handler data.$input.trigger( 'disable.rcSwitcher', data ); } else { data.$switcher.removeClass( 'disabled' ); data.$input.trigger( 'enable.rcSwitcher', data ); if( info.type == 'radio' ) { --family.disabled; ++family.enabled; if( family.enabled - family.disabled >= 2 ) family.switchable = true; // console.log( family ); } data.switcherDisabled = false; } } }, /** * Turn Off switch * * it also fire 'switcher.turnoff' custom event * * @param {Object} data data object represents switch entry in switcher properties * @param {Object} switcherP switcher plugin properties * @param {Boolean} styleOnly turnoff style only * @return void */ turnOff: function( data, switcherP, outsideChange, styleOnly ){ // console.log( 'calculated on eventOFF' ) data.components.$toggler.css( switcherP.transformOff ).removeClass( 'on' ).addClass('off'); data.switcherChecked = false; if( styleOnly ) return; // Set Input To Off if( !outsideChange ) data.$input.prop( 'checked', false ); // Fire Event and pass data object to event handler // Wait Transition 500ms Time setTimeout( function(){ data.$input.trigger( 'turnoff.rcSwitcher', data ); data.$input.trigger( 'toggle.rcSwitcher', [ data, 'turnoff' ] ); }, 500 ); }, /** * Turn On switch * * it also fire 'switcher.turnon' custom event * * @param {Object} data data object represents switch entry in switcher properties * @param {Object} switcherP switcher plugin properties * @param {Boolean} styleOnly turnon style only * @return void */ turnOn: function( data, switcherP, outsideChange, styleOnly ){ // console.log( 'calculated on eventOn' ) data.components.$toggler.css( switcherP.transformOn ).addClass( 'on' ).removeClass('off'); data.switcherChecked = true; if( styleOnly ) return; // Set To ON and trigger original change event // auto set checked property doesnot fire toggle event, so we need to trigger event manually if( !outsideChange ) data.$input.prop( 'checked', true ); // Wait Transition 500ms Time setTimeout( function(){ data.$input.trigger( 'turnon.rcSwitcher', data ); data.$input.trigger( 'toggle.rcSwitcher', [ data, 'turnon' ] ); }, 500 ); }, /** * Set Switcher Plugin Events * * @param {Object} switcherP plugin properties * @return void */ behaviour: function( switcherP ){ // behaviour: function( switcherP, $switcher ){ // Disable click for input switcherP.$this.on( 'click', function( e ) { e.preventDefault(); e.stopPropagation(); }.bind( this ) ); // On Click on switchers switcherP.$switchers.on( 'click', function( e ) { var obj = {}; obj.type = e.currentTarget.getAttribute( 'input-type' ); obj.name = e.currentTarget.getAttribute( 'input-name' ); obj.value = e.currentTarget.getAttribute( 'input-value' ); // Toggle this.toggle( obj, switcherP ); e.preventDefault(); e.stopPropagation(); }.bind( this ) ); }, }; /** * SWITCHER PLUGIN * @param {Object} options switcher options * @return {jQ Obj} this */ $.fn.rcSwitcher = function( options ){ // Defining Properties var switcherP = { checkboxs: { group:{}, groupLength: 0, length: 0, }, radios: { group:{}, groupLength: 0, length: 0, }, // $this: this, $switchers: $([]), cssValues:{}, }; // Filter this // Get Only Checkbox and Radio inputs only switcherP.$this = this.filter( 'input[type=checkbox], input[type=radio]' ).not( '[data-has-rcswitcher]' ); // Stop if we havenot any checkboxs or radios if( switcherP.$this.length == 0 ) return this; switcherP.cssValues.dir = window.getComputedStyle( switcherP.$this[0], null ).direction || 'ltr'; // Detect Transform direction switcherP.transformDir = ( switcherP.cssValues.dir == 'rtl' ) ? '' : '-'; // Set Options options = $.extend( { onText: 'ON', offText: 'OFF', reverse: false, inputs: false, width: 56, height: 20, blobOffset: 1, autoFontSize: false, theme: 'light', autoStick: false, }, options || {} ); // Hide Them ( selected inputs ) All if( !options.inputs ) switcherP.$this.css( 'display', 'none' ); // Make Sure that Theme is Supported if( templates.themes.indexOf( options.theme ) == -1 ) options.theme = 'light'; // Start switcherM.start( switcherP, options ); // Return selected jquery object to allow chaining return this; }; })(jQuery); // الحمد لله