AngularJS element directive with ng-init runs before view renders - javascript

I am attempting to loop through an array and create multiple instances of a custom directive that creates different graphs based on some variables on the rootScope. Everything works fine except when I try to place those in a view and call ng-init to a method on the scope and pass it arguments.
What I am finding is that ng-init seems to run before anything (and I think ng-init is the wrong approach), which causes errors because the variables being set in the method aren't set yet when the ng-init runs in the view.
When I first load the index view, then go into this view, all is well, but when I try to load this view first or reload it, I am getting the errors. The ng-init is trying to call the chart() method before anything else runs.
On the index view, I have this chart in a modal that gets called onclick, so ng-init is not needed, therefore it works great.
I am a little stuck and what I need is advice on the "right" or better way to accomplish this.
On the detail view, I need to loop across that array to get four charts based on four different objects of data.
The data is a static file for now, as this is a prototype.
My code is essentially this:
View:
<div id="chart_list">
<div class="chart" ng-repeat="val in ['abc', 'def', 'ghi', 'jkl']" ng-init="chart('line', val, itemId, val)">
<h3>{{val | uppercase}}</h3>
<chart/>
</div>
</div>
AppController method:
// get data set up for chart consumption
$scope.chart = function(chart, kpi, socId, chartId) {
$rootScope.visualize = {};
$rootScope.visualize.chart = chart;
$rootScope.visualize.chartId = chartId;
$rootScope.visualize.val = val;
$rootScope.visualize.item = $rootScope.itemList[itemId];
$rootScope.visualize.valName = $rootScope.visualize.item[val].name;
};
DetailView controller:
app.controller('ItemDetailController', function($scope, $rootScope, $routeParams, ItemList) {
var itemId = $scope.itemId = $routeParams.itemId;
ItemList.get({}, function(res) {
$rootScope.itemList = res.data;
$scope.item = $rootScope.itemList[itemId];
});
});
Service:
app.factory('ItemList', function($resource){
return $resource("/api/item-list.json");
});
Directive:
app.directive('chart', function() {
return {
restrict: 'E',
link: function(scope, element, attrs){
if ($(window).width() <= 1200){
var width = 300;
} else {
var width = 450;
}
var visualize = scope.visualize;
var data = visualize.soc[visualize.val].data;
var numTicks = Object.keys(data).length;
element.append('<div class="chart_container"><div id="y_axis' + visualize.chartId +'" class="y_axis"></div><div id="chart' + visualize.chartId + '" class="chart"></div><div id="x_axis' + visualize.chartId + '" class="x_axis"></div></div>');
element.append('<div id="legend_container' + visualize.chartId +'" class="legend_container"><div id="smoother' + visualize.chartId +'" title="Smoothing"></div><div id="legend' + visualize.chartId +'"></div></div>');
var valSeries = [];
var valSeries2 = [];
var valSeries3 = [];
var valMap = {};
var i = 0;
Object.keys(data).forEach(function(propertyName) {
var value = data[propertyName];
var val2 = (Math.random() * (102 - 87) + 86) / 100;
var val3 = (Math.random() * (95 - 70) + 69) / 100;
valSeries.push({x: i, y: data[propertyName].amount});
valSeries2.push({x: i, y: data[propertyName].amount * val2});
valSeries3.push({x: i, y: data[propertyName].amount * val3});
valMap[i] = data[propertyName].name;
i++;
});
var graph = new Rickshaw.Graph({
element: document.querySelector('#chart' + visualize.chartId),
width: width,
height: 150,
renderer: visualize.chart,
stroke: true,
series: [
{
data: valSeries3,
color: '#F0AD4E',
name: 'Three years ago',
},
{
data: valSeries2,
color: '#5BC0DE',
name: 'Two years ago',
},
{
data: valSeries,
color: '#5CB85C',
name: 'Past year'
}
]
});
var format = function(n) {
var map = valMap;
return map[n];
}
var x_ticks = new Rickshaw.Graph.Axis.X({
graph: graph,
width: width,
orientation: 'bottom',
element: document.getElementById('x_axis' + visualize.chartId),
pixelsPerTick: width/numTicks,
tickFormat: format
});
var y_axis = new Rickshaw.Graph.Axis.Y({
graph: graph,
orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis' + visualize.chartId),
});
graph.render();
var hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
formatter: function(series, x, y) {
var content = app.lib[visualize.dataFormat](parseInt(y)) + "<br>";
return content;
}
});
var legend = new Rickshaw.Graph.Legend( {
graph: graph,
element: document.getElementById('legend' + visualize.chartId)
} );
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
graph: graph,
legend: legend
} );
}
};
});
Edit:
I fixed this by removing the chart method altogether and replacing ng-init with attributes. Something like this:
<chart data-attrone="somedata" data-attrtwo="some other data" />
Then the attrs are available in the directive with attrs.attrone, etc.
Attribute names must be lower-case.
Hope this helps someone in the future.

