Getting properties from AEM multifieldpanel dialog stops working when a second entry is added - aem

I have created an AEM Dialog which prompts the user for a set of links and labels.
These links and labels are stored in a jcr node and are used to generate a menu.
To avoid having to create a custom xtype, I am using the acs-commons multifieldpanel solution, which enables me to nest children under the fieldConfig node.
This works great with only 1 Label/Link pair, but when I add a second one - the property cannot be fetched anymore, since instead of a String, it returns the String hashcode.
The property generated by the multifieldpanel in the jcr node is of type String and is filled correctly when inspecting in CRXDE. The problem occurs when I try to fetch the value from within a Sightly HTML file.
Code
Dialog:
Definitions.js:
"use strict";
use(function () {
var CONST = {
PROP_URLS: "definitions",
};
var json = granite.resource.properties[CONST.PROP_URLS];
log.error(json);
return {
urls: json
};
});
Log output
1 element in multifieldpanel
jcr node variable content
definitions: {"listText": "facebook", "listPath": "/content/en"}
log output
{"linkText":"facebook","linkPath":"/content/en"}
Multiple elements in multifieldpanel
jcr node variable content
definitions: {"listText": "facebook", "listPath": "/content/en"},{"listText": "google", "listPath": "/content/en"}
log output
[Ljava.lang.String;#7b086b97
Conclusion
Once the multifieldpanel has multiple components and stores it, when accessing the property the node returns the String hashcode instead of the value of the property.
A colleague has pointed out that I should use the MultiFieldPanelFunctions class to access the properties, but we are using HTML+Sightly+js and are trying to avoid .jsp files at all cost. In JavaScript, this function is not available. Does anyone have any idea how to solve this issue?

That is because, when there is a single item in the multifield, it returns a String, where as it returns a String[] when there is more than a single item configured.
Use the following syntax to read the property as a String array always.
var json = granite.resource.properties[CONST.PROP_URLS] || [];
Additionally, you can also use TypeHints to make sure your dialog saves the value as String[] always, be it single item or multiple items that is configured.

Don't forget that the use() in JS is compiled into Java Byte code and if you are reading Java "primitives", make sure you convert them to JS types. It's part of the Rhino subtleties.
On another note, I tend to not use the granite.* because they are not documented no where, I use the Sightly global objects instead https://docs.adobe.com/content/docs/en/aem/6-0/develop/sightly/global-objects.html
To access properties, I use properties.get("key")
Hope this help.

Related

TYPO3: repository->findAll() not working

I am building an extension with a backend module. When I call the findAll() method it returns a "QueryResult" object.
I tried to retrieve objects with findByUid() and it does work.
I set the storage pid in the typoscript:
plugin.tx_hwforms.persistence.storagePid = 112
I can also see it in the typoscript object browser.
I also added this to my repository class:
public function initializeObject()
{
$defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class);
$defaultQuerySettings->setRespectStoragePage(false);
$this->setDefaultQuerySettings($defaultQuerySettings);
}
so that the storage pid is ignored ...
It's still not working, findAll doesn't return an array of entites as it should
Repository must return a QueryResult from the findAll methods. Only methods which return a single object (findOneByXYZ) will return anything else.
All of the following operations will cause a QueryResult to load the actual results it contains. Until you perform one of these, no results are loaded and debugging the QueryResult will yield no information except for the original Query.
$queryResult->toArray();
$queryResult->offsetGet($offset); and $queryResult[$offset];
$queryResult->offsetExists($offset);
$queryResult->offsetSet($offset, $value); and $queryResult[$offset] = $value; (but be aware that doing this yourself with a QueryResult is illogical).
$queryResult->offsetUnset($offset); and unset($queryResult[$offset]); (again, illogical to use this yourself)
$queryResult->current(), ->key(), ->next(), ->prev(), ->rewind() and ->valid() which can all be called directly or will be called if you begin iterating the QueryResult.
Note that ->getFirst() and ->count() do not cause the original query to fire and will not fill results if they are not already filled. Instead, they will perform an optimised query.
Summa summarum: when you get a QueryResult you must trigger it somehow, which normally happens when you begin to render the result set. It is not a prefilled array; it is a dynamically filled Iterator.
This should work.there must be issue with your storage page in FindAll() extbase check for storage but in findByXXX() it ignore storage.
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\Extbase\\Object\\ObjectManager');
$querySettings = $objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Typo3QuerySettings');
$querySettings->setRespectStoragePage(FALSE);
$this->cityRepository->setDefaultQuerySettings($querySettings);
$cities = $this->cityRepository->findAll();
Use additionally typoscript module configuration, like
module.tx_hwforms.persistence.storagePid = 112
Ensure your Typoscript is loaded in root. For BE modules I prefere to use
EXT:hwforms/ext_typoscript_setup.txt
where you write your module and extbase configuration.
Try to debbug like below and check findAll() method present for this repositry. I think this is useful for you click here
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump(get_class_methods($this->yourRepositryName)); exit();
Afetr added all your changes once you need to uninsatll/install extension.
I would inspect the generated query itself. Configure the following option in the install tool:
$GLOBALS["TYPO3_CONF_VARS"]["sqlDebug"]
Note: dont do this in production environemnt!!
Explanation for sqlDebug:
Setting it to "0" will avoid any information being print on screen.
Setting it to "1" will show errors only.
Setting it to "2" will print all queries on screen.
So in Production you want to keep it at "0", in development environments you should set it to "1", if you want to know why some result is empty, configure it to "2".
I would guess that some enablefield configuration causes your problem.
If you retrieve an object by findByUid you will have the return because enablefields are ignored. In every other case enablefields are applied and that may cause your empty result.

