Set nested property in Freemarker template - alfresco

I'm working in Alfresco and I'd like to create my own custom component (for a form) extending an exiting one. In my case "number.ftl".
I'd like to provide the ability to set a defaultValue, so that if field.value is empty the default value will be used
default-number.flt
<#if field.control.params.defaultValue?? && !field.value?has_content>
<#assign field.value=field.control.params.defaultValue>
</#if>
<#include "/org/alfresco/components/form/controls/number.ftl" />
It complains about the dot (.) in field.value:
Caused by: freemarker.core.ParseException: Parsing error in template "org/alfresco/components/form/controls/year.ftl" in line 3, column 23:
Encountered ".", but was expecting one of:
"="
"in"
">"
How do I set the variable?
UPDATE
I've tried as suggested by abarisone (but I can't set the variable after importing "number.ftl" or the old value would be used):
<#if field.control.params.curretYearDefaultValue?? && !field.value?has_content>
<#assign value in field>
${.now?string("yy")}
</#assign>
</#if>
<#include "/org/alfresco/components/form/controls/number.ftl" />
but i get:
FreeMarker template error: For "#assign" namespace: Expected a namespace, but this evaluated to an extended_hash+string (org.alfresco.web.scripts.forms.FormUIGet$Field wrapped into f.e.b.StringModel): ==> field [in template "org/alfresco/components/form/controls/year.ftl" at line 3, column 27]
UPDATE2 (solved)
As suggested by ddekany here a working solution
<#if field.control.params.useCurretYearAsDefaultValue?? && field.control.params.useCurretYearAsDefaultValue = "true" && !field.value?has_content>
${field.setValue(.now?string("yyyy"))!}
</#if>
<#include "/org/alfresco/components/form/controls/number.ftl" />

FreeMarker templates don't support data-model modification. They only meant to read the stuff that was already prepared by Java code. You can set top-level variables only because those aren't part of the data-model (they are in a scope in front of the top-level data-model variables). And so FreeMarker doesn't even have a syntax for modifying a subvariable. But, a back door might exists to achieve what you want. Depending on configuration settings and on what field is on Java-level, ${field.setValue(...)} might works. It's quite a hack to do that, mind you. Manipulating form content from a template, that stinks. You should call out to some Java helper method at least.

If you look at the Freemarker assign reference, you will see that when you use assign you usually write <#assign name=value> where name is the name of the variable. It is not expression.
In fact the compiler complains about the fact it was expecting a "=" or "in".
The latter makes sense if you look further to this:
<#assign name in namespacehash>
capture this
</#assign>
explained like that:
If you know what namespaces are: assign directive creates variables in namespaces. Normally it creates the variable in the current namespace (i.e. in the namespace associated with the template where the tag is). However, if you use in namespacehash then you can create/replace a variable of another namespace than the current namespace. For example, here you create/replace variable bgColor of the namespace used for /mylib.ftl:
<#import "/mylib.ftl" as my>
<#assign bgColor="red" in my>
So I can suggest you to import the number.ftl file first and then search for the variable you're wishing to set.

Related

What's the best way for a formula to provide attribute defaults?

