[Vue warn]: Failed to resolve async component: datatables and nuxt js - javascript

I'm developing an admin panel with "SCUTUM" admin template with nuxt.js.
When I trying to use datatables component in the template it gives following error in the console. Component is made by template provider. And I used it as the example given by them.
[Vue warn]: Failed to resolve async component: function Datatable() {
return Promise.all(/*! import() */[__webpack_require__.e(0), __webpack_require__.e(1)]).then(__webpack_require__.bind(null, /*! ~/components/datatables/Datatables */ "./components/datatables/Datatables.vue"));
}
Reason: TypeError: Cannot read property 'display' of undefined
What is the reason for this?
Here is my code
<template>
<div id="sc-page-wrapper">
<div id="sc-page-content">
<ScCard class="">
<ScCardHeader seperator>
<div class="uk-flex-middle uk-grid-small uk-grid" data-uk-grid>
<div class="uk-flex-1">
<ScCardTitle>
List of Lecturers
</ScCardTitle>
</div>
<div class="uk-width-auto#s">
<div id="sc-dt-buttons"></div>
</div>
</div>
</ScCardHeader>
<ScCardBody>
<client-only>
<Datatable
id="sc-dt-buttons-table"
ref="buttonsTable"
:data="dtDData"
:options="dtDOptions"
:buttons="true"
#initComplete="dtButtonsInitialized"
/>
</client-only>
</ScCardBody>
</ScCard>
</div>
</div>
</template>
<script>
const rows = require('~/data/pages/datatables.json');
import ScCard from "#/components/card/";
import {ScCardTitle, ScCardBody, ScCardHeader} from "#/components/card"
export default {
name: 'Lecturer',
components: {
Datatable: () => import('~/components/datatables/Datatables'),
ScCard,
ScCardTitle,
ScCardHeader,
ScCardBody
},
data: () => ({
dtDData: JSON.parse(JSON.stringify(rows)),
dtDOptions: {
buttons: [
{
extend: "copyHtml5",
className: "sc-button",
text: 'Copy'
},
{
extend: "csvHtml5",
className: "sc-button",
text: 'CSV '
},
{
extend: "excelHtml5",
className: "sc-button",
text: 'Excel '
},
{
extend: "pdfHtml5",
className: "sc-button sc-button-icon",
text: '<i class="mdi mdi-file-pdf md-color-red-800"></i>'
},
{
extend: "print",
className: "sc-button sc-button-icon",
text: '<i class="mdi mdi-printer"></i>',
title: 'Custom Title',
messageTop: 'Custom message on the top',
messageBottom: 'Custom message on the bottom',
autoPrint: true
}
]
}
}),
mounted () {
this.$nextTick(() => {
})
},
methods: {
dtButtonsInitialized () {
// append buttons to custom container
this.$refs.buttonsTable.$dt.buttons().container().appendTo(document.getElementById('sc-dt-buttons'))
},
}
}
</script>
<style lang="scss">
</style>
Here is the Datatables component
<template>
<div>
<table :id="id" class="uk-table uk-table-striped uk-table-middle" :class="[tableClass]" style="width:100%">
<thead>
<tr>
<th v-for="header in headers" :key="header" class="uk-text-nowrap">
{{ header }}
</th>
</tr>
</thead>
<slot name="footer"></slot>
</table>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { scMq } from '~/assets/js/utils'
require('~/plugins/jquery');
require('datatables.net/js/jquery.dataTables');
require('datatables.net-responsive');
require('datatables.net-select');
require('./dataTables.uikit');
require('./dataTables.responsive.uikit');
export default {
name: 'Datatable',
props: {
data: {
type: Array,
default: () => [],
required: true
},
options: {
type: Object,
default: () => {}
},
id: {
type: String,
required: true
},
buttons: {
type: Boolean,
default: false
},
autoWidth: Boolean,
tableClass: {
type: String,
default: ''
},
customEvents: {
type: Array,
default: () => []
}
},
data: () => ({
$dt: null
}),
computed: {
dtData () {
return JSON.parse(JSON.stringify(this.data))
},
headers () {
let names = [];
Object.keys(this.data[0]).map(k => {
let name = k.replace(/_/g, ' ');
names.push(name.charAt(0).toUpperCase() + name.slice(1))
});
return names
},
columns () {
let columns = [];
Object.keys(this.data[0]).map(k => {
columns.push({
data: k
})
});
return columns;
},
...mapState([
'vxSidebarMainExpanded'
])
},
watch: {
dtData (newVal, oldVal) {
const newLength = newVal.length;
const oldLength = oldVal.length;
const newIds = newVal.map(k => {
return k.id
});
const oldIds = oldVal.map(k => {
return k.id
});
if(newLength > oldLength) {
let uniq = newIds.filter(k => {
return !oldIds.includes(k)
});
if (uniq.length) {
const newEl = newVal.filter(obj => {
return obj.id === uniq[0]
});
this.$dt.row.add(newEl[0]).draw('full-hold');
}
} else {
let uniq = oldIds.filter(k => {
return !newIds.includes(k)
});
if (uniq.length) {
this.$dt.row(':eq('+ uniq[0] +')').remove().draw('full-hold')
}
}
},
vxSidebarMainExpanded () {
if(scMq.mediumMin()) {
setTimeout(() => {
$('#' + this.id).resize()
}, 300);
}
}
},
mounted () {
const self = this;
if(self.buttons) {
const pdfMake = require('~/assets/js/vendor/pdfmake/pdfmake');
const pdfFonts =require('~/assets/js/vendor/pdfmake/vfs_fonts');
pdfMake.vfs = pdfFonts.pdfMake.vfs;
window.JSZip = require("~/assets/js/vendor/jszip");
require('datatables.net-buttons');
require('datatables.net-buttons/js/buttons.html5');
require('datatables.net-buttons/js/buttons.print');
}
let _options = {
data: self.data,
columns: self.columns,
responsive: true,
"initComplete" (settings, json) {
self.$dt = this.api();
self.$emit('initComplete');
}
};
const options = $.extend(_options, self.options);
if(options.responsive === 'responsiveModal') {
_options.responsive = {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header (row) {
return 'Details for row ' + (parseInt(row[0]) + 1);
}
}),
renderer: $.fn.dataTable.Responsive.renderer.tableAll({
tableClass: 'table'
})
}
}
}
$('#' + this.id).DataTable(options);
if(this.customEvents.length) {
this.customEvents.forEach(event => {
this.$dt.on(event.name, event.function)
})
}
}
}
</script>
Is there a way to resolve this?

