riotjs access child tag by name - tags

I thought this.name is the way to access a custom tag. But that's undefined, this.tags only includes 2 of the 6-8 custom tags on the page.
<eservices-mma-receive-inventory-scan>
<style scoped>
:scope {
position: relative;
display: block;
}
.relative {
position: relative;
}
.table-info {
margin-top: 18px;
}
.table-info .clearfix span {
color: var(colorPrimary1);
display: inline-block;
font-weight: 200;
font-size: 20px;
margin-top: 2px;
}
.receive-inventory-scan-button-area {
padding: 14px;
background-color: var(colorPrimaryNeutral0);
margin-top:15px;
}
.receive-inventory-scan-button-area mdt-button {
margin-bottom: 0;
}
.receive-inventory-general-toast-area {
margin-top:15px;
margin-bottom:15px;
}
mdt-searchbar {
margin-top: 15px;
}
.right {
float: right;
}
.h3 {
color: var(colorSecondaryNeutral1);
font-size: 16px;
font-weight: 200;
line-height: 19px;
margin: 0 0 5px 0;
}
.total-count-no-tabs {
font-size: 20px;
font-weight: 200;
padding-left: 6px;
color: var(colorSecondaryNeutral1);
text-align: left;
}
.total-count-no-tabs strong {
color: var(colorPrimary2);
font-weight: 200;
}
mdt-tabs .total-count {
/* Base text style */
font-size: 20px;
font-weight: 200;
padding-right: 6px;
color: var(colorSecondaryNeutral1);
}
mdt-tabs .total-count strong {
color: var(colorPrimary2);
font-weight: 200;
}
#media screen and (min-width: var(breakpointDtoDHD)) {
:scope .container {
width: 1135px;
}
}
#media screen and (min-width: var(breakpointMtoT)) {
:scope .container {
width: auto;
}
:scope > span {
width: auto;
}
:scope .table-info > span {
font-size: 20px;
margin-top: 0;
}
}
/* mdt-tabs style temp override */
[role=topbuffer]{
height:10px;
background:var(colorPrimary6);
}
[role=bottombuffer]{
height:0px;
background:var(colorPrimary5);
}
[role=wrapper]{
overflow:hidden;
background:var(colorPrimary6);
}
[role=tab]{
width:33.33%;
color:var(colorPrimaryNeutral0);
height:35px;
line-height:35px;
float:left;
background:var(colorPrimary4);
cursor:pointer;
font-size: 16px;
text-align: center;
text-transform: uppercase;
border-right:1px solid var(colorPrimary4);
border-top:1px solid var(colorPrimary4);
}
[role=tab]:not(.active):hover{
background:var(colorPrimaryAlternate2);
}
[role=tab].active {
cursor:auto;
background: var(colorPrimary2);
}
[role="custom-content"] {
float: right;
text-align: right;
}
eservices-warning-toast {
margin-top: 10px;
}
</style>
<!-- general PO info -->
<mma-receive-inventory-review-head-info po-number="{ order.po }"
po-date="{ order.submissiondate}"
supplier-name="{ order.supplier.name }"
po-is-noar="{ order.isnoar }"
noar-reason="{ order.reason.name }"
po-itemsinshipment=" { order.itemsinshipment }"
/>
<div class="receive-inventory-general-toast-area">
<!-- cancel submit scanned results buttons -->
<div class="container-fluid receive-inventory-scan-button-area" each="{ displaySubmitActionButtons ? [true] : [] }">
<div class="row" >
<div class="col-md-6">
<mdt-button name="mdt-receiveinventory-scan-cancel" label="{__('nis.receiveInventoryScan.buttons.cancel')}" context="cancel"
onclick="{cancelCheckin}" disabled="{ !cancelButtonEnabled }" />
</div>
<div class="col-md-6">
<mdt-button name="mdt-receiveinventory-scan-reviewandsubmit" label="{__('nis.receiveInventoryScan.buttons.submit')}" context="primary"
type="submit" onclick="{submitCheckin}" disabled="{ !submitButtonEnabled}"
/>
</div>
</div>
</div>
<eservices-warning-toast each="{warning ? [true] : []}" title="{warning.title}" message="{warning.message}" cancel-label="{warning.cancelLabel}"
confirm-icon="{warning.cancelIcon}" confirm-label="{warning.confirmLabel}" oncancel="{warning.cancelCallback}" onconfirm="{warning.confirmCallback}">
</eservices-warning-toast>
<!-- cancel checking toast -->
<eservices-warning-toast each="{ (displayCheckinCancelToast) ? [true] : [] }"
title="{__('nis.receiveInventoryScan.cancelCheckinToast.title')}"
message="{ __('nis.receiveInventoryScan.cancelCheckinToast.content')}"
onconfirm="{onCancelCheckinConfirm}"
oncancel="{onCancelCheckinCancel}"
confirm-label="{__('nis.receiveInventoryScan.cancelCheckinToast.buttonConfirm')}"
cancel-label="{__('nis.receiveInventoryScan.cancelCheckinToast.buttonCancel')}"/>
<!-- mark all as damaged toast -->
<mma-receive-inventory-scan-table-warninginput-toast
title="{__('nis.receiveInventoryScan.scanitemstatustoasts.alldamaged.title')}"
content="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alldamaged.content') }"
button-label-cancel="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alldamaged.buttonCancel') }"
button-label-confirm="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alldamaged.buttonConfirm') }"
input-label="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alldamaged.commentinputlabel') }"
input-placeholder="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alldamaged.commentinputplaceholder') }"
onconfirm="{ onSubmitMarkAllAsDamaged }"
oncancel="{ onCancelMarkAllAsDamaged }"
each="{ displayMarkAllAsDamagedToast ? [true] : [] }"
inline="{ false }"
/>
<!-- not in catalog toast -->
<mma-receive-inventory-scan-warning-toast
title="{ __('nis.receiveInventoryScan.scanitemstatustoasts.notincatalog.title') }"
content="{ __('nis.receiveInventoryScan.scanitemstatustoasts.notincatalog.content') }"
button-label-confirm="{ __('nis.receiveInventoryScan.scanitemstatustoasts.notincatalog.buttonConfirm') }"
onconfirm="{ onSubmitNotInCatalog }"
each="{ displayNotInCatalogToast ? [true] : [] }"
inline="{ false }"
/>
<!-- item already received on already received list -->
<mma-receive-inventory-scan-warning-toast
title="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alreadyreceived.title') }"
content="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alreadyreceived.content') }"
button-label-confirm="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alreadyreceived.buttonConfirm') }"
onconfirm="{ onSubmitAlreadyReceived }"
each="{ displayIsAlreadyReceivedToast ? [true] : [] }"
inline="{ false }"
/>
<!-- serialised item already in stockroom -->
<mma-receive-inventory-scan-warning-toast
title="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alreadyinstockroom.title') }"
content="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alreadyinstockroom.content') }"
button-label-confirm="{ __('nis.receiveInventoryScan.scanitemstatustoasts.alreadyinstockroom.buttonConfirm') }"
onconfirm="{ onSubmitAlreadyInStockroom }"
each="{ displayAlreadyInStockroomToast ? [true] : [] }"
inline="{ false }"
/>
</div>
<!-- scan section -->
<mdt-searchbar name="scanInput" disabled="{ isScanDisabled }" icon-face="barcode" action-context="tertiary" mode="scan" placeholder="{ __('nis.receiveInventoryScan.scanPlaceholder') }"
each="{ displaySc anSearchBar ? [true] : [] }" storage="{storage}">
</mdt-searchbar>
<audio name="audioPlayer" >
<source src="mp3/UBDError.mp3" type="audio/mpeg" />
{ __('generic.audio.audioCompatibilityError.notSupported') }
</audio>
<mma-receive-inventory-scan-toast-error each="{ displayScanErrorToast ? [true] : [] }"></mma-receive-inventory-scan-toast-error>
<mma-receive-inventory-scan-toast-missing-information each="{ displayMissingInfosToast ? [true] : [] }"></mma-receive-inventory-scan-toast-missing-information>
<mma-receive-inventory-scan-toast-manual-entry each="{ displayManualEntryToast ? [true] : [] }"></mma-receive-inventory-scan-toast-manual-entry>
<!-- list of scanned products -->
<div>
<div each="{ tabsVisible ? [] : [true] }" class="total-count-no-tabs">{ __('nis.receiveInventoryScan.labels.totalScannedCount') } <strong>{totalScannedCount}</strong></div>
</div>
<mdt-tabs each="{ tabs && tabsVisible ? [true] : []}" name="tabSelector" tabs="{ parent.tabs }" onselect="{parent.onTabSelected}">
<span if="{parent.displayProductsToBeReceived}" class="total-count">{ __('nis.receiveInventoryScan.labels.totalScannedCount') } <strong>{parent.totalScannedCount}</strong></span>
<span if="{parent.displayProductsReceived}" class="total-count">{ __('nis.receiveInventoryScan.labels.totalReceivedCount') } <strong>{parent.totalReceivedCount}</strong></span>
</mdt-tabs>
<mma-receive-inventory-scan-table table-interaction-disabled="{ isTableInteractionDisabled }" />
<script>
var self = this;
//if no param is passed in the url, assume its a noar
this.orderId = riot.router.current.params.orderid;
RiotBus.requestStore('receiveinventoryscan', this, this.orderId ? this.orderId : null);
this.storage = {scanInput: ''};
this.store = this.stores.receiveinventoryscan;
this.states = this.stores.receiveinventoryscan.states;
this.on('update', function() {
self.displayScanErrorToast = self.stores.receiveinventoryscan.is(this.stores.receiveinventoryscan.states.SCAN_ERROR);
self.displayMissingInfosToast = this.stores.receiveinventoryscan.is(this.stores.receiveinventoryscan.states.MISSING_INFORMATION) && !this.stores.receiveinventoryscan.is(this.stores.receiveinventoryscan.states.MANUAL_ENTRY);
self.displayManualEntryToast = this.stores.receiveinventoryscan.is(this.stores.receiveinventoryscan.states.MANUAL_ENTRY);
self.isScanDisabled = !this.stores.receiveinventoryscan.is(this.stores.receiveinventoryscan.states.SCAN_ENABLED);
self.displaySubmitActionButtons = true;
self.displayCheckinCancelToast = false;
self.displayCheckinSubmitToast = false;
self.displayMarkAllAsDamagedToast = false;
self.displayNotInCatalogToast = false;
self.displayIsAlreadyReceivedToast = false;
self.displayAlreadyInStockroomToast = false;
if( self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.CHECKIN_CANCEL) ) {
self.displayCheckinCancelToast = true;
};
if( self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.CHECKIN_SUBMIT) ) {
self.displayCheckinSubmitToast = true;
};
if( self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.MARK_ALL_AS_DAMAGED) ) {
self.displayMarkAllAsDamagedToast = true;
};
if( self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.SCAN_ERROR_NOTINCATALOG) ) {
self.displayNotInCatalogToast = true;
};
if( self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.ALREADY_RECEIVED) ) {
self.displayIsAlreadyReceivedToast = true;
};
if( self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.ALREADY_INSTOCKROOM) ) {
self.displayAlreadyInStockroomToast = true;
};
//todo: use state for this?
var isAnyItemInErrorState = _.any(self.stores.receiveinventoryscan.getItems(), function(item) { return self.isItemInErrorState(item);});
self.displaySubmitActionButtons = !self.displayMarkAllAsDamagedToast && !self.displayAlreadyInStockroomToast && !self.displayIsAlreadyReceivedToast && !self.displayNotInCatalogToast && !self.displayCheckinCancelToast && !self.displayCheckinSubmitToast &&
!(self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.MISSING_INFORMATION) || self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.MANUAL_ENTRY));
self.displayScanSearchBar = !self.displayCheckinSubmitToast && !self.displayCheckinCancelToast && !self.displayMarkAllAsDamagedToast;
self.submitButtonEnabled = self.displaySubmitActionButtons && (!self.displayCheckinCancelToast && !self.displayCheckinSubmitToast && !self.displayAlreadyInStockroomToast && !self.displayIsAlreadyReceivedToast) && !isAnyItemInErrorState;//&& self.totalScannedCount >0
self.cancelButtonEnabled = self.displaySubmitActionButtons && (!self.displayCheckinCancelToast && !self.displayCheckinSubmitToast);
self.tabs = self.stores.receiveinventoryscan.getTabs();
self.tabsVisible = _.any(self.tabs, function(tab) { return tab.isVisible() === true; });
self.displayProductsToBeReceived = self.stores.receiveinventoryscan.isTabSelected(self.stores.receiveinventoryscan.tabids.TAB_PRODUCTSTOBERECEIVED);
self.displayProductsReceived = self.stores.receiveinventoryscan.isTabSelected(self.stores.receiveinventoryscan.tabids.TAB_PRODUCTSRECEIVED);
if(!self.displayProductsToBeReceived && !self.displayProductsReceived){
self.displayProductsToBeReceived = true;
}
self.isScanDisabled = !self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.SCAN_ENABLED) || isAnyItemInErrorState
|| self.displayMarkAllAsDamagedToast || self.displayAlreadyInStockroomToast || self.displayIsAlreadyReceivedToast || self.displayNotInCatalogToast || self.displayMissingInfosToast || self.displayManualEntryToast
|| self.displayProductsReceived;
//TODO: instead of doing these checks, on every set of MISSING_INFORMATION, MANUAL_ENTRY, SCAN_ERROR_NOTINCATALOG, we should also remove scanenabled
self.isTableInteractionDisabled = function() { return self.isScanDisabled;};
if(!self.isScanDisabled){
/*
setTimeout(function(){
$("[name='scanInput']").find('input').focus();
$("[name='scanInput']").find('input').focusout(function(){
$("[name='scanInput']").find('input').focus();
});
}, 1);
*/
//$("[name='scanInput']").setFocus();
debugger;
this.tags.scanInput.setFocus();
}
self.buildWarning();
});
/**
* Building warning toast references
*/
this.buildWarning = function () {
self.flushWarning();
if(self.store.is(self.states.SCAN_ERROR_INVALID_FOR_SUPPLIER)) {
self.warning = {
title: __('nis.receiveInventoryScan.warning.invalidForSupplier.title'),
message: __('nis.receiveInventoryScan.warning.invalidForSupplier.message'),
confirmLabel: __('nis.receiveInventoryScan.warning.invalidForSupplier.ok'),
confirmCallback: self.onScanInvalidForSupplierConfirm
};
}
if (self.warning) {
window.scrollTo(0, 0);
}
};
/**
* Killingwarning toast references
*/
this.flushWarning = function () {
if (self.warning) {
self.warning.title = null;
self.warning.message = null;
self.warning.cancelLabel = null;
self.warning.cancelIcon = null;
self.warning.confirmLabel = null;
self.warning.cancelCallback = null;
self.warning.confirmCallback = null;
self.warning = null;
}
};
this.isItemInErrorState = function(instance) {
return instance.isalreadyreceived
|| (instance.isunexpected && !instance.isunexpectedconfirmed)
|| (instance.isshortdated && !instance.isshortdatedconfirmed)
|| (instance.isexpired && !instance.isexpiredconfirmed)
|| (instance.isovershipment && !instance.isovershipmentconfirmed)
|| (instance.markasdamaged && !instance.isdamagedconfirmed);
},
this.on('mount', function(){
this.orderId = riot.router.current.params.orderid;
RiotBus.on('receiveinventoryscan.changed', this.refreshFromStore);
RiotBus.on('beforeunload', this.beforeUnload);
this.root.addEventListener('mdtsearchchange', this.scan);
this.refreshFromStore();
});
this.on('unmount', function(){
RiotBus.releaseStore('receiveinventoryscan', this);
RiotBus.off('receiveinventoryscan.changed', this.refreshFromStore);
RiotBus.off('beforeunload', this.beforeUnload);
this.root.removeEventListener('mdtsearchchange', this.scan);
});
this.scan = function(e) {
RiotBus.trigger('scan.item.scancodebar', e.target.value);
// Reset value
self.storage.scanInput = "";
self.update();
};
this.submitCheckin = function(e){
RiotBus.trigger('receiveinventoryscan.submitCheckin');
};
this.cancelCheckin= function(e){
RiotBus.trigger('receiveinventoryscan.cancelCheckin');
};
this.refreshFromStore = function() {
self.order = self.stores.receiveinventoryscan.getOrder();
self.totalScannedCount = self.stores.receiveinventoryscan.getScannedItemsCount();
self.totalReceivedCount = self.stores.receiveinventoryscan.getItemsReceivedCount();
// Sound effect on error
if (self.stores.receiveinventoryscan.is(self.stores.receiveinventoryscan.states.SCAN_ERROR)) {
self.audioPlayer.play();
}
self.update();
};
this.beforeUnload = function(e){
RiotBus.trigger('receiveinventoryscan.beforeunload', e);
},
this.onTabSelected = function(e) {
RiotBus.trigger('receiveinventoryscan.tabselected', e.currentTarget.value)
};
this.onCancelCheckinCancel = function() {
RiotBus.trigger('receiveinventoryscan.cancelCheckin.cancel');
};
this.onCancelCheckinConfirm = function() {
RiotBus.trigger('receiveinventoryscan.cancelCheckin.confirm');
};
this.onCancelMarkAllAsDamaged = function() {
RiotBus.trigger('receiveinventoryscan.markallasdamaged.cancel');
};
this.onSubmitMarkAllAsDamaged = function(comments) {
RiotBus.trigger('receiveinventoryscan.markallasdamaged.submit', comments);
};
this.onSubmitNotInCatalog = function() {
RiotBus.trigger('receiveinventoryscan.notincatalog.submit');
};
this.onSubmitAlreadyReceived = function() {
RiotBus.trigger('receiveinventoryscan.alreadyreceived.submit');
};
this.onSubmitAlreadyInStockroom = function() {
RiotBus.trigger('receiveinventoryscan.alreadyinstockroom.submit');
};
this.onScanInvalidForSupplierConfirm = function() {
RiotBus.trigger('receiveinventoryscan.productinvalidforsupplier.confirm');
};
</script>
I know it's a lot of code, but I'm very new to riotjs so want to be sure that the typo is visible to you guys.
So in if(!self.isScanDisabled){} I want to access mdt-searchbar. The tags array only includes mma-receive-inventory-review-head-info and mma-receive-inventory-scan-table.
Any idea?

