Related
I am currently working on a .NET Core application based on a CMS Framework named PiranhaCMS. This framework allows the definition of configurable "Blocks", basically widgets, that can be added by the users on their pages. The configuration page of the blocks is realized as a Vue.js component, and code is then compiled via gulp in a standard JS format (from the .vue file to the Vue.component(...) syntax) for the Piranha framework to read and render. The author of Piranha confirmed that this is the only way to define new blocks.
In one of our custom blocks, we are trying to implement a DevExpress Web Dashboard. I have tried following the steps outlined at https://docs.devexpress.com/Dashboard/401150/web-dashboard/dashboard-component-for-vue, but to no avail, since the compiler throws an Exception stating that the top-level declaration should be an export default { ... }, and not an import statement.
I came up with a workaround in which I dynamically load the required scripts and styles on the created() method of the component, and then define the dashboard in the same way I would in a classic javascript case (https://docs.devexpress.com/Dashboard/119158/web-dashboard/dashboard-control-for-javascript-applications-jquery-knockout-etc/add-web-dashboard-to-a-javascript-application);;) however, I am sure there is a more elegant solution to this problem.
Below is the code relevant to the problem. Here is the custom block itools-dashboard.vue:
<template>
<div class="form-group block-body">
<div :id="'dashboard-designer-' + uid" class="dashboard-designer">
<div :id="'dashboard_' + uid" style="height: 100%;">
</div>
</div>
<div class="row">
<div class="col-sm-6" style="padding:10px; margin-top: 0px;vertical-align: top;">
<fieldset>
<legend>Dashboard</legend>
<div class="form-group">
<label>Dashboard name</label>
<select class="form-control small" :id="'dashboard-names-' + uid" v-model="model.dashboardName.value">
<option v-for="dash in dashboardNames">{{ dash }}</option>
</select>
</div>
<div class="form-group">
<label>Update time</label>
<input class="form-control small" type="number" v-model="model.updateTime.value">
</div>
<div class="form-group">
<label>Width</label>
<input class="form-control small" type="text" v-model="model.width.value">
</div>
<div class="form-group">
<label>Height</label>
<input class="form-control small" type="text" v-model="model.height.value">
</div>
</fieldset>
</div>
<div class="col-sm-6" style="padding:10px; margin-top: 0px; background-color: #fcfcfc; border:1px dotted lightgray; vertical-align: top;">
<itools-base :model="model"></itools-base>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["uid", "toolbar", "model"],
data: function () {
return {
dashboardNames: [],
dahsboardConfig: null,
updateModes: ["period", "realtime"],
basePath: "../../../../assets/",
// define all the css and js files paths
cssResources: [
"devextreme/dist/css/light.css",
"#devexpress/analytics-core/dist/css/dx-analytics.common.css",
"#devexpress/analytics-core/dist/css/dx-analytics.light.css",
"#devexpress/analytics-core/dist/css/dx-querybuilder.css",
"devexpress-dashboard/dist/css/dx-dashboard.light.min.css"
],
jsResources: [
"js/jquery/jquery-3.3.1.min.js",
"jquery-ui-dist/jquery-ui.js",
"knockout/build/output/knockout-latest.js",
"ace-builds/src-min-noconflict/ace.js",
"ace-builds/src-min-noconflict/ext-language_tools.js",
"ace-builds/src-min-noconflict/theme-dreamweaver.js",
"ace-builds/src-min-noconflict/theme-ambiance.js",
"devextreme/dist/js/dx.all.js",
"devextreme/dist/js/dx.aspnet.mvc.js",
"devextreme-aspnet-data/js/dx.aspnet.data.js",
"#devexpress/analytics-core/dist/js/dx-analytics-core.min.js",
"#devexpress/analytics-core/dist/js/dx-querybuilder.min.js",
"devexpress-dashboard/dist/js/dx-dashboard.min.js"
]
}
},
created: function () {
// dynamically add the required css
this.cssResources.forEach(x => {
let link = document.createElement("link");
link.setAttribute("href", this.basePath + x);
link.setAttribute("rel", "stylesheet");
document.head.appendChild(link);
});
// dynamically add the js files.
// It needs to be a synchronous ajax call so that the exports are visible in the code
// (eg the new DevExpress call)
this.jsResources.forEach(x => {
$.ajax({
async: false,
url: this.basePath + x,
dataType: "script"
})
});
this.model.width.value = this.model.width.value || "100%";
this.model.height.value = this.model.height.value || "300";
this.model.updateTime.value = this.model.updateTime.value || 5000;
},
mounted: function () {
var h = document.getElementById("dashboard-designer-" + this.uid).clientHeight;
DevExpress.Dashboard.ResourceManager.embedBundledResources();
var dashboardControl = new DevExpress.Dashboard.DashboardControl(document.getElementById("dashboard_" + this.uid), {
endpoint: "/api/dashboard",
workingMode: "Designer",
width: "100%",
height: "100%",
initialDashboardId: this.model.dashboardName.value,
});
dashboardControl.render();
},
beforeCreate: function () {
fetch("/api/Dashboards/GetDashboardNames")
.then(response => response.json())
.then(data => {
this.dashboardNames = data;
});
},
}
</script>
which is then compiled via gulp task to
Vue.component("itools-dashboard", {
props: ["uid", "toolbar", "model"],
data: function () {
return {
dashboardNames: [],
dahsboardConfig: null,
updateModes: ["period", "realtime"],
basePath: "../../../../assets/",
cssResources: ["devextreme/dist/css/light.css", "#devexpress/analytics-core/dist/css/dx-analytics.common.css", "#devexpress/analytics-core/dist/css/dx-analytics.light.css", "#devexpress/analytics-core/dist/css/dx-querybuilder.css", "devexpress-dashboard/dist/css/dx-dashboard.light.min.css"],
jsResources: ["js/jquery/jquery-3.3.1.min.js", "jquery-ui-dist/jquery-ui.js", "knockout/build/output/knockout-latest.js", "ace-builds/src-min-noconflict/ace.js", "ace-builds/src-min-noconflict/ext-language_tools.js", "ace-builds/src-min-noconflict/theme-dreamweaver.js", "ace-builds/src-min-noconflict/theme-ambiance.js", "devextreme/dist/js/dx.all.js", "devextreme/dist/js/dx.aspnet.mvc.js", "devextreme-aspnet-data/js/dx.aspnet.data.js", "#devexpress/analytics-core/dist/js/dx-analytics-core.min.js", "#devexpress/analytics-core/dist/js/dx-querybuilder.min.js", "devexpress-dashboard/dist/js/dx-dashboard.min.js"]
};
},
created: function () {
this.cssResources.forEach(x => {
let link = document.createElement("link");
link.setAttribute("href", this.basePath + x);
link.setAttribute("rel", "stylesheet");
document.head.appendChild(link);
});
this.jsResources.forEach(x => {
$.ajax({
async: false,
url: this.basePath + x,
dataType: "script"
});
});
this.model.width.value = this.model.width.value || "100%";
this.model.height.value = this.model.height.value || "300";
this.model.updateTime.value = this.model.updateTime.value || 5000;
},
mounted: function () {
DevExpress.Dashboard.ResourceManager.embedBundledResources();
var dashboardControl = new DevExpress.Dashboard.DashboardControl(document.getElementById("dashboard_" + this.uid), {
endpoint: "/api/dashboard",
workingMode: "Designer",
width: "100%",
height: "100%",
initialDashboardId: this.model.dashboardName.value
});
dashboardControl.render();
},
beforeCreate: function () {
fetch("/api/Dashboards/GetDashboardNames").then(response => response.json()).then(data => {
this.dashboardNames = data;
});
},
template: "\n<div class=\"form-group block-body\">\n <div :id=\"'dashboard-designer-' + uid\" class=\"dashboard-designer\">\n <div :id=\"'dashboard_' + uid\" style=\"height: 100%;\">\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\" style=\"padding:10px; margin-top: 0px;vertical-align: top;\">\n <fieldset>\n <legend>Dashboard</legend>\n <div class=\"form-group\">\n <label>Dashboard name</label>\n <select class=\"form-control small\" :id=\"'dashboard-names-' + uid\" v-model=\"model.dashboardName.value\">\n <option v-for=\"dash in dashboardNames\">{{ dash }}</option>\n </select>\n </div>\n <div class=\"form-group\">\n <label>Update time</label>\n <input class=\"form-control small\" type=\"number\" v-model=\"model.updateTime.value\">\n </div>\n <div class=\"form-group\">\n <label>Width</label>\n <input class=\"form-control small\" type=\"text\" v-model=\"model.width.value\">\n </div>\n <div class=\"form-group\">\n <label>Height</label>\n <input class=\"form-control small\" type=\"text\" v-model=\"model.height.value\">\n </div>\n </fieldset>\n </div>\n <div class=\"col-sm-6\" style=\"padding:10px; margin-top: 0px; background-color: #fcfcfc; border:1px dotted lightgray; vertical-align: top;\">\n <itools-base :model=\"model\"></itools-base>\n </div>\n </div>\n</div>\n"
});
The gulp task responsible for the compilation, defined by Piranha, is:
var gulp = require('gulp'),
sass = require('gulp-sass'),
cssmin = require("gulp-cssmin"),
uglifyes = require('uglify-es'),
composer = require('gulp-uglify/composer'),
uglify = composer(uglifyes, console),
rename = require("gulp-rename"),
concat = require("gulp-concat");
var path = require('path'),
vueCompiler = require('vue-template-compiler'),
babel = require("#babel/core"),
babelTemplate = require("#babel/template").default,
codeFrameColumns = require('#babel/code-frame').codeFrameColumns,
babelTypes = require("#babel/types"),
through2 = require('through2');
function vueCompile() {
return through2.obj(function (file, _, callback) {
var relativeFile = path.relative(file.cwd, file.path);
console.log(relativeFile);
var ext = path.extname(file.path);
if (ext === '.vue') {
var getComponent;
getComponent = function (ast, sourceCode) {
const ta = ast.program.body[0]
if (!babelTypes.isExportDefaultDeclaration(ta)) {
var msg = 'Top level declaration in file ' + relativeFile + ' must be "export default {" \n' + codeFrameColumns(sourceCode, { start: ta.loc.start }, { highlightCode: true });
throw msg;
}
return ta.declaration;
}
var compile;
compile = function (componentName, content) {
var component = vueCompiler.parseComponent(content, []);
if (component.styles.length > 0) {
component.styles.forEach(s => {
const linesToStyle = content.substr(0, s.start).split(/\r?\n/).length;
var msg = 'WARNING: <style> tag in ' + relativeFile + ' is ignored\n' + codeFrameColumns(content, { start: { line: linesToStyle } }, { highlightCode: true });
console.warn(msg);
});
}
var ast = babel.parse(component.script.content, {
parserOpts: {
sourceFilename: file.path
}
});
var vueComponent = getComponent(ast, component.script.content);
vueComponent.properties.push(babelTypes.objectProperty(babelTypes.identifier('template'), babelTypes.stringLiteral(component.template.content)))
var wrapInComponent = babelTemplate("Vue.component(NAME, COMPONENT);");
var componentAst = wrapInComponent({
NAME: babelTypes.stringLiteral(componentName),
COMPONENT: vueComponent
})
ast.program.body = [componentAst]
babel.transformFromAst(ast, null, null, function (err, result) {
if (err) {
callback(err, null)
}
else {
file.contents = Buffer.from(result.code);
callback(null, file)
}
});
}
var componentName = path.basename(file.path, ext);
if (file.isBuffer()) {
compile(componentName, file.contents.toString());
}
else if (file.isStream()) {
var chunks = [];
file.contents.on('data', function (chunk) {
chunks.push(chunk);
});
file.contents.on('end', function () {
compile(componentName, Buffer.concat(chunks).toString());
});
}
} else {
callback(null, file);
}
});
}
var js = {
name: "itools-blocks.js",
path: "wwwroot/assets/js/blocks/*.vue"
}
//
// Compile & minimize js files
//
gulp.task("min:js", function (done) {
gulp.src(js.path, { base: "." })
.pipe(vueCompile())
.pipe(concat("wwwroot/assets/js/blocks/" + js.name))
.pipe(gulp.dest("."))
.pipe(uglify().on('error', function (e) {
console.log(e);
}))
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest("."));
done();
});
any kind of help is well appreciated
The gulpfile with the method “vueCompile” that you’re referring to was specifically written to suit the needs of the internal components we provide in the framework, it’s by no means a silver bullet for all Vue component compilation. However I understand your problem, before writing this code we desperately searched for existing npm-packages that would give us the functionality we needed, but this wasn’t that easy to find as we only use a subset of the features available in Vue.js
We’d be more than happy to get feedback or more information on how this could be done, so we’ll be watching this thread 👍🏼
I developed a web GIS tool to find some features on map using Find task of ArcGIS javascript api and show the attribute in a grid using grid enhanced of dojo. everything work fine at the first time. I can find features using keywords and show attributes in the grid but when i use the find tool again , I only can show the features on map and the grid not refresh after the first use. How can i Refresh and show new values in the grid?
the geonet has a code sample like my code . I search in the stackoverflow and found the how-to-refresh-datagrid but i could not use the solutions.
define([
"esri/tasks/FindTask",
"esri/tasks/FindParameters",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/Color",
"dgrid/Grid",
"dgrid/Selection",
'dojo/_base/declare',
"dojo/on",
"dojo/dom",
"dijit/registry",
"dojo/_base/array",
"dijit/form/Button",
"dojo/parser",
"esri/symbols/SimpleMarkerSymbol","dojo/data/ItemFileReadStore","dojox/grid/EnhancedGrid","dojo/data/ItemFileWriteStore",
"dojox/grid/enhanced/plugins/Pagination","dojox/grid/enhanced/plugins/Selector","dojox/grid/enhanced/plugins/Filter","dojox/grid/enhanced/plugins/exporter/CSVWriter","dojo/io/iframe",
"dojo/domReady!"],function ( FindTask, FindParameters, SimpleLineSymbol, SimpleFillSymbol, Color,
Grid, Selection, declare, on, dom, registry, arrayUtils, Button, parser,SimpleMarkerSymbol,ItemFileReadStore,EnhancedGrid,ItemFileWriteStore) {
return{
Find: function (map) {
var findTask, findParams;
var grid, store;
parser.parse();
registry.byId("searchfind").on("click", doFind);
//Create Find Task using the URL of the map service to search
findTask = new FindTask("http://...:6080/arcgis/rest/services/layers2/MapServer/");
map.on("load", function () {
//Create the find parameters
findParams = new FindParameters();
findParams.returnGeometry = true;
findParams.layerIds = [0];
findParams.searchFields = ["Name"];
findParams.outSpatialReference = map.spatialReference;
console.log("find sr: ", findParams.outSpatialReference);
});
function doFind() {
//Set the search text to the value in the box
var ownerNameBox = dom.byId("findName");
findParams.searchText = dom.byId("findName").value;
findTask.execute(findParams, showResults);
}
function showFilterBar(){
dijit.byId('grid').showFilterBar(true);
}
function showResults(results) {
//This function works with an array of FindResult that the task returns
map.graphics.clear();
var symbol = new SimpleMarkerSymbol();
symbol.setColor(new Color([0,255,255]));
//create array of attributes
var items = dojo.map(results, function (result) {
var graphic = result.feature;
graphic.setSymbol(symbol);
map.graphics.add(graphic);
return result.feature.attributes;
});
var data = {
identifier: 'OBJECTID',
label:'OBJECID',
items: items
};
store = new dojo.data.ItemFileReadStore({data: data});
/*set up layout*/
var layout = [[
{'name': 'OBJECTID', 'field': 'OBJECTID', 'width':'9em',datatype:"number"},
{'name': 'Name', 'field': 'Name','width':'16em',datatype:"string",autocomplete:true},
{'name':'Address','field':'Address','width':'18em',datatype:"string",autocomplete:true}
]];
/*create a new grid:*/
var grid = new dojox.grid.EnhancedGrid({
id: 'grid',
store:store,
structure: layout, rowSelector: '1px',
plugins: {
// pagination: {
// pageSizes: ["5", "10", "All"],
// description: true,
// sizeSwitch: false,
// pageStepper: true,
// gotoButton: true,
// /*page step to be displayed*/
// maxPageStep: 3,
// /*position of the pagination bar*/
// position: "bottom"
// },
filter: {
// Show the closeFilterbarButton at the filter bar
closeFilterbarButton: true
// Set the maximum rule count to 5
// ruleCount: 5,
// Set the name of the items
// itemsName: "songs",
}
}
},
document.createElement('div'));
/*append the new grid to the div*/
dojo.byId("grid").appendChild(grid.domNode);
/*Call startup() to render the grid*/
grid.startup();
grid.setStore(store);
grid.refresh()
}
//Zoom to the parcel when the user clicks a row
//display the results in the grid
//Zoom back to the initial map extent
// map.centerAndZoom(center, zoom);
// //Zoom to the parcel when the user clicks a row
function onRowClickHandler(evt) {
var clickedTaxLotId = event.rows[0].data.BRTID;
var selectedTaxLot = arrayUtils.filter(map.graphics.graphics, function (graphic) {
return ((graphic.attributes) && graphic.attributes.BRTID === clickedTaxLotId);
});
if ( selectedTaxLot.length ) {
map.setExtent(selectedTaxLot[0].geometry.getExtent(), true);
}
}
}
}
}
)
<body class="claro" role="main">
<div id="appLayout" style="width:100%; height:100%;" >
</div>
<!--<div id="rightpane">-->
<!--</div>-->
<div id="center">
<!-->
some divs
<!-->
</div>
<div id="bottom" style="height: 330px" >
</button>
<div id="grid" style="height:98%;font-size: 14px" ></div>
</div>
</body>
In order to change change values in the grid, you will need to change the value in the grid's store. The dojo grid widget will update itself as needed as it is directly liked to your store.
I'm trying to edit individual elements of an array.
I have a JSON array like this:
[ {"idComponent":1,"left":"100px","top":"100px","idHTML":"name_div","width":"200px","height":"300px","value":"My name is"}
,{"idComponent":2,"left":"200px","top":"200px","idHTML":"date_div","width":"200px","height":"300px","value":"2016-Enero-12"}
,{"idComponent":3,"left":"300px","top":"300px","idHTML":"weigth_div","width":"200px","height":"300px","value":"1.5 KG"}
,{"idComponent":4,"left":"400px","top":"400px","idHTML":"talla_div","width":"200px","height":"300px","value":"23"}
,{"idComponent":5,"left":"500px","top":"500px","idHTML":"description_div","width":"300px","height":"300px","value":"The text"}
]
These are converted to observables in knockout.
Each one is binded to a DIV element to display in screen.
"left", "top", "height" and "width" are CSS attributes that are applied to each one.
When you click ones of the DIV contains with the mouse, I'm trying to bind the element to HTML inputs to edit the CSS values. You can see the idea in the next picture:
Mockup Image - Click here to see the image
The code is the next:
function convertPixelsToInches(pixels){
return pixels/96;
}
// Elemento regresados desde el servidor.
var dataFromServer =
{
"idTemplate":"1"
,"components" :
[{"idComponent":1,"left":"100px","top":"100px","idHTML":"name_div","width":"200px","height":"300px","value":"Sergio Pinto Fernandez"}
,{"idComponent":2,"left":"200px","top":"200px","idHTML":"date_div","width":"200px","height":"300px","value":"2016-Enero-12"}
,{"idComponent":3,"left":"300px","top":"300px","idHTML":"weigth_div","width":"200px","height":"300px","value":"1.5 KG"}
,{"idComponent":4,"left":"400px","top":"400px","idHTML":"talla_div","width":"200px","height":"300px","value":"23"}
,{"idComponent":5,"left":"500px","top":"500px","idHTML":"description_div","width":"300px","height":"300px","value":"Tomar dos cucharadas cada 3 hras."}
]
,"paperSize":"papersize_USLetter_portrait"
,"templateImage":{
"imageUrl":"images/SolicitudLaboratorioExpress.jpg"
,"width":"8.5in"
,"height":"11in"
,"left":"0px"
,"top":"0px"
}
};
function componentModel(dataComponent) {
if (!dataComponent) {
dataComponent = {};
}
var self = this;
self.idComponent = ko.observable(dataComponent.idComponent);
self.left = ko.observable(dataComponent.left);
self.top = ko.observable(dataComponent.top);
self.idHTML = ko.observable(dataComponent.idHTML);
self.width = ko.observable(dataComponent.width);
self.height = ko.observable(dataComponent.height);
self.value = ko.observable(dataComponent.value);
}
/**
* data Json from server.
*
*/
function templateModel(data) {
if (!data) {
data = {};
}
var self = this;
self.components = ExtractComponents(self, data.components, componentModel);
self.currentSelectedComponent = ko.observable();
self.currentSelectedComponentIndex = ko.observable(-1);
//self.currentSelectedComponentLeft = ko.observable();
self.currentSelectedComponentLeft = ko.computed(function(){
var value = self.currentSelectedComponentIndex();
console.log(typeof value);
//value=value*1;
console.log(value);
if (value ) {
return "TT";
}
// Get "200px y must save as 200"
//return self.currentSelectedComponent().left;//.substring(0,data.length-2);
return "FF";
});
self.currentSelectedComponentTop = ko.observable();
self.editComponent = function(component,index){
self.currentSelectedComponentIndex(index);
self.currentSelectedComponent(component);
// Binding the component to the editor.
// ??
};
function bindComponentToEditor() {
ko.applyBindings()
}
self.idTemplate = ko.observable(data.idTemplate);
self.paperSize = ko.observable(data.paperSize);
/* */
self.paperSizeWidth = ko.observable(convertPixelsToInches(1067));
self.paperSizeHeigth = ko.observable(convertPixelsToInches(1067));
//
self.templateImage_imageUrl = ko.observable(data.templateImage.imageUrl);
self.templateImage_width = ko.observable(data.templateImage.width);
self.templateImage_height = ko.observable(data.templateImage.height);
self.templateImage_left = ko.observable(data.templateImage.left);
self.templateImage_top = ko.observable(data.templateImage.top);
}
/**
* parent: referencia al objeto o funcion que mando a llamar esta fucnion.
*
* dataArr: Array de elementos que se desea la funcion ExtractComponents haga mapeo.
* Ejemplo de dataArr:
*
* [ {"idComponent":1,"left":"100px","top":"100px","idHTML":"name_div","width":"200px","height":"300px","value":"Sergio Pinto Fernandez"}
,{"idComponent":2,"left":"200px","top":"200px","idHTML":"date_div","width":"200px","height":"300px","value":"2016-Enero-12"}
,{"idComponent":3,"left":"300px","top":"300px","idHTML":"weigth_div","width":"200px","height":"300px","value":"1.5 KG"}
,{"idComponent":4,"left":"400px","top":"400px","idHTML":"talla_div","width":"200px","height":"300px","value":"23"}
,{"idComponent":5,"left":"500px","top":"500px","idHTML":"description_div","width":"300px","height":"300px","value":"Tomar dos cucharadas cada 3 hras."}
]
*
* modelConstructor: funcion con que se creara un nuevo componente, es decir el modelo.
*
*/
function ExtractComponents(parent, dataArr, modelConstructor) {
var components = [];
if (dataArr == null) {
return components;
}
for (var i = 0; i < dataArr.length; i++) {
var dataArrElement = dataArr[i];
var component = new modelConstructor(dataArrElement,parent);
components.push(component);
}
return components;
}
ko.applyBindings(new templateModel(dataFromServer));
I have two problems:
The Input for left and top values only accept integer values, the i need substring "200px" to 200 but alway i receive a error: ".left is undefined"
self.currentSelectedComponentLeft = ko.computed(function(){
var value = self.currentSelectedComponentIndex();
console.log(typeof value);
//value=value*1;
console.log(value);
if (value ) {
return "TT";
}
// Get "200px and save as 200"
//return self.currentSelectedComponent().left;//.substring(0,data.length-2);
return "FF";
});
The principal problem... how can I bind the DIV element when I click the element to the inputs at the right?
I think I need dynamic binding, or dynamic subscription, but can't find answer to this problem of dynamic double data binding.
This is the HTML:
<div id="mainContainer">
<div id="contentPrint_div" class="page" data-bind="css: paperSize">
<img id="template_img" data-bind="attr: {src: templateImage_imageUrl},
style: {width: templateImage_width, height: templateImage_height, left: templateImage_left, top: templateImage_top}">
<div id="fieldsArea_div" class="" data-bind="foreach: components">
<div class="field" data-bind="html: value, style: {width: width, left:left, top:top},
attr: {id: idHTML}, click: $parent.editComponent($data,$index)"></div>
</div>
</div>
<div id="toolsbar">
<div id="toolbarPanel">
ID template:<span data-bind='text: idTemplate'></span>
<div id="panelMenuInfoElements_div">
Elemento actual: <span data-bind='text: idTemplate'></span>
Posicion
X:<input type="text" class="form-control" id="" placeholder="x" min="0" step="0.01"
data-bind="attr: {max: paperSizeWidth}, value: currentSelectedComponentTop">
Y:<input type="text" class="form-control" id="" placeholder="y" min="0" step="0.01"
data-bind="attr: {max: paperSizeHeigth}, value: currentSelectedComponentLeft">
</div>
</div>
<div id="toolbarCode">
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
</div>
</div>
</div>
You posted quite some code; I'm not 100% sure if I got all your questions correctly. But I tried to create an example that I think shows how to solve your problems.
I've used a computed to create a style object that rewrites x and y observables that are numbers, to the style binding compatible: { top: 0px, left: 0px }
I've used a with binding to link your <input> elements to the last clicked block.
var Block = function(name, x, y) {
this.label = name;
this.x = ko.observable(x);
this.y = ko.observable(y);
this.style = ko.computed(function() {
return {
top: this.y() + "px",
left: this.x() + "px"
}
}, this);
}
var VM = function() {
this.blocks = [
new Block("Block 1", 100, 100),
new Block("Block 2", 200, 100),
new Block("Block 3", 300, 100),
];
this.selectedBlock = ko.observable(this.blocks[0]);
};
ko.applyBindings(new VM());
.Block {
position: absolute;
background: red;
padding: 1rem;
list-style: none;
}
.Block.is-selected { background: yellow; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="Inputs" data-bind="with: selectedBlock">
<div>
Editing <span data-bind="text: label"></span>
</div>
<div>Left:
<input type="number" data-bind="value: x">
</div>
<div>Top:
<input type="number" data-bind="value: y">
</div>
</div>
<ul data-bind="foreach: blocks">
<li class="Block" data-bind="
click: $parent.selectedBlock,
style: style,
text: label,
css: { 'is-selected': $data == $parent.selectedBlock()}"></li>
</ul>
i'm trying to create a chart using dojo. I choose a StackedColumns chart. I want to make it interactive. When a user clicks on a graph columns, an hyperlink should be invoked. But to create hyperlink'url, I need the series name. Is there any way to get Series Name when user clicks on a column? I've been searching for days but i didn't found any solution. Here is my code:
<div id="chartNode" style="width: 1024px; height: 768px;"></div>
<div id="legend"></div>
<script type="text/javascript">
require(['dojox/charting/Chart',
'dojox/charting/themes/PrimaryColors',
'dojox/charting/plot2d/StackedColumns',
'dojox/charting/plot2d/Grid',
'dojox/charting/widget/Legend',
'dojox/charting/action2d/Tooltip',
'dojox/charting/action2d/Magnify',
'dojox/charting/action2d/Highlight',
'dojo/store/Observable',
'dojo/store/Memory',
'dojox/charting/StoreSeries',
'dojox/charting/axis2d/Default',
'dojo/domReady!'], function(Chart, theme, StackedColumns,Grid,Legend,Tooltip,Magnify,Highlight,Observable,Memory,StoreSeries){
var myData = [{id:1,value:1,site:'1'},
{id:2,value:2,site:'1'},
{id:3,value:6,site:'1'},
{id:4,value:4,site:'1'},
{id:5,value:5,site:'1'},
{id:6,value:1,site:'2'},
{id:7,value:3,site:'2'},
{id:8,value:1,site:'2'},
{id:9,value:2,site:'2'},
{id:10,value:7,site:'2'}];
var myStore = new Observable(new Memory({
data: { identifier: 'id',
items: myData
}
}));
var serie_1 = new StoreSeries(myStore, { query: { site: 1 } }, 'value');
var serie_2 = new StoreSeries(myStore, { query: { site: 2 } }, 'value');
var myChart = new Chart('chartNode');
myChart.setTheme(theme);
myChart.addAxis('x', { fixLower: 'minor',
fixUpper: 'minor',
natural: true,
rotation: 90
});
myChart.addAxis('y', {vertical: true,fixLower: 'major',fixUpper: 'major',minorTicks: true,includeZero: true});
myChart.addPlot('myPlot', { type: 'StackedColumns', gap: 5, minBarSize: 8});
myChart.addSeries('Serie 1',serie_1,{ stroke: { color: 'red' }, fill: 'lightpink' });
myChart.addSeries('Serie 2',serie_2,{ stroke: { color: 'blue' }, fill: 'lightblue' });
var highlight = new Highlight(myChart, 'myPlot');
myChart.render();
var legend = new Legend({ chart: myChart }, 'legend');
myChart.connectToPlot('myPlot',function(evt) {
// React to click event
if(type == 'onclick') {
var msg = 'x:'+evt.x+';y:'+evt.y+';index: '+evt.index+';value: '+evt.run.data[evt.index];
alert(msg);
//How to get series informations when onclick event fires???
}
var tip = new Tooltip(myChart,'myPlot',{text:function(evt){return evt.run.data[evt.index];}});
});
});
</script>
I tried this in a stacked bar and works well:
chart.connectToPlot("default", function(evt) {var type = evt.type; if(type=="onclick") console.log(evt.run.name);})
evt.run.name provides the series name based on the column you clicked
Great! Thank you Noelia!
I have used it for a pie chart. You can get the index number of the slice with the parameter evt.index.
i am populating a jqgrid from database and one of its columns is a color column like red, blue, etc. Can i set the cell color of this column based on the value coming from database at run time? how should i set formatter in this case? i tried like this but do not work
var colorFormatter = function(cellvalue, options, rowObject) {
var colorElementString = '<div class="colorBlock" style="background-color: red;"></div>';
return colorElementString;
---
---
colModel: [
{ name: 'GroupName', index: 'GroupName', width: 200, align: 'left' },
{ name: 'Description', index: 'Description', width: 300, align: 'left' },
{ name: 'Color', index: 'Color', width: 60, align: 'left', formatter: colorFormatter}],
Add a load complete call to a function that formats the colors:
loadComplete: function (data) {
$.each(data.rows, function (i, item) {
var rowId = data.rows[i].id || data.rows[i]._id_;
var myRow = new Array(item.valueOf());
jQuery('#' + grid).setCell(rowId, colName, '', { background: 'red'});
});
}
You just need to add the code to check for the conditions you want to apply.
I am changing the background color for the row based on status ("status" is the data column). Hope this might help. Here is the sample code:
<style>
.state_inactive {
background-color:##FF9999 !important;
border:1px solid ##A6C9E2;
color:##222222;
}
</style>
<script type="text/javascript">
$j("#Grid").jqGrid({
url:"getData.php",
datatype:"json",
colNames:['Name', 'Organization', 'Status'],
colModel:[{name:'name', index:'name'}, {name:'organization', index:'organization'}, {name:'status', index:'status'}],
},
gridComplete:function() {
var ids = jQuery("#Grid").jqGrid('getDataIDs');
for (var i = 0; i < ids.length; i++) {
var status = jQuery("#Grid").jqGrid("getCell", ids[i], 'status');
if (status == "-20") {
$j('#' + ids[i]).removeClass("ui-widget-content");
$j('#' + ids[i]).addClass("state_inactive");
}
}
})
</script>
Looks like a design flaw of jqgrid.
The best way is to implement this during loading and the custom formatter only has a value where you can do this. But you need to do this not on the value, but on the div or span that is in front of it.