It is best to place logic related to ui and layout in views
. So for this task i would suggest to use a view
or a component
. As you already realised the didInsertElement
event is the appropriate place to call this kind of logic, since you need to make sure that the view has been inserted into the DOM and all required elements such as div#chart
are available.
I'm providing a solution which is a bit rough but gives a starting point.
The idea is to create, a reusable view GoogleChartView
with an associated template google-chart
that can be included in any other view. I assumed that dashboard view will eventually include other charts as well that will be using its model to present data in charts, tables etc.
This chart related view has been included with the assistance of view helper {{view "googleChart"}}
. Another important point is that google.load
needs to be called in head.
http://jsbin.com/oCIcexOB/1/edit
JS
App = Ember.Application.create();
/******** VIEWS ********/
App.DashboardView = Ember.View.extend({
name: "",
init: function(){
//google.load("visualization", "1", {packages:["corechart"]});
//google.setOnLoadCallback(drawBarGraph);
this.set('name', 'ted');
return this._super();
}
});
App.GoogleChartView = Ember.View.extend({
templateName:"google-chart",
drawChart:function(){
drawChart();
}.on("didInsertElement")
});
/******** ROUTES ********/
App.Router.map(function() {
this.resource('items');
this.resource('dashboard');
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('items');
}
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
var a = Em.A();
a.pushObject( App.Item.create({title: 'A', cost: '100', options: buildMockOptions('A')}));
a.pushObject( App.Item.create({title: 'B', cost: '200', options: buildMockOptions('B')}));
a.pushObject( App.Item.create({title: 'C', cost: '300', options: buildMockOptions('C')}));
return a;
}
});
buildMockOptions = function(someVar){
var arr = [];
for(var i = 0;i<3;i++){
var opt = App.Option.create({someOption: 'Option for ' + someVar + ': ' + i});
opt.cost = 5;
arr.pushObject(opt);
}
return arr;
};
/******** MODELS ********/
App.Item = Ember.Object.extend({
title: '',
cost: '',
quantity: '',
options: null,
totalOptionsCost: function(){
var j = this.get('options').reduce(
function(prevValue, currentValue, index, array){
return prevValue + parseInt(currentValue.get('cost'), 10); }, 0);
return j;
}.property('options.@each.cost')
});
App.Option = Ember.Object.extend({
someOption: '',
cost: ''
});
/******** CONTROLLERS ********/
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, cost){
return parseInt(cost.get('cost'),10) + prevCost;
}, 0);
}.property('@each.cost'),
totalOptions: function(){
var opts = this.mapBy('options');
return [].concat.apply([], opts).length;
}.property(),
totalOptionCost: function(){
var sum = this.reduce(function(prev, curr){
return prev + curr.get('totalOptionsCost');},0);
return sum;
}.property('@each.totalOptionsCost')
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
optionsTotal: Ember.computed.alias('controllers.items.totalOptions'),
optionsCost: Ember.computed.alias('controllers.items.totalOptionCost')
});
// GOOGLE CHART FUNCTION
function drawChart() {
// Create and populate the data table.
var options = {
title:"Yearly Coffee Consumption",
width:600,
height:400,
animation: {duration: 1000, easing: 'out'},
vAxis: {title: "Cups", minValue:0, maxValue:500},
hAxis: {title: "Year"}
};
var data = new google.visualization.DataTable();
data.addColumn('string', 'N');
data.addColumn('number', 'Value');
data.addRow(['2003', 0]);
data.addRow(['2004', 0]);
data.addRow(['2005', 0]);
// Create and draw the visualization.
var chart = new google.visualization.ColumnChart(document.getElementById('chart'));
chart.draw(data, options);
data.setValue(0, 1, 400);
data.setValue(1, 1, 300);
data.setValue(2, 1, 400);
chart.draw(data, options);
var button = document.getElementById('b1');
button.onclick = function() {
if (data.getNumberOfRows() > 5) {
data.removeRow(Math.floor(Math.random() * data.getNumberOfRows()));
}
// Generating a random x, y pair and inserting it so rows are sorted.
var x = Math.floor(Math.random() * 10000);
var y = Math.floor(Math.random() * 1000);
var row = 0;
while (row < data.getNumberOfRows() && parseInt(data.getValue(row, 0),10) < x) {
row++;
}
data.insertRows(row, [[x.toString(), y]]);
chart.draw(data, options);
};
}
function drawChart() {
// Create and populate the data table.
var options = {
title:"Yearly Coffee Consumption",
width:600,
height:400,
animation: {duration: 1000, easing: 'out'},
vAxis: {title: "Cups", minValue:0, maxValue:500},
hAxis: {title: "Year"}
};
var data = new google.visualization.DataTable();
data.addColumn('string', 'N');
data.addColumn('number', 'Value');
data.addRow(['2003', 0]);
data.addRow(['2004', 0]);
data.addRow(['2005', 0]);
// Create and draw the visualization.
var chart = new google.visualization.ColumnChart(document.getElementById('chart'));
chart.draw(data, options);
data.setValue(0, 1, 400);
data.setValue(1, 1, 300);
data.setValue(2, 1, 400);
chart.draw(data, options);
var button = document.getElementById('b1');
button.onclick = function() {
if (data.getNumberOfRows() > 5) {
data.removeRow(Math.floor(Math.random() * data.getNumberOfRows()));
}
// Generating a random x, y pair and inserting it so rows are sorted.
var x = Math.floor(Math.random() * 10000);
var y = Math.floor(Math.random() * 1000);
var row = 0;
while (row < data.getNumberOfRows() && parseInt(data.getValue(row, 0),10) < x) {
row++;
}
data.insertRows(row, [[x.toString(), y]]);
chart.draw(data, options);
};
}
HTML
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Ember.js example: Trying to display a google chart in a template." />
<meta charset=utf-8 />
<title>JS Bin</title>
<script src="http://www.google.com/jsapi?ext.js"></script>
<script src="http://code.jquery.com/jquery-2.0.2.js"></script>
<script src="http://builds.emberjs.com/handlebars-1.0.0.js"></script>
<script src="http://builds.emberjs.com/ember-latest.js"></script>
<script src="http://www.google.com/jsapi?ext.js"></script>
<script>google.load("visualization", "1", {packages:["corechart"]});</script>
</head>
<body>
<script type="text/x-handlebars" data-template-name="application">
<p><strong>Ember.js example:</strong><br> Trying to display a google chart in a template.</p>
<p>This jsbin represents a dashboard that analyzes a hierarchy of items and child 'options' and visualizes that info in a 'dashboard' section'. Currently, I am trying to integrate a google chart into the dashboard right above the 'Change Value' button.</p>
<p>Google chart example: <a href='http://jsfiddle.net/doub1ejack/h7mSQ/96/'>http://jsfiddle.net/doub1ejack/h7mSQ/96/</a><br>
Stackoverflow post: <a href='http://stackoverflow.com/questions/20198317/ember-js-rendering-google-chart-in-template-aka-target-dom-element-only-when-p'>http://stackoverflow.com/questions/20198317/...</a></p>
{{render dashboard}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="items">
<h2>Items:</h2>
<dl>
{{#each}}
<dt>Item: {{title}}</dt>
<dd>Cost: {{cost}}</dd>
<dd class='options'>{{render 'options' options}}</dd>
{{/each}}
</dl>
</script>
<script type="text/x-handlebars" data-template-name="options">
{{log controller}}
<dl>
{{#each option in controller }}
<dt>{{option.someOption}}</dt>
<dd>Option cost: {{option.cost}}</dd>
{{/each}}
</dl>
</script>
<script type="text/x-handlebars" data-template-name="dashboard">
<h2>Dashboard:</h2>
{{view "googleChart"}}
<div id="chart"></div>
{{! ITEM/OPTION ANALYSIS }}
{{#if controllers.items}}
<h3>Item Overview:</h3>
Total number of items (expect 3): {{itemsLength}}<br>
Total cost of items (expect 600): {{itemsTotalCost}}
<h3>Option Overview:</h3>
Total number of options (expect 30): {{optionsTotal}}<br>
Total cost of options (expect 45): {{optionsCost}}
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="google-chart">
<div id="chart"></div>
<form><input id="b1" type="button" value="Change Value"/></form>
</script>
</body>
</html>
EDIT
This is another example where the code with the properties and data related to drawing the chart are accomodated in the GoogleChartView
class, allowing for binding of properties, execution of actions etc. This is also a draft example just to demonstrate the idea. The change value action executes the old code but it could certainly do modifications to the data property of GoogleChartView. The data property of GoogleChartView could certainly take better advantage of the model. Interesting points are the retrieval of the view's DOM via this.$().find
, the access to the controller (DashboardController) of the context where this view in included, since at this point any data could be retrieved,parsed and drawn.
http://jsbin.com/UHajoX/1/edit
App.GoogleChartView = Ember.View.extend({
templateName:"google-chart",
chart:null,
options:{
title:"Yearly Coffee Consumption",
width:600,
height:400,
animation: {duration: 1000, easing: 'out'},
vAxis: {title: "Cups", minValue:0, maxValue:500},
hAxis: {title: "Year"}
},
data:function(){
var items = this.get("controller").get("items");
var chartData = new google.visualization.DataTable();
chartData.addColumn('string', 'N');
chartData.addColumn('number', 'Value');
chartData.addRow(['2003', 0]);
chartData.addRow(['2004', 0]);
chartData.addRow(['2005', 0]);
items.forEach(function(item,index){
chartData.setValue(index,1,parseInt(item.get("cost"),10));
});
/*chartData.setValue(0, 1, 400);
chartData.setValue(1, 1, 300);
chartData.setValue(2, 1, 400);*/
return chartData;
}.property(),
drawChart:function(){
this.drawChart2();
}.on("didInsertElement"),
drawChart2:function(){
var data = this.get("data");
// Create and draw the visualization.
this.set("chart", new google.visualization.ColumnChart(this.$().find('#chart').get(0)));
this.get("chart").draw(data, this.get("options"));
},
actions:{
changeValue:function(){
var data = this.get("data");
if (data.getNumberOfRows() > 5) {
data.removeRow(Math.floor(Math.random() * data.getNumberOfRows()));
}
// Generating a random x, y pair and inserting it so rows are sorted.
var x = Math.floor(Math.random() * 10000);
var y = Math.floor(Math.random() * 1000);
var row = 0;
while (row < data.getNumberOfRows() && parseInt(data.getValue(row, 0),10) < x) {
row++;
}
data.insertRows(row, [[x.toString(), y]]);
this.set("data",data);
this.get("chart").draw(data, this.get("options"));
}
}
});
caveat:
I guess in the future the data may originate from the ember app itself, but now when calling google load there might be a possibility where the view tries to call drawChart and the data is not been available yet. To overcome this you may need to check that the data have been loaded before calling drawChart.