In my code, I am displaying a paragraph and I want the user to change that paragraph and save it. So that the paragraph is updated and new. The component on which the text is displayed is a side panel of the main page. The code works, the user can edit the data and when saved, you can see it's updated in the network. But when I reopen the page, the old text is back as if I haven't done anything to it even though it has changed. My code is below, I searched, but I can't figure out the problem and what should I do to fix it.
HTML:
<div class="h-100-p" fxLayout="column" fusePerfectScrollbar>
<div class="group mt-32">
<div fxLayoutAlign="start center">
<header class="purple-fg" style="font-size:18.72px"><strong>Sticker Info:</strong></header>
</div>
<p>
<span contenteditable [textContent]="_stickerData?.StickerData" (input)="onStickerDataChange($event.target.innerHTML)">
{{_stickerData?.StickerData}}
</span>
</p>
</div>
</div>
<button mat-button matRipple class="purple-500 fuse-white-fg mr-12" (click)="save()"> Update Sticker </button>
TS:
private _stickerData: IStickerData;
#Input()
set StickerData(prm: IStickerData) {
if (this._stickerData != prm) {
this._stickerData = prm;
}
}
get StickerData(): IStickerData {
return this._stickerData;
}
dataSource: MatTableDataSource<IStickerData>;
constructor(
private _productionService: ProductionService,
private cd: ChangeDetectorRef,
) {}
ngOnInit() {
}
onStickerDataChange(data) {
this._stickerData.StickerData = data;
}
save(){
this.confirmDialogRef = this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false,
});
this.confirmDialogRef.componentInstance.confirmMessage =
"Sticker will be changed.";
this.confirmDialogRef.afterClosed().subscribe((result) => {
if (result) {
this._productionService
.saveStickerData(this._stickerData)
.subscribe((response: IStickerData) => {
this._stickerData = response;
this._messages.Show(
"Sticker is updated",
3
);
this.cd.markForCheck();
});
}
});
}
TS for service:
saveStickerData(data: IStickerData): Observable<IStickerData> {
return this._http.post("Production/SaveStickerData", data);
}
You need to fetch the data from where it's being saved.
Update your service file by adding a get function for the data. Something like...
production.service.ts
getStickerData(): Observable<IStickerData> {
return this.httpClient.get("Production/GetStickerData")
}
Then invoke the function in your app component:
app.component.ts
ngOnInit() {
this.productionService.getStickerData()
.subscribe(data => this._stickerData.StickerData = data)
}
That should populate the paragraph with saved and updated data from production...
Related
I'm writing a code where a user can change a paragraph on the screen and save it. Currently, the user can change the paragraph and the save() operation works. But when saved, the changed paragraph doesn't go through the network. The paragraph doesn't change it saves the unedited version as if I haven't written anything. What should I change to achieve that?
HTML:
<div class="content fuse-white ml-24 mr-8 h-100-p" fusePerfectScrollbar>
<div class="label-list">
<p>
A Paragraph:
<span contenteditable="true">
{{_stickerData?.StickerData}}
</span>
</p>
</div>
</div>
TS:
save(){
this.confirmDialogRef = this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false,
});
this.confirmDialogRef.componentInstance.confirmMessage =
"The paragraph will be changed";
this.confirmDialogRef.afterClosed().subscribe((result) => {
if (result) {
this._productionService
.saveStickerData(this._stickerData)
.subscribe((response: IStickerData) => {
this._stickerData = response;
this._messages.Show(
"SUCCESS",
3
);
});
}
});
}
Service TS:
saveStickerData(data: IStickerData): Observable<IStickerData> {
return this._http.post("Production/SaveStickerData", data);
}
[EDITED]
When you interpolate your variable in the template and use contenteditable - it doesn't update your variable. You should update it manually.
<span contenteditable [textContent]="_stickerData?.StickerData" (input)="onStickerDataChange($event.target.innerHTML)">
</span>
and in TS should be something like this:
onStickerDataUpdate(data) {
this._stickerData.StickerData = data;
}
Useful Links: 1, 2
[OLD RECOMMENDATIONS]
Are your sure that response has edited paragraph? If yes, Probably there is a problem with Change Detection. Import in component constructor ChangeDetectorRef and trigger markForCheck() method when getting data.
Nested subscriptions is a bad practice, try to refactor with switchMap()
This is a pseudocode to show the idea:
class MyComponent {
constructor(private cd: ChangeDetectorRef) {}
save(){
this.confirmDialogRef =
this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false,});
this.confirmDialogRef.componentInstance.confirmMessage =
"The paragraph will be changed";
this.confirmDialogRef.afterClosed().pipe(switchMap(result) => {
if (result) {
return this._productionService.saveStickerData(this._stickerData);
} else {
return EMPTY;
}
}).subscribe((response) => {
this._stickerData = response;
this._messages.Show(
"SUCCESS",
3
);
this.cd.markForCheck();
});
}
}
I'm having trouble finding a way to do this. I want to display a mat-error on my input form if an http get request fails (status = 404).
I have a search form and every time a user searches for something that doesn't exist I want to display the mat-error telling the user that his search is not valid.
Here's my client side code:
rest.service.ts
private extractData(res: Response) {
let body = res;
return body || { };
}
getOrder(id): Observable<any> {
return this.http.get(endpoint + 'orders/' + id, httpOptions).pipe(
map(this.extractData));
}
getReturns(): Observable<any> {
return this.http.get(endpoint + 'returns', httpOptions).pipe(
map(this.extractData));
}
MyComponent.component.ts
Here I have the getReturnByOrderID function that checks if the response data is empty or not. If it isn't then I open a mat dialog that will lead me to some other part of my site, however, if it is empty then I'm supposed to warn the user that what he has searched for doesn't exist.
I'm also using the getErrorMessage() function to handle all the form errors.
private inputForm = new FormControl('', [Validators.pattern(/^\d+$/)]);
constructor(public rest: RestService, private route: ActivatedRoute, private router: Router, public dialog: MatDialog) { }
getReturnByOrderId(value) {
if (value.length > 0) {
this.rest.getOrder(value).subscribe((data: {}) => {
if (Object.entries(data).length !== 0) {
this.openDialog(data);
} else {
//if 404, do something here
}
});
} else {
this.rest.getReturns().subscribe((data: {}) => {
this.returns = data;
});
}
}
openDialog(el) {
const dialogRef = this.dialog.open(MyDialogComponent, {
width: '70%',
data: el
});
dialogRef.afterClosed().subscribe(result => {
console.log(`Dialog result: ${result}`);
});
}
getErrorMessage() {
return this.inputForm.hasError('pattern') ? 'Insert numeric value!' : '';
// if the get request fails I'd have a message here saying "The order you searched for doesn't exist!"
}
MyComponent.component.html
<div class="row">
<div class="col-2"></div>
<div class="col-8">
<mat-form-field class="search-form-field" appearance="outline">
<mat-label>Search</mat-label>
<input matInput class="search-input" placeholder="Search" [formControl]="inputForm" #searchInput>
<mat-error *ngIf="inputForm?.invalid">{{ getErrorMessage() }}</mat-error>
<mat-hint>Insert an order ID.</mat-hint>
</mat-form-field>
<span matSuffix>
<button mat-raised-button class="search-btn" color="accent" [disabled]="inputForm?.invalid" (click)="getReturnByOrderId(searchInput.value)"><i class="fa fa-search"></i></button>
</span>
</div>
<div class="col-2"></div>
</div>
I'm not sure if my server side code would be usefull, if anyone needs it I'll edit my question...
Any suggestions on how I can achieve this? Thanks for the help!
When the server returns 404 status code, in this case, your subscribe block won't execute. so your need to write error block where you can handle 404 error and set form as invalid like below.
getReturnByOrderId(value) {
if (value.length > 0) {
this.rest.getOrder(value).subscribe((data: {}) => {
if (Object.entries(data).length !== 0) {
this.openDialog(data);
} else {
// if data is empty show
this.inputForm.setErrors({ 'invalid': true });
}
}, error => {
//when 404 error
this.inputForm.setErrors({ 'invalid': true });
});
} else {
this.rest.getReturns().subscribe((data: {}) => {
this.returns = data;
});
}
}
Hope this will help!
I followed the following tutorial to create a chat application.
https://github.com/ammezie/laravel-chat
Every thing is right, messages are storing in db , showing on console in pusher desktop, all message show on page re load.
Problem is when i send a new message it not show in other user tab until i reload the page. I need to make it dynamic
following is the code for app.js where fetch function is written
created() {
this.fetchMessages();
Echo.private('chat')
.listen('MessageSent', (e) => {
this.messages.push({
message: e.message.message,
user: e.user
});
});
},
methods: {
fetchMessages() {
axios.get('/messages').then(response => {
this.messages = response.data;
});
},
addMessage(message) {
this.messages.push(message);
axios.post('/messages', message).then(response => {});
}
here
Following is chat view code of component
<template>
<ul class="chat">
<li class="left clearfix" v-for="message in messages">
<div class="chat-body clearfix">
<div class="header">
<strong class="primary-font">
{{ message.user.name }}
</strong>
</div>
<p>
{{ message.message }}
</p>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['messages']
};
</script>
Thanks for help if some thing unclear i'll provide
You can try.
methods: {
var vm = this;
fetchMessages() {
axios.get('/messages').then(response => {
vm.messages = response.data;
});
},
addMessage(message) {
var vm = this;
vm.messages.push(message);
axios.post('/messages', message).then(response => {});
}
using this inside function causing a problem, because it refers to that particular function create a global variable with reference to this
Hope this helps.
You may want to check if there are Laravel Echo credentials for pusher correct in bootstrap.js
I have used List.JS before successfully, but this time I'm trying to use it with a Vue.JS rendering of a list from JSON data.
I have a button at the top that when clicked should show only the QB position player.
Unfortunately I just get nothing, all list items are removed and I don't get an error in the console so I'm not sure how to diagnose this.
Could it have something to do with the fact that the list elements aren't prerendered/static html but injected using vue.js?
https://jsfiddle.net/nolaandy/hw2mheem/
HTML/Vue Template
<div id='app'>
<div class="all-players-wrapper" id="all-player-listings">
<button id="filter-qb">QB</button>
<ul class="list">
<li v-for="player in playerJSON">
<div class="player-listing">
<div class="player-left">
<div class="player-name">{{player.firstName}} {{player.lastName}}</div>
<div class="playerPosition">{{ player.Position }}</div>
</div><!-- end player-left -->
<div class="player-right">
<div class="player-grade">GRADE <span>{{player.NFLGrade}}</span></div>
</div> <!--end player-right -->
</div>
</li>
</ul>
</div>
</div>
JS
var vm = new Vue({
el: '#app',
data: {
status: 'Combine Particpants',
playerJSON: []
},
created: function () {
this.loadData();
},
methods: {
loadData: function () {
var self = this;
axios.get('https://s3-us-west-2.amazonaws.com/s.cdpn.io/500458/tiny.json').then(function (response) {
self.playerJSON = response.data
console.log(response.data);
})
.catch(function (error) {
self.status = 'An error occurred - ' + error
});
}
}
});
var options = {
valueNames: [ 'playerPosition' ]
};
var featureList = new List('all-player-listings', options);
$('#filter-qb').click(function() {
featureList.filter(function(item) {
if (item.values().playerPosition == "QB") {
return true;
} else {
return false;
}
});
return false;
});
As you suspected, List.js isn't going to work properly if the DOM changes unpredictably. In this case, axios makes its call and populates the data after the (empty) List has been read into featureList.
Your example would work if you put the list-selecting-and-filtering code in the resolution of the axios call, but that's not going to be a solution that works in a truly dynamic environment.
A custom directive will be called every time the DOM updates, so you can apply your adjustments consistently. Here's a directive to apply a filter using List.js:
directives: {
filteredList(el, binding) {
if (binding.value) {
const options = {
valueNames: ['playerPosition']
};
const featureList = new List(el, options);
featureList.filter((item) => item.values().playerPosition === binding.value);
}
}
}
Apply it like so:
<div class="all-players-wrapper" v-filtered-list="filterValue">
Add the filterValue data item, and have the button set it:
<button id="filter-qb" #click="() => filterValue='QB'">QB</button>
and you're in business.
It's worth noting that you could get the same effect by using a computed to filter the data, and you wouldn't need an external library.
Updated fiddle
i have problem. When I click the button, it receives an entire database, but I want laod part database. How can I do this?
For example: After every click I would like to read 10 posts.
Thx for help.
Messages.vue:
<div class="chat__messages" ref="messages">
<chat-message v-for="message in messages" :key="message.id" :message="message"></chat-message>
<button class="btn btn-primary form-control loadmorebutton" #click="handleButton">Load more</button>
</div>
export default{
data(){
return {
messages: []
}
},
methods: {
removeMessage(id){...},
handleButton: function () {
axios.get('chat/messagesmore').then((response) => {
this.messages = response.data;
});
}
},
mounted(){
axios.get('chat/messages').then((response) => {
this.messages = response.data
});
Bus.$on('messages.added', (message) => {
this.messages.unshift(message);
//more code
}).$on('messages.removed', (message) => {
this.removeMessage(message.id);
});
}
}
Controller:
public function index()
{
$messages = Message::with('user')->latest()->limit(20)->get();
return response()->json($messages, 200);
}
public function loadmore()
{
$messages = Message::with('user')->latest()->get();
// $messages = Message::with('user')->latest()->paginate(10)->getCollection();
return response()->json($messages, 200);
}
paginate(10) Loads only 10 posts
You can do it like this:
<div class="chat__messages" ref="messages">
<chat-message v-for="message in messages" :key="message.id" :message="message"></chat-message>
<button class="btn btn-primary form-control loadmorebutton" #click="handleButton">Load more</button>
</div>
export default{
data(){
return {
messages: [],
moreMessages: [],
moreMsgFetched: false
}
},
methods: {
removeMessage(id){...},
handleButton: function () {
if(!this.moreMsgFetched){
axios.get('chat/messagesmore').then((response) => {
this.moreMessages = response.data;
this.messages = this.moreMessages.splice(0, 10);
this.moreMsgFetched = true;
});
}
var nextMsgs = this.moreMessages.splice(0, 10);
//if you want to replace the messages array every time with 10 more messages
this.messages = nextMsgs
//if you wnt to add 10 more messages to messages array
this.messages.push(nextMsgs);
}
},
mounted(){
axios.get('chat/messages').then((response) => {
this.messages = response.data
});
Bus.$on('messages.added', (message) => {
this.messages.unshift(message);
//more code
}).$on('messages.removed', (message) => {
this.removeMessage(message.id);
});
}
}
-initialize a data property morMsgFetched set to false to indicate if more messages are fetched or not
if morMsgFetched is false make the axios request and st the response to moreMessages, then remove 10 from moreMessages and set it to messages[]..
After that set morMsgFetched to true
on subsequest click remove 10 from moreMessages and push it to 'messages[]`
Use Laravels built in pagination.
public function index()
{
return Message::with('user')->latest()->paginate(20);
}
It returns you next_page url which you can use to get more results calculated automatically
This might be too late but i believe the best way to do it is using pagination, Initially onMounted you'll send a request to let's say /posts?page=1, the one is a variable let's say named 'pageNumber', each time the user clicks on the "Load More" button, you'll increment the pageNumber and resent the request, the link will page /posts?page=2 this time, at this point you can append the results you've got to the already existing one and decide if the Load More button should be shown based on the last_page attribute returned by laravel paginator...
I'm sure you already solved your problem or found another alternative, this might be usefull for future developers.