load only components scripts that are in the current page

What I'm trying to achieve is that if i have 2 components nodes :
component1
clientlib
component1.js
component2
clientlib
component2.js
and i drag them into page1, then when page1 is generated, only component1.js and component2.js will be loaded when navigating to page1 .
One approach i saw is to use custom Tag Library as described here : http://www.icidigital.com/blog/best-approaches-clientlibs-aem-part-3/
I have two questions :
1) is there an existing feature in AEM to do this ?
2) if not, what is the easiest way to create such custom Tag Library ?
EDIT:
Assume that there is no ability to just include all component clientLibs, rather load only those that are added to the page.
There is no built in feature to do this. Although I've heard that the clientlib infrastructure is being looked at for a re-write so I'm optimistic that something like this will be added in the future.
We have, and I know other company have, created a "deferred script tag." Ours is a very simple tag that take a chunk of html like a clientlib include, add it to a unique list and then on an out call at the footer, spits it all out one after another.
Here's the core of a simple tag implementation that extends BodyTagSupport. Then in your footer grab the attribute and write it out.
public int doEndTag() throws JspException {
SlingHttpServletRequest request = (SlingHttpServletRequest)pageContext.getAttribute("slingRequest");
Set<String> delayed = (Set<String>)request.getAttribute(DELAYED_INCLUDE);
if(delayed == null){
delayed = new HashSet<String>();
}
if(StringUtils.isNotBlank(this.bodyContent.getString())){
delayed.add(this.bodyContent.getString().trim());
}
request.setAttribute(DELAYED_INCLUDE, delayed);
return EVAL_PAGE;
}
Theoretically the possible way of doing is to write script in your page component/abstract page component that does something like this -
Step1 : String path = currentPage.getPath()
Step2 : Query this path for components (one way is to have a master list do a contains clause on sling:resourceType)
Step 3: User resource resolver to resolve the resourceType in Step 3, this will give you resource under your apps.
Step 4: From the above resource get the sub-resource with primary type as cq:ClientLibraryFolder
Step 5: from the client libs resource in Step 4 get the categories and include the JS from them
you could actually write a model to adapt a component resource to a clientLibrary to actually clean the code.
Let me know if you need actual code, I can write that in my free time.

Accessing value by javascript Play Framework/Scala

First of all I am using Play framework with scala.
I am creating a graph and with the node id I would like to show some information at the same page.
In order to do that, first I need to get node.name but for some reasons #node.name function is not working. When searching for it I learnt that it's because play is server-side and js is client-side. However I need to get the data somehow.
I also cannot access:
var html = "<h4>" + node.name + "</h4><b> connections:</b><ul><li>"
How can I access this through the view?
My second question is after reaching the js node.name, I need to access to controller and do the same action one more time but this time with the new node.name .
View Part:
onClick: function(node) {
#node.name
}
1) Is this code in your controller? And are the node variable in scope? If so this should be perfectly legal code, since it will be evaluated as pure scala.
2) The templates are a different story however. You probably know they parse everything as normal html, unless escaped. To use a variable you have to bring it into scope by either:
defining a 'constructor' for the template at the absolute beginning of the file:
#(node : Node)
...
#node.name // later in the file
See http://www.playframework.com/documentation/2.0/ScalaTemplates
or define a variable inside the template:
#defining( Get.node.from.somewhere ) { node =>
#node.name
}
See Play! framework: define a variable in template?
If you did either of the two, you should have no problem accessing the node variable. Even in scripts. But note that external scripts does not have access to the same variables. It is thus very common to use inline scripts or import it as another template if you need to access a variable from JavaScript.
Edit: I've made a gist of a template, controller and routes file: https://gist.github.com/Jegp/5732033