Chef has a very elaborate (maybe too much so) scheme for cookbooks to provide default values of attributes. I think Puppet does something similar with class parameters where defaults usually go into params.pp. With Salt, I've seen:
specifying default value in dictionary/pillar lookups.
the grains.filter_by merging of default attribute values with user-provided pillar data (e.g., map.jinja in apache-formula)
in a call to file.managed state, specifying default attribute values as the defaults parameter and user-specified pillar data as context.
Option 1 seems to be the most common, but has the drawback that the template file becomes very hard to read. It also requires repeating the default value whenever the lookup is done, making it very easy to make a mistake.
Option 2 feels closest in spirit to Chef's approach, but seems to expect the defaults broken down into a dictionary of cases based on some filtering attribute (e.g., the OS type recorded in grains).
Option 3 is not bad, but puts attribute defaults into the state file, instead of separating them into their own file as they are with option 2.
Saltstack's best practices doc endorses Option 2, except that it doesn't address how to merge defaults with user-specified values without having to use grains.filter_by. Is there any way around it?
Note: The behavior of defaults.get changed in version 2015.8, and so the method described here no longer works. I am leaving this answer for users of older versions and will post a similar method for current versions.
defaults.get coupled with a defaults.yaml file should do what you want. Assume your formula tree looks like this:
my-formula/
files/
template.jinja
init.sls
defaults.yaml
# my-formula/init.sls
my-formula-conf-file:
file.managed:
- name: {{ salt['defaults.get']('conf_location') }}
- source: {{ salt['defaults.get']('conf_source') }}
... and so on.
# defaults.yaml
conf_location: /etc/my-formula.conf
conf_source: salt://my-formula/files/template.jinja
# pillar/my-formula.sls
my-formula:
conf_location: /etc/my-formula/something.conf
This will end with the configuration file placed at /etc/my-formula/something.conf (the pillar value) using salt://my-formula/files/template.jinja as the source (the default, for which no pillar override was supplied).
Note the unintuitive structure of the pillar and defaults files; defaults.get expects defaults.yaml to have its values at the root of the file, but expects the pillar overrides to be in a dictionary named after the formula, because consistency is for the weak.
The documentation for defaults.get gives its example using defaults.json instead of defaults.yaml. That works but I find yaml much more readable. And writable.
There is a bug using defaults.get from inside a managed template rather than within the state file, and as far as I know it's still open. It can still be made to work; the workaround is behind the link.
The behavior of defaults.get changed in 2015.8, possibly due to a bug. This answer describes a compatible method of getting the same results in (at least) 2015.8 and later.
Suppose your formula tree looks like this:
something/
files/
template.jinja
init.sls
defaults.yaml
# defaults.yaml
conf_location: /etc/something.conf
conf_source: salt://something/files/template.jinja
# pillar/something.sls
something:
conf_location: /etc/something/something.conf
The idea is that formula defaults are in defaults.yaml, but can be overridden in pillar. Anything not provided in pillar should use the value in defaults. You can accomplish this with a few lines at the top of any given .sls:
# something/init.sls
{%- set pget = salt['pillar.get'] %} # Convenience alias
{%- import_yaml slspath + "/defaults.yaml" as defaults %}
{%- set something = pget('something', defaults, merge=True) %}
something-conf-file:
file.managed:
- name: {{ something.conf_location }}
- source: {{ something.conf_source }}
- template: jinja
- context:
slspath: {{ slspath }}
... and so on.
What this does: The contents of defaults.yaml are loaded in as a nested dictionary. That nested dictionary is then merged with the contents of the something pillar key, with the pillar winning conflicts. The result is a nested dictionary containing both the defaults and any pillar overrides, which can then be used directly without concern to where a particular value came from.
slspath is not strictly required for this to work; it's a magic variable that contains the directory path to the currently-running sls. I like to use it because it decouples the formula from any particular location in the directory tree. It is not normally available from managed templates, which is why I pass it on as explicit context above. It may not work as expected in older versions, in which case you'll have to provide a path relative to the root of the salt tree.
The downside to this method is that, so far as I know, you can't access the final dictionary with salt's colon-based nested-keys syntax; you need to descend through it one level at a time. I have not had problems with that (dot syntax is easier to type anyway), but it is a downside. Another downside is the need for a few lines of boilerplate at the top of any .sls or template using the technique.
There are a few upsides. One is that you can loop over the final dictionary or its sub-dicts with .items() and the Right Thing will happen, which was not the case with defaults.get and which drove me insane. Another is that, if and when the salt team restores defaults.get's old functionality, the defaults/pillar structure suggested here is already compatible and they'll work fine side by side.

Override Alfresco pickerresults.lib.ftl

I've found a bug in pickerresults.lib.ftl that i have already reported
Briefly: if the user does not have the permission on a file parent, the line
<#if row.item.parent??>"parentName": "${row.item.parent.name!""}",
will fail, failing the entire script (and the user can't see any file)
So, waiting for the bug being resolved i need to patch this. I'd like to override the macro "pickerResultsJSON" defined in the file removong the line or putting a string value in place of "${row.item.parent.name!""}" that cause the exception
I have no idea of how to redefine the macro and where to place the file inside my amp. Can someone help me?
UPDATE
I'm using the Alfresck SDK 2.0 and my project structure is:
I've tryed to put a file "custom-pickerresults.lib.ftl" with the following content (as suggested by sev) but it does not seem to be the right position. Or should i "register" it in some way?
<#macro pickerResultsJSON results>
<#-- new code here -->
</#macro>
<#global pickerResultsJSON = pickerResultsJSON />
Since macros are just variables, you might be able to do something like this:
<#macro pickerResultsJSON>
<#-- new code here -->
</#macro>
<#global pickerResultsJSON = pickerResultsJSON />
As to where you would put that... you could put it in any file that is included globally on your project. It might require a little trial and error since I'm not sure what your project structure is.
Many thanks to sev, he pointed me the right way.
I realized that pickerresults.lib.ftl is used by the webservice pickerchildren.get.desc.xml and pickerchildren.post.desc.xmlso, the solution is to copy the web service definition and files along with the library in
/alfresco-myamp-repo/src/main/amp/config/alfresco/extension/templates/webscripts/com/my/repository/forms/
(repository/forms/ just becouse the original files are inside config/alfresco/templates/webscripts/org/alfresco/repository/forms/ but any other folder under /alfresco-myamp-repo/src/main/amp/config/alfresco/extension shuld do)
and change the library like this:
...
"type": "${row.item.typeShort}",
"parentType": "${row.item.parentTypeShort!""}",
<#-- from here -->
<#attempt>
<#if row.item.parent??>"parentName": "${row.item.parent.name!""}",</#if>
<#recover>
"parentName": "<unknown>",
</#attempt>
<#-- to here -->
"isContainer": ${row.item.isContainer?string},
<#if row.container??>"container": "${row.container!""}",</#if>
...
This way even if the user has not the permissions to read the parent's name the template can complete without errors (i don't know if the value "unknown" in place of the parent name can cause any troubles, but i did not notice nothing right now)