Related

Displaying image component from inside a list. How do you do this?

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>

Vuejs Loses its #click binding handler when toggled on and off with v-if

I am working with vue-leaflet and esri-leaflet
I am rendering two seperate ESRIFeatureLayers in one vue component and switching between them via a v-if. on first page load. the map #click handlers work fine. but once they have been toggled off via v-if the click handlers no longer fire
This is the vue component
<LLayerGroup
name="Zones"
layer-type="overlay"
>
<EsriFeatureLayer v-if="this.hydroUnit === GROUNDWATER"
ref="gw-layer"
:url="GW_FEATURES_URL"
:token="ARCGIS_TOKEN"
:style-function="gwStyle"
:simplify-factor="0.5"
#mouseover="mouseover"
#mouseout="mouseout"
#click="clickGW"
#featureLayer="updateFeatureLayer"
/>
<LMarker v-if="markerLatLng"
name="clickMarker"
:lat-lng="markerLatLng">
<LTooltip :options="{ permanent: true }">{{ Markertooltip }}</LTooltip>
</LMarker>
<EsriFeatureLayer v-if="this.hydroUnit === SURFACE_WATER && this.sWSitesColors.length > 0"
ref="sw-layer"
:url="SW_FEATURES_URL"
:token="ARCGIS_TOKEN"
:style-function="swStyle"
:simplify-factor="0.5"
#mouseover="mouseover"
#mouseout="mouseout"
#click="clickSW"
#layerClicked="clickSW"
#featureLayer="updateFeatureLayer"
/>
</LLayerGroup>
this is the ESRIFeatureLayer plugin component
<template>
<div style="display: none;">
<slot #click="$event('layerClicked')" v-if="ready" />
</div>
</template>
<script>
import { findRealParent, propsBinder } from 'vue2-leaflet'
import { featureLayer } from 'esri-leaflet'
import * as L from 'leaflet'
const props = {
url: {
type: String,
required: true,
},
styleFunction: {
type: Function,
default: undefined,
},
simplifyFactor: {
type: Number,
default: undefined,
},
precision: {
type: Number,
default: undefined,
},
visible: {
type: Boolean,
default: true,
},
layerType: {
type: String,
default: undefined,
},
name: {
type: String,
default: undefined,
},
token: {
type: String,
default: undefined,
},
pane: {
type: String,
default: undefined,
},
}
export default {
name: 'EsriFeatureLayer',
props,
data () {
return {
ready: false,
}
},
watch: {
styleFunction (newVal) {
this.mapObject.setStyle(newVal)
},
url (newVal) {
this.parentContainer.removeLayer(this)
this.setOptions()
this.parentContainer.addLayer(this, !this.visible)
},
},
mounted () {
this.setOptions()
L.DomEvent.on(this.mapObject, this.$listeners)
propsBinder(this, this.mapObject, props)
this.ready = true
this.parentContainer = findRealParent(this.$parent)
this.parentContainer.addLayer(this, !this.visible)
},
beforeDestroy () {
this.parentContainer.removeLayer(this)
},
methods: {
setVisible (newVal, oldVal) {
if (newVal === oldVal) return
if (newVal) {
this.parentContainer.addLayer(this)
} else {
this.parentContainer.removeLayer(this)
}
},
setOptions () {
const options = {}
if (this.url) {
options.url = this.url
}
if (this.styleFunction) {
options.style = this.styleFunction
}
if (this.simplifyFactor) {
options.simplifyFactor = this.simplifyFactor
}
if (this.precision) {
options.precision = this.precision
}
if (this.token) {
options.token = this.token
}
if (this.pane) {
options.pane = this.pane
}
this.mapObject = featureLayer(options)
this.$emit('featureLayer', this.mapObject)
},
updateVisibleProp (value) {
this.$emit('update:visible', value)
},
},
}
</script>
I have tried adding a event click handler as you can see with #click="$event('layerClicked')"
but no click event is firing once they have been toggled off once.
how do i re-bind the #click handler to the ESRIFeatureLayer if a component is re-shown via the v-if binding?
I found a fix
all i had to do was re-initialize the listeners when a watched property (url) was changed on the child component
Parent
<EsriFeatureLayer
:url="this.hydroUnit === GROUNDWATER ? GW_FEATURES_URL : SW_FEATURES_URL"
:token="ARCGIS_TOKEN"
:style-function="this.hydroUnit === GROUNDWATER ? gwStyle : swStyle"
:simplify-factor="0.5"
#mouseover="mouseover"
#mouseout="mouseout"
#click="click"
#featureLayer="updateFeatureLayer"
/>
ternary operator changed the URL when the hydrounit is changed
the watched url variable in ESRIFeatureLayer re-inits the listeners
watch: {
styleFunction (newVal) {
this.mapObject.setStyle(newVal)
},
url (newVal) {
this.parentContainer.removeLayer(this)
this.setOptions()
this.parentContainer.addLayer(this, !this.visible)
L.DomEvent.on(this.mapObject, this.$listeners)
},
},
mounted () {
this.setOptions()
L.DomEvent.on(this.mapObject, this.$listeners)
propsBinder(this, this.mapObject, props)
this.ready = true
this.parentContainer = findRealParent(this.$parent)
this.parentContainer.addLayer(this, !this.visible)
},

