Important announcements from CouchCMS team
12 posts Page 1 of 2
Hi everybody,

Couch v2.1 beta has moved on to become the release version and time now for a new beta - Couch v2.2 beta.
Introducing the defining feature of this release -

Conditional Fields

I am sure you must be familiar with conditional fields but allow me to define them in context of Couch.
As used in Couch, conditional fields are regular <cms:editable> and <cms:input> fields that come to life (in a manner of speaking) only if the conditions set for them (by you, as we'll see later) are fulfilled.

To elaborate 'coming to life', all normal (i.e. non-conditional) fields are always alive - i.e. they are visible, they accept inputs posted through them, validate their inputs (throwing errors if the validations fail), enforce the 'required' status if so set by not allowing blank inputs upon form posting etc.

In contrast to the normal fields described above, conditional-fields behave likewise but *only* when conditions set for them are true. Otherwise, they go invisible, ignore posted inputs, skip validation and disregard the 'required' check.

For all practical purposes, they become non-existent if conditions are not right.

That last observation about them makes them very useful in a variety of situations - the most common being using them in complex forms where we can simplify the user-experience by revealing/hiding relevant fields based on the choices made by the user.

Let us build one such form to illustrate and document the usage of conditional fields.
The form we'll use is actually only a small and simplified portion of a real-world DataBound Form (DBF) and so is not particularly complex but should serve our purpose here.

Before we get into actually creating it, I think a preview of what the finished form would look like will be helpful.
As I mentioned above, this particular form was meant to be used on the front-end as a DBF but even a DBF begins life as a normal Couch template so here is what it looks like in the admin-panel -

00.png
00.png (5.46 KiB) Viewed 1690 times

When the form loads, as you can see above, only one field is visible.
It is a checkbox asking for some further information. If the user chooses not to check the box, the form can be submitted successfully without any problem.

However, if the user checks the box, following is what happens -
01.png
01.png (8.88 KiB) Viewed 1690 times

A dropdown, which was hitherto hidden, becomes visible.
At this point, if you try to save the form it won't succeed if an option is not selected from the dropdown -
02.png
02.png (13.59 KiB) Viewed 1690 times

The dropdown, obviously, was internally part of the form even when the checkbox was not ticked but at that time it did not bother to enforce selecting an option when the form was posted. It is only when the checkbox was ticked that the dropdown 'came alive'.

OK, moving ahead -
select 'One' from the dropdown and you see a set of three new fields appearing on the screen -
03.png
03.png (12.21 KiB) Viewed 1690 times


So now, the dropdown becomes the 'controller' and the three text fields its 'dependents'.
Notice also how the three new fields are 'required' - so posting the form at this point will result in the following
04.png
04.png (19.03 KiB) Viewed 1690 times


Ok, no more harping over the conditionally 'coming alive' point which should be abundantly clear by now.
Take a look at the form displaying all its fields (with 'Three' selected from the dropdown) and we can move on to the coding part -
05.png
05.png (19.1 KiB) Viewed 1690 times


At this point let me define two terms we'll be frequently using in any discussion about conditional field -
In the form above, the dropdown field is the 'dependent' field while the checkbox field is the 'controller' field (because, obviously, it controls the dependent dropdown).

The terms, however, should be seen only in context of describing the relation between two fields.
A field that is 'dependent' in relation to field 'A' can very well be a 'controller' in relation to field 'B' if 'B' happens to depend on it.
This is what happens with the dropdown when it is seen in relation to the nine text fields following it - it now becomes the 'controller' while the text fields become its dependents.

One reason for my stressing these terms is that for conditional-fields to work properly in Couch, any 'dependent' field must always come *after* its 'controlling' field(s) when they are displayed in the form. This is because the conditional logic of a 'dependent' depends on values submitted in the controlling fields and if the dependent happens to be displayed before them, at the point of working out the logic, the controlling field wouldn't even exist and the logic will fail.
This limitation might necessitate making some changes to your existing templates and hence the stress on this point.

1. Using Conditional fields in the backend:

Following is the code defining the editable regions *before* we begin adding the conditional logic to them -
Code: Select all
<cms:template title='Conditional Fields Test'>
    <cms:editable
        type='checkbox'
        name='previous_work_experience'
        label='Any previous employment experience?'
        opt_values='Yes'
    />

    <cms:editable
        type='dropdown'
        name='previous_number_of_employers'
        label='Please select the number of places you have been previously employed'
        opt_values='Please indicate=- | One | Two | Three'
        required='1'
    />
   
    <cms:editable name='previous_employer1' type='row'>
        <cms:editable name='previous_employer1_name' label='Employer Name (1)' type='text' required='1' class='col-md-4' />
        <cms:editable name='previous_employer1_phone' label='Employer Phone (1)' type='text' required='1' class='col-md-4' />
        <cms:editable name='previous_employer1_email' label='Employer Email (1)' type='text' required='1' class='col-md-4' />
    </cms:editable>
   
    <cms:editable name='previous_employer2' type='row'>
        <cms:editable name='previous_employer2_name' label='Employer Name (2)' type='text' required='1' class='col-md-4' />
        <cms:editable name='previous_employer2_phone' label='Employer Phone (2)' type='text' required='1' class='col-md-4' />
        <cms:editable name='previous_employer2_email' label='Employer Email (2)' type='text' required='1' class='col-md-4' />
    </cms:editable>
   
    <cms:editable name='previous_employer3' type='row'>
        <cms:editable name='previous_employer3_name' label='Employer Name (3)' type='text' required='1' class='col-md-4' />
        <cms:editable name='previous_employer3_phone' label='Employer Phone (3)' type='text' required='1' class='col-md-4' />
        <cms:editable name='previous_employer3_email' label='Employer Email (3)' type='text' required='1' class='col-md-4' />
    </cms:editable>
</cms:template>

As you can see, those are standard editable region definitions (if type 'row' seems unfamiliar, please see viewtopic.php?f=8&t=11023. This type is now part if Couch's core.).

Let us begin with making the dropdown a conditional field where it depends upon the checkbox. For brevity, I'll show only the relevant portion of the code.
Code: Select all
    <cms:func _into='my_cond' previous_work_experience=''>
        hide
    </cms:func>

    <cms:editable
        type='dropdown'
        name='previous_number_of_employers'
        label='Please select the number of places you have been previously employed'
        opt_values='Please indicate=- | One | Two | Three'
        required='1'
        not_active=my_cond
    />

Compare the modified code to the definition of the dropdown in previously quoted code and you'll find only two differences -
1. The code block using <cms:func> is defining a function. If this appears unfamiliar, please see viewtopic.php?f=8&t=11368&start=10#p30174 for full discussion.
Code: Select all
    <cms:func _into='my_cond' previous_work_experience=''>
        hide
    </cms:func>

Broadly speaking, a function is nothing more than a collection of Couch statements which can be invoked by calling that function (as it stands at the moment, our function above will simply output 'hide' when called).

The function we are defining above is a little different from the ones we demonstrated in the above mentioned post. This function is 'anonymous' i.e. it does not have a fixed name of its own; rather, we store the function in a variable ('my_cond' in the code above).

Compared to regular functions, anonymous functions have several advantages e.g. they can be stored in arrays, passed as parameters to other tags or even functions as simple variables. But you don't have to get into the nitty gritty of it - the only thing to understand above is that we are creating a function and storing it in a variable named 'my_cond'.

2. The second modification to the original code is where we make use of the 'my_cond' variable we discussed above -
Code: Select all
<cms:editable 
    ..
    not_active=my_cond
/>

As you'll remember, 'my_cond' variable actually holds a function within it.
In the code above, we set this function into the field's parameter named 'not_active'.

Now this is what happens when the form is being rendered by Couch and the system has to decide whether the field being rendered is 'alive' or not -
If the field's definition does not contain the 'not_active' parameter, the field is a normal field and is always considered active (alive).
If, however, that parameter is used in the field's definition and it contains a function (as is the case above), Couch goes ahead and calls the function.

If the function outputs 'hide', it deactivates the field. If the function outputs 'show', it activates the field.
As far as Couch is concerned, it is as simple as that.

It is for the function (actually for us as we'll write the function) now to apply any kind of logic it wishes before deciding what directive to pass back to Couch.
It could be checking against the database for a particular value in a particular template or it could even be checking the current time of the day or whatever. What matters to Couch is whether the function outputs 'show' or 'hide' to activate or deactivate the field.

In our code above, our function is simply returning 'hide' and this will cause the dropdown to always remain inactive no matter what.

Most functions would check the 'controlling' field(s) of the field being processed and make Couch activate/deactivate it accordingly. So, in our case the function should ideally check if the checkbox is ticked. If it is indeed ticked, output 'show' to activate the dropdown. If no, output 'hide' to deactivate it.

The process is a lot simpler in doing than it appears in the explanation so let us do that.

Please take a look again at our function's definition above and note that it has one parameter named 'previous_work_experience'. Take a look at the editable regions being defined in the template and you'll find that 'previous_work_experience' is the name of the checkbox field that is supposed to become the controller of the dropdown field we are currently dealing with.

When Couch calls the function, it first finds all the fields specified as the function's parameters (in our case it is only one but we can have any number depending on the use-case, as we'll see shortly), gets the values currently being held in those fields and then passes those values to the function.

So the code we put into our function needs only check if the values being passed to us fullfil the conditions necessary to activate the field or not and output the directive accordingly.

The controlling 'previous_work_experience' field for our dropdown is a checkbox but for just a little while let us assume it is a dropdown or a radio (you'll remember that these two, as opposed to a checkbox, can only contain a single value thus making our code a tad simpler). With that assumption, the following is the complete working code that will make the dropdown conditionally active only if the controlling field contains 'Yes' -
Code: Select all
    <cms:func _into='my_cond' previous_work_experience=''>
        <cms:if previous_work_experience='Yes'>show<cms:else />hide</cms:if>
    </cms:func>

Not too difficult, is it?
If you so prefer, you may use line-breaks and whitespaces to make the code more readable e.g. the following version works just the same as the one above -
Code: Select all
    <cms:func _into='my_cond' previous_work_experience=''>
        <cms:if previous_work_experience='Yes'>
            show
        <cms:else />
            hide
        </cms:if>
    </cms:func>

Of course, our controlling field is a checkbox and can, potentially, contain multiple selected values so the actual code we'll use is as follows -
Code: Select all
    <cms:func _into='my_cond' previous_work_experience=''>
        <cms:if "<cms:is 'Yes' in=previous_work_experience />">
            show
        <cms:else />
            hide
        </cms:if>
    </cms:func>

As you can see, we are making use of another tag (<cms:is>) to figure out if a particular value is contained within the checkboxes selected values or not. For those familiar with how Couch works with arrays (viewtopic.php?f=5&t=10892), <cms:is> is just a shortened version of <cms:arr_val_exists>.

Try it out and you'll find that the dependent dropdown faithfully follows its controller checkbox in being active or not.
So that is one conditional field taken care of. Before we move on to the three groups of nine text fields that come next, allow me to bring to your notice certain points from the code we have used so far as those will pertain to all conditional fields -

As we have seen, in the part of the form we applied the conditional logic so far, the checkbox was the controller field and the dropdown was its dependent. Did you notice that all the work we did above was *only* on the dependent field i.e the dropdown? The controller (checkbox) field required no amendments whatsoever.

You'll see this in all conditional fields - to make a field conditional, we'll make all the required changes (i.e. creating the logic calculating function and adding it to the field) only to the dependent field. The controlling field demands nothing. So how does Couch figure out which is the controlling field? I think that should be easy for you to answer. The answer is the names of the parameters we use in the function added to the dependent field. It is these names that tie a dependent to its controller(s).

Keep an eye on that point as we make rest of the fields conditional.

Moving on to the text boxes, the use case demands that the first set shows up when 'One' is selected from the dropdown. When 'Two' is selected, the second row shows up *but* the first row also keeps showing. Same with the selection of 'Three' which causes the third row to show up but the first and the second row also keep showing. We have figured out the conditions needed to show these rows.

So now we see that the dropdown becomes the controller and the text fields its dependent.
It is easy to miss at this point that the three rows additionally *also* depend on the checkbox - if the checkbox is not 'Yes', they don't exist.
This has to be explicitly stated (as we'll soon see).
Point here being that conditionality does not cascade - simply because the dropdown is dependent on the checkbox and the text fields are dependent on the dropdown does not mean the text fields are automatically dependent upon the checkbox.

So there are two controllers (the checkbox and the dropdowns) for the text fields.
As we have seen above, the definition of the controllers needs no changes. We specify them indirectly using the parameters of the logic function.

Take a look at how we make the first set of three text fields conditional depending upon two controllers -
Code: Select all
    <cms:func _into='my_cond' previous_work_experience='' previous_number_of_employers=''>
        <cms:if
            "<cms:is 'Yes' in=previous_work_experience />"
            &&
            (previous_number_of_employers='One' || previous_number_of_employers='Two' || previous_number_of_employers='Three')
        >
            show
        <cms:else />
            hide
        </cms:if>
    </cms:func>

    <cms:editable name='previous_employer1' type='row'>
        <cms:editable name='previous_employer1_name' label='Employer Name (1)' type='text' required='1' class='col-md-4' not_active=my_cond />
        <cms:editable name='previous_employer1_phone' label='Employer Phone (1)' type='text' required='1' class='col-md-4' not_active=my_cond />
        <cms:editable name='previous_employer1_email' label='Employer Email (1)' type='text' required='1' class='col-md-4' not_active=my_cond />
    </cms:editable>

Points to note -
1. We have created only one function and added the same to all the three text boxes. That is perfectly ok as the three fields share the same conditions.
2. The function, as expected, defines two parameters - these are the names of the checkbox and the dropdown. When Couch calls the function, these parameters will be automatically filled with the then current values contained in the respective parameters.
3. Notice how the code within the function uses the passed parameters to figure out whether or not to make the field being processed active.
I don't think it'd be too difficult to figure out the logic - "Show if checkbox is 'yes' and dropdown is either 'one' or 'two' or 'three'".
Please see http://docs.couchcms.com/tags-reference/if.html if you need any help with the use of OR and AND logic.

And with that we can wrap up the form by converting the last remaining two rows -
Code: Select all
    <cms:func _into='my_cond' previous_work_experience='' previous_number_of_employers=''>
        <cms:if
            "<cms:is 'Yes' in=previous_work_experience />"
            &&
            (previous_number_of_employers='Two' || previous_number_of_employers='Three')
        >
            show
        <cms:else />
            hide
        </cms:if>
    </cms:func>

    <cms:editable name='previous_employer2' type='row'>
        <cms:editable name='previous_employer2_name' label='Employer Name (2)' type='text' required='1' class='col-md-4' not_active=my_cond />
        <cms:editable name='previous_employer2_phone' label='Employer Phone (2)' type='text' required='1' class='col-md-4' not_active=my_cond />
        <cms:editable name='previous_employer2_email' label='Employer Email (2)' type='text' required='1' class='col-md-4' not_active=my_cond />
    </cms:editable>
   
    <cms:func _into='my_cond' previous_work_experience='' previous_number_of_employers=''>
        <cms:if "<cms:is 'Yes' in=previous_work_experience />" && previous_number_of_employers='Three'>
            show
        <cms:else />
            hide
        </cms:if>
    </cms:func>

    <cms:editable name='previous_employer3' type='row'>
        <cms:editable name='previous_employer3_name' label='Employer Name (3)' type='text' required='1' class='col-md-4' not_active=my_cond />
        <cms:editable name='previous_employer3_phone' label='Employer Phone (3)' type='text' required='1' class='col-md-4' not_active=my_cond />
        <cms:editable name='previous_employer3_email' label='Employer Email (3)' type='text' required='1' class='col-md-4' not_active=my_cond />
    </cms:editable>

The changes to the last two groups are minor variations of what was required for the first group so shouldn't be difficult to understand.

2. Using Conditional fields on the frontend with DBFs:

With the backend completed, let us see what it takes to use conditional fields on the frontend (in a DataBound Form).

The full code for the DBF is contained in the attached files and you'll find that it is just a standard databound form as discussed in the docs (http://docs.couchcms.com/concepts/databound-forms.html). So, here I'll discuss only those portions of it that are specific to implementing the condition field logic.

Following portion showing only the checkbox, the dropdown and the first row of textboxes should suffice for our discussion -
Code: Select all
<div class="form-group">
    <label>Any previous employment experience?</label>
    <div>
        <cms:input name='previous_work_experience' type='bound' />
    </div>
</div>

<div class="form-group" id="k_element_previous_number_of_employers">
    <label for="previous_number_of_employers">Please select the number of places you have been previously employed</label>
    <div>
        <cms:input name='previous_number_of_employers' type='bound' />
    </div>
</div>

<div class="row">
    <div class="col-md-4 form-group" id="k_element_previous_employer1_name">
        <label for="previous_employer1_name">Employer Name (1)</label>
        <cms:input name='previous_employer1_name' type='bound' />
    </div>
    <div class="col-md-4 form-group" id="k_element_previous_employer1_phone">
        <label for="previous_employer1_phone">Employer Phone (1)</label>
        <cms:input name='previous_employer1_phone' type='bound' />
    </div>
    <div class="col-md-4 form-group" id="k_element_previous_employer1_email">
        <label for="previous_employer1_email">Employer Email (1)</label>
        <cms:input name='previous_employer1_email' type='bound' />
    </div>
</div>

As you can see, as is the norm with DBF, we are are using <cms:input>s with type "bound" to render our editable regions.
There is no need to define the logic calculating function etc. again on the frontend as the 'bound' fields automatically pick those from the backend.

If you submit the form at this point, you'll see that the backend enforces the 'required' status of all dependent fields depending upon what was selected in their controlling fields. What does *not* happen is the automatic hiding and showing of the fields on the front-end.

That part actually makes use of some (Couch generated) JS code. While that JS is automatically included in the admin-panel forms, the same does not happen on the frontend.

So while using our conditional fields on the frontend in a DBF, we need to explicitly take some steps (two to be precise) to make the fields show/hide as the controllers change.

1. Add the following at the end of the template just before the closing </BODY> tag -
Code: Select all
<script type="text/javascript">
    //<![CDATA[
    <cms:conditional_js />
    //]]>
</script>

That takes care of including the JS code required to toggle the fields' visibility conditionally.

2. The JS code we added above was generated primarily for the backend and so makes an assumption regarding the DIVs (or any other element) that work as wrappers around the conditional fields (it is actually these wrappers that are hidden or shown).

The assumption is that if a conditional field is named 'xyx', the wrapper would have the ID 'k_element_xyz' (i.e. 'k_element_' prefixed to the field name). That happens automatically in the backend but on the front-end you need to take care of this point yourself. This might necessitate using an extra div around existing markup.

Take a look at our code above and notice how all the divs around the conditional fields have the kind of IDs I mentioned.

[
IMP: This point of having the wrappers with a particular ID is very easy to miss during development (happened to me a few times).
Anytime you find that conditional fields in a DBF are not working as expected, always check two things -
a. do a view-source and make sure the required JS code is being included
b. make sure you have given the correct IDs to the wrappers.
]

That is all that needs to be done for using conditional fields in frontend DBFs.

3. Using Conditional fields on the frontend with normal forms:
So far we have been only using <cms:editable>s as the conditional fields.
Plain <cms:input>s too can be used as conditionals on frontend forms and the process is almost identical.

The attached template contains a second version of the form using only regular <cms:input>s.
I'll discuss here only the points that are specific to such forms.

Following is a portion from the form showing the same inputs as that our DBF sample did above -
Code: Select all
<div class="form-group">
    <label>Any previous employment experience?</label>
    <div>
        <cms:input name="previous_work_experience" type="checkbox" opt_values="Yes" />
    </div>
</div>

<cms:func _into='my_cond' previous_work_experience=''>
    <cms:if "<cms:is 'Yes' in=previous_work_experience />">show<cms:else />hide</cms:if>
</cms:func>

<div class="form-group" id="k_element_previous_number_of_employers">
    <label for="previous_number_of_employers">Please select the number of places you have been previously employed</label>
    <div>
        <cms:input name="previous_number_of_employers" type="dropdown" opt_values='Please indicate=- | One | Two | Three' required='1' not_active=my_cond />
    </div>
</div>

<cms:func _into='my_cond' previous_work_experience='' previous_number_of_employers=''>
    <cms:if
        "<cms:is 'Yes' in=previous_work_experience />"
        &&
        (previous_number_of_employers='One' || previous_number_of_employers='Two' || previous_number_of_employers='Three')
    >
        show
    <cms:else />
        hide
    </cms:if>
</cms:func>

<div class="row">
    <div class="col-md-4 form-group" id="k_element_previous_employer1_name">
        <label for="previous_employer1_name">Employer Name (1)</label>
        <cms:input name="previous_employer1_name" type="text" required='1' size='105' not_active=my_cond />
    </div>
    <div class="col-md-4 form-group" id="k_element_previous_employer1_phone">
        <label for="previous_employer1_phone">Employer Phone (1)</label>
        <cms:input name="previous_employer1_phone" type="text" required='1' size='105' not_active=my_cond />
    </div>
    <div class="col-md-4 form-group" id="k_element_previous_employer1_email">
        <label for="previous_employer1_email">Employer Email (1)</label>
        <cms:input name="previous_employer1_email" type="text" required='1' size='105' not_active=my_cond />
    </div>
</div>

You'll immediately notice that, unlike the type 'bound' inputs we used in the DBF, these are regular inputs and so require *exactly* the same two changes as <cms:editable>s did in "1. Using Conditional fields in the backend" sample i.e. -
a. Creating a function calculating the logic
b. Adding that function to <cms:input> through 'not_active' parameter.

Additionally, since the form is being displayed on the frontend, it will also require the two changes that "2. Using Conditional fields on the frontend with DBFs" did i.e. -
a. Manually adding the generated JS
b. Explicitly giving the required IDs to wrapper divs.

So a normal frontend form is kind of a combo of a backend form and a frontend DBF in terms of making it use conditional fields.

4. Using Conditional fields as repeatable-regions
To wrap up this discussion, let us see how we can use conditional fields with Repeatable regions (http://docs.couchcms.com/concepts/repea ... gions.html).

Although conditional fields will work just the same in regular 'tabular' repeatable regions, the newer 'stacked' version of repeatable regions (documented at "3. Revised repeatable-regions" viewtopic.php?f=5&t=11105) shows their effects in a more marked manner so I'll use that in the sample code below.

Place the following code in your test template -
Code: Select all
<cms:repeatable name='slides' label='Slides' stacked_layout='1'>
     <cms:editable name='text' label='Slide Text' type='text' />
     
     <cms:editable name='slide_type' type='radio' label='Select slide type' opt_values='Image | Video' />
     <cms:editable name='image' label='Image' desc='Recommended image width 2880px' type='image' show_preview='1' preview_width='150' />
     <cms:editable name='video' label='Video Embed Code' type='textarea' height='70' />
</cms:repeatable>

It should result in the creation of following repeatable region -
06.png
06.png (11.39 KiB) Viewed 1690 times

In the repeatable region, we'd like to make the 'image' and 'video' regions dependent on the 'slide_type' radio buttons above them in a way that selecting 'Image' radio button makes the image region visible while selecting 'Video' makes the video region visible.

Following is the amended code that does just that -
Code: Select all
<cms:repeatable name='slides' label='Slides' stacked_layout='1'>
    <cms:editable name='text' label='Slide Text' type='text' />

    <cms:editable name='slide_type' type='radio' label='Select slide type' opt_values='Image | Video' />

    <cms:func _into='my_cond' slide_type=''>
        <cms:if slide_type='Image'>show<cms:else />hide</cms:if>
    </cms:func>
    <cms:editable name='image' label='Image' desc='Recommended image width 2880px' type='image' show_preview='1' preview_width='150' not_active=my_cond />
   
    <cms:func _into='my_cond' slide_type=''>
        <cms:if slide_type='Video'>show<cms:else />hide</cms:if>
    </cms:func>
    <cms:editable name='video' label='Video Embed Code' type='textarea' height='70' not_active=my_cond />
</cms:repeatable>

I am sure you'll find the code used above to make the regions conditionally visible very familiar.
Here is how the repeatable region behaves now -
07.png
07.png (10.21 KiB) Viewed 1690 times

08.png
08.png (7.03 KiB) Viewed 1690 times

The usual caveat of the dependent fields coming after their controlling fields applies within repeatable regions also.

In the code above, the two fields were dependent on a sibling field (i.e. a fellow field defined within the same repeatable region).
If the use-case so demands, there is no problem in making the fields within a repeatable region depend on fields defined outside the repeatable region (i.e. sibling fields of the repeatable region).

Following is a, somewhat contrived, use-case where we have a checkbox before the repeatable region and the fields within the repeatable regions depend on this checkbox (please notice once again that the controlling field i.e. the checkbox is set to display before its dependent fields - this time forcibly by the use of 'order' parameter).
Code: Select all
<cms:editable 
    type='checkbox'
    name='show_slide_type'
    label='Show slide types in table below?'
    opt_values='Yes'
    order='20'
/>
<cms:repeatable name='slides' label='Slides' stacked_layout='1' order='21'>
    <cms:editable name='text' label='Slide Text' type='text' />

    <cms:func _into='my_cond' show_slide_type=''>
        <cms:if "<cms:is 'Yes' in=show_slide_type />">show<cms:else />hide</cms:if>
    </cms:func>
    <cms:editable name='slide_type' type='radio' label='Select slide type' opt_values='Image | Video' not_active=my_cond />

    <cms:func _into='my_cond' show_slide_type='' slide_type=''>
        <cms:if "<cms:is 'Yes' in=show_slide_type />" && slide_type='Image'>show<cms:else />hide</cms:if>
    </cms:func>
    <cms:editable name='image' label='Image' desc='Recommended image width 2880px' type='image' show_preview='1' preview_width='150' not_active=my_cond />
   
    <cms:func _into='my_cond' show_slide_type='' slide_type=''>
        <cms:if "<cms:is 'Yes' in=show_slide_type />" && slide_type='Video'>show<cms:else />hide</cms:if>
    </cms:func>
    <cms:editable name='video' label='Video Embed Code' type='textarea' height='70' not_active=my_cond />
</cms:repeatable>

Following is how the checkbox affects the fields within the repeatable region -
09.png
09.png (6.26 KiB) Viewed 1690 times

10.png
10.png (11.71 KiB) Viewed 1690 times

In the code above you'll see that nothing special needed to be done to make a field defined outside the repeatable region become a controlling field for the fields within. We followed the usual method of indirectly declaring the field as a controller by using its name as a parameter of the function attached with the dependent fields. Couch was smart enough to figure out that 'show_slide_type' field is defined outside the repeatable region and use it.

With that we come to the end of this tutorial.
Before I wrap up, I wonder if you noticed that in all the examples above all the controlling fields were either of type 'checkbox', 'dropdown' or 'radio'? Also that in all the logic functions, we used no other tags except <cms:if>, <cms:else> and <cms:is>?
So are there any kind of limitations on what type of fields can serve as controllers and which tags can be used in the logic functions?

The answer is yes and no.
Allow me to explain -
as you might have figured out from the examples above, there are two separate components attached with all conditional fields -
1. There is a backend component that figures out if a field is active or not and then accordingly enforces or skips validation checks etc.
2. There is a frontend component comprising of JS code that shows/hides the fields depending upon the set conditions.

As for the backend component, it is easy to see that it uses the logic function we attached to all dependent fields to figure out what needs to be done. However, what about the frontend JS code? We did not write any of that.

Actually, Couch uses the same backend logic function and, so to say, 'transpiles' it to automatically generate the matching JS code (do a view-source of the page and you'll see the generated code).

Now this transpiling business is a tricky one and Couch cannot possibly take into consideration the potentially myriad of ways one could define the conditions within the logic function (e.g. one may decide to use <cms:pages> to get value from a certain page for figuring out if a field is active or not) or the ways the controlling fields behave (e.g. a popup relation field).

So, if you want Couch to automatically generate the JS code for you, there are some restrictions to contend with -
1. The controlling fields can only be of type 'checkbox', 'dropdown' or 'radio'. All other types will be ignored possibly resulting in the function not working as expected.
2. Only the logic tags (i.e. <cms:if>, <cms:else>, <cms:else_if>, <cms:not>) and <cms:is> (or its equivalent <cms:arr_val_exists>) tags are recognized for the purpose of generating JS. All other tags are ignored - again resulting in either JS error or the function not working as expected.

If, however, you are ready to code up your own frontend JS, then there are no restrictions whatsoever.
You are free to use any type field as the controller and can use absolutely any Couch tag within the logic function.

So, assuming that is what you intend to do with a certain dependent field, following is what needs to be done to make Couch skip the JS generation -
Add the '_no_js' parameter to the logic function and set it to '1' as in the example below -
Code: Select all
<cms:func _into='my_cond' slide_type='' _no_js='1'>
    <cms:if slide_type='Image'>show<cms:else />hide</cms:if>
</cms:func>

All the dependent fields that the function above is attached with will not have any JS generated for them.
That is to say, that the backend component will still be applied to them but the frontend component would be skipped (the assumption being that it would be provided by you).

I'd say that nine out of ten times, the auto generated JS would suffice. It is only for the rare use-cases that you'd want to disengage the auto-pilot and take over the command yourself.

Hope the community finds the conditional field feature useful.
As always, feedback is solicited.

Thanks.

UPDATE:
If you wish to use your own JS for checkbox, radio or dropdowns, an easy way now is to specify that using a <cms:alt_js> block as follows
Code: Select all
<cms:func _into='my_cond' my_folder=''>
    .. existing Couch code for backend processing ..
   
    <cms:alt_js>
        // JS code placed in this block will be used instead of autogenerating it using the Couch code above
    </cms:alt_js>
</cms:func>

Attachments

Hello fans of the world's greatest CMS!
Version 22 changes the possibilities of building a modern website totally

Conditional Fields make very easy and elegant to make a site with a totally flat SEO structure.
The approach is very easy - the whole site has only one routable template - index.php
There are no other templates - blog, products, manufacturers ... all clonable templates are groups of editable regions managed by functions!

In this forum many developers have asked before - can you hide the name of the template in url address?
The answer was sad - It's impossible.
Now it's possible - BRAVO!

Thanks a lot to KAMRAN! - The most important person here :D
Thank you very much

(IM sorry for my bad English)
This post basically seeks to address a problem faced by @orbital in his post above where he is trying to show/hide groups of editable regions based on which 'folder' is selected but the techniques used in it may serve as a guide for several other use-cases.

As mentioned in the OP, there are two facets to conditional fields - the backend logic and the frontend JS.
Since the backend logic is written in regular Couch tags, there is no restriction on which fields can serve as control fields and what tags may be used in the logic function. The same, unfortunately, cannot be said about the frontend logic as the generated JS forces us to use only checkbox, radio and dropdown editable regions as control fields and a very limited sub-set of Couch tags in logic.

Trying to use the system folders dropdown as a control field brings us face to face with this dichotomy.

If the use-case demands only imposing backend conditionality upon fields dependent on the folders dropdown (i.e. we only need to set the 'required' status on/off and don't not need to show/hide the fields), there is no problem at all - please see the following thread for a real-life example -
viewtopic.php?f=4&t=11526#p30870

However, if the use-case demands (as with @orbital's case) that dependent fields be hidden/shown dynamically depending on which folder is selected in the dropdown we are in a fix - the JS component of conditional field will refuse to even acknowledge the presence of this dropdown because it is not an <cms:editable> region of type 'dropdown'.

As a workaround this problem, I came up with the following solution -
hide the system folders dropdown and in its place use a <cms:editable> of type dropdown with exactly the same contents.

Here are the steps for doing that -
Define our proxy dropdown editable region as follows -
Code: Select all
<cms:editable name='my_folder' label='Folder' type='dropdown' opt_values='folders-dropdown.html'
    dynamic='opt_values' required='1' order='-10' />

As you might recognize, the region above is using the technique described at viewtopic.php?p=2483#p2483 for dynamically pulling values from a snippet.

The snippet being used above is named 'folders-dropdown.html' so you need to create a snippet with that name in your 'couch/snippets' folder and place the following contents in it -
Code: Select all
--Select Folder--=-|
<cms:folders childof='' hierarchical='1' depth='0' orderby='weight' order='asc'>
    <cms:set pad = "<cms:repeat k_level>- &nbsp;&nbsp;&nbsp;</cms:repeat>" />
    <cms:show pad /><cms:show k_folder_title /> = <cms:show k_folder_name /> |
</cms:folders>

And now (after refreshing the template as super-admin) you should see a dropdown below the system folders dropdown showing exactly the same values.

If you wish to remove the minor CSS differences between the two, add the following to the template -
Code: Select all
<cms:config_form_view>
    <cms:style>
        #k_element_my_folder div.select{ width: 36%; min-width: 445px; }
    </cms:style>
</cms:config_form_view>

Before we hide the system dropdown, let us make sure that the value selected in the proxy dropdown is also persisted in the original field. For that make the <cms:config_form_view> as follows -
Code: Select all
<cms:config_form_view>
    <cms:persist
        k_page_folder_id = "<cms:if frm_my_folder ne '-' >
                                <cms:folders root=frm_my_folder depth='1'><cms:show k_folder_id /></cms:folders>
                            <cms:else />
                                 -1
                            </cms:if>"
    />

    <cms:style>
        #k_element_my_folder div.select{ width: 36%; min-width: 445px; }
    </cms:style>
</cms:config_form_view>

Try saving the page. You should see that whatever folder is selected in the proxy, automatically gets passed on into the system folder dropdown as well.

Once we are sure that is happening, we can do away with the now superfluous system dropdown by adding the following directive to the <cms:config_form_view> block -
Code: Select all
<cms:config_form_view>
    ...
    ...
    <cms:field name='k_page_folder_id' hide='1' />
</cms:config_form_view>

And that should be it. Our new folders dropdown is ready to serve as a control field to other regions in the template.

Moving ahead -
for use-cases where some fields are shown/hidden if a specific folder is selected (e.g. 'blog'), it should now be as easy as this -
Code: Select all
<cms:func _into='my_cond' my_folder=''>
    <cms:if my_folder='blog'>show<cms:else />hide</cms:if>
</cms:func>

@orbital's use-case, however, is a bit more involved.
To understand that, suppose following is the folder hierarchy -
Code: Select all
Blog
    Fruit
        Cherry
        Banana
    Meat
Portfolio
    Business
        Corporate
    Personal 

- where 'Blog' and 'Portfolio' are the two top folders with sub-folders beneath them.
The use-case demands that a set of fields (pertaining to blog) be shown when 'Blog' or *any* of its sub-folders is selected.
The same goes with the portfolio related fields.

Obviously, it cannot be a simple case of comparing the name of the selected folder - we now have an entire hierarchy of folders to traverse upwards till the root (e.g. bog or portfolio) is reached before making a decision.

For the backend part we may use <cms:parentfolders> to go through the parents but, as we know, Couch will not know how to generate the JS from this tag.

So, for our use case, we'll put in our own hand crafted JS.
The idea is this - taking as example the folder hierarchy listed above, suppose the following JS object were to be available in our template (each root folder as a key with all its descendants flattened into a single array) -
Code: Select all
<script>
    var my_root_folders = { 
       "blog": [ "blog", "fruit", "cherry", "banana", "meat" ],
       "portfolio": [ "portfolio", "business", "corporate", "personal" ]
    };
</script>

It'd now be a straightforward case of checking if the the selected folder exists within the array "my_root_folders.blog" to categorize it as a blog item.

Here is how we create the array shown above dynamically -
Make the following addition to the <cms:config_form_view> block
Code: Select all
<cms:config_form_view>
    ..
    ..

    <cms:script>
        <cms:set my_root_folders='[]' is_json='1' scope='global' />

        <cms:folders depth='1'>
            <cms:set top_folder = "my_root_folders.<cms:show k_folder_name />" />
            <cms:put top_folder '[]' is_json='1' scope='global' />
           
            <cms:folders root=k_folder_name>
                <cms:put "<cms:show top_folder />." k_folder_name scope='global' />
            </cms:folders>
        </cms:folders>
       
        var my_root_folders = <cms:show my_root_folders as_json='1' />
    </cms:script>
</cms:config_form_view>

Do a view-source of the admin-panel and you should see this JS array added.

Let us use it in our conditional logic -
assuming the following editable region should belong to blog group -
Code: Select all
<cms:editable name='blog_text' label='Blog Text' type='textarea' required='1'  />

add a logic function to it as follows -
Code: Select all
<cms:func _into='my_cond' my_folder=''>


    <cms:alt_js>
        // to do
    </cms:alt_js>
</cms:func>
<cms:editable name='blog_text' label='Blog Text' type='textarea' required='1' not_active=my_cond />

The <cms:alt_js> block within the <cms:func> signals to Couch that it should not try to auto-generate the JS; rather it should take the code from within the <cms:alt_js> block and output it in the generated HTML.

Try doing a view-source and see where the //to do statement appears.

Now modify the code to put in the real code -
Code: Select all
<cms:func _into='my_cond' my_folder=''>


    <cms:alt_js>
        if($.inArray(my_folder, my_root_folders.blog)!=-1){
            return 0; //show
        }
        else{
            return 1; //hide
        }
    </cms:alt_js>
</cms:func>
<cms:editable name='blog_text' label='Blog Text' type='textarea' required='1' not_active=my_cond />

You can see how we are checking the selected folder's name against the 'my_root_folders' array we generated elsewhere.

Try out the admin-panel and the field should show/hide as expected.
That was the JS component. Time to place the backend component in place too.

As mentioned, we could use <cms:parentfolders> for this but, incidentally, while creating the JS 'my_root_folders' array we also first created a Couch array that is available from 'global' context.

So, let us make use of that to complete our logic function that now becomes -
Code: Select all
<cms:func _into='my_cond' my_folder=''>
    <cms:if "<cms:is my_folder in=my_root_folders.blog />" >
        show
    <cms:else/>
        hide
    </cms:if>

    <cms:alt_js>
        if($.inArray(my_folder, my_root_folders.blog)!=-1){
            return 0; //show
        }
        else{
            return 1; //hide
        }
    </cms:alt_js>
</cms:func>
<cms:editable name='blog_text' label='Blog Text' type='textarea' required='1' not_active=my_cond />

The same logic can be used with other field groups as well.

The complete template is attached here for those who wish to try it out without having to type things.

Hope this helps.

Attachments

Hi KK,
This is a super modern solution - thank you very much for the great technique. I already have a site that works with her and everything is fine - thanks again :)
This is absolutely fantastic! v2.1 brought us the Mosaic (among other great upgrades), and I thought that was a complete game changer... v2.2 and Conditional Fields is just blowing me away! CouchCMS gives us the power to develop and manage any type of web application imaginable. It has already been such a great tool to implement with my websites and it continues to impress. Thank you as always kk, I'm very appreciative of your hard work.
WorthDesignCo.com
Where can I download version 2.2?
https://github.com/CouchCMS/CouchCMS
Please use the "Clone or download" button.
what will be in new version?

maybe exist TODO list somewhere?
Site admin tweaks order of blog posts.

Backend:
Code: Select all
<cms:globals>

   <cms:editable type='dropdown' name='editable_orderby' label='Orderby' desc='Tweak ordering'
              opt_values='Publish date = publish_date | Post title = page_title | Weight = weight | Random = random'
              opt_selected='publish_date' order='' />
   
   <cms:func _into='my_cond' editable_orderby=''>
        hide
    </cms:func>
   
   <cms:editable type='dropdown' name='editable_order' label='Direction' desc='Tweak ordering direction'
              opt_values = 'Asc = asc | Desc = desc'
              opt_selected = 'desc' not_active=my_cond order='' />
</cms:globals>


Frontend
Code: Select all
<cms:pages show_future_entries='0' limit="<cms:get_global 'editable_limit' />" include_subfolders='1' 
           orderby="<cms:get_global 'editable_orderby' />" order="<cms:get_global 'editable_order' />" >
   
   ....
</cms:pages>


The idea is to not show editable_order if editable_orderby chosen as 'random'.
Does conditional fields work in "template globals"?
@trendoman
Does conditional fields work in "template globals"?

Yes, they do.
There is a slight hitch, however, in that the <cms:globals> tag recognizes only a finite set of Couch tags and <cms:func> is not amongst those.

In your sample code, if you move the func block to place it outside the <cms:globals> block, things will begin to function as expected -
Code: Select all
<cms:func _into='my_cond' editable_orderby=''>
    hide
</cms:func>
   
<cms:globals>

   <cms:editable type='dropdown' name='editable_orderby' label='Orderby' desc='Tweak ordering'
              opt_values='Publish date = publish_date | Post title = page_title | Weight = weight | Random = random'
              opt_selected='publish_date' order='' />

   <cms:editable type='dropdown' name='editable_order' label='Direction' desc='Tweak ordering direction'
              opt_values = 'Asc = asc | Desc = desc'
              opt_selected = 'desc' not_active=my_cond order='' />
</cms:globals>

Hope this helps.
12 posts Page 1 of 2

Who is online

In total there is 1 user online :: 0 registered, 0 hidden and 1 guest
(based on users active over the past 5 minutes)

Users browsing this forum: No registered users and 1 guest

cron