diff --git a/CHANGELOG.md b/CHANGELOG.md
index 212e84f57d24212aa27aae791a91c9da0b230ebc..b05ea21855cb43f71a4446b76710fb7dbff0ce5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
 CHANGELOG
 =========
 
+5.1.7
+-----
+- Nouvel élément de formulaire Collection et aide de vue associée FormElementCollection (adaptés d'Octopus).
+
 5.1.6
 -----
 - Nouveau collecteur dans la barre LaminasDevTools : messages produits par \UnicaenApp\Service\MessageCollector.   
diff --git a/config/module.config.php b/config/module.config.php
index a9158c46cff787724b9ce6dbb8bd44588eb8fd62..6a46951b0254e1e1056a9803cd0b9c159712bb1d 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -13,6 +13,9 @@ use UnicaenApp\DeveloperTools\MessageCollectorServiceFactory;
 use UnicaenApp\Form\Element\SearchAndSelect2;
 use UnicaenApp\Form\View\Helper\FormControlGroup;
 use UnicaenApp\Form\View\Helper\FormControlGroupFactory;
+use UnicaenApp\Form\View\Helper\FormControlText;
+use UnicaenApp\Form\View\Helper\FormElementCollection;
+use UnicaenApp\Form\View\Helper\FormElementRow;
 use UnicaenApp\Form\View\Helper\FormSearchAndSelect2;
 use UnicaenApp\HostLocalization\HostLocalization;
 use UnicaenApp\HostLocalization\HostLocalizationFactory;
@@ -432,6 +435,7 @@ return [
             'formDate'                  => 'UnicaenApp\Form\View\Helper\FormDate',
             'formDateTime'              => Form\View\Helper\FormDateTime::class,
             'formDateInfSup'            => 'UnicaenApp\Form\View\Helper\FormDateInfSup',
+            'formElementCollection'     => FormElementCollection::class,
             'formRowDateInfSup'         => 'UnicaenApp\Form\View\Helper\FormRowDateInfSup',
             'formSearchAndSelect'       => 'UnicaenApp\Form\View\Helper\FormSearchAndSelect',
             'formSearchAndSelect2'      => FormSearchAndSelect2::class,
diff --git a/src/UnicaenApp/Form/Element/Collection.php b/src/UnicaenApp/Form/Element/Collection.php
new file mode 100644
index 0000000000000000000000000000000000000000..12f4e12e270ce205b934075ead73c59990bb544d
--- /dev/null
+++ b/src/UnicaenApp/Form/Element/Collection.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace UnicaenApp\Form\Element;
+
+use Laminas\Form\Element\Collection as ZendCollection;
+use Laminas\Form\Element\Text;
+use Laminas\Form\ElementInterface;
+use Laminas\Form\FieldsetInterface;
+
+/**
+ * Collection d'éléments :
+ * - possibilité d'ajouter un autocomplete sur un (et un seul) élément
+ * - possibilité de limiter le nombre d'éléments
+ *
+ * @author     David Surville <david.surville at unicaen.fr>
+ */
+class Collection extends ZendCollection
+{
+    const AUTOCOMPLETE_MIN_LENGTH = 2;
+    const AUTOCOMPLETE_DELAY = 750;
+
+
+    /**
+     * Liste des autocomplete
+     * Format : ['nom_element' => ['source' => , 'min-length' => , 'delay' => ]]
+     *
+     * @var array
+     */
+    protected $autocomplete = [];
+
+    /**
+     * Nombre d'éléments minimum de la collection
+     * 0 = pas de limitation
+     *
+     * @var int
+     */
+    protected $minElements = 0;
+
+    /**
+     * Nombre d'éléments maximum de la collection
+     * 0 = pas de limitation
+     *
+     * @var int
+     */
+    protected $maxElements = 0;
+
+
+    /**
+     * @param  null|int|string $name Optional name for the element
+     * @param  array $options Optional options for the element
+     */
+    public function __construct($name = null, $options = array())
+    {
+        parent::__construct($name, $options);
+        $this->setAttribute('id', uniqid('collection'));
+    }
+
+
+    /**
+     * @return array
+     */
+    public function getAutocomplete()
+    {
+        return $this->autocomplete;
+    }
+
+
+    /**
+     * Ajoute un autocomplete sur un élément
+     *
+     * @param string $elementName
+     * @param string $source
+     * @param int $minLength
+     * @param int $delay
+     */
+    public function addAutocomplete($elementName, $source, $minLength = null, $delay = null)
+    {
+        $targetElement = $this->getTargetElement();
+
+        if ($targetElement instanceof FieldsetInterface) {
+            $elements = array_keys($targetElement->getElements());
+            if (!in_array($elementName, $elements)) {
+                throw new \RuntimeException(sprintf("L'élément '%s' associé à l'autocomplete n'existe pas.", $elementName));
+            }
+
+            $element = $targetElement->get($elementName);
+        } elseif ($targetElement instanceof ElementInterface) {
+            if ($targetElement->getName() !== $elementName) {
+                throw new \RuntimeException(sprintf("L'élément '%s' associé à l'autocomplete n'existe pas.", $elementName));
+            }
+
+            $element = $targetElement;
+        }
+
+        if (!$element instanceof Text) {
+            throw new \RuntimeException("L'autocomplete doit être associé à un élément de type 'text'.");
+        }
+
+        $element->setAttribute('class', sprintf('%s-autocomplete', $this->getAttribute('id')));
+
+        $this->autocomplete[$elementName] = [
+            'source' => $source,
+            'min-length' => $minLength ?: self::AUTOCOMPLETE_MIN_LENGTH,
+            'delay' => $delay ?: self::AUTOCOMPLETE_DELAY
+        ];
+    }
+
+    /**
+     *
+     * @return int
+     */
+    function getMinElements()
+    {
+        return $this->minElements;
+    }
+
+    /**
+     *
+     * @param int $minElements
+     *
+     * @return self
+     */
+    function setMinElements($minElements)
+    {
+        $this->minElements = $minElements;
+
+        return $this;
+    }
+
+    /**
+     *
+     * @return int
+     */
+    function getMaxElements()
+    {
+        return $this->maxElements;
+    }
+
+    /**
+     *
+     * @param int $maxElements
+     *
+     * @return self
+     */
+    function setMaxElements($maxElements)
+    {
+        $this->maxElements = $maxElements;
+
+        return $this;
+    }
+
+
+}
diff --git a/src/UnicaenApp/Form/View/Helper/FormElementCollection.php b/src/UnicaenApp/Form/View/Helper/FormElementCollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..f4a66d8ccd6f49bed84b0f525b0ff960054a0eaf
--- /dev/null
+++ b/src/UnicaenApp/Form/View/Helper/FormElementCollection.php
@@ -0,0 +1,300 @@
+<?php
+namespace UnicaenApp\Form\View\Helper;
+
+use UnicaenApp\Form\Element\Collection;
+use Laminas\Form\View\Helper\FormCollection;
+use Laminas\Form\ElementInterface;
+use Laminas\Form\Element\Collection as CollectionElement;
+use Laminas\Form\FieldsetInterface;
+use Laminas\Form\LabelAwareInterface;
+use Laminas\Form\Element\Button;
+
+/**
+ * Aide de vue dessinant une collection d'éléments.
+ *
+ * Ajout par rapport à celle de base :
+ * - boutons d'ajout et de suppression d'élément à la collection
+ * - respect du nombre mini et maxi d'éléments dans la collection
+ * - styles inline :-/
+ *
+ * @see \UnicaenApp\Form\Element\Collection
+ * @author Unicaen
+ */
+class FormElementCollection extends FormCollection
+{
+    /**
+     * This is the default wrapper that the collection is wrapped into
+     *
+     * @var string
+     */
+    protected $wrapper = '<fieldset%4$s>%2$s%1$s%3$s%5$s</fieldset>%6$s%7$s';
+
+    /**
+     * The name of the default view helper that is used to render sub elements.
+     *
+     * @var string
+     */
+    protected $defaultElementHelper = 'formControlGroup';
+
+    /**
+     * Nombre d'éléments à afficher
+     *
+     * @var int
+     */
+    protected $countElements;
+
+
+    /**
+     * Render a collection by iterating through all fieldsets and elements
+     *
+     * @param  ElementInterface $element
+     * @return string
+     */
+    public function render(ElementInterface $element)
+    {
+        $renderer = $this->getView();
+        if (!method_exists($renderer, 'plugin')) {
+            // Bail early if renderer is not pluggable
+            return '';
+        }
+
+        $index=0;
+        $markup = '';
+        $linkMarkup = '';
+        $templateMarkup = '';
+        $elementHelper = $this->getElementHelper();
+        $fieldsetHelper = $this->getFieldsetHelper();
+
+        if ($element instanceof CollectionElement && $element->shouldCreateTemplate()) {
+            $this->countElements = $element->getCount();
+            $linkMarkup = $this->getAddLink();
+            $javascript = $this->getJavascript($element);
+            $css = $this->getCss($element);
+            $element->getTemplateElement()->setAttribute('data-index', '__index__');
+            $templateMarkup = $this->renderTemplate($element);
+        }
+        elseif ($element instanceof FieldsetInterface) {
+            $linkMarkup = $this->getDelLink();
+        }
+
+        foreach ($element->getIterator() as $elementOrFieldset) {
+            if ($elementOrFieldset instanceof FieldsetInterface) {
+                $elementOrFieldset->setAttribute('data-index', $index);
+                $markup .= $fieldsetHelper($elementOrFieldset, $this->shouldWrap());
+                $index++;
+            } elseif ($elementOrFieldset instanceof ElementInterface) {
+                $markup .= $elementHelper($elementOrFieldset);
+            }
+        }
+
+        // Every collection is wrapped by a fieldset if needed
+        if ($this->shouldWrap) {
+            $attributes = $element->getAttributes();
+            unset($attributes['name']);
+            $attributesString = $attributes ? ' ' . $this->createAttributesString($attributes) : '';
+
+            $label = $element->getLabel();
+            $legend = '';
+
+            if (!empty($label)) {
+                if (null !== ($translator = $this->getTranslator())) {
+                    $label = $translator->translate(
+                        $label,
+                        $this->getTranslatorTextDomain()
+                    );
+                }
+
+                if (!$element instanceof LabelAwareInterface || !$element->getLabelOption('disable_html_escape')) {
+                    $escapeHtmlHelper = $this->getEscapeHtmlHelper();
+                    $label = $escapeHtmlHelper($label);
+                }
+
+                $legend = sprintf(
+                    $this->labelWrapper,
+                    $label
+                );
+            }
+
+            $markup = sprintf(
+                $this->wrapper,
+                $markup,
+                $legend,
+                $templateMarkup,
+                $attributesString,
+                $linkMarkup,
+                $css,
+                $javascript
+            );
+        } else {
+            $markup .= $templateMarkup . $linkMarkup;
+        }
+
+        return $markup;
+    }
+
+    protected function getDelLink()
+    {
+        return '<div class="collection-delete-link">' .
+                    '<a class="btn btn-link" title="Supprimer cet élément de la collection">' .
+                        '<i class="fas fa-times" aria-hidden="true"></i>' .
+                    '</a>' .
+                '</div>';
+    }
+
+    /**
+     * Get add link
+     *
+     * @return string
+     */
+    protected function getAddLink()
+    {
+        return '<div class="collection-add-link">' .
+                    '<a class="btn btn-link" title="Ajouter un élément à la collection">' .
+                        '<i class="fas fa-plus" aria-hidden="true"></i> Ajouter' .
+                    '</a>' .
+                '</div>';
+    }
+
+    /**
+     * @param Collection $container
+     * @return string
+     */
+    protected function getCss($container)
+    {
+        $fieldsetId = $container->getAttribute('id');
+
+        $css = <<<EOT
+<style>
+    #{$fieldsetId} {
+        margin-bottom: 15px;
+    }
+    
+    #{$fieldsetId} div.input-group {
+        position: relative;
+        float: left;
+        margin: 0 10px 0 0;
+    }
+    
+    #{$fieldsetId} div.collection-delete-link {
+        position: relative;
+        float: right;
+    }
+    
+    #{$fieldsetId} div.collection-delete-link > .btn {
+        padding: 6px 0;
+    }
+    
+    #{$fieldsetId} div.collection-add-link {
+        margin-bottom: 15px;
+    }
+</style>
+EOT;
+        return $css;
+    }
+
+    /**
+     * @param Collection $container
+     * @return string
+     */
+    protected function getJavascript($container)
+    {
+        $maxEnabled = ($container->getMaxElements()) ? 'true' : 'false';
+        $minEnabled = ($container->getMinElements()) ? 'true' : 'false';
+
+        $javascript = <<<EOT
+$('#{$container->getAttribute('id')}').on('click', '.collection-add-link > a', function() {
+    var container	 = $('#{$container->getAttribute('id')}');
+    var currentCount = $(container).children('fieldset').length;
+    var lastFieldset = $(container).children('fieldset').last();
+    var index = currentCount === 0 ? currentCount : parseInt(lastFieldset.attr('data-index')) + 1;
+    
+    var template	= $(container).children('span').data('template');
+    template = template.replace(/__index__/g, index);
+
+    if(currentCount === 0) {
+        $(container).prepend(template);
+    }
+    else {
+        lastFieldset.after(template);
+    }
+
+    if({$maxEnabled}) {
+        if(currentCount+1 >= {$container->getMaxElements()}) {
+            $('.collection-add-link').remove();
+        }
+    }
+});
+
+$('#{$container->getAttribute('id')}').on('click', '.collection-delete-link' , function() {
+    var container = $('#{$container->getAttribute('id')}');
+    var item	  = $(this).closest('fieldset');
+    var curPos    = item.index();
+    var curInd    = item.attr('data-index');
+
+    // suppression du fieldset lié à l'élément
+    item.remove();
+
+    var currentCount = $(container).children('fieldset').length;
+    if(currentCount > 0) {
+        var index = (curInd < {$this->countElements}) ? {$this->countElements} : curInd;
+        $(container).children('fieldset').each(function(i, el) {
+            if(i >= curPos) {
+                $(el)
+                    .attr('data-index', '' + index)
+                    .find('select,input').attr('name', function () {
+                        return this.name.replace(/\[\d+\](\[.*\])$/, '['+index+']$1');
+                    });
+                index++;
+            }
+        });
+    }
+
+    if({$maxEnabled}) {
+        if(currentCount+1 == {$container->getMaxElements()}) {
+            $(container).append('{$this->getAddLink()}');
+        }
+    }
+});
+
+$().ready(function updateButtons()
+{
+    var container = $('#{$container->getAttribute('id')}');
+    var buttons   = $(container).find('.collection-delete-link');
+    var currentCount = $(container).children('fieldset').length;
+
+    if({$maxEnabled}) {
+        if(currentCount >= {$container->getMaxElements()}) {
+            $('.collection-add-link').remove();
+        }
+    }
+
+    if($minEnabled) {
+        buttons.each(function(i, el) {
+            if(i<={$container->getMinElements()}-1) {
+                $(el).remove();
+            }
+        });
+    }
+});
+
+
+EOT;
+
+        foreach($container->getAutocomplete() as $name => $options) {
+            $javascript .= <<<EOT
+$('#{$container->getAttribute('id')}').on('keyup', '.{$container->getAttribute('id')}-autocomplete', function() {
+    $(this).autocompleteOctopus({
+        source: '{$options['source']}',
+        minLength: {$options['min-length']},
+        delay: {$options['delay']}
+    });
+});
+
+
+EOT;
+
+        }
+
+        return '<script type="text/javascript">' . $javascript . '</script>';
+    }
+}