onClick Event on Element in customComponent - ajax

I am currently deep diving into JSF and I have a question.
I am using JSF 2.2 (Mojarra 2.2.9) in a small application. I have build a custom component to render a data structure as a menu. The code looks like something like this:
#FacesComponent(createTag = true, tagName = "UIRecursiveTree", namespace = "")
public class UIRecursiveTree extends UIComponentBase {
#Override
public String getFamily() {
return "";
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
}
private void writeFolderStructure(Map<IContainer, ?> map, ResponseWriter writer, String prefix) {
}
}
I have stripped some code out and of course I have set the family and the namespace, just hid it. The control has a value attribute which receives a LinkedHashMap which is rendered in my application.
Everything works fine. Currently I am just printing some labels with a ResponseWriter which represent my structure. Problem is, the structure can be quite big and the mechanic to load the structure is not ideal, at least if I am loading everything at once, which takes ages.
So, my idea is that I want to bind my elements (currently just labels but will be replpaced by links or something like that) to an onClick event, which triggers the component to load itself again with new values (Ajax) (fetch the child elements of the current element and add them to my structure so that the values are only loaded when they are needed)
I know there are maybe some controls in some libraries which I could use to map such a structure, but I am learning JSF and using things already done teaches me nothing. =)
So, the question is, how can I bind an onClick event to my elements to force the component to refresh itself?
Thanks in advance.

after a bit of back and forth, I was able to solve my problem.
I delayed the "do it completely yourself" course and used the tree control from PrimeFaces.
<p:tree id="treeMap" value="#{treeView.root}" var="container" dynamic="true" selectionMode="single" selection="#{treeView.selectedNode}">
<p:ajax event="expand" update="treeMap" listener="#{treeView.onNodeExpand}" />
<p:ajax event="select" update=":form" listener="#{treeView.onNodeSelect}" />
<p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
<h:outputText value="#{container.getName()}" />
</p:treeNode>
<p:treeNode type="dummy" icon="ui-icon-video" rendered="false" styleClass="hidden" />
</p:tree>
<h:commandButton value="Reset Tree" action="#{treeView.reset()}" />
I use those actionListeners to build up my tree.
Initially I only fetch the first level of item, which is the root. Everything else is loaded when I expand the node. To be able to expand the node (I need a child node under the root node) I add a dummy node under every node. When I click on the expand symbol I fire the onExpand listener. This method calls my service which fetches the child nodes of the current node and puts them under the node in my bean and refresh the tree. That way I can build up my structure when I need the child nodes. The only problem is that of curse under every node there is a dummy node. I delete the dummy when I have expanded the parent node, but without a full refresh there is a expand icon in front of every node, no matter whether there is actually a node under it or not. But in my case that's not that important.
I hope I could help someone who is looking for a similar solution.

Related

Dynamically adding custom elements to DOM Aurelia [duplicate]

