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
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 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)
},
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.
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;
})
});
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