if(isScanEnabled){
if (self.scanInput) {
self.scanInput._tag.setFocus();
}
}
DOM wasn't ready when function was executed in the first run, so check if tag exists and wait a few cycles

Related

Chartjs stacked bar separate tooltip for all stacked

I want to make a separate tooltip for every stacked bar, Ex. My demo "2022-01-17" has TWO stacked bars with FOUR values but I need a total of Stack 1 group and Stack 2 group
I've reviewed most of the options in chartjs https://www.chartjs.org/docs/3.5.1/samples/bar/stacked-groups.html
var barChartData = {
labels: ["2022-01-17","2022-01-18","2022-01-19","2022-01-20","2022-01-21","2022-01-22","2022-01-23","2022-01-24","2022-01-25","2022-01-26","2022-01-27","2022-01-28","2022-01-29","2022-01-30"],
datasets: [{"label":"Product 2","data":["292.53","328.5","273.83","305.44","260.33","251.87","118.15","253.95","86.64","87.78","116.68","295.49","61.32","83.78"],"backgroundColor":"#66bb6a","borderColor":"#66bb6a","pointBackgroundColor":"#66bb6a","stack":"Stack 0"},{"label":"Product ","data":["1522.27","1844.83","1581.01","2767.68","2821.36","2940.31","2876.1","2037.79","1593.01","1900.86","1607.21","2188.92","2428.74","2508.81"],"backgroundColor":"#1b5e20","borderColor":"#1b5e20","pointBackgroundColor":"#1b5e20","stack":"Stack 0"},{"label":"Product 2","data":["200","4.14","28.51","13.68","0","0","19.93","0","0","0","10.47","23.05","9.42","10.58"],"backgroundColor":"#ffcdd2","borderColor":"#ffcdd2","pointBackgroundColor":"#ffcdd2","stack":"Stack 1"},{"label":"Product ","data":["680.2","536.51","524.41","479.69","453.19","521.87","530.57","485.13","440.25","591.29","722.73","711.58","686.63","510.72"],"backgroundColor":"#ef9a9a","borderColor":"#ef9a9a","pointBackgroundColor":"#ef9a9a","stack":"Stack 1"}]
};
const footer = (tooltipItems) => {
let sum = 0;
tooltipItems.forEach(function(tooltipItem) {
sum += tooltipItem.parsed.y;
});
return 'Sum: ' + sum;
};
var ctx = document.getElementById("canvas").getContext("2d");
var myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
tooltip: {
callbacks: {
footer: (tooltipItem) => {
let sum = 0;
tooltipItem.forEach(function(tooltipItem) {
sum += tooltipItem.parsed.y;
});
return 'Sum: ' + sum;
}
}
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.7.0/dist/chart.min.js"></script>
<canvas id="canvas" height="100"></canvas>
to get the total of each stack, you can use the dataPoints found in the tooltip context
and use the dataset labels to group by each stack
// group stacks
const groups = {};
tooltip.dataPoints.forEach(function (point) {
if (groups.hasOwnProperty(barChartData.datasets[point.datasetIndex].label)) {
groups[barChartData.datasets[point.datasetIndex].label] += parseFloat(barChartData.datasets[point.datasetIndex].data[point.dataIndex]);
} else {
groups[barChartData.datasets[point.datasetIndex].label] = parseFloat(barChartData.datasets[point.datasetIndex].data[point.dataIndex]);
}
});
e.g. --> {"Product 2":492.53,"Product ":2202.4700000000003}
then use the external option to create a custom tooltip
see following working snippet...
$(document).ready(function() {
var barChartData = {
labels: ["2022-01-17","2022-01-18","2022-01-19","2022-01-20","2022-01-21","2022-01-22","2022-01-23","2022-01-24","2022-01-25","2022-01-26","2022-01-27","2022-01-28","2022-01-29","2022-01-30"],
datasets: [{"label":"Product 2","data":["292.53","328.5","273.83","305.44","260.33","251.87","118.15","253.95","86.64","87.78","116.68","295.49","61.32","83.78"],"backgroundColor":"#66bb6a","borderColor":"#66bb6a","pointBackgroundColor":"#66bb6a","stack":"Stack 0"},{"label":"Product ","data":["1522.27","1844.83","1581.01","2767.68","2821.36","2940.31","2876.1","2037.79","1593.01","1900.86","1607.21","2188.92","2428.74","2508.81"],"backgroundColor":"#1b5e20","borderColor":"#1b5e20","pointBackgroundColor":"#1b5e20","stack":"Stack 0"},{"label":"Product 2","data":["200","4.14","28.51","13.68","0","0","19.93","0","0","0","10.47","23.05","9.42","10.58"],"backgroundColor":"#ffcdd2","borderColor":"#ffcdd2","pointBackgroundColor":"#ffcdd2","stack":"Stack 1"},{"label":"Product ","data":["680.2","536.51","524.41","479.69","453.19","521.87","530.57","485.13","440.25","591.29","722.73","711.58","686.63","510.72"],"backgroundColor":"#ef9a9a","borderColor":"#ef9a9a","pointBackgroundColor":"#ef9a9a","stack":"Stack 1"}]
};
var ctx = document.getElementById("canvas").getContext("2d");
var myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
tooltip: {
enabled: false,
position: 'nearest',
external: function (context) {
// init
const {chart, tooltip} = context;
// remove old tooltip
var container = chart.canvas.parentNode.querySelector('.tooltip');
if (container) {
chart.canvas.parentNode.removeChild(container);
}
// determine if tooltip exists
if (tooltip.opacity === 0) {
return;
}
// group stacks
const groups = {};
tooltip.dataPoints.forEach(function (point) {
if (groups.hasOwnProperty(barChartData.datasets[point.datasetIndex].label)) {
groups[barChartData.datasets[point.datasetIndex].label] += parseFloat(barChartData.datasets[point.datasetIndex].data[point.dataIndex]);
} else {
groups[barChartData.datasets[point.datasetIndex].label] = parseFloat(barChartData.datasets[point.datasetIndex].data[point.dataIndex]);
}
});
// build tooltip rows
var rows = '';
Object.keys(groups).forEach(function (groupName) {
rows += renderTemplate('template-tooltip-row', {
group: groupName,
value: groups[groupName].toLocaleString(undefined, {minimumFractionDigits: 2})
});
});
// build tooltip
chart.canvas.parentNode.insertAdjacentHTML('beforeEnd', renderTemplate('template-tooltip', {
rows: rows,
title: tooltip.title[0]
}));
// position tooltip
const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;
container = chart.canvas.parentNode.querySelector('.tooltip');
container.style.left = positionX + tooltip.caretX + 'px';
container.style.top = positionY + tooltip.caretY + 'px';
container.style.font = tooltip.options.bodyFont.string;
container.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';
}
}
}
}
});
/**
* render html template
* #param {string} templateId - id of html template
* #param {object} templateValues - values for each template placeholder
* #return {string} template content
*/
function renderTemplate(templateId, templateValues) {
var propHandle; // property key
var templateText; // html template content
var templateValue; // value for template placeholder
// get template content, replace each placeholder with value
templateText = document.querySelector('#' + templateId).innerHTML;
if (templateValues) {
for (propHandle in templateValues) {
if (templateValues.hasOwnProperty(propHandle)) {
templateValue = '';
// convert template value to string
if (templateValues[propHandle] !== null) {
if (templateValues[propHandle].hasOwnProperty('results')) {
templateValue = encodeURIComponent(JSON.stringify(templateValues[propHandle].results));
} else {
templateValue = templateValues[propHandle].toString();
}
}
// handle dollar sign in template value
if (templateValue.indexOf('$') > -1) {
templateValue = templateValue.replace(new RegExp('\\$', 'g'), '$$$');
}
// replace template placeholder(s) with template value
if (templateText.indexOf('{{' + propHandle + '}}') > -1) {
templateText = templateText.replace(
new RegExp('{{' + propHandle + '}}', 'g'),
templateValue
);
}
}
}
}
return templateText.trim();
}
});
.align-right {
text-align: right;
}
.table {
border-collapse: separate;
border-spacing: 0vw 0vw;
display: table;
}
.table-body {
display: table-row-group;
}
.table-cell {
display: table-cell;
padding: 4px;
}
.table-foot {
display: table-footer-group;
}
.table-head {
display: table-header-group;
}
.table-row {
display: table-row;
}
.title {
font-weight: bold;
}
.tooltip {
background-color: rgba(0, 0, 0, 0.85);
border-radius: 3px;
color: #ffffff;
pointer-events: none;
position: absolute;
transform: translate(-50%, 0);
transition: all 0.1s ease;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.7.0/dist/chart.min.js"></script>
<canvas id="canvas" height="100"></canvas>
<script id="template-tooltip" type="text/html">
<div class="tooltip">
<div class="title">{{title}}</div>
<div class="table">
<div class="table-body">{{rows}}</div>
</div>
</div>
</script>
<script id="template-tooltip-row" type="text/html">
<div class="table-row">
<div class="table-cell title">{{group}}:</div>
<div class="table-cell align-right">{{value}}</div>
</div>
</script>

I want to use "el-upload" to upload images and documents, but I want to display the images

I want to use "el-upload" to upload images and documents, but I want to display the images. If "list-type='picture-card'" is used, the document will also be shown with an incorrect cover. So is there any way I can upload files that show images thumbnails, documents that show names?
enter image description here
<el-upload
action=""
ref="upload"
:auto-upload="false"
:on-change="onchange">
<el-button slot="trigger" size="small" type="primary">Click to Upload</el-button>
</el-upload>
<el-image
style="width: 100px; height: 100px"
:src="fileList[0]"
:preview-src-list="fileList">
</el-image>
data() {
return {
fileList: []
}
}, methods: {
onchange(file, fileList){
this.fileList.push(URL.createObjectURL(file.raw))
}
}
<template>
<div class="upload-file">
<el-upload
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
class="upload-file-uploader"
ref="upload"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<!-- <div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
的文件
</div> -->
<!-- 文件列表 -->
<transition-group
class="upload-file-list el-upload-list el-upload-list--text"
name="el-fade-in-linear"
tag="ul"
>
<li
:key="file.uid"
class="el-upload-list__item ele-upload-list__item-content"
v-for="(file, index) in fileList">
<el-link
:href="`${baseUrl}${file.url}`"
:underline="false"
target="_blank"
>
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link :underline="false" #click="handleDelete(index)" type="danger"
>删除</el-link
>
</div>
</li>
</transition-group>
</div>
</template>
<script setup>
import { getToken } from "#/utils/auth";
const props = defineProps({
modelValue: [String, Object, Array],
// 数量限制
limit: {
type: Number,
default: 1,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 2,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["png", "jpg", "gif"],
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true,
},
});
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const baseUrl = "URL HERE";
const uploadFileUrl = baseUrl + "Folder_URL"; //
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
watch(
() => props.modelValue,
(val) => {
if (val) {
let temp = 1;
const list = Array.isArray(val) ? val : props.modelValue.split(",");
fileList.value = list.map((item) => {
if (typeof item === "string") {
item = { name: item, url: item };
}
item.uid = item.uid || new Date().getTime() + temp++;
return item;
});
} else {
fileList.value = [];
return [];
}
},
{ deep: true, immediate: true }
);
function handleBeforeUpload(file) {
if (props.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
const isTypeOk = props.fileType.some((type) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
if (!isTypeOk) {
proxy.$modal.msgError(
${props.fileType.join("/")}
);
return false;
}
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
}
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
function handleUploadError(err) {
proxy.$modal.msgError("上传文件失败");
}
// 上传成功回调
function handleUploadSuccess(res, file) {
uploadList.value.push({ name: res.fileName, url: res.fileName });
if (uploadList.value.length === number.value) {
fileList.value = fileList.value
.filter((f) => f.url !== undefined)
.concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
console.log("url", file);
}
}
// 删除文件
function handleDelete(index) {
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
}
// 获取文件名称
function getFileName(name) {
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
} else {
return "";
}
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
let strs = "";
separator = separator || ",";
for (let i in list) {
if (undefined !== list[i].url) {
strs += list[i].url + separator;
}
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
}
</script>
//tu
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>

How do I list my gists?

Can I get a listing of my gists?
Such a listing would list all gists, not only four and not show the contents of a gist until I click it.
There is a simple way:
https://gist.github.com/anders
just put user name in the end of the url: gist.github.com/[user name]
Use GitHub Gist API: https://developer.github.com/v3/gists/#list-a-users-gists
For example, list all public Gist of user anders: https://api.github.com/users/anders/gists
a part of return result:
"html_url": "https://gist.github.com/5b2bd534992dafe3f05202d43d8e48a2",
Then access: https://gist.github.com/5b2bd534992dafe3f05202d43d8e48a2 you will see detail content of this specific gist.
You can use the github API
for example:
MY Gists
https://api.github.com/users/{your git username}/gists
produces a json file like seen below:
This answer correctly suggests retreiving gist information with the free, unauthenticated, cors-enabled API: https://api.github.com/users/${username}/gists.
However, the answer doesn't mention that the initial request only returns the first page. To get all public gists for a user, iterate the pages until you see an empty array response (I guess you could save a request if any returns less than the maximum page size, which appears to be 30). Here's an example in JS for convenience:
const fetchJson = (...args) =>
fetch(...args).then(response => {
if (!response.ok) {
throw Error(response.status);
}
return response.json();
});
const fetchGists = async (username, maxPages=10) => {
const gists = [];
for (let page = 1; page <= maxPages; page++) {
const url = `https://api.github.com/users/${username}/gists?page=${page}`;
const chunk = await fetchJson(url);
if (chunk.length === 0) {
break;
}
gists.push(...chunk);
}
return gists;
};
fetchGists("gaearon").then(gists => {
// for all fields:
//document.body.textContent = JSON.stringify(gists, null, 2);
// ...or select interesting fields:
gists = gists.map(({
description, files, html_url, created_at, updated_at
}) => ({
description, files, html_url, created_at, updated_at
})).sort((a, b) => b.updated_at.localeCompare(a.updated_at));
document.body.textContent =
`total number of public gists: ${gists.length}
${JSON.stringify(gists, null, 2)}`;
});
body {
white-space: pre;
font-family: monospace;
}
Caution: the rate limit at the time of writing is 60 requests per hour, which is easy to exceed. You can monitor your current rate limits in the response headers:
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1664391218
x-ratelimit-resource: core
x-ratelimit-used: 60
Here's a more comprehensive example which shows the rate limits and gists and a prettier interface. I host a version on GitHub pages so I can easily find my gists (the built-in GitHub website gist page is awkward to click through 10 items at a time and has poor search functionality):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gist List</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
body {
margin: 0;
padding: 2.5em;
box-sizing: border-box;
}
.markdown-body {
min-width: 200px;
max-width: 980px;
margin: 0 auto;
}
.github-limits {
margin-top: 1em;
margin-bottom: 2em;
}
.gists ul {
list-style-type: none;
padding: 0 !important;
margin: 0 !important;
}
.gists th {
cursor: pointer;
position: relative;
}
.gists .sorted-desc:after {
position: absolute;
right: 0.5em;
content: "\25BC";
font-size: 0.8em;
}
.gists .sorted-asc:after {
position: absolute;
right: 0.5em;
content: "\25B2";
font-size: 0.8em;
}
.github-logo {
position: absolute;
right: 1.5em;
top: 1.5em;
}
td, th {
min-width: 75px;
font-size: 0.9em;
word-break: break-word;
}
#media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
#media (max-width: 467px) {
.markdown-body {
padding: 10px;
}
td, th {
min-width: 70px;
font-size: 0.8em;
}
}
</style>
</head>
<body class="markdown-body">
<div class="github-logo">
<a href="https://github.com/ggorlen/gist-list">
<img src="https://github.githubassets.com/images/modules/site/icons/footer/github-mark.svg" alt="GitHub mark" width="20" height="20">
</a>
</div>
<h1>Gist List</h1>
<div>
<form class="gist-search">
<input
class="github-username"
placeholder="Enter a GitHub username"
autofocus
>
<input type="submit" value="search">
</form>
</div>
<div class="github-limits"></div>
<div class="gists"></div>
<script>
"use strict";
class GHRequestError extends Error {
constructor(limits = {}, ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, GHRequestError);
}
this.name = "GHRequestError";
this.limits = limits;
}
}
const fetchJson = (...args) => fetch(...args).then(async response => {
const {headers} = response;
const limits = {
remaining: headers.get("x-ratelimit-remaining"),
limit: headers.get("x-ratelimit-limit"),
reset: new Date(headers.get("x-ratelimit-reset") * 1000),
};
if (!response.ok) {
const message = response.statusText || response.status;
throw new GHRequestError(limits, message);
}
return {limits, payload: await response.json()};
});
const fetchGists = async (username, maxPages=10) => {
const gists = [];
let limits;
for (let page = 1; page <= maxPages; page++) {
const url = `https://api.github.com/users/${username}/gists?page=${page}`;
const {limits: lastLimits, payload: chunk} = await fetchJson(url);
limits = lastLimits;
if (chunk.length === 0) {
break;
}
gists.push(...chunk);
}
return {limits, gists};
};
const firstFile = gist =>
Object.keys(gist.files).length
? Object.values(gist.files)[0].filename : "";
const gistDescription = gist => `
<a href="${gist.html_url}">
${gist.description || (
Object.keys(gist.files).length
? firstFile(gist)
: "<em>no description</em>"
)
}
</a>
`;
const gistFiles = gist => `
<ul>
${Object.values(gist.files)
.map(e => `
<li>${e.filename}</li>
`)
.join("")}
</ul>
`;
const gistTableRow = gist => `
<tr>
<td>
${gistDescription(gist)}
</td>
<td>
${gistFiles(gist)}
</td>
<td>
${gist.created_at.slice(0, 10)}
</td>
<td>
${gist.updated_at.slice(0, 10)}
</td>
</tr>
`;
const gistsTable = gists =>
gists.length === 0 ? "<div>No gists found</div>" : `
<table>
<tbody>
<tr>
${headers.map(({name}) => `<th>${name}</th>`).join("")}
</tr>
${gists.map(gistTableRow).join("")}
</tbody>
</table>
`;
const listenToHeaderClick = header => {
header.addEventListener("click", ({target: {textContent}}) => {
let {key, ascending} = headers.find(e => e.name === textContent);
if (sortedBy === textContent) {
gists.reverse();
ascending = header.classList.contains("sorted-desc");
}
else {
sortedBy = textContent;
gists.sort((a, b) => key(a).localeCompare(key(b)));
if (!ascending) {
gists.reverse();
}
}
gistsToDOM(gists);
showSortedIcon(ascending);
});
};
const gistsToDOM = gists => {
document.querySelector(".gists").innerHTML = gistsTable(gists);
document.querySelectorAll(".gists th").forEach(listenToHeaderClick);
};
const limitsToDOM = limits => {
document.querySelector(".github-limits").innerHTML = `
<small>
<em>
${limits.remaining}/${limits.limit} API requests remaining.
Usage resets at ${limits.reset}.
</em>
</small>
`;
};
const showSortedIcon = ascending => {
document.querySelectorAll(".gists th").forEach(e => {
if (e.textContent === sortedBy) {
e.classList.add(ascending ? "sorted-asc" : "sorted-desc");
}
});
};
const handleGistsResponse = response => {
({limits, gists} = response);
limitsToDOM(limits);
gistsToDOM(gists);
showSortedIcon(headers.find(e => e.name === sortedBy).ascending);
};
const handleError = err => {
const gistsEl = document.querySelector(".gists");
gistsEl.innerHTML = err.message;
limitsToDOM(err.limits);
};
const searchGists = username => {
const submit = document.querySelector(".gist-search [type='submit']");
submit.disabled = true;
fetchGists(username)
.then(handleGistsResponse)
.catch(handleError)
.finally(() => submit.disabled = false);
};
const trySearchFromURLParam = () => {
const username = new URLSearchParams(window.location.search).get("username");
if (username) {
const unEl = document.querySelector(".github-username");
unEl.value = username;
searchGists(username);
}
};
const listenForSubmit = () => {
document.querySelector(".gist-search")
.addEventListener("submit", event => {
event.preventDefault();
const {value} = event.target.querySelector(".github-username");
searchGists(value);
});
};
let limits;
let gists;
let sortedBy = "Created";
const headers = Object.freeze([
{
name: "Description",
key: e => e.description || firstFile(e),
ascending: true,
},
{
name: "Files",
key: firstFile,
ascending: true,
},
{
name: "Created",
key: e => e.created_at,
ascending: false,
},
{
name: "Updated",
key: e => e.updated_at,
ascending: false,
},
]);
trySearchFromURLParam();
listenForSubmit();
</script>
</body>
</html>

