How to provide JSON data to Dojo tree using Struts2 framework - json

I'm currently developing a web application using Struts2 framework. This application requires to dynamically update the objects on the screen based on data received from another application.
At the moment, I would like to implement a dynamic tree view which nodes are updated periodically with data provided by an Action class. I’m trying to do so using the dojo.dijit.tree object from the dojo toolkit. I’m aware that I can do so using the dojo tags which are part of the struts framework, however, it lacks much of the functionality that I need (persistence, open and close branches dynamically, etc) therefore, I have opted for using the dojo toolkit instead.
My problem with the dojo.dijit.tree is that I don’t know how to provide its data using a JSON result type. I have already created a class which returns a JSON result type with the same structure needed by the dojo tree component. I have tested the generation of a dojo tree using a file “test.txt” which was generated by the class and it works as expected. However, I would like to pass the JSON data directly to the dojo.dijit.tree component without saving a file on disk. When I execute the application I get a “save as” window to save the returned JSON result.
This is my struts.xml file:
<struts>
<constant name="struts.devMode" value="true" />
<package name="default" namespace="/" extends="struts-default">
<action name="devResult" class="gui.JsonAction">
<result name="success">/start1.jsp</result>
</action>
</package>
<package name="example" namespace="/" extends="json-default">
<result-types>
<result-type name="json" class="org.apache.struts2.json.JSONResult"></result-type>
</result-types>
<action name="getJSONResult" class="gui.JsonAction">
<result type="json"/>
</action>
</package>
This is the jsp file which displays the tree:
<head>
<title>Testing Tree</title>
<style type="text/css">
#import "js/dojo/dojo/resources/dojo.css";
#import "js/dojo/dijit/themes/nihilo/nihilo.css";
</style>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js"
djConfig="isDebug: true,parseOnLoad: true">
</script>
<script type="text/javascript">
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
</script>
<body class="nihilo">
The Tree:<br><br>
<s:url id="devResult" action="jsonAction.action"></s:url>
<div dojoType="dojo.data.ItemFileReadStore" href="%{devResult}" jsid="popStore" />
<div dojoType="dijit.Tree" store="popStore" labelAttr="sname" label="Tree" />
</body>
This is the Action class which produces the JSON result:
public class JsonAction extends ActionSupport {
private static final long serialVersionUID = 7392602552908646926L;
private String label = "name";
private String identifier = "name";
private List<ChildrenClass> items = new ArrayList<ChildrenClass>();
public JsonAction() {
ChildrenClass item1 = new ChildrenClass("name1", "cat");
ChildrenClass item2 = new ChildrenClass("name2", "cat");
ChildrenClass item3 = new ChildrenClass("name3", "cat");
ChildrenClass item4 = new ChildrenClass("name4", "cat");
items.add(item1);
items.add(item2);
items.add(item3);
items.add(item4);
}
public String execute() {
return SUCCESS;
}
public void setLabel(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getIdentifier() {
return identifier;
}
public void setItems(List<ChildrenClass> lists) {
this.items = lists;
}
public List<ChildrenClass> getItems() {
return items;
}
}
This is the ChildrenClass which is used in the class above:
public class ChildrenClass {
private String name;
private String type;
private ChildrenClass[] children;
public ChildrenClass() {
name = "DefaultName";
type = "DefaultType";
}
public ChildrenClass(String aName, String aType) {
name = aName;
type = aType;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setChildren(ChildrenClass[] children) {
this.children = children;
}
public ChildrenClass[] getChildren() {
return children;
}
}
I would like to ask to the stackoverflow reader to please indicate me how to do to read the JSON data in the jsp file in order to populate the dojo tree. In addition, I would like to ask how can I refresh the content of it periodically.
PS: If somebody has a better method to implement something similar to this, I would be glad to receive your comments.
Thanks in advance.

I have found out a way to pass data directly from a JSON result to a dojo.dijit.tree component. Setting the "url" parameter to the action name which returns the json result type.
This is my new body of the .jsp file:
Simple Tree:<br><br>
<div dojoType="dojo.data.ItemFileReadStore" url=getJSONResult handleAs="json" jsid="popStore" />
<div dojoType="dijit.Tree" store="popStore" labelAttr="sname" label="PID 512" />

Related

Apex - Displaying a list after deserialising json

I would like to make a http callout to import json and display the information in a visualforce page. The problem I'm having is that the json is nested and so I need to loop through the child nodes.
I have the following class derived using the json2apex application:
public class CHForm {
public class FilingHistoryItem {
public String DocumentDate;
public String FormType;
public String DocumentCategory;
public String Document;
public String DocumentDescription;
}
public FilingHistory FilingHistory;
public class FilingHistory{
public List<FilingHistoryItem> FilingHistoryItem;
}
public static CHForm parse(String json) {
return (CHForm) System.JSON.deserialize(json, CHForm.class);
}
}
In my controller class I can create a CHForm object (reponseForm) and deserialise the json into the reponseForm object using the following:
HttpResponse res = h.send(req);
String chFormJson = res.getBody();
responseForm = CHForm.parse(chFormJson);
but how do I then display a list of all FilingHistoryItem in the visualforce page? Do I need to create a list object in my controller or is there a way of directly referencing the list from the visualforce page?
I'm a bit confused by your apex, shouldn't it be :
public List<FilingHistoryItem> FilingHistory;
public class FilingHistoryItem {
public String DocumentDate;
public String FormType;
public String DocumentCategory;
public String Document;
public String DocumentDescription;
}
Then you'd be able to do:
<apex:repeat value="{!responseform.FilingHistory.}" var="item" id="theRepeat">
<apex:outputText value="{! item.DocumentDate}" /> //etc
</apex:repeat
The way you've set it up now, I think it may work like this:
<apex:repeat value="{!responseform.FilingHistory.FilingHistoryItem}" var="item" id="theRepeat">
<apex:outputText value="{! item.DocumentDate}" /> //etc
</apex:repeat
But it's a bit strange you name a list property as "...Item" and not "..Items", and nest so deeply, but maybe that's a json dependency.

Win 8.1 SearchBox - binding suggestions

We are writing a Windows 8.1 Store App that uses the new SearchBox XAML control. It looks like the only way to get suggestions into the dropdown list as the user types is to use the SearchBoxSuggestionsRequestedEventArgs and get the SearchSuggestionCollection from the event then append the suggestions to that.
We're using Prism for WinRT and want to separate the SearchBox and it's events from the ViewModel that is getting the list of suggestion strings.
I can't find anyway of binding a list of strings to the SearchSuggestionCollection or any way of adding them programatically that doesn't involve using the event args, which is making out unit testing very complex.
Is there a way of binding/adding the suggestions that doesn't involve the event args?
Okay, so I got obsessed with this question, and here is a solution for when using the SearchBox. I've uploaded a full sample on MSDN and GitHub
In short, use the Behavior SDK and and the InvokeCommand, and then use a converter to grab whatever data you need by using the new attributes InputConvert and InputConverterParameter.
XAML:
<SearchBox SearchHistoryEnabled="False" x:Name="SearchBox" Width="500" Height="50">
<SearchBox.Resources>
<local:SearchArgsConverter x:Name="ArgsConverter"/>
</SearchBox.Resources>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SuggestionsRequested">
<core:InvokeCommandAction
Command="{Binding SuggestionRequest}"
InputConverter="{StaticResource ArgsConverter}"
InputConverterLanguage="en-US"
InputConverterParameter="{Binding ElementName=SearchBox, Path=SearchHistoryEnabled}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
Converter:
public sealed class SearchArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = (SearchBoxSuggestionsRequestedEventArgs)value;
var displayHistory = (bool)parameter;
if (args == null) return value;
ISuggestionQuery item = new SuggestionQuery(args.Request, args.QueryText)
{
DisplayHistory = displayHistory
};
return item;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
Mainpade codebehind - of course you want this in a VM :)
public sealed partial class MainPage
{
public DelegateCommand<string> Search { get; set; }
public DelegateCommand<ISuggestionQuery> SuggestionRequest { get; set; }
public MainPage()
{
InitializeComponent();
Search = new DelegateCommand<string>(SearchedFor, o => true);
SuggestionRequest = new DelegateCommand<ISuggestionQuery>(SuggestionRequestFor, o => true);
DataContext = this;
}
private void SuggestionRequestFor(ISuggestionQuery query)
{
IEnumerable<string> filteredQuery = _data
.Where(suggestion => suggestion.StartsWith(query.QueryText,
StringComparison.CurrentCultureIgnoreCase));
query.Request.SearchSuggestionCollection.AppendQuerySuggestions(filteredQuery);
}
private readonly string[] _data = { "Banana", "Apple", "Meat", "Ham" };
private void SearchedFor(string queryText)
{
}
}
I wrote up a full walk through on my blog, but the above is all you really need :)

