Important announcements from CouchCMS team
22 posts Page 1 of 3
Hi everyone,

As anyone who has worked with Couch would have noticed, Couch variables have so far been only simple strings or numbers.
A recent conversation with @trendoman led to the development of this feature where now Couch variables can contain multiple values thus acting like Arrays in JavaScript or PHP.

To give you a full context (and also as a lazy way of documenting how this feature works), I am reproducing verbatim the conversation I had with @trendoman.

==================================
Hi Kamran,

How about this
Code: Select all
<cms:capture into='climate'>
{
   "Russia" : {
      "Moscow" : "cold",
      "Sochi"   : "warm"
   }
}
</cms:capture >

Climate in Moscow: <cms:key 'Russia.Moscow' from=climate />


Variation:

Code: Select all
<cms:set lang='es' />
<cms:set i18 = '
{
    "en" : {
        "app" : {
            "greet" : "Hello!",
        }
    },
    "es" : {
        "app" : {
            "greet" : "Hola!"
        }
    }
}
' />

Maybe a little more advanced parsing:
<h3><cms:key 'i18[lang].app.greet' /></h3>


================================================
Hi Anton,

Hope you recall the conversation we had some time back regarding 'multi-string' variables (the thread is attached to this message).

I have done some work on the topic
..
..
try out the following in any of your template -

Code: Select all
<cms:capture into='climate' is_json='1'>
{
   "Russia" : {
      "Moscow" : "cold",
      "Sochi"   : "warm"
   }
}
</cms:capture >

Climate in Moscow: <cms:show climate.Russia.Moscow /><br>
Climate in Sochi: <cms:show climate.Russia.Sochi />

output:
Climate in Moscow: cold
Climate in Sochi: warm

You'll remember, it is one of the example you used earlier.
Point to note is that we are using regular <cms:show> and <cms:capture>.
The only new point above is the use of 'is_json' with cms:capture.

Another of your example -
Code: Select all
<cms:set i18 = '
{
    "en" : {
        "app" : {
            "greet" : "Hello!"
        }
    },
    "es" : {
        "app" : {
            "greet" : "Hola!"
        }
    }
}
' is_json='1' />

<cms:set lang='es' />
<cms:get "i18.<cms:show lang />.app.greet" /><br>

<cms:set lang='en' />
<cms:get "i18.<cms:show lang />.app.greet" /><br>

output:
Hola!
Hello!

Again the only new thing is 'is_json' with cms:set.
Rest is regular Couch code.

So news is that now Couch natively supports 'arrays' which makes possible the use of the 'dotted' syntax we saw above.
All the 'setter' tags (cms:set, cms:capture, cms:put), all the 'getter' tags (cms:show, cms:get), all 'conditional' tags(cms:if, cms:else, cms:else_if) and the iterator tag cms:each now recognize 'array' as a valid type.

The syntax for initially setting an array is a valid JSON string with the 'is_json' parameter set to '1'.
Once a variable is set as an array, further interaction with it can be done using the 'dot' syntax alone, as seen above.
Consider the following, for example -
Code: Select all
<cms:set my_climate=climate.Russia />
<cms:show my_climate.Sochi />

output:
warm

In the example above, the 'my_climate' automatically becomes an array because we used a preexisting array variable (climate.Russia) to set it.
There was no need to use 'is_json' while setting it.

