How to reuse data between routes in Aurelia? - aurelia

I have an user entity in the system and the following route fetches it from server and displays its details:
routerConfiguration.map([
// ...
{route: 'user/:id', name: 'user-details', moduleId: './user-details'}
]);
Now I want to display an edit form for the displayed user. I have the following requirements:
Edit form should have a separate URL address, so it can be sent to others easily.
When user clicks the Edit button on the user's details page, the edit form should use an already loaded instance of the user (i.e. it should not contact the API again for user details).
When user clicks the Edit button on the user's details page and then the Back button in the browser, he should see the details page without edit form again.
1st attempt
I tried to define the edit form as a separate page:
routerConfiguration.map([
// ...
{route: 'user/:id/edit', name: 'user-edit', moduleId: './user-edit'}
]);
This passes the #1 and #3 requirement but it has to load the user again when the edit form is opened.
I don't know any way to smuggle some custom data between the routes. It would be perfect if I could pass the preloaded user instance to the edit route and the edit component would use it or load a new one if it is not given (e.g. user accesses the URL directly). I have only found how to pass strings to the routes in a slighlty hacky way.
2nd attempt
I decided to display the edit form in a modal and show it automatically when there is a ?action=edit GET parameter. The code inspired by this and this question:
export class UserDetails {
// constructor
activate(params, routeConfig) {
this.user = /* fetch user */;
this.editModalVisible = params.action == 'edit';
}
}
and when the user clicks the Edit button, the following code is executed:
displayEditForm() {
this.router.navigateToRoute('user/details', {id: this.user.id, action: 'edit'});
this.editModalVisible = true;
}
This passes #1 (the edit url is user/123?action=edit) and #2 (the user instance is loaded only once). However, when user clicks the Back browser button, the URL changes as desired from user/123?action=edit to user/123 but I have no idea how to detect it and hide the edit form (the activate method is not called again). Therefore, this solution fails the #3 requirement.
EDIT:
In fact, I have found that I can detect the URL change and hide the edit form with event aggregator:
ea.subscribe("router:navigation:success",
(event) => this.editModalVisible = event.instruction.queryParams.action == 'edit');
But still, I want to know if there is a better way to achieve this.
The question is
How to cope with this situation in a clean and intuitive way?

How about adding a User class that will serve as the model and use dependency injection to use it in your view-models?
export class User {
currentUserId = 0;
userData = null;
retrieve(userId) {
if (userId !== this.currentUserId) {
retrieve the user data from the server;
place it into this.userData;
}
return this.userData;
}
}

Related

Vue2-Dropzone process form when files manually added

Is it possible to manually process the dropzone form (or queue) when the file is manually loaded?
We have the concept of a drivers license. The user uploads a photo and enters other information such as the license number, expiration date, etc.. The user clicks the save button and I call processQueue() which submit the entire form. This all works just fine.
Next, we display this license in a non-form way with an edit button. When they click the "Edit" button, I display the form again and populate the fields with previously entered data including manually adding the previously submitted photo of their license. Basically like this from the documentation:
mounted: () {
var file = { size: 300, name: "Icon", type: "image/png" };
var url = "https://example.com/img/logo_sm.png";
this.$refs.myVueDropzone.manuallyAddFile(file, url);
}
This appears to all work as expected. I see the dropzone with a thumbnail of the previously uploaded file. The input fields are all populated with previously entered data.
The problem occurs when I try to process this form again with:
onSubmit() {
this.$refs.myVueDropzone.processQueue()
}
If they only make changes to the input fields like the license number and do not upload a new file, the onSubmit() or processQueue() does not work. It only works if I remove and re-add a file or add a second file. It's as if it does not recognize a file has been added. Is manuallyAddFile only for displaying and not actually adding the file?
How can I submit this form when the file is manually added?
After a bit of research on Vue2 Dropzone
Manually adding files
Using the manuallyAddFile method allows you to programatically add files to your dropzone area. For example if you already have files on your server that you'd like to pre-populate your dropzone area with then simply use the function when the vdropzone-mounted event is fired.
source
So the solutions is to check and see if anything needs to be processed in your queue. Since you manually added the file you already have it, it does not need to be uploaded again. Only if the user adds a new file does it need to be uploaded.
You could do this a few ways, but I would recommend something like the example below for it's simplicity:
onSubmit() {
if (this.$refs.myVueDropzone.getQueuedFiles().length) {
this.$refs.myVueDropzone.processQueue()
}
}
If you need to wait until the queue has finished processing to send your form you can leverage vdropzone-queue-complete. Below is a quick example:
<template>
<!-- SOME FORM ELEMENTS HERE -->
<vue-dropzone
ref="myVueDropzone"
:options="dropzoneOptions"
#vdropzone-error="errorUploading"
#vdropzone-success="fileUploaded"
#vdropzone-queue-complete="submitForm"
/>
<button #click="saveForm">Save</button>
</template>
<script>
export default {
methods: {
saveForm () {
if (this.$refs.myVueDropzone.getQueuedFiles().length) {
this.$refs.myVueDropzone.processQueue()
} else {
this.submitForm()
}
},
errorUploading (file, message, xhr) {
// do something when a file errors
// perhaps show a user a message and create a data element to stop form from processing below?
},
fileUploaded (file, response) {
// do something with a successful file upload. response is the server response
// perhaps add it to your form data?
},
submitForm () {
// Queue is done processing (or nothing to process), you can now submit your form
// maybe check for errors before doing the below step
// do what ever you need to submit your form this.$axios.post(...) etc.
}
}
}
</script>

