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)

4 réflexions sur « Script JQuery pour gérer les embbed forms sous Symfony2 »

  1. Super script !

    Il m’a permit de réaliser des formulaires imbriqués pour un projet. Le soucis est que celui que vous nous donnez ne permet pas la suppression (en tout cas cela ne fonctionne pas chez moi). De plus, vous utilisez la fonction .live() qui n’est plus supportée.

    Par ailleurs, j’ai ajouté quelques lignes pour permettre l’imbrication de formulaires dans des formulaires eux-même imbriqués (formception).

    Je donne donc mon code pour ceux que ça intéresserai :

    //Init vars
    addingIndexes = new Array();
    
    $(document).ready(function() {
    	
    	// Init vars
    	addingIndexes = new Array();
    
    	$(document).ready(function() {
    		
    		
    		// 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) {
    			$(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)
    				);
    			}
    
    			$('a.remove_object').on('click', function() {
    				$(this).parent().remove();
    			});
    		}
    
    		/**
    		 * Function creating add button
    		 * @param {type} _toAddId
    		 * @returns {String}
    		 */
    		function createAddButton(_toAddId) {
    			return 'Add';
    		}
    
    		 function createAddSubButton(_toAddId) {
    				return 'Add sub';
    			}
    
    		/**
    		 * function creating remove button
    		 * @returns {String}
    		 */
    		function createRemoveButton() {
    			return 'Remove';
    		}
    
    		// Handle add buttons' click event
    		$('.add_object').click(function() {
    			addObject($(this).attr('data-objectid'));
    			console.log($('div *[data-prototype]').find('*[data-prototype]'));
    			$('div').find('*[data-prototype] *[data-prototype]').each(function(_id) {
    				$(".add_subobject").remove();
    				$(this).parent().append(createAddSubButton($(this).attr('id')));
    				$('.add_subobject').click(function() {
    				addObject($(this).attr('data-objectid'));
    				});
    				
    			});
    
    		});
    
    	});
    });
    

    En tout cas bravo à vous !

    Thibault

  2. Je suis content que mon script t’ai plu.

    C’est exacte, j’avais entamé ce projet il y a un peu plus d’un an et j’utilisais donc encore jQuery 1.6. J’ai corrigé l’utilisation de .live(), merci à toi de me l’avoir fait remarqué !

    Pour ce qui est de la suppression, elle fonctionne dans mon environnement. Le clic sur le bouton est sensé supprimer l’élément du DOM parent dans la structure créée par défaut par le prototypage symfony 2.3. Il faudrait vérifier la structure du html générée chez toi pour identifier le problème.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Le temps imparti est dépassé. Merci de recharger le CAPTCHA.