It seems Aurelia is not aware when I create and append an element in javascript and set a custom attribute (unless I am doing something wrong). For example,
const e = document.createElement('div');
e.setAttribute('custom-attr', 'some value');
body.appendChild(e);
Is there a way to make Aurelia aware of this custom attribute when it gets appended?
A little background: I am creating an app where the user can select their element type (e.g. input, select, checkbox etc.) and drag it around (the dragging is done in the custom attribute). I thought about creating a wrapper <div custom-attr repeat.for="e of elements"></div> and somehow render the elements array, but this seemed inefficient since the repeater will go through all the elements everytime I push a new one and I didn't not want to create a wrapper around something as simple as a text input that might be created.
You would have to manually trigger the Aurelia's enhance method for it to register the custom attributes or anything Aurelia related really. And you also have to pass in a ViewResources object containing the custom attribute.
Since this isn't as straight forward as you might think, I'll explain it a bit.
The enhance method requires the following parameters for this scenario:
Your HTML as plain text (string)
The binding context (in our scenario, it's just this)
A ViewResources object that has the required custom attribute
One way to get access to the ViewResources object that meets our requirements, is to require the custom attribute into your parent view and then use the parent view's ViewResources. To do that, require the view inside the parent view's HTML and then implement the created(owningView, thisView) callback in the controller. When it's fired, thisView will have a resources property, which is a ViewResources object that contains the require-d custom attribute.
Since I am HORRIBLE at explaining, please look into the example provided below.
Here is an example how to:
app.js
import { TemplatingEngine } from 'aurelia-framework';
export class App {
static inject = [TemplatingEngine];
message = 'Hello World!';
constructor(templatingEngine, viewResources) {
this._templatingEngine = templatingEngine;
}
created(owningView, thisView) {
this._viewResources = thisView.resources;
}
bind() {
this.createEnhanceAppend();
}
createEnhanceAppend() {
const span = document.createElement('span');
span.innerHTML = "<h5 example.bind=\"message\"></h5>";
this._templatingEngine.enhance({ element: span, bindingContext: this, resources: this._viewResources });
this.view.appendChild(span);
}
}
app.html
<template>
<require from="./example-custom-attribute"></require>
<div ref="view"></div>
</template>
Gist.run:
https://gist.run/?id=7b80d2498ed17bcb88f17b17c6f73fb9
Additional resources
Dwayne Charrington has written an excellent tutorial on this topic:
https://ilikekillnerds.com/2016/01/enhancing-at-will-using-aurelias-templating-engine-enhance-api/

Rich Picklist + A4j Support + onlistchange

There are a weird behavior happening when I use rich picklist component attached to an a4j support onlistchange event.
If I have "n" selected items into picklist component, the server will try to populate it "n" times (running gruposDeTributosQuery.resultListOrdered() "n" times!!!)!
This causes a hard delay, because the query used to populate is a little bit slower...
Above is my code:
<rich:pickList id="picklisttributos" value="#{criarEstudo.tributosDoAssuntoList}"
label="Tributos" >
<s:selectItems var="_tributos" value="#{gruposDeTributosQuery.resultListOrdered}"
label="#{_tributos.nome} | #{_tributos.id}" />
<s:convertEntity />
<a4j:support event="onlistchange" process="picklistOF" reRender="picklistOF" />
</rich:pickList>
Bizarre.
New discover...
The problem is gruposDeTributosQuery.resultListOrdered!
public List<Tributo> getResultListOrdered() {
this.setOrder("nome");
//ArrayList<Tributo> lista = new ArrayList<Tributo>(
// this.getResultList());
return this.getResultList();
}
If I use direclty resultList this bahavior doesn´t happen!

Can you prevent a VisualForce component from rendering until it's contents become visible?

I have a VisualForce page that contains two components which are each inside of a tab, like this:
<apex:tabpanel switchtype="ajax" selectedTab="tab1">
<apex:tab label="First Tab" name="tab1" id="tab1">
<c:firstComponent />
</apex:tab>
<apex:tab label="Second Tab" name="tab2" id="tab2">
<c:secondComponent />
</apex:tab>
</apex:tabpanel>
Is it possible to prevent the page from running the code in secondComponent (contained in tab2) until the user actually clicks on the second tab?
This is a specific case, of the more general question: is it possible to NOT run all of the code on a page when it first loads and only run it when the user takes a particular action.
(One reason for wanting to do this is to reduce the number of queries on the page to avoid hitting the limit.)
I think it works as you want! You probably have too much logic in the constructors. And component's constructor HAS TO execute.
Check this example, it works for me (meaning exception is thrown only when I navigate to the second tab).
public class tabTest{
public Contact getContact(){
throw new exampleException('That\'s no moon. It\'s a space station.');
}
public class exampleException extends Exception{}
}
<apex:page controller="tabTest">
<apex:tabpanel switchtype="ajax" selectedTab="tab1">
<apex:tab name="tab1" label="1st" >1</apex:tab>
<apex:tab name="tab2" label="it's a trap!">{!contact.LastName}</apex:tab>
</apex:tabpanel>
</apex:page>
Do you have a lot of logic invoked when constructors are invoked (on the main page and then for each of the components)? Can you move some of it into action methods (the ones returning getter methods:
public List<Contact> myData{
get{
if(myData == null){
myData = [SELECT Id FROM Contact LIMIT 5];
}
return myData;
}
private set;
}
I know it looks crazy but it's a valid syntax since 2008 ;) You can also have a traditional getMyData() call.
It's a matter of preference but I try to make my constructors as thin as possible, only basic initialisation. If I'll start fetching all kinds of stuff it means delay for the end user who might not need all the data every time.
As for the more generic question about running code when user performs an action - there's whole range of tags & options: commandButtons, commandLinks, actionSupport, actionFunction, JavaScript remoting... I'm not going to paste all the links but there are tons of examples, not the least of them being http://wiki.developerforce.com/page/Visualforce_DynamicEditPage.

Exclude an id from update

Can I exclude a specific id from update=":myComponent"?
I have a larger page (with several tables, input fields and so on) which is wrapped in a <p:panel id="outerPanel">. Most of the time, I just execute update=":outerPanel", which works quite fine. But now I'm facing a problem that I have to update the page except for ONE table.
How can I exclude that table (or any component in general) from an update process?
If the ID not to refresh can be identified by some logic (i.e.:
if(myVar==1) { idToExclue = 'id1';}
else // ... etc
Then you can try building a JSF call using the jsf.util.chain: (Note this uses Mojarra JSF impl.)
function myJsfSubmit(callerElement) {
var myIdsToSubmit = 'main-form:id-to-refresh-1 main-form:id-to-refresh-2';
// put your logic to exclude the desired ID here
// ...
// then
jsf.util.chain(callerElement,null,'mojarra.ab(this,event,\'action\',
\'main-form:id_to-submit\',\'+ myIdsToSubmit +\')');
return false;
}
And use that above function on the onClick() or onSubmit() of your form or component:
<h:commandButton onclick="myJsfSubmit(this)" />
No, I think that is not possible from the facelet. On the server side you could manipulate the ajax request, but I don't think that is what you want.
Maybe your view allows to wrap some h:panelGroup around the parts to update.

Define a dataObject and pass it to the next view in Flashbuilder

I am trying to convert my java-app for Android to a Flex/Air app with Flashbuilder.
I am nearly there (thanks to sample code on Adobe) but have problems with passing data between views.
I have 3 views. The first have a list of items and an event handler that choose one of this list items and pass this to the next view:
<s:List id="list" left="0" top="0" bottom="0" width="768"
change="navigator.pushView(Intro, list.selectedItem)" dataProvide "{data}">
This works fine and I can use the values stored in {data}.
e.g.
<s:Label text="{data.title}"/>
Now I want to pass the same data on a button click to the next view, spelaView.
Something like this:
<s:Button id="backBtn" label="Spela"
click="navigator.pushView(SpelaSaga, dataObj)" />
Sorry to say, I do not know how I transform the data-object {data} (with three items: data.title, data.description, data.audio) to dataObj in a form that the next view are abel to use.
Hope someone is kind enough to give me some help on this.
I just solved it.
Defined this function and could then get the data in the next view
protected function spelaSaga():void{
var dataObj:Object=
{
titel:data.title bild:data.description, audio:data.audio
};
navigator.pushView(views.SpelaSaga, dataObj);
}

Resources