Remove namespace prefix while JAXB marshalling

I have JAXB objects created from a schema. While marshalling, the xml elements are getting annotated with ns2. I have tried all the options that exist over the net for this problem, but none of them works. I cannot modify my schema or change package-info.java. Please help
After much research and tinkering I have finally managed to achieve a solution to this problem. Please accept my apologies for not posting links to the original references - there are many and I wasn't taking notes - but this one was certainly useful.
My solution uses a filtering XMLStreamWriter which applies an empty namespace context.
public class NoNamesWriter extends DelegatingXMLStreamWriter {
private static final NamespaceContext emptyNamespaceContext = new NamespaceContext() {
#Override
public String getNamespaceURI(String prefix) {
return "";
}
#Override
public String getPrefix(String namespaceURI) {
return "";
}
#Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
};
public static XMLStreamWriter filter(Writer writer) throws XMLStreamException {
return new NoNamesWriter(XMLOutputFactory.newInstance().createXMLStreamWriter(writer));
}
public NoNamesWriter(XMLStreamWriter writer) {
super(writer);
}
#Override
public NamespaceContext getNamespaceContext() {
return emptyNamespaceContext;
}
}
You can find a DelegatingXMLStreamWriter here.
You can then filter the marshalling xml with:
// Filter the output to remove namespaces.
m.marshal(it, NoNamesWriter.filter(writer));
I am sure there are more efficient mechanisms but I know this one works.
For me, only changing the package-info.java class worked like a charm, exactly as zatziky stated :
package-info.java
#javax.xml.bind.annotation.XmlSchema
(namespace = "http://example.com",
xmlns = {#XmlNs(prefix = "", namespaceURI = "http://example.com")},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package my.package;
import javax.xml.bind.annotation.XmlNs;
You can let the namespaces be written only once. You will need a proxy class of the XMLStreamWriter and a package-info.java. Then you will do in your code:
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = new Wrapper((XMLStreamWriter) XMLOutputFactory
.newInstance().createXMLStreamWriter(stringWriter));
JAXBContext jaxbContext = JAXBContext.newInstance(Collection.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(books, writer);
System.out.println(stringWriter.toString());
Proxy class (the important method is "writeNamespace"):
class WrapperXMLStreamWriter implements XMLStreamWriter {
private final XMLStreamWriter writer;
public WrapperXMLStreamWriter(XMLStreamWriter writer) {
this.writer = writer;
}
//keeps track of what namespaces were used so that not to
//write them more than once
private List<String> namespaces = new ArrayList<String>();
public void init(){
namespaces.clear();
}
public void writeStartElement(String localName) throws XMLStreamException {
init();
writer.writeStartElement(localName);
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
init();
writer.writeStartElement(namespaceURI, localName);
}
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
init();
writer.writeStartElement(prefix, localName, namespaceURI);
}
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if(namespaces.contains(namespaceURI)){
return;
}
namespaces.add(namespaceURI);
writer.writeNamespace(prefix, namespaceURI);
}
// .. other delegation method, always the same pattern: writer.method() ...
}
package-info.java:
#XmlSchema(elementFormDefault=XmlNsForm.QUALIFIED, attributeFormDefault=XmlNsForm.UNQUALIFIED ,
xmlns = {
#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package your.package;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
You can use the NamespacePrefixMapper extension to control the namespace prefixes for your use case. The same extension is supported by both the JAXB reference implementation and EclipseLink JAXB (MOXy).
http://wiki.eclipse.org/EclipseLink/Release/2.4.0/JAXB_RI_Extensions/Namespace_Prefix_Mapper
Every solution requires complex overwriting or annotations which seems not to work with recent version. I use a simpler approach, just by replacing the annoying namespaces. I wish Google & Co would use JSON and get rid of XML.
kml.marshal(file);
String kmlContent = FileUtils.readFileToString(file, "UTF-8");
kmlContent = kmlContent.replaceAll("ns2:","").replace("<kml xmlns:ns2=\"http://www.opengis.net/kml/2.2\" xmlns:ns3=\"http://www.w3.org/2005/Atom\" xmlns:ns4=\"urn:oasis:names:tc:ciq:xsdschema:xAL:2.0\" xmlns:ns5=\"http://www.google.com/kml/ext/2.2\">", "<kml>");
FileUtils.write(file, kmlContent, "UTF-8");

struts 2 + json

I want to know that if Struts 2 core jar must be in sync with the Struts2-Json-Plugin jar. because when i am returning 'SUCCESS' from a method in Action class then exception is occurring . ihave declared result type as 'json' in my xml as
<result-type name="json" class="org.apache.struts2.json.JSONResult"/>
and the exception i am getting is
java.lang.NoSuchMethodError: com.opensymphony.xwork2.ActionContext.get(Ljava/lang/String;)Ljava/lang/Object;
at org.apache.struts2.json.JSONResult.execute(JSONResult.java:166)
at com.opensymphony.xwork2.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:348)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:253)
at com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:221)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:86)
i am using struts2-core-2.0.11.jar and my struts.xml is
<action name="editEmployee" class="myaction.AddEmployeeAction" method="getValue">
<result name="success" type="json" />
</action>
and my action is
public class AddEmployeeAction extends ActionSupport implements ParameterAware {
/**
*
*/
private static final long serialVersionUID = 1L;
private EmployeeDaoImp empdao;
private Map parameters;
public EmployeeDaoImp getEmpdao() {
return empdao;
}
public void setEmpdao(EmployeeDaoImp empdao) {
this.empdao = empdao;
}
public String getValue() throws Exception
{
//JSONArray jsonArr = new JSONArray();
JSONObject jsonObject = new JSONObject();
String query = getParameterValue("selChar");
List<String> names = empdao.getData(query);
/*for (String name : names) {
jsonArr.add(name);
}*/
jsonObject.put("namesList", names);
return SUCCESS;
}
}
S2 plugin versions must match the S2 version; plugins use mechanisms provided by struts2-core.
While a plugin may work with a different version of core, it will never be tested like that, so unless you provide your own test harness, there's no way to know what the behavior will be if you start mixing and matching at random. You should not mix and match.

