how can I update the number of columns in angular-datatables with server-side rendering. version 6.0.0 or higher - angular6

I am having trouble getting my angular-datatable to show a new column list after a rerender. I have followed the example shown in the docs for rerendering and I can get the table to rerender. I am able to manipulate certain features like searching and pageLength, but for some reason I cannot get my columns to change.
I have a very deep data set that would make my table look awful if I rendered all the columns at once, so I would like to give users the ability to select which columns they see.
I would even be open to loading in all the columns at once and just switching visibility off and on, but I cannot effect visibility either.
Has anyone had this issue before?
Rerender function:
rerender(): void {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
dtInstance.destroy();
// these work
this.dtOptions.searching = true;
this.dtOptions.pageLength = 2;
// these do not
this.dtOptions.columns = newColumnList;
this.dtOptions.columns[some-index].visible = false;
this.dtTrigger.next();
});}
Initial dtOptions:
this.dtOptions = {
searching: false,
pagingType: 'full_numbers',
pageLength: 10,
retrieve: true,
serverSide: true,
processing: true,
language: {
zeroRecords: 'Nothing Found'
},
ajax: (dataTablesParameters: any, callback) => {
const payload = this.passFilterService.processPagination(this.filter, dataTablesParameters);
this.http
.post<any>(
environment.api + '/things/list',
{payload: payload}, {}
).subscribe(resp => {
if (resp.data.data === null) {
resp.data.data = 0;
}
callback({
recordsFiltered: resp.data.totalCount,
data: resp.data.data,
recordsTotal: resp.data.totalCount
});
});
},
columns: this.tableColumns
};
Initial Columns (limited fields):
tableColumns = [
{
title: 'Customer',
data: 'Id',
render: function(data) {
return `Action`;
}
}, {
title: 'Created',
data: 'createdAt',
orderable: true,
visible: true,
}, {
title: 'Updated',
data: 'updatedAt',
orderable: true,
visible: true,
}, {
title: 'Disabled',
data: 'isVoided',
orderable: true,
visible: true,
}
];
Table implementation:
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover">
</table>

I faced the same issue, spent hours debugging it until I found something that worked for me. I will advice separating the DT config into an independent object that can be loaded separately. Once you update your DT options and any other config, you can use the functions below to reload the entire DT, destroying and reloading it accordingly;
async rerender(newSettings?: DataTables.Settings) {
try {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
if (newSettings) {
// FIX To ensure that the DT doesn't break when we don't get columns
if (newSettings.columns && newSettings.columns.length > 1) {
dtInstance.destroy();
this.dtOptions = Promise.resolve(newSettings);
this.displayTable(this.dtTableElement);
}
}
});
} catch (error) {
console.log(`DT Rerender Exception: ${error}`);
}
return Promise.resolve(null);
}
This function calls the below one to actually destroy the DT and rerender it.
private displayTable(renderIn: ElementRef): void {
this.dtElement.dtInstance = new Promise((resolve, reject) => {
Promise.resolve(this.dtOptions).then(dtOptions => {
// Using setTimeout as a "hack" to be "part" of NgZone
setTimeout(() => {
$(renderIn.nativeElement).empty();
var dt = $(renderIn.nativeElement).DataTable(dtOptions);
// this.dtTrigger.next();
resolve(dt);
});
}).catch(error => reject(error));
});
}
I removed the dtTrigger execution from the reconstruction function as this was executing twice.
The dtTableElement is defined as #ViewChild('dtTableElement') dtTableElement: ElementRef; where the HTML contains the respective reference on the datatable as:
<table #dtTableElement datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="table table-striped row-border hover" width="100%"></table>

Related

Merge mixin in vue

