swimlane/ngx-datatable, How can I kick the cellClass function? - ngx-datatable

The cellClass function is not called when the component properties change.
How do I kick a rowClass orcellClass?
#Component({
...,
template: `<ngx-datatable [rowClass]="rowClass"></ngx-datatable>`
})
class SomeComponent {
someVariable = true;
rowClass = (row) => {
return {
'some-class': (() => { return this.someVariable === row.someVariable })()
};
}
}
Related
https://github.com/swimlane/ngx-datatable/issues/774

I was able to solve it by changing this.rows.
https://swimlane.gitbook.io/ngx-datatable/cd
this.rows = [...this.rows];
If you are using a store, you need to cancel the immutable attribute.
Example
#Input() set list(list: Record<string, unknown>[]) {
if (list.length) {
// If the search results are reflected in the table.
// And 20 items are loaded at a time.
if (list.length === 20) {
this.rows = list.map((item) => ({ ...item }));
// Load more items
} else {
const newRows = list.map((item) => ({ ...item })).slice(this.rows.length);
this.rows = this.rows.concat(newRows);
}
}
}

Related

Vue 3 ref access always returns undefined

Ok. so I do have this image object I get from my DB and store it into a vuex store:
// State
const state = {
userImages: [],
};
// Getters
const getters = {
getImages: (state) => {
return state.userImages;
},
getFilePathExists: (state) => (fullPath) => {
return !!state.userImages.find((item) => item.fullPath === fullPath);
},
getFileNameExists: (state) => (name) => {
return !!state.userImages.find((item) => item.name === name);
},
getImagesInFolder: (state) => (folder) => {
if(folder) {
return state.userImages.filter(im => {
if(im.folders) {
return im.folders.includes(folder)
}
return false
})
}
return state.userImages
}
};
// Mutations
const mutations = {
setImages(state, val) {
state.userImages = val;
},
};
export default {
state,
getters,
// actions,
mutations,
};
So far so good. Now if I use the '''getImages''' getter, the Object gets to be loaded into a ref:
//...
const detailImage = shallowRef({});
const showImageDetails = (image) => {
if (!showDetails.value) {
showDetails.value = true;
}
activeImage.value = image.id
detailImage.value = image;
}
The JSON has a nested Object called exif_data. If I onsole.log this, I get: Proxy {Make: 'Apple', Flash: 'Flash did not fire. No strobe return detection fun…on present. No red-eye reduction mode or unknown.', Model: 'iPhone 6', FNumber: 2.2, COMPUTED: {…}, …}.
So far so good, but when I would like to access any property of the nested Object, either with or without using .value I just get undefined.
Any ideas?

vue apollo 2 composition api track result