Can ngOnDestroy() be triggered on condition? When selecting the checkboxes, it keeps remembering my previous selects even after the grid reload

I have these two grid. The bottom one is based on the top one:
Each of the items in the lower grid is related to the Program Name and the Tool# selected from the top grid. In this picture the "Delete Tool Change" button is enable since I have selected an item from the lower grid.
Now, if I choose a different Program name and Tool# (Eg: from #6 to 1 on the top grid), and choose a different item from the bottom grid(Eg:1#), it suddenly disables the "Delete Tool Change" button.
The two grid after choosing a different item from upper grid
This is my code for the upper grid component.
columns: ColumnDef[] = [
{ field: 'programName', name: 'Program Name', editable: false, filterField: true, width: '12em', tooltip: 'Read Only' },
{ field: 'toolNoPrefix', name: 'Tool #(Prefix)', editable: false, filterField: true, width: '12em', tooltip: 'First 8 characters of the Tool Tip - Read Only' },
{ field: 'toolNoSuffix', name: '(Suffix)', filterField: true, width: '8em' },
{ field: 'toolName', name: 'Tool Name', editable: false, filterField: true, width: '24em' },
{ field: 'tlLeadFileName', name: 'Tool File Name' },
{ field: 'typeName', name: 'Fixture Type', editable: false, width: '12em' },
{field: 'engineerId', name: 'MSE', type: 'dropdown',
optionsList: this.engineers, optionsListField: 'id', optionsListName: 'lastFirstName', width: '10em'},
{ field: 'userSource', name: 'User Source', editable: false, width: '12em' },
{ field: 'tprCreateDate', name: 'Date Created', type: 'date', editable: false, width: '8em' },
];
hasLoaded = false;
resourced = false;
selectedEcmTool$: BehaviorSubject<any> = new BehaviorSubject(null);
#ViewChild('tools') dataTable: DataTableComponent;
constructor(
private router: Router,
private cgpAlertDialogService: CgpAlertDialogService,
private ecmService: EcmService,
private dialog: MatDialog,
private readonly toastr: ToastrService
) {}
ngOnInit() {
if (!this.selectionCriteria) {
this._init = this.cgpAlertDialogService.showAlertDialog({
title: 'Invalid Selection Criteria',
message: 'A selection criteria has not been selected. Redirecting back to the main page.',
alert: cgpAlertTypes.warning,
closeLabel: 'OK'
}).afterClosed().subscribe(
() => this.router.navigate([''])
);
}
if (this.router.url === '/tprecm/ecm/re-source') {
this.resourced = true;
this.columns.forEach(val => val.editable = false);
}
this.updateNameSources();
this.hasLoaded = true;
}
ngOnDestroy() {
if (this._init) {
this._init.unsubscribe();
}
}
loadECMs() {
this.loading = true;
const body = {
...this.selectionCriteria,
filterColumn: this._currentFilters,
reSourced: +this.resourced,
};
this.ecmService.getAllTools(body, this.pageOptions)
.pipe(
filter(Boolean),
finalize(() => this.loading = false)
).subscribe((res: { totalCount: number, data: any[] }) => {
this.total = res.totalCount;
this.data = res.data;
if (this.data.length >= 1) {
this.dataTable.selections = [this.data[0]];
this.selectedEcmTool$.next(this.data[0]);
}
});
}
onSelect(selectedEcmTool) {
this.selectedEcmTool$.next(selectedEcmTool);
}
This is my html for the uppergrid:
<cgp-app-card titleText="View/Update ECM" showFullScreenToggle="true" [showBackButton]="true"
[onBackButtonClicked]="onBackButtonClicked">
<div *ngIf="hasLoaded">
<data-table #tools [data]="data" [columns]="columns" (lazyLoad)="onLazyLoad($event)" [lazy]="true" [paging]="true"
[pageSize]="pageOptions.size" [totalRecords]="total" [loading]="loading" [filterable]="true" (edit)="updateTool($event)"
(select)="onSelect($event)">
<ng-container actionStart>
<button mat-button (keypress)="onEcmNecReportsClick()" (click)="onEcmNecReportsClick()">Nec Reports</button>
<button mat-button (keypress)="onEcmReportsClick()" (click)="onEcmReportsClick()">ECM Reports</button>
<button mat-button (keypress)="onToolPartRelationshipClick()" (click)="onToolPartRelationshipClick()">Edit
Tool/Part Relationship</button>
<button mat-button (keypress)="onEcmPartsClick()" (click)="onEcmPartsClick()">Parts</button>
<button mat-button [disabled]="this.resourced" (keypress)="onEcmPartsUploadClick()"
(click)="onEcmPartsUploadClick()">Upload Parts from a File</button>
</ng-container>
<ng-container actionEnd>
<button mat-button>Change Log</button>
</ng-container>
</data-table>
<ecm-tool-change [resourced]="this.resourced" [selectedEcmTool$]="selectedEcmTool$"></ecm-tool-change>
</div>
</cgp-app-card>
This is my code for the lower grid component:
#Input() selectedEcmTool$: BehaviorSubject<any>;
#ViewChild('toolChangeTable') toolChangeTable: DataTableComponent;
constructor(
private readonly ecmToolChangeService: EcmToolChangeService,
private readonly ecmService: EcmService,
private readonly dialog: MatDialog,
private readonly toastr: ToastrService,
private readonly confirmation: CgpConfirmationDialogService,
private readonly cgpAlertDialogService: CgpAlertDialogService,
) {
}
onSelect(selectedEcmTool, toolChangeTable: DataTableComponent) {
if (selectedEcmTool.dtShippedToDatabank) {
const selected = toolChangeTable.selections;
const index = selected.findIndex(s => s.toolId === selectedEcmTool.toolId);
if (index !== -1) {
toolChangeTable.selections.splice(index, 1);
}
this.toastr.error('You cannot check this Tool Change after you have entered the Shipped to Databank Date ');
}
}
onUnSelect(dataItem) {
return dataItem;
}
ngOnInit() {
if (this.resourced) {
this.columns.forEach((val) => val.editable = false);
}
this.selectedEcmTool$.subscribe(
(selectedEcmTool) => {
if (selectedEcmTool) {
const toolId = selectedEcmTool.toolId;
this.updateSelectedEcmTool(toolId);
this.updateDesignSources();
} else {
this.data = [];
}
}
);
}
ngOnDestroy() {
if (this.selectedEcmTool$) { this.selectedEcmTool$.unsubscribe(); }
}
onLazyLoad(event: LazyLoadEvent) {
this.pageOptions.order = event.sortOrder === 1 ? 'asc' : 'desc';
this.pageOptions.size = event.rows;
this.pageOptions.sort = event.sortField;
this.pageOptions.page = event.first / this.pageOptions.size;
this.updateSelectedEcmTool(this.toolId);
}
clearSelections() {
this.toolChangeTable.selections = [];
}
updateSelectedEcmTool(toolId) {
if (!toolId) {
return;
}
this.toolId = toolId;
this.loading = true;
this.ecmToolChangeService.getToolChange(toolId, this.pageOptions)
.pipe(filter(Boolean))
.subscribe({
next: (res: { totalCount: number, data: any[] }) => {
this.total = res ? res.totalCount : 0;
this.data = res ? res.data : [];
},
complete: () => this.loading = false
});
}
updateToolChange(event: any) {
const body = event.data;
body.sourceName = undefined;
this.ecmToolChangeService.updateToolChange(body)
.subscribe();
}
This is my code for the lower grid html:
<data-table #toolChangeTable [columns]="columns" [data]="data" [loading]="loading" (lazyLoad)="onLazyLoad($event)" [lazy]="true"
[lazyLoadOnInit]="false" [pageSize]="pageOptions.size" [multiselect]="true" [paging] = "true" [totalRecords]="total"
defaultSortField="necLec" (edit)="updateToolChange($event, toolChangeTable)" (select)="onSelect($event, toolChangeTable)" (unSelect)="onUnSelect($event)">
<ng-container actionStart>
<button mat-button (click)="onMultiRowUpdateClick()" (keypress.enter)="onMultiRowUpdateClick()"
[disabled]="this.resourced || hasSelectedNone">Multi-Edit</button>
<button mat-button (click)="clearSelections()" (keypress.enter)="clearSelections()">Clear All</button>
<button mat-button (click)="onAddToolChangeClick()" [disabled]="this.resourced">Add Tool Change</button>
<button mat-button (click)="onDeleteToolChangeClick()" (keypress.enter)="onDeleteToolChangeClick()"
[disabled]="!hasSelectedSingle">Delete Tool Change</button>
<button mat-button [disabled]="!hasSelectedSingle" (click)="onEditAuthoritiesClick()"
(keypress.enter)="onEditAuthoritiesClick()">Edit Tool Change
Authorities</button>
</ng-container>
</data-table>
How can I write a function or trigger ngOnDestroy so that it does not remembers the previously selected rows anymore.
why not just call clearSelections() from within this.selectedEcmTool$.subscribe()?
every time the selectedEcmTool$ observable gets a new value, the lower grid clears it's selections.
or am I missing something?
ngOnInit() {
if (this.resourced) {
this.columns.forEach((val) => val.editable = false);
}
this.selectedEcmTool$.subscribe(
//clear selections whenever the tool changes
this.clearSelections();
(selectedEcmTool) => {
if (selectedEcmTool) {
const toolId = selectedEcmTool.toolId;
this.updateSelectedEcmTool(toolId);
this.updateDesignSources();
} else {
this.data = [];
}
}
);
}
I fixed it by adding:
get hasSelectedSingleCheck(): boolean {
return (this.toolChangeTable.selections || [])
.filter((row) => row.toolId === this.selectedToolId).length === 1;
}
and adding this check in the html to disable the button if its true.