I'm working in vue/quasar application.
I've my mixin like this in my view.cshtml
var mixin1 = {
data: function () {
return { data1:0,data2:'' }
}
,
beforeCreate: async function () {
...}
},
methods: {
addformulaire(url) {
},
Kilometrique() { }
}
}
And I want merge with my content in js file (it's to centralize same action an severals cshtml)
const nomeMixins = {
data: function () {
return { loadingcdt: false, lstclt: [], filterclient: [], loadingdoc: false, lstdoc: [], filterdoc: [] }
},
computed: {
libmntpiece(v) { return "toto"; }
},
methods: {
findinfcomplemtX3(cdecltx3, cdedocx3) {
},
preremplissagex3: async function (cdecltx3, cdedocx3) {
}
}
}
};
I want merge this 2 miwin in one. But when I try assign or var mixin = { ...mixin1, ...nomeMixins };
I've only mixin1 nothing about methods,data from my js file nomeMixins but merging failed cause I've same key in my json object. I'm trying to make a foreach but failed too
Someone try to merge to mixin / json object with same key in the case you've no double child property ?
You cant merge mixins in that way. the spread syntax will overwrite keys e.g data, computed, methods etc and final result will not be suitable for your purpose.
refer documentation for adding mixins in your component. Also note that You can easily add multiple mixins in any component, so I don't think combination of two mixins will be any useful.
UPDATE
reply to YannickIngenierie answer and pointing out mistakes in this article
Global Mixins are not declared like this
// not global mixin; on contrary MyMixin is local
// and only available in one component.
new Vue({
el: '#demo',
mixins: [MyMixin]
});
Local Mixins are not declared like this
// NOT local mixin; on contrary its global Mixin
// and available to all components
const DataLoader = Vue.mixin({....}}
Vue.component("article-card", {
mixins: [DataLoader], // no need of this
template: "#article-card-template",
created() {
this.load("https://jsonplaceholder.typicode.com/posts/1")
}
});
Point is refer documentation first before reading any article written by some random guy, including me. Do slight comparison what he is saying whats in documentation.
After working and searching... I find this one And understand that I can add directly mixin in my compoment (don't laught I'm begging with vue few months ago)
my custommiwin.js
const DataLoader = Vue.mixin({
data: function () {
return { loadingcdt: false, lstclt: [], filterclient: [], loadingdoc: false, lstdoc: [], filterdoc: [] }
},
methods: {
filterClt: async function (val, update, abort) {
if (val.length < 3) { abort(); return; }
else {//recherche
this.loadingcdt = true;
let res = await axios...
this.loadingcdt = false;
}
update(() => {
const needle = val.toLowerCase();
this.filterclient = this.lstclt.filter(v => v.libelle.toLowerCase().indexOf(needle) > -1 || v.id.toLowerCase().indexOf(needle) > -1);
})
},
filterDocument: async function (val, update, abort, cdecltx3) {
if (!cdecltx3 || val.length < 3) { abort(); return; }
else {//recherche
this.loadingdoc = true;
let res = await axios({ ...) }
this.loadingdoc = false;
}
update(() => {
const needle = val.toLowerCase();
this.filterdoc = this.lstdoc.filter(v => v.id.toLowerCase().indexOf(needle) > -1);
})
},
}
});
and in my compoment.js I add this
mixins: [DataLoader],
I include all my js file in my cshtml file

Conditional loading of large json files with axios.get

A large json file is fetched with the below. It works as expected, however, since the case_data.json is large, the page takes up to two minutes to render.
export default {
name: "cases",
data() {
return {
columns: ['Case number', 'Summary', 'Name', 'Address'],
cases: []
};
},
methods: {
fetchData(){
axios.get('/case_data.json').then(response => {
this.cases = response.data;
})
}
},
created(){
this.fetchData();
}
};
It contains cases from several months, so the idea is to break it down into daily or weekly data portions and only import what is required at the time. The expectation is to improve performance. I would like to pass an input parameter to fetchData() and load a smaller dataset. Does the below approach make sense? Is the fetchData() code correct?
export default {
name: "cases",
props: {
day: {
required: true,
type: String
}
},
data() {
return {
columns: ['Case number', 'Summary', 'Name', 'Address'],
cases: []
};
},
methods: {
fetchData(this.day){
if(this.day == 'day1') axios.get('/case_data_day1.json').then(response => {this.cases = response.data;})
else if(this.day == 'day2') axios.get('/case_data_day2.json').then(response => {this.cases = response.data;})
...
else if(this.day == 'dayN') axios.get('/case_data_dayN.json').then(response => {this.cases = response.data;})
}
},
created(){
this.fetchData();
}
};
use the below code instead, its dynamic, you don't have to write manual condition and it will fetch the data upon this.day and use fetchData() on mounted hook.
methods: {
fetchData(){
let that = this
axios.get(`/case_data_${that.day}.json`).then(response => {that.cases = response.data;})
}
},
mounted(){
this.fetchData();
}

Transform Request to Autoquery friendly

