I've created a sample of my chart below using
nicholas bering, API Promise. I faked the $http data callback in my demo below.
My question is how to correctly access the draw() method of the chart after it is already displayed in the browser?
In my demo below, I create a google.visualization.DataView() so I can access the hideRows() method. Once that occurs, the documentation says I need to call on the draw() method to repaint the chart with the newly altered row information.
In this case I'm trying to let the user hide rows of items where the quantity being displayed is zero (row 2 "Olives" in my data). Once I get this working, I will let the user toggle other things but for now I'm trying to keep my question simple.
But this is where I get lost... the draw() method as I understand it should already exist on the original chart I created. How does one expose the draw() method of the original chart without having to poke at the DOM with a document.getElementById('myBarChart'). This seems so unlike everything Angular.
Here is my code:
<div ng-controller="ChartsController as ChartsController"
ng-init="ChartsController.init()">
<button ng-click="ChartsController.ToggleZeroDistributionOff()">No Zeros</button><br>
<div google-chart chart="chartMe" id="myBarChart" />
</div>
now my controller:
'use strict';
app.controller('ChartsController', ['$scope', '$http', '$q', 'googleChartApiPromise', function ($scope, $http, $q, googleChartApiPromise) {
this.name = "ChartsController";
this.$inject = ['$scope', '$q', '$http', 'googleChartApiPromise'];
$scope.chartMe = {};
this.init = function () {
// simulated $http callback data returned in promise
var dataPromise = {
"data": [
{"itemname": "Mushrooms", "qty": 13 },
{"itemname":"Onions", "qty": 11},
{"itemname":"Olives", "qty": 0},
{"itemname":"Zucchini", "qty": 1},
{"itemname": "Pepperoni", "qty": 27 }
]
}
// bind data and chart loading before building the my chart
$q.all({ data: dataPromise, api: googleChartApiPromise })
.then(apiLoadSuccess);
};
function apiLoadSuccess(result) {
$scope.chartMe.type = 'BarChart';
//create a new DataTable loaded with data from the HTTP response
$scope.chartMe.data = new google.visualization.DataTable();
$scope.chartMe.data.addColumn('string', 'Item Name/Units');
$scope.chartMe.data.addColumn('number', 'Qty');
// create an array to hold index of items
var aNoQty = [];
var aQty = [];
var aRows = [];
for (var i = 0; i < result.data.data.length; i++) {
var oData = [];
aRows.push(i);
oData[0] = result.data.data[i].itemname;
oData[1] = result.data.data[i].qty;
// which items quanity exist
if (result.data.data[i].qty > 0) {
aQty.push(i);
} else {
aNoQty.push(i);
};
// now add the row
$scope.chartMe.data.addRow(oData);
};
$scope.aNoQty = aNoQty;
$scope.aQty = aQty;
$scope.chartMe.options = {
title: "Item(s) Distributed",
isStacked: false,
displayExactValues: true,
};
};
this.ToggleZeroDistributionOff = function () {
$scope.chartMe.view = new google.visualization.DataView($scope.chartMe.data);
$scope.chartMe.view.hideRows($scope.aNoQty)
// this seems like the wrong way to attach to existing chart...
// i'm referring to using document.getElementById() - not very Angular !
// but how else to expose the draw() method ??
var myChart = new google.visualization.BarChart(document.getElementById('myBarChart'));
// now draw() method is expoised
myChart.draw($scope.chartMe.view.toDataTable(), $scope.chartMe.options)
}
}]);
Thanks in advance for any suggestions.
Thank you WhiteHat for your suggestion. It worked both ways but in my case found your first answer to be easier to work with.
I posted a complete solution below:
'use strict';
app.controller('ChartsController', ['$scope', '$http', '$q',
'googleChartApiPromise', function ($scope, $http, $q, googleChartApiPromise) {
this.name = "ChartsController";
this.$inject = ['$scope', '$q', '$http', 'googleChartApiPromise'];
$scope.chartMe = {};
this.init = function () {
// simulated $http callback data returned in promise
var dataPromise = {
"data": [
{"itemname": "Mushrooms", "qty": 13 },
{"itemname":"Onions", "qty": 11},
{"itemname":"Olives", "qty": 0},
{"itemname":"Zucchini", "qty": 1},
{"itemname": "Pepperoni", "qty": 27 }
]
}
// bind data and chart loading before building the my chart
$q.all({ data: dataPromise, api: googleChartApiPromise })
.then(apiLoadSuccess);
};
function apiLoadSuccess(result) {
$scope.chartMe.type = 'BarChart';
//create a new DataTable loaded with data from the HTTP response
$scope.chartMe.data = new google.visualization.DataTable();
$scope.chartMe.data.addColumn('string', 'Item Name/Units');
$scope.chartMe.data.addColumn('number', 'Qty');
// create an array to hold index of items
var aNoQty = [];
var aQty = [];
var aRows = [];
for (var i = 0; i < result.data.data.length; i++) {
var oData = [];
aRows.push(i);
oData[0] = result.data.data[i].itemname;
oData[1] = result.data.data[i].qty;
// which items quanity exist
if (result.data.data[i].qty > 0) {
aQty.push(i);
} else {
aNoQty.push(i);
};
// now add the row
$scope.chartMe.data.addRow(oData);
};
$scope.aNoQty = aNoQty;
$scope.aQty = aQty;
$scope.chartMe.options = {
title: "Item(s) Distributed",
isStacked: false,
displayExactValues: true,
};
// chart view used later
$scope.chartMe.view = new google.visualization.DataView($scope.chartMe.data);
// grab a reference to the chart
$scope.chartMe.myChart = new google.visualization.BarChart(document.getElementById('myBarChart'));
};
this.ToggleZeroDistributionOff = function () {
// $scope.chartMe.view = new google.visualization.DataView($scope.chartMe.data);
$scope.chartMe.view.hideRows($scope.aNoQty)
// draw() method exists so refresh with new view
$scope.chartMe.myChart.draw($scope.chartMe.view.toDataTable(), $scope.chartMe.options)
}
}]);
This worked for me and opened lots of new options.
Thanks! Another way to hide rows as a response to an input in the view, assuming that you don't have that much data, is to use ng-change and set
the value of the cells in your row(s)/column(s) to = null. You'll have to find all the cells that you want to set to null because you can't simply set the
whole row or column to null. One by one. The advantage is that you can stick to the simple way of using ng-google-charts. Again this might make things easier only for small charts. You can also write a function which does a push() to the table an put in the ng-change of your input to do insert data from the view. If you ng-change affects directly the DataTable, make a variable that stores the original values, that way you can hide and then restore columns or rows.
https://www.w3schools.com/angular/ng_ng-change.asp
Related
(was not sure what to have as a title, so if you have a better suggestion, feel free to come up with one - I will correct)
I am working on an angular application where I have some menues and a search result list. I also have a document view area.
You can sort of say that the application behaves like an e-mail application.
I have a few controllers:
DateCtrl: creates a list of dates so the users can choose which dates they want to see posts from.
SourceCtrl: Creates a list of sources so the user can choose from which sources he/she wants to see posts from.
ListCtrl: The controller populating the list. The data comes from an elastic search index. The list is updated every 10-30 seconds (trying to find the best interval) by using the $interval service.
What I have tried
Sources: I have tried to make this a filter, but a user clicks two checkboxes the list is not sorted by date, but on which checkbox the user clicked first.
If it is possible to make this work as a filter, I'd rather continue doing that.
The current code is like this, it does not do what I want:
.filter("bureauFilter", function(filterService) {
return function(input) {
var selectedFilter = filterService.getFilters();
if (selectedFilter.length === 0) {
return input;
}
var out = [];
if (selectedFilter) {
for (var f = 0; f < selectedFilter.length; f++) {
for (var i = 0; i < input.length; i++) {
var myDate = input[i]._source.versioncreated;
var changedDate = dateFromString(myDate);
input[i]._source.sort = new Date(changedDate).getTime();
if (input[i]._source.copyrightholder === selectedFilter[f]) {
out.push(input[i]);
}
}
}
// return out;
// we need to sort the out array
var returnArray = out.sort(function(a,b) {
return new Date(b.versioncreated).getTime() - new Date(a.versioncreated).getTime();
});
return returnArray;
} else {
return input;
}
}
})
Date: I have found it in production that this cannot be used as a filter. The list of posts shows the latest 1000 posts, which is only a third of all posts arriving each day. So this has to be changed to a date-search.
I am trying something like this:
.service('elasticService', ['es', 'searchService', function (es, searchService) {
var esSearch = function (searchService) {
if (searchService.field === "versioncreated") {
// doing some code
} else {
// doing some other type of search
}
and a search service:
.service('searchService', function () {
var selectedField = "";
var selectedValue = "";
var setFieldAndValue = function (field, value) {
selectedField = field;
selectedValue = value;
};
var getFieldAndValue = function () {
return {
"field": selectedField,
"value": selectedValue
}
};
return {
setFieldAndValue: setFieldAndValue,
getFieldAndValue: getFieldAndValue
};
})
What I want to achieve is this:
When no dates or sources are clicked the whole list shall be shown.
When Source or Date are clicked it shall get the posts based on these selections.
I cannot use filter on Date as the application receives some 3000 posts a day and so I have to query elastic search to get the posts for the selected date.
Up until now I have put the elastic-search in the listController, but I am now refactoring so the es-search happens in a service. This so the listController will receive the correct post based on the selections the user has done.
Question is: What is the best pattern or method to use when trying to achieve this?
Where your data is coming from is pretty irrelevant, it's for you to do the hook up with your data source.
With regards to how to render a list:
The view would be:
<div ng-controller='MyController as myCtrl'>
<form>
<input name='searchText' ng-model='myCtrl.searchText'>
</form>
<ul>
<li ng-repeat='item in myCtrl.list | filter:myCtrl.searchText' ng-bind='item'></li>
</ul>
<button ng-click='myCtrl.doSomethingOnClick()'>
</div>
controller would be:
myApp.controller('MyController', ['ElasticSearchService',function(ElasticSearchService) {
var self = this;
self.searchText = '';
ElasticSearchService.getInitialList().then(function(list) {
self.list = list;
});
self.doSomethingOnClick = function() {
ElasticSearchService.updateList(self.searchText).then(function(list) {
self.list = list;
});
}
}]);
service would be:
myApp.service('ElasticSearchService', ['$q', function($q) {
var obj = {};
obj.getInitialList = function() {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
obj.updateList = function(param) {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
return obj;
}]);
This code has NOT been tested but gives you an outline of how you should approach this. $q is used because promises allow things to be dealt with asynchronously.
Hi i'm building a form doing a lot of calculations, such as summarizing keys in objects
[
{ bar: 5, .. },
{ bar: 6, .. },
...
]
I use this expression in currently 35 places in my HTML. Sometimes connected to further calculations and with different keys
<div>
{{ sumKeys('bar') + foobar }}
</div>
The function i use is declared as
app.controller('someCtrl', function($scope){
$scope.sumKeys= function(key){
return (calculated sum);
}
}
My problem is, that if i write a single letter into any input field the sum function is called about 400 times. I know there is a rule, that calls the function if a scope is changed up to 10 times, but isn't there a more efficient way?
Can i output the result without changing the scope? Or force this calculation just to be done once? I know the results do not change any involved scope. So the results should be the same after 2 iterations.
I also implemented the function as a filter with same results.
What you're looking for is a SINGLETON service. You can make one in angular like this: (one of my real examples)
angular.module('ocFileUpload', [])
.service('ocFileUpload', ['$http', '$rootScope', function ($http, $rootScope) {
// Will house our files
this.files = [];
// This fn appends files to our array
this.addFile = function(files){
for (var i = 0; i < files.length; i++){
this.files.push(files[i]);
}
};
this.getFileDetails = function(ele){
// push files into an array.
for (var i = 0; i < ele.files.length; i++) {
this.files.push(ele.files[i])
}
};
this.removeFile = function(fileToRemove){
var indexOfFile = this.files.indexOf(fileToRemove);
this.files.splice(indexOfFile, 1);
};
this.clearFiles = function(){
this.files = [];
};
// Return files array
this.getFiles = function(){
return this.files;
};
}]);
Then just simply use it in your controller/directive:
.controller(['ocFileUpload', function(ocFileUpload){
var ref = ocFileUpload.getFiles();
}]
I'm new to angular and firebase and I want to order my fetched list of contestants dynamically (3-way-binding).
app: https://shining-torch-1269.firebaseapp.com/#/ see it's not ordered!
code of the service for getting the data from firebase:
app.service('ContestantsService', function ($firebaseArray, FIREBASE_URI) {
var service = this;
var ref = new Firebase(FIREBASE_URI);
var contestants = $firebaseArray(ref);
service.getContestants = function () {
return contestants;
};
service.addContestant = function (contestant) {
contestants.$add(contestant);
};
service.updateContestant = function (contestant) {
contestants.$save(contestant);
};
service.removeContestant = function (contestant) {
contestants.$remove(contestant);
};
});
I have tried already the method var contestants = $firebaseArray(ref).orderBy('score')
Is there a way to order list as to be seen in the link above?
I get the solution.. here's the solution in code
I basically some parts and here a 4 step solution for the problem.
1 orderByChild
var query = ref.orderByChild('score').limitToLast(10); // added this one
var contestants = $firebaseArray(query);
2 FIREBASE RULES
{
"rules": {
".read": true,
".write": true,
".indexOn":"score",
"score": {
".indexOn": ".value"
}
}
}
3 angular filter for the reserve
app.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
};
});
4 use the filter in the view
<tr ng-repeat="contestant in main.contestants | reverse ">
Hi I am trying to update my data in angularJS(frontend) and laravel(backend).
But even I got an data from id, but I always make a new data.
I thought it cause my laravel code's fault, but not sure.
AngularJS service
app.factory('Assets', function($resource) {
return $resource('/api/assets/:assetId',{assetId:'#id'},{
update: {
method: 'PUT' // this method issues a PUT request
}
});
});
Controller (for data list)
app.controller('fixedAssetListCtrl',['$scope','$location','$rootScope','Assets', function($scope, $location, $rootScope,Assets){
$scope.assets = Assets.query();
$scope.goEdit = function(id){
Assets.query({assetId:id}.edit);
$location.path('/editFixedAsset').search({assetId:id});
}
}]);
Controller (for edit)
app.controller('fixedAssetEditCtrl',['$scope','$location','$rootScope','Assets',
function($scope, $location, $rootScope, Assets){
var edit_id=$location.path('/editFixedAsset').search();
var assetId=parseInt(edit_id.assetId);
//window.alert($rootScope.assetId.ID);
$scope.editassets = Assets.query({assetId});// getting data
// console.log($scope.editassets);
$scope.asset = {};
$scope.updateFixedAsset =function(asset){
var faData ={
detailAssetCode:$scope.detailAssetCode,
detailDescription:$scope.detailDescription,
detailParchaseDate:$scope.detailParchaseDate,
detailSoldDate:$scope.detailSoldDate
} //end of faData
Assets.update({assetId},asset);
}
}]);
Laravel Routes
Route::group(array('prefix' => '/api'), function () {
Route::resource('assets', 'AssetController');
});
Controller
public function show($id){
$assets = FAModel::find($id);
$response[] = [
'detailAssetCode' => $assets->AssetCode,
'detailDescription' => $assets->Description,
'detailPurchaseDate' => $assets->PurchaseDate,
'detailoldDate' =>$assets->SoldDate,
];
return Response::json($response, 200);
}
public function update($id){
$FAModelObj = new FAModel;
$fixedAssetData = FAModel::find($id);
$FAModelObj->AssetCode = Input::json('detailAssetCode');
$FAModelObj->Description = Input::json('detailDescription');//need to be JSON object??
$FAModelObj->PurchaseDate = Input::json('detailPurchaseDate');
$FAModelObj->SoldDate = Input::json('detailSoldDate');
$FAModelObj->push();
return Response::json(array('success'=>true));
}
Can you find the problem??
How can I change to update the data? Thanks.
The problem is that you create a new instance to save data instead of existing one. That's why new row is created in database. Your code should be like that.
public function update($id){
$fixedAssetData = FAModel::find($id);
$fixedAssetData->AssetCode = Input::json('detailAssetCode');
$fixedAssetData->Description = Input::json('detailDescription');//need to be JSON object??
$fixedAssetData->PurchaseDate = Input::json('detailPurchaseDate');
$fixedAssetData->SoldDate = Input::json('detailSoldDate');
$fixedAssetData->push();
return Response::json(array('success'=>true));
}
Hope it will be useful for you.
I am having problems with my grid column widths in ng-grid. My issue is that I don't know ahead of time what the columns will be (they are retrieved from a service call at the same time as the values. My Json object has two properties in it. An array of strings for column names and then a 2d array for values. currently the columns do not size to fit the column headers. I understand the columns will resize to fit the row data but what if no results are returned. then you have a mess. I tried making it so I did not have to set grid options until I had my data but then get an undefined error. I saw another post where the solution for that was to use ng-if in the div tag so the grid does not load until you want it to. that did not work either as the grid still tried to load before the if statement was satisfied. Below is my code. any thoughts? also,my ng-if was like this: ng-if="contentAvailable". Also adding a screen shot. My expectations would be for a horizontal scrollbar to show up.
app.controller('mainController',function($scope,dataFactory){
$scope.contentAvailable = false;
$scope.gridOptions = {
columnDefs: 'columns',
showGroupPanel: true
};
$scope.url = 'http://example.com';
if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str) {
return this.indexOf(str) == 0;
};
}
$scope.Fetch = function () {
dataFactory.Fetch($scope.url,$scope.config).success(function (blah) {
var result = $scope.transformJsonDataSet(blah);
})
.error(function (error) {
$scope.result = error;
});
};
$scope.transformJsonDataSet = function (ds) {
var tmp = angular.fromJson(ds);
var fieldNames = tmp.FieldNames;
var fieldValues = tmp.FieldValues;
var columns = [];
for (var i = 1; i < fieldNames.length; i++) {
if (fieldNames[i].startsWith('DECODE_') == false) {
columns.push({ field: fieldNames[i], displayName: fieldNames[i], cellClass: 'headerStyle'});
}
}
$scope.columns = columns;
$scope.contentAvailable = true;
return ds;
};
});
app.factory('dataFactory', ['$http', function ($http) {
var dataFactory = {};
dataFactory.Fetch = function (url,config) {
return $http.get(url,config);
};
return dataFactory;
}]);
There's no $scope.columns declared in your controller?
Try defining a $scope.columns variable and assign it an empty variable:
app.controller('mainController',function($scope,dataFactory){
$scope.contentAvailable = false;
$scope.columns = [];
$scope.gridOptions = {
columnDefs: 'columns',
showGroupPanel: true
};
/* */