Source: js/forms/fields_collection/fields_collection.js

/*
# ***** BEGIN LICENSE BLOCK *****
# Assets Library - The open source PHP/JavaScript/CSS library of Les Ateliers Pierrot
# Copyleft (c) 2013-2014 Pierre Cassat and contributors
# <www.ateliers-pierrot.fr> - <contact@ateliers-pierrot.fr>
# License GPL-3.0 <http://www.opensource.org/licenses/gpl-3.0.html>
# Sources <http://github.com/atelierspierrot/assets-library>
#
# Ce programme est un logiciel libre distribué sous licence GNU/GPL.
#
# ***** END LICENSE BLOCK ***** */


/*****************************************/

// FORM FIELDS COLLECTION
// based on <http://symfony.com/doc/2.0/reference/forms/types/collection.html>

/******************************************/

// Keep traces of all collection counters by id
var COLLECTION_COUNTERS={};

// Keep traces of all collection models by id
var COLLECTION_MODELS={};

/**
 * Add a new item in a collection
 *
 * The model HTML string needs to be escaped and HTML tags need to be entities. The keywords "$$counter$$" and "$$value$$" will
 * be replaced by their corresponding content if so.
 * For example :
 *
 *     <label for="myFieldName_$$counter$$">My field $$counter$$</label><input type="text" id="myFieldName_$$counter$$" name="myFieldName[$$counter$$]" value="" />&nbsp;[<a href="javascript:remove_collection_field( 'my-fields-list-$$counter$$' );" title="Remove this email field">-</a>]
 *
 * To set an array of values (if there is an array of fields), you have to inform the function of each value name (and default value if so),
 * as follow :
 *
 *     var values = {
 *         first_field_name: 'first_field_default_value',
 *         second_field_name: '', // this will set a blank default value but the field name declaration is required
 *         ...
 *     };
 *     add_collection_field( 'fields-list-id', 'tag_name', null, null, values );
 *
 * Basic usage :
 *
 *     <ul id="my-fields-list">
 *     </ul>
 *     [<a href="javascript:add_collection_field( 'my-fields-list', 'li', '0', '<label for="myFieldName_$$counter$$">My field $$counter$$</label><input type="text" id="myFieldName_$$counter$$" name="myFieldName[$$counter$$]" value="" />&nbsp;[<a href="javascript:remove_collection_field( 'my-fields-list-$$counter$$' );" title="Remove this email field">-</a>]', 'first value' );" title="Add a new field">+</a>]
 *
 * HTML5 usage :
 *
 *     <ul id="my-fields-list"
 *         data-prototype="<label for="myFieldName_$$counter$$">My field $$counter$$</label><input type="text" id="myFieldName_$$counter$$" name="myFieldName[$$counter$$]" value="" />&nbsp;[<a href="javascript:remove_collection_field( 'my-fields-list-$$counter$$' );" title="Remove this email field">-</a>]"
 *         data-counter="0">
 *     </ul>
 *     [<a href="javascript:add_collection_field( 'my-fields-list', 'li' );" title="Add a new field">+</a>]
 *
 * @param string id The ID string of the collection holder (parent) | required
 * @param tag_name child_type The name of the tag to create in the parent for the new item | required
 * @param num counter The counter where to begin the collection count | optional, if not set, the function will try to get the
 *                    "data-counter" attribute of the parent, and by default, will count the parent's children entries
 * @param string field_model The HTML string to put in the collection item | optional, if not set, the function will try to get
 *                    the "data-prototype" attribute of the parent
 * @param string _value The value to insert in the new collection item if so | optional, default is ''
 */
function add_collection_field( id, child_type, counter, field_model, _value )
{
    if (typeof this.window['_dbg_info'] == 'function')
        _dbg_info('[add_collection_field()] Adding a field for id=['+id+']'
            +"\n"+' with counter=['+(counter || COLLECTION_COUNTERS[id])+']'
            +"\n"+' with model=['+(field_model || COLLECTION_MODELS[id])+']'
            +"\n"+' with child_type=['+child_type+']'
            +"\n"+' with _value=['+_value+']');
    var _parent = document.getElementById( id );
    if (_parent)
    {
        // make sure we have a count
        var count = COLLECTION_COUNTERS[id] || counter ||
            _parent.getAttribute('data-counter') || _parent.getElementsByTagName( child_type ).length+1;
        if (COLLECTION_COUNTERS[id]==undefined)
            COLLECTION_COUNTERS[id] = count;
        if (typeof this.window['_dbg'] == 'function')
            _dbg('getting counter : ['+count+']');

        // make sure we have a model
        var mod = field_model || COLLECTION_MODELS[id] || _parent.getAttribute('data-prototype');
        if (mod==undefined)
        {
            throw new Error('No model set and no data-prototype attribute found!');
            return;
        }
        if (COLLECTION_MODELS[id]==undefined)
            COLLECTION_MODELS[id] = mod;
        if (typeof this.window['_dbg'] == 'function')
            _dbg('getting field model : ['+mod+']');

        // create our new node
        var _new_id = id+'-'+count;
        var val = _value || '';
        if (typeof this.window['_dbg'] == 'function')
            _dbg('getting value : ['+val+']');

        var new_child = document.createElement( child_type );
        if (new_child)
        {
            new_child.id = _new_id;
            mod = mod.replace(/\$\$counter\$\$/g, count);
            if (typeof(_value) == 'object') {
                for(var key in _value) {
                    _patrn = new RegExp('\\$\\$value\\['+key+'\\]\\$\\$', 'ig');
                    _dbg('pattern is : ['+_patrn+']');
                    mod = mod.replace(_patrn, _value[key]);
                }
            } else {
                _patrn = new RegExp('\\$\\$value\\$\\$', 'ig');
                _dbg('pattern is : ['+_patrn+']');
                mod = mod.replace(_patrn, val);
            }
            if (typeof this.window['_dbg'] == 'function')
                _dbg('new model is : ['+mod+']');
            new_child.innerHTML = mod;
            _parent.appendChild( new_child );
            COLLECTION_COUNTERS[id]++;
        }
    }
}

/**
 * Remove an item of a collection by its ID
 *
 * @param string id The ID string of the collection item to remove | required
 */
function remove_collection_field( id )
{
    if (typeof this.window['_dbg_info'] == 'function')
        _dbg_info('[remove_collection_field] removing a field for id['+id+']');
    var _node = document.getElementById( id );
    if (_node)
    {
        _node.parentNode.removeChild( _node );
    }
}

// Endfile