Consider a VueJS app that allows for showing / editing blogposts.
On clicking "edit blogpost" a modal (Vue component) is shown to edit the current blogpost (just an ordinary js-object).
To this end I pass the current blogpost as a prop to the modal-component.
The Modal component has a couple of form fields that should be populated with the blogpost-attributes.
However, the Vue 'way' is to have a data object on the Modal-component that acts as the model to populate the form fields, and vice-versa, be updated when the user changes the form fields.
The problem: How do I connect the blogpost which is passed as a prop to the data field of the modal-component so that:
the blogpost populates the form fields
the blogpost gets updated when updating the form fields
What's the correct Vue way to accomplish this?
You have three (at least) ways of doing it:
1) Connect the data via the prop attribute, just as you are doing, but add the .sync attribute to it. This way, when the data is modified on the form, it automatically gets modified too on the component. The problem with this solution is that the update is automatic, so if a validation fails, or the user closes the modal without saving the changes, these are saved anyway. An example: https://jsfiddle.net/Lz3aq64f/
2) The other way of doing it is getting the modal to $dispatch an event with the saved information when it's saved. The blogpost element should listen for this event and act accordingly.
On the modal:
this.$dispatch('update-post', this.title, this.author);
On the blogpost:
this.$on('update-post', function(title, author) {
this.title = title;
this.author = author
});
If the blogpost component and the modal component are not in the same hierarchy, things get a little bit more complicated, and probably you need the common parent element to act as a proxy. Think of the modal element dispatching the information up, the #app element catching it via $on, and then doing $broadcast to the blogpost element.
3) Use something like vuex to act as a central repository of state. I don't know how big is your application, but this would be the cleanest way to go: http://vuex.vuejs.org/en/index.html
Good luck!
Related
Context
Let's say I have page that displays a list of employees, along with many other buttons and components. and whenever I click on an employee, a side panel appears with a bunch of information about that employee. If I click on any other employee while an employee is selected, the side panel remains intact with only the information inside it being re-fetched and updated.
I can also, change the employee's information from that side panel by editing the different fields and clicking Update which bulk updates every updated fields.
Problem Statement
Whenever an employee's information is being edited, I can also click on another employee, which, of course, means that the edited data for the previous employee is lost.
Objective
What I need to do in this case is that, whenever an employee's information is being edited from the side panel (a flag is present to denote that), and if any other component/buttons apart from Update is clicked, then I need to show a modal which states that everything that is unsaved would be lost. If the user selects Ok, the component that was clicked should mount/rendered/perform whatever it does. Otherwise, the user should go back to the previous state.
I initially tried to achieve that by using componentWillUnmount of the sidepanel, but it didn't really work because that component will unmount anyway. What I am thinking is that, I need to put a onClick handler in every button/component now apart from that 'Update' which checks if any information is being edited, if yes it will render that component, if not it will render that modal, and the user decides what to do in that modal. I am very skeptic about this as well by placing checks everywhere for a minor thing.
Anyone with experience about related to this use case ? Any insights would be highly appreciated. :)
First Put isEdited state isEdited: false Then when you edit the post then set isEdited to true. When user clicks update then set isEdited back to false. When user clicks button other than update then check isEdited if isEdited is true then dispay modal with message and button when button of model is click set isEdited to false.
Hopefully this will help
There are a few ways I can think to do this.
Add a check at the start of the function for each buttons onClick. As you've mentioned already, this is tedious, especially if there are lots of buttons that should affect the state.
Create a function that wraps the onClick functions, to perform this check. Similar to 1, however a bit easier to repeat:
const myOnClick = newEmployeeNumber => {
myState.employeeToView = newEmployeeNumber;
}
const checkUserWantsToContinue = (onClickToRunWhenContinued) => {
if (myAppState.isEdited) {
promptUserToSaveFirst();
} else {
onClickToRunWhenContinued();
}
}
return (
<div onClick={checkUserWantsToContinue(myOnClick)} />
);
You mentioned that you are trying to put this into the componentWillUnmount function. That implies that something is making the component unmount. Can you add the check in there? So rather than checking with the user before the component unmounts, check with them before the action that makes the component unmount.
Say, I am showing some posts that I got from an ajax response. Now I want to add the option to edit any particular post on clicking some button. I guess, I could do it using v-show, where I will attach a form component or something with every single post and when the user clicks the edit button I will hide the post div and show the form with post's body and title and again on clicking save I could hide the form, send a request to the server to update the post then show it again.
Now my question is, is it doable without attaching and hiding anything in the first place? Because, how many times will I get users wanting to edit their posts? I want to call a function or something else with the post on some button click which will return a component with one or two text fields that have post data as their value.
Is it even possible using Vue?
You can append a component to another this way:
// the parent component
const CommentComponent = new Vue({
template: '<div>...</div>'
})
// the child component
const EditCommentComponent = Vue.extend({
template: '<div> child </div>'
})
CommentComponent.$addChild({}, EditCommentComponent).$mount().$appendTo(CommentComponent.$el)
If you wish to do this within the parent component, you can do this by replacing a with this.
- Source
Although as I have inferred from the comments, this would not be the most appropriate way to handle your case.
All the examples I have found that keep local state can't provide an initial value for the input. In my case a parent component retrieves field from a server and passes these to my input form where the fields shall be editable. But I don't want to pass each change back up the hierarchy if I can avoid it, rather only when the form is submitted (either via button or by pressing enter)
Update: the usage is as follows. Imagine a todo list. The top level holds the list and the detail component. When I click at the list the detail should update to show the selected todo text. That text should be editable.
So as far the the detail component goes the initial state of the input is the text from the list that gets passed down in props. It needs to change when a different todo in the list is selected. On the other hand I should be able to edit it and when submit triggered that todo text should be passed back up via a callback prop.
So I have to keep local state to collect the input, but I want that state to be initialized with the existing todo text from the list. If I use the Facebook example of an uncontrolled form, I find the edited text remains displayed when I switch to a different todo in the list. Maybe I'm doing it wrong or is it a conceptual problem? Using controlled input initializing the state in the constructor doesn't work either because the constructor only gets called once (not on each re-render)!
I solved the problem by using the lifecycle method componentWillReceiveProps(nextprops) where I can set the state to the new props. This method gets called each time a re-render becomes necessary - in my case because the parent changes the childs props.
See https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
You can set the initial state based on the property passed in from the parent, then edit that state in the component before submitting with the form submission
I have a template where recipes are rendering through {{#each recipes}} and I'm using ReactiveVar to toggle the edit form of each recipe from hide to show. Everything works fine, but I want that when I press edit button in one recipe then all other recipe forms, which were opened before, will set to hide
Template.Recipe.onCreated(function(){
this.editMode = new ReactiveVar(false);
});
Template.Recipe.helpers({
editMode: function() {
return Template.instance().editMode.get();
}
});
Template.Recipe.events({
'click .fa-pencil': function(event, template) {
//Right here I guess should be something that switches all "editMode" to false
template.editMode.set(!template.editMode.get());
},
});
I think you need a different approach
a Session variable which has the Id of the current editing recipe and then helper on the template to see if the current recipe is the one being edited
add a property to the recipe whether its being edited or not, then update the collection when you change which one is edited
A common object with a reactive var, and much the same technique as the Session variable. If you have a parent template, then it could live there (this is probably the nicest option)
In order to achieve this functionality, you should think about how your templates communicate. One template shouldn't be able to set the state for it's siblings, which means you are thinking it wrong.
You should store that state somewhere global so all templates can see and react to it. That option would mean using Session (as Keith mentioned) or a route parameter (better option, in my opinion).
Another option is to use the parent to store that data, using events to communicate between parent and children. A child template can send and event to the parent who direct another event to each other child.
i have my main page and also two partial views. One partial view is a menu and the other is a telerik grid.
What i want to achieve is selecting a row in the grid and when i click a button in the menu i want the page to navigate to that action passing the selected row (id).
i want to refresh the entire page and not only the div with the grid.
I tried using document.location = "/Pedido/DetalhePedido/" + id; but i don't receive the id n the controller.
I also tried using $.get('#Url.Action("detalhePedido", "Pedido")', data, function (result) { }); usually i use this to refresh a div and i can't seem to make this work with the entire page (and it probably shouldn't ).
Wich methods do you usually use in your web apps to reproduce this sort of behaviour?
Because clicking on the row happens on the browser, you can't depend on anything from the controller or model unless it's already somewhere in your original model. I implement this by adding a hidden Id column to the grid and model it uses for rendering, then add client events and handlers to the grid and view. Check out the samples provided for the Grid under Client-Side Events for some of the different client events you can take advantage of.
On your grid, first add the Id column (hidden if you like):
.Columns(columns =>
{
columns.Bound(o => o.Id).Hidden(true);
}
Then add ClientEvents and wire up onRowSelect like so:
.ClientEvents(events =>
{
events.OnRowSelect("onRowSelected");
}
Then add a function to handle this event like so:
function onRowSelected(e) {
var id = e.row.cells[0].innerHTML;
window.location = "Something/Details/" + id;
}
UPDATE
Sounds like you are doing everything right on the client. The problem is likely elsewhere, in your Action or Bindings. But to be certain, take a look at what /Pedido/DetalhePedido/" + id actually is with an alert before venturing down that path. You should be able to take that and enter it directly into the url of your browser and hit your action as long as your action is correctly defined, accepting an int called id.
If its still a problem, you need to look at you action. Is it marked for Post? If it is Window.Location wont work because it's not a post. Is the argument it accepts named Id and of type int? If not, should it be? Have you changed your Routes, and if so does your Url match any routes defined?