I am trying to implement a solution to sort a table by clicking its headers, using AngularJS.
I found a good example after doing a Google search: https://scotch.io/tutorials/sort-and-filter-a-table-using-angular
I am able to see the up and down arrows, but the table does not sort when I click them.
I think the problem resides in how the JSON object is formatted in my situation. I have not been able to figure it out, and I am hoping that with the information that I am providing on this post, I can get some help to understand what I am doing incorrectly.
Here is a copy of the JavaScript:
(function (define, angular) {
'use strict';
define(function () {
var opportunityController = function ($scope, Metadata, Factory) {
var vm = this;
//set the default sort type
vm.sortType = 'Products';
//set the default sort order
vm.sortReverse = false;
Factory.Data(caller.sp, caller.filter).then(function (payload) {
var data = angular.fromJson(payload.data).Table;
ProcessData(data);
});
function ProcessData(data) {
if (angular.isDefined(data)) {
var counter = 0;
vm.products = [];
vm.productsSet = FindByAsObjectArray(function (x) {
return (x.TypeName == "Product");
}, data);
for (var index = 0, length = vm.productsSet.length; index < length; index++) {
vm.products[index] = {
data: vm.productsSet[index]
};
}
}
}
};
return ['$scope','Metadata','Factory',opportunityController];
});
})(define, angular);
I got it to work, final version: https://jsfiddle.net/itortu/nhhppf53/
Many thanks.
You are using controllerAs
ng-controller="Company:OpportunityController as opportunity"
therefor you have to reference sortType and sortReverse in your ng-click like so:
ng-click="opportunity.sortType = 'Products'; opportunity.sortReverse = !opportunity.sortReverse"
Related
I'm trying to make a generic code to make it in a simple way for next level purpose. Please find the commented code which I've made. But, its not working.
var app = angular.module('myApp', []);
app.factory('mainFactory',function($http){
return {
getData: function() {
return $http.get("data.json");
}
};
});
app.controller('mainCtrl', function($scope,$http,mainFactory){
var data = mainFactory.getData();
if(angular.isDefined(data)) {
data.success(function(d,s){
// I want this commented out code for the four lines defined below.
/*var a = [{name:"imagesArray"},{name:"taskArray"},{name:"courseArray"},{name:"newsArray"}];
for(var i = 0; i < a.length; i++) {
$scope.a[i].name = d.a[i].name ? d.a[i].name : [];
}*/
// I dont want this number of lines.
$scope.imagesArray = d.imagesArray ? d.imagesArray : [];
$scope.taskArray = d.taskArray ? d.taskArray : [];
$scope.courseArray = d.courseArray ? d.courseArray : [];
$scope.newsArray = d.newsArray ? d.newsArray : [];
});
}
});
If it can be simplified further, please let me know
You can use this for better way to define scope property, edit your code like below:
data.success(function(d,s){
var keyCollection = ["imagesArray","taskArray","courseArray","newsArray"];
keyCollection.forEach(function(key){
$scope[key] = d[key];
});
});
or if your project are also using lodash(or underscore).You can simply do this(just further , don't import lodash or underscore just because this one, no need cost):
// outside, create advance function
var advancePick = _.partialRight(_.pick, "imagesArray", "taskArray", "courseArray", "newsArray");
data.success(function(d,s){
// just beauty like this
_.extend($scope, advancePick(d));
});
Change $scope.a[i].name = d.a[i].name ? d.a[i].name : []; to
$scope[a[i].name] = d[a[i].name] || [];
In Javascript, you can access properties two ways :
1)
item.property
2)
item["property"]
Thomas already gave you the right answer.
If you want to simplify it further, you can get rid of the definition of your "var a" array by making an object. Then you can enumerate each public property using :
for(var propertyName in yourObject) {
// propertyName is is the name of each property
// you can get the value using: yourObject[propertyName ]
}
Happy coding!
var a = ["imagesArray","taskArray","courseArray","newsArray"];
for(var i = 0; i < a.length; i++) {
$scope[a[i]] = d[a[i]] ? d[a[i]] : [];
}
with the name version
var a = [{name:"imagesArray"},{name:"taskArray"},{name:"courseArray"},{name:"newsArray"}];
for(var i = 0; i < a.length; i++) {
$scope[a[i].name] = d[a[i].name] ? d[a[i].name] : [];
}
A last solution and the shortest most of all!
angular.merge($scope, d);
EDIT : this will work only if d is properly initialized before.
Other simplifications not related to those 4 lines:
you don't need $http injected into your controller, and you don't
need to test the result of the call to getData(): it returns a
promise, take that as given.
Also use .then(...) rather than the deprecated .success(...).
You
might however want to add .catch(...) onto the promise to handle or
at least log errors, but if just to log errors you can put that inside .getData() and keep the controller short.
Here's how those changes might look:
var app = angular.module('myApp', []);
app.factory('mainFactory',function($http, $log){
return {
getData: function() {
return $http.get("data.json")
.catch(function (e) { $log.error(e); });
}
};
});
app.controller('mainCtrl', function($scope,mainFactory){
mainFactory.getData()
.then(function(d){
// choose one of the other answers for the code here...
var keyCollection = ["imagesArray","taskArray","courseArray","newsArray"];
keyCollection.forEach(function(key){
$scope[key] = d[key];
});
});
});
When I load my json data from the server I need to have additional view only properties on json data. But thats not possible.
Then I thought about creating angularjs models from factories like:
'use strict';
angular.module('TGB').factory('TestViewModel', function () {
function TestViewModel(test) {
this.id = test.id;
this.schoolclassCode = test.schoolclassCode;
this.testType = test.type;
this.creationDate = test.date;
this.number = test.number;
this.isExpanded = false;
}
return (TestViewModel);
});
Where do you create these viewmodels in angular?
At the moment I have this method in my Controller , call it there and assign the result to $scope.testViewModels = toTestListViewModel(tests)
function toTestListViewModel(tests)
{
var testListViewModel = [];
for (var i = 0; i < tests.length; i++) {
var testViewModel = new TestViewModel(tests[i]);
testListViewModel.push(testViewModel);
}
return testListViewModel;
}
Controller is a proper place to create your 'view model' that can be accessed using $scope.
.controller('sampleCtrl', function ($scope, testViewModel) {
$scope.vM = testViewModel;
}
To be honest I am a bit new to angularjs, so this may be problem with my fundamental understanding of angular, rather than angular-charts.
I have two controllers (PieItemsCtrl and PieCtrl) and I would like to communicate between them by using a factory service (called pieItems)
On the one hand the pieItems works as designed in the PieItemsCtrl.
ie:
$scope.slices = pieItems.list();
Whenever something changes in the pieItems service (ie another element is added), then the HTML is automatically updated via a repeater :
<div ng-repeat="(key, val) in slices">
However in the PieCtrl I have this line, and i would expect the pie chart to update automatically :
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
It seems to set these data values upon loading/initialisation of the PieCtrl and that's it. Whenever the pieItems data changes these scope values are not updated.
The source of the two controller and factory object are below. And I also made an unworkable fiddle, incase that helps
PieItemsCtrl :
app.controller('PieItemsCtrl', function($scope, $http, $rootScope, pieItems) {
$scope.slices = pieItems.list();
$scope.buttonClick = function($event) {
pieItems.add(
{
Name: $scope.newSliceName,
Percent: $scope.newSlicePercent,
Color: $scope.newSliceColor
}
)
}
$scope.deleteClick = function(item, $event) {
pieItems.delete(item);
}
}
)
PieCtrl :
app.controller("PieCtrl", function ($scope, $timeout, pieItems) {
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
});
pieItems :
app.factory('pieItems', function() {
var items = [];
var itemsService = {};
itemsService.add = function(item) {
items.push(item);
};
itemsService.delete = function(item) {
for (i = 0; i < items.length; i++) {
if (items[i].Name === item.Name) {
items.splice(i, 1);
}
}
};
itemsService.list = function() {
return items;
};
itemsService.labelsItems = function() {
var a = ['x', 'y'];
for (i = 0; i < items.length; i++) {
a.push(items[i].Name);
}
return a;
};
itemsService.data = function() {
var a = [50,50];
for (i = 0; i < items.length; i++) {
a.push(items[i].Percent);
}
return a;
};
return itemsService;
});
The controller doesn't notice when the value in your factory changes. To include your item-Array in an Angular digest-cycle, tell Angular to $watch that Array.
If you don't want to expose the Array, create a getter:
itemsService.get = function() { return items; }
Then you can include that getter in your $watch expression in your controller:
$scope.$watch(getItems, watcherFunction, true);
function getItems() {
return pieItems.get();
}
The getItems-Function gets called on digest cycle and fires the watcherFunction if the value changed and has the newData as argument. true as 3rd argument creates a deep watch.
function watcherFunction(newData) {
console.log(newData);
// do sth if array was changed
}
For more complex objets, you can use a $watchCollection.
I can't seem to get this right: I've got an array with categories (objects) and a post object:
var categories = $http.get('/api/categories').success(function (data) {
$scope.categories = data;
});
// The code below uses a Rails gem to transport data between the JS and Rails.
$scope.post = gon.post;
// Suffice to say the $scope.post is an object like so:
...
_id: Object { $oid="54f4706f6364653c7cb60000"}
_slugs: ["first-post"]
author_id: Object { $oid="54ef30d063646514c1000000"}
category_ids: [Object, Object]
...
As you can see the post object has a property category_ids which is an array whit all categories associated with this post. In my view (haml) I have the following:
%label
%input{"type" => "checkbox", "ng-model" => "cat.add", "ng-change" => "addCategory(cat)", "ng-checked" => "currentCat(cat)"} {{cat.name}}
As you can see, the ng-checked fires the currentCat() function:
$scope.currentCat = function (cat) {
for (var i = 0; i < cat.post_ids.length; i++){
if (cat.post_ids[i].$oid == $scope.post._id.$oid) {
return true;
}
}
};
The function above loops through the categories in the post (the category_ids property of the post object) and compares it with the parameter given. It works fine with existing categories. The problem appears when I dynamically add a new category and push it in the categories array:
$scope.addCatBtn = function () {
var category = $scope.cat;
$http.post('/api/categories', category).success(function (data) {
$scope.categories.push(data.category);
$scope.cat = '';
$scope.addCategory(data.category);
});
};
The new category does not appear 'checked' in the view. What am I missing?
Any thoughts would be appreciated.
EDIT: Adding addCategory function:
$scope.addCategory = function (cat) {
var found = false;
for (var i in $scope.post.category_ids) {
if (cat._id.$oid === $scope.post.category_ids[i].$oid) {
$scope.post.category_ids.splice(i, 1); // splice, not slice
found = true;
}
}
if (!found) { // add only if it wasn't found
$scope.post.category_ids.push(cat._id);
}
console.log($scope.post);
}
ngModel and ngChecked are not meant to be used together.
You should be fine to just use ngModel.
In Angular, I have in scope a object which returns lots of objects. Each has an ID (this is stored in a flat file so no DB, and I seem to not be able to user ng-resource)
In my controller:
$scope.fish = [
{category:'freshwater', id:'1', name: 'trout', more:'false'},
{category:'freshwater', id:'2', name:'bass', more:'false'}
];
In my view I have additional information about the fish hidden by default with the ng-show more, but when I click the simple show more tab, I would like to call the function showdetails(fish.fish_id).
My function would look something like:
$scope.showdetails = function(fish_id) {
var fish = $scope.fish.get({id: fish_id});
fish.more = true;
}
Now in the the view the more details shows up. However after searching through the documentation I can't figure out how to search that fish array.
So how do I query the array? And in console how do I call debugger so that I have the $scope object to play with?
You can use the existing $filter service. I updated the fiddle above http://jsfiddle.net/gbW8Z/12/
$scope.showdetails = function(fish_id) {
var found = $filter('filter')($scope.fish, {id: fish_id}, true);
if (found.length) {
$scope.selected = JSON.stringify(found[0]);
} else {
$scope.selected = 'Not found';
}
}
Angular documentation is here http://docs.angularjs.org/api/ng.filter:filter
I know if that can help you a bit.
Here is something I tried to simulate for you.
Checkout the jsFiddle ;)
http://jsfiddle.net/migontech/gbW8Z/5/
Created a filter that you also can use in 'ng-repeat'
app.filter('getById', function() {
return function(input, id) {
var i=0, len=input.length;
for (; i<len; i++) {
if (+input[i].id == +id) {
return input[i];
}
}
return null;
}
});
Usage in controller:
app.controller('SomeController', ['$scope', '$filter', function($scope, $filter) {
$scope.fish = [{category:'freshwater', id:'1', name: 'trout', more:'false'}, {category:'freshwater', id:'2', name:'bass', more:'false'}]
$scope.showdetails = function(fish_id){
var found = $filter('getById')($scope.fish, fish_id);
console.log(found);
$scope.selected = JSON.stringify(found);
}
}]);
If there are any questions just let me know.
To add to #migontech's answer and also his address his comment that you could "probably make it more generic", here's a way to do it. The below will allow you to search by any property:
.filter('getByProperty', function() {
return function(propertyName, propertyValue, collection) {
var i=0, len=collection.length;
for (; i<len; i++) {
if (collection[i][propertyName] == +propertyValue) {
return collection[i];
}
}
return null;
}
});
The call to filter would then become:
var found = $filter('getByProperty')('id', fish_id, $scope.fish);
Note, I removed the unary(+) operator to allow for string-based matches...
A dirty and easy solution could look like
$scope.showdetails = function(fish_id) {
angular.forEach($scope.fish, function(fish, key) {
fish.more = fish.id == fish_id;
});
};
Angularjs already has filter option to do this ,
https://docs.angularjs.org/api/ng/filter/filter
Your solutions are correct but unnecessary complicated. You can use pure javascript filter function. This is your model:
$scope.fishes = [{category:'freshwater', id:'1', name: 'trout', more:'false'}, {category:'freshwater', id:'2', name:'bass', more:'false'}];
And this is your function:
$scope.showdetails = function(fish_id){
var found = $scope.fishes.filter({id : fish_id});
return found;
};
You can also use expression:
$scope.showdetails = function(fish_id){
var found = $scope.fishes.filter(function(fish){ return fish.id === fish_id });
return found;
};
More about this function: LINK
Saw this thread but I wanted to search for IDs that did not match my search. Code to do that:
found = $filter('filter')($scope.fish, {id: '!fish_id'}, false);