Pass values to route

I have a list of items. When the user clicks on an item, the user will be taken to item details page.
I want to pass an object containing item details(like item's image URL) to the route. However, I don't want to expose it in the routes url.
If there were a way to do something like <a route-href="route: details; settings.bind({url: item.url})">${item.name}</a> that would be gold.
I have seen properties can be passed to a route if defined in the route configuration. However, I don't know how to change that from the template. Another way could be is to define a singleton and store the values there and inject the object to the destination route.
Is there a way to pass values to routes from view (like angular ui-routers param object)?
Okay so I figured out a way to achieve something closer to what I wanted:
Objective: Pass data to route without exposing them in the location bar.
Let's say, we have a list of users and we want to pass the username to the user's profile page without defining it as a query parameter.
In the view-model, first inject Router and then add data to the destination router:
goToUser(username) {
let userprofile = this.router.routes.find(x => x.name === 'userprofile');
userprofile.name = username;
this.router.navigateToRoute('userprofile');
}
Now when the route changes to userprofile, you can access the route settings as the second parameter of activate method:
activate(params, routeData) {
console.log(routeData.name); //user name
}
For those #Sayem's answer didn't worked, you can put any additional data (even objects) into setting property like this:
let editEmployeeRoute = this.router.routes.find(x => x.name === 'employees/edit');
editEmployeeRoute.settings.editObject = employeeToEdit;
this.router.navigateToRoute('employees/edit', {id: employeeToEdit.id});
So editObject will be delivered on the other side:
activate(params, routeConfig, navigationInstruction) {
console.log(params, routeConfig, navigationInstruction);
this.editId = params.id;
this.editObject = routeConfig.settings.editObject;
}
hopes this helps others encountering same problem as me. TG.

CasperJS/PhantomJS how to keep old page open?

I have form with some dynamic input that need to be filled. The problem is that to fill the form I need to visit another page to get data that depends on input on the previous page. So after I got a data and then backed to form, the form is already changed so I need to keep that form open while I getting a data for it. So question is how to make it?
You could probably create two casper instances. Where your first instance will have first page, and when required create another instance and get the data from it. Store data in casper's global scope.
Make waitFor until your variable value set # first instance.
Maybe with casper.back() and casper.forward() :
https://github.com/n1k0/casperjs/blob/master/tests/suites/casper/history.js
I suppose your previous form will be saved in the previous step.I hope so, try it.
Edit :
getData(newPage) was an example of your own casper method, you could try something like that :
var password ="wtoc95a";
casper.getData = function (newPage, previousPageInputData) {
//visit another page
casper.open(newPage)
//or casper.open(newPage+previousPageInputData) if the new page opened use the previous input
.then(function() {
//here you do what you want using "input" argument of the previous page
//just return the data you need to fill the form
});
return dataNeeded;
};
And in your form :
this.fill('form[id="signup_frm"]', {
nick_name: "pseudo",
e_mail: "mail",
password: "password",
password2: password,
input : casper.getData(arg1,arg2)
http://docs.casperjs.org/en/1.1-beta2/extending.html

Allow Administrators to impersonate users using an iframe

I have an MVC project with three roles: Users, Account Managers, and Administrators.
Administrators have their own MVC Area where they have full control over Users and Account Managers. I'm trying to implement functionality to allow Administrators to view the site as any User or Account Manager.
In the Admin Area of the site, I have a View of a list of Users and Account Managers. The list contains a "View Site As User" button for each record.
I have never done anything like this before, but the ViewAs Controller Action is currently set up to create a Session with the selected User's information, like so:
ViewBag.SiteSession = Session["SiteSession"] = new SiteSession()
{
ID = user.ID,
AccountID = user.AccountID,
DisplayName = user.DisplayName,
IsManager = user.IsAdmin,
IsAdmin = false
};
The View relevant to this Action has the Model defined as a string, and nothing else but an iframe with the Model as the src attribute, like so:
#model string
<iframe src="#Model" >
</iframe>
What I'm trying to do is render whichever portion of the site was requested in this iframe. When an Administrator clicks "View As User," I'd like to direct to Home. The URL is generated through this call:
Url.Action("Index", "Home", new { Area = "" }));
The Area is set to nothing to avoid rendering the Admin Area's Home.
Currently, this is not working. I don't know where to even begin, minus what I already have.
I'm looking for any suggestions. All help is greatly appreciated, as this doesn't seem like an easy task.
If you don't know how to help, it would also be appreciated if you could direct this question to somebody that can.
Again, thanks in advance.
The way that I've done this in the past has been to use the concept of an an actual user and an effective user. Most display actions use the effective user to generate their content. Typically I've implemented it as "impersonation" rather than "preview" so the user is actually navigating the site as the user rather than displaying in a separate window. In this case I simply set both in the current session. Things that require admin permission (like switching to/from impersonation) obviously use the real user.
If you wanted to do preview then I'd think about using a parameter on each request to set the effective user. The code would need to understand to add this parameter to all links so that you could navigate in the iframe without messing up navigation in the original interface.
As for removing the area from the url, I think what you have (setting to the empty string) should work. If it's not working you might want to try lowercase area, Url.Action("Index", "Home", new { area = "" }). I'm pretty sure that the RouteValueDictionary that gets created under the hood uses a case insensitive key comparison, though, so it shouldn't matter.
For this task, I ended up creating a separate controller, ViewAsController, which had a controller-wide [Authorize] attribute that only allowed users with the Admin role to access its actions.
In the Start action, a Session object containing the selected User's information is created, like so:
[HttpGet]
public ActionResult Start(int id)
{
var user = db.Users
.First(u => u.ID == id);
Session["SiteSession"] = new SiteSession()
{
//Session data...
};
return PartialView("_IFrame");
}
This Action returns a Partial View that I ended up displaying in a jQuery UI modal dialog window.
Here's the code for that Partial View:
#{
ViewBag.SiteSession = (SiteSession)Session["SiteSession"];
}
<h2>Viewing Site As #ViewBag.SiteSession.DisplayName</h2>
<div>
<iframe src="#Url.Action("Index", "Home", new { Area = "" })"></iframe>
</div>
As you can see, it's extremely bare, and that's exactly what it needs to be. The <iframe> acts as a browser in a browser, allowing the Admin user full access to whichever Actions the selected User would.
For the sake of detail, here's the jQuery that creates the dialog and opens it:
$(function () {
$("#viewAsDialog").dialog({
modal: true,
autoOpen: false,
resizable: true,
draggable: true,
closeOnEscape: false,
height: $(window).height() * .9,
width: 1000,
closeText: '',
close: function () {
$.post("#Url.Action("End", "ViewAs", new { Area = "Admin" })")
.success(function (result) {
});
}
});
});
function viewAs(result) {
$("#viewAsDialog").html(result);
$("#viewAsDialog").dialog("open");
}
You can see here that the dialog is initialized on document-ready, and is not opened until the AJAX call that retrieves the Partial View is successfully completed.
Once the Admin closes the dialog, the server calls the End action in the ViewAs Controller, destroying the session:
[HttpPost]
public ActionResult End()
{
Session["SiteSession"] = null;
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK);
}