nvd3 Display 2 Chart

at my work I try to print some graph with nvd3.
But I can only display 1 graph on my page, and I don't understand why the previous graph don't appear.
Could you give me some hint ?
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<link href="lib/css/nv.d3.css" rel="stylesheet">
</head>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
/**********
* Legend
*/
.nvd3 .nv-legend .nv-series {
cursor: pointer;
}
.nvd3 .nv-legend .nv-disabled circle {
fill-opacity: 0;
}
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
#chart, #pid svg {
height: 600px;
width: 600px;
}
</style>
<div id="pid">
<svg></svg>
</div>
<div id="chart">
<svg></svg>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="lib/js/nv.d3.js"></script>
<script>
var divs = ["pid", "chart"];
divs["pid"]= {id:"#pid svg", datam:[
{
values:[
{x:"M",y:1},
{x:"T",y:2},
{x:"W",y:3},
{x:"R",y:3},
{x:"F",y:4},
{x:"S",y:5},
{x:"U",y:6}
],
key:"Apples"
},
{
values:[
{x:"M",y:5},
{x:"T",y:2},
{x:"W",y:6},
{x:"R",y:8},
{x:"F",y:2},
{x:"S",y:4},
{x:"U",y:1}
],
key:"Zebras"
},
{
values:[
{x:"M",y:4},
{x:"T",y:6},
{x:"W",y:5},
{x:"R",y:7},
{x:"F",y:7},
{x:"S",y:2},
{x:"U",y:5}
],
key:"Bananas"
}
], color:['purple', 'black', 'yellow']};
divs["chart"]= {id:"#chart svg", datam:[
{
values:[
{x:"M",y:1},
{x:"T",y:2},
{x:"W",y:3},
{x:"R",y:3},
{x:"F",y:4},
{x:"S",y:5},
{x:"U",y:6}
],
key:"Apples"
},
{
values:[
{x:"M",y:5},
{x:"T",y:2},
{x:"W",y:6},
{x:"R",y:8},
{x:"F",y:2},
{x:"S",y:4},
{x:"U",y:1}
],
key:"Zebras"
}
], color:['red', 'blue', 'green']};
console.log(divs)
var i=0;
var chart = new Array(2);
nv.render = function render(step) {
// number of graphs to generate in each timeout loop
step = step || 1;
nv.render.active = true;
nv.dispatch.render_start();
setTimeout(function() {
var chart, graph;
for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
chart = graph.generate();
if (typeof graph.callback == typeof(Function)) graph.callback(chart);
nv.graphs.push(chart);
}
nv.render.queue.splice(0, i);
if (nv.render.queue.length) setTimeout(arguments.callee, 0);
else {
nv.dispatch.render_end();
nv.render.active = false;
}
}, 0);
};
nv.render.active = false;
nv.render.queue = [];
for (var key in divs) {
console.log(i);
nv.addGraph(function(obj) {
if (typeof arguments[0] === typeof(Function)) {
obj = {generate: arguments[0], callback: arguments[1]};
}
nv.render.queue.push(obj);
console.log(nv.render.queue.length);
if (!nv.render.active) {
nv.render();
}
chart[i] = nv.models.multiBarChart().showControls(true).groupSpacing(0.5).color(divs[key]['color']);
chart[i].yAxis
.tickFormat(d3.format(',.1f'));
d3.select(divs[key]['id'])
.datum(divs[key]['datam'])
.transition().duration(500).call(chart[i]);
nv.utils.windowResize(chart[i].update);
return chart[i];
});
i++;
};
// render function is used to queue up chart rendering
// in non-blocking timeout functions
</script>
I hope you colud help me, thanks.