Render multiple views within a single request

I'm trying to return multiple views within a single request, returning them all in a JSON string.
Example:
#RequestMapping(value = "my-request")
public void myRequest(HttpServletRequest request, HttpServletResponse response) throws Exception
{
Map<String,Object> model1 = new Hashtable<String,Object>();
model1.put(...);
ModelAndView modelAndView1 = new ModelAndView("path/to/view1", model1);
// Render modelAndView1 in some way, in order to obtain the rendered HTML as a String
Map<String,Object> model2 = new Hashtable<String,Object>();
model2.put(...);
ModelAndView modelAndView2 = new ModelAndView("path/to/view2", model2);
// Render modelAndView2 in some way, in order to obtain the rendered HTML as a String
// Now write a JSON String to the HttpServletResponse containing both the rendered views (strings).
// (this is not part of my problem, I'm able to do it as long as I have the two strings)
}
I'm using Spring MVC with Tiles 2.
Can anyone help me?
Update 1 - View names are resolved using a ViewResolver:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/tiles/" />
<property name="suffix" value=".jsp" />
</bean>
Update 2 - I've created a github repository containing a very small example reproducing the problem.
UPDATE: There are some approaches for solving your issue. I'd try this one, but maybe there are cleaner ways.
#Component
public class JsonMultipleViewFactory {
#Autowired private List<ViewResolver> viewResolverList;
public View getView(List<ModelAndView> mavList) {
for (ModelAndView mav : mavList) {
if (mav.getView()==null) {
mav.setView(resolve(mav.getViewName()));
}
}
return new JsonMultipleView(mavList);
}
private View resolve(String viewName) {
for (ViewResolver vr : viewResolverList) {
View view = vr.resolve(viewName, LocaleContextHolder.getLocale());
if (view!=null) {
return view;
}
}
return null;
}
}
public class JsonMultipleView implements View {
private final List<ModelAndView> mavList;
public JsonMultipleView(List<ModelAndView> mavList) {
this.mavList = mavList;
}
public String getContentType() {
return "application/json";
}
public void render(Map<String,?> model, HttpServletRequest request, HttpServletResponse response) {
Json json = new Json(); // You can use several Json libraries here
for (ModelAndView mav : mavList) {
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
mav.getView().render(mav.getModel(), request, mockResponse);
json.add(mav.getViewName(), mockResponse.getContentAsString());
}
json.write(response.getOutputStream());
response.getOutputStream().close();
}
}
And can be used like this:
#Autowired private JsonMultipleViewFactory jsonMultipleViewFactory;
#RequestMapping(value = "my-request")
public View myRequest() {
...
List<ModelAndView> mavList = new ArrayList<ModelAndView>();
mavList.add(modelAndView1);
mavList.add(modelAndView2);
return jsonMultipleViewFactory.getView(mavList);
}