EDIT: clone this repository for a non working reproduction. https://github.com/FickleLife/meteor-c3-test
I am using https://github.com/peernohell/meteor-c3.js/
I pull two examples off the C3 site http://c3js.org/examples.html and can get them to display once on the page, but when I try to add a second on the same page the first disappears. There's no console error and the javascript variables are different.
chart 1 html template:
<template name="chart_cp_overview">
<div id="cpOverview"></div>
</template>
chart 1 js helper:
Template.chart_cp_overview.rendered = function () {
var cpOverview = c3.generate({
data: {
columns: [
['data1', 30, 200],
['data2', 130, 100]
],
type: 'bar',
groups: [
['data1', 'data2']
]
},
grid: {
y: {
lines: [{value:0}]
}
}
});
}
chart 2 html template:
<template name="chart_status">
<div id="chart"></div>
</template>
chart 2 helper:
Template.chart_status.rendered = function() {
var chart = c3.generate({
data: {
columns: [
['Dropped', 30],
['On Course', 120],
['DNS', 20],
['Finished', 40]
],
colors: {
'Dropped': '#E60000',
'On Course': '#00ACED',
'DNS': '#DBDBDB',
'Finished': '#00BD07'
},
type : 'donut',
onclick: function (d, i) { console.log("onclick", d, i); }
// onmouseover: function (d, i) { console.log("onmouseover", d, i); },
// onmouseout: function (d, i) { console.log("onmouseout", d, i); }
},
donut: {
title: "Entrant Status",
label: {
format: function (value, ratio) {
return value;
}
}
}
});
};
display code :
<div class="row">
<div class="col-md-6">
{{> chart_cp_overview}}
</div>
<div class="col-md-6">
{{> chart_status}}
</div>
</div>
This code above displays only the last chart - chart_status. If I remove any one of the handlebars reference the other chart displays fine, or if I have multiple handlebars to multiple charts whatever was last declared is displayed.
How can I get multiple charts to display within one page? Example is on github at https://github.com/FickleLife/meteor-c3-test
It looks like maybe you are intending the two variable names you have chosen in your template rendered functions, cpOverview and chart, to bind to the dom elements with those ids. It won't work that way.
The variable names you have chosen are local to their functions and in any case would not automatically attach to elements with that id even if they were global. So c3 is binding all these charts to the same dom element (the docs say the default is #chart), and the last one is overriding the prior ones.
You want to bind each chart to its respective element. You can use this.firstNode inside your rendered function (based on the way you have it set up), or use jquery, or this.find("div#cpOverview"), and then use the c3 api to bind the chart to it - it looks like { bindto: "div#cpOverview" } may be the one you want.
Related
I am using the vue-select library. How can I force input entries to make all characters lower case? Right now, when I type the word "Baseball" into the input field, the tag is saved as "Baseball". I would like all tags to only keep a lower case version of the entry such as "baseball".
I have a sandbox here: https://stackblitz.com/edit/vue-ranqmt?file=src/App.vue
<template>
<div id="app">
<h3>Vue Select</h3>
<v-select
v-model="selected"
taggable
multiple
:options="options"
#input="setSelected"
></v-select>
<br /><br />
selected: <br />
<pre>{{ selected }}</pre>
</div>
</template>
<script>
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
export default {
name: 'App',
components: {
'v-select': vSelect,
},
data() {
return {
selected: null,
options: [],
};
},
methods: {
setSelected(value) {
console.log(value);
},
},
};
</script>
a - You could add a computed property which returns the lowercased version to use in whichever part of your app you need it to be.
computed: {
selectedLowerCase() {
return this.selected.toLowerCase();
}
}
b - if you are using this for something like an API call, then you can turn the variable(s) into lowercase before submitting.
c - if you want the variable to appear as lowercase even in the input field you need to add the #input action to your input field and point it to a function to lowercase your input
methods: {
setSelected(value) {
this.selected = this.selected.toLowerCase();
console.log(value);
},
},
If I create a table in a Template, is there a way to add a row of "reactive totals" at the bottom of the table using Meteor? Or do I have to write the code to sum the columns, the same way I would in ordinary JavaScript?
For example, given the table...
State Sales
NY 1234
FL 670
TX 2306
Total 4210
Can I use some reactive technique to calculate and keep the Total cell showing 4210 up-to-date as other cells change?
Another way to do this is to use MongoDB $sum aggregator
Taking Erik Johnson's example, you need to do this:
// HTML
<div id="allSales">
<h2><span>Total sales: </span>{{allSales}}</h2>
</div>
// JS
Template.templatename.helpers({
allSales: function() {
var allSales = allStates.aggregate( [ { $group: { _id: null, total: { $sum: "$sales" } } } ]);
return allSales.total;
},
});
This is how I've done it - I'm sure there's probably an easier way, but I'm still a beginner at Meteor:
// HTML
<div id="allSales">
<h2><span>Total sales: </span>{{allSales}}</h2>
</div>
// JS
Template.templatename.helpers({
allSales: function() {
var allStates = Statescollection.find();
var mappedItems = allStates.map((function(allStates) {return allStates.sales;}));
var allSales = _.reduce(mappedItems, function(memo, num){ return memo + num; }, 0);
return allSales;
},
});
I've created the package peppelg:mongo-sum. With it, MyCollection.find().sum('field') will return the sum of all the values in the field 'field'.
I had the same issue, and have found the following solution for both columns and rows of a table
In Meteor, how can i do a sum of some computed fields in a table?
When I go to a child state, I want to hide a ui-view component of a quadrant ui-view in root state. How can achieve this.
##index.html
<div ui-view="a">
</div>
<div ui-view="b">
</div>
<div ui-view="c">
</div>
##b.html
<div ui-view>
</div>
##config
$stateProvider.state('start', {
'views': {
'a': {
templateUrl: ...
},
'b': {
templateUrl: 'b.html'
},
'c': {
templateUrl: ...
}
},
controller: 'indexController
}).state('start.all', {
templateUrl: 'd.html',
controller: 'allController'
});
So when I reach start.all, I would like that the ui-view tagged c vanishes. How can I accomplish this.
There is an example demonstrating approach discussed below. The native way of ui-router, I'd say, is to manage all the views from current (active) state. We can do it with :
View Names - Relative vs. Absolute Names
... Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname#statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item
In our case, the full name of the view 'c' would be c#, i.e. c as view name, # as delimiter and empty string representing the root (a bit weird but in fact logical).
Having that we can change the start.all definition like this:
.state('start.all', {
url : '/all',
'views': {
'': {
template: '<span>this is start ALL</span>',
},
'c#': {
template: '<span></span>',
},
},
})
And we will change the content of the c view in the root. And that should be the most native way with ui-router. It does not effectively remove it, but we can replace it with some empty stuff.
Also, into your example above, I placed controller called bController as contra example to the indexController:
.state('start', {
url : '/start',
'views': {
'a': {
template: ...
},
'b': {
template: ...
// HERE a new controller bController
controller: 'bController',
},
'c': {
template: ...
}
},
// the orginal contoller
controller: 'indexController',
})
and also defined them this way:
.controller('indexController', function($scope, $state, $stateParams) {
console.log('indexConroller was invoked');
})
.controller('bController', function($scope, $state, $stateParams) {
console.log('bConroller was invoked');
})
Why? to show you, that indexController will never be invoked. Contollers belongs to templates/views not to state...
Check all that together here
You could do it in a few ways. One way would be to have an abstract state containing views a and b. That abstract state then has two concrete child states: start, which adds view c, and all, which adds view d.
Another option is to just use the ng-show directive on the c view's root element bound to some scope variable. I would go with the first option.
Notably this does not answer the question because all is no longer a child of start. If there is a real need for all to inherit from start (there appears to be no need at present) you can just make start abstract and create a start.main and start.all.
Though Radim's solution is very clever and much appreciated, I think this is much more readable and intuitive than overriding a parent view with an empty template.
<body>
<div ng-app="myApp">
<a ui-sref="start.main">start</a> | <a ui-sref="start.all">all</a>
<hr />
<div class="rootView" ui-view="a"></div>
<div class="rootView" ui-view="b"></div>
<div class="rootView" ui-view=""></div>
</div>
<script>
var myApp = angular.module('myApp', ['ui.router']);
myApp.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/start/main');
$stateProvider
// Content common to all views
.state('shell', {
abstract: true,
views: {
"a": { template: '<div>View a here.</div>' },
"b": { template: '<div>View b here.</div>' },
"": { template: '<div ui-view></div>' }
}
})
// Content common to all 'start' views (currently nothing)
.state('start', {
parent: 'shell',
url: '/start',
abstract: true,
template: '<div ui-view></div>'
})
.state('start.main', {
url: '/main',
template: '<div>View c is here</div>'
})
.state('start.all', {
url: '/all',
template: '<div>View d is here</div>'
});
});
</script>
</body>
I have two grids side by side.
The left grid has a list of tags the user can select, and the grid on the right is empty so the user can drag the tags he wants there.
The plugin code for both grids is:
viewConfig: {
plugins: [
Ext.create('Ext.grid.plugin.DragDrop', {
ddGroup: 'selectedTags'
})
]
}
So, as I wanted to limit the user to be able to drag only 5 tags, I've added the following code to the grid on the right:
listeners: {
beforedrop: {
fn: function() {
if (grid.getStore().data.items.length > 4) {
dropHandlers.cancelDrop();
}
},
scope: me
}
}
This is working perfectly but what I wanted is to show the NO-DROP icon when the items are dragged over the grid instead of showing the green line as if the action was allowed.
Thanks,
After looking for this solution for a while, I finally figured it out.
You must add two methods to the dropZone in the Target Grid:
notifyOver and onNodeDrop
The solution for my problem would be the code below:
Ext.create('Ext.grid.Panel', {
store: myStore,
columns: [columns],
viewConfig: {
plugins: {
ptype: 'gridviewdragdrop',
dragText: 'Drag and drop to reorganize',
pluginId : 'dragNdrop',
dropZone:{
notifyOver:function(source, e, data){
var store = this.view.ownerCt.getStore();
return store.getCount()<5?this.dropAllowed:this.dropNotAllowed
},
onNodeDrop:function(targetNode,dragZone,e,data){
var sourceStore = dragZone.view.ownerCt.getStore(),
targetStore = this.view.ownerCt.getStore(),
isDropValid = targetStore.getCount()<5;
if(isDropValid){
sourceStore.remove(data.records[0])
targetStore.add(data.records[0]);
}
return isDropValid;
}
}
}
},
height: 200,
width: 400,
renderTo: Ext.getBody()
});
Lopes, you can use column renderer in grid where you can check the items count and display appropriate icon. Code snippet for your reference:
gridCheckboxRenderer: function(value, meta, rec, rowInd, colInd, store){
var cssPrefix = Ext.baseCSSPrefix, cls = [cssPrefix + 'grid-checkheader'];
if (condition == false) {
cls.push(cssPrefix + 'grid-checkheader-checked-disabled');
return '<div class="' + cls.join(' ') + '"> </div>';
}
return '<div class="x-grid-row-checker"> </div>';
}
Hope it helps.
In Handlebars.js, how can I use #index to subscript into another parallel array in the object I am passing to a template?
For example, say I have an object set up like the following:
var table = {
cols : [
{ name: "Column 1" },
{ name: "Column 2" },
{ name: "Column 3", highlighted: true }
],
rows : [
{
label: "Row 1",
data: [
{ val: 5 },
{ val: 3 },
{ val: 8 }
]
},
{
label: "Row 2",
data: [
{ val: 8 },
{ val: 4 },
{ val: 0 }
]
}
]
};
I need to be able to use the #index from an {{#each rows}}{{#each data}} loop to check if the column is highlighted to apply a style to cells in the column, but Handlebars.js does not appear to allow using #index in a subscript operator.
E.g.
{{#index}} <!-- Index of current rows.data is 2. -->
{{#if ../../cols.[#index].highlighted }}
<!-- Never Executed -->
{{/if}}
{{#if ../../cols.[2].highlighted }}
<!-- Executes -->
{{/if}}
Is this not supported? Am I doing something wrong? How can I get this to work easily?
I posted an example on jsfiddle.net.
#key and #index behave specially, not as variables. So, even when you can use myvar.[123] you cannot use myvar.[#key] even though #key is 123. (At least, as of Handlebars 1.3.0)
There are two solutions. The first is to write your own handlebars helper, that takes both arrays/objects. The second is to restructure your data, i.e. merge your two parallel arrays.
As an example of the latter approach, if you have these two arrays:
var X={
cat:"meow",
sheep:"baaa"
};
var Y={
cat:3,
sheep:5
};
Then you could do:
var Z={};
for(var ix in X){
Z[ix]={X:X[ix],Y:Y[ix]};
}
And then pass Z to your handlebars template:
myTemplate({animals:Z});
which might look like this:
{{#each animals}}
The {{#key}} goes {{this.X}}; we have {{this.Y}} of them.
{{/each}}