1

I'm trying to post a public entry form in a matrix field. I've added a count so that the block id is unique. However it's just clearing the matrix field and saving a single entry.

{% set matrix = entry.measurement | length %}
{% set count = matrix + 1 %}
{% set matrix_count = 'new' ~ count %}

<form method="post" accept-charset="UTF-8">
    {{ getCsrfInput() }}
    <input type="hidden" name="action" value="entries/saveEntry">
    <input type="hidden" name="redirect" value="{{ entry.url }}">
    <input type="hidden" name="sectionId" value="3">
    <input type="hidden" name="enabled" value="1">
    <input type="hidden" name="entryId" value="{{ entry.id }}">

    <input type="hidden" name="fields[measurement][{{ matrix_count }}][type]" value="measurement">

    <div class="legend">
      <h2>Tumour Volume</h2>
      <p>All entries should be submitted in (mm)</p>
    </div>
    <div class="fields">
      <div class="field">
        <label for="">Length</label>
        <input type="number" name="fields[measurement][{{ matrix_count }}][fields][length]">
      </div>
    </div>

    <input type="submit" value="Save">
</form>

Thanks!

3

Of course it clears all the existing matrix elements if you update an entry. For craft it's like you delete the existing matrix blocks and create a new one. In order to keep all of them you have to send them in the form or create a custom controller to do it. You can insert all current matrix blocks in hidden fields, so users won't see them but craft will not delete them.

