How to navigate to the Edit route after New in a Spine stack - ajax

I'm using Spine.Stack and I'm trying to find the best way to end up at the Edit page for a new record that gets created. The spine/rails example on the site shows #navigate going back to the index listing, but I need to end up on the edit page the the record I've just created. Currently I have
item = WorkRequest.fromForm(e.target)
if item.save()
#navigate '/work_requests', item.id, 'edit' if item
Now this does end up on the edit page but because the model is an Ajax one it then saves to the server and gets the server side ID, but at the point I call #navigate the item.id is the client side ID (c-##). Therefore the URL I end up at is /work_requests/c-##/edit which has no sever side equivalent.
As far as I can see, I have two options
Wait for the Ajax to respond and then navigate to edit with the server generated ID
Call navigate, but then update the URL once the Ajax save responds (when the edit form gets re-rendered with the server side ID.
Option 2 seems best in the spirit of the non-blocking interface, but I need to be sure that if the user does anything in the meantime, that the ID's will all sort themselves out. Option 1 seems safer but does force the user to wait for the save to complete before being able to edit it.
As a side note; the reason for the immediate edit is to create one or more child items (like lines on an invoice) and these all use the nested resource path /work_requests/:id/work_request_lines
Any help greatly appreciated, cheers.

Related

Wicket (1.6) stateless form resets paging

I've already tried to find anything about that problem but I guess I either was not sure how to shortly describe the problem to find a solution or nobody else had that before which I can't think of. Maybe my thinking is wrong, too.
I have a stateless wicket 1.6 form with an ajax supporting panel (WebMarkupContainer with output id). The panel holds a dataview with paging navigator. The dataview itself is filled by a DataProvider.
The panel shows some entries from the database and below that is the navigator. by clicking any page on the navigator, the panel is refreshed (ajax) and shows content from that page. The page itself is not re-rendered by the browser.
When I now leave the page by navigating to another internal page (so basically when leaving the dataview-panel-page in any way) to open a detail page or so and then return to that dataview-page the navigator is resetted (because it's stateless I guess). The navigator can't remember which page to show and begins at the top of the first page again.
The question is: How can I solve this? I would like to i.ex. navigate to page 2 and then temporary leave the page for another internal page. When returning I want to be on page 2, focussed on the record where I clicked the link to "details" before. This also happens when I just open a new page in a new Browser tab.
Thank you!
Here's some code:
final WebMarkupContainer gamesPanel = new AjaxContainer("gamesPanel");
final DataView<Game> dataView =
new GameDataView("gameOverview", targetCurrencyModel, searchTextModel, gameFilterModel,
new GameDataProvider(searchTextModel, gameFilterModel, targetCurrencyModel));
dataView.setItemsPerPage(ITEMS_PER_PAGE);
gamesPanel.add(dataView);
final XNPagingNavigator navigator = new XNPagingNavigator("navigator", dataView);
navigator.setOutputMarkupId(true);
add(navigator);
You guys can try what I mean: The page I'm talking about is http://www.xbox-now.de. Just navigate to page 2, then click on details and return to main page.
I think you might use History API to push a new state when you click on navigation bar. In the new URL for the state you can include a parameter that indicates the index of the current navigator page.
You can customize your AJAX link in order to perform this operations when user click on it.
For more details on History API see Updating address bar with new URL without hash or reloading the page
I solved this in the NoWicket framework by introducing a model aware page cache which reuses page instances when hashcode/equals matches in the page model. You can see the implementation in the GitHub repo, try this implementation of the IPageFactory wrapper as a starting point, but it is more code involved there, just check out the code, debug the example and navigate the code to understand it better in order to apply it to your application. See your use case in action by trying this data table example in the documentation website (which you can also debug locally): http://invesdwin.de/nowicket/ajaxdatatable
Try changing the paging index, navigate to a different page and navigate back to the data table example page. You will still see the paging index that you left there.
Thank you guys for your replies. I've done it now another way. Used the base code from here and modified it in some ways. Added some nice css AttributeModifiers to indicate the actual page:
item.add(new AttributeModifier("class", new PageLinkCssModel(pageable, pageIndex, "active")));
Then I modified some code to add or reset the page parameter, that it's 1) used only once and 2) keeps all the actual page parameters which were there before adding own ones. So I am just appending the page number now. This way I can keep my initially mount path like www.foo.bar/path/path/path. Complete URL would now look like: www.foo.bar/path/path?page=123.
To pass my entered page (i.e. page=5) to the data provider I just had to override the providers iterator. It should start with the page I entered. Due to the ugly generated navigator URLs (which are extremly bad for SEO) I now have nice looking urls which are working independently what wasn't possible before. And that's also the reason why I could not get back to the correct page. The navigator URL was not lookup-able by wicket.
new DataView<GamePrice>("gamePriceOverview", new GameDetailDataProvider(gameId, targetRegion) {
#Override
public Iterator<GamePrice> iterator(final long first, final long count) {
return super.iterator(ITEMS_PER_PAGE * getCurrentPage(), count);
}
getCurrentPage() comes from the base template and gets the actual page number entered (if one is entered):
public long getCurrentPage() {
// -1 weil zero based
return getRequest().getQueryParameters().getParameterValue("page").toString() != null
? (getRequest().getQueryParameters().getParameterValue("page").toLong() - 1)
: 0;
}
So instead of having ugly SEO-unfriendly URLs which are also not compatible to work independant (directly enter the generated url) I now have the same URL I expect with an added page-parameter.
URL now would looks like:
http://localhost:8080/game/4249/de/doom-preorder?page=2
URL before was:
localhost:8080/game/4249/DE/doom-preorder?0-1.ILinkListener-gamePrices-navigator-navigation-2-pageLink
If I now go back from the detail page to the main index with active "Bookmarkable-Navigator", I correctly come back to the page and position where I left it (because of bookmarkable page links).
That's how I achieved this problem with a nice bonus: User- and SEO-friendly URLs.

Django editing a user generated object

I'm trying to figure out how to do an inline editing for a user-generated object, what the rough procedure (no code just steps), and whether or not there's some way to do this without AJAX - of course it wouldn't be "inline" anymore.
Say the user object is just 1 line of text and 1 image. Something like,
class UserObject(models.Model):
text = models.CharField()
image_path = models.CharField()
If I were to use AJAX, would this be how it'd go? (sorry this is vague, I can figure out the details just trying to see if I understand the concepts correctly)
Create a form, populate it with an instance of the object belonging to the current user
Next to the image, I'd have, say, a "remove" button, which triggers an AJAX call to a URL that's something like project/remove/ab12345 that's connected to a view that handles it.
Wait for the AJAX call to be done
Then somehow remove the image and buttons, maybe by just deleting the div that contains it all
Is that right??
Also, what if I don't want to use AJAX? Would it go something like this?
Create a form, populate it with an instance of the object belonging to the current user
Next to the image, I'd have, say, a "remove" button, which directly links to the URL that's something like project/remove/ab12345 that's connected to a view that handles it
After the view deletes the image, it goes back to the editing page, which just refreshes and the image is no longer there.
Any pointers would be greatly appreciated!! I can figure out the details of the coding, just wondering if I am getting the concepts right.
An object created by a user is really no different from one you create yourself (except you have to be suspicious of potentially malicious input!). The simplest way to be able to edit objects outside the admin interface is to use the built-in UpdateView. Similarly you can delete them with a DeleteView. If you want to restrict you can edit objects you can user the PermissionRequiredMixin from django braces.
OK since I posted this ultra-vague question I'm going to answer it.
AJAX-free:
The AJAX free version is pretty much as I described, create a view and a URL that deletes the image and goes right back to the referring page. Next going to try the AJAX version, which basically requires a view that returns some kind of a signal of failure or success.
urls.py
url('^project/remove_image/(?P<image_id_string>[0-9A-Fa-f]+)/$', pbrand.views.ProjectRemoveImageView.as_view(), name='project_remove_image'),
views.py
class ProjectRemoveImageView(View):
redirect_url = '/project/edit' # the editing url
def get(self, request, image_id_string, *args, **kwargs):
# ... some checks on permissions
image.delete()
return HttpResponseRedirect(self.redirect_url + "/" + project.id_string)
inside the template
<a class = "btn btn-default btn-sm" href="{% url 'project_remove_image' i.id_string %}" role="button">remove</a>

Rails form, load new record page rather than redirecting to it

I'm creating my first app in rails.
Basically, I have a new customer form, normally when you enter a new customer you are redirected to the record you created.
However as I am loading all my pages via ajax I want to load the new record in rather than re-direct to it.
I already have the form firing via ajax, I just need to know how I can access the new record URL to load it into my container.
Hope that makes sense. Thanks in advance!
You can add an option :remote => true to your form_for helper method, so that instead of page redirect the form gets posted via ajax.
For Ex :
<%= form_for(#post, :remote => true) do |f| %>
...
<% end %>
Then create a new template named create.js.erb which will get rendered after create method has been executed.
In this template, you can display the details of the new record you created.
For Ex :
$('some_element').replaceWith('<%=#posts.name %>');
Edit: You mentioned using load in your javascript. I would generally avoid this as you're making two round trips to the server. 1) Create 2) Get html to load into a div. Additionally, if there is an error that happens, it will be more difficult to catch and do the "good thing", whatever that might be.
Once you've added the remote: true option to your form, you need to deal with what happens on the server side of things.
Assuming your using REST, you'll have a controller with a create action in it. Normally, this method is creating the item and then subsequently returning the HTML for the create page. Instead of this, we need to create a javascript view for the action. This will tell the calling page what to when this action is hit.
Let's assume your form has a div called "new_record_form" and that you have another div called "new_records". You'll want to blank out the form elements in the form, effectively resetting it. You'll also want to add the new record to the "new_records" div.
To add the record to the new records div, you might do something like this.
$("#new_records").append("#{#new_record.name}");
When you submit the form, you should see this added. One great way to debug issues is to use the inspector. If you're in chrome, right click anywhere, inspect element and select network. Do this prior to form submission. You'll be able to see the ajax call and the response. Your logs will also be helpful.
Don't forget to blank out the form as well.
Note: You mentioned all your pages are ajax, but I highly suggest you evaluate if this makes 100% sense due to the various issues that result. Not saying this is the best article on the subject but might be worth a read.

ASP MVC 3 prevent multiple inserts on page refresh

Let's say I have a simple ASP MVC3 list controller, with an add method, with an id parameter.
List:
http://localhost/MVCAPP/ListFoo/
Add method
http://localhost/MVCAPP/ListFoo/Add?id=1
In my Add method, I update my Viewmodel with the added element, then makes a call to:
return View("ListFoo", viewModel);
The updated list is displayed, and everything's almost fine.
The problem is that with such a return, the URL in the address bar is still
http://localhost/MVCAPP/ListFoo/Add?id=1
And if the user hits F5, another item will be added, which I'd like to prevent.
I know I can filter out such a behavior in the controller, but I'd rather prefer to redirect the browser address bar to:
http://localhost/MVCAPP/ListFoo/
Do you know any way to do this?
By the way, I'm not sure trying to control the address bar content is the right way to look at this issue...
Use the Action.RedirectToAction method to redirect the client after the work is done in the controller.
Besides that, you could use POST as FormMethod to send data to the server.
That is why you need to use PRG: Post-Redirect-Get when you are doing any such form post.
Have a look here.
So the best option is to redirect the user to a GET method to display the page.

Ajax state history in coldfusion page

I'm confused as to how to accomplish this. I have a page which, has a popup filter, which has some input elements and an "Apply" button (not a submit). When the button is clicked, two jquery .get() calls are made, which load a graph, a DataTables grid, photos, and miscellaneous info into four separate tabs. Inside the graph, if one clicks on a particular element, the user is taken to another page where the data is drilled down to a finer level. All this works well.
The problem is if the user decides to go back to the original page, but with the ajax generated graph/grid/photos etc. Originally I thought that I would store a session variable with the filter variables used to form the original query, and on returning to the page, if the session var was found, the original ajax call would be made again, re-populating the tabs.
The problem that I find with this method is that Coldfusion doesn't recognize that the session variable has been set when returning to the page using the browser's back button. If I dump out the session var at both the original and the second page, I can see the newly set var at the second page, and I can see it if I go to the original page through the navigation menu, but NOT if I use the back button.
SO.... from reading posts on here about ajax browser history plugins, it seems that there are various jquery plugins which help with this, including BBQ. The problem that I see with this approach is that it requires the use of anchor elements to trigger it, and then modifies the query string using the anchors' href attributes. I suppose that I could modify the page to include a hidden anchor.
My question, at long last is: is an ajax history plugin like BBQ the best way to accomplish this, or is there a way to make Coldfusion see the newly created session var when returning to the page via the back button? Or, should I consider re-architecting the page so that the ajax calls are replaced by a form submission back to the page instead?
Thanks in advance, as always.
EDIT: some code to help clarify things:
Here's the button that makes the original ajax calls:
<button id="applyFilter">APPLY</button>
and part of the js called on #applyFilter, wrapped in $(document).ready():
$('#applyFilter').click(function(){
// fill in the Photos tab
$.get('tracking/listPhotos.cfm',
{
id: id,
randParam: Math.random()
},
function(response){
$('#tabs-photos').html(response);
}
);
});
Finally, when the user calls the drill-down on the ajax generated graph, it uses the MaintAction form which has been populated with the needed variables:
function DrillDown() {
//get the necessary variables and populate the form inputs
document.MaintAction.action = "index.cfm?file=somepage.cfm&Config=someConfig";
document.MaintAction.submit();
}
and that takes us to the new page, from which we'd like to return to the first page but with the ajax-loaded photos.
The best bet is to use the BBQ method. For this, you don't have to actually include the anchor tags in your page; in fact, doing so would cause problems. This page: http://ajaxpatterns.org/Unique_URLs explains how the underlying process works. I'm sure a jQuery plugin would make the actual implementation much easier.
Regarding your other question, about how this could be done with session variables - I've actually done something similar to that, prior to learning about the BBQ method. This was specifically to save the state of a jqGrid component, but it could be easily changed to support any particular Ajax state. Basically, what I did was keep a session variable around for each instance of each component that stored the last parameters passed to the server via AJAX requests. Then, on the client side, the first thing I did was run a synchronous XHR request back to the server to fetch the state from that session variable. Using the callback method for that synchronous request, I then set up the components on my page using those saved parameters. This worked for me, but if I had to do it again I would definitely go with the BBQ method because it is much simpler to deal with and also allows more than one level of history.
Some example code based on your update:
$('#applyFilter').click(function(){
var id = $("#filterid").val(); // assumes the below id value is stored in some input on the page with the id "filterid"
// fill in the Photos tab
$.get('tracking/listPhotos.cfm',
{
id: id // I'm assuming this is what you need to remember when the page is returned to via a back-button...
//randParam: Math.random() - I assume this is to prevent caching? See below
},
function(response){
$('#tabs-photos').html(response);
}
);
});
/* fixes stupid caching behavior, primarily in IE */
$.ajaxSetup({ cache: false });
$.ajax({
async: false,
url: 'tracking/listPhotosSessionKeeper.cfm',
success: function (data, textStatus, XMLHttpRequest)
{
if (data.length)
{
$("#filterid").val(data);
$('#applyFilter').trigger('click');
}
}
});
This is what you need on the client-side to fetch the state of the photo list. On the server side, you'll need to add this modification to tracking/listPhotos.cfm:
<cfset session.lastUsedPhotoFilterID = URL.id>
And add this new one-line file, tracking/listPhotosSessionKeeper.cfm:
<cfif IsDefined("session.lastUsedPhotoFilterID")><cfoutput>#session.lastUsedPhotoFilterID#</cfoutput></cfif>
Together these changes will keep track of the last ID used by the user, and will load it up each time the page is rendered (whether via a back button, or simply by the user revisiting the page).

Resources