We are working with a 3rd party grid (telerik kendo) that has paging/sorting/filtering built in. It will send the requests in a certain way when making the GET call and I'm trying to determine if there is a way to translate these requests to AutoQuery friendly requests.
Query string params
Sort Pattern:
sort[{0}][field] and sort[{0}][dir]
Filtering:
filter[filters][{0}][field]
filter[filters][{0}][operator]
filter[filters][{0}][value]
So this which is populated in the querystring:
filter[filters][0][field]
filter[filters][0][operator]
filter[filters][0][value]
would need to be translated to.
FieldName=1 // filter[filters][0][field]+filter[filters][0][operator]+filter[filters][0][value] in a nutshell (not exactly true)
Should I manipulate the querystring object in a plugin by removing the filters (or just adding the ones I need) ? Is there a better option here?
I'm not sure there is a clean way to do this on the kendo side either.
I will explain the two routes I'm going down, I hope to see a better answer.
First, I tried to modify the querystring in a request filter, but could not. I ended up having to run the autoqueries manually by getting the params and modifying them before calling AutoQuery.Execute. Something like this:
var requestparams = Request.ToAutoQueryParams();
var q = AutoQueryDb.CreateQuery(requestobject, requestparams);
AutoQueryDb.Execute(requestobject, q);
I wish there was a more global way to do this. The extension method just loops over all the querystring params and adds the ones that I need.
After doing the above work, I wasn't very happy with the result so I investigated doing it differently and ended up with the following:
Register the Kendo grid filter operations to their equivalent Service Stack auto query ones:
var aq = new AutoQueryFeature { MaxLimit = 100, EnableAutoQueryViewer=true };
aq.ImplicitConventions.Add("%neq", aq.ImplicitConventions["%NotEqualTo"]);
aq.ImplicitConventions.Add("%eq", "{Field} = {Value}");
Next, on the grid's read operation, we need to reformat the the querystring:
read: {
url: "/api/stuff?format=json&isGrid=true",
data: function (options) {
if (options.sort && options.sort.length > 0) {
options.OrderBy = (options.sort[0].dir == "desc" ? "-" : "") + options.sort[0].field;
}
if (options.filter && options.filter.filters.length > 0) {
for (var i = 0; i < options.filter.filters.length; i++) {
var f = options.filter.filters[i];
console.log(f);
options[f.field + f.operator] = f.value;
}
}
}
Now, the grid will send the operations in a Autoquery friendly manner.
I created an AutoQueryDataSource ts class that you may or may not find useful.
It's usage is along the lines of:
this.gridDataSource = AutoQueryKendoDataSource.getDefaultInstance<dtos.QueryDbSubclass, dtos.ListDefinition>('/api/autoQueryRoute', { orderByDesc: 'createdOn' });
export default class AutoQueryKendoDataSource<queryT extends dtos.QueryDb_1<T>, T> extends kendo.data.DataSource {
private constructor(options: kendo.data.DataSourceOptions = {}, public route?: string, public request?: queryT) {
super(options)
}
defer: ng.IDeferred<any>;
static exportToExcel(columns: kendo.ui.GridColumn[], dataSource: kendo.data.DataSource, filename: string) {
let rows = [{ cells: columns.map(d => { return { value: d.field }; }) }];
dataSource.fetch(function () {
var data = this.data();
for (var i = 0; i < data.length; i++) {
//push single row for every record
rows.push({
cells: _.map(columns, d => { return { value: data[i][d.field] } })
})
}
var workbook = new kendo.ooxml.Workbook({
sheets: [
{
columns: _.map(columns, d => { return { autoWidth: true } }),
// Title of the sheet
title: filename,
// Rows of the sheet
rows: rows
}
]
});
//save the file as Excel file with extension xlsx
kendo.saveAs({ dataURI: workbook.toDataURL(), fileName: filename });
})
}
static getDefaultInstance<queryT extends dtos.QueryDb_1<T>, T>(route: string, request: queryT, $q?: ng.IQService, model?: any) {
let sortInfo: {
orderBy?: string,
orderByDesc?: string,
skip?: number
} = {
};
let opts = {
transport: {
read: {
url: route,
dataType: 'json',
data: request
},
parameterMap: (data, type) => {
if (type == 'read') {
if (data.sort) {
data.sort.forEach((s: any) => {
if (s.field.indexOf('.') > -1) {
var arr = _.split(s.field, '.')
s.field = arr[arr.length - 1];
}
})
}//for autoquery to work, need only field names not entity names.
sortInfo = {
orderByDesc: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'desc'), 'field'), ','),
orderBy: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'asc'), 'field'), ','),
skip: 0
}
if (data.page)
sortInfo.skip = (data.page - 1) * data.pageSize,
_.extend(data, request);
//override sorting if done via grid
if (sortInfo.orderByDesc) {
(<any>data).orderByDesc = sortInfo.orderByDesc;
(<any>data).orderBy = null;
}
if (sortInfo.orderBy) {
(<any>data).orderBy = sortInfo.orderBy;
(<any>data).orderByDesc = null;
}
(<any>data).skip = sortInfo.skip;
return data;
}
return data;
},
},
requestStart: (e: kendo.data.DataSourceRequestStartEvent) => {
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if ($q)
ds.defer = $q.defer();
},
requestEnd: (e: kendo.data.DataSourceRequestEndEvent) => {
new DatesToStringsService().convert(e.response);
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if (ds.defer)
ds.defer.resolve();
},
schema: {
data: (response: dtos.QueryResponse<T>) => {
return response.results;
},
type: 'json',
total: 'total',
model: model
},
pageSize: request.take || 40,
page: 1,
serverPaging: true,
serverSorting: true
}
let ds = new AutoQueryKendoDataSource<queryT, T>(opts, route, request);
return ds;
}
}

