handle a String[] via the PortletPreferences (Liferay6.2) - html

I have built a MVCPortlet that runs on Liferay 6.2.
It uses a PortletPReferences page that works fine to set/get String preferences parameters via the top right configuration menu.
Now I would need to store there a String[] instead of a regular String.
It seems to be possible as you can store and get some String[] via
portletPreferences.getValues("paramName", StringArrayData);
I want the data to be stored from a form multiline select.
I suppose that I need to call my derived controller (derived from DefaultConfigurationAction) and invoke there portletPreferences.setValues(String, String[]);
If so, in the middle, I will neeed the config jsp to pass the String[] array to the controller via a
request.setAttribute(String, String[]);
Do you think the app can work this way in theory?
If so, here are the problems I encountered when trying to make it work:
For any reason, in my config jsp,
request.setAttribute("paramName", myStringArray);
does not work ->
actionRequest.getAttribute("paramName")
retrieves null in my controller
This is quite a surprise as this usually works.
Maybe the config.jsp works a bit differently than standard jsps?
Then, how can I turn my multiline html select into a String[] attribute?
I had in mind to call a JS function when the form is submitted.
this JS function would generate the StringArray from the select ID (easy)
and then would call the actionURL (more complicated).
Is it possible?
thx in advance.

In your render phase (e.g. in config.jsp) you can't change the state of your portlet - e.g. I wouldn't expect any attributes to persist that are set there. They might survive to the end of the render phase, but not persist to the next action call. From a rendered UI to action they need to be part of a form, not request attributes.
You can store portletpreferences as String[], no problem, see the API for getting and setting them

I think maybe you can use an array in client side, and you can update the javascript array, when user is selecting new values.
So you have the javascript array, then when user click on the action, you can execute the action from javascript also, something like this:
Here "products" is the array with your products.
A.io.request(url, {type: 'POST',
data: {
key: products
},
on: {
success: function(event, id, obj) {
}
}
});
From Action methd you can try to get the parameter with:
ParamUtil.getParameterValues(request,"key");

Related

Setting value using UseState hook adds a "tableData" object to data fetched from API

I am fetching data from a flask API using Axios, then using the useState hook to use that data to build a table. The error I'm encountering is that after I use the setValue function to update the state, an extra "tableData:{id:0}" object is added to the JSON object the API returning, and it seems to happen after the setValue function is called.
I have already tried to use delete values.key.tableData when I was only dealing with single objects, but now that I have an array of objects it doesn't work anymore, and I don't understand why this key is even added in the first place.
I get an "undefined" error when I don't use the initial state like I did below.
The console shows the original string (not parsed yet) when i log the API's original response, but shows this when I log the state:
​
0: Object { Email: "", tableData: {…} }
​
length: 1
​
<prototype>: Array []
​
How do I keep the "tableData" key from being added to the response? Is there an issue with the way I'm using the hook?
The relevant parts of the code are down here:
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
axios.get("http://127.0.0.1:5000/api",{params: { start: selectedstartDate, end: selectedendDate}}).then((response)=>{
console.log(response.data.details);
setValues(response.data.details);
console.log(values);
}, []);
After looking at the way MaterialTable behaves, the issue was created by it mutating whatever is passed as data, so in this case my state variable, and adding that extra key to render the table. To solve this, I added a second state variable which is a copy of the first one, that I only use for the table data prop, and kept using the original values for everything else.

Calling cross controller function