Paste html content in Pell Editor and format with Striptags nodejs package

On our project we use pell WYSIWYG text editor, with following code:
import { moduleInit } from 'gs-components/export/js/_utils';
import pell from 'pell';
class Wysiwyg {
constructor (element) {
this._element = element;
this._store = this._element.querySelector('.input__input')
this._placeholder = this._element.querySelector('.input__label');
this._element.appendChild(this._placeholder);
this._editor = pell.init({
element: this._element.querySelector('.wysiwyg__editor'),
defaultParagraphSeparator: 'p',
onChange: html => this._store.value = html,
actions: [
{
name: 'heading2',
icon: '<b>Überschrift</b>',
title: 'Überschrift'
},
{
name: 'paragraph',
icon: 'Text',
title: 'Text'
},
'bold',
'italic',
'underline',
'olist',
'ulist'
],
classes: {
button: 'gs-btn gs-btn--xs gs-btn--bordered',
selected: 'gs-btn--dark'
}
});
this._editor.content.innerHTML = this._store.value;
const pellContent = this._element.querySelector('.pell-content');
pellContent.addEventListener('focus', (e) => {
this._store.dispatchEvent(this._buildEvent('focus'));
});
pellContent.addEventListener('blur', (e) => {
this._store.dispatchEvent(this._buildEvent('blur'));
});
this._store.dispatchEvent(this._buildEvent('blur'));
}
_buildEvent(type) {
const event = document.createEvent('HTMLEvents');
event.initEvent(type, true, true);
return event;
}
}
export const init = () => moduleInit('.input--wysiwyg', element => {
new Wysiwyg(element);
});
and we must implement paste event and striptags function:
Example
var striptags = require('striptags');
var html =
'<a href="https://example.com">' +
'lorem ipsum <strong>dolor</strong> <em>sit</em> amet' +
'</a>';
striptags(html);
striptags(html, '<strong>');
striptags(html, ['a']);
striptags(html, [], '\n');
But, how and where?
Here is answer:
pellContent.addEventListener('paste', (e) => {
setTimeout(() => {
this._editor.content.innerHTML = striptags(this._editor.content.innerHTML, ['h2', 'p', 'br', 'ul', 'ol', 'li']);
this._store.value = this._editor.content.innerHTML;
})
});