Is it possible to show hidden characters in CodeMirror?

Is it possible to show hidden characters (like Carriage Return character) in Codemirror Text Editor, but I've not found any configuration reference about it in its documentation. Is it possible do this?
This could be done with help of overlays and predefined styles with whitespace and EOL symbol this way:
cm.addOverlay({
name: 'invisibles',
token: function nextToken(stream) {
var ret,
spaces = 0,
peek = stream.peek() === ' ';
if (peek) {
while (peek && spaces < Maximum) {
++spaces;
stream.next();
peek = stream.peek() === ' ';
}
ret = 'whitespace whitespace-' + spaces;
} else {
while (!stream.eol() && !peek) {
stream.next();
peek = stream.peek() === ' ';
}
ret = 'cm-eol';
}
return ret;
}
});
You could use addon CodeMirror Show Invisibles for this purpose.
Carriage return is interpreted specially by CodeMirror (when on its own, it'll create a line break, when in front of a line feed, it'll be ignored), so in that case, no you can not.
But other non-printing characters (for example \b) will be visible as red dots by default, and you can adapt the relevant CSS class cm-invalidchar to customize their appearance.
Yes you can do with overlay.js, and with any mode language. Only you need to define the overlay for each mode you want, but is the same javascript code for all modes.
window.onload = function() {
CodeMirror.defineMode("javascript-hidden-chars", function(config, parserConfig) {
var alterList = ["alter1", "alter2"];
var spacesCount = 0;
var hiddenCharsOverlay = {
token: function(stream, state) {
if (stream.match(/(?= )/)) {
let alterSpace = spacesCount++ % alterList.length;
stream.eat(/ /);
return `space special-chars ${alterList[alterSpace]}`;
}
while (stream.next() != null && !stream.match(" ", false)) {}
return null;
}
};
return CodeMirror.overlayMode(
CodeMirror.getMode(
config,
parserConfig.backdrop || "javascript"
),
hiddenCharsOverlay
);
});
var editorSimple = CodeMirror(document.querySelector("#simple-parser"), {
lineNumbers: true,
lineWrapping: true,
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
mode: "javascript-hidden-chars",
value: `// Demo code (the actual new parser character stream implementation)
function StringStream(string) {
this.pos = 0;
this.string = string;
}
StringStream.prototype = {
done: function() {return this.pos >= this.string.length;},
peek: function() {return this.string.charAt(this.pos);},
next: function() {
if (this.pos < this.string.length)
return this.string.charAt(this.pos++);
},
eat: function(match) {
var ch = this.string.charAt(this.pos);
if (typeof match == "string") var ok = ch == match;
else var ok = ch && match.test ? match.test(ch) : match(ch);
if (ok) {this.pos++; return ch;}
},
eatWhile: function(match) {
var start = this.pos;
while (this.eat(match));
if (this.pos > start) return this.string.slice(start, this.pos);
},
backUp: function(n) {this.pos -= n;},
column: function() {return this.pos;},
eatSpace: function() {
var start = this.pos;
while (/\s/.test(this.string.charAt(this.pos))) this.pos++;
return this.pos - start;
},
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
if (consume !== false) this.pos += str.length;
return true;
}
}
else {
var match = this.string.slice(this.pos).match(pattern);
if (match && consume !== false) this.pos += match[0].length;
return match;
}
}
};
`
});
}
body {
background: #4CB8C4;
/* fallback for old browsers */
background: -webkit-linear-gradient(to right, #3CD3AD, #4CB8C4);
/* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #3CD3AD, #4CB8C4);
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
.container-editor {
font-size: 15px;
}
.CodeMirror-code div:not(:last-child) .CodeMirror-line:after {
content: "↵";
text-align: center;
opacity: 0.5;
z-index: 1;
height: inherit;
position: absolute;
white-space: nowrap;
pointer-events: none;
}
.cm-special-chars,
.cm-tab {
position: relative;
opacity: 0.5;
}
.cm-special-chars:after,
.cm-tab:after {
opacity: 0.4;
text-align: center;
left: 0;
top: 0;
width: 100%;
position: absolute;
overflow: hidden;
white-space: nowrap;
pointer-events: none;
}
.cm-space {
white-space: break-spaces;
word-break: break-all;
}
.cm-space:after {
content: '·';
width: inherit;
}
.cm-tab {
white-space: pre-wrap;
}
.cm-tab:after {
content: '<---';
direction: rtl;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.1/codemirror.min.css" />
<link rel="stylesheet" href="https://codemirror.net/addon/fold/foldgutter.css" />
<div id="simple-parser" class="container-editor"></div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.1/codemirror.min.js"></script>
<script type="text/javascript" src="https://codemirror.net/mode/javascript/javascript.js"></script>
<script type="text/javascript" src="https://codemirror.net/addon/mode/overlay.js"></script>
<script type="text/javascript" src="https://codemirror.net/addon/fold/foldcode.js"></script>
<script type="text/javascript" src="https://codemirror.net/addon/fold/foldgutter.js"></script>
<script type="text/javascript" src="https://codemirror.net/addon/fold/comment-fold.js"></script>
<script type="text/javascript" src="https://codemirror.net/addon/fold/indent-fold.js"></script>
<script type="text/javascript" src="https://codemirror.net/addon/fold/brace-fold.js"></script>