I have two pairs of controller and view. The first view contains a list of items, while the in second shows some details of a specific item. What I want to achieve is that a click on one list item, the function onSelect should call second controller of detail view and update its content with the selected list item.
So far I have following code:
//first list controller
onSelect : function () {
var secondController = sap.ui.controller("controller.Detail");
secondController.updateFunction("some text");
}
Then in second controller:
//second detail-controller
updateFunction: function (someText) {
var view = sap.ui.xmlview("view.Detail");
view.byId("someTextField").setText(someText);
}
The problem is that this is not working. It seems that sap.ui.xmlview is not returning the same view which is displayed.
When I execute following code:
var model = view.getModel(model);
console.log(model);
within 2 functions of detail controller, but first is called by outside controller and second is called by onInit or function called by detail view event, the id is different.
How can I achieve such a cross-controller function calling with updating content of different view? Or is my approach not proper?
I would recommend to use either the EventBus or Routing for inter view communication.
Routing is nice as it uses the hash part (#) of the url to communicate for example an event like the selection of an item (f.e. https://example.com/myUi5App/index.html#/item/123). The user can use the browser history and bookmarks to navigate through your app.
A view can register to the router to be notified when a specific url pattern is matched. The walkthrough in the SAPUI5 Developer Guide does nicely explain routing step by step and in detail here and here.
EventBus is a global object that can publish arbitrary events. Anyone interested can register to the EventBus. There is an EventBus on the Component which you should use if you have a component and a global EventBus.
Both techniques help decoupling your views. It does not matter if there are one, many or none views listening to the selection change. And it does not matter for the listeners who fired the event.
If both views have been called once you can achieve this via the view (from my opionion, this is quite hacky and should be solved otherway)
this.getView().byId("yourViewId").oController.yourMethod();
means in your case
onSelect : function () {
var secondController = this.getView().byId("view_id").oController;
secondController.updateFunction("some text");
}
maybe this helps you, he is passing the controller reference which would be a better option: Calling Controller Function from inside a press handler in sapui5
I have found solution.
sap.ui.getCore().byId("__xmlview1");
According to documentation var view = sap.ui.xmlview("view.Detail"); always creates a new view.
However I am still struggling about specifying id of xmlview. Since "___xmlview1" is dynamicly given name and the number 1 means serial number of views within application. So if I create another view before creation of "view.Detail", the id will point to the new one.
I am creating xmlview like this:
<mvc:XMLView viewName="view.Detail"></mvc:XMLView>

Spring HATEOAS template link expansion

Using the HATEOAS links functionality which is great I am trying to output a templated url to highlight the filter params available to a user
Example controller method
#RequestMapping(value = "/persons", method = RequestMethod.GET, produces = "application/hal+json")
public PersonsResource getPersons (#RequestParam(required = false, value = "name") String name, #RequestParam(required = false, value = "age") Integer age) {
...
personsResource.add(ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(PersonController.class).getPersons(name, age)).withSelfRel());
}
When this method is invoked with no parameters links appears
_links: {
self: {
href: "http://myserver:8080/persons"
}
}
But I'd like
href: "http://myserver:8080/persons?name={name}&age={age}
Even better if one param was supplied then
href: "http://myserver:8080/persons?name={name}&age=21
Icing on the cake would be query parameters of {...] to be ignored ?
Does anyone know if this is possible using the Spring HATEOAS api ? I have managed to code around this but it seems like a reasonable suggestion for the API ?
You could try AffordanceBuilder from spring-hateoas-ext as a drop-in replacement for ControllerLinkBuilder. It creates template variables for parameters you leave undefined in the linkTo-methodOn idiom.
It not only allows to create templates, but also gives you the full capabilities of a RFC 5988 Link and has knowledge about request bodies, so that one can render Hydra or Html or Siren Responses with form-style request descriptors from it.
Disclaimer: I'm the author of spring-hateoas-ext.
This has been addressed in the latest spring-hateoas version. You can check the following issue:
https://github.com/spring-projects/spring-hateoas/issues/169
You should be able to get the required templated URL using something like:
resource.add(linkTo(methodOn(Controller.class).method(null)).withSelfRel());
I guess, the framework is still pretty immature.
I have v.0.11.0.RELEASE and have the same issue.
When you don't supply parameter values you don't have template URL as a result of the ControllerLinkBuilder.linkTo(methodOn) invocation. It's just the way you said, base path from the method annotation.
But when you supply parameter values it's exactly like you say:
https://stackoverflow.com/some/service/path?name=SomeName&age=11
(in my case parameters are different, but the effect is the one you see here)
The 'conceptually correct' URL should be
https://stackoverflow.com/some/service/path{?name,age}
But Spring HATEOAS doesn't support this. Unless you want to append it yourself in the code. Which is really undesirable.
I checked the UriBuilder from JavaEE, it works the same way, no templating for query parameters supported.

Does calling ko.mapping.fromJS two time is right thing to do?

