4

I'm building an MVP for a project, I'm using Craft and would like to:

  1. Create an experiment (/experiments/)
  2. Create multiple cages for the animals within the previous created experiment eg. 5 cages - (/experiments//cages/)
  3. Add a number of rodents to each of those cages e.g. Create 5 cages with 5 rodents in each. (/experiments//cages/)
  4. Add measurements to a specific mouse e.g., weight / tumour size (/experiments//cages/)

I was thinking this would be possible by using an entry form to add entries to a structure that looks like this-

--Experiments
----Cage 1
------Rodent 1
------Rodent 2
------Rodent 3
----Cage 2
------Rodent 1
------Rodent 2
------Rodent 3
...

My question is -

  1. Is it possible to create multiple entries per form eg. Create 5 cages with 5 rodents in each. from a single entry form submission?

  2. Is it possible to assign the entry type in an entry form? (eg. Cage or Mouse entry type?)

  3. Is it possible to assign the parent to an entry via the entry form?

If these aren't possible with an entry form + structure what would be the best way to approach it?

Thanks!

  • 2
    It's relatively easy and fast to achieve once you've learned how craft works. It would just be nice to know if you need this form in your frontend or only in your backend. I think the most difficult part is to create the dynamic form with pure Javascript/jquery if you don't want to use angular. If you don't refuse to create a custom plugin I could provide you the basic code you need but I need to know if it's only for your backend or for your frontend too. As for your question for frontend request: I can say yes - it's all possible with a custom controller – Robin Schambach Nov 9 '17 at 14:17
  • Hi Robin - This is only on the front end, I can handle the javascript side (It's my strong point!) I've never actually coded a custom controller so any help would be really appreaciated! – Graham Winston Nov 9 '17 at 14:52
8

A little bit late but here it is.

  1. Go to https://pluginfactory.io/ and create a plugin, you need at least 1 Controller but you can leave the Name value blank to create a controller with the same Name as your PluginHandle. So if you choose "Experiment" as your Pluginhandle your controller will be ExperimentController
  2. Click on "Build my Plugin" and save the files in your craft/plugins/>>pluginhandle<< folder (of course you need to create the >>pluginhandle<< directory eg.) Then you should install the plugin
  3. Insert this function in your controller. Note: this is just an example. I don't know your fieldLayout or some special behaviors you might want to include. So this is just the basic logic with some error handling. Important please read the commands and change the entryType Ids

         /**
         * @var    bool|array Allows anonymous access to this controller's actions.
         * @access protected
         */
        protected $allowAnonymous = true;
    
        /**
         * @throws Exception
         */
        public function actionSaveEntry(){
            $this->requirePostRequest();
            // check for an Id if there is one we update an existing entry if not we create a new one
            if($entryId = craft()->request->getParam('entryId')){
                if(!$entry = craft()->elements->getElementById($entryId)){
                    throw new Exception('Could not find entry with id');
                }
            }else{
                $entry = new EntryModel();
                $entry->typeId = craft()->request->getParam('entryType');
                $entry->sectionId = craft()->request->getParam('sectionId');
            }
    
            // set the content of your entry/experiment
            $entryData = craft()->request->getParam('experiment');
            $entry->getContent()->setAttributes($entryData['fields']);
    
            // try to save the entry
            if(craft()->entries->saveEntry($entry)){
                // fetch the id from the experiment so we can use that as a parentId for the cage
                $entryId = $entry->id;
                // if everything is alright we can save the cage like we did it with the experiment
                if($cagesData = craft()->request->getParam('cage')){
    
                    foreach ($cagesData as $cageData){
                        // like with the entry check for an Id
                        if($cageId = $cageData['entryId']){
                            if(!$cage = craft()->elements->getElementById($cageId)){
                                throw new Exception('Could not find cage with the given Id');
                            }
                        }else{
                            $cage = new EntryModel();
                            $cage->typeId = 5; // you could as well create a hidden field like for the entry itself but you must change the typeId to your entryType Id
                            $cage->sectionId = craft()->request->getParam('sectionId');
                        }
    
                        // save the content as well
                        $cage->getContent()->setAttributes($cageData['fields']);
                        // and set the parent
                        $cage->parentId = $entryId;
                        if(craft()->entries->saveEntry($cage)){
                            $cageId = $cage->id;
                            // and now we are going to do the same with the animals
                            if($rodentsData = $cageData['rodents']) {
                                $rodentErrors = [];
                                foreach ($rodentsData as $rodentData){
                                    // like with the entry check for an Id
                                    if (array_key_exists('entryId', $rodentData) && $rodentId = $rodentData['entryId']) {
                                        if (!$rodent = craft()->elements->getElementById($rodentId)) {
                                            throw new Exception('Could not find rodent with the given Id');
                                        }
                                    } else {
                                        $rodent = new EntryModel();
                                        $rodent->typeId = 6; // change your entryType Id here
                                        $rodent->sectionId = craft()->request->getParam('sectionId');
                                    }
    
                                    $rodent->getContent()->setAttributes($rodentData['fields']);
                                    $rodent->parentId = $cageId;
                                    if (!craft()->entries->saveEntry($rodent)) {
                                        // important note: if there are any errors here, the cage and the experiment is
                                        // saved anyway so you should insert the id of the experiment to the form in order
                                        // to update the form instead of creating a new one again and again
                                        // if the experiment has an Id but the cage/rodent not you'll just update the experiment
                                        // and create new cages/rodents for it
                                        $rodentErrors[] = $rodent->getErrors();
                                    }else{
                                        $rodentErrors = [];
                                    }
                                }
                                // include errors of each item
                                craft()->urlManager->setRouteVariables(array(
                                    'rodentErrors' => $rodentErrors
                                ));
                            }
                        }else{
                            craft()->urlManager->setRouteVariables(array(
                                'cageErrors' => $cage->getErrors()
                            ));
                        }
                    }
    
                }
            }else{
                // handle your errors here.. you can use $entry->getErrors()
                // return them to your frontend and render them in twig or something
                craft()->urlManager->setRouteVariables(array(
                    'experimentErrors' => $entry->getErrors()
                ));
            }
        }
    
  4. Your html template. Note I don't know your fieldLayout so I just included the title and the craft default body field. In order to add your custom fields to the html you need to set the input name to entryType[fields][fieldHandle] so if you want to add a field with the handle foo to the cage you would name it cage[fields][foo]. As for the routing: your action must be pluginHandle/functionName

    <form method="post" accept-charset="UTF-8">
        {{ getCsrfInput() }}
        {# your plugin name and function name #}
        <input type="hidden" name="action" value="test/saveEntry">
        {# the id of your section for me its 4 you need to change this value to your section Id #}
        <input type="hidden" name="sectionId" value="4">
        {# enable the entry #}
        <input type="hidden" name="enabled" value="1">
        {# id of your entry leave it blank to insert a new entry, insert a id to change an existing entry#}
        <input type="hidden" name="entryId" value="">
        {# the id of your experiments entryType for me its 4 you need to change this value #}
        <input type="hidden" name="entryType" value="4">
    
        <label for="title">Title</label>
        <input id="title" type="text" name="experiment[fields][title]">
        <input  type="text" name="experiment[fields][body]">
    
        {# now the cage as a children #}
        <div>
            <label for="title">cage title</label>
            <input type="text" name="cage[0][fields][title]">
            <input type="text" name="cage[0][fields][body]">
            <input type="hidden" name="cage[0][entryId]" value="">
            {# and some Rodents as children for the cages #}
            <div>
                <label for="title">Rodent title</label>
                <input type="text" name="cage[0][rodents][0][fields][title]">
                <input type="text" name="cage[0][rodents][0][fields][body]">
                <input type="hidden" name="cage[0][rodents][0][entryId]" value="">
            </div>
    
            <div>
                <label for="title">Rodent title</label>
                <input type="text" name="cage[0][rodents][1][fields][title]">
                <input type="text" name="cage[0][rodents][1][fields][body]">
                <input type="hidden" name="cage[0][rodents][1][entryId]" value="">
            </div>
    
            <div>
                <label for="title">Rodent title</label>
                <input type="text" name="cage[0][rodents][2][fields][title]">
                <input type="text" name="cage[0][rodents][2][fields][body]">
                <input type="hidden" name="cage[0][rodents][2][entryId]" value="">
            </div>
        </div>
    
        {# and another cage #}
        <div>
            <label for="title">cage title</label>
            <input type="text" name="cage[1][fields][title]">
            <input type="text" name="cage[1][fields][body]">
            <input type="hidden" name="cage[1][entryId]" value="">
            {# and some Rodents as children for the cages #}
            <div>
                <label for="title">Rodent title</label>
                <input type="text" name="cage[1][rodents][0][fields][title]">
                <input type="text" name="cage[1][rodents][0][fields][body]">
                <input type="hidden" name="cage[1][rodents][0][entryId]" value="">
            </div>
        </div>
    
        <input type="submit" value="Publish">
    

    {# just as an example how you could display errors or at least receive the error object #}
    {% if rodentErrors is defined %}
        {% for errors in rodentErrors %}
            {% for fieldHandle, errorMessages in errors  %}
                {{ fieldHandle }} {{ errorMessages[0] }}<br>
            {% endfor %}
        {% endfor %}
    {% endif %}
    

Please let me know when you need help and feel free to ask what you like.
If done right, you'll end up with a structure like this with your form (note: please ignore the titles...) enter image description here

  • Robin this is absolutely perfect - thank you so much for the help! – Graham Winston Nov 10 '17 at 11:59
  • Wow Robin, this is beyond phenomenal. Thank you so much for posting this! I look forward to hopping in and implementing some of the things you did here; really has me thinking about a whole host of wonderful changes to make in workflow. – Jeremiah Nov 10 '17 at 13:40
  • How can this be accomplished in Craft 3? – Andrea DeMers Oct 31 '18 at 22:25

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.