1/** 2 * jQuery Form Repeater Plugin 0.1.0 3 * 4 * Copyright (c) 2011 Corey Ballou 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be 15 * included in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 * 25 * Example Usage: 26 * 27 <div class="container"> 28 <div class="r-group"> 29 <p> 30 <label for="vehicle_0_0" data-pattern-text="Vehicle Name +=:">Vehicle Name 1:</label> 31 <input type="text" name="vehicle[0][name]" id="vehicle_0_name" data-pattern-name="vehicle[++][name]" data-pattern-id="vehicle_++_name" /> 32 </p> 33 <p> 34 <label for="vehicle_0_0" data-pattern-text="Vehicle Type +=:">Vehicle Type 1:</label> 35 <input type="text" name="vehicle[0][type]" id="vehicle_0_type" data-pattern-name="vehicle[++][type]" data-pattern-id="vehicle_++_type" /> 36 </p> 37 <p> 38 <-- Add a remove button for the item. If one didn't exist, it would be added to overall group --> 39 <button type="button" class="r-btnRemove">Remove -</button> 40 </p> 41 </div> 42 <button type="button" class="r-btnAdd">Add +</button> 43 </div> 44 <script type="text/javascript"> 45 $('.container').repeater({ 46 btnAddClass: 'r-btnAdd', 47 btnRemoveClass: 'r-btnRemove', 48 groupClass: 'r-group', 49 minItems: 1, 50 maxItems: 0, 51 startingIndex: 0, 52 reindexOnDelete: true, 53 repeatMode: 'append', 54 animation: null, 55 animationSpeed: 400, 56 animationEasing: 'swing', 57 clearValues: true 58}); 59 </script> 60 */ 61(function($) { 62 63 $.fn.repeater = function(options, data) { 64 var $container = $(this), 65 $btnAdd, $btnRemove, patternName, patternId, patternText, 66 idVal, nameVal, labelText, labelFor, $elem, elemName, 67 $label, row, $newClone, $formElems; 68 69 $container.opts = $.extend({}, $.fn.repeater.defaults, options); 70 $container.repeatCount = 0; 71 72 $btnAdd = $container.find('.' + $container.opts.btnAddClass); 73 if (!$btnAdd.length) { 74 alert('You must specify a valid jQuery selector for the add button option in Form Repeater.'); 75 return false; 76 } 77 78 // parse out group details 79 $container.group = $('.' + $container.opts.groupClass); 80 if (!$container.group.length) { 81 alert('You must specify a valid jQuery selector for the form element grouping option in Form Repeater.'); 82 return false; 83 } 84 85 // ensure the remove button exists 86 $btnRemove = $container.group.find('.' + $container.opts.btnRemoveClass); 87 if (!$btnRemove.length) { 88 $btnRemove = $('<button type="button" name="rBtnRemove" class="' + $container.opts.btnRemoveClass + '" style="display:none" />') 89 $btnRemove.appendTo($container); 90 } else { 91 // default hidden 92 $btnRemove.hide(); 93 } 94 95 // narrow the group down to the first copy 96 $container.group = $container.group.eq(0); 97 // retrieve form elements 98 $container.groupClone = $container.group.clone(); 99 // watch for remove 100 //$container.find('.' + $container.opts.btnRemoveClass).live('click', removeRepeater); 101 $container.delegate('.' + $container.opts.btnRemoveClass, 'click', $container, removeRepeater); 102 // watch for add 103 //$container.find('.' + $container.opts.btnAddClass).live('click', addRepeater); 104 $container.delegate('.' + $container.opts.btnAddClass, 'click', $container, addRepeater); 105 106 // allows for initial population of form data 107 if (data && data.length) { 108 109 // create grouping for every row of data 110 for (row in data) { 111 112 // keep cloning 113 $newClone = $container.groupClone.clone(); 114 115 if ($.isFunction($container.opts.beforeAdd)) { 116 $newClone = $container.opts.beforeAdd.call(this, $newClone); 117 } 118 119 $formElems = $newClone.find(':input'); 120 if ($formElems.length) { 121 122 // populate each input field 123 $formElems.each(function() { 124 $elem = $(this), 125 elemName = $elem.data('name'); 126 127 // check for matching value 128 if (typeof data[row][elemName] != 'undefined') { 129 $elem.val(data[row][elemName]); 130 } else { 131 $elem.val(''); 132 } 133 134 patternName = $elem.data('pattern-name'); 135 if (patternName) { 136 nameVal = $elem.attr('name'); 137 nameVal = parsePattern(patternName, nameVal, row, $container); 138 $elem.attr('name', nameVal); 139 } 140 141 patternId = $elem.data('pattern-id'); 142 if (patternId) { 143 idVal = $elem.attr('id'); 144 idVal = parsePattern(patternId, idVal, row, $container); 145 $elem.attr('id', idVal); 146 } 147 148 $label = $newClone.find('label[for=' + $elem.attr('id') + ']'); 149 if (!$label.length) $label = $elem.parent('label'); 150 if (!$label.length) $label = $elem.siblings('label'); 151 if ($label.length) { 152 // ensure we have one copy 153 $label = $label.eq(0); 154 // update label text 155 patternText = $label.data('pattern-text'); 156 labelText = $label.html(); 157 if (labelText) { 158 labelText = parsePattern(patternText, labelText, row, $container); 159 $label.html(labelText); 160 } 161 // update label attribute 162 labelFor = $label.attr('for'); 163 if (labelFor && idVal) { 164 $label.attr('for', idVal); 165 } 166 } 167 }); 168 169 } 170 171 // append new clone to container 172 $newClone.insertAfter($('.' + $container.opts.groupClass).last(
172)); 173 174 if ($container.group) { 175 $container.group.remove(); 176 $container.group = null; 177 } 178 179 if ($.isFunction($container.opts.afterAdd)) { 180 $container.opts.afterAdd.call(this, $newClone); 181 } 182 183 } 184 185 // show removal buttons 186 $('.' + $container.opts.groupClass + ' .' + $container.opts.btnRemoveClass).show(); 187 188 } 189 190 // daisy chain 191 return this; 192 } 193 194 /** 195 * Add a new repeater. 196 */ 197 function addRepeater(data) { 198 var container = data.data, 199 tmpCount = container.repeatCount + 1, 200 $doppleganger = container.groupClone.clone(); 201 202 if ($.isFunction(container.opts.beforeAdd)) { 203 $doppleganger = container.opts.beforeAdd.call(this, $doppleganger); 204 } 205 206 // don't exceed the max allowable items 207 if (container.opts.maxItems > 0 && container.repeatCount == container.opts.maxItems) { 208 alert('You have hit the maximum allowable items.'); 209 return false; 210 } 211 212 _reindex($doppleganger, tmpCount, container); 213 214 // ensure remove button is showing 215 $doppleganger.find('.' + container.opts.btnRemoveClass).show(); 216 217 // append repeater to container 218 if (container.opts.repeatMode == 'append') { 219 $doppleganger.appendTo(container); 220 } else if (container.opts.repeatMode == 'prepend') { 221 $doppleganger.prependTo(container); 222 } else if (container.opts.repeatMode == 'insertAfterLast') { 223 $doppleganger.insertAfter(container.find('.' + container.opts.groupClass).last()); 224 } 225 226 container.repeatCount++; 227 228 if ($.isFunction(container.opts.afterAdd)) { 229 container.opts.afterAdd.call(this, $doppleganger); 230 } 231 232 return false; 233 } 234 235 /** 236 * Remove a repeater. 237 */ 238 function removeRepeater(data) { 239 var $btn = $(this), 240 container = data.data, 241 $repeaters = container.find('.' + container.opts.groupClass); 242 numRepeaters = $repeaters.length, 243 $match; 244 245 if (numRepeaters > container.opts.minItems) { 246 247 // check if removing a specific repeater instance 248 $match = $btn.closest('.' + container.opts.groupClass); 249 if (!$match.length) { 250 // determine if removing first or last repeater 251 if (container.opts.repeatMode == 'append') { 252 var $match = $repeaters.filter(':last'); 253 } else if (container.opts.repeatMode == 'prepend') { 254 var $match = $repeaters.filter(':first'); 255 } else if (container.opts.repeatMode == 'insertAfterLast') { 256 var $match = $repeaters.filter(':last'); 257 } 258 } 259 260 // ensure we have a match 261 if ($match.length) { 262 // remove the repeater 263 if (container.opts.animation) { 264 if (container.opts.animation == 'slide') { 265 $match.slideUp(container.opts.animationSpeed, container.opts.animationEasing, function() { 266 _remove($match, container); 267 }); 268 } else if (container.opts.animation == 'fade') { 269 $match.fadeOut(container.opts.animationSpeed, container.opts.animationEasing, function() { 270 _remove($match, container); 271 }); 272 } else if (typeof container.opts.animation == 'object') { 273 $match.animate(container.opts.animation, container.opts.animationSpeed, container.opts.animationEasing, function() { 274 _remove($match, container); 275 }); 276 } 277 } else { 278 _remove($match, container); 279 } 280 281 } 282 283 } 284 285 return false; 286 } 287 288 /** 289 * Parse the pattern. 290 */ 291 function parsePattern(pattern, replaceText, count, container) { 292 var returnVal = replaceText; 293 294 count = parseInt(count); 295 if (pattern) { 296 // check pattern type 297 if (pattern.indexOf('+=') > -1) { 298 var matches = pattern.match(/\+=(\d+)/i); 299 if (matches && matches.length && matches[1]) { 300 var incr = parseInt(matches[1]); 301 returnVal = pattern.replace(/\+=(\d)+/i, container.opts.startingIndex + count + incr); 302 } 303 } 304 305 if (pattern.indexOf('++') > -1) { 306 returnVal = pattern.replace(/\+\+/gi, container.opts.startingIndex + count); 307 } 308 } 309 return returnVal; 310 } 311 312 /** 313 * Wrapper to handle re-indexing form elements in a group. 314 */ 315 function reindex(container) { 316 var $repeaters = container.find('.' + container.opts.groupClass), 317 startIndex = container.opts.startingIndex, 318 $curGroup; 319 320 $repeaters.each(function() { 321 $curGroup = $(this); 322 _reindex($curGroup, startIndex, container); 323 startIndex++; 324 }); 325 } 326 327 /** 328 * Remove a match and reindex. 329 */ 330 function _remove($match, container) { 331 $match.remove(); 332 if (container.repeatCount) { 333 container.repeatCount--; 334 } 335 336 reindex(container); 337 338 if ($.isFunction(container.opts.afterDelete)) { 339 container.opts.afterDelete.call(this, $match); 340 } 341 } 342 343 /** 344 * Handle reindexing each form element in a group. 345 */ 346 function _reindex($curGroup, index, container) { 347 var $formElems = $curGroup.find(':input'), 348 patternName, patternId, patternText, 349 idVal, nameVal, labelText, labelFor, 350 $elem; 351 352 if ($formElems.length) { 353 $formElems.each(function() { 354 $elem = $(this); 355 356 patternName = $elem.data('pattern-name'); 357 if (patternName) { 358 nameVal = $elem.attr('name'); 359 nameVal = parsePattern(patternName, nameVal, index, container); 360 $elem.attr('name', nameVal); 361 } 362 363 patternId = $elem.data('pattern-id'); 364 if (patternId) { 365 idVal = $elem.attr('id'); 366 idVal = parsePattern(patternId, idVal, index, container); 367 $elem.attr('id', idVal); 368 } 369 370 $label = $curGroup.find('label[for=' + $elem.attr('id') + ']'); 371 if (!$label.length) $label = $elem.parent('label'); 372 if (!$label.length) $label = $elem.siblings('label'); 373 if ($label.length) { 374 // ensure we have one copy 375 $label = $label.eq(0); 376 // update label text 377 patternText = $label.data('pattern-text'); 378 labelText = $label.html(); 379 if (labelText) { 380 labelText = parsePattern(patternText, labelText, index, container); 381 $label.html(labelText); 382 } 383 // update label attribute 384 labelFor = $label.attr('for'); 385 if (labelFor && idVal) { 386 $label.attr('for', idVal); 387 } 388 } 389 }); 390 } 391 392 return $curGroup; 393 } 394 395})(jQuery); 396 397// default values 398jQuery.fn.repeater.defaults = { 399 groupClass: 'r-group', 400 btnAddClass: 'r-btnAdd', 401 btnRemoveClass: 'r-btnRemove', 402 minItems: 1, 403 maxItems: 0, 404 startingIndex: 0, 405 reindexOnDelete: true, 406 repeatMode: 'insertAfterLast', // append, prepend, insertAfterLast 407 animation: null, 408 animationSpeed: 400, 409 animationEasing: 'swing', 410 clearValues: true, 411 beforeAdd: function($doppleganger) { return $doppleganger; }, 412 afterAdd: function($doppleganger) { }, 413 beforeDelete: function() { }, 414 afterDelete: function() { } 415};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.