So I am trying to add product impressions to my site by following this article:
https://developers.google.com/tag-manager/enhanced-ecommerce#product-impressions
I have created a bit of logic to fire off the required data like this:
import { getCurrentInstance } from "#vue/composition-api";
import { useGtm } from "#gtm-support/vue2-gtm";
export function useTrackProductImpressions(items: any[]) {
console.log("trying to track products", items);
if (!items?.length) return;
const gtm = useGtm();
if (!gtm.enabled()) return;
const dataLayer = window.dataLayer;
if (!dataLayer) return;
console.log(items);
const products = items.map((product, i) => {
const retailers = product.retailers ?? [];
return {
name: product.title, // Name or ID is required.
id: product.id,
price: retailers[0].price,
brand: product.brand,
category: product.categorySlug,
variant: product.variant,
position: i,
};
});
const instance = getCurrentInstance();
const route = instance.proxy.$route;
const routeName = route.meta?.title ?? route.name;
dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object.
dataLayer.push({
event: "productClick",
ecommerce: {
click: {
actionField: { list: routeName }, // Optional list property.
products,
},
},
// eventCallback: function () {
// document.location = productObj.url;
// },
});
}
This seems pretty normal and I have a click version of this that works fine.
The problem is, the click event can be fired when a link is clicked, this one needs to fire when the view loads, I assume in setup.
So, I have my apollo logic:
import { useQuery, useResult } from "#vue/apollo-composable";
import * as listProducts from "#graphql/api/query.products.gql";
export const defaultParameters: {
identifier?: string;
searchTerm: string;
itemsToShow: number;
page: number;
filters: any;
facets: string[];
} = {
searchTerm: "*",
itemsToShow: 12,
page: 1,
filters: [],
facets: ["Criteria/Attribute,count:100"],
};
export function useSearchProducts(params) {
const { result, loading, error, fetchMore } = useQuery(listProducts, params);
const response = useResult(result, null, (data) => data.searchProducts);
return { response, loading, error, fetchMore };
}
And from my setup I invoke like this:
const { category } = toRefs(props);
const page = ref(1);
const skip = ref(0);
const orderBy = ref([
{
key: "InVenue",
value: "desc",
},
]);
const params = computed(() => {
const filters = createFilters("CategorySlug", [category.value.slug]);
const request = createRequest(
defaultParameters,
page.value,
filters,
orderBy.value
);
return { search: request };
});
const { response, loading, error} = useSearchProducts(params);
Which I can then return to the template like this:
return { response, loading, error };
Now I have done this, I want to add some tracking, so initially I did this:
watch(response, (result) => useTrackProductImpressions(result?.value?.items));
But it was always undefined.
I added console log on result within the watch method and it is always undefined.
So I changed to this:
const track = computed(() => {
useTrackProductImpressions(response.value.items);
});
But this never gets invoked (I assume because it has no return value and I don't use it in the template).
My question is, which is the best way to do what I am attempting? Am I missing something or am I on the write track?
I think I was close, I just used the computed property to return my products like this:
const products = computed(() => {
if (!response.value) return [];
useTrackProductImpressions(response.value.items);
return response.value.items;
});
const total = computed(() => {
if (!response.value) return 0;
return response.value.total;
});
const hasMoreResults = computed(() => {
if (!response.value) return false;
return response.value.hasMoreResults;
});
return {
products,
loading,
error,
total,
hasMoreResults,
skip,
more,
search,
};

VueJS $set not making new property in array of objects reactive

In my VueJS 2 component below, I can add the imgdata property to each question in the area.questions array. It works - I can see from the console.log that there are questions where imgdata has a value. But despite using $set it still isn't reactive, and the imgdata isn't there in the view! How can I make this reactive?
var componentOptions = {
props: ['area'],
data: function() {
return {
qIndex: 0,
};
},
mounted: function() {
var that = this;
that.init();
},
methods: {
init: function() {
var that = this;
if (that.area.questions.length > 0) {
that.area.questions.forEach(function(q) {
Util.HTTP('GET', '/api/v1/photos/' + q.id + '/qimage').then(function(response) {
var thisIndex = (that.area.questions.findIndex(entry => entry.id === q.id));
var thisQuestion = (that.area.questions.find(entry => entry.id === q.id));
thisQuestion.imgdata = response.data;
that.$set(that.area.questions, thisIndex, thisQuestion);
})
});
}
console.log("area.questions", that.area.questions);
},
Since area is a prop, you should not be attempting to make changes to it within this component.
The general idea is to emit an event for the parent component to listen to in order to update the data passed in.
For example
export default {
name: "ImageLoader",
props: {
area: Object
},
data: () => ({ qIndex: 0 }), // are you actually using this?
mounted () {
this.init()
},
methods: {
async init () {
const questions = await Promise.all(this.area.questions.map(async q => {
const res = await Util.HTTP("GET", `/api/v1/photos/${encodeURIComponent(q.id)}/qimage`)
return {
...q,
imgdata: res.data
}
}))
this.$emit("loaded", questions)
}
}
}
And in the parent
<image-loader :area="area" #loaded="updateAreaQuestions"/>
export default {
data: () => ({
area: {
questions: [/* questions go here */]
}
}),
methods: {
updateAreaQuestions(questions) {
this.area.questions = questions
}
}
}
Here that variable has a value of this but it's bound under the scope of function. So, you can create reactive property in data as below :
data: function() {
return {
qIndex: 0,
questions: []
};
}
Props can't be reactive so use :
that.$set(this.questions, thisIndex, thisQuestion);
And assign your API output to directly questions using this.questions.

The useFocusEffect doesn't update setstate

I have this useFocusEffect function:
useFocusEffect(
useCallback(() => {
async function setFilterValue() {
let filterValue = await AsyncStorage.getItem('filterValue');
let inputValue = JSON.parse(filterValue);
if (gpsFilterValue === null) {
setGpsFilterValue(inputValue);
console.log('null')
// setRefreshing(true)
} else if (inputValue !== parseInt(gpsFilterValue)) {
setGpsFilterValue(inputValue);
console.log('refreshing1')
setTimeout(() => {
console.log(gpsFilterValue)
}, 1000);
//console.log(inputValue + ' ' + gpsFilterValue)
//setRefreshing(true)
} else {
console.log('test')
}
}
setFilterValue();
}, [])
);
When I debug the gpsFilterValue in the setTimeout it stays undefined, but when I make a button in the render to debug the gpsFilterValue state value and press it, it has updated. How come the values are different?
From the looks of it gpsFilterValue is part of your state as you have a state setter being invoked in the callback. However, your useCallback isn't listing gpsFilterValue as a dependency (that empty array that is the second parameter to useCallback. Add it to that dependency list and you should see the updated value.
useFocusEffect(
useCallback(() => {
async function setFilterValue() {
let filterValue = await AsyncStorage.getItem('filterValue');
let inputValue = JSON.parse(filterValue);
if (gpsFilterValue === null) {
setGpsFilterValue(inputValue);
console.log('null')
// setRefreshing(true)
} else if (inputValue !== parseInt(gpsFilterValue)) {
setGpsFilterValue(inputValue);
console.log('refreshing1')
setTimeout(() => {
console.log(gpsFilterValue)
}, 1000);
//console.log(inputValue + ' ' + gpsFilterValue)
//setRefreshing(true)
} else {
console.log('test')
}
}
setFilterValue();
}, [gpsFilterValue])
);

Computed property not updating on props changes

I can't get a computed property to update, when a nested property in a passed prop object is changed.
this.favourite is passed via props, but the computed property is not updating when this.favourite.selectedChoices.second.id and this.favourite.selectedChoices.first.id is changed.
Any ideas of how to make this reactive?
Here's the computed property:
isDisabled() {
const hasMultipleChoices = this.favourite.choices.length
? this.favourite.choices[0].value.some(value => value.choices.length) :
false;
if (hasMultipleChoices && !this.favourite.selectedChoices.second.id) {
return true;
} else if (this.favourite.choices.length && !this.favourite.selectedChoices.first.id) {
return true;
}
return false;
}
TESTED
In my test.vue
props: {
variant: {
type: String,
default: ''
}
}
const myComputedName = computed(() => {
return {
'yellow--warning': props.variant === 'yellow',
'red--warning': props.variant === 'red',
}
})
test.spec.js
import { shallowMount } from '#vue/test-utils'
import test from '#/components/test.vue'
let wrapper
//default values
function createConfig(overrides) {
let variant = ''
const propsData = { variant }
return Object.assign({ propsData }, overrides)
}
//test
describe('test.vue Implementation Test', () => {
let wrapper
// TEARDOWN - run after to each unit test
afterEach(() => {
wrapper.destroy()
})
it('computed return red if prop variant is red', async (done) => {
const config = createConfig({ propsData: { variant: 'red' } })
wrapper = shallowMount(test, config)
wrapper.vm.$nextTick(() => {
//checking that my computed has changed, in my case I want to matchanObject
expect(wrapper.vm.myComputedName).toMatchObject({
'red--warning': true
})
//check what your computed value looks like
console.log(wrapper.vm.myComputedName)
done()
})
})
//TEST 2 Variant, this time instead red, lets say yellow
it('computed return yellow if prop variant is red', async (done) => {
const config = createConfig({ propsData: { variant: 'yellow' } })
wrapper = shallowMount(test, config)
wrapper.vm.$nextTick(() => {
//checking that my computed has changed, in my case I want to matchanObject
expect(wrapper.vm.myComputedName).toMatchObject({
'yellow--warning': true
})
//check what your computed value looks like
console.log(wrapper.vm.myComputedName)
done()
})
})
})
for more info, this page helped me.
https://vuejsdevelopers.com/2019/08/26/vue-what-to-unit-test-components/
The reason why the computed property didn't update, was because I created the id object of both this.favourite.selectedChoices.second and this.favourite.selectedChoices.first, after the component was rendered. Declaring the id objects before render was the solution.