When the page loads, no scrolling is done. There are no errors too.
Can anyone tell me what I'm doing wrong?
Is there another way to go to the desired row? I don't need the scrolling animation, just to get to the row.
$: if (tableElement && data && columns) {
table = new Tabulator(tableElement, {
height: "100%",
layout: "fitData",
data: data,
cellVertAlign: "middle",
resizableColumns: "header",
groupStartOpen: true,
groupToggleElement: "header",
columnHeaderSortMulti: true,
columnCalcs: "both",
tooltipsHeader: true,
virtualDomBuffer: 50000, // I read in another post to increase this, but it does nothing
groupHeader: (value, count, data, group) => {
return `
${value}
<span style="color:#d00; margin-left:10px;">
${count}
</span>
`;
},
columns: columns,
});
}
$: if (table && $scrollToRecordId) {
console.log("SCROLLING");
table
.scrollToRow($scrollToRecordId, "top", true)
.then(() => {
console.log("done");
})
.catch((error) => {
console.log(error);
});
}
The solution to this is using tick.
import { tick } from "svelte";
$: if (table && $scrollToRecordId) {
scrollTable();
}
async function scrollTable() {
await tick();
table.scrollToRow($scrollToRecordId, "top", true);
}
If you are getting a no matching row error, use this:
table.on("tableBuilt", () => {
if ($scrollToSku) {
table.scrollToRow($scrollToSku, "top", true);
$scrollToSku = null;
}
});
Related
I hope you could help me out.
Before going through the code, let me quickly explain what I want:
I have two components that I use for uploading and displaying images. I have FileResourceService that is used for uploading, and FileResourceImage which is used for storing and displaying the data. These work together with a v-model called profilePictureFileResourceId which basically just ties the images to specific users on the page, depending on who is logged on.
When displaying the image on a template, it is very straightforward. I just grab the FileResourceImage component and tie it with the v-model.
<file-resource-image v-model="form.user.profilePictureFileResourceId" can-upload style="width: 100px; height: 100px;" />
That is all very easy, but I have some pages where I use tables that contain information about my users, and I would like for the user’s profile images to actually be displayed in the table. Here is an example of a list used for the table.
fields() {
return [
{
key: "email",
label: this.$t('email'),
sortable: true,
template: {type: 'email'}
},
{
key: "name",
label: this.$t('name'),
sortable: true
},
{
key: 'type',
label: this.$t('type'),
formatter: type => this.$t(`model.user.types.${type}`),
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: 'status',
label: this.$t('status'),
formatter: type => this.$t(`model.user.status.${type}`),
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: "actions",
template: {
type: 'actions',
head: [
{
icon: 'fa-plus',
text: 'createUser',
placement: 'left',
to: `/users/add`,
if: () => this.$refs.areaAuthorizer.fullControl
}
],
cell: [
{
icon: 'fa-edit',
to: data => `/users/${data.item.id}/edit`
}
]
}
I know that I cannot just make an array that looks like this:
fields() {
return [
{
<file-resource-image v-model="form.user.profilePictureFileResourceId" can-upload />
}
]
}
So how would you make the component display from within in the list? I believe it can be done with props, but I am totally lost at what to do.
By the way, these are the two components I use for uploading and display. I thought I might as well show them, so you can get an idea of what they do.
For upload:
import axios from '#/config/axios';
import utils from '#/utils/utils';
export const fileResourceService = {
getFileResource(fileResourceId) {
return axios.get(`file/${fileResourceId}`);
},
getFileResourceFileContent(fileResourceId) {
return axios.get(`file/${fileResourceId}/download`, {responseType: 'arraybuffer', timeout: 0});
},
downloadFileResource(fileResourceId) {
return fileResourceService.getPublicDownloadToken(fileResourceId)
.then(result => fileResourceService.downloadPublicTokenFile(result.data));
},
downloadPublicTokenFile(fileResourcePublicDownloadTokenId) {
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href =
`${axios.defaults.baseURL}/file/public/${fileResourcePublicDownloadTokenId}/download`;
tempLink.setAttribute('download', '');
document.body.appendChild(tempLink);
tempLink.click();
setTimeout(() => document.body.removeChild(tempLink), 0);
},
getPublicDownloadToken(fileResourceId) {
return axios.get(`file/${fileResourceId}/public-download-token`);
},
postFileResource(fileResource, file) {
return axios.post(`file`, utils.toFormData([
{name: 'fileResource', type: 'json', data: fileResource},
{name: 'file', data: file}
]), {timeout: 0});
}
};
Then we have the component that is used for DISPLAYING the images:
<template>
<div :style="style" #click="upload" style="cursor: pointer;">
<div v-if="url === null">
<i class="fas fa-camera"></i>
</div>
<div v-if="canUpload" class="overlay">
<i class="fas fa-images"></i>
</div>
</div>
</template>
<script>
import {fileResourceService} from '#/services/file-resource';
import utils from '#/utils/utils';
export default {
model: {
prop: 'fileResourceId',
event: 'update:fileResourceId'
},
props: {
fileResourceId: String,
canUpload: Boolean,
defaultIcon: {
type: String,
default: 'fas fa-camera'
}
},
data() {
return {
url: null
};
},
computed: {
style() {
return {
backgroundImage: this.url && `url(${this.url})`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
};
}
},
methods: {
upload() {
if(this.canUpload) {
utils.openFileDialog()
.then(([file]) => fileResourceService.postFileResource({}, file))
.then(result => this.$emit('update:fileResourceId', result.data.id))
.catch(() => this.$bvModalExt.msgBoxError())
}
}
},
watch: {
fileResourceId: {
immediate: true,
handler() {
this.url = null;
if (this.fileResourceId) {
fileResourceService.getFileResourceFileContent(this.fileResourceId).then(result => {
const reader = new FileReader();
reader.onload = event => this.url = event.target.result;
reader.readAsDataURL(new Blob([result.data]));
});
}
}
}
}
};
</script>
I have a vuetify data-table which renders the data received from axios call.
On the last column I'm using template v-slot for bespoke column so I can add two buttons. v-btn accepts has two props for loading state as per the documentation https://vuetifyjs.com/en/components/buttons#loaders:
:loading
:disabled
The problem is that when I call a function that changes those values, all of the buttons in the data table are receiving the prop state so instead of 1 button displying loader, all of them are.
<v-row no-gutters>
<v-data-table
:headers="tableHeaders"
:items="requests"
:items-per-page="10"
class="elevation-1"
:loading="loading" loading-text="Loading... Please wait"
>
<template v-slot:item.action="{ item }">
<v-btn color="success" #click="createPacks(item)" :loading="createloading" :disabled="createloading">Create</v-btn>
<v-btn color="error" #click="excludeRequest(item)" :loading="cancelloading" :disabled="cancelloading">Cancel</v-btn>
</template>
</v-data-table>
</v-row>
I'm aware this is because the buttons in the DOM are not unique and the framework is calling all of them but I have no idea how to change that default behaviour.
Data:
export default {
data() {
return {
loading: null,
error: null,
tableHeaders: [
{
text: 'ID',
value: 'req_id',
align: 'center',
},
{ text: 'Template', value: 'templateConcatenated'},
{ text: 'No of batches', value: 'no_batches' },
{ text: 'Batch size', value: 'batch_size' },
{ text: 'Date requested', value: 'requested_at' },
{ text: 'Requested by', value: 'requester' },
{ text: 'Actions', value: 'action', sortable: false, align: 'center'},
],
createloading: false,
cancelloading: false,
successmessage: '',
errormessage: '',
};
},
methods: {
createPacks(item) {
this.loading = true;
this.createloading = true;
let page_url = '/api/CreateProcedure?api_token='+this.$api_token;
axios
.post(page_url, item)
.then((response) => {
this.loading = false;
this.error = null;
this.createloading = false;
this.successmessage = 'Packs created successfully!';
this.errormessage = null;
})
.catch(err => {
this.createloading = false;
this.successmessage = null;
this.errormessage = 'Error creating the packs: '+err;
console.log("error: "+err);
})
},
}
}
Any idea how to call each individual button to change it's state?
Thank you
You've to set the loading properties on the item itself instead of defining them globally:
createPacks(item) {
this.loading = true;
item.createloading = true;
let page_url = '/api/CreateProcedure?api_token='+this.$api_token;
axios
.post(page_url, item)
.then((response) => {
this.loading = false;
this.error = null;
item.createloading = false;
this.successmessage = 'Packs created successfully!';
this.errormessage = null;
})
.catch(err => {
item.createloading = false;
this.successmessage = null;
this.errormessage = 'Error creating the packs: '+err;
console.log("error: "+err);
})
},
== UPDATE ==
I added a code based on the codepen you added in the comments, you have to use item.createloasing also in the HTML else it is not working. https://codepen.io/reijnemans/pen/LYPerLg?editors=1010
Currently only one button is working at the same time but this is probably because of axios is not defined in the codepen.
I have been tasked with formatting some columns in charts using vue-google-charts, a vue.js wrapper for Google Charts and I am not sure that 'NumberFormat()' is even supported in vue-google-charts.
First, if somebody knows if it is or isn't, I would like to know so I don't waste much time pursuing something that isn't possible. But if it is, I sure would love an example of how to do it.
What we are doing is returning our chart data from the database and passing it into this vue.js wrapper. We are creating several charts but there are columns that have commas in them we want to remove.
Please review the existing code. I am trying to implement this using #ready as documented in the docs for vue-google-charts.
vue-google-charts docs -> https://www.npmjs.com/package/vue-google-charts
Here is our existing code with a little framework of the onChartReady method already in place.
<GChart
v-if="chart.data"
id="gchart"
:key="index"
:options="{
pieSliceText: chart.dropDownPie,
allowHtml: true
}"
:type="chart.ChartType"
:data="filtered(chart.data, chart.query, chart.query_type)"
:class="[
{'pieChart': chart.ChartType == 'PieChart'},
{'tableChart': chart.ChartType == 'Table'}
]"
#ready = "onChartReady"
/>
And then ...
<script>
import { GChart } from 'vue-google-charts';
import fuzzy from 'fuzzy';
import 'vue-awesome/icons';
import Icon from 'vue-awesome/components/Icon';
export default {
components: {
GChart,
Icon
},
props: {
},
data() {
return {
charts: window.template_data,
selected: 'null',
selects: [],
chartToSearch: false,
directDownloads: {
'Inactive Phones' : {
'slug' : 'phones_by_status',
'search_by' : 2,
'search' : '/Inactive/'
},
'Active Phones' : {
'slug' : 'phones_by_status',
'search_by' : 2,
'search' : '/Active/'
},
}
}
},
created(){
for (let i in this.charts){
if( !this.charts[i].slug ) continue;
$.post(ajaxurl, {
action: 'insights_' + this.charts[i].slug,
}, (res) => {
console.log(res.data);
if (res.success) {
this.$set(this.charts[i], 'data', res.data);
}
});
}
// console.log(this.charts);
},
methods: {
onChartReady(chart,google) {
let formatter = new.target.visualization.NumberFormat({
pattern: '0'
});
formatter.format(data, 0);
chart.draw(data)
},
toggleChart(chart) {
jQuery.post(ajaxurl, {
'action': 'update_insight_chart_type',
'chartType': chart.ChartType,
'chartSlug': chart.slug
}, (res) => {
chart.ChartType = res.data
})
},
csvHREF(chart) {
return window.location.href + '&rr_download_csv=' + chart.slug + '&rr_download_csv_search_by=' + chart.query_type + '&rr_download_csv_search=' + chart.query.trim()
},
filtered(data, query, column) {
query = query.trim();
if (query){
let localData = JSON.parse(JSON.stringify(data));
let column_Headers = localData.shift();
localData = localData.filter((row)=>{
if( query.endsWith('/') && query.startsWith('/') ){
return new RegExp(query.replace(/\//g, '')).test(String(row[column]));
}
return String(row[column]).toLowerCase().indexOf(query.toLowerCase()) > -1;
});
localData.unshift(column_Headers);
return localData;
}
return data;
},
filterIcon(chart) {
chart.searchVisible = !chart.searchVisible;
chart.query = "";
setTimeout(()=>{
document.querySelector(`#chart-${chart.slug} .insightSearch`).focus();
}, 1);
}
}
}
document.getElementsByClassName('google-visualization-table')
If anybody can help in ANY way, I am all ears.
Thanks!
not familiar with vue or the wrapper,
but in google charts, you can use object notation in your data,
to provide the formatted values.
all chart types will display the formatted values by default.
google's formatters just simply do this for you.
so, in your data, replace your number values with objects,
where v: is the value and f: is the formatted value...
{v: 2000, f: '$2,000.00'}
see following working snippet...
google.charts.load('current', {
packages: ['table']
}).then(function () {
var chartData = [
['col 0', 'col 1'],
['test', {v: 2000, f: '$2,000.00'}],
];
var dataTable = google.visualization.arrayToDataTable(chartData);
var table = new google.visualization.Table(document.getElementById('chart_div'));
table.draw(dataTable);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
I have a number of dynamically created elements that I want to later animate. I know that dynamic elements can by referenced using:
$(document).on(event, selector, cb)
but I am not sure how to implement this with animate. Here is my code if it helps. "state.headings" and "state.text" reference the dynamic elements
var state = {
sm: () => {return $(window).width() < "576"},
blocks: [
$("#first-block"),
$("#second-block")
],
pairs: [
$("#first-pair"),
],
headings: [
$("#first-heading"),
$("#second-heading"),
],
text: [
$("#first-text"),
$("#second-text"),
],
watching: 0,
}
$(window).on("scroll", () => {
if (state.sm()) {
if (isInViewport(state.blocks[state.watching])) {
if (state.headings[state.watching].css("right") !== "0px") {
state.headings[state.watching].animate({
right: "0px"
})
state.text[state.watching].animate({
left: "0px"
}, () => {
if (state.watching < state.blocks.length -1){
state.watching++
}
})
}
}
} else {
//handle animations for larger devices
}
})
I was able to get around it by setting my selectors to regular strings and using
$(document.getElementById(selector)).animate()
but I am still interested to know if jQuery provides its own solution.
connectLoadRenderStoreAndGetCheckBox: function () {
this.someStore = new Ext.data.Store({
proxy:
reader:
]),
sortInfo: { field: , direction: }
});
this.someStore.load(
{
params:
{
}
});
//I left out parameters; lets assume they are valid and work.
this.someStore.on("load", this._renderColumns, this);
return ([this._getCheckBox()]);
}
On 'load' I want to both execute the function this._renderColumns (which uses the data from the server) and also return a checkbox (which also uses data from the server).
What is a quick & easy way to only return the checkbox after the data is loaded?
_getCheckBox: function () {
if (UseDefault == "y") {
return new Ext.form.Checkbox(
{
fieldLabel:,
itemCls:,
checked: true,
labelStyle:
});
}
else {
return new Ext.form.Checkbox(
{
fieldLabel:,
itemCls:,
checked: false,
labelStyle:
});
}
},
_renderColumns: function () {
var record2 = this.something.something2(2);
UseDefault = record2.get("UseDefault");
}