Loading CHANGELOG.md +4 −0 Original line number Diff line number Diff line 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. Loading config/module.config.php +4 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading src/UnicaenApp/Form/Element/Collection.php 0 → 100644 +153 −0 Original line number Diff line number Diff line <?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; } } src/UnicaenApp/Form/View/Helper/FormElementCollection.php 0 → 100644 +300 −0 Original line number Diff line number Diff line <?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>'; } } Loading
CHANGELOG.md +4 −0 Original line number Diff line number Diff line 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. Loading
config/module.config.php +4 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading
src/UnicaenApp/Form/Element/Collection.php 0 → 100644 +153 −0 Original line number Diff line number Diff line <?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; } }
src/UnicaenApp/Form/View/Helper/FormElementCollection.php 0 → 100644 +300 −0 Original line number Diff line number Diff line <?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>'; } }