diff --git a/resources/views/extend/forms/fields/assets.antlers.html b/resources/views/extend/forms/fields/assets.antlers.html index 3b5e1d1ea5..4e88a6b117 100644 --- a/resources/views/extend/forms/fields/assets.antlers.html +++ b/resources/views/extend/forms/fields/assets.antlers.html @@ -1,7 +1,7 @@ + {{ foreach:options as="option|label" }} + {{ fields scope="__field" }} + + {{ slot:addContext(__field) }} + + {{ /fields }} + diff --git a/resources/views/extend/forms/fields/integer.antlers.html b/resources/views/extend/forms/fields/integer.antlers.html index 8392cb99a9..299b37e20a 100644 --- a/resources/views/extend/forms/fields/integer.antlers.html +++ b/resources/views/extend/forms/fields/integer.antlers.html @@ -1,6 +1,6 @@ - + slot = $slot; + + collect($this->data['fields'] ?? []) + ->each(fn ($field) => $field['field']->slot($slot)); + + return $this; + } + + public function isBlade(bool $isBlade): self + { + $this->isBlade = $isBlade; + + collect($this->data['fields'] ?? []) + ->each(fn ($field) => $field['field']->isBlade($isBlade)); + + return $this; + } + + public function toHtml(): string + { + $data = array_merge($this->data, [ + 'slot' => $this->slot, + ]); + + return $this->minifyFieldHtml( + view($this->field->fieldtype()->view(), $data)->render(), + ); + } + + public function __toString(): string + { + return $this->toHtml(); + } + + protected function minifyFieldHtml(string $html): string + { + // Leave whitespace around these html elements. + $ignoredHtmlElements = collect(['a', 'span'])->implode('|'); + + // Trim whitespace between all other html elements. + $html = preg_replace('/\s*(<(?!\/*('.$ignoredHtmlElements.'))[^>]+>)\s*/', '$1', $html); + + return $html; + } +} diff --git a/src/Forms/RenderableFieldSlot.php b/src/Forms/RenderableFieldSlot.php new file mode 100644 index 0000000000..e2554b742d --- /dev/null +++ b/src/Forms/RenderableFieldSlot.php @@ -0,0 +1,32 @@ +context = $context; + + return $this; + } + + public function __toString(): string + { + if ($this->isBlade) { + return Blade::render($this->html, ['field' => $this->context]); + } + + return (string) Antlers::parse($this->html, $this->context); + } +} diff --git a/src/Forms/Tags.php b/src/Forms/Tags.php index f44018e7c6..40716b2b0e 100644 --- a/src/Forms/Tags.php +++ b/src/Forms/Tags.php @@ -5,6 +5,7 @@ use DebugBar\DataCollector\ConfigCollector; use DebugBar\DebugBarException; use Statamic\Contracts\Forms\Form as FormContract; +use Statamic\Facades\Antlers; use Statamic\Facades\Blink; use Statamic\Facades\Blueprint; use Statamic\Facades\Form; @@ -138,6 +139,27 @@ public function create() return $html; } + /** + * Maps to {{ form:fields }}. + * + * @return string + */ + public function fields() + { + $isBlade = $this->isAntlersBladeComponent(); + + $slot = new RenderableFieldSlot($this->content, $isBlade); + + collect($this->context['fields']) + ->each(fn ($field) => $field['field']->slot($slot)->isBlade($isBlade)); + + if ($isBlade) { + return $this->tagRenderer->render('@foreach($fields as $field)'.$this->content.'@endforeach', $this->context->all()); + } + + return Antlers::parse('{{ fields }}'.$this->content.'{{ /fields }}', $this->context->all()); + } + /** * Maps to {{ form:errors }}. * diff --git a/src/Tags/Concerns/RendersForms.php b/src/Tags/Concerns/RendersForms.php index 8f86e7edec..33c9283fd1 100644 --- a/src/Tags/Concerns/RendersForms.php +++ b/src/Tags/Concerns/RendersForms.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Support\MessageBag; use Statamic\Fields\Field; +use Statamic\Forms\RenderableField; use Statamic\Support\Str; trait RendersForms @@ -142,46 +143,54 @@ protected function getRenderableField($field, $errorBag = 'default', $manipulate ->filter()->all(); $formHandle = $field->form()?->handle() ?? Str::slug($errorBag); + $data = array_merge($configDefaults, $field->toArray(), [ + 'handle' => $field->handle(), + 'name' => $this->convertDottedHandleToInputName($field->handle()), 'id' => $this->generateFieldId($field->handle(), $formHandle), 'instructions' => $field->instructions(), 'error' => $errors->first($field->handle()) ?: null, 'default' => $field->value() ?? $field->defaultValue(), - 'old' => old($field->handle()), + 'old' => old($field->handle()), // TODO: Ensure dotted path for old input works here. 'value' => $value, ], $field->fieldtype()->extraRenderableFieldData()); + if ($field->fieldtype()->handle() === 'group') { + $data['fields'] = collect($field->fieldtype()->fields()->all()) + ->map(fn ($child) => $child->setHandle($field->handle().'.'.$child->handle())) + ->map(fn ($child) => $this->getRenderableField($child, $errorBag, $manipulateDataCallback)) + ->values() + ->all(); + } + if ($manipulateDataCallback instanceof Closure) { $data = $manipulateDataCallback($data, $field); } - $data['field'] = $this->minifyFieldHtml(view($field->fieldtype()->view(), $data)->render()); + $data['field'] = new RenderableField($field, $data); return $data; } /** - * Minify field html. - * - * @param string $html - * @return string + * Generate a field id to associate input with label. */ - protected function minifyFieldHtml($html) + private function generateFieldId(string $fieldHandle, ?string $formName = null): string { - // Leave whitespace around these html elements. - $ignoredHtmlElements = collect(['a', 'span'])->implode('|'); - - // Trim whitespace between all other html elements. - $html = preg_replace('/\s*(<(?!\/*('.$ignoredHtmlElements.'))[^>]+>)\s*/', '$1', $html); - - return $html; + return ($formName ?? 'default').'-form-'.$fieldHandle.'-field'; } /** - * Generate a field id to associate input with label. + * Convert dotted handle to input name that can be submitted as array value in form html. */ - private function generateFieldId(string $fieldHandle, ?string $formName = null): string + protected function convertDottedHandleToInputName(string $handle): string { - return ($formName ?? 'default').'-form-'.$fieldHandle.'-field'; + $parts = collect(explode('.', $handle)); + + $first = $parts->pull(0); + + return $first.$parts + ->map(fn ($part) => '['.$part.']') + ->join(''); } } diff --git a/src/View/Antlers/Language/Runtime/RuntimeParser.php b/src/View/Antlers/Language/Runtime/RuntimeParser.php index 7df144a7da..a00b15643e 100644 --- a/src/View/Antlers/Language/Runtime/RuntimeParser.php +++ b/src/View/Antlers/Language/Runtime/RuntimeParser.php @@ -125,6 +125,11 @@ public function __construct(DocumentParser $documentParser, NodeProcessor $nodeP $this->antlersParser = $antlersParser; } + public function getCascade() + { + return $this->cascade; + } + /** * Sets the RuntimeConfiguration instance. *