Script JQuery pour gérer les embbed forms sous Symfony2

Bonjour,

Dans cet article je vais simplement partager avec vous mon petit script JS pour pouvoir rapidement utiliser les embbed forms de Symfony2. En effet, Symfony2 n’est plus du tout magique et choisi de nous laisser nous dépatouiller avec les embbed forms côté client (et côté serveur, mais c’est un autre sujet que j’aborderais peut-être dans un prochain article).

Pour l’utiliser :
gestion d’ajout/suppression :

  • définissez la classe with_default pour créer un objet vide si aucun n’existe.
  • définissez la classe with_empty pour créer un objet vide quelque soit le cas
  • définissez data-number-to-add pour définir le nombre d’objet vide à ajouter lors du clique sur le bouton d’ajout.
'class' => 'with_default',
'data-number-to-add' => 1

option uniqify (utile par exemple pour cocher une propriété « image principale » sur une gallerie) : définissez la classe uniqify ainsi qu’un data-uniqify-id pour n’autoriser qu’un seul élément à être coché à travers plusieurs formulaire embarqués :

'class' => 'uniqify',
'data-uniqify-id' => 'galleryFormIsMain',

Bref, ne vous laissons pas attendre plus longtemps, voici le script :

// Init vars
addingIndexes = new Array();

$(document).ready(function() {
	
	// Handle uniqifying
	$('input.uniqify').on('click', function(event) {
		console.log('uniqify : ' + $(this).data('uniqify-id'));
		$("input[data-uniqify-id='" + $(this).data('uniqify-id') + "']").prop('checked', false);
		$(this).prop('checked', true);
	});

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Embed forms fonctions
	
	// Initialisation
	/**
	 * This will always add empty object if the embedded form type has class "with_empty"
	 * This will add empty object only if no object exists if the embedded form type has class "with_default"
	 * Number of added object is defined in data-number-to-add attribute of the embedded form type
	 */
	$('div').find('*[data-prototype]').each(function(_id) {
		// init vars
		existingObjects = false;
		// adding remove button for existing embedded forms items
		$(this).children().each(function(_id) {
			$(this).append(createRemoveButton());
			existingObjects = true;
		});
		// creating default empty object for each embedded forms
		if ($(this).hasClass('with_empty') || $(this).hasClass('with_default') && !existingObjects) {
			addObject($(this).attr('id'));
		}
		// creating add button
		$(this).parent().append(createAddButton($(this).attr('id')));
	});


	/**
	 * Function creating embedded forms items
	 * @param {type} _id
	 * @returns {undefined}
	 */
	function addObject(_id) {
		console.log("Embedded form : add object " + _id);
		// handle items indexes in an array
		if(!addingIndexes[_id]) {
			console.log('creating indexes for ' + _id);
			addingIndexes[_id] = $('#' + _id).children().length;
		} else {
			console.log('increment indexes for ' + _id);
			addingIndexes[_id]++;
		}

		// add objects
		numberToAdd = $('#' + _id).data('number-to-add');
		for (i = 0; i < numberToAdd; i++) {
			$('#' + _id).append(
					$($('#' + _id).attr('data-prototype').replace(/__name__label__/g, '').replace(/__name__/g, addingIndexes[_id])).append(createRemoveButton)
			);
		}
	}

	/**
	 * Function creating add button. You can create your own btnAdd js var in your template.
	 * @param {type} _toAddId
	 * @returns {String}
	 */
	function createAddButton(_toAddId) {
		thisBtnAdd = '<a class="add_object" data-objectid="' + _toAddId + '">Add</a>';
		if (typeof btnAdd !== 'undefined') {
			btnAdd = $.parseHTML(btnAdd);
			$(btnAdd).attr('data-objectid', _toAddId);
			thisBtnAdd = btnAdd;
		}
		return thisBtnAdd;
	}

	/**
	 * function creating remove button. You can create your own btnRemove js var in your template.
	 * @returns {String}
	 */
	function createRemoveButton() {
		thisBtnRemove = '<a class="remove_object">Remove</a>';
		if (typeof btnRemove !== 'undefined') {
			thisBtnRemove = btnRemove;
		}
		return thisBtnRemove;
	}

	// Handle add buttons' click event
	$('.add_object').click(function() {
		addObject($(this).attr('data-objectid'));
	});

	// Handle remove buttons' click event
	$('form').on('click', '.remove_object', function() {
		$(this).parent().remove();
	});
	///////////////////////////////////////////////////////////////////////////////////////////////

});

Et voilà, plus qu’à importer votre script dans votre template Twig ! 😉

Si je trouve le courage de faire un système pour laisser la possibilité de personnaliser facilement (et d’internationaliser) les boutons, je mettrais à jour cet article.

Enjoy !

Mise à jour du 10 novembre 2013 :
Le comptage du nombre d’enfant à été corrigé en cas de suppression et de rajout.
L’ajout de configuration par un jeu de classes et de data a été ajouté !
Mise à jour du 10 novembre 2013 :
L’utilisation des fonctions .live() a été remplacé par .on(), merci Thibault pour cette judicieuse remarque 😉
Mise à jour du 24 août 2014 :
l’utilisation de .on() à été mis à jour pour fonctionner avec les dernière version de jQuery et je gère à présent la création des boutons add et remove dans le template twig (donc avec internationalisation)