I fixed this by removing the chart method altogether and replacing ng-init with attributes. Something like this:
<chart data-attrone="somedata" data-attrtwo="some other data" />
Then the attrs are available in the directive with attrs.attrone, etc.
Attribute names must be lower-case.
Hope this helps someone in the future.

Related

Pass Custom Parameter to query.send Google Query Language

I am using google's charts API to create a web-based dashboard. I want to draw many graphs and need to pass custom parameters to the handleDataQueryResponse function from this link: https://developers.google.com/chart/interactive/docs/spreadsheets#sheet-name. This function is called via a query.send(handleDataQueryResponse) call. I would have thought I could do this by calling: query.send(function() { handleDataQueryResponse(parameters) }); but this hasn't been working for me. Any ideas? Open to other approaches to making the query and its handler reusable!
More info on google's javascript chart API here: https://developers.google.com/chart/interactive/docs/quick_start.
something like this work for you?
google.charts.load('current', {
callback: function () {
drawChart0();
drawChart1();
},
packages:['corechart', 'table']
});
function drawChart0() {
var chart = new google.visualization.BarChart(document.getElementById('barchart'));
var query = "http://www.google.com/fusiontables/gvizdata?tq=SELECT 'Label' as beach, 'Pieces total' as Total FROM 1c-FiEDwdwMt55a4AlE8Xuu40rUBR_qeI8ENiHtPV";
var options = {
animation: {
duration: 500,
startup: true,
easing: 'out'
},
chartArea: {
width: '40%'
}
};
sendQuery(query, chart, options);
}
function drawChart1() {
var chart = new google.visualization.Table(document.getElementById('tablechart'));
var query = "https://docs.google.com/spreadsheets/d/19olM1pEF5qQvvKhwSH3d_X4w2DVfOWDwDtZKNFlMY3w/edit#gid=0&tq=select A, B where A >= date '2016-01-01'";
var options = {};
sendQuery(query, chart, options);
}
function sendQuery(query, chart, options) {
new google.visualization.Query(query).send(function (response) {
chart.draw(response.getDataTable(), options);
});
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="barchart"></div>
<div id="tablechart"></div>
I figured out one solution, and let me tell you, it's not pretty. But it works. As always, happy for improvements and feedback! Thanks #WhiteHat for helping me out.
google.charts.setOnLoadCallback(function() {
drawChart('chart1', 0, 'SELECT A, B, C, D, E', {title: 'Chart 1'})
drawChart('chart2', 1, 'SELECT A, H, I, J', {title: 'Chart 2'})
});
var chartsArray = [];
var optionsArray = [];
var nextID = 0;
function drawChart(tag, id, sqlCommand, options= {}, sheetName='Sheet1', numHeaders=1) {
chartsArray[id] = new google.visualization.ColumnChart(document.getElementById(tag));
optionsArray[id] = options;
var queryString = encodeURIComponent(sqlCommand);
var query = new google.visualization.Query(
'https://docs.google.com/spreadsheets/d/1dfRA_NDsdfED3OEdx-v3ZzA2-oWPS4kU_2mV-PY/gviz/tq?sheet=' + sheetName + '&headers=' + numHeaders.toString() + '&tq=' + queryString);
query.send(handleDataQueryResponse);
}
function handleDataQueryResponse(response, id) {
if (response.isError()) {
alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var data = response.getDataTable();
chartsArray[nextID].draw(data, optionsArray[nextID]);
nextID += 1;
}

JS object events

I've been using JS for a long time, but i'm just getting into properly using objects.
I'm creating an image generator using canvas, which you can then download as a png. The user can add text to the image using input boxes. I've made this a JS object so that I can create multiple instances of it on a page. It all works fine, except for my event listener to update the text.
The input boxes are created by parameters passed to the function, and then a keyup event is added so that the canvas can update as the user types. This is all working, except for that fact I don't know how to tell the keyup event what JS Object it should be updating.
Here's my code:
function Canvas_to_img( name, container, image, height, width, content_areas ){
// Assign params to object
object = this;
this.name = name;
this.container = container;
this.image = image;
this.height = height;
this.width = width;
this.content_areas = content_areas;
// Prepare image
var imageObj = new Image();
imageObj.src = image;
// Add canvas div
container.prepend('<div class="canvas canvas--' + name + '"></div>')
// Add temp canvas div
container.append('<div class="canvas-temp canvas-temp--' + name + '"></div>')
// Add canvas wrapper
var wrapper = new Concrete.Wrapper({
container: container.find('.canvas')[0],
width: width,
height: height
});
// Add BG and Text layers
var bgLayer = new Concrete.Layer();
var textLayer = new Concrete.Layer();
wrapper.add(bgLayer).add(textLayer);
// Add image to BG layer
imageObj.onload = function() {
bgLayer.sceneCanvas.context.drawImage(imageObj,0,0, width, height);
};
// Set up input areas and event listeners
for (key in content_areas){
for (subkey in content_areas[key].fields){
name_field = content_areas[key].fields[subkey].name;
name_layer = content_areas[key].fields[subkey].name;
// Add input fields for text layer
container.prepend('<div class="field-set__item">\
<span class="field-set__label">' + name_field +'</span>\
<input data-object="'+object+'" data-area="' + key + '" data-name="' + name + '" data-field="' + subkey + '" class="field-set__input ' + name_field + '" id="text1" type="text">\
</div>');
$( "."+name_field ).keyup(function() {
area = $(this).data('area');
field = $(this).data('field');
object = $(this).data('object');
console.log(object);
value = $(this).val();
content_areas[key].fields[subkey].name;
//content_areas[area].fields[field].value = value;
OBJECTNAME.updateContent(area, field, value)
OBJECTNAME.updateCanvas(wrapper);
});
}
}
container.append('Download');
$('body').on('click', '.download--' + name, function(e) {
var dataURL = $('.canvas-temp--fb-banner canvas')[0].toDataURL('image/png');
$(this).attr('download', 'test.png');
document.getElementById('download--' + name).href = dataURL;
});
}
Canvas_to_img.prototype = {
updateContent: function(area, field, value){
this.content_areas[area].fields[field].value = value;
},
updateCanvas: function(wrapper) {
$('.canvas-temp--'+this.name+' canvas').remove();
wrapper.layers[1].destroy();
var textLayer = new Concrete.Layer();
wrapper.add(textLayer);
content_areas = this.content_areas;
for (key in content_areas){
textLayer.sceneCanvas.context.fillStyle = content_areas[key].colour;
textLayer.sceneCanvas.context.font = "bold 24px Open Sans";
textLayer.sceneCanvas.context.textAlign = content_areas[key].alignment;
layer_content = '';
for (subkey in content_areas[key].fields){
//console.log(content_areas[key].fields[subkey]);
layer_content += content_areas[key].fields[subkey].prepend + " ";
layer_content += content_areas[key].fields[subkey].value;
layer_content += content_areas[key].fields[subkey].append + " ";
//console.log(layer_content);
}
textLayer.sceneCanvas.context.fillText(layer_content, content_areas[key].position_x, content_areas[key].position_y);
}
//ctx.fillText($(this).val(), cnvs.width/2, 300);
// textLayer.sceneCanvas.context.fillText('Test', 325, 300);
var canvas = wrapper.toCanvas({
pixelRatio: 1
});
//console.log(canvas.canvas);
$('.canvas-temp--'+this.name).append(canvas.canvas);
},
update: function(){
}
};
var content_areas = {
1: {
position_x: 425,
position_y: 300,
alignment: 'center',
colour: '#fff',
font: 'Open Sans',
size: '24px',
weight: 600,
fields: {
field_1: {
name: 'date',
value: '',
prepend: '',
append: ','
},
field_2: {
name: 'venue',
value: '',
prepend: '',
append: ''
}
}
}
}
var content_areas_2 = {
1: {
position_x: 425,
position_y: 300,
alignment: 'center',
colour: '#fff',
font: 'Open Sans',
size: '24px',
weight: 600,
fields: {
field_1: {
name: 'date',
value: '',
prepend: '',
append: ','
},
field_2: {
name: 'venue',
value: '',
prepend: '',
append: ''
},
field_3: {
name: 'time',
value: '',
prepend: '',
append: ''
}
}
}
}
var facebook_banner = new Canvas_to_img('fb-banner', $('.banner-container'), 'http://localhost:1234/images/mainsite5/bb-fb-cover.jpg', 315, 851, content_areas);
var facebook_banner_2 = new Canvas_to_img('fb-banner-2', $('.banner-container-2'), 'http://localhost:1234/images/mainsite5/bb-fb-cover.jpg', 315, 851, content_areas_2);
These are the function calls that need to be associated with an object:
OBJECTNAME.updateContent(area, field, value)
OBJECTNAME.updateCanvas(wrapper);
I probably need to instead make this eventlistener linked to the object itself? I'm not sure how do to that.
updateContent and updateCanvas are prototypes of that objects you create at the bottom of your code. Normally, you would access those with this.updateContent(area, field, value).
BUT you are inside a callback function, so this refers to the scope of that function.
Earlier in your code, you set object = this; (why is object global b.t.w.?), so you already store this inside a variable that you should have access to inside the callback. So your code should read:
object.updateContent(area, field, value); , etc // object is your this
Here is another similar but simplified question.
I've found it useful to save the instance to the element. You can do this by taking the instance assigned here:
var facebook_banner = new Canvas_to_img('fb-banner', $('.banner-container'), 'http://localhost:1234/images/mainsite5/bb-fb-cover.jpg', 315, 851, content_areas);
And setting it to a data property of its element:
$('.banner-container').data('canvas_to_img', facebook_banner);
Now you can get that instance when you need it:
$('.banner-container').data('canvas_to_img');
Or if you're within the context of that element already:
$(this).data('canvas_to_img');

Why doesnt an ember observer fire on arrays?

I am trying to listen to specific property on every element in an array and get a result from that. However, updates dont appear to happen properly.
var emptyEmberObjectClass = Ember.Object.extend({});
var container = Ember.Object.extend({
data: Ember.A([
emptyEmberObjectClass.create({yo:1}),
emptyEmberObjectClass.create({yo:2}),
emptyEmberObjectClass.create({yo:3})
]),
computedData: Ember.computed('data.#each.yo', function(){
var sum = 0;
this.get('data').forEach(function(data){
sum = sum + data.yo;
});
return sum;
}),
test: Ember.observer('computedData', function(){
Ember.$('#a').html('woohoO!');
})
}).create();
var existingItem = container.get('data');
existingItem.objectAt(0).set('yo', 50);
http://jsfiddle.net/stb0nr2y/1/
As you can see, the text field still says 'start' and doesnt get updated to 'woohoO!'.
Any help will be greatly appreciated.
JavaScript:
var emptyEmberObjectClass = Ember.Object.extend({});
var container = Ember.Object.extend({
data: Ember.A([
emptyEmberObjectClass.create({yo:1}),
emptyEmberObjectClass.create({yo:2}),
emptyEmberObjectClass.create({yo:3})
]),
computedData: Ember.computed('data.#each.yo', function(){
var sum = 0;
this.get('data').forEach(function(data){
sum += data.get('yo');
});
return sum;
}),
testObs: Ember.on('init', Ember.observer('computedData', function() {
Ember.$('#a').html('woohoO! cd: `' + this.get('computedData') + '`');
}))
}).create();
var existingItem = container.get('data');
var target = existingItem.objectAt(0);
target.set('yo', 50);
target.set('yo', 100);
Updates DOM correctly with:
woohoO! cd: 55
and then:
woohoO! cd: 105
Please note that I'm using Ember v1.13.10:
<script src="http://builds.emberjs.com/tags/v1.13.10/ember.min.js"></script>
Working demo.

highlightSeries plugin for jquery flot charts

I'm looking for help with the highlightSeries plugin made by Brian Peiris (http://brian.peiris.name/highlightSeries/). It doesn't appear to be working; I'm positive that the event is firing, because an alert test I performed earlier worked just fine, printing out $(this).text(). I'm trying to get the series on the chart to be highlighted when the user mouses over the series name in the legend (something which works perfectly fine on Mr. Peiris's website).
$('.server-chart').each(function() {
var serv = $(this).attr('id').substr(6);
var plotData = [];
//alert(serv + ": " + $.toJSON(serverStats[serv]));
for (var k in serverStats[serv].StatsByCollection) {
var outlabel = k;
var outdata = serverStats[serv].StatsByCollection[k];
plotData.push({ label: outlabel, data: outdata});
}
plotOptions = {
legend: {
container: $('#legend-' + serv),
labelFormatter: function(label, series) {
return '' + label + '';
},
noColumns: 2
},
series: {
lines: {
show: true,
fill: false
},
points: {
show: false,
fill: false
}
},
colors: colors,
grid: {
hoverable: false
},
highlightSeries: {
color: "#FF00FF"
}
}
$_plot = $.plot(this, plotData, plotOptions);
$plots.push($_plot);
$('#legend-' + serv + ' .legendLabel, #legend-' + serv + ' .legendColorBox').on('mouseenter', function () {
$_plot.highlightSeries($(this).text());
});
$('#legend-' + serv + ' .legendLabel, #legend-' + serv + ' .legendColorBox').on('mouseleave', function () {
$_plot.unHighlightSeries($(this).text());
});
});
I'm not sure what other code to put on here, so tell me if you need more; the charts are all working fine, this is just the part of the ready function setting up all of the plots and their options inside the placeholders.
Also, there's a couple of classes attached to the labels that are extraneous; I was trying different things to get this stuff to work.
The plugin requires a patched version of flot to work (it introduces a public drawSeries method). The last patched version is for an older version of flot (0.7).
With that said, I wouldn't use this plugin. If you just want to highlight a series on legend mouseover it's pretty simple.
$('#legend .legendLabel, #legend .legendColorBox').on('mouseenter', function() {
var label = $(this).text();
var allSeries = $_plot.getData();
// find your series by label
for (var i = 0; i < allSeries.length; i++){
if (allSeries[i].label == label){
allSeries[i].oldColor = allSeries[i].color;
allSeries[i].color = 'black'; // highlight it in some color
break;
}
}
$_plot.draw(); // draw it
});
$('#legend .legendLabel, #legend .legendColorBox').on('mouseleave', function() {
var label = $(this).text();
var allSeries = $_plot.getData();
for (var i = 0; i < allSeries.length; i++){
if (allSeries[i].label == label){
allSeries[i].color = allSeries[i].oldColor;
break;
}
}
$_plot.draw();
});
See example here.

Angularjs, nvd3, and real time line chart

I have to find a way to workaround this bug https://github.com/novus/nvd3/issues/335
What I want to do is to find a way to do the transformation after I put a new data inside the array of the samples that are shown in the chart. My code is this:
var windowDimension = 60; //seconds since we update every second
$scope.summary = {};
$scope.ddd = [
{
"key": "Series 1",
"values": []
}
];
var summaryResource = summaryService.getSummaryResource();
summaryResource.get(function(data) {
$timeout(function() {
$scope.summary = data;
}, 500);
});
/**/
$scope.onTimeout = function(){
summaryResource.get(function(data) {
$timeout(function() {
$scope.summary = data;
}, 500);
});
mytimeout = $timeout($scope.onTimeout,1000);
$scope.ddd[0].values.push([$scope.summary.timeAsLong, $scope.summary.totalThroughput]);
if($scope.ddd[0].values.length > windowDimension)
$scope.ddd[0].values.shift();
};
var mytimeout = $timeout($scope.onTimeout,1000);
This code is inside the controller of the page and the page has a "nvd3-line-chart" directive that accept "ddd" as the source of the data.
Everything works, as in the bug is explained but I want to avoid the y transformation and do the call to transform the graph between the values.push and the values.shift calls.
The transform call is this
path.attr("d", line).attr("transform", null).transition()
.ease("linear").attr("transform", "translate(" + x(-1) + ")");
but I don't know how to call it inside the controller

Categories

Resources