I am using Express and an API to get some data which I then pass into my view. I can loop through that data and print it from within my EJS template, so I know It's there is some capacity. However when I try to use that data in a chart.js chart (all in the same template file) it says it is "not defined"... Why is this happening?
App.js:
app.get('/search', function(req, res) {
var query = req.query.search;
endPoint = 'https://api.iextrading.com/1.0/stock/' + query + '/chart/1d';
request(endPoint, function(error, response, body) {
if(!error && response.statusCode == 200) {
stockData = JSON.parse(body);
console.log(stockData);
res.render('search.ejs', {stockData : stockData});
} else {
console.log(error);
}
});
});
EJS Template file
<% stockData.forEach(function(minute) { %>
<canvas id="myChart" width="400" height="400"></canvas>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: [minute['minute']],
datasets: [{
label: '# of Votes',
data: minute['open'],
backgroundColor: [
'rgba(255, 99, 132, 0.2)'
]
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
</script>
<% }) %>
EDIT
If I change it to be like this it then says that "stockData" is undefined:
<% stockData.forEach(function(minute) { %>
<canvas id="myChart" width="400" height="400"></canvas>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: [stockData['open']],
datasets: [{
label: '# of Votes',
data: stockData['open'],
backgroundColor: [
'rgba(255, 99, 132, 0.2)'
]
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
</script>
<% }) %>
Your stockData in chart.js is generate by javascript on browser. But stockData that really hold value that not undefine is generate by Nodejs on backend. If you wanna use like this. First, you need to render your ejs page, then send an ajax to server, get the response data. Then use that data you just receive to draw your chart. Somethings like this:
axios.get('/search')
.then(function (response) {
let data = response.data;
new Chart(document.getElementById('line-chart'), {
type: 'line',
data: {
labels: [],
datasets: [{
data: [your_response_data_from_nodejs.open],
label: 'Blabla',
borderColor: '#52D0C4',
fill: false
}
]
},
options: {
title: {
display: true,
text: 'Blala '
}
}
});
})
.catch(function (error) {
throw new error;
});
Related
Right now I am able to fetch the data and show in the table in front side, but I need
help to implement the code for skip, limit, searching, sorting and pagination so everything will be a functional.
I looked over the internet but didn't find good source to implement, I hope this might
help others in future. Anyone’s help will be appreciated.
**javascript code**
$(document).ready(function() {
let table = $('#exportable_table').DataTable({
"processing": true,
"searching": true,
"serverSide": true,
"ajax": {
url:"/employee/data-table/get-data",
error:(error)=>{
console.log(error);
}
},
'columns': [
{ data: '_id' },
{ data: 'employee_name' },
{ data: 'gender' },
{ data: 'email' },
{ data: 'mobile_number' },
{ data: 'salary' },
{ data: 'created_at' },
{ data: 'updated_at' },
{ data: '', "defaultContent": "<button class='btn btn-primary' onclick='edititem();'>Edit</button>" },
{ data: '', "defaultContent": "<button class='btn btn-danger' onclick='deleteitem();'>Delete</button>"}
],
});
});
**backend side logic**
employeeDataTableData: async (req, res) => {
let employeeData = await EmployeeService.getEmployee();
var response = {
"draw": parseInt(req.query.draw),
"iTotalRecords": employeeData.length,
"iTotalDisplayRecords": 2,
"data": employeeData
}
res.status(200).send(response);
}
**logic to fetch data from database**
exports.getEmployee = async () => {
try {
let employeeData = await EmployeeModel.find().lean();
if (!employeeData) return false;
return employeeData;
} catch (error) {
console.log("Error : ", error);
}
};
I'm running into a funny problem. I'm using NextJS for its server-side rendering capabilities and am using ReactQuill as my rich-text editor. To get around ReactQuill's tie to the DOM, I'm dynamically importing it. However, that presents another problem which is that when I try to attach a ref to the ReactQuill component, it's treated as a loadable component instead of the ReactQuill component. I need the ref in order to customize how images are handled when uploaded into the rich-text editor. Right now, the ref returns current:null instead of the function I can use .getEditor() on to customize image handling.
Anybody have any thoughts on how I can address this? I tried ref-forwarding, but it's still applying refs to a loadable component, instead of the React-Quill one. Here's a snapshot of my code.
const ReactQuill = dynamic(import('react-quill'), { ssr: false, loading: () => <p>Loading ...</p> }
);
const ForwardedRefComponent = React.forwardRef((props, ref) => {return (
<ReactQuill {...props} forwardedRef={(el) => {ref = el;}} />
)})
class Create extends Component {
constructor() {
super();
this.reactQuillRef = React.createRef();
}
imageHandler = () => {
console.log(this.reactQuillRef); //this returns current:null, can't use getEditor() on it.
}
render() {
const modules = {
toolbar: {
container: [[{ 'header': [ 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
['link', 'image'],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'align': [] }],
['blockquote', 'code-block'],],
handlers: {
'image': this.imageHandler
}
}
};
return(
<ForwardedRefComponent
value={this.state.text}
onChange={this.handleChange}
modules={modules}
ref={this.reactQuillRef}/> //this.reactQuillRef is returning current:null instead of the ReactQuill function for me to use .getEditor() on
)
}
}
const mapStateToProps = state => ({
tutorial: state.tutorial,
});
export default connect(
mapStateToProps, {createTutorial}
)(Create);
I share my solution with hope that it helps you too.
Helped from https://github.com/zenoamaro/react-quill/issues/642#issuecomment-717661518
const ReactQuill = dynamic(
async () => {
const { default: RQ } = await import("react-quill");
return ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;
},
{
ssr: false
}
);
export default function QuillWrapper() {
const quillRef = React.useRef(false)
return <>
<ReactQuill forwardedRef={quillRef} />
</>
}
for example you can use the ref to upload image with custom hanlder
import React, { useMemo } from "react";
import dynamic from "next/dynamic";
const ReactQuill = dynamic(
async () => {
const { default: RQ } = await import("react-quill");
return ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;
},
{
ssr: false,
}
);
export default function QuillWrapper({ value, onChange, ...props }) {
const quillRef = React.useRef(false);
// Custom image upload handler
function imgHandler() {
// from https://github.com/quilljs/quill/issues/1089#issuecomment-318066471
const quill = quillRef.current.getEditor();
let fileInput = quill.root.querySelector("input.ql-image[type=file]");
// to prevent duplicate initialization I guess
if (fileInput === null) {
fileInput = document.createElement("input");
fileInput.setAttribute("type", "file");
fileInput.setAttribute(
"accept",
"image/png, image/gif, image/jpeg, image/bmp, image/x-icon"
);
fileInput.classList.add("ql-image");
fileInput.addEventListener("change", () => {
const files = fileInput.files;
const range = quill.getSelection(true);
if (!files || !files.length) {
console.log("No files selected");
return;
}
const formData = new FormData();
formData.append("file", files[0]);
formData.append("uid", uid);
formData.append("img_type", "detail");
quill.enable(false);
console.log(files[0]);
axios
.post("the/url/for/handle/uploading", formData)
.then((response) => {
// after uploading succeed add img tag in the editor.
// for detail visit https://quilljs.com/docs/api/#editor
quill.enable(true);
quill.insertEmbed(range.index, "image", response.data.url);
quill.setSelection(range.index + 1);
fileInput.value = "";
})
.catch((error) => {
console.log("quill image upload failed");
console.log(error);
quill.enable(true);
});
});
quill.root.appendChild(fileInput);
}
fileInput.click();
}
I don't know much about useMemo
but if i don't use the hook,
the editor keeps rerendered resulting in losing focus and I guess perfomance trouble too.
const modules = useMemo(
() => ({
toolbar: {
container: [
[{ font: [] }],
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
["bold", "italic", "underline", "strike"], // toggled buttons
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ script: "sub" }, { script: "super" }], // superscript/subscript
[{ header: 1 }, { header: 2 }], // custom button values
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent
[{ direction: "rtl" }], // text direction
[{ align: [] }],
["link", "image"],
["clean"], // remove formatting button
],
handlers: { image: imgHandler }, // Custom image handler
},
}),
[]
);
return (
<ReactQuill
forwardedRef={quillRef}
modules={modules}
value={value}
onChange={onChange}
{...props}
/>
);
}
In NextJS, React.useRef or React.createRef do not work with dynamic import.
You should Replace
const ReactQuill = dynamic(import('react-quill'), { ssr: false, loading: () => <p>Loading ...</p> }
);
with
import ReactQuill from 'react-quill';
and render after when window is loaded.
import ReactQuill from 'react-quill';
class Create extends Component {
constructor() {
super();
this.reactQuillRef = React.createRef();
this.state = {isWindowLoaded: false};
}
componentDidMount() {
this.setState({...this.state, isWindowLoaded: true});
}
.........
.........
render(){
return (
<div>
{this.isWindowLoaded && <ReactQuil {...this.props}/>}
</div>
)
}
}
Use onChange and pass all the arguments, here one example to use the editor.getHTML()
import React, { Component } from 'react'
import dynamic from 'next/dynamic'
import { render } from 'react-dom'
const QuillNoSSRWrapper = dynamic(import('react-quill'), {
ssr: false,
loading: () => <p>Loading ...</p>,
})
const modules = {
toolbar: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ size: [] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[
{ list: 'ordered' },
{ list: 'bullet' },
{ indent: '-1' },
{ indent: '+1' },
],
['link', 'image', 'video'],
['clean'],
],
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
}
/*
* Quill editor formats
* See https://quilljs.com/docs/formats/
*/
const formats = [
'header',
'font',
'size',
'bold',
'italic',
'underline',
'strike',
'blockquote',
'list',
'bullet',
'indent',
'link',
'image',
'video',
]
class BlogEditor extends Component {
constructor(props) {
super(props)
this.state = { value: null } // You can also pass a Quill Delta here
this.handleChange = this.handleChange.bind(this)
this.editor = React.createRef()
}
handleChange = (content, delta, source, editor) => {
this.setState({ value: editor.getHTML() })
}
render() {
return (
<>
<div dangerouslySetInnerHTML={{ __html: this.state.value }} />
<QuillNoSSRWrapper ref={this.editor} onChange={this.handleChange} modules={modules} formats={formats} theme="snow" />
<QuillNoSSRWrapper value={this.state.value} modules={modules} formats={formats} theme="snow" />
</>
)
}
}
export default BlogEditor
If you want to use ref in Next.js with dynamic import
you can use React.forwardRef API
more info
I am using exceljs npm package for formatting of excelsheet But I am not able to do textwrap inside cell when data is too large. My Description cell contains large data when excel is exported it looks like text is overlapping to next cell.
try {
var workbook = new excel.Workbook();
var worksheet = workbook.addWorksheet('ABC');
worksheet.columns = [
{ header: 'ABC', key: 'ABC', width: 15 },
{ header: 'Description', key: 'Description', width: 20 },
{ header: 'Comments', key: 'Comments', width: 20 }
];
// setStyleToHeader(worksheet);
worksheet.getRow(1).font = { name: 'Calibri', family: 4, size: 12, bold: true };
worksheet.getRow(1).border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
new sql.ConnectionPool(dbconfig).connect().then(pool => {
return pool.request()
.execute('sp_getAll_data')
}).then(result => {
debugger;
let rows = result.recordset;
for (var i = 0; i < rows.length; i++) {
worksheet.addRow({
ABC: rows[i].ABC,
Description: rows[i].Description ,
Comments: rows[i].Comments
});
}
sql.close();
workbook.xlsx.write(res);
debugger;
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
res.setHeader("Content-Disposition", "attachment; filename=" + "ABC.xlsx");
res.end(result, 'binary');
res.status(200).json();
}).catch(err => {
// res.status(500).send({ message: "${err}", err })
// sql.close();
});
} catch (e) {
}
If you want to just wrap text you can use 'alignment' attribute of cell.
worksheet.getCell('D1').alignment = { wrapText: true };
It's alredy mentioned here in plugin page: https://www.npmjs.com/package/exceljs#alignment
I am able to create a custom field with jsGrid and jquery autocomplete. All ajax CRUD calls are working for all other fields. The below code activates autocomplete and shows the available options in the input field as expected.
var tags = ["tag1", "tag2", "tag3"];
MyDescriptionField.prototype = new jsGrid.Field({
insertTemplate: function(value) {
return this._editPicker = $("<input>").autocomplete({source : tags});
},
editTemplate: function(value) {
return this._editPicker = $("<input>").autocomplete({source : tags});
},
........... (more code)
So far so good. However to actually capture the value so it can be inserted into the db, I also need to define insertValue and editValue.
The code below is NOT working
insertValue: function(){
return this._insertPicker = $("<input>").val();
},
...........(more code)
this one is not working eiter:
insertValue: function(){
return this._insertPicker.autocomplete({
select: function(event, ui) {
$("<input>").val(ui.item.value);
}
});
},
reference: jsGrid. http://js-grid.com/demos/
autocomplete: https://jqueryui.com/autocomplete/
Try this snippet:
$(function() {
var myTagField = function(config) {
jsGrid.Field.call(this, config);
};
myTagField.prototype = new jsGrid.Field({
sorter: function(tag1, tag2) {
return tag1.localeCompare(tag2);
},
itemTemplate: function(value) {
return value;
},
insertTemplate: function(value) {
return this._insertAuto = $("<input>").autocomplete({source : tags});
},
editTemplate: function(value) {
return this._editAuto = $("<input>").autocomplete({source : tags}).val(value);
},
insertValue: function() {
return this._insertAuto.val();
},
editValue: function() {
return this._editAuto.val();
}
});
jsGrid.fields.myTagField = myTagField;
$("#jsGrid").jsGrid({
width: "100%",
inserting: true,
editing: true,
sorting: true,
paging: true,
fields: [
{ name: "Name", type: "text" },
{ name: "Tag", type: "myTagField", width: 100, align: "center" },
{ type: "control", editButton: false, modeSwitchButton: false }
],
data: db.users
});
});
var tags = ["tag1", "tag2", "tag3"];
var db = {};
db.users = [
{
"Name": "Carson Kelley",
"Tag": ""
},
{
"Name": "Prescott Griffin",
"Tag": "tag1"
},
{
"Name": "Amir Saunders",
"Tag": "tag3"
},
{
"Name": "Derek Thornton",
"Tag": "tag2"
},
{
"Name": "Fletcher Romero",
"Tag": ""
}];
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><script src="//code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
<script src="//rawgit.com/tabalinas/jsgrid/master/dist/jsgrid.min.js"></script>
<link href="//code.jquery.com/ui/1.11.2/themes/cupertino/jquery-ui.css" rel="stylesheet"/>
<link href="//rawgit.com/tabalinas/jsgrid/master/dist/jsgrid.min.css" rel="stylesheet"/>
<link href="//rawgit.com/tabalinas/jsgrid/master/dist/jsgrid-theme.css" rel="stylesheet"/>
<div id="jsGrid"></div>
or this codepen: https://codepen.io/beaver71/pen/rpaLEo
Thanks #beaver. Your pen helped my understand custom fields better. I extended it a bit to add filtering with autocomplete. https://codepen.io/obrienje/pen/aQKNry
$(function() {
var myTagField = function(config) {
jsGrid.Field.call(this, config);
};
myTagField.prototype = new jsGrid.Field({
autosearch: true,
sorter: function(tag1, tag2) {
return tag1.localeCompare(tag2);
},
itemTemplate: function(value) {
return '<span class="label label-primary">' + value + '</span>';
},
insertTemplate: function(value) {
return this._insertAuto = $("<input>").autocomplete({
source: tags
});
},
filterTemplate: function(value) {
if (!this.filtering)
return "";
var grid = this._grid,
$result = this._filterAuto = $("<input>").autocomplete({
source: tags
});
if (this.autosearch) {
$result.on("change", function(e) {
grid.search();
});
}
return $result;
},
editTemplate: function(value) {
return this._editAuto = $("<input>").autocomplete({
source: tags
}).val(value);
},
insertValue: function() {
return this._insertAuto.val();
},
filterValue: function() {
return this._filterAuto.val();
},
editValue: function() {
return this._editAuto.val();
}
});
jsGrid.fields.myTagField = myTagField;
$("#jsGrid").jsGrid({
width: "100%",
filtering: true,
inserting: true,
editing: true,
sorting: true,
paging: true,
fields: [{
name: "Name",
type: "text"
},
{
name: "Tag",
type: "myTagField",
width: 100,
align: "center"
},
{
type: "control",
editButton: false,
modeSwitchButton: false
}
],
data: db.users,
controller: {
loadData: function(filter) {
return $.grep(db.users, function(item) {
return (!filter.Tag || item.Tag.toLowerCase().indexOf(filter.Tag.toLowerCase()) > -1);
});
}
}
});
});
var tags = ["tag1", "tag2", "tag3"];
var db = {};
db.users = [{
"Name": "Carson Kelley",
"Tag": ""
},
{
"Name": "Prescott Griffin",
"Tag": "tag1"
},
{
"Name": "Amir Saunders",
"Tag": "tag3"
},
{
"Name": "Derek Thornton",
"Tag": "tag2"
},
{
"Name": "Fletcher Romero",
"Tag": ""
}
];
<html>
<head>
<link href="https://rawgit.com/tabalinas/jsgrid/master/dist/jsgrid.min.css" rel="stylesheet"/>
<link href="https://rawgit.com/tabalinas/jsgrid/master/dist/jsgrid-theme.css" rel="stylesheet"/>
<link href="//code.jquery.com/ui/1.11.2/themes/cupertino/jquery-ui.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css"/>
</head>
<body>
<h1>Custom Grid DateField filtering with autocomplete</h1>
<div id="jsGrid"></div>
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
<script src="https://rawgit.com/tabalinas/jsgrid/master/dist/jsgrid.min.js"></script>
</body>
</html>
Thanks #beaver. Your pen helped my understand custom fields better. I extended it a bit to add filtering with autocomplete. https://codepen.io/obrienje/pen/aQKNry
As the topic states, I would like to group countries to create my own "areas". It works nearly, but I don't know whats wrong.
Here is my map: http://jsfiddle.net/wiesson/oktajn6e
It is mostly derived from the examples, but it does not work. If I set "allAreas" to false, it is okay but I would like to display all other countries, too!
Any ideas?
$(function () {
// Instanciate the map
$('#container').highcharts('Map', {
chart: {
borderWidth: 0
},
title: {
text: 'Group Hover'
},
legend: {
enabled: true
},
plotOptions: {
map: {
allAreas: true,
joinBy: ['iso-a2', 'code'],
mapData: Highcharts.maps['custom/world']
},
series: {
states:{
normal: {
animation: false
}
},
point: {
events: {
mouseOver: function(){
var ser = this.series;
var data = ser.data;
$.each(data, function(){
this.setState("hover")
});
},
mouseOut: function(){
var ser = this.series;
var data = ser.data;
$.each(data, function(){
this.setState()
});
}
}
}
}
},
series: [{
name: 'Nordic Countries',
data: $.map(['IS', 'NO', 'SE', 'FI', 'DK'], function (code) {
return {
code: code
};
}),
}, {
name: 'Some of central Europe',
data: $.map(['DE', 'AT', 'GB', 'FR'], function (code) {
return {
code: code
};
}),
}]
});
});
This solution should fix your problem: http://jsfiddle.net/oktajn6e/5/
But let me explain what happens in your code:
With both series you create a full world map with all areas. So if you activate both series, the second series covers the complete first series.
It means, the blue areas get covered by series' two grey areas.
I managed to solve it this way:
series: [{
allAreas: true,
mapData: Highcharts.maps['custom/world'],
showInLegend: false,
}, {
allAreas: false,
name: 'Nordic Countries',
joinBy: ['iso-a2', 'code'],
mapData: Highcharts.maps['custom/world'],
data: $.map(['IS', 'NO', 'SE', 'FI', 'DK'], function (code) {
return {
code: code
};
}),
}, {
allAreas: false,
name: 'Some of central Europe',
joinBy: ['iso-a2', 'code'],
mapData: Highcharts.maps['custom/world'],
data: $.map(['DE', 'AT', 'GB', 'FR'], function (code) {
return {
code: code
};
}),
}]
By creating each series individually and setting "allAreas:false" we can simply render it on the first series, where we only show the full map.