redux-extension-customizer.js (15802B)
1 /* global jQuery, document, redux, redux_change:true, wp */ 2 3 /** 4 * SerializeJSON jQuery plugin. 5 * https://github.com/marioizquierdo/jquery.serializeJSON 6 * version 2.6.0 (Apr, 2015) 7 8 * Copyright (c) 2012, 2015 Mario Izquierdo 9 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 10 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 11 */ 12 (function( $ ) { 13 'use strict'; 14 15 // Usage: jQuery('form').serializeJSON() . 16 $.fn.serializeJSON = function( options ) { 17 var serializedObject, formAsArray, keys, type, value, f, opts; 18 f = $.serializeJSON; 19 opts = f.setupOpts( options ); // Calculate values for options {parseNumbers, parseBoolens, parseNulls}. 20 formAsArray = this.serializeArray(); // Array of objects {name, value}. 21 f.readCheckboxUncheckedValues( formAsArray, this, opts ); // Add {name, value} of unchecked checkboxes if needed. 22 23 serializedObject = {}; 24 25 $.each( 26 formAsArray, 27 function( i, input ) { 28 i = null; 29 30 keys = f.splitInputNameIntoKeysArray( input.name, opts ); 31 type = keys.pop(); // The last element is always the type ('string' by default). 32 if ( 'skip' !== type ) { // Aasy way to skip a value. 33 value = f.parseValue( input.value, type, opts ); // String, number, boolean or null. 34 if ( opts.parseWithFunction && '_' === type ) { 35 value = opts.parseWithFunction( value, input.name ); 36 } // Allow for custom parsing. 37 38 f.deepSet( serializedObject, keys, value, opts ); 39 } 40 } 41 ); 42 43 return serializedObject; 44 }; 45 46 // Use $.serializeJSON as namespace for the auxiliar functions 47 // and to define defaults. 48 $.serializeJSON = { 49 50 defaultOptions: { 51 checkboxUncheckedValue: undefined, // To include that value for unchecked checkboxes (instead of ignoring them). 52 53 parseNumbers: false, // Convert values like '1', '-2.33' to 1, -2.33. 54 parseBooleans: false, // Convert 'true', 'false' to true, false. 55 parseNulls: false, // Convert 'null' to null. 56 parseAll: false, // All of the above. 57 parseWithFunction: null, // To use custom parser, a function like: function(val){ return parsed_val; }. 58 59 customTypes: {}, // Override defaultTypes. 60 defaultTypes: { 61 string: function( str ) { 62 return String( str ); 63 }, number: function( str ) { 64 return Number( str ); 65 }, boolean: function( str ) { 66 return (['false', 'null', 'undefined', '', '0'].indexOf( str ) === - 1 ); 67 }, null: function( str ) { 68 return (['false', 'null', 'undefined', '', '0'].indexOf( str ) !== - 1 ) ? null : str; 69 }, array: function( str ) { 70 return JSON.parse( str ); 71 }, object: function( str ) { 72 return JSON.parse( str ); 73 }, auto: function( str ) { 74 return $.serializeJSON.parseValue( 75 str, 76 null, 77 { 78 parseNumbers: true, 79 parseBooleans: true, 80 parseNulls: true 81 } 82 ); 83 } // Try again with something like "parseAll". 84 }, 85 86 useIntKeysAsArrayIndex: false // Name="foo[2]" value="v" => {foo: [null, null, "v"]}, instead of {foo: ["2": "v"]}. 87 }, 88 89 // Merge option defaults into the options. 90 setupOpts: function( options ) { 91 var opt, validOpts, defaultOptions, optWithDefault, parseAll, f; 92 f = $.serializeJSON; 93 94 if ( null === options || undefined === options ) { 95 options = {}; // Options ||= {}. 96 } 97 98 defaultOptions = f.defaultOptions || {}; // Default Options. 99 100 // Make sure that the user didn't misspell an option. 101 validOpts = [ 102 'checkboxUncheckedValue', 103 'parseNumbers', 104 'parseBooleans', 105 'parseNulls', 106 'parseAll', 107 'parseWithFunction', 108 'customTypes', 109 'defaultTypes', 110 'useIntKeysAsArrayIndex' 111 ]; // Re-define because the user may override the defaultOptions. 112 113 for ( opt in options ) { 114 if ( validOpts.indexOf( opt ) === - 1 ) { 115 throw new Error( 'serializeJSON ERROR: invalid option ' + opt + '. Please use one of ' + validOpts.join( ', ' ) ); 116 } 117 } 118 119 // Helper to get the default value for this option if none is specified by the user. 120 optWithDefault = function( key ) { 121 return ( false !== options[key] ) && ( '' !== options[key] ) && ( options[key] || defaultOptions[key] ); 122 }; 123 124 // Return computed options (opts to be used in the rest of the script). 125 parseAll = optWithDefault( 'parseAll' ); 126 return { 127 checkboxUncheckedValue: optWithDefault( 'checkboxUncheckedValue' ), 128 parseNumbers: parseAll || optWithDefault( 'parseNumbers' ), 129 parseBooleans: parseAll || optWithDefault( 'parseBooleans' ), 130 parseNulls: parseAll || optWithDefault( 'parseNulls' ), 131 parseWithFunction: optWithDefault( 'parseWithFunction' ), 132 typeFunctions: $.extend( {}, optWithDefault( 'defaultTypes' ), optWithDefault( 'customTypes' ) ), 133 useIntKeysAsArrayIndex: optWithDefault( 'useIntKeysAsArrayIndex' ) 134 }; 135 }, 136 137 // Given a string, apply the type or the relevant "parse" options, to return the parsed value. 138 parseValue: function( str, type, opts ) { 139 var typeFunction, f; 140 f = $.serializeJSON; 141 142 // Parse with a type if available. 143 typeFunction = opts.typeFunctions && opts.typeFunctions[type]; 144 if ( typeFunction ) { 145 return typeFunction( str ); // Use specific type. 146 } 147 148 // Otherwise, check if there is any auto-parse option enabled and use it. 149 if ( opts.parseNumbers && f.isNumeric( str ) ) { 150 return Number( str ); 151 } // Auto: number. 152 153 if ( opts.parseBooleans && ( 'true' === str || 'false' === str ) ) { 154 return 'true' === str; 155 } // Auto: boolean. 156 157 if ( opts.parseNulls && 'null' === str ) { 158 return null; 159 } // Auto: null. 160 161 // If none applies, just return the str. 162 return str; 163 }, 164 165 isObject: function( obj ) { 166 return obj === Object( obj ); 167 }, // Is this variable an object? 168 isUndefined: function( obj ) { 169 return obj === void 0; 170 }, // Safe check for undefined values. 171 isValidArrayIndex: function( val ) { 172 return /^[0-9]+$/.test( String( val ) ); 173 }, // 1,2,3,4 ... are valid array indexes 174 isNumeric: function( obj ) { 175 return obj - parseFloat( obj ) >= 0; 176 }, // Taken from jQuery.isNumeric implementation. Not using jQuery.isNumeric to support old jQuery and Zepto versions. 177 178 optionKeys: function( obj ) { 179 var keys; 180 var key; 181 182 if ( Object.keys ) { 183 return Object.keys( obj ); 184 } else { 185 keys = []; 186 for ( key in obj ) { 187 if ( obj.hasOwnProperty( key ) ) { 188 keys.push( key ); 189 } 190 } 191 192 return keys; 193 } 194 }, // Polyfill Object.keys to get option keys in IE<9. 195 196 // Split the input name in programatically readable keys. 197 // The last element is always the type (default "_"). 198 // Examples: 199 // "foo" => ['foo', '_'] 200 // "foo:string" => ['foo', 'string'] 201 // "foo:boolean" => ['foo', 'boolean'] 202 // "[foo]" => ['foo', '_'] 203 // "foo[inn][bar]" => ['foo', 'inn', 'bar', '_'] 204 // "foo[inn[bar]]" => ['foo', 'inn', 'bar', '_'] 205 // "foo[inn][arr][0]" => ['foo', 'inn', 'arr', '0', '_'] 206 // "arr[][val]" => ['arr', '', 'val', '_'] 207 // "arr[][val]:null" => ['arr', '', 'val', 'null'] . 208 splitInputNameIntoKeysArray: function( name, opts ) { 209 var keys, nameWithoutType, type, _ref, f; 210 211 f = $.serializeJSON; 212 _ref = f.extractTypeFromInputName( name, opts ); 213 nameWithoutType = _ref[0]; 214 type = _ref[1]; 215 216 keys = nameWithoutType.split( '[' ); // Split string into array. 217 keys = $.map( 218 keys, 219 function( key ) { 220 return key.replace( /]/g, '' ); 221 } 222 ); // Remove closing brackets. 223 224 if ( '' === keys[0] ) { 225 keys.shift(); 226 } // Ensure no opening bracket ("[foo][inn]" should be same as "foo[inn]"). 227 228 keys.push( type ); // Add type at the end. 229 return keys; 230 }, 231 232 // Returns [name-without-type, type] from name. 233 // "foo" => ["foo", '_'] 234 // "foo:boolean" => ["foo", 'boolean'] 235 // "foo[bar]:null" => ["foo[bar]", 'null']. 236 extractTypeFromInputName: function( name, opts ) { 237 var match, validTypes, f; 238 239 match = name.match( /(.*):([^:]+)$/ ); 240 241 if ( match ) { 242 f = $.serializeJSON; 243 244 validTypes = f.optionKeys( opts ? opts.typeFunctions : f.defaultOptions.defaultTypes ); 245 validTypes.push( 'skip' ); // Skip is a special type that makes it easy to remove. 246 if ( validTypes.indexOf( match[2] ) !== - 1 ) { 247 return [match[1], match[2]]; 248 } else { 249 throw new Error( 'serializeJSON ERROR: Invalid type ' + match[2] + ' found in input name "' + name + '", please use one of ' + validTypes.join( ', ' ) ); 250 } 251 } else { 252 return [name, '_']; // No defined type, then use parse options. 253 } 254 }, 255 256 // Set a value in an object or array, using multiple keys to set in a nested object or array: 257 // jscs:disable requireCapitalizedComments 258 // 259 // deepSet(obj, ['foo'], v) // obj['foo'] = v 260 // deepSet(obj, ['foo', 'inn'], v) // obj['foo']['inn'] = v // Create the inner obj['foo'] object, if needed 261 // deepSet(obj, ['foo', 'inn', '123'], v) // obj['foo']['arr']['123'] = v // 262 // 263 // deepSet(obj, ['0'], v) // obj['0'] = v 264 // deepSet(arr, ['0'], v, {useIntKeysAsArrayIndex: true}) // arr[0] = v 265 // deepSet(arr, [''], v) // arr.push(v) 266 // deepSet(obj, ['arr', ''], v) // obj['arr'].push(v) 267 // 268 // arr = []; 269 // deepSet(arr, ['', v] // arr => [v] 270 // deepSet(arr, ['', 'foo'], v) // arr => [v, {foo: v}] 271 // deepSet(arr, ['', 'bar'], v) // arr => [v, {foo: v, bar: v}] 272 // deepSet(arr, ['', 'bar'], v) // arr => [v, {foo: v, bar: v}, {bar: v}]. 273 deepSet: function( o, keys, value, opts ) { 274 var key, nextKey, tail, lastIdx, lastVal, f; 275 if ( null === opts ) { 276 opts = {}; 277 } 278 279 f = $.serializeJSON; 280 281 if ( f.isUndefined( o ) ) { 282 throw new Error( 'ArgumentError: param \'o\' expected to be an object or array, found undefined' ); 283 } 284 if ( ! keys || 0 === keys.length ) { 285 throw new Error( 'ArgumentError: param \'keys\' expected to be an array with least one element' ); 286 } 287 288 key = keys[0]; 289 290 // Only one key, then it's not a deepSet, just assign the value. 291 if ( 1 === keys.length ) { 292 if ( '' === key ) { 293 o.push( value ); // '' is used to push values into the array (assume o is an array) 294 } else { 295 o[key] = value; // Other keys can be used as object keys or array indexes. 296 } 297 298 // With more keys is a deepSet. Apply recursively. 299 } else { 300 nextKey = keys[1]; 301 302 // '' is used to push values into the array, 303 // with nextKey, set the value into the same object, in object[nextKey]. 304 // Covers the case of ['', 'foo'] and ['', 'var'] to push the object {foo, var}, and the case of nested arrays. 305 if ( '' === key ) { 306 lastIdx = o.length - 1; // asume o is array. 307 lastVal = o[lastIdx]; 308 if ( f.isObject( lastVal ) && ( f.isUndefined( lastVal[nextKey] ) || keys.length > 2 ) ) { // If nextKey is not present in the last object element, or there are more keys to deep set. 309 key = lastIdx; // then set the new value in the same object element. 310 } else { 311 key = lastIdx + 1; // otherwise, point to set the next index in the array. 312 } 313 } 314 315 // '' is used to push values into the array "array[]" 316 if ( '' === nextKey ) { 317 if ( f.isUndefined( o[key] ) || ! $.isArray( o[key] ) ) { 318 o[key] = []; // define (or override) as array to push values. 319 } 320 } else { 321 if ( opts.useIntKeysAsArrayIndex && f.isValidArrayIndex( nextKey ) ) { // if 1, 2, 3 ... then use an array, where nextKey is the index. 322 if ( f.isUndefined( o[key] ) || ! $.isArray( o[key] ) ) { 323 o[key] = []; // define (or override) as array, to insert values using int keys as array indexes. 324 } 325 } else { // for anything else, use an object, where nextKey is going to be the attribute name. 326 if ( f.isUndefined( o[key] ) || ! f.isObject( o[key] ) ) { 327 o[key] = {}; // define (or override) as object, to set nested properties. 328 } 329 } 330 } 331 332 // Recursively set the inner object. 333 tail = keys.slice( 1 ); 334 f.deepSet( o[key], tail, value, opts ); 335 } 336 }, 337 338 // Fill the formAsArray object with values for the unchecked checkbox inputs, 339 // using the same format as the jquery.serializeArray function. 340 // The value of the unchecked values is determined from the opts.checkboxUncheckedValue 341 // and/or the data-unchecked-value attribute of the inputs. 342 readCheckboxUncheckedValues: function( formAsArray, $form, opts ) { 343 var selector, $uncheckedCheckboxes, $el, dataUncheckedValue, f; 344 if ( null === opts ) { 345 opts = {}; 346 } 347 348 f = $.serializeJSON; 349 350 selector = 'input[type=checkbox][name]:not(:checked):not([disabled])'; 351 $uncheckedCheckboxes = $form.find( selector ).add( $form.filter( selector ) ); 352 $uncheckedCheckboxes.each( 353 function( i, el ) { 354 i = null; 355 $el = $( el ); 356 dataUncheckedValue = $el.attr( 'data-unchecked-value' ); 357 if ( dataUncheckedValue ) { // data-unchecked-value has precedence over option opts.checkboxUncheckedValue. 358 formAsArray.push( { name: el.name, value: dataUncheckedValue } ); 359 } else { 360 if ( ! f.isUndefined( opts.checkboxUncheckedValue ) ) { 361 formAsArray.push( { name: el.name, value: opts.checkboxUncheckedValue } ); 362 } 363 } 364 } 365 ); 366 } 367 368 }; 369 370 }( window.jQuery || window.$ ) ); 371 372 (function( $ ) { 373 'use strict'; 374 375 redux.customizer = redux.customizer || {}; 376 377 $( document ).ready( 378 function() { 379 redux.customizer.init(); 380 } 381 ); 382 383 redux.customizer.init = function() { 384 var reduxChange; 385 var redux_initFields; 386 387 $( 'body' ).addClass( redux.customizer.body_class ); 388 $( '.accordion-section.redux-section, .accordion-section.redux-panel, .accordion-section-title' ).on( 389 'click', 390 function() { 391 $.redux.initFields(); 392 } 393 ); 394 395 if ( undefined === redux.optName ) { 396 console.log( 'Redux customizer extension failure' ); 397 return; 398 } 399 400 redux.optName.args.disable_save_warn = true; 401 reduxChange = redux_change; 402 redux_change = function( variable ) { 403 variable = $( variable ); 404 reduxChange.apply( this, arguments ); 405 redux.customizer.save( variable ); 406 }; 407 408 redux_initFields = $.redux.initFields; 409 410 $.redux.initFiles = function() { 411 redux_initFields(); 412 }; 413 }; 414 415 redux.customizer.save = function( $obj ) { 416 var $parent = $obj.hasClass( 'redux-field' ) ? $obj : $obj.parents( '.redux-field-container:first' ); 417 redux.customizer.inputSave( $parent ); 418 }; 419 420 redux.customizer.inputSave = function( $parent ) { 421 var $id; 422 var $nData; 423 var $key; 424 var $control; 425 426 if ( ! $parent.hasClass( 'redux-field-container' ) ) { 427 $parent = $parent.parents( '[class^="redux-field-container"]' ); 428 } 429 430 $id = $parent.parent().find( '.redux-customizer-input' ).data( 'id' ); 431 432 if ( ! $id ) { 433 $parent = $parent.parents( '.redux-container-repeater:first' ); 434 $id = $parent.parent().find( '.redux-customizer-input' ).data( 'id' ); 435 } 436 437 // var $nData = $parent.serializeJSON(); . 438 $nData = $parent.find( ':input' ).serializeJSON(); 439 440 $.each( 441 $nData, 442 function( $k, $v ) { 443 $k = null; 444 $nData = $v; 445 } 446 ); 447 448 $key = $parent.parent().find( '.redux-customizer-input' ).data( 'key' ); 449 if ( $nData[$key] ) { 450 $nData = $nData[$key]; 451 } 452 453 $control = wp.customize.control( $id ); 454 455 // Customizer hack since they didn't code it to save order... 456 if ( JSON.stringify( $control.setting._value ) !== JSON.stringify( $nData ) ) { 457 $control.setting._value = null; 458 } 459 460 $control.setting.set( $nData ); 461 }; 462 })( jQuery );