client web - how to get current record id at any time

I'm trying to work on the "different permissions based on workflow state" issue but I'm struggling with the fact that it seems impossible to get the id of the current object 'at any time' that is necessary in order to get the permission of that object. What I mean is that I manage to get it from the client state following jquery bbq docs like:
$.bbq.getState().id
BUT it looks like this is doable only AFTER a complete page load. I investigated this by placing some alert in the main view events, like:
openerp.web.PageView = openerp.web.PageView.extend({
on_loaded: function(data) {
this._super(data);
alert('page load ' + $.bbq.getState().id);
},
do_show: function() {
this._super();
alert('page show ' + $.bbq.getState().id);
},
reload: function() {
this._super();
alert('page reload ' + $.bbq.getState().id);
},
on_record_loaded: function(record) {
this._super(record);
alert('record loaded ' + $.bbq.getState().id);
}
});
and I found that when you open the page view (by clicking on an item in a search view, for instance) you get always "undefined".
Then, you get it into "reload" and "on_record_loaded" when passing from an object to another using paged navigation. And then, you miss it again when you click on the "edit" button.
In the form view I successfully got it only on the 1st load because it seems that some caching is in-place. So that, if I place a pdb into web client's fields_view_get and I do this into the form "init_view":
var ids = [];
if ($.bbq.getState().id){
ids = [parseInt($.bbq.getState().id)];
}
console.log(ids);
return this.rpc("/web/view/load", {
"model": this.model,
"view_id": this.view_id,
"view_type": "form",
toolbar: this.options.sidebar,
context: context,
ids: ids,
}, this.on_loaded);
I get it only the 1st time that the page gets loaded. The same happen if I take ids from
this.dataset.ids
I looked anywhere at the core web module and I can't find a proper API for this and it looks weird (above all on dataset) that we don't have a proper way for getting/working on the current record/s. Even the context and the session do not have any information about that.
Probably I should store this into the view itself on 1st load...
Thanks in advance for any pointers.
try:
this.view.datarecord.id
OpenERP 7 in form view:
debugged using google chrome
Try the combination of the
this.dataset.ids and this.dataset.index
like
curr_id = this.dataset.ids[this.dataset.index]
this might solve your problem.