clone.js (7725B)
1 ( function ( $, rwmb ) { 2 'use strict'; 3 4 // Object holds all methods related to fields' index when clone 5 var cloneIndex = { 6 /** 7 * Set index for fields in a .rwmb-clone 8 * @param $inputs .rwmb-clone element 9 * @param index Index value 10 */ 11 set: function ( $inputs, index ) { 12 $inputs.each( function () { 13 var $field = $( this ); 14 15 // Name attribute 16 var name = this.name; 17 if ( name && ! $field.closest( '.rwmb-group-clone' ).length ) { 18 $field.attr( 'name', cloneIndex.replace( index, name, '[', ']', false ) ); 19 } 20 21 // ID attribute 22 var id = this.id; 23 if ( id ) { 24 $field.attr( 'id', cloneIndex.replace( index, id, '_', '', true, true ) ); 25 } 26 27 $field.trigger( 'update_index', index ); 28 } ); 29 }, 30 31 /** 32 * Replace an attribute of a field with updated index 33 * @param index New index value 34 * @param value Attribute value 35 * @param before String before returned value 36 * @param after String after returned value 37 * @param alternative Check if attribute does not contain any integer, will reset the attribute? 38 * @param isEnd Check if we find string at the end? 39 * @return string 40 */ 41 replace: function ( index, value, before, after, alternative, isEnd ) { 42 before = before || ''; 43 after = after || ''; 44 45 if ( typeof alternative === 'undefined' ) { 46 alternative = true; 47 } 48 49 var end = isEnd ? '$' : ''; 50 51 var regex = new RegExp( cloneIndex.escapeRegex( before ) + '(\\d+)' + cloneIndex.escapeRegex( after ) + end ), 52 newValue = before + index + after; 53 54 return regex.test( value ) ? value.replace( regex, newValue ) : (alternative ? value + newValue : value ); 55 }, 56 57 /** 58 * Helper function to escape string in regular expression 59 * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions 60 * @param string 61 * @return string 62 */ 63 escapeRegex: function ( string ) { 64 return string.replace( /[.*+?^${}()|[\]\\]/g, "\\$&" ); 65 }, 66 67 /** 68 * Helper function to create next index for clones 69 * @param $container .rwmb-input container 70 * @return integer 71 */ 72 nextIndex: function ( $container ) { 73 var nextIndex = $container.data( 'next-index' ); 74 $container.data( 'next-index', nextIndex + 1 ); 75 return nextIndex; 76 } 77 }; 78 79 // Object holds all method related to fields' value when clone. 80 var cloneValue = { 81 setDefault: function() { 82 var $field = $( this ); 83 84 if ( true !== $field.data( 'clone-default' ) ) { 85 return; 86 } 87 88 var type = $field.attr( 'type' ), 89 defaultValue = $field.data( 'default' ); 90 91 if ( 'radio' === type ) { 92 $field.prop( 'checked', $field.val() === defaultValue ); 93 } else if ( $field.hasClass( 'rwmb-checkbox' ) || $field.hasClass( 'rwmb-switch' ) ) { 94 $field.prop( 'checked', !! defaultValue ); 95 } else if ( $field.hasClass( 'rwmb-checkbox_list' ) ) { 96 var value = $field.val(); 97 $field.prop( 'checked', Array.isArray( defaultValue ) ? -1 !== defaultValue.indexOf( value ) : value == defaultValue ); 98 } else if ( 'select' === type ) { 99 $field.find( 'option[value="' + defaultValue + '"]' ).prop( 'selected', true ); 100 } else if ( ! $field.hasClass( 'rwmb-hidden' ) ) { 101 $field.val( defaultValue ); 102 } 103 }, 104 clear: function() { 105 var $field = $( this ), 106 type = $field.attr( 'type' ); 107 108 if ( 'radio' === type || 'checkbox' === type ) { 109 $field.prop( 'checked', false ); 110 } else if ( 'select' === type ) { 111 $field.prop( 'selectedIndex', - 1 ); 112 } else if ( ! $field.hasClass( 'rwmb-hidden' ) ) { 113 $field.val( '' ); 114 } 115 } 116 }; 117 118 /** 119 * Clone fields 120 * @param $container A div container which has all fields 121 */ 122 function clone( $container ) { 123 var $last = $container.children( '.rwmb-clone' ).last(), 124 $clone = $last.clone(), 125 nextIndex = cloneIndex.nextIndex( $container ); 126 127 // Clear fields' values. 128 var $inputs = $clone.find( rwmb.inputSelectors ); 129 $inputs.each( cloneValue.clear ); 130 131 // Insert clone. 132 $clone.insertAfter( $last ); 133 134 // Trigger custom event for the clone instance. Required for Group extension to update sub fields. 135 $clone.trigger( 'clone_instance', nextIndex ); 136 137 // Set fields index. Must run before trigger clone event. 138 cloneIndex.set( $inputs, nextIndex ); 139 140 // Set fields' default values: do after index is set to prevent previous radio fields from unchecking. 141 $inputs.each( cloneValue.setDefault ); 142 143 // Trigger custom clone event. 144 $inputs.trigger( 'clone', nextIndex ); 145 146 // After cloning fields. 147 $inputs.trigger( 'after_clone', nextIndex ); 148 149 // Trigger custom change event for MB Blocks to update block attributes. 150 $inputs.first().trigger( 'mb_change' ); 151 } 152 153 /** 154 * Hide remove buttons when there's only 1 of them 155 * 156 * @param $container .rwmb-input container 157 */ 158 function toggleRemoveButtons( $container ) { 159 var $clones = $container.children( '.rwmb-clone' ); 160 $clones.children( '.remove-clone' ).toggle( $clones.length > 1 ); 161 162 // Recursive for nested groups. 163 $container.find( '.rwmb-input' ).each( function () { 164 toggleRemoveButtons( $( this ) ); 165 } ); 166 } 167 168 /** 169 * Toggle add button 170 * Used with [data-max-clone] attribute. When max clone is reached, the add button is hid and vice versa 171 * 172 * @param $container .rwmb-input container 173 */ 174 function toggleAddButton( $container ) { 175 var $button = $container.children( '.add-clone' ), 176 maxClone = parseInt( $container.data( 'max-clone' ) ), 177 numClone = $container.children( '.rwmb-clone' ).length; 178 179 $button.toggle( isNaN( maxClone ) || ( maxClone && numClone < maxClone ) ); 180 } 181 182 function addClone( e ) { 183 e.preventDefault(); 184 185 var $container = $( this ).closest( '.rwmb-input' ); 186 clone( $container ); 187 188 toggleRemoveButtons( $container ); 189 toggleAddButton( $container ); 190 sortClones.apply( $container[0] ); 191 } 192 193 function removeClone( e ) { 194 e.preventDefault(); 195 196 var $this = $( this ), 197 $container = $this.closest( '.rwmb-input' ); 198 199 // Remove clone only if there are 2 or more of them 200 if ( $container.children( '.rwmb-clone' ).length < 2 ) { 201 return; 202 } 203 204 $this.parent().trigger( 'remove' ).remove(); 205 toggleRemoveButtons( $container ); 206 toggleAddButton( $container ); 207 208 // Trigger custom change event for MB Blocks to update block attributes. 209 $container.find( rwmb.inputSelectors ).first().trigger( 'mb_change' ); 210 } 211 212 /** 213 * Sort clones. 214 * Expect this = .rwmb-input element. 215 */ 216 function sortClones() { 217 var $container = $( this ); 218 219 if ( undefined !== $container.sortable( 'instance' ) ) { 220 return; 221 } 222 if ( 0 === $container.children( '.rwmb-clone' ).length ) { 223 return; 224 } 225 226 $container.sortable( { 227 handle: '.rwmb-clone-icon', 228 placeholder: ' rwmb-clone rwmb-sortable-placeholder', 229 items: '> .rwmb-clone', 230 start: function ( event, ui ) { 231 // Make the placeholder has the same height as dragged item 232 ui.placeholder.height( ui.item.outerHeight() ); 233 }, 234 stop: function( event, ui ) { 235 ui.item.trigger( 'mb_init_editors' ); 236 ui.item.find( rwmb.inputSelectors ).first().trigger( 'mb_change' ); 237 } 238 } ); 239 } 240 241 function start() { 242 var $container = $( this ); 243 toggleRemoveButtons( $container ); 244 toggleAddButton( $container ); 245 246 $container.data( 'next-index', $container.children( '.rwmb-clone' ).length ); 247 sortClones.apply( this ); 248 } 249 250 function init( e ) { 251 $( e.target ).find( '.rwmb-input' ).each( start ); 252 } 253 254 rwmb.$document 255 .on( 'mb_ready', init ) 256 .on( 'click', '.add-clone', addClone ) 257 .on( 'click', '.remove-clone', removeClone ); 258 259 // Export for use outside. 260 rwmb.cloneIndex = cloneIndex; 261 rwmb.cloneValue = cloneValue; 262 rwmb.sortClones = sortClones; 263 rwmb.toggleRemoveButtons = toggleRemoveButtons; 264 rwmb.toggleAddButton = toggleAddButton; 265 } )( jQuery, rwmb );