getting main class object inside function without using arrow function

In my angular project, I am using datatable where I have row grouping and row callbacks.
datatable options
openPositionDatatableOptions = {
sDom: 'rt<"bottom"p>',
ajax: (data, callback, settings) => {
this.service.getOpenPositions()
.map(data => data.json().data).catch(this.handleError)
.subscribe(jsondata => {
this.openPositionsData = jsondata;
jsondata.forEach(element => {
if (element.side === 'SLD') {
element.openQuantity = '-' + element.openQuantity;
}
element['delta'] = 0;
element['pl'] = 0;
});
if (jsondata) {
callback({
aaData: jsondata
});
}
},
error => {
this.notifiicationAlert('Some Error Occured While Retrieving Positions Data', 'WARNING');
});
},
columns: [
{ data: 'account.brokerId' },
{ data: 'contract.localSymbol' },
{ data: 'openQuantity' },
{ data: 'delta' },
{ data: 'pl' },
{
data: null,
orderable: false,
defaultContent:
'<a class="fa fa-remove" style="color:#8B0000"></a>',
responsivePriority: 2
}
],
//Grouping By Row Logic
"columnDefs": [
{ "visible": false, "targets": 0 }
],
"order": [[0, 'asc']],
"drawCallback": function (settings) {
var api = this.api();
var rows = api.rows({ page: 'current' }).nodes();
var last = null;
api.column(0, { page: 'current' }).data().each(function(group, i) {
if (last !== group) {
$(rows).eq(i).before(
'<tr class="group"><td colspan="5"><div class="row"><div class="col-lg-6" style="text-align:left">' + group + '</div><div class="col-lg-6" style="text-align:right"><button class="datatableGroupingBtn btn btn-default btn-xs fa fa-remove" value='+group+'></button></div></div></td></tr>'
);
last = group;
}
});
// jQuery button click event
$(".datatableGroupingBtn").on('click',(value)=>{
var clickedRow = value.currentTarget.value;
});
},
//Grouping By Row Logic Ends
rowCallback: (row: Node, data: any | Object, index: number) => {
$('a', row).bind('click', () => {
this.service.closePosition(data.id).catch(this.handleError).subscribe(
res => {
if (res.status == 200) {
//TODO Re-implement this using web-socket
if ($.fn.DataTable.isDataTable(this.service.openPositionTableAlias)) {
const table = $(this.service.openPositionTableAlias).DataTable();
if (this.openPositionsData.length > 1) {
$('td', row)
.parents('tr')
.remove();
} else {
table.clear().draw();
}
this.notifiicationAlert('Position Closed Successfully', 'SUCCESS');
}
}
},
(error: Response) => {
this.notifiicationAlert('Some Problem While Closing The Position', 'WARNING');
}
);
});
return row;
}
};
In my datatable options, I have a drawCallback function inside I am grouping the rows. Inside the function, I also have a jquery click event over #datatableGroupingBtn
"drawCallback": function (settings) {
var api = this.api();
var rows = api.rows({ page: 'current' }).nodes();
var last = null;
api.column(0, { page: 'current' }).data().each(function(group, i) {
if (last !== group) {
$(rows).eq(i).before(
'<tr class="group"><td colspan="5"><div class="row"><div class="col-lg-6" style="text-align:left">' + group + '</div><div class="col-lg-6" style="text-align:right"><button class="datatableGroupingBtn btn btn-default btn-xs fa fa-remove" value='+group+'></button></div></div></td></tr>'
);
last = group;
}
});
// jQuery button click event
$(".datatableGroupingBtn").on('click',(value)=>{
var clickedRow = value.currentTarget.value;
});
}
Now my requirement is, I have to access the class level this that is My OrderComponent class object inside the jquery click event binding of #datatableGroupingBtn. I know that this can be done if I use arrow function over the drawCallback but if I use then other required functionalities won't work as you can see that I have used some properties using function() level this inside drawCallback function.
My component
import { NotificationService } from './../shared/utils/notification.service';
import { Global } from 'app/shared/global';
import { endponitConfig } from 'environments/endpoints';
import { Observable } from 'rxjs';
import { Http } from '#angular/http';
import { OrderService } from './order.service';
import { Component, OnInit, OnDestroy, AfterViewInit } from '#angular/core';
import { Router } from '#angular/router';
declare var $: any;
#Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent {
openPositionsData: any;
openOrdersData: any;
openPositionDatatableOptions = {
sDom: 'rt<"bottom"p>',
ajax: (data, callback, settings) => {
this.service.getOpenPositions()
.map(data => data.json().data).catch(this.handleError)
.subscribe(jsondata => {
this.openPositionsData = jsondata;
jsondata.forEach(element => {
if (element.side === 'SLD') {
element.openQuantity = '-' + element.openQuantity;
}
element['delta'] = 0;
element['pl'] = 0;
});
if (jsondata) {
callback({
aaData: jsondata
});
}
},
error => {
this.notifiicationAlert('Some Error Occured While Retrieving Positions Data', 'WARNING');
});
},
columns: [
{ data: 'account.brokerId' },
{ data: 'contract.localSymbol' },
{ data: 'openQuantity' },
{ data: 'delta' },
{ data: 'pl' },
{
data: null,
orderable: false,
defaultContent:
'<a class="fa fa-remove" style="color:#8B0000"></a>',
responsivePriority: 2
}
],
//Grouping By Row Logic
"columnDefs": [
{ "visible": false, "targets": 0 }
],
"order": [[0, 'asc']],
"drawCallback": function (settings) {
var api = this.api();
var rows = api.rows({ page: 'current' }).nodes();
var last = null;
api.column(0, { page: 'current' }).data().each(function(group, i) {
if (last !== group) {
$(rows).eq(i).before(
'<tr class="group"><td colspan="5"><div class="row"><div class="col-lg-6" style="text-align:left">' + group + '</div><div class="col-lg-6" style="text-align:right"><button class="datatableGroupingBtn btn btn-default btn-xs fa fa-remove" value='+group+'></button></div></div></td></tr>'
);
last = group;
}
});
// jQuery button click event
$(".datatableGroupingBtn").on('click',(value)=>{
var clickedRow = value.currentTarget.value;
});
},
//Grouping By Row Logic Ends
rowCallback: (row: Node, data: any | Object, index: number) => {
$('a', row).bind('click', () => {
this.service.closePosition(data.id).catch(this.handleError).subscribe(
res => {
if (res.status == 200) {
//TODO Re-implement this using web-socket
if ($.fn.DataTable.isDataTable(this.service.openPositionTableAlias)) {
const table = $(this.service.openPositionTableAlias).DataTable();
if (this.openPositionsData.length > 1) {
$('td', row)
.parents('tr')
.remove();
} else {
table.clear().draw();
}
this.notifiicationAlert('Position Closed Successfully', 'SUCCESS');
}
}
},
(error: Response) => {
this.notifiicationAlert('Some Problem While Closing The Position', 'WARNING');
}
);
});
return row;
}
};
constructor(private http : Http, private service : OrderService){
this.http.get(Global.serviceUrl).subscribe(response=>{
this.openPositionsData = response.json().data;
})
}
}
Assign your openPositionDatatableOptions in the constructor, after declaring a self variable
openPositionDatatableOptions : any;
constructor()
{
const self = this;
this.openPositionDatatableOptions = {
//....
"drawCallback": function (settings) {
//....
// jQuery button click event
$(".datatableGroupingBtn").on('click',(value)=>{
var clickedRow = value.currentTarget.value;
console.log(self);//<=== self is your class instance
});
},
}
Inside the method your jQuery event binding is, declare a variable like so
let self = this;
now you should be able to use this variable in your jquery click event binding

Categories

Resources