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

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>

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>

Tinymce multiple instances in same page

I am using tinymce multiple instances in same page. When I try to insert image, I can but it will be added always in last text area fields( I have same 10 textarea with different name and id).
Html
<div class="admin__field field field-options_options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_1 with-note">
<label class="label admin__field-label" for="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_1" data-ui-id="adminhtml-widget-form-field-wysiwygeditor-0-text-parameters-content-1-label"><span>Content 1</span></label>
<div class="admin__field-control control">
<textarea id="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_13662927431522736609" name="parameters[content_1]" class="textarea admin__control-textarea wysiwyg-editor" rows="5" cols="15" data-ui-id="product-tabs-attributes-tab-fieldset-element-textarea-parameters[content_1]" aria-hidden="true"></textarea>
<button id="id_5abf1b8e544f38a8ca21aa8815613abd" title="WYSIWYG Editor" type="button" class="action-default scalable action-wysiwyg hidden" onclick="basewidgetWysiwygEditor.open('http://cei-shop.local/admin/catalog/product/wysiwyg/key/0367c4936326c7be6bf0fda5c407d274c1f3e4a9922173f53b72e75411d13406/', 'options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_13662927431522736609')" data-ui-id="widget-button-0">
<span>WYSIWYG Editor</span>
</button>
<button type="button" class="scalable action-show-hide" style="" id="toggleoptions_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_13662927431522736609"><span><span><span>Show / Hide Editor</span></span></span></button>
</div>
</div>
<div class="admin__field field field-options_options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_2 with-note">
<label class="label admin__field-label" for="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_2" data-ui-id="adminhtml-widget-form-field-wysiwygeditor-1-text-parameters-content-2-label"><span>Content 2</span></label>
<div class="admin__field-control control">
<textarea id="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_26925735391522736609" name="parameters[content_2]" class="textarea admin__control-textarea wysiwyg-editor" rows="5" cols="15" data-ui-id="product-tabs-attributes-tab-fieldset-element-textarea-parameters[content_2]" aria-hidden="true"></textarea>
<button id="id_504c308d5e46c342e202e961e97b2904" title="WYSIWYG Editor" type="button" class="action-default scalable action-wysiwyg hidden" onclick="basewidgetWysiwygEditor.open('http://cei-shop.local/admin/catalog/product/wysiwyg/key/0367c4936326c7be6bf0fda5c407d274c1f3e4a9922173f53b72e75411d13406/', 'options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_26925735391522736609')" data-ui-id="widget-button-1">
<span>WYSIWYG Editor</span>
</button>
<button type="button" class="scalable action-show-hide" style="" id="toggleoptions_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_26925735391522736609"><span><span><span>Show / Hide Editor</span></span></span></button>
</div>
</div>
setup.js
define([
'jquery',
'underscore',
'tinymce',
'mage/adminhtml/wysiwyg/tiny_mce/html5-schema',
'mage/translate',
'prototype',
'mage/adminhtml/events',
'mage/adminhtml/browser'
], function(jQuery, _, tinyMCE, html5Schema) {
vesBaseTinyMceWysiwygSetup = Class.create();
vesBaseTinyMceWysiwygSetup.prototype = {
mediaBrowserOpener: null,
mediaBrowserTargetElementId: null,
initialize: function(htmlId, config) {
if (config.baseStaticUrl && config.baseStaticDefaultUrl) {
tinyMCE.baseURL = tinyMCE.baseURL.replace(config.baseStaticUrl, config.baseStaticDefaultUrl);
}
this.id = htmlId;
this.config = config;
this.schema = config.schema || html5Schema;
_.bindAll(this, 'beforeSetContent', 'saveContent', 'onChangeContent', 'openFileBrowser', 'updateTextArea');
varienGlobalEvents.attachEventHandler('tinymceChange', this.onChangeContent);
varienGlobalEvents.attachEventHandler('tinymceBeforeSetContent', this.beforeSetContent);
varienGlobalEvents.attachEventHandler('tinymceSaveContent', this.saveContent);
if (typeof tinyMceEditors == 'undefined') {
tinyMceEditors = $H({});
}
tinyMceEditors.set(this.id, this);
},
setup: function(mode) {
if (this.config.widget_plugin_src) {
tinyMCE.PluginManager.load('magentowidget', this.config.widget_plugin_src);
}
if (this.config.plugins) {
this.config.plugins.each(function(plugin) {
tinyMCE.PluginManager.load(plugin.name, plugin.src);
});
}
if (jQuery.isReady) {
tinyMCE.dom.Event.domLoaded = true;
}
tinyMCE.init(this.getSettings(mode));
},
getSettings: function(mode) {
var plugins = 'inlinepopups,safari,pagebreak,style,layer,table,advhr,advimage,emotions,iespell,media,searchreplace,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras',
self = this;
if (this.config.widget_plugin_src) {
plugins = 'magentowidget,' + plugins;
}
var magentoPluginsOptions = $H({});
var magentoPlugins = '';
if (this.config.plugins) {
this.config.plugins.each(function(plugin) {
magentoPlugins = plugin.name + ',' + magentoPlugins;
magentoPluginsOptions.set(plugin.name, plugin.options);
});
if (magentoPlugins) {
plugins = '-' + magentoPlugins + plugins;
}
}
var settings = {
mode: (mode != undefined ? mode : 'none'),
elements: this.id,
theme: 'advanced',
plugins: plugins,
theme_advanced_buttons1: magentoPlugins + 'magentowidget,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect',
theme_advanced_buttons2: 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor',
theme_advanced_buttons3: 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,iespell,media,advhr,|,ltr,rtl,|,fullscreen',
theme_advanced_buttons4: 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,pagebreak',
theme_advanced_toolbar_location: 'top',
theme_advanced_toolbar_align: 'left',
theme_advanced_statusbar_location: 'bottom',
theme_advanced_resizing: true,
theme_advanced_resize_horizontal: false,
convert_urls: false,
relative_urls: false,
content_css: this.config.content_css,
custom_popup_css: this.config.popup_css,
magentowidget_url: this.config.widget_window_url,
magentoPluginsOptions: magentoPluginsOptions,
doctype: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
setup: function(ed){
ed.onInit.add(self.onEditorInit.bind(self));
ed.onSubmit.add(function(ed, e) {
varienGlobalEvents.fireEvent('tinymceSubmit', e);
});
ed.onPaste.add(function(ed, e, o) {
varienGlobalEvents.fireEvent('tinymcePaste', o);
});
ed.onBeforeSetContent.add(function(ed, o) {
varienGlobalEvents.fireEvent('tinymceBeforeSetContent', o);
});
ed.onSetContent.add(function(ed, o) {
varienGlobalEvents.fireEvent('tinymceSetContent', o);
});
ed.onSaveContent.add(function(ed, o) {
varienGlobalEvents.fireEvent('tinymceSaveContent', o);
});
var onChange = function(ed, l) {
varienGlobalEvents.fireEvent('tinymceChange', l);
};
ed.onChange.add(onChange);
ed.onKeyUp.add(onChange);
ed.onExecCommand.add(function(ed, cmd, ui, val) {
varienGlobalEvents.fireEvent('tinymceExecCommand', cmd);
});
}
};
// Set the document base URL
if (this.config.document_base_url) {
settings.document_base_url = this.config.document_base_url;
}
if (this.config.files_browser_window_url) {
settings.file_browser_callback = function(fieldName, url, objectType, w) {
varienGlobalEvents.fireEvent("open_browser_callback", {
win: w,
type: objectType,
field: fieldName
});
};
}
if (this.config.width) {
settings.width = this.config.width;
}
if (this.config.height) {
settings.height = this.config.height;
}
if (this.config.settings) {
Object.extend(settings, this.config.settings)
}
return settings;
},
applySchema: function (editor) {
var schema = editor.schema,
schemaData = this.schema,
makeMap = tinyMCE.makeMap;
jQuery.extend(true, {
nonEmpty: schema.getNonEmptyElements(),
boolAttrs: schema.getBoolAttrs(),
whiteSpace: schema.getWhiteSpaceElements(),
shortEnded: schema.getShortEndedElements(),
selfClosing: schema.getSelfClosingElements(),
blockElements: schema.getBlockElements()
}, {
nonEmpty: makeMap(schemaData.nonEmpty),
boolAttrs: makeMap(schemaData.boolAttrs),
whiteSpace: makeMap(schemaData.whiteSpace),
shortEnded: makeMap(schemaData.shortEnded),
selfClosing: makeMap(schemaData.selfClosing),
blockElements: makeMap(schemaData.blockElements)
});
},
openFileBrowser: function(o) {
//console.log(o.win.tinyMCE.activeEditor.editorId);
var typeTitle,
storeId = this.config.store_id !== null ? this.config.store_id : 0,
frameDialog = jQuery(o.win.frameElement).parents('[role="dialog"]'),
wUrl = this.config.files_browser_window_url +
'target_element_id/' + this.id + '/' +
'store/' + storeId + '/';
this.mediaBrowserOpener = o.win;
this.mediaBrowserTargetElementId = o.field;
if (typeof(o.type) != 'undefined' && o.type != "") {
typeTitle = 'image' == o.type ? this.translate('Insert Image...') : this.translate('Insert Media...');
wUrl = wUrl + "type/" + o.type + "/";
} else {
typeTitle = this.translate('Insert File...');
}
frameDialog = jQuery('[role="dialog"]');
frameDialog.hide();
jQuery('#mceModalBlocker').hide();
MediabrowserUtility.openDialog(wUrl, false, false, typeTitle, {
closed: function() {
jQuery(document).find('[role="dialog"]').show();
jQuery('#mceModalBlocker').show();
}
});
},
translate: function(string) {
return jQuery.mage.__ ? jQuery.mage.__(string) : string;
},
getMediaBrowserOpener: function() {
return this.mediaBrowserOpener;
},
getMediaBrowserTargetElementId: function() {
return this.mediaBrowserTargetElementId;
},
getToggleButton: function() {
return $('toggle' + this.id);
},
getPluginButtons: function() {
return $$('#buttons' + this.id + ' > button.plugin');
},
turnOn: function(mode) {
this.closePopups();
this.setup(mode);
tinyMCE.execCommand('mceAddControl', false, this.id);
this.getPluginButtons().each(function(e) {
e.hide();
});
return this;
},
turnOff: function() {
this.closePopups();
tinyMCE.execCommand('mceRemoveControl', false, this.id);
this.getPluginButtons().each(function(e) {
e.show();
});
return this;
},
closePopups: function() {
if (typeof closeEditorPopup == 'function') {
// close all popups to avoid problems with updating parent content area
closeEditorPopup('widget_window' + this.id);
closeEditorPopup('browser_window' + this.id);
}
},
toggle: function() {
if (!tinyMCE.get(this.id)) {
this.turnOn();
return true;
} else {
this.turnOff();
return false;
}
},
onEditorInit: function (editor) {
this.applySchema(editor);
},
onFormValidation: function() {
if (tinyMCE.get(this.id)) {
$(this.id).value = tinyMCE.get(this.id).getContent();
}
},
onChangeContent: function() {
// Add "changed" to tab class if it exists
if (tinyMCE.get(this.id)) {
jQuery('#' + this.id).val(tinyMCE.get(this.id).getContent()).trigger('change');
}
if (this.config.tab_id) {
var tab = $$('a[id$=' + this.config.tab_id + ']')[0];
if ($(tab) != undefined && $(tab).hasClassName('tab-item-link')) {
$(tab).addClassName('changed');
}
}
},
// retrieve directives URL with substituted directive value
makeDirectiveUrl: function(directive) {
return this.config.directives_url.replace('directive', 'directive/___directive/' + directive);
},
encodeDirectives: function(content) {
// collect all HTML tags with attributes that contain directives
return content.gsub(/<([a-z0-9\-\_]+.+?)([a-z0-9\-\_]+=".*?\{\{.+?\}\}.*?".+?)>/i, function(match) {
var attributesString = match[2];
// process tag attributes string
attributesString = attributesString.gsub(/([a-z0-9\-\_]+)="(.*?)(\{\{.+?\}\})(.*?)"/i, function(m) {
return m[1] + '="' + m[2] + this.makeDirectiveUrl(Base64.mageEncode(m[3])) + m[4] + '"';
}.bind(this));
return '<' + match[1] + attributesString + '>';
}.bind(this));
},
encodeWidgets: function(content) {
return content.gsub(/\{\{widget(.*?)\}\}/i, function(match) {
var attributes = this.parseAttributesString(match[1]);
if (attributes.type) {
attributes.type = attributes.type.replace(/\\\\/g, "\\");
var imageSrc = this.config.widget_placeholders[attributes.type];
var imageHtml = '<img';
imageHtml += ' id="' + Base64.idEncode(match[0]) + '"';
imageHtml += ' src="' + imageSrc + '"';
imageHtml += ' title="' + match[0].replace(/\{\{/g, '{').replace(/\}\}/g, '}').replace(/\"/g, '"') + '"';
imageHtml += '>';
return imageHtml;
}
}.bind(this));
},
decodeDirectives: function(content) {
// escape special chars in directives url to use it in regular expression
var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1');
var reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)'));
return content.gsub(reg, function(match) {
return Base64.mageDecode(match[1]);
}.bind(this));
},
decodeWidgets: function(content) {
return content.gsub(/<img([^>]+id=\"[^>]+)>/i, function(match) {
var attributes = this.parseAttributesString(match[1]);
if (attributes.id) {
var widgetCode = Base64.idDecode(attributes.id);
if (widgetCode.indexOf('{{widget') != -1) {
return widgetCode;
}
return match[0];
}
return match[0];
}.bind(this));
},
parseAttributesString: function(attributes) {
var result = {};
attributes.gsub(/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/, function(match) {
result[match[1]] = match[2];
});
return result;
},
updateTextArea: function () {
var editor = tinyMCE.get(this.id),
content;
if (!editor) {
return;
}
content = editor.getContent();
content = this.decodeContent(content);
jQuery('#' + this.id).val(content).trigger('change');
},
decodeContent: function (content) {
var result = content;
if (this.config.add_widgets) {
result = this.decodeWidgets(result);
result = this.decodeDirectives(result);
} else if (this.config.add_directives) {
result = this.decodeDirectives(result);
}
if(result!=''){
result = result.replace(/ /g, ' ');
}
return result;
},
encodeContent: function (content) {
var result = content;
if (this.config.add_widgets) {
result = this.encodeWidgets(result);
result = this.encodeDirectives(result);
} else if (this.config.add_directives) {
result = this.encodeDirectives(result);
}
return result;
},
beforeSetContent: function(o){
o.content = this.encodeContent(o.content);
},
saveContent: function(o) {
o.content = this.decodeContent(o.content);
}
};
});

