In my app, I have a list where the user can add to and delete elements from it. My problem is, when I click an element (it can be in the middle, at the end etc.), it deletes the first element of the list. And when I refresh the page, I can see the previously 'deleted' elements. Like I haven't deleted anything. Here is my code. What's wrong with it and how should I fix it?
HTML:
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row)">block</mat-icon>
</button>
TS:
deleteWorkItem(row: IProduct, index: number) {
let supplierProduct: ISupplierProduct = {
Supplier: {
SupplierId: this.SupplierId
},
Product: {
ProductId: row.ProductId
}
};
this.confirmDialogRef = this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false
});
this.confirmDialogRef.componentInstance.confirmMessage = 'Ürünü silmek istiyor musunuz?';
this.confirmDialogRef.afterClosed().subscribe(result => {
if (result) {
this._service.update('Supplier/DeleteSupplierProduct', supplierProduct).subscribe(response => {
this._customNotificationService.Show('Ürün silindi', 'Tamam', 2);
});
let tempData = this.dataSource.data.slice(0);
tempData.splice(index, 1);
this.dataSource = new MatTableDataSource(tempData);
this.EditIndex = undefined;
this._products = this.dataSource.data;
this.ProductChange.emit(this._products);
}
});
}
You don't seem to pass index into deleteWorkItem method.
You need to declare a template variable within *ngFor as follows:
<div *ngFor="let row of data; let i = index">
...
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row, i)">block</mat-icon>
</button>
</div>
Related
I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>
Here's my issue. I created a tool with vue.js and the WordPress API to search through the search endpoints for any keyword and display the result. So far so good, everything is working, except for a bug that I spotted.
Here's the deal:
const websiteurl = 'https://www.aaps.ca'; //yourwebsite or anything really
var vm = new Vue({
el: '#blog-page',
data: {
noData: false,
blogs: [],
page: 0,
search: '',
totalPagesFetch: "",
pageAmp: "&page=",
apiURL: `${websiteurl}/wp-json/wp/v2/posts?per_page=6`,
searchbyid: `${websiteurl}/wp-json/wp/v2/posts?per_page=6&include=`,
searchUrl: `${websiteurl}/wp-json/wp/v2/search?subtype=post&per_page=6&search=`,
},
created: function () {
this.fetchblogs();
},
methods: {
fetchblogs: function () {
let self = this;
self.page = 1;
let url = self.apiURL;
fetch(url)
.then(response => response.json())
.then(data => vm.blogs = data);
},
searchonurl: function () {
let ampersand = "&page=";
searchPagination(1, this, ampersand);
},
}
});
function searchPagination(page, vm, pagen) {
let self = vm;
let searchword = self.search.toLowerCase();
let newsearchbyid = self.searchbyid;
let url;
self.page = page;
url = self.searchUrl + searchword + pagen + self.page;
self.mycat = 'init';
fetch(url)
.then(response => {
self.totalPagesFetch = response.headers.get("X-WP-TotalPages");
return response.json();
})
.then(data => {
let newid = [];
data.forEach(function (item, index) {
newid.push( item.id );
});
if (newid.length == 0) {
return newsearchbyid + '0';
} else {
return newsearchbyid + newid;
}
})
.then(response2 => {
return fetch(response2)
})
.then(function(data2) {
return data2.json();
})
.then(function(response3) {
console.log(response3)
if (response3.length == 0) {
vm.noData = true;
vm.blogs = response3;
} else {
vm.noData = false;
vm.blogs = response3;
}
})
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="lazyblock-blogs testblog" id="blog-page">
<div class="container">
<div class="row controls">
<div class="col-md-12">
<div class="search-blog">
<img height="13" src="" alt="search">
<input id="sb" type="text" v-model="search" #keyup="searchonurl" placeholder="search">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4" v-for="(b, index) in blogs">
<div class="h-100 box" v-cloak>
<img width="100%" v-bind:src=b.featured_image_url>
<a v-bind:href="b.link">
<h3 v-html=b.title.rendered></h3>
</a>
<div v-html=b.excerpt.rendered></div>
<p class="read-more"><a v-bind:href="b.link">read more</a></p>
</div>
</div>
<div class="no-data" v-if="noData">
<div class="h-100">
No post
</div>
</div>
</div>
</div>
</div>
I'm using a keyup event which is causing me some problems because it works, but in same cases, for example, if the user is very fast to type characters and then suddenly he wants to delete the word and start again, the response for the API has some sort of lag.
The problem is that I guess that the Vue framework is very responsive (I create a variable call search that will update immediately) but the API call in the network is not (please check my image here):
This first image appears if I type lll very fast, the third result will return nothing so it is an empty array, but if I will delete it immediately, it will return an url like that: https://www.aaps.ca//wp-json/wp/v2/search?subtype=post&per_page=6&search=&page=1 which in turn should return 6 results (as a default status).
The problem is that the network request won't return the last request but it gets crazy, it flashs and most of the time it returns the previous request (it is also very slow).
Is that a way to fix that?
I tried the delay function:
function sleeper(ms) {
return function(x) {
return new Promise(resolve => setTimeout(() => resolve(x), ms));
};
}
and then I put before the then function:
.then(sleeper(1000))
but the result is the same, delayed by one second (for example)
Any thought?
This is the case for debounced function. Any existing implementation can be used, e.g. Lodash debounce. It needs to be declared once per component instance, i.e. in some lifecycle hook.
That searchPagination accepts this as an argument means that something went wrong with its signature. Since it operates on component instance, it can be just a method and receive correct this context:
methods: {
searchPagination(page, pagen) {
var vm = this;
...
},
_rawsearchonurl() {
let ampersand = "&page=";
this.searchPagination(1, ampersand);
}
},
created() {
this.searchonurl = debounce(this._rawsearchonurl, 500);
...
}
You could use debounce, no call will leave until the user stop typing in the amount of time you chose
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
}
// in your "methods" (I put 1000ms of delay) :
searchonurl: function () {
let ampersand = "&page=";
debounce(searchPagination, 1000)(1, this, ampersand);
}
One of best ways is to use Debounce which is mentioned in this topic
Or use a function and combine it with watch. Follow these lines:
In mounted or created make an interval with any peroid you like (300 etc.) define a variable in data() and name it something like searched_value. In interval function check the value of your input and saerch_value, if they were not equal (===) then replace search_value with input value. Now you have to watch search_value. When it changed you call your api.
I use this method and works fine for me. Also it`s managable and everything is in your hand to config and modify.
===== UPDATE: =====
A simple code to do what I said above
<template>
<div>
<input type="search" v-model="search_key">
</div> </template>
<script> export default {
name: "SearchByApi",
data() {
return {
search_key: null,
searched_item: null,
loading: false,
debounceTime: 300,
}
},
created() {
this.checkTime()
const self = this
setInterval(function() {
self.checkTime()
}, this.debounceTime);
},
watch: {
searched_item() {
this.loadApi()
}
},
methods: {
checkTime() {
if (this.searched_item !== this.search_key && !this.loading) {
this.searched_item = this.search_key
}
},
loadApi() {
if (!this.loading && this.searched_item?.length > 0) {
this.loading = true
const api_url = 'http://api.yourdomain.com'
axios(api_url, {search: this.searched_item}).then(res => {
// whatever you want to do when SUCCESS
}).catch(err => {
// whatever you want to do when ERROR
}).then(res => {
this.loading = false
})
}
}
}
}
</script>
I have an object called main and its one of the item, data: , points to an array defined as submenuitem. When user clicks on something, the UI gets updated with array items in submenuitems. It also updates the topbar to display the title of current menu which is basically the variable name SUBMENUITEMS.
Can anyone tell how can I get the submenu item variable name data: "--->this<--" as String?
let submenuItems = ["subMenu 1","subMenu 2","subMenu 3","subMenu 4","subMenu 5","subMenu 6"];
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:submenuItems
}
Edit:
I see a lot of confusion. Actually this a part of my code which is written using Vuejs -
<div v-for="(item,index) in dataArray" v-on:click="subMenu(item)" v-bind:id="'tile_'+index" class="card">
<div><img v-bind:src=item.menuLogo onerror="this.src='img/default.svg'" /></div>
<div>{{item.title}}</div>
<div>{{index+1}}</div>
</div>
let subhome = [
{"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]},
{"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]}, {"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]},`
{"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]}
]
let home = [
{"title":"card_title","cardimage":"item1.png",data:[]},
{"title":"card_title","cardimage":"item1.png",data:subhome}, {"title":"card_title","cardimage":"item1.png",data:[]},`
{"title":"card_title","cardimage":"item1.png",data:[]}
]
let menu = new Vue({
el: '#menu',
data: {
dataArray: home
},
methods: {
subMenu: function(item) {
if (item.data.length > 0) {
this.dataArray = item.data;
console.log('sub menu array -->', this.dataArray);
$('#menu-title').text(eval(item));
}
},setTitle: function(val) {
let title = eval(val); // <---- how to vaiable name of data array
$('#spancontainer').text(title)
}
}
});
When you do
let submenuItems = ["subMenu 1","subMenu 2","subMenu 3","subMenu 4","subMenu 5","subMenu 6"];
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:submenuItems
}
it is equating to
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:["subMenu 1","subMenu 2","subMenu 3","subMenu 4","subMenu 5","subMenu 6"]
}
In order to get "submenuItems", you'd need to pass it as an object and get its key like this
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:{
submenuItems: submenuItems
}
}
console.log(Object.keys(main.data)[0]);
or simply create a new property and set it to "submenuItems" like this
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:{
name: 'submenuItems',
submenuItems: submenuItems
}
}
console.log(main.data.name);
Sorry for my English. I am trying to pre select those checkboxes whos values have been saved in the database. I did it using javascript in vuejs but those selected checkboxes values are not storing in array.
My code is like
role.component.js
getRoleRowData(data) {
this.roleaction = "edit";
this.addrolemodal = true;
console.log(data.role_id);
axios
.post(apiUrl.api_url + "getRolePermissionData", {
role_id: data.role_id
}).then(
result => {
this.permid = result.data;
var list = [];
result.data.forEach(function(value) {
list.push(value.perm_id);
});
var options = list;
for (var i = 0; i < options.length; i++) {
if (options[i]) document.querySelectorAll('input[value="' + options[i] + '"][type="checkbox"]')[0].checked = true;
}
},
error => {
console.error(error);
}
);
this.addrole = data;
},
And role.component.html
<div class="col-md-8">
<b-form-fieldset>
<div class="form" id="demo">
<h6>Permissions</h6>
<span v-for="perm_name_obj in listPermissionData">
<input type="checkbox" class="perm_id" v-bind:value="perm_name_obj.perm_id" name="perm_id" id="perm_name" v-model="checkedPerm_Id"> {{perm_name_obj.perm_name}}<br>
</span>
<span>Checked names: {{ checkedPerm_Id }}</span>
</div>
</b-form-fieldset>
</div>
And the Output
And the Ouput I got
In simple words I want to pre check checkboxes in vuejs of which values are stored in database.
See the following example, using simulation data
var app = new Vue({
el: '#app',
data () {
return {
listPermissionData: [],
checkedPerm_Id: []
}
},
created () {
setTimeout(_=>{
//Here simulates axois to request Permission data
this.listPermissionData = [
{perm_id:1,perm_name:'perm_name1'},
{perm_id:2,perm_name:'perm_name2'},
{perm_id:3,perm_name:'perm_name3'},
{perm_id:4,perm_name:'perm_name4'},
{perm_id:5,perm_name:'perm_name5'}
];
//Here simulates axois to request Selected Permissions (result.data)
var selected = [
{perm_id:2,perm_name:'perm_name2'},
{perm_id:5,perm_name:'perm_name5'}
]
this.checkedPerm_Id = selected.map(o=>o.perm_id)
},1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div class="form">
<h6>Permissions</h6>
<span v-for="perm_name_obj in listPermissionData">
<input type="checkbox" class="perm_id" v-bind:value="perm_name_obj.perm_id" name="perm_id" id="perm_name" v-model="checkedPerm_Id"> {{perm_name_obj.perm_name}}<br>
</span>
<span>Checked names: {{ checkedPerm_Id }}</span>
</div>
</div>
I solved my problem, here is my code
role.component.js
getRoleRowData(data) {
this.roleaction = "edit";
this.addrolemodal = true;
console.log(data.role_id);
let tempthis = this;
axios
.post(apiUrl.api_url + "getRolePermissionData", {
role_id: data.role_id
}).then(
result => {
this.permid = result.data;
var list = [];
result.data.forEach(function(value) {
//by using tempthis variable we provided the current access to the checkedPerm_Id array which not accessible from out of the function which is getRoleRowData
tempthis.checkedPerm_Id.push(value.perm_id);
list.push(value.perm_id);
});
},
error => {
console.error(error);
}
);
this.addrole = data;
},
All I want to do is to display the value of a ngModel, a variable defined in my controller. But the value didn't change to the correct value until I click somewhere else on the page or click the update button again. Although it does change to the correct value in the console.
My themeLayout.html file
<div class="theme-body" ng-controller="ThemeController as theme">
<select ng-model="theme.selectedTheme" ng-change="theme.getThemeDetails(theme.selectedTheme)">
<option ng-repeat="option in theme.themePacks">{{option.title}}</option>
</select>
<div class="form-group">
<label for="appLogo">App Logo</label>
<div>
<img ng-src="{{theme.currentThemeImageUrl}}" alt="Description" />
</div>
<input type="text" class="form-control" id="appLogo" ng-model="theme.currentThemeImageUrl">
</div>
</div>
And this is my theme controller
export default class ThemeController {
constructor($log, ApiService, $scope, $state, $window) {
'ngInject';
this.s3 = new this.AWS.S3();
this.selectedTheme = '';
this.currentThemeId = '';
this.currentS3ThemeOption = {};
this.currentS3ThemeOptionToUpload = {};
this.currentThemeImageUrl = '';
}
getThemeDetails(theme) {
this.$log.log(`get theme details function been called`, theme);
const obj = this;
for(let item of this.themePacks) {
if (theme === item.title) {
this.currentThemeId = item.themeId;
}
}
this.s3.getObject({
Bucket: `improd-image-pipeline`,
Key: `remoteUX/qa/${obj.currentThemeId}.json`,
}, (err, data) => {
if (err) {
obj.$log.log(err);
} else {
obj.currentS3ThemeOption = JSON.parse(data.Body);
obj.currentS3ThemeOptionToUpload = obj.currentS3ThemeOption;
for (const prop in obj.currentS3ThemeOption.colors) {
obj[prop] = obj.getColors(obj.currentS3ThemeOption.colors[prop]);
}
obj.currentThemeImageUrl = obj.currentS3ThemeOption.layout.titleImageUrl;
obj.$log.log(`We should have upadted theme opion now`, obj.currentS3ThemeOption, obj.currentThemeImageUrl);
}
});
this.$log.log(obj.currentS3ThemeOption, this.currentS3ThemeOption);
}
}
This is when I click the fox option in the selection, it read the data and stroe it into the currentSeThemeOption.
As you can see from the console, it also print of the value
What I am thingking is that might the 'this' and obj is causing the problem.
After I did add $scope.apply() function as he suggested, but it didn't solve the problem.
Update your model inside a scope.$apply() call:
getThemeDetails(theme) {
this.$log.log(`get theme details function been called`, theme);
const obj = this;
for(let item of this.themePacks) {
if (theme === item.title) {
this.currentThemeId = item.themeId;
}
}
this.s3.getObject({
Bucket: `improd-image-pipeline`,
Key: `remoteUX/qa/${obj.currentThemeId}.json`,
}, (err, data) =>
scope.$apply(() => {
if (err) {
obj.$log.log(err);
} else {
obj.currentS3ThemeOption = JSON.parse(data.Body);
obj.currentS3ThemeOptionToUpload = obj.currentS3ThemeOption;
for (const prop in obj.currentS3ThemeOption.colors) {
obj[prop] = obj.getColors(obj.currentS3ThemeOption.colors[prop]);
}
obj.currentThemeImageUrl = obj.currentS3ThemeOption.layout.titleImageUrl;
obj.$log.log(`We should have upadted theme opion now`, obj.currentS3ThemeOption, obj.currentThemeImageUrl);
}
})
);
this.$log.log(obj.currentS3ThemeOption, this.currentS3ThemeOption);
}
Once you have assigned a $scope object into the ng-model object. then you just put
$scope.$apply();
so that it will bind properly with UI elements.
It's the problem of digest cycle the digest cycle in triggered in only in four conditions
event (i.e your click),
http request,
change in input,
Timers with callbacks ($timeout etc.),