In a blog-like Vue.js app I'd like to update list of comments of a post with new comments, when a new comment is successfully posted to the server.
Here is my code:
<div v-for="(post, index) in posts" :key="index" >
<div>{{post.body}}</div>
<div class="comments">
<ul v-for="(c, j) in post.comments" :key="j" class="comment">
<li>{{c.user_id}} : {{c.body}} </li>
</ul>
</div>
<div>
<input type="text" v-on:keyup.enter="submitComment(post)" v-model="post.comment" placeholder=" Add your comment" />
</div>
</div>
And the method:
submitComment(post) {
const formData = new FormData();
formData.append('token', this.token);
formData.append('pid', post.id);
formData.append('body', post.comment);
axios.post(this.BASE_URL + "/api/comment", formData)
.then( (res)=> {
console.log('Updated comments are', res.data.comments);
this.$set(post, 'comment', res.data.comments) ; //Here is the problem
})
.catch( error => {
console.log(error);
});
}
Despite the fact that I receive the updated list of comments, empty results is rendered. How can I fix this?
as #blaz pointed out in the comments above, it looks like the error is from a typo in the $set method call as the property should be the plural comments not comment
Related
I am using Laravel 6 (mix) + Vue.js 2.
I am creating a multi-lang dictionary where users can add words and for each word they can add multiple definitions with their translations.
Inside index.blade.php
<div class="modal fade" id="modal-addItem">
<form>
<div class="card" v-for="(value, index) in nbDefinitions">
<div class="card-header">#{{ value }}.</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">English</dt>
<dd class="col-sm-9">
<autocomplete data-language="english" :search="searchDefinition" :get-result-value="getDefinitionResultValue" #submit="handleSearchDefinitionSubmit"></autocomplete>
</dd>
<dt class="col-sm-3">French</dt>
<dd class="col-sm-9">
<autocomplete data-language="french" :search="searchDefinition" :get-result-value="getDefinitionResultValue" #submit="handleSearchDefinitionSubmit"></autocomplete>
</dd>
</dl>
</div>
</div>
<div class="row">
<button type="button" class="btn btn-primary" id="addItems-addDefinition" #click="addDefinition">+ Add definition</button>
</div>
</form>
</div>
Inside myCustom.js
new Vue({
el: '#modal-addItem',
data: {
nbDefinitions: 0,
nbSentencesPerDef: [],
translatedDefinitions: []
},
methods: {
addDefinition: function() {
this.nbDefinitions++;
this.nbSentencesPerDef.push(1);
this.translatedDefinitions.push({
english: null,
french: null
});
},
searchDefinition: function (input) {
// How can I know which <autocomplete> is triggered?
return new Promise(resolve => {
if (input.length < 3) { return resolve([]); }
fetch(`/api/v1/definitions?search=${encodeURI(input)}`)
.then(response => response.json())
.then(responseJson => {
resolve(responseJson.definitions);
})
})
},
getDefinitionResultValue: function(result) {
// How can I know which <autocomplete> is triggered?
let definition = result.definition;
let item = result.item.name;
return `${item} - ${definition}`;
},
handleSearchDefinitionSubmit: function(result) {
// How can I know which <autocomplete> is triggered?
console.log(this);
}
}
});
I am using autocomplete which is an external component loaded globally (in Laravel main app.js) https://autocomplete.trevoreyre.com/#/
My question is: How can I know inside the methods “addDefinition”, “searchDefinition” and “handleSearchDefinitionSubmit” what is the child component who was triggered? Because I have 3 autocomplete components inside my Vue object, and this refers to the root (in my case the html modal), so I have no idea which autocomplete child was triggered. Also this.$refs is empty.
Maybe it is an architectural issue, but I don’t have enough experience to know how to get it done.
Component itself doesn't support this directly (by passing itself as a parameter of functions for example). But luckily in case of functions passed as props we can use JS feature called "closure" - instead of just function name, call a function which returns another function. In case of events, we can use the feature of Vue allowing us to access special '$event' value when defining handlers.
Like this:
before: <autocomplete data-language="english" :search="searchDefinition" #submit="handleSearchDefinitionSubmit" />
after: <autocomplete data-language="english" :search="getSearchDefinitionFunc('english')" #submit="handleSearchDefinitionSubmit($event, 'english')" />
...and change your methods like this:
getSearchDefinitionFunc(lang) {
return input => this.searchDefinition(input, lang);
},
searchDefinition: function(input, lang) {
console.debug(`searchDefinition called with "${input}" and "${lang}"`);
if (input.length < 3) {
return Promise.resolve([]);
}
// ...don't create new Promise, just return an existing one
return fetch(`/api/v1/definitions?search=${encodeURI(input)}`)
.then(response => response.json())
.then(responseJson => responseJson.definitions);
},
handleSearchDefinitionSubmit: function(result, lang) {
console.log(`Submit: ${lang}`);
}
I also refactored your code a bit as creating new promise is not necessary.
You can find working example here
Could be refactored even more by defining your languages in component data (['english', 'french']) and generating each row using v-for
I am new to js and would like to convert my JQuery-based js to Vue. I want to send a get request and output back the data. What is the best way of doing this?
Here is the html:
<div>
<div>
<input type="text" id="var1" placeholder="Search...">
</div>
<div>
<button id="submit">Submit</button>
</div>
</div>
<div>
<p>Results</p>
<p id="results"></p>
</div>
Below is my js:
$(document).read(function() {
$('#var1').keypress(function(e) {
if (e.keyCode == 13)
('#submit').click();
});
});
$(document).ready(function() {
$("#submit").click(function() {
var var1 = document.getElementById("var1").value
// sends get request to URL
$.getJSON("URL" + var1, function(search, status) {
console.log(search);
// cont.
$("#results").text(search.results);
});
});
});
EDIT: Here is what I have so far with axios:
function performGetRequest() {
var var1 = document.getElementById('var1').value;
axios.get('URL', {
params: {
id: var1
}
})
.then(function (response) {
console.log(search);
})
}
I am not sure if the above code is correct or how to factor in keypress and click-- is there a simple way to do that?
Well, I am not sure what you want to do with this ajax call, but hopefully this may help you. Vue is data driven, so I always try to focus on that aspect. So this is an example of how you can access and input and send the data using axios.
<div>
<div>
<input v-model='input' type="text" placeholder="Search...">
</div>
<div>
<button #click="searchInput()">Submit</button>
</div>
</div>
<div>
<p>Results</p>
<p >{{ result }}</p>
</div>
you must have those models in your data
// Your data
data() {
return {
input: '',
result: '',
}
}
and your method will look something like this.
searchInput() {
axios({
method: 'GET',
url: url + this.input,
}).then(response => {
this.result = response.data;
}).catch(error => {
//handle error
})
}
So this is a very basic example. you can do the same process in different ways, you could pass the input to the method or loop over the results, but the idea is taking advantage of Vue.js data driven system and think data first.
Hopefully this will help you, remember to escape your input and add necessary validations. Good luck
Some background knowledge for my app:
Backend - Python, Flask. The database is currently just nested dictionaries and lists
Frontend - Reactjs
Functions that I will refer to:
FetchPlayers() - uses fetch() to get a array with all the players in it from the backend. It also adds the players to the state using setState().
EditPlayer(playerid) - uses fetch() to POST request the backend to change the information on one of the players.
So currently when the user starts the page, FetchPlayers() is called to get a list of players which will be displayed. Users can also edit these players information through a menu. When the user confirms the edit (through button press), first the app calls EditPlayer(), and then in the line after it calls FetchPlayers(), to update the list of players on the webpage.
The problem is the page is not updating with the new edit. For me the view the new edit I must reload the page. This suggests that FetchPlayers() is somehow being called before EditPlayer(). Does anyone know how I could fix this?
Thanks in advance!
Edit
edit_player(_playerid, _newfirstname, _newlastname, _newclass) {
fetch('http://' + Globals.BaseIP + Globals.PortForBackend + '/api/editplayer', {
//Options for the fetch request
method:'POST',
headers: {
//Header objects
'Accept':'application/json, text/plain, */*',
'Content-type':'application/json'
},
body:JSON.stringify({UserID:UserServiceLocal.UserID(), token:UserServiceLocal.token(), GameID:this.props.gameID, PlayerID:_playerid, Firstname:_newfirstname, Lastname:_newlastname, Class:_newclass}),
mode:'cors'
})
.catch((error) => console.log(error));
}
Code for FetchPlayers
FetchPlayers () {
fetch('http://' + Globals.BaseIP + Globals.PortForBackend + '/api/fetchplayers', {
//Options for the fetch request
method:'POST',
headers: {
//Header objects
'Accept':'application/json, text/plain, */*',
'Content-type':'application/json'
},
body:JSON.stringify({UserID:UserServiceLocal.UserID(), token:UserServiceLocal.token(), GameID:this.props.gameID}),
mode:'cors'
})
.then((res) => res.json())
.then((data) => this.parse_fetch_players_response(data))
.catch((error) => console.log(error))
}
parse_fetch_players_response(data) {
console.log(data['players']);
this.setState({Playerlist:data['players']});
this.ListScrollToBottom();
}
Code that runs when confirm edit
Btn_EditPlayer() {
this.edit_player(this.state.playereditID, this.state.playereditName,
this.state.playereditLastname, this.state.playereditClass);
this.FetchPlayers();
Render function:
return (
<div className="PageFooter" onKeyPress={(event) => this.EnterKeyPress_EditPlayer(event)}>
<Textinput className="FirstNameTextbox" id="playereditName" label="First name" value={this.state.playereditName} onChange={this.textinput_handleChange}/>
<Textinput className="LastNameTextbox" id="playereditLastname" label="Last name" value={this.state.playereditLastname} onChange={this.textinput_handleChange}/>
<Textinput className="ClassTextbox" id="playereditClass" label="Class" value={this.state.playereditClass} onChange={this.textinput_handleChange}/>
<button id='editPlayerButton' className="mdc-button mdc-button--unelevated mdl-button--colored mdc-ripple-surface" onClick={() => this.Btn_EditPlayer()}>Edit Player</button>
<button id="cancel-edit-player-btn" className="mdc-button mdc-button--raised mdl-button--colored mdc-ripple-surface" onClick={() => this.EditPlayer_Cancel_Btn()}>Cancel</button>
</div>
);
List Element Render function:
return (
<div>
<ul className="mdc-list" id="list-container" >
<li role="separator" className="mdc-list-divider"></li>
<li className="mdc-list-item" >
<span className="mdc-list-item__text list_text_firstname">
<b>Firstname</b>
</span>
<span className="mdc-list-item__text list_text_lastname">
<b>Lastname</b>
</span>
<span className="mdc-list-item__text list_text_class">
<b>Class</b>
</span>
<span className="mdc-list-item__graphic" role="presentation">
<i className="material-icons list_edit_icon">edit</i>
<i className="material-icons list_remove_icon">delete</i>
</span>
</li>
<li role="separator" className="mdc-list-divider"></li>
<ListEntry ListItemCSS="selected-list-entry" firstname="This is above" lastname="Butter" class="Jelly" id="1" delete_self={(playerID) => this.delete_item(playerID)} edit_button_clicked={(playerID) => this.Edit_Button_Clicked(playerID)}/>
<ListEntry firstname="Peanut" lastname="Butter" class="Jelly" id="1" delete_self={(playerID) => this.delete_item(playerID)} edit_button_clicked={(playerID) => this.Edit_Button_Clicked(playerID)}/>
{playerListItems}
</ul>
</div>
);
Try removing this.FetchPlayers() from Btn_EditPlayer() and adding it as a callback for edit_player like below:
this.edit_player(this.state.playereditID, this.state.playereditName,
this.state.playereditLastname, this.state.playereditClass, this.FetchPlayers);
edit_player(_playerid, _newfirstname, _newlastname, _newclass, callBack) {
fetch('http://' + Globals.BaseIP + Globals.PortForBackend + '/api/editplayer', {
//Options for the fetch request
method:'POST',
headers: {
//Header objects
'Accept':'application/json, text/plain, */*',
'Content-type':'application/json'
},
body:JSON.stringify({UserID:UserServiceLocal.UserID(), token:UserServiceLocal.token(), GameID:this.props.gameID, PlayerID:_playerid, Firstname:_newfirstname, Lastname:_newlastname, Class:_newclass}),
mode:'cors'
}).then((res) => {
// run your callback (fetchPlayers in this case) only when we know the update is done.
callBack()
}).catch((error) => console.log(error));
}
Edit (typooo)
It's impossible to know without seeing the code, but usually the problem is that you are not storing the information in state or your JSX in the render return isn't using the properties of this.state so it doesn't know to rerender.
I have this method:
getAnnouncements() {
this.restService.get('announcement').subscribe(data => {
this.announcements = data['payload'];
this.optionsFromDb = this.announcements;
this.options = this.optionsFromDb.map(option => ({checked: false, code: option.code, name: option.name}));
});
}
In html I have this:
<div class="form-group" *ngFor="let option of options">
<label class="checkbox-inline custom-checkbox nowrap">
<input type="checkbox" [checked]="option.checked" (change)="option.checked = !option.checked" />
<span>{{option.name}}</span>
</label>
</div>
getListOfChecked() {
return this.options.map( announcement => announcement.checked );
}
What I want is to display this option.name in html, but remove it from options. Any suggestion how can I do that? Because my post function receives only checked and code, not a name, thats why i want to remove it
If I understand, you want to display the name, but remove it before you send it to your web service, right ?
If you want to use map to do that, just do
let paylaodToSendToBackend = this.options.map(option => ({ checked: option.checked, code: option.code }));
before your HTTP call.
When I'm submitting the form, I get the following response:
{"data":{"attributes":{"title":null,"description":null},"type":"cards"}}
I'm not sure why I am getting title and description as null.
routes/cards/new.js:
actions: {
save() {
const newCard = this.get('store').createRecord('card', this.get('model'));
newCard.save().then((card) => {
this.transitionTo('cards.all');
});
},
cancel() {
this.transitionTo('cards');
}
}
templates/cards/new.hbs:
<form>
<div>
<label>Title:</label>
{{input type="text" value=model.title}}
</div>
<div>
<label>Body:</label>
{{textarea rows="5" value=model.description}}
</div>
<div>
<button {{action 'save'}}>Speichern</button>
<button {{action 'cancel'}}>Abbrechen</button>
</div>
</form>
Repo link: https://github.com/ghoshnirmalya/hub-client
You are not passing title and description from your .hbs to the route properly. You are creating the model after you fire the action save. Change model.title for title and do the same for the description. Pass them up to your route: {{ save title description }}. Then define two parameters in your save action like: save(title, description). I'm sure you can figure out the rest.
Here is what I usually do in my routes:
setupController(controller /*, model */ ) {
this._super(...arguments);
Ember.set(controller, 'newCard', {}); //newCard is an empty object
},
actions: {
save(newCard) {
Ember.assert('Model is missing or undefined', newCard);
let newCard = this.store.createRecord('card', newCard);
newCard.save().then(( /* response */ ) => {
this.transitionTo('cards.all');
}, (error) => {
//handle error
});
}
}
And in your template you could do something like this:
<form id="save" {{action "save" newCard on="submit"}}>
{{input name="title" id="title" value=newCard.title type="text"}}
<button class="button" type="submit">Save</button>
</form>
Hope this helps. Jeff
In comment you mentioned
doing a console.log(this.get('model')) just prints the model function
That's the answer to your question!. since in route you might have model hook function. so this.get('model') will return function instead of model.
So create controller for cards/new.js and you can move existing save actions. this should work.