putyourlightson / craft-sprig

A reactive Twig component framework for Craft CMS.
https://putyourlightson.com/plugins/sprig
MIT License
124 stars 9 forks source link

How submit front-end matrix fields with sprig? #159

Closed maartenheideman closed 2 years ago

maartenheideman commented 2 years ago

Question

After reading in the cookbook about the multi row table field. I'll tried to make it work for matrix field in a front-end submission but can't get it work. With the code below I'll getting an error when adding a row Argument 1 passed to craft\web\View::renderTemplate() must be of the type string, null given, called. Any suggestions?

Additional context

{# Sets the default row to contain 2 columns #}
{% set defaultRow = {
    type: 'row' ,
    enabeld: 1,
    fields: {
        number: 1,
        description: 'test',
        amount: 0
    } }
%}
{# Sets `rows` to the submitted value or the default row #}
{% set rows = fields.offerRows.blocks ?? [defaultRow] %}
{% if addRow is defined %}
  {% set rows = rows|merge([defaultRow]) %}
{% elseif removeRow is defined %}
  {% set rows = rows|filter((val, key) => removeRow != key) %}
{% endif %}

{% for row in rows %}
    <tr>
        <td>
            <input type="hidden" name="fields[offerRows][blocks][new{{ loop.index }}][type]" value="row">
            <input type="hidden" name="fields[offerRows][blocks][new{{ loop.index }}][enabled]" value="1">
            <input type="hidden" name="fields[offerRows][sortOrder][]" value="new{{loop.index}}">

            <input
                type="number"
                name="fields[offerRows][blocks][new{{ loop.index }}][fields][number]"
                class="form-control"
                value="{{ row.fields.number ?? 0}}"
            >
        </td>
        <td>
            <input
                type="text"
                name="fields[offerRows][blocks][new{{ loop.index }}][fields][description]"
                class="form-control"
                value="{{ row.fields.description ?? '' }}"
            >
        </td>
        <td>
            <input
                type="number"
                name="fields[offerRows][blocks][new{{ loop.index }}][fields][amount]"
                class="form-control"
                value="{{ row.fields.amount ?? 0}}"
            >
        </td>
    {# Outputs the remove button if more than one row exists #}
    <td>
        {% if rows|length > 1 %}
            <a sprig s-val:remove-row="{{ loop.index }}" href="#" title="Remove row">
                delete row
            </a>
        {% endif %}
    </td>
</tr>
{% endfor %}

<tr>
    <td colspan="7" class="text-right">
        <a class="btn btn-secondary" sprig s-val:add-row="1">add row</a>
    </td>
</tr>
bencroker commented 2 years ago

That's strange. Have you got it working with a table field?

maartenheideman commented 2 years ago

When I'll copy the example from the documentation it works. As soon as I'll change the markup; for example replace the {% for row in rows %}<p></p>{% endfor %} for {% for row in rows %}<tr><td></td></tr>{% endfor %}. I'll get the earlier mentioned error.

In my template I'll include the sprig component like this: {{ sprig('_sprig/matrix') }}. Also I'll set the useSprig param to true for loading the sprig script. And in the layout I'll added:

       {% if useSprig is defined %}
            {# Loads the required script from a CDN #}
            {{ sprig.script }}
        {% endif %}

The full error:

Argument 1 passed to craft\web\View::renderTemplate() must be of the type string, null given, called in /Users/username/Sites/localhost/offer.sitename.nl/vendor/putyourlightson/craft-sprig/src/controllers/ComponentsController.php on line 64
    "craftcms/cms": "3.7.12",
    "vlucas/phpdotenv": "2.4.0",
    "craftcms/redactor": "2.8.8",
    "verbb/image-resizer": "2.0.10",
    "topshelfcraft/wordsmith": "3.3.0.1",
    "am-impact/amcommand": "3.1.4",
    "putyourlightson/craft-sprig": "1.8.1"
bencroker commented 2 years ago

The error suggests that _sprig/matrix is not a valid template path.

maartenheideman commented 2 years ago

It is the correct path, the first row (defaultRow) also shown up. The problem is adding a new row.

bencroker commented 2 years ago

Can you provide a URL to a test site so I can take a closer look?

maartenheideman commented 2 years ago

Currently I'll this project is in progress and not has a live url yet. I'll send you an invite for repository acces to view the project.

bencroker commented 2 years ago

I see the code above, so need to see this issue take place to help further. It can of course be on a test/staging server.

maartenheideman commented 2 years ago

After some testing I'll find out that removing tr td structure fixed the problem. If Ill put all input fields in a paragraph or div tag it works without errors. Putting it in a table row (tr,td) causes errors. When using the tr td structure the query string params are not present in the header. Has this something to do with htmx / nested divs or something. This is a workaround but putting it in a table with a tr td structure should work?

{% set defaultRow = {
    type: 'regel' ,
    enabeld: 1,
    fields: {
        number: 1,
        description: 'test',
        amount: 1,
    }
} %}

{# Sets `rows` to the submitted value or the default row #}
{% set rows =  fields.offerRows.blocks ?? [defaultRow] %}
{% if addRow is defined %}
  {% set rows = rows|merge([defaultRow]) %}
{% elseif removeRow is defined %}
  {% set rows = rows|filter((val, key) => removeRow != key) %}
{% endif %}

{% for row in rows %}
    <p>
        <input type="hidden" name="fields[offerRows][blocks][new{{ loop.index0 }}][type]" value="row">
        <input type="hidden" name="fields[offerRows][blocks][new{{ loop.index0 }}][enabled]" value="1">
        <input type="hidden" name="fields[offerRows][sortOrder][]" value="new{{loop.index0}}">

        <input
            type="number"
            name="fields[offerRows][blocks][new{{ loop.index0 }}][fields][number]"
            class="form-control"
            value="{{ row.fields.aantal ?? 1 }}"
        >
        <input
            type="text"
            name="fields[offerRows][blocks][new{{ loop.index0 }}][fields][description]"
            class="form-control"
            value="{{ row.fields.omschrijving ?? '' }}"
        >
        <input
            type="number"
            name="fields[offerRows][blocks][new{{ loop.index0 }}][fields][amount]"
            class="form-control"
            value="{{ row.fields.bedrag ?? 1 }}"
        >
        {% if rows|length > 1 %}
            <a sprig s-val:remove-row="{{ loop.index0 }}" href="#" title="Remove row">
                verwijder
            </a>
        {% endif %}
    </p>
{% endfor %}

<a class="btn btn-secondary" sprig s-val:add-row="1">add row</a>
bencroker commented 2 years ago

Using tr and td elements should work the exact same. The only issue I see in your code at https://github.com/putyourlightson/craft-sprig/issues/159#issue-996417558 is the lack of a wrapping table element. A Sprig component will always be wrapped in a div, so you need to check the outputted source code to ensure that it is valid.