Locating text within a div's ul/li

using .NET MVC/Razor, I am working on setting the validation summary manually client side using the following method for a few js client side operations which will not be handled in data annotations:
function RenderError(message) {
var myDiv = $('[data-valmsg-summary="true"]');
myDiv.removeClass("validation-summary-valid");
myDiv.addClass("validation-summary-errors");
var list = myDiv.find('ul');
$("<li />").html(message).appendTo(list);
}
This works fine, but obviously repeats the errors. What is the simplest way to check and see if the message has already been entered in a list item? Keeping in mind that I already have a handle to the div itself?
Thanks in advance
You could check in a if statement if the data-valmsg-summary field is true and skip it accordingly, does it make sense?

Zend Framework / Form Element is rendering as a text box rather than a dropdown box

I have the following in a config.ini file: (Zend_Form_Element)
site_status.name = "site_status"
site_status.type = "select"
site_status.label = "Status"
site_status.options.multiOptions.active.key = "Active"
site_status.options.multiOptions.active.value = "Active"
site_status.options.multiOptions.active.key = "Inactive"
site_status.options.multiOptions.active.value = "Inactive"
As you can see this is supposed to be a dropdown (select) box, however it is being rendered as a standard text box. What am I doing wrong?
--> Edit
Rather than tying the elements to a form, I am trying to tie them to a database: In my code it would look something like this:
[{tablename}] // the table name would represent a section in the ini
{column}.name = "{column_name/form_field_id}";
{column}.type = "{form_element_type}"
{column}.label = "{form_element_label}"
...
From there I would pull in the database table(s) that the form would represent data for (one or more tables as necessary). As far as the reasoning for this approach is that (down the road), I want to define (either by ini or some other storage method), a configuration file that would be a list of fields/elements that belong to a specific form (that a non-programmer type could easily edit), that the 'generic' form class would read, pull in the element info, and create the form on the fly.
I do realize however this poses another problem which I haven't yet figured out, and that is how to use table lookups for select elements (without coding the database retrieval of the lookup into the form, so that a non-user could easily just define it without any programming, purely configuration, but that is a whole other topic not part of my question here. (and I think I have viable ideas/solutions to that part of the problem anyhow) -- extra config entries and a generic routine pretty much.
I hope that clarifies my thought process and reason why I am doing it the way I am in the example above.
I have not yet played with using a Zend_Config to construct an instance of Zend_Form.
But a look at the code suggests that Zend_Form::addElement() doesn't directly take a Zend_Config instance as a param. Rather, it looks like you need pass your Zend_Config instance to the form constructor. It also seems that the config format needs to be a little deeper in order to map config keys to setXXX() calls.
In path/to/config/myForm.ini:
[myForm]
myForm.elements.site_status.name = "site_status"
myForm.elements.site_status.type = "select"
myForm.elements.site_status.label = "Status"
myForm.elements.site_status.options.multiOptions.active.key = "Active"
myForm.elements.site_status.options.multiOptions.active.value = "Active"
myForm.elements.site_status.options.multiOptions.inactive.key = "Inactive"
myForm.elements.site_status.options.multiOptions.inactive.value = "Inactive"
Then instantiating:
$formConfig = new Zend_Config_Ini('path/to/config/myForm.ini', 'myForm');
$form = new Zend_Form($formConfig);
Not tested, but looking at this example:
Using Zend_Form with Zend_Config - Andrew Vayanis
it feels like it should go something like the above.
Update
In view of the comments/feedback from #Aaron, two more approaches.
We could extend Zend_Form, implementing a method called something like addElementByConfig in which we would pass the shallow Zend_Config instance that describes the element itself. In fact, we could even just override addElement(), taking a recursive approach: if the first param is an instance of Zend_Config, then call addElement() using the component data.
If the atomicity and re-usability are the primary benefits we seek in using Zend_Config to describe an element, then perhaps we just make a custom element extending Zend_Form_Element. Then we could use these elements in any forms we wish.