Populate model of the model with sails js

I'm trying to populate model of the model with sails unfortunally it doesn't work.
I have 3 models
/**
Conversation.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'conversation',
attributes: {
idConversation:{
columnName:'IDCONVERSATION',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer',
index:true
},
dateStartConversation:{
columnName:'DATEDEBUT',
type:'date',
index:true
},
user1:{
columnName:'IDUSER1',
model:'user',
notNull:true
},
user2:{
columnName:'IDUSER2',
model:'user',
notNull:true
},
article:
{
model:'article',
columnName:'IDARTICLE',
notNull:true
}
}
};
/**
Article.js
**/
module.exports = {
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'article',
attributes: {
idArticle:{
type:'integer',
unique:true,
columnName:'IDARTICLE',
autoIncrement:true,
primaryKey:true
},
title:{
type:'string',
required:true,
columnName:'TITRE',
index:true,
notNull:true
},
utilisateur:{
model:'utilisateur',
columnName:'IDUTILISATEUR',
required:true,
notNull:true,
dominant:true
},
images:{
collection:'image',
via:'article'
},
conversation:{
collection:'conversation',
via:'article'
}
}
};
/**
Image.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'image',
attributes: {
idImage:{
columnName:'IDIMAGE',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer'
},
pathImage:{
columnName:'PATHIMAGE',
required:true,
type:'string',
notNull:true
},
article:{
model:'article',
columnName:'IDARTICLE',
notNull:true,
dominant:true
}
}
};
As you can see in my model, an conversation its between Two user, about one article, and those article cas have one or many Images.
So I want to get all conversations of one user and I able to populate with article but I'm not able to populate article with Image below how I proceed
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
i++;
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
//I try to set article with the newArticle but it don't work
element.article=newArticle;
})
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
});
})
Because deep populate is not possible using sails, I try to use a loop to populate each article with associate Images and set it in conversation, But article is never set in conversation.
How can I fix it ?
Judging by the if(i==conversations.length) at the end, you seem to have an inkling that you need to write asynchronous code. But you're iterating i inside of the synchronous forEach loop, so your response is happening before any of the database queries even run. Move the i++ and the if inside of the callback for Article.findOne:
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
// Associate the article with the conversation,
// calling `toObject` on it first
element.article= newArticle.toObject();
// Done processing this conversation
i++;
// If we're done processing ALL of the conversations, send the response
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
})
});
})
You'll also need to call toObject on the newArticle instance before assigning it to the conversation, because it contains getters and setters on the images property which behave unexpectedly when copied.
I'd also recommend refactoring this to use async.each, which will make it more readable.
Until this is resolved (https://github.com/balderdashy/sails-mongo/issues/108), you can use this function that I developed to solve this: https://gist.github.com/dinana/52453ecb00d469bb7f12

Extjs4 Button rednerer in grid dom is null error

i am facing a problem which i want to generate a button in grid column by using reconfigure function.
i find my code consist of the Extjs error during renderer function 'Uncaught TypeError: Cannot read property 'dom' of null'
i checked that it should be come from "renderTo: id3", do you have any idea on it? Do i do something wrong in my render button function in the grid?
Although i pop up error message, but the UI still can show the button i genereated. it is very confused.
var createColumns = function (json) {
var keys = getKeysFromJson(json[0]);
return keys.map(function (field) {
if(field!='unread'&&field!='linkerType'&&field!='linkParam'&&field!='refNumber'&&field!='oid'&&field!='refOwner'){
return {
text: Ext.String.capitalize(field),
//flex : 1,
dataIndex: field,
sortable: true,
menuDisabled: true,
renderer: function (val, metadata, record) {
if(field=='action') {
var str = val.split(":");
metadata.style = "text-align: left";
if(str[2]=='true'&&str[1]=='false'){
var id3 = Ext.id();
Ext.defer(function () {
Ext.widget('button', {
renderTo: id3,
margin: '0 0 0 10',
iconAlign: 'center',
tooltip:'Ok to Ship Again',
cls: 'x-btn-default-small',
text: '<img src="images/OKToShipAgain.png">',
handler: function() {
items=[];
items.push({
"oid" : record.get('oid'),
"refNumber" : record.get('refNumber'),
"refOwner" : record.get('refOwner')
});
Ext.Ajax.request({
url: '#{csMenuBean.contextPath}/ws3/todolistservice/submitOktoship',
params: {data: Ext.encode(items)},
success : function(response){
}
});
}
})
}, 50);
return Ext.String.format('<span id="{0}"></span>', id3);
}