does any template engine like Freemarker support customized constraints?

I want to use template to render my output, just like Freemarker does, but I want to add customized constraints, for example, ${name}[length > 4 && notEmpty], ${name} is a variable, expression in [] is customized expression, "length > 4" means ${name}.legnth is greater than 4, "notEmpty" means ${name} is not null nor "", if these constraints do not return true, template engine can stop rendering with throwing an exception or do something else. Every variable in template may have its own constraints, and I don't want to write too many [#if][/#if] tags(it seems that Freemarker does not support stopping rendering if [#if] returns false). I just search Freemarker, it doesn't support, do you know any other template engine can meet my demand?
thanks.

Issue in accessing package variable in Dreamweaver Template

I am facing a issue in accessing a package variable in DWT of Page Template.
I have a compound page template with 4 TBB's as follows:
1) Constant TBB - This TBB reads all values of a component(Embedded multivalued component) as key-value pairs and pushes them to a package.
E.g :
Item item = this._package.CreateStringItem(contentType, "test");
this._package.PushItem("key", item);
2) C# DLL of Page Template - This contains the logic of Page Template
3) DWT of Page Template - All package variables are outputted here.
4) Default Finish Actions
The issue I am facing is as follows:
In my DWT ,I want to compare the Metadata of Component template with the package variable set in Constant TBB.
The syntax I am using is :
<!-- TemplateBeginIf cond="ComponentTemplate.Metadata.section_name = key" -->
where key is the package name set in constant TBB having value "test"
But somehow this package variable "key" is giving a value of 0 and not test.
Can someone let me know where exactly I am going wrong.
You should specify the type of item that you're adding to the package by replacing:
Item item = this._package.CreateStringItem(contentType, "test");
with
Item item = this._package.CreateStringItem(contentType.Text, "test");
You should also confirm that the variable is being passed properly to DWT by using
##test##
outside of the condition. This will show the value that you're comparing it to.
Please check following.
Check if there more than one variable in a package with "Key" name.
check if you verifying at correct place in package.

XQuery Update: insert or replace depending if node exists not possible?

I am trying to build simple XML database (inside BaseX or eXist-db), but I have trouble figuring how to modify values in document :
content is simple as this for test :
<p>
<pl>
<id>6></id>
</pl>
</p>
I am trying to build something like function which would insert element into <pl> if that element is not present or replace it if it is present. But XQuery is giving me troubles yet :
When I try it head-on with if-then-else logic :
if (exists(/p/pl[id=6]/name)=false)
then insert node <name>thenname</name> into /p/pl[id=6]
else replace value of node /p/pl[id=6]/name with 'elsename'
I get error Error: [XUDY0027] Replace target must not be empty. Clearly I am confused, why the else part is evaluated in both cases, thus the error.
When I empty out the else part :
if (exists(/p/pl[id=6]/name)=true)
then insert node <name>thenname</name> into /p/pl[id=6]
else <dummy/>
Then I get Error: [XUST0001] If expression: no updating expression allowed.
When I try through declaring updating function, even then it reports error :
declare namespace testa='test';
declare updating function testa:bid($a, $b)
{
if (exists(/p/pl[id=6]/name)=true)
then insert node <name>thenname</name> into /p/pl[id=6]
else <dummy/>
};
testa:bid(0,0)
Error: [XUST0001] If expression: no updating expression allowed.
I've got these errors from BaseX 6.5.1 package.
So how can I modify values in a simple fashion if possible ?
If I call insert straight, the there could be multiple elements of same value.
If I call replace it will fail when the node does not exist.
If I delete the node before insert/replace then I could destroy sub-nodes which I don't want.
In most SQL databases, these are quite simple task (like MYSQL 'replace' command).
#Qiqi: #Alejandro is right. Your if expression is incorrect XQuery syntax:
if (exists(/p/pl[id=6]/name))
then insert node <name>thenname</name> into /p/pl[id=6]
else replace value of node /p/pl[id=6]/name with 'elsename'
Note that eXist-db's XQuery Update functionality is currently an eXist-specific implementation, so in eXist (currently) 1.4.x and 1.5dev, you'll want:
if (exists(/p/pl[id=6]/name))
then update insert <name>thenname</name> into /p/pl[id=6]
else update value /p/pl[id=6]/name with 'elsename'
This eXist-specific XQuery Update syntax is documented on http://exist-db.org/update_ext.html. This syntax was developed before the W3C XQuery Update spec had reached its current state. The eXist team plans to make eXist fully compliant with the W3C spec soon, but in the meantime the docs above should help you achieve what you need to if you use eXist.
Note too that your example code contains a typo inside the pl and id elements. The valid XML version would be:
<p>
<pl>
<id>6</id>
</pl>
</p>

Resources