converting from blob to binary to save it to mongodb

I get the object like that from the front end to my node express server.
{ picture: [ { preview: 'blob:http://localhost:3000/1f413443-83d8-499e-a432-9ac51a2592b7' } ],
name: 'fsdfs',
description: 'fdfd',
url: 'fdfd',
about: 'dfdf' }
i get this error :
TypeError: path must be a string or Buffer
at TypeError (native)
this is my function where i save to the mongodb
exports.create_a_project = function(req, res) {
console.log(req.body);
var new_project = new Project(req.body);
new_project.picture.data = fs.readFileSync(req.body.picture[0]);
new_project.picture.contentType = 'image/png';
new_project.save(function(err, project) {
if (err)
res.send(err);
res.json(project);
});
};
some how i need to convert the image i am receiving to be binary in order to save.
or i need to send it as a binary base64 from the client side it self.
my client side i use react redux Dropzone for sending my data.
here is my form and how it look like.
import React from 'react';
import {Field, reduxForm} from 'redux-form';
import Dropzone from 'react-dropzone';
const FILE_FIELD_NAME = 'picture';
const renderDropzoneInput = (field) => {
const files = field.input.value;
return (
<div>
<Dropzone
name={field.name}
onDrop={(filesToUpload, e) => field.input.onChange(filesToUpload)}
>
<div>Try dropping some files here, or click to select files to
upload.
</div>
</Dropzone>
{field.meta.touched &&
field.meta.error &&
<span className="error">{field.meta.error}</span>}
{files && Array.isArray(files) && (
<ul>
{files.map((file, i) => <li key={i}>{file.name}</li>)}
</ul>
)}
</div>
);
};
const validate = values => {
const errors = {};
if (!values.name) {
errors.name = 'Required';
} else if (values.name.length > 15) {
errors.name = 'Must be 15 characters or less';
}
if (!values.description) {
errors.description = 'Required';
} else if (values.description.length > 15) {
errors.description = 'Must be 75 characters or less';
}
if (!values.url) {
errors.url = 'Required';
} else if (values.url.length > 15) {
errors.url = 'Must be 15 characters or less';
}
if (!values.about) {
errors.about = 'Required';
} else if (values.about.length > 15) {
errors.about = 'Must be 15 characters or less';
}
if (!values.picture) {
errors.picture = 'Required';
} else if (values.picture.length > 15) {
errors.picture = 'Must be 15 characters or less';
}
// if (!values.email) {
// errors.email = 'Required';
// } else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
// errors.email = 'Invalid email address';
// }
// if (!values.age) {
// errors.age = 'Required';
// } else if (isNaN(Number(values.age))) {
// errors.age = 'Must be a number';
// } else if (Number(values.age) < 18) {
// errors.age = 'Sorry, you must be at least 18 years old';
// }
return errors;
};
const warn = values => {
const warnings = {};
// if (values.age < 19) {
// warnings.age = 'Hmm, you seem a bit young...';
// }
return warnings;
};
const renderField = ({input, label, type, meta: {touched, error, warning}}) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type}/>
{touched && ((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>))}
</div>
</div>
);
const SyncValidationForm = (props) => {
const {handleSubmit, pristine, reset, submitting} = props;
return (
<form onSubmit={handleSubmit}>
<Field name="name" type="text" component={renderField}
label="Name"/>
<Field name="description" type="text" component={renderField}
label="Description"/>
<Field name="url" type="text" component={renderField}
label="Url"/>
<Field name="about" type="text" component={renderField}
label="About"/>
{/*<Field name="picture" type="text" component={renderField}*/}
{/*label="Picture"/>*/}
<Field
name={FILE_FIELD_NAME}
component={renderDropzoneInput}
/>
{/*<Field name="email" type="email" component={renderField} label="Email"/>*/}
{/*<Field name="age" type="number" component={renderField} label="Age"/>*/}
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting}
onClick={reset}>Clear Values
</button>
</div>
</form>
);
};
export default reduxForm({
form: 'syncValidation', // a unique identifier for this form
validate, // <--- validation function given to redux-form
warn // <--- warning function given to redux-form
})(SyncValidationForm);
this is my service function which deals with the rest api.
const addProject = (newProject) => {
let data = JSON.stringify(newProject);
return axios.post('http://localhost:3008/projects', data, {
headers: {
'Content-Type': 'application/json',
}
}
).then(response => {
// console.log(response)
}).catch(error => {
console.log(error)
});
};
So I think your problem is in this line:
new_project.picture.data = fs.readFileSync(req.body.picture[0]);
And it's the mongoDB table column that you are inserting data into that is giving you that error. It's expecting a string or Buffer and you gave it a File object.
You can get a base64 byte string using what I posted here, which I'll try and integrate with your code, below:
You'd need to ensure you have a variable to collect your file(s). This is how I have the top of my page set up:
import React from 'react'
import Reflux from 'reflux'
import {Form, Card, CardBlock, CardHeader, CardText, Col, Row, Button } from 'reactstrap'
import actions from '~/actions/actions'
import DropZone from 'react-dropzone'
// stores
import SomeStore from '~/stores/someStore.jsx'
Reflux.defineReact(React)
export default class myUploaderClass extends Reflux.Component {
constructor(props) {
super(props);
this.state = {
attachments: [],
};
this.stores = [
SomeStore,
]
....
Then bind new functions:
....
this.getData = this.getData.bind(this);
this.processFile = this.processFile.bind(this);
this.errorHandler = this.errorHandler.bind(this);
this.progressHandler = this.progressHandler.bind(this);
} // close constructor
Then we work on supplying the bytes to attachments and sending it to new_project.picture.data. For me, I use a function that runs onDrop of the DropZone using onDrop={this.uploadFile}. I can't really tell what you're doing because I have no clue what filesToUpload refers to. My uploadFile looks like this:
uploadFile(event){
this.setState({
files: event,
});
document.getElementById('docDZ').classList.remove('dragover');
document.getElementById('progress').textContent = '000';
document.getElementById('progressBar').style.width = '0%';
this.state.files = event; // just for good measure
for (let i = 0; i < this.state.files.length; i++) {
const a = i + 1;
console.log('in loop, pass: ' + a);
const f = this.state.files[i];
this.getData(f); // this will load the file to SomeStore.state.attachments
}
}
and this would be the getData function ran for each file dropped/added to the DropZone:
getData(f) {
const reader = new FileReader();
reader.onerror = this.errorHandler;
reader.onprogress = this.progressHandler;
reader.onload = this.processFile(f);
reader.readAsDataURL(f);
}
Then processFile() from the onload runs:
processFile(theFile) {
return function(e) {
const bytes = e.target.result.split('base64,')[1];
const fileArray = [];
// *** Collect any existing attachments ***
// I found I could not get it from this.state, but had to use
// my store, instead
if (SomeStore.state.attachments.length > 0) {
for (let i=0; i < SomeStore.state.attachments.length; i++) {
fileArray.push(SomeStore.state.attachments[i]);
}
}
// Add the new one to this.state
fileArray.push(bytes);
// Update the state
SomeStore.setState({
attachments: fileArray,
});
// This seemed to automatically add it to this.state, for me.
}
}
Once you have that, you should be able to do:
new_project.picture.data = this.state.attachments[0];
If not, for some reason, you might try to call this inside exports.create_a_project(), as the first thing you do:
getData(req.body.picture[0]);
This might even work without having to modify your onDrop routine from what you have. And if this.state.attachments doesn't have anything, your SomeStore.state.attachments definitely should, assuming you have this saved to a folder called stores as someStore.jsx.
import Reflux from 'reflux'
import Actions from '~/actions/actions`
class SomeStore extends Reflux.Store
{
constructor()
{
super();
this.state = {
attachments: [],
};
this.listenables = Actions;
this.baseState = {
attachments: [],
};
}
onUpdateFields(name, value) {
this.setState({
[name]: value,
});
}
onResetFields() {
this.setState({
attachments: [],
});
}
}
const reqformdata = new SomeStore
export default reqformdata
Additional functions
errorHandler(e){
switch (e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
alert('File not found.');
break;
case e.target.error.NOT_READABLE_ERR:
alert('File is not readable.');
break;
case e.target.error.ABORT_ERR:
break; // no operation
default:
alert('An error occurred reading this file.');
break;
}
}
progressHandler(e) {
if (e.lengthComputable){
const loaded = Math.round((e.loaded / e.total) * 100);
let zeros = '';
// Percent loaded in string
if (loaded >= 0 && loaded < 10) {
zeros = '00';
}
else if (loaded < 100) {
zeros = '0';
}
// Display progress in 3-digits and increase bar length
document.getElementById("progress").textContent = zeros + loaded.toString();
document.getElementById("progressBar").style.width = loaded + '%';
}
}
And my DropZone & applicable progress indicator markup:
render(){
const dropZoneStyle = {
height: "34px",
width: "300px",
border: "1px solid #ccc",
borderRadius: "4px",
};
return (
<Form>
<Col xs={5}>
<DropZone type="file" id="docDZ"
onDrop={this.uploadFile}
onDropRejected={this.onDropRejected}
onClick={this.handleUploadClick}
onChange={this.handleChange}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
accept=".doc, .docx, .gif, .png, .jpg, .jpeg, .pdf"
multiple="true"
maxSize={999000}
style={dropZoneStyle}>
{'Click HERE to upload or drop files here...'}
</DropZone>
<table id="tblProgress">
<tbody>
<tr>
<td><b><span id="progress">000</span>%</b> <span className="progressBar"><span id="progressBar" /></span></td>
</tr>
</tbody>
</table>
</Col>
</Row>
</Form>
)
} // close render
} // close class
And CSS:
.progressBar {
background-color: rgba(255, 255, 255, .1);
width: 100%;
height: 26px;
}
#progressBar {
background-color: rgba(87, 184, 208, .5);
content: '';
width: 0;
height: 26px;
}
Other functions you're missing:
handleUploadClick(){
return this.state;
}
handleChange(){
this.state.errors.fileError = "";
}
handleDragEnter(event){
event.preventDefault();
document.getElementById("docDZ").classList.add("dragover");
}
handleDragLeave(event){
event.preventDefault();
document.getElementById("docDZ").classList.remove("dragover");
}
onDropRejected(files){
const errors ={}
let isAlertVisible = false;
for(let i=0, j = files.length; i < j; i++){
const file = files[i];
const ext = file.name.split('.').pop().toLowerCase();
//console.log(ext)
if(this.isFileTypeValid(ext)===false){
errors.fileError = "Only image files (JPG, GIF, PNG), Word files (DOC, DOCX), and PDF files are allowed.";
isAlertVisible = true;
}
if(ext === "docx" || ext ==="gif" || ext ==="png" || ext ==="jpg" || ext ==="jpeg" || ext ==="pdf" || ext ==="doc" && file.size > 999000){
errors.fileError = "Exceeded File Size limit! The maximum file size for uploads is 999 KB.";
isAlertVisible = true;
}
this.setState({
"errors": errors,
"isAlertVisible": isAlertVisible,
})
}
}

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>

riotjs access child tag by name

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