----------------UPDATE----------------
I've entirely re-written my question in attempt to clarify what was apparently not well written. I've also included even more code in the hopes that someone can offer some help. My apologies for the confusing stuff provided earlier.
Basically my problem seems to be that I don't entirely understand Spring and REST. So I'm hoping someone can perhaps clarify things for me and perhaps look over my code and inform me of specifically why it doesn't work. Though I have some idea of the cause I don't understand why it is the way it is.
I've got a very basic Spring app. The user is displayed a page that lists (from the DB) a table made up of two columns filled with usernams and a boolean of whether or not they're enabled.
#RequestMapping(value="/admin/modifyUser", method=RequestMethod.GET)
public void showModifyUser() {}
Clicking on a link in the enabled column simply switches their status. The link is created by sending the user to /admin/access and appending the username and access variable. So, an example would be http://localhost:8080/myApp/admin/access?username=test&access=true (or whatever the exact syntax is). That code was:
#RequestMapping(value="/admin/access",method=RequestMethod.GET)
public String submitModifyAccess(#RequestParam("username")String username,
#RequestParam("access")String access) {
....
return "redirect:/admin/modifyUser";
}
That worked fine. It would update the user's access and return to the page with the table and user data. (Maybe not the best way to implement it?) Later on I wanted to populate data in a Dojo grid and therefore needed the data put into JSON format. Hence I read up in my Spring book on REST and such. So, to start out easy, I decided to make the above Handler RESTful. So I changed it to:
#RequestMapping(value="/admin/access/{username}/{access}",method=RequestMethod.GET)
public String submitModifyAccess(#PathVariable String username,
#PathVariable String access) {
....
return "redirect:/admin/modifyUser";
}
I also updated the JSP to make the link go to /admin/access/username/access, so for example: http://localhost:8080/myApp/admin/access/test/true. And voila, things still worked. I assumed I had made it RESTful. A couple of things did strike me as odd though.
First, when clicking on the link, it did update the status properly, but when returning to the /admin/modifyUser page (which is where it sends you), the two variables would be appended to the URL. So instead of showing http://localhost:8080/myApp/admin/modifyUser, it showed http://localhost:8080/myApp/admin/modifyUser?username=test&access=true. Pretty sure that wasn't supposed to be happening.
Second, I realized that the RequestMethod for submitModifyAccess should be POST (or perhaps PUT).
But as I said, it still worked so I didn't worry about it too much.
Next I tried to modify the other link, the username link. When clicking on that link the user is taken to a form populated with the data of that person. Originally that was called by just appending the username to the URL with a GET request to display the form. So the code was:
#RequestMapping(value="/admin/editUser", method=RequestMethod.GET)
public void showEditUser(Model model, #RequestParam("username") String username) {
NFIUser user = userService.getUser(username);
UserDetails userDetails = userDetailsManager.loadUserByUsername(username);
....
model.addAttribute("user", user);
}
Worked fine. So I updated the JSP so the username links called the proper URL and then I tried to RESTify this method by changing it to:
#RequestMapping(value="/admin/editUser/{username}", method=RequestMethod.GET)
public String showEditUser(Model model, #PathVariable String username) {
NFIUser user = userService.getUser(username);
UserDetails userDetails = userDetailsManager.loadUserByUsername(username);
....
model.addAttribute("user", user);
return "redirect:/admin/editUser";
}
Upon doing that I started to see 405 errors and I now realize I'm clearly not understanding something. First, I believe that in order to do a REST PUT or POST you have to have a GET of that exact same URL. Is that correct? What do people think I should do in this situation?
Oh, and in case anyone wants it, the form I was sending people to is as follows (though it's not ever getting loaded as when the user clicks on the link they get the 405 error):
<div align="center">
<b>If you change the Username you MUST change the password as well.</b>
<s:url value="/admin/editUser" var="edit_url" />
<sf:form method="POST" modelAttribute="user" dojoType="dijit.form.Form" action="${edit_url}">
<script type="dojo/method" event="onSubmit">
if (!this.validate()) {
return false;
}
return true;
</script>
<sf:hidden path="username"/>
<table>
<tr>
<td align="right">Username: </td>
<td>
<sf:input path="newUsername" dojoType="dijit.form.ValidationTextBox" trim="true" required="true" value="${user.username}"/>
</td>
</tr>
<tr>
<td align="right">Password: </td>
<td>
<sf:input path="password" type="password" dojoType="dijit.form.ValidationTextBox" required="true"/>
</td>
</tr>
<tr>
<td align="right">Enabled: </td>
<td>
Yes<sf:radiobutton path="enabled" value="true" dojoType="dijit.form.RadioButton"/>
No<sf:radiobutton path="enabled" value="false" dojoType="dijit.form.RadioButton"/>
</td>
</tr>
<tr>
<td align="right">Admin: </td>
<td>
Yes<sf:radiobutton path="isAdmin" value="true" dojoType="dijit.form.RadioButton"/>
No<sf:radiobutton path="isAdmin" value="false" dojoType="dijit.form.RadioButton"/>
</td>
</tr>
<tr>
<td align="right" colspan="2">
<button dojoType="dijit.form.Button" type="submit">Submit</button>
</td>
</tr>
</table>
</sf:form>
</div>
So hopefully that makes things more clear. Again, if you've got some idea of what I'm doing wrong, if you can explain what I clearly don't get about REST, or any other comments that would improve my code, by all means let me know. Thank you very much.
First the main issue, your 405 error. 405 means "method not supported", i.e. the HTTP method (GET/PUT/POST/etc.) is not supported. Your redirect to /admin/editUser will return a 302 to the client (browser) with a header indicating the redirect URL. The browser will then issue a GET against the URL (i.e. it will redirect itself). From your mappings it looks like the closest matching request you can handle is GET /admin/editUser/{username} - but you are redirecting to GET /admin/editUser. My guess is that is the problem - your redirect does not match any endpoints you've declared.
Note I'm assuming your /admin/editUser/username URL in your example should really have been /admin/editUser/{username} because you need to use curly braces for #PathVariable's.
Also note that it is a bit uncommon to redirect from a GET. You just got something, presumably to return to the client - why redirect to something else?
Regarding whether something like http://localhost:8080/myApp/admin/access/test/true is "RESTful" may be an opinion, but my take is that it is not. The resource to me is the user's "access". To grant access I would probably do a PUT /users/{username}/access. To deny access I would probably do a DELETE /users/{username}/access. Note the uniform interface: different HTTP methods operating on the same URL.
If you have verbs in your URLs (e.g. "modifyUser") or pass data in your URLs (e.g. the true in access=true or /access/true) you are probably not adhering to REST principles.
Lastly, I feel you could benefit from a good book on REST rather than what I am guessing you are using is blogs/articles found online. I find Rest in Practice to be the best so far.
modifyUser vs. editUser ?
You wrote that the link is:
http://localhost:8080/myApp/admin/modifyUser
but you mapped to
/admin/editUser/{username}
If this is not the problem, then try to make your question a bit more clear - especially what is the current implementation and what does not work currently.
Related
Good evening to all,
I have an error when I want to modify an entity.
I have my post entity, another error entity, and an associative postError entity.
So I want to modify the errors chosen for the concerned item on the updatePosteErreur form.
I already managed the case for the addition of the itemError, and it works.
However, when I want to modify the itemError by modifying the errors, I have this error message:java.lang.IllegalArgumentException: Parameter value [6] did not match expected type [com.MailleCoTech.SuiviProduction.entities.Poste (n/a)]
I don't understand...
Here is my service and my updateView:
<th:block th:each="erreur: ${erreurs}">
<input class="form-label mt-4" type="checkbox" name="erreurs" th:field="*{erreurs}" th:value="${erreur.id}"/>
<label th:text="${erreur.name}"></label>
<br>
</th:block>
if(!posteUpdateForm.getErreurs().isEmpty()){
List<PosteErreur> posteErreurs = posteUpdateForm.getErreurs().stream().map(erreur -> {
PosteErreur posteErreur = posteErreurService.findByIdPoste(posteUpdateForm.getId());
posteErreur.setId(posteErreur.getId());
posteErreur.setIdErreur(erreur);
posteErreur.setIdPoste(poste);
return posteErreur;
}).collect(Collectors.toList());
this.posteErreurService.save(posteErreurs);
}
return poste;
I don't put all the code, because I have the same view to add, and an add function in the service and everything works, it's when I modify that I get this error message :/
thank you in advance for your help and your time
I have a table with orders and order ids. I would like to click on a row to view the order details in another page. My code:
<form id="orderDetalsForm" th:action="#{/restaurant/orderdetails}"
method="POST" th:object="${order}">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th scope="col">Order Id</th>
<th scope="col">Order Details</th>
<th scope="col">Date</th>
<th scope="col">Amount</th>
</tr>
</thead>
<tbody>
<tr id="orderRow" th:each="order : ${orders}"
style="cursor: pointer"
th:onclick="'getOrderItems(\''+${order.orderId}+ '\');'">
<td scope="row" th:text="${order.orderId}"></td>
<td th:text="${order.orderDetais}"></td>
<td th:text="${order.orderDate}"></td>
<td th:text="${order.amount}"></td>
</tr>
<tr>
<td colspan="3">Total</td>
<td th:text="${grandTotal}"></td>
</tr>
</tbody>
</table>
</form>
I tried an ajax form submit:
<script>
function getOrderItems(orderId) {
var url = "/restaurant/orderdetails";
$.ajax({
url : url,
type : "post",
data : {
"orderId" : orderId
},
success : function(data) {
console.log(data);
},
error : function() {
console.log("There was an error");
}
});
}
</script>
In my controller I have this:
#PostMapping(value="/restaurant/orderdetails")
public ModelAndView orderDetails(#RequestParam String orderId){
List<Product> orderDetails = userService.getOrderDetails(orderId);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("orderDetails", orderDetails);
modelAndView.setViewName("restaurant/orderdetails");
return modelAndView;
}
While the ajax works, the page is not getting redirected to my orderdetails page.
You can't use AJAX to redirect to a page. AJAX is for getting data from the server that is then processed/displayed using JavaScript. You however basically want clicking on the row to behave like clicking a link or (since you are using a POST mapping) submitting a form.
First off, using POST makes this a bit more complicated. You should consider using a GET mapping, not only because it makes this problem easier, but also because a POST mapping isn't really appropriate here. POST is used to send data to the server, which you are not doing.
Another thing you should consider it that using a (pure) JavaScript solution to link the table row hinders accessibility. For example, users that can't/don't use a mouse (such as disabled people or search engines) won't be able to see or even use such a link. To solve this it is a good idea to add a proper link to the row. Then that link can used by "clicking" on it with the JavaScript of the click handler.
<tr th:each="order : ${orders}" onclick="orderRowClick(this)">
<td scope="row"><a th:href="#{/restaurant/orderdetails(orderId=${order.orderId})}" th:text="${order.orderId}"></a></td>
<td th:text="${order.orderDetais}"></td>
<td th:text="${order.orderDate}"></td>
<td th:text="${order.amount}"></td>
</tr>
<script>
// Look for a link in the row and click on it
function orderRowClick(row) {
row.querySelector("a").click();
}
</script>
Several more points:
IDs must be unique in HTML. By putting id="orderRow" on such a repeated row will result in invalid HTML.
You shouldn't be using on... attributes to assign event handlers. I'm just using it here or otherwise this answer will go too far.
Remove the <form> from around the table. It doesn't do anything.
If you do want to/have to use a POST mapping, then replace the link in the table row with a form with a hidden field containing the order ID and a submit button and in the JavaScript look for the form instead of the link and submit it: row.querySelector("form").submit();.
BTW there are several (possibly better) ways to do what you are trying. For example:
Forget the JavaScript and just put a link into every cell. With the right CSS the row/cells can be changed so that it looks like you are clicking on the row.
It seems like you are using Bootstrap, which has the "stretched link" feature. Unfortunately it's a bit tricky to get to work with table rows, but it's worth looking at.
What I've understand so far is that you want to redirect user to a new page when the user clicks on the button on the table, for that there're different approaches -
Issue with your approach -
Since you're using ajax it wont be redirecting user to a new page ( because thats exactly how a AJAX works, for more info on AJAX us this link - https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX ), unless you explicitly tells your ajax-code to redirect the user on a new page.
For that you can simply put your page redirection code to a on your ajax success, something like this -
<script>
//your code
$.ajax({
// your code
success : function(data) {
//page redirection code here
window.location.href = "your-base-URL/restaurant/orderdetails/orderId="+orderId;
},
error : function() {
console.log("There was an error");
}
});
}
</script>
PS - This is not efficient programming practice since you're technically making an extra call to your server un-necessarily.
Approach 2nd
Simply make your html table buttin a-href link , like this -
<html>
// your code
<a th:href="#{'/restaurant/orderdetails/orderId=' + ${order.orderId}}">Order details button </a>
//your rest of the code
</html>
Approach-3rd , You can alternatively use java-script function for page redirection as well, simply modify you ajax function , something like this -
function getOrderItems(orderId) {
//page redirection code here
window.location.href = "your-base-URL/restaurant/orderdetails/orderId="+orderId;
}
We have a JSF 2.0, Primefaces 5.0, Spring Security 3.2.3.RELEASE application.
To handle session timeout, I am using primefaces idleMonitor and p:dialog & javascript to display a countdown popup and redirect them back to login page.
I have also implemented a custom CacheControlPhaseListener so that the pages are not cached. I set the no-cache in the response headers in the CacheControlPhaseListener.
<lifecycle><phase-listener id="nocache">com..filter.CacheControlPhaseListener</phase-listener></lifecycle>
I also have error handling configured in my web.xml:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/error.jsf</location></error-page>
I have also implemented a ViewExpiredHandler that extends ViewHandlerWrapper
#Override
public UIViewRoot restoreView(FacesContext ctx, String viewId)
{
UIViewRoot viewRoot = super.restoreView(ctx, viewId);
try
{
if (viewRoot == null)
{
viewRoot = super.createView(ctx, viewId);
ctx.setViewRoot(viewRoot);
}
}
catch (Exception e)
{
e.printStackTrace();
}
return viewRoot;
}
The problem I am still having is:
1. When the session expires on a idle page (E.g. Search page) and if some ajax action is triggered on a page, even though I logout, when I navigate back to the page (e.g. Login-> Home-> Search page). I see a partial-response xml error:
<partial-response><changes><update id="blGridId"><table id="blGridId" style="width:100%;">
<tbody>
<tr>
<td><div id="blTableId" class="ui-datatable ui-widget ui-datatable-scrollable ui-datatable-resizable"><div id="sublTableId_paginator_top" class="ui-paginator ui-paginator-top ui-widget-header ui-corner-top" role="navigation"><span class="ui-paginator-prev ui-state-default ui-corner-all ui-state-disabled"><span class="ui-icon ui-icon-seek-prev">p</span></span><span class="ui-paginator-next ui-state-default ui-corner-all ui-state-disabled"><span class="ui-icon ui-icon-seek-next">p</span></span></div><div class="ui-widget-header ui-datatable-scrollable-header"><div class="ui-datatable-scrollable-header-box"><table role="grid"><thead id="blTableId_head"><tr role="row"><th id="blTableId:j_idt101" class="ui-state-default ui-resizable-column" role="columnheader" style="width:34px; #width:37px;"><span class="ui-column-title"><span style="word-wrap: break-word;white-space: normal;">Client </span></span></th><th id="blTableId:j_idt104" class="ui-state-default
2. If I hit a browser refresh, it loads back the page and I can continue with the actions.
Please let me know what I need to do in addition to the above to resolve the partial-response error. Do I need to add a servlet filter to invalidate the session?
I would really appreciate any help and feedback on this as it is high priority.
I had got the same issue when session had been expired. I thought it was too late, but maybe would be helpful for others who has issues like me.
The root cause is Spring Security saves the last request before redirecting client to do the authentication. After then, Spring security would try to perform the request again when user visits the page of last request. Unfortunately, the request was ajax/partial and its view was expired -> partial xml content was returned.
Easy way to get rid of this issue is removing the saving behavior of Spring Security. SavedRequestAwareAuthenticationSuccessHandler class is used to handle these kind of behaviours. Configure as:
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:authenticationManager-ref="authenticationManager"
p:authenticationFailureHandler-ref="authenticationFailureHandler"
p:authenticationSuccessHandler-ref="authenticationSuccessHandler"
p:usernameParameter="username"
p:passwordParameter="password">
</bean>
...
<bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"
p:defaultTargetUrl="/"
p:alwaysUseDefaultTargetUrl="true"/>
Hope it would help.
I'm new to mvc3 and razor, and C# for that mater, and a small company just gave me a contract to do this huge project base on a failed one ... right now I need to create a table of books based on choice saved by the user ... books currently have about 40 different info in the model, and I'm wondering how to set it correctly in an template Editor or any other way ...
right now it looks like that in the view :
<table>
<tr class="header">
#if (!((List<string>)Session["searchPreferences"]).Contains("Authors"))
{
<td>Author</td>
}
#if (!((List<string>)Session["searchPreferences"]).Contains("bookName"))
{
<td>bookName</td>
}
#if (!((List<string>)Session["searchPreferences"]).Contains("editorName"))
{
<td>editorName</td>
}
</tr>
#foreach (BookModel book in Model.bookList)
{
<tr class="element">
#if (!((List<string>)Session["searchPreferences"]).Contains("Author"))
{
<td>#book.Author</td>
}
#if (!((List<string>)Session["searchPreferences"]).Contains("editorName"))
{
<td>#book.editorName</td>
}
</tr>
+ 1 <tr> </tr> ... for each 40 attributes in books
</table>
how can I go around making an Editor template and include the search preferences ( which are stored in session or on a LogUser model ...
other questions :
I've grasped the concept of using #Html.EditorFor(model => model.Books) and it'll loop threw my list of books, but how can I make sure it'll show only what the user wants ?, like can I pass some kind of parameters to the template and use the list passed on to validate each columns ?
and is there a simpler way to write down my template .. kinda like "get each property in my book model and create a row with is if the user have selected it" because the client come with a new way to classified his books and i'll have to recode everything each time he change of idea ...which I've been by my senior happens very very often. ( the pay is very good for the little experience I have, so i'll try to tackle this even if the project was started wrong and the client refuse to let us re-start from scratch )
thanks alot in advance for any info, help etc ... even if its a link toward a good demo or blog entry that would help.
I'm not 100% sure what you are trying to do, but if I understand you correctly you have a list of user preferences of what to show in a table. My advice to you would be to build a View Model that already has formatted the data the way you want it to be.
Meaning, by the time you pass your model to the view, you only have the column and data the user has specified in his / her search preferences.
You could have a Book Object, which holds a list of columns, Each 'Column' is its own object, with a Label and a Value.
public BookViewModel()
{
List Data { get; set; }
}
public Column()
{
string Label { get; set; }
string Value { get; set; }
}
When loading the model you would only create columns for the data the user wanted.
There may be better ways for you to format it, I just thought of this at a moments notice. But I think you should look at handling the filtering and the formatting of the data server side, not in Razor on the page.
The Editor Template is meant for customizing how an object is represented on an html page, especially if the object will be used in a lot of places on a page so you don't need to repeat yourself. Its not really made for filtering data.
Let me know if this doesn't make sense or I misunderstood you.
I am getting the following response
<div id="weblogs">
<tr>
<td nowrap class="bl">1</td>
</tr>
<tr>
<td nowrap class="bl">2</td>
</tr>
</div>
Now I am trying to attach the rows like the following:
function _ajax(postData)
{
loadUrl = "getweblogs.asp";
$.ajax( {
url : loadUrl, // your ajax file
type : 'post',
data : postData,
success : function( resp ) {
alert($("#weblogs" , resp).html());
$('#weblogs > tbody:last').append($("#weblogs" , resp).html());
}
});
return false;
}
The replace is working fine. My problem is, that the htmls elements from the response are removed. I'm getting only 1 and 2. instead of
<tr>
<td nowrap class="bl">1</td>
</tr>
<tr>
<td nowrap class="bl">2</td>
</tr>
I don't know what am I doing wrong. Could someone give me any clue?
Thank you!
Greetings
Magda
What you're trying to do is not so clear. Is there already an element with id="weblogs" in the page? If so, why does your response have an element with the same id (not a good idea), and if not, why are you trying to append an element's contents to itself like that (also, not a good idea)?
Why not just change the server-side to send the html required, without a wrapping div tag (which makes it invalid html anyway, another bad idea), and then use it as-is?
Another problem is that you're trying to select .html() of something that will always be an empty jquery object: $(selector, string) will never match anything. You'll need to make the string a jquery object if you want to search its substructure: string = $(string).
And I think you're misunderstanding the use of $(selector, $obj). The selector must be in the $obj's sub-structure:
$('#foo', $('<div id="foo"><span/></div>')); // returns empty jquery object
So looking for #weblogs in the substructure of an element with id weblogs will also never find anything.
Your question isn't entirely clear, but I'll try and answer: are you working with Internet Explorer? If so, consider this paragraph from the docs:
For example, Internet Explorer sometimes leaves off the quotes around attribute values if they contain only alphanumeric characters.
API docs: html()