I am using knockout mapping plugin to map JSON data to knockout view model. The issue is JSON comes from server data doesn't have all the properties always. But my computed obeservables refer them. So I creates all the observable in first mapping using an empty object(templateStructure) contains all properties and then doing seocond call with actual data to populate the observable with current data. This works fine but want to know that if there any better way to handle the situation?
This is how the two time call is happening right now. templateStructure is dummay object with all the properties and data is actual data.
ko.mapping.fromJS(templateStructure, {}, this);
ko.mapping.fromJS(data, {}, this);
Calling mapping.fromJS to update an existing view model is right. If you're receiving updates to your model using AJAX, it's the easiest way to do it (if you didn'd use mapping, you'd have to do it by hand, property by property).
Your approach of creating a "template viewmodel" with all the properties, so that they exist even if you don't receive it in you JSON responses is good. Besides, it's easier to understand the JavaScript code: you don't need to see the server side to discover which properties are in the view model, as would happen if you made the first mapping directly from the server.
However, if the received model is nearly complete, you can always customize the "create" of your mapping (You could look for missing observable properties using js hasOwnProperty and adding the missing ones). The last example of the docs in the link so precisely how to add a new observable (in this sample, a computed observable):
var myChildModel = function(data) {
ko.mapping.fromJS(data, {}, this); // this is the view model
this.nameLength = ko.computed(function() { // nameLength is added to the vm
return this.name().length;
}, this);
}
In this sample you could add the condition to create nameLength only if not present on the received data, like this:
if (!data.hasOwnProperty('nameLength')) {
/* add the missing observ. property here */
}
(NOTE: you can also customize the update, if needed).
I have solved it using jQuery extend method by merging the object before mapping. So I only needed one call to the mapping function.
var mergedData = jQuery.extend(true,data,templateStructure);
ko.mapping.fromJS(mergedData, {}, this);

REST: Updating multiple records

I need to update multiple records using a single HTTP request. An example is selecting a list of emails and marking them as 'Unread'. What is the best (Restful) way to achieve this?
The way I doing right now is, by using a sub resource action
PUT http://example.com/api/emails/mark-as-unread
(in the body)
{ids:[1,2,3....]}
I read this site - http://restful-api-design.readthedocs.io/en/latest/methods.html#actions - and it suggests to use an "actions" sub-collection. e.g.
POST http://example.com/api/emails/actions
(in the body)
{"type":"mark-as-unread", "ids":[1,2,3....]}
Quotes from the referenced webpage:
Sometimes, it is required to expose an operation in the API that inherently is non RESTful. One example of such an operation is where you want to introduce a state change for a resource, but there are multiple ways in which the same final state can be achieved, ... A great example of this is the difference between a “power off” and a “shutdown” of a virtual machine.
As a solution to such non-RESTful operations, an “actions” sub-collection can be used on a resource. Actions are basically RPC-like messages to a resource to perform a certain operation. The “actions” sub-collection can be seen as a command queue to which new action can be POSTed, that are then executed by the API. ...
It should be noted that actions should only be used as an exception, when there’s a good reason that an operation cannot be mapped to one of the standard RESTful methods. ...
Create an algorithm-endpoint, like
http://example.com/api/emails/mark-unread
bulk-update is an algorithm name, a noun. It gets to be the endpoint name in REST, the list of ids are arguments to this algorithm. Typically people send them as URL query arguments in the POST call like
http://example.com/api/emails/mark-unread?ids=1,2,3,4
This is very safe, as POST is non-idempotent and you need not care about any side effects. You might decide differently and if your bulk update carries entire state of such objects opt for PUT
http://example.com/api/emails/bulk-change-state
then you would have to put the actual state into the body of the http call.
I'd prefer a bunch of simple algo like mark-unread?ids=1,2,3,4 rather than one monolithic PUT as it helps with debugging, transparent in logs etc
It a bit complicated to get array of models into an action method as argument. The easiest approach is to form a json string from your client and POST all that to the server (to your action mehtod). You can adopt the following approach
Say your email model is like this:
public class Email
{
public int EmailID {get; set;}
public int StatusID {get; set;}
// more properties
}
So your action method will take the form:
public bool UpdateAll(string EmailsJson)
{
Email[] emails = JsonConvert.DeserializeObject<Emails[]>(EmailsJson);
foreach(Email eml in emails)
{
//do update logic
}
}
Using Json.NET to help with the serialization.
On the client you can write the ajax call as follows:
$.ajax({
url: 'api/emailsvc/updateall',
method: 'post',
data: {
EmailsJson: JSON.stringify([{
ID: 1,
StatusID:2,
//...more json object properties.
},
// more json objects
])
},
success:function(result){
if(result)
alert('updated successfully');
});