The following code is totally untested (I don't have much time) it is just to show you the direction

{% for block in entry.measurement %}
    <input type="hidden" name="fields[measurement][{{ block.id }}][type]" value="{{ block.getType().handle }}">
    <input type="hidden" name="fields[measurement][{{ block.id }}][enabled]" value="1">
    <input type="hidden" name="fields[measurement][{{ block.id }}][fields][length]" value="{{ block.length }}" >
{% endfor %}

Important note: your new matrix element's should not have the id of the latest+1. Your new matrix elements index should be newX with X = the number of the new element. So if you want to insert 1 new block your index would be new1, your second block would be new2

<input type="number" name="fields[measurement][new1][fields][length]">

you can inspect the backend form in your cp to get to know the structure craft need. It should look like this:

<!-- existing block -->
<div class="matrixblock" data-id="7" data-type="block">
    <input type="hidden" name="fields[contentMatrix][7][type]" value="block">
    <input type="hidden" name="fields[contentMatrix][7][enabled]" value="1">
    <!-- some fields here -->
</div>

<!-- new block -->
<div class="matrixblock" data-id="new1" data-type="newBlock" style="opacity: 1;">
    <input type="hidden" name="fields[contentMatrix][new1][type]" value="newBlock">
    <input type="hidden" name="fields[contentMatrix][new1][enabled]" value="1">
    <!-- some fields here -->
</div>

If you do't want to render the entire matrix as hidden fields for any reason let me know and I'll provide you a custom controller action to add the matrix block instead of overwriting it

Update
This is your controller action

/**
 * @throws Exception
 */
public function actionSaveEntryMatrix(){
    $this->requirePostRequest();
    // check for the entries id, throw an exception if there is none (eg user manipulated the form)
    if(!$entryId = craft()->request->getParam('entryId')){
        throw new Exception('could not find EntryId');
    }
    if(!$entry = craft()->elements->getElementById($entryId)){
        throw new Exception('Could not find entry with id');
    }

    // get all attributes
    $attributes = $entry->getContent()->getAttributes();
    $entryData = craft()->request->getParam('fields');

    // just in case you want to update other fields of the entry
    if($entryData && is_array($entryData)){
        foreach ($entryData as $k => $v){
            if(array_key_exists($k, $attributes)){
                $attributes[$k] = $v;
            }
        }
    }

    // now the fun starts...
    // insert your field Handle of your matrix field here to get the field
    $measurementField = craft()->fields->getFieldByHandle('measurement');
    // fetch all available blocktypes for that field
    $blockTypes = craft()->matrix->getBlockTypesByFieldId($measurementField->id, 'handle');

    // get all measurements from post request
    $measurements = craft()->request->getParam('measurement');
    // this array will contain all your matrixblocks
    $measurementsBlocks = [];

    // fetch all old/existing matrix blocks
    $criteria = craft()->elements->getCriteria(ElementType::MatrixBlock);
    $criteria->fieldId = $measurementField->id;
    $criteria->ownerId = $entry->id;
    $criteria->limit = null;
    $criteria->status = null;
    $criteria->localeEnabled = null;
    $criteria->locale = $entry->locale;
    $oldBlocks = $criteria->find();

    // Index them by ID
    foreach ($oldBlocks as $oldBlock) {
        $measurementsBlocks[$oldBlock->id] = $oldBlock;
    }

    // start the new sortOrder
    $sortOrder = count($measurementsBlocks);
    foreach ($measurements as $blockId => $blockData) {
        $blockType = $blockTypes[$blockData['type']];
        // Is this new a new block or an existing?
        if (strncmp($blockId, 'new', 3) === 0){
            $block = new MatrixBlockModel();
            $block->fieldId = $measurementField->id;
            $block->typeId  = $blockType->id;
            $block->ownerId = $entry->id;
            $block->locale  = $entry->locale;

            $isNew = true;
            // Preserve the collapsed state, which the browser can't remember on its own for new blocks
            $block->collapsed = !empty($blockData['collapsed']);
        }else{
            // if it is an existing block just load it
            $block = $measurementsBlocks[$blockId];
            $isNew = false;
        }

        // get content from Post
        if(isset($blockData['fields'])){
            $block->setContentFromPost($blockData['fields']);
        }

        // if it is a new block increase the sortOrder and add it to the array
        if($isNew){
            $sortOrder++;
            $block->sortOrder = $sortOrder;
            $measurementsBlocks[] = $block;
        }else{
            // otherwise just update the array
            $measurementsBlocks[$block->id] = $block;
        }
    }

    // add the matrix elements + all updated field to the entries content
    $attributes['measurement'] = $measurementsBlocks;
    $entry->getContent()->setAttributes($attributes);

    // finally save the entry
    if(craft()->elements->saveElement($entry)){
        // redirect to your preferred url
        $this->redirect(craft()->request->getParam('redirect'));
    }else{
        // if there are errors add the entry to the route variables so you can display errors in twig via
        // entry.getErrors()
        craft()->urlManager->setRouteVariables(array(
            'entry' => $entry
        ));
    }
}

And this will be your form

<form method="post" accept-charset="UTF-8">
    {{ getCsrfInput() }}
    <!-- change your action value to your one... my plugin has the handle test -->
    <input type="hidden" name="action" value="test/saveEntryMatrix">
    <!--
        set your redirect value to your needs
        as you said if you want to add a field
        `/{{ entry.url }}/add-data`
        or
        `/{{ entry.url }}/edit-data?id=yourId`
    -->
    <input type="hidden" name="redirect" value="{{ entry.url }}">
    <input type="hidden" name="sectionId" value="3">
    <input type="hidden" name="enabled" value="1">
    <input type="hidden" name="entryId" value="{{ entry.id }}">

    <!-- in case you want to edit an existing block note: 50 was my id to test things -->
    <input type="hidden" name="measurement[50][type]" value="measurement">

    <div class="legend">
        <h2>Tumour Volume</h2>
        <p>All entries should be submitted in (mm)</p>
    </div>
    <div class="fields">
        <div class="field">
            <label for="">Length</label>
            <input type="number" name="measurement[50][fields][length]">
        </div>
    </div>
    <!-- endcase -->

    <!-- in case you want to add a new one -->
    <input type="hidden" name="measurement[new1][type]" value="measurement">

    <div class="legend">
        <h2>Tumour Volume</h2>
        <p>All entries should be submitted in (mm)</p>
    </div>
    <div class="fields">
        <div class="field">
            <label for="">Length</label>
            <input type="number" name="measurement[new1][fields][length]">
        </div>
    </div>
    <!-- endcase -->

    <input type="submit" value="Save">
</form>

About the redirect: do you know how craft rooting works? It seems like you have to make use of the public function registerSiteRoutes() function: in case you don't know how that works, please open another question to keep this one clean or read the documentation

  • Hi Robin - thanks again for responding - I'd ideally not want to add all the matrix entries over again - i'd love to see that controller! Thanks! – Graham Winston Nov 11 '17 at 8:09
  • Also would this be the same case for editing a specific matrix block? – Graham Winston Nov 11 '17 at 8:15
  • Yes. The behavior of the default entries/saveEntry is fetching all matrix elements from your form, inserting new one + updating existing one and deleting the rest. I'll update my answer with the controller as soon as I can but it could take a few hours, sry for that. – Robin Schambach Nov 11 '17 at 8:19
  • Thanks Robin - I really appreciate the time you are spending just a few points: 1. I'm trying to build it so that I can add and edit single blocks within the matrix field 2. The form is going to be in a separate URL eg. (/<rodent-url>/add-data) (/rodents-url/edit-data?id=<matrix-block-id>) – Graham Winston Nov 11 '17 at 8:25
  • I updated my answer.. With this function you can add and update as many blocks as you like. When you index it with a current matrix id it is going to be updated, if you insert a newX it is going to be inserted. Old matrix blocks won't be deleted. As for the url: you just have to insert the url you wish to be redirected to the redirect field. – Robin Schambach Nov 11 '17 at 10:42
0

Since I can't add a comment yet, I'll do it via an answer: @Robin Schambach: thanks for all that help, this could be very useful to me as well. Could you please tell me what to update in the controller to have it work with Craft CMS 3?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.