Vue.js method - Access to `this` within lodash _each loop - javascript

My app has a list of collaborators, and a checkbox next to each one.
The user can check off multiple collaborators, then click a button to remove them, which triggers the following Vue.js method:
methods: {
remove: function () {
if (confirm('Are you sure you want to delete these collaborators?')) {
axios.get('/collaborators/api/destroy', {
params: {
ids: this.selectedCollaborators
}
})
.then(response => {
// Loop through the `selectedCollaborators` that were deleted and
// remove them from `collaborators`
_.each(this.selectedCollaborators, function (value, key) {
console.log('Remove collaborator: ' + value);
// Following line produces: TypeError: Cannot read property 'collaborators' of undefined
this.collaborators.splice(this.collaborators.indexOf(value), 1)
});
});
}
},
// [...etc...]
As you can see in the above code, when handling the ajax response, I attempt to loop through each of the selectedCollaborators using a lodash's _each, and for each one, remove that collaborator from the collaborators data property using splice.
The problem is this.collaborators is not accessible within the _.each function and the following error is produced:
TypeError: Cannot read property 'collaborators' of undefined
How can I fix this/is there a better way to handle this?

Try replace function to arrow function with lexical context:
methods: {
remove: () => {
if (confirm('Are you sure you want to delete these collaborators?')) {
axios.get('/collaborators/api/destroy', {
params: {
ids: this.selectedCollaborators
}
})
.then(response => {
// Loop through the `selectedCollaborators` that were deleted and
// remove them from `collaborators`
_.each(this.selectedCollaborators, (value, key) => {
console.log('Remove collaborator: ' + value);
// Following line produces: TypeError: Cannot read property 'collaborators' of undefined
this.collaborators.splice(this.collaborators.indexOf(value), 1)
});
});
}
},

What you can do is save this in a variable.
methods: {
remove: function () {
if (confirm('Are you sure you want to delete these collaborators?')) {
axios.get('/collaborators/api/destroy', {
params: {
ids: this.selectedCollaborators
}
})
.then(response => {
const t = this;
// Loop through the `selectedCollaborators` that were deleted and
// remove them from `collaborators`
_.each(this.selectedCollaborators, function (value, key) {
console.log('Remove collaborator: ' + value);
t.collaborators.splice(t.collaborators.indexOf(value), 1)
});
});
}
},
// [...etc...]

Related

Way to proxy/override ServiceWorkerRegistration function

I'm trying to proxy the showNotification method of a ServiceWorkerRegistration object. Here's how I'm doing it right now:
function swNotificationCallback(title, opt) {
console.log("title", title);
console.log("options", opt);
return true
}
function createSWHandler(original) {
return (title, opt) => {
if (swNotificationCallback(title, opt)) {return original(title, opt)}
}
}
navigator.serviceWorker.getRegistrations().then(val => val.forEach(sw => {
if (!sw._showNotification) {
// backup the old just in case
sw._showNotification = sw.showNotification;
sw.showNotification = createSWHandler(sw.showNotification);
}
}));
Calling sw.showNotification correctly logs everything, but no notification is shown and this error is thrown:
Uncaught (in promise) TypeError: 'showNotification' called on an object that does not implement interface ServiceWorkerRegistration.
Any way to remedy this? I think it might be possible to use a Proxy but I'm not sure how one would go about reassigning the registered service worker.
You need to invoke the method on the instance, not as a plain function without context. Use call:
function createSWHandler(original) {
return function(title, opt) {
// ^^^^^^^^
if (swNotificationCallback(title, opt)) {
return original.call(this, title, opt)
// ^^^^^^^^^^^
}
}
}
Alternatively you could
createSWHandler(sw.showNotification.bind(sw))

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

VueJS how to use _.debounce on data changes

I'm building a little vue.js-application where I do some post requests. I use the watch-method to whach for api changes which then updates the component if the post request is successfull. Since the watcher constantly checks the API I want to add the ._debounce method but for some reason it doesn't work.
here is the code:
<script>
import _ from 'lodash'
export default {
data () {
return {
cds: [],
cdCount: ''
}
},
watch: {
cds() {
this.fetchAll()
}
},
methods: {
fetchAll: _.debounce(() => {
this.$http.get('/api/cds')
.then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
created() {
this.fetchAll();
}
}
</script>
this gives me the error: Cannot read property 'get' of undefined
Can someone maybe tell me what I'm doing wrong?
EDIT
I removed the watch-method and tried to add
updated(): {
this.fetchAll()
}
with the result that the request runs in a loop :-/ When I remove the updated-lifecycle, the component does (of course) not react to api/array changes... I'm pretty clueless
Mind the this: () => { in methods make the this reference window and not the Vue instance.
Declare using a regular function:
methods: {
fetchAll: _.debounce(function () {
this.$http.get('/api/cds/add').then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
Other problems
You have a cyclic dependency.
The fetchAll method is mutating the cds property (line this.cds = response.body) and the cds() watch is calling this.fetchAll(). As you can see, this leads to an infinite loop.
Solution: Stop the cycle by removing the fetchAll call from the watcher:
watch: {
cds() {
// this.fetchAll() // remove this
}
},

How to add a function to Object's named property with correct context in javascript

I have a working piece of code as below:
let pageParams = {
data: { todos: [], desc: '' }
}
pageParams.onLoad = function () {
//I am trying to encapsulate this to a standalone function and
// make it generic, instead of hard coding the 'this.addTodo=XXX'
const evProducer = {
start: listener => {
//Here, I am adding a named property function
this.addTodo = ev => {
listener.next(ev.detail.value)
}
},
stop: ()=>{}
}
const input$ = xs.create(evProducer)
input$.compose(debounce(400)).subscribe({
next: val => console.log(val)
})
}
The code works and now I am going to do some refactor work, i.e. move the logic out of this onLoad function. So I move the logic to another module
let xsCreator = {}
xsCreator.fromEvent = function(handler){
const evProducer = {
start: listener => {
handler = ev => listener.next(ev.detail.value)
},
stop: () => {}
}
return xs.create(evProducer)
}
And in the previous onLoad function becomes the following:
pageParams.onLoad = function () {
xs.fromEvent(this.addTodo).subscribe(blablabla)
}
but it does not work. I guess I might use apply/call/bind to make this work, but don't know how to. Anyone can help? Thanks in advance
I've found the solution, I should use Object.defineProperty to add a named property for object.
xsCreator.fromInputEvent = (srcObj, propertyName) => {
const evProducer = {
start: (listener) => {
Object.defineProperty(
srcObj,
propertyName,
{value: ev => listener.next(ev.detail.value)})
},
stop: () => {}
}
return xs.create(evProducer)
}

Update an item in the list

I'm a beginner on knockout.
I made this page:
http://jsfiddle.net/LhTx4/
I would like to update only the item that comes back from the sellIt function.
How can I do that?
You are setting the quantity property incorrectly. quantity is a ko observable so you need to use the syntax:
self.sellIt = function (product) {
$.post('/Product/SellIt', { id: product.id },
function (data) {
var res = Enumerable.From(self.products)
.Where("i => i.id == " + data.Id)
.Select("s => s");
res.quantity(data.Quantity); // this is the important bit!!
});
};
However, I think you could probably shorten your code down to just:
self.sellIt = function (product) {
$.post('/Product/SellIt', { id: product.id },
function (data) {
product.quantity(data.Quantity);
});
};

Categories