| CARVIEW |
Freeform and Matrix
First of all, you need to have Freeform installed, a free from managing module by the add-on veterans at Solspace. In Freeform, you can define different form fields that can be populated by any number of forms. The only down side is, you still have to create the HTML for those forms in your templates and there’s no good way to automate this. Until now.
Secondly, you’ll need Pixel & Tonic’s Matrix. This field type allows you to create tabular data in channel entries, which can be used in a plethora of ways. You define the columns, and the client can add as many rows to the matrix as desired. Pixel & Tonic is also responsible for many other great field types, like P&T List which we’ll use in this example, too.
Channel and Fields
Now, to start automating from creation, we’ll create a designated channel called Forms. To this channel, we’ll add a couple of custom fields, for example:
form_description: a textarea for adding a form description;form_fields: a matrix field with all the form fields for this form;form_feedback: a textarea for the message displayed to the user after submitting the form;form_notify: a text input field where you can enter email addresses that will receive form notifications.
The fun starts with the Matrix field. To that, we’ll add the following columns:
field_name: the Low Freeform Field;field_label: the label associated with the form field;field_type: a drop down to determine the type of input field, eg. text, textarea, select, radio, checkbox…field_options: if the type is select, radio or checkbox, you can use this field to define the different options. P&T List is really useful here;field_required: a checkbox to select whether this field is required or not.
You could add more columns if needed. For example, you could add a field_class for custom styling or field_notes if you need to display per-field instructions. Below is a screenshot of the publish form with the above settings, but as you can imagine, the possibilities are endless.
The template
To bring it all together, we need to create a clever template. You could get creative and use an embed and relationships to add forms to other entries, but in this case, we’ll add the code to the index template in the forms template group. A link to a single form would look like this: domain.com/forms/my-custom-form. Here’s some code to go with it:
{exp:channel:entries channel="forms" limit="1"}
<h1>{title}</h1>
{if segment_3 == 'thanks'}
{form_feedback}
{/if}
{if segment_3 != 'thanks'}
{form_description}
{exp:freeform:form
collection="{title}"
notify="{form_notify}"
required="{form_fields search:field_required="y" backspace="1"}{field_name}|{/form_fields}"
return="{segment_1}/{segment_2}/thanks"}
<fieldset>
{form_fields}
<label for="{field_name}">
{field_label}
{if field_required == 'y'} <span class="required">*</span>{/if}
</label>
{if field_type == 'text'}
<input type="text" name="{field_name}" id="{field_name}" />
{if:elseif field_type == 'textarea'}
<textarea name="{field_name}" id="{field_name}" rows="10" cols="40"></textarea>
{if:elseif field_type == 'select'}
<select name="{field_name}" id="{field_name}">
{field_options}
<option value="{item}">{item}</option>
{/field_options}
</select>
{if:elseif field_type == 'radio'}
{field_options}
<label><input type="radio" name="{field_name}" value="{item}" /> {item}</label>
{/field_options}
{if:elseif field_type == 'checkbox'}
{field_options}
<label><input type="checkbox" name="{field_name}[]" value="{item}" /> {item}</label>
{/field_options}
{/if}
{/form_fields}
<button type="submit">Submit form</button>
</fieldset>
{/exp:freeform:form}
{/if}
{/exp:channel:entries}
After opening the channel:entries tag, we’ll open the freeform:form tag, using the entry’s title as the form collection. Using Matrix’ search option, we’ll define freeform’s required parameter. We can set more parameters, depending on the channel fields available.
Then we’ll loop through the form_fields. Use an advanced conditional to account for all the different input types and loop through the field_options if necessary. As you can see, the output is fully customisable.
Awesomeness achieved
This approach allows for fairly flexible custom forms, and with a bit of creative thinking, you could make it even better.
[ETA] Great minds think alike, so it seems. Here’s a write-up for the same idea in EE1 that I wasn’t aware of.
]]>Pro tip: don’t ever, ever nest a channel:entries tag inside another tag that loops. Srsly. Don’t. #eecms
…which sparked some response. The main reason for not nesting ExpressionEngine’s channel:entries tags is performance. Let me explain with an example.
Category archives
EE’s category archives tag is limited. To overcome this, some people use a channel:entries tag inside a channel:categories tag. Something like this:
{exp:channel:categories channel="news" style="linear" disable="category_fields"}
<h2>{category_name}</h2>
{exp:channel:entries channel="news" category="{category_id}" dynamic="no" disable="member_data|pagination|category_fields"}
<h3>{title}</h3>
{news_body}
{/exp:channel:entries}
{/exp:channel:categories}
Now, this will work. However, given enough content, this will slow your site down considerably. The number of queries that will be executed to generate the output is not fixed. Instead, a new channel:entries tag will be generated for each category found, which will result in an unknown number of queries. Dozens at least, hundreds if you have lots of content.
I did a test where I pasted the above code in an empty template, turned on the profiler and checked the number of queries used. With 4 categories and 6 entries, it took 41 queries to generate the page. Then I added a new entry in a new category and reloaded the page. This time it took 45 queries. Just imagine what would happen if there are 20 categories with 200 entries. And that’s not even a lot.
Fix it
To fix this potential problem, we need to make sure the amount of queries needed to generate the page is fixed. We can achieve this by un-nesting the two tags and using PHP on Output. The code is a bit more complicated, but it will improve performance dramatically. Here’s the code in full. I’ll explain each bit below.
<?php
$entries = array();
{exp:channel:entries channel="news" dynamic="no" disable="member_data|pagination|category_fields"}
$entry =<<<EOE
<h3>{title}</h3>
{news_body}
EOE;
{categories}
$entries[{category_id}][] = $entry;
{/categories}
{/exp:channel:entries}
?>
{exp:channel:categories channel="news" style="linear" disable="category_fields"}
<h2>{category_name}</h2>
<?php if (isset($entries[{category_id}])): ?>
<?=implode("\n", $entries[{category_id}])?>
<?php endif; ?>
{/exp:channel:categories}
First, we’ll define an array ($entries). Using the channel:entries tag — without a category="" parameter — we’ll fill that array with entries grouped by category. After putting the template chunk for an entry in the $entry variable, we’ll loop through each related category and add the entry to $entries. What we end up with, is a nested array with all the entries in the right order, grouped by category_id.
To output the entries, we’ll simply use the channel:categories tag to display each category. We then check if there are entries set in the $entries array for that category_id. If there are, we’ll echo them to the page after implode-ing them to a string. All done.
Performance FTW
So, exactly how many queries did we save using this method? I reloaded the page using the above code, with 5 categories and 7 entries: 29 queries. That’s right, 16 less than the nested method. How many queries would it take if there are 20 categories with 200 entries? 29.
]]>Restaurants die meedoen aan deze actie moeten dus voor 25 euro een voor-, hoofd- en nagerecht fabriceren voor een volle tent. Zo ook het restaurant dat ik aandeed, vorige week: La Cloche in Leiden, wat bekend staat als één van de betere restaurants uit de buurt. Echter, het voer dat ik voorgeschoteld kreeg kon ik op z’n positiefst beschrijven als saai en inspiratieloos: zalmtartaar met mosterd-honing dressing (*gaap*), confit de canard (uit blik) met gewokte groente (teveel gembersiroop) en roseval aardappeltjes (één partje op mijn bord), en boerenroomijs met warme (= 2 graden warmer dan het ijs) bosbessensaus en (nou komt het) slagroom.
Slaat de Restaurantweek nu niet zijn doel voorbij? Er werd productie gedraaid, geen eten bereid. En ik neem aan dat La Cloche niet de enige is geweest met deze instelling. Mensen die door de lage prijsdrempel eindelijk eens proberen om bij een zogenaamd toprestaurant uit eten te gaan, komen hierdoor dus bedrogen uit. Nee, ik betaal liever iets meer en eet goed, zoals twee weken geleden, bij La Rive.
]]>PHP in templates
My presentation slash workshop was about using PHP in templates, but also covered EE’s template parse order. Prior to the presentation, I dove right into EE’s template class and created a little cheat sheet as a handout people could refer to during my talk. I think it went fairly well and judging from the feedback I’ve read and heard, my audience felt the same way. Here are the slides, if you haven’t already seen them on SlideShare:
Next up
The next EECI conference will be held, like the first edition, in my home town Leiden. Robert already cooked up some excellent speakers and there will also be a so-called DevDay on October 1st (also my birthday, coincidently). What will happen on the DevDay is yet to be announced, but one thing’s for sure: I’ll be there eating cake.
]]>Of course, the best way to explore a foreign country is with the help of the locals. So here’s what I thought would be fun… Why not ask some fellow web geeks if they mind having me and my girlfriend over? I think it would be a fantastic way to meet people and see the country.
To be precise, we will be traveling through California from June 3rd till June 17th, and we would love to stay one or two nights. Drop me an email if you live somewhere around or in between SF and LA and you’re up for it: low@loweblog.com
]]>