IMP: Using a valid JSON to set an array variable is crucial as anything invalid will result in the variable not getting set at all.
For example, your second example (<cms:set i18 />/> originally had a syntax error and so did not work in the first try. It would be a good idea to use a validator (e.g. https://jsonformatter.curiousconcept.com/ that I did) to first verify the validity of a complex JSON string.

OK, moving on and extending your example a bit further now, try adding this -
Code: Select all
<cms:set climate.India='[]' is_json='1' />
<cms:set climate.India.Mumbai='pleasant' />
<cms:set climate.India.Delhi='moderate' />

<cms:set climate.Miami = 'great' />

Climate in Mumbai: <cms:show climate.India.Mumbai /><br>
Climate in Delhi: <cms:show climate.India.Delhi /><br>
Climate in Miami: <cms:show climate.Miami /><br>

output:
Climate in Mumbai: pleasant
Climate in Delhi: moderate
Climate in Miami: great

Example above shows how we can dynamically add to existing arrays.

To see a textual representation of an array variable (for perhaps debugging), we can ask Couch to spit it out as JSON, as in the following example -
Code: Select all
<cms:show climate as_json='1' /><br>

output:
{"Russia":{"Moscow":"cold","Sochi":"warm"},"India":{"Mumbai":"pleasant","Delhi":"moderate"},"Miami":"great"}

Moving on, all the examples we saw above set values within arrays (e.g. 'Russia', 'India') using 'keys' (e.g. 'Moscow', 'Mumbai' etc.).
If we so wish, we can set values directly without using keys, for example as follows -
Code: Select all
<cms:set climate.Utopia='[]' is_json='1' />

<cms:set climate.Utopia. = 'unknown' />
<cms:set climate.Utopia. = 'uncertain' />
<cms:set climate.Utopia. = 'finicky' />

As you can see, we are still using the 'dots' but there are no named keys after those.
Internally, Couch automatically creates numeric keys for you (using the last unnamed key) for each unnamed key.
So, in the example above, we actually created the following variables -
Code: Select all
<cms:show climate.Utopia.0 /><br>
<cms:show climate.Utopia.1 /><br>
<cms:show climate.Utopia.2 /><br>

output:
unknown
uncertain
finicky

Let us take a look at how the array internally looks like -
Code: Select all
<cms:show climate as_json='1' /><br>

output:
{"Russia":{"Moscow":"cold","Sochi":"warm"},"India":{"Mumbai":"pleasant","Delhi":"moderate"},"Miami":"great","Utopia":["unknown","uncertain","finicky"]}

Let us now see how 'conditionals' work with arrays -
Code: Select all
<cms:if climate.Russia.Moscow=='cold'>
    Cold!
<cms:else />
    Warm
</cms:if>

As you can see, we can use the dotted syntax with no problems.

Finally, take a look at how we can use cms:each to iterate through the arrays -
Code: Select all
<cms:each climate>
    <cms:show key /> - <cms:show item /><br>
</cms:each>

output:
Russia - Array
India - Array
Miami - great
Utopia - Array

In the example above, the 'climate' variable is an array with three of its keys in turn being arrays themselves.
The cms:each loop correctly reported this (outputting the term 'Array'). The 'Miami' key was a simple variable (i.e. not an array) and so its value was outputted verbatim.

The real meat could be had by looping through the sub-arrays themselves, for example -
Code: Select all
<cms:each climate.Russia>
    <cms:show key /> - <cms:show item /><br>
</cms:each>

output:
Moscow - cold
Sochi - warm

Code: Select all
<cms:each climate.India>
    <cms:show key /> - <cms:show item /><br>
</cms:each>

output:
Mumbai - pleasant
Delhi - moderate

To loop through all countries with their cities in our example, we can use the following (shown in two steps for clarity) -
Step 1-
Code: Select all
<cms:each climate>
    <cms:if "<cms:is_array item />">
        <cms:show key /> (<cms:array_count item />)<br>
    <cms:else />
        <cms:show key /> - <cms:show item /><br>
    </cms:if>   
</cms:each>

output:
Russia (2)
India (2)
Miami - great
Utopia (3)

Step 2 -
In the block above that shows we are dealing with an array, add the <cms:each> once again to loop through its descendants like this -
Code: Select all
<cms:each climate>
    <cms:if "<cms:is_array item />">
        <cms:show key /> (<cms:array_count item />)<br>
       
        <cms:each item>
            <cms:if "<cms:is_array item />">
                -- <cms:show key /> (<cms:array_count item />)<br>
            <cms:else />
                -- <cms:show key /> - <cms:show item /><br>
            </cms:if>   
        </cms:each>

    <cms:else />
        <cms:show key /> - <cms:show item /><br>
    </cms:if>   
</cms:each>   

output:
Russia (2)
-- Moscow - cold
-- Sochi - warm
India (2)
-- Mumbai - pleasant
-- Delhi - moderate
Miami - great
Utopia (3)
-- 0 - unknown
-- 1 - uncertain
-- 2 - finicky

OK, Anton, that is all I have for now. There is a lot more that can be done with arrays (sorting, recursively iterating through all levels etc.) but I have left that for perhaps the future.
..
==============================================

Alright, coming back to present - this is a feature that will be useful as we go ahead and the newer addons begin exposing their data as arrays, So, for example, we could be iterating through cart items as follows -
Code: Select all
<cms:each cart_items as='item'>
    <cms:show item.price />
    <cms:show item.qty />
    <cms:show item.discount />
</cms:each>

For now, if anyone is willing to try out this feature, please download Couch from GitHub -
https://github.com/CouchCMS/CouchCMS

Feedback solicited, as always.

Thanks.
Really really nice work !

Big Thanks!
I load frameworks and write bugs on top of them, after that I rearrange the code so that it looks like a cool product.
This is some powerful stuff for app building, just saw this thread!

I was doing something similar by storing the JSON string and using CMS:PHP to handle the data later, this changes everything. :D Wow awesome stuff!
Hi Kamran,

Is it possible to set values directly without using keys, if value is an array? I am referring to this sample:
Code: Select all
<cms:set climate.Utopia='[]' is_json='1' />
<cms:set climate.Utopia. = 'unknown' />

So far I had to provide numeric keys starting with '0' and use 'cms:capture'. Isn't it possible with 'cms:set' yet?
Code: Select all
{ 
    "0":{ "firstname":"Marilyn", "lastname":"Monroe" } ,
    "1":{ "firstname":"Abraham", "lastname":"Lincoln" } ,
    "2":{ "firstname":"Christopher", "lastname":"Columbus" }
}

Thanks


UPDATE: There is a complete tutorial, including many new examples and nuances, available here:

Core Concepts » Couch Arrays

Contents:
- Multi-level variables or Arrays
- - Syntax
- - Setting arrays
- - Iterate arrays
- Practice
- - cms:capture vs. cms:set
- - Objects vs. Arrays
- - Adding to arrays
- - Dynamic values
- - - Escaping
- - - Multi-word keys
- - - cms:get and cms:put
- - - Loops
- - Tags
- - Related tags
- - Related pages
Another question - is it possible to get the k_count by the known key?
Code: Select all
<cms:set climate.India='[]' is_json='1' />
<cms:set climate.India.Mumbai='pleasant' />
<cms:set climate.India.Delhi='moderate' />
<cms:set climate.India.Thiruvananthapuram='hot' />

I would like to get '1' if provide climate.India.Delhi or '0' if it's climate.India.Mumbai without iterating through all the keys :)
@trendoman, you asked -
Is it possible to set values directly without using keys, if value is an array?
So far I had to provide numeric keys starting with '0' and use 'cms:capture'. Isn't it possible with 'cms:set' yet?

There should be no difference between cms:capture and cms:set.
For example, the sample code you used with cms:capture, works the same with cms:set -
Code: Select all
<cms:set persons='
    {
        "0":{ "firstname":"Marilyn", "lastname":"Monroe" } ,
        "1":{ "firstname":"Abraham", "lastname":"Lincoln" } ,
        "2":{ "firstname":"Christopher", "lastname":"Columbus" }
    }
    ' is_json='1' />
<cms:show persons as_json='1' /><br>

<cms:each persons as='person' >
    <cms:show person.firstname /> <cms:show person.lastname /> <br />
</cms:each>

If you remove the keys from your code so as to make it as follows -
Code: Select all
{ 
    { "firstname":"Marilyn", "lastname":"Monroe" } ,
    { "firstname":"Abraham", "lastname":"Lincoln" } ,
    { "firstname":"Christopher", "lastname":"Columbus" }
}

the code will fail in both cms:capture as well as cms:set.

Reason? Because it is invalid JSON (https://jsonformatter.curiousconcept.com/)

'Arrays' in JavaScript, as opposed to PHP *cannot* have keys while 'Objects' *always* have keys.
The modified (and faulty) code above is declaring an outermost object (curly braces) but is not using keys for its child elements. Hence it fails.

Modify it to change just the outermost element to be an Array (change the curly braces to make them square brackets) and it now becomes valid JSON -
Code: Select all
[ 
    { "firstname":"Marilyn", "lastname":"Monroe" } ,
    { "firstname":"Abraham", "lastname":"Lincoln" } ,
    { "firstname":"Christopher", "lastname":"Columbus" }
]

So finally, here is the code with cms:set that does not use keys (by using array) -
Code: Select all
<cms:set persons='
    [
        { "firstname":"Marilyn", "lastname":"Monroe" } ,
        { "firstname":"Abraham", "lastname":"Lincoln" } ,
        { "firstname":"Christopher", "lastname":"Columbus" }
    ]
    ' is_json='1' />

<cms:show persons as_json='1' /><br>

<cms:each persons as='person' >
    <cms:show person.firstname /> <cms:show person.lastname /> <br />
</cms:each>

Outputs -
Code: Select all
Marilyn Monroe 
Abraham Lincoln
Christopher Columbus

As you can see, it is exactly the same as the version where you explicitly provided keys.

For documentation sake, following alternative syntax would also be the exact equivalent -
Code: Select all
<cms:set persons='[]' is_json='1' />
<cms:set persons. = '{ "firstname":"Marilyn", "lastname":"Monroe" }' is_json='1' />
<cms:set persons. = '{ "firstname":"Abraham", "lastname":"Lincoln" }' is_json='1' />
<cms:set persons. = '{ "firstname":"Christopher", "lastname":"Columbus" }' is_json='1' />


Hope this answers your query.

As for the second question you had, I am afraid I don't think that is currently possible.
@KK, thank you, it is exactly what I meant and I am so glad to see these new samples :)
@KK, another question:
Is it possible to dynamically fetch data into a multi-value variable, using 'set' tag?
Yes, of course it is possible.

In your example, however, the use of double-quotes as value will confuse the parser so we need to escape them as follows -
Code: Select all
<cms:set company. = "{ \"country\" : \"<cms:show c_country />\" }" scope='global' is_json='1' />

For such cases, using cms:capture would be simpler as it wouldn't need that escaping.

Hope this helps.
I am adding a how to work with 'multi word' keys after my experiments.

Initializing a sample variable 'person'.
Code: Select all
<cms:capture into='person'  is_json='1' >
{
    "First Name" : "Jane"
}
</cms:capture>

Alternative way of doing the same would be creating an empty variable and then adding to it:
Code: Select all
<cms:set person = '[]' is_json='1' scope='global' />
<cms:capture into='person.First Name'  is_json='1' >

"Jane"

</cms:capture>

Alternative way of doing the same would be using 'set' tag.
Code: Select all
<cms:set person = '{"First Name" : "Jane"}' scope='global' is_json='1' />


Let's add a valid string as our new key 'Last Name'.
It is not possible with <cms:set /> to add keys with spaces (or I did not find a way?), so use 'capture' for this.
Code: Select all
<cms:capture into='person.Last Name'  is_json='1' >

"Doe"

</cms:capture>


'set' can be used to add regular-looking keys without spaces:
Code: Select all
<cms:set person.gender = 'female' scope='global' />


Note that 'capture' tag executes in global scope by default, it means that 'set' in my sample must also use the same global scope or things would mix up. 'capture' tag has only 2 scopes: global and parent, while 'set' can be local (by default), global and parent. This knowledge is very important, as much time can be spent debugging why some values are not appearing. If you mix tags and every other time - Watch Your Scope!

Let's output our multi-value variable. Starting simple:
Code: Select all
<cms:show person as_json='1' scope='global' />

{"First Name":"Jane","Last Name":"Doe","gender":"female"}

Output gender:
Code: Select all
<cms:show person.gender as_json='1' scope='global' />

female

It is required to use 'get' tag to output Last Name or First Name, because it allows to put variable key in quotes, unlike 'show' tag.
Code: Select all
<cms:get 'person.Last Name' as_json='1' scope='global' />

Doe


It will definitely save a looot of figuring out. :)
22 posts Page 1 of 3
cron