Nested Controllers that use JSON array in AngularJS - javascript

I wanted to have nested controllers like this...
Controller 1 - This is the parent. It is populated from a JSON array that comes from a REST and uses ngRepeat.
Controller 2 - This is the child. It should get data from a REST call as well, but it needs to know which parent object it is under.
Here's a visual...
Parent 1
---Child 1
---Child 2
---Child 3
Parent 2
---Child 4
---Child 5
---Child 6
The children will be populated by calling a REST service and passing info about the parent.
Make sense?
Here is some HTML that I have structured...
<div ng-app="App">
<div ng-controller="spRisks">
<table width="100%" cellpadding="10" cellspacing="2" class="employee-table">
<tr ng-repeat="risk in Risks">
<td>
<table width="100%" cellpadding="10" cellspacing="2" class="employee-table">
<tr id="{{risk.Id}}RiskDesc">
<td>{{risk.Risk_x0020_Description}}</td>
</tr>
<tr id="{{risk.Id}}RiskControls">
<td>
<div ng-controller="spControls">
<table width="100%" cellpadding="10" cellspacing="2" class="employee-table">
<tr ng-repeat="control in Controls">
<td id="{{control.Id}}Control">{{control.Title}}</td>
<td>
<input type="radio" name="{{control.Id}}Answer" value="Yes">Yes
<input type="radio" name="{{control.Id}}Answer" value="No">No
</td>
<td>
<textarea id="{{control.Id}}Comment"></textarea>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>
And here is some code I have working that populates the parent controller...
<script>
function getDataWithCaml(listName, caml) {
var endpoint = "https://myteamsite.sharepoint.com//_api/web/lists/GetByTitle('" + listName + "')/GetItems(query=#v1)?#v1={\"ViewXml\":\"'" + caml + "'\"}";
return jQuery.ajax({
url: endpoint,
method: "POST",
headers: {
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"Accept": "application/json;odata=verbose",
"Content-Type": "application/json;odata=verbose"
}
});
}
var App = angular.module('App', ['ngRoute'])
App.controller('spRisks', function ($scope, $http) {
var caml = "<View><Query><Where><Eq><FieldRef Name='Owner'/><Value Type='Integer'><UserID/></Value></Eq></Where></Query></View>";
var ownerData = getDataWithCaml("Owners", caml);
ownerData.success(function (data) {
var arrayOfExpressions = [];
for (var i = 0; i < data.d.results.length; i++){
arrayOfExpressions.push(CamlBuilder.Expression().LookupMultiField("Process_x0020_Owner_x0020_Title").EqualTo(data.d.results[i].Title));
}
var newCaml = new CamlBuilder().View().Query().Where().All(arrayOfExpressions).ToString();
newCaml = newCaml.replace(/"/g, '\'');
var jsonData = getDataWithCaml("Risks", newCaml);
jsonData.success(function (jsonDataResults) {
$scope.$apply(function(){$scope.Risks = jsonDataResults.d.results;});
});
});
});
function replaceAll(string, find, replace) {
return string.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
</script>

One very cool thing that you should learn about angular is that if a controller is on a child element underneath another controller, then it inherits the values of the parent into its scope. It does this through prototypal inheritance. This is worth reading up on, but it basically means that any changes you make to the child scope will not modify the parent scope, so you aren't going to corrupt anything (you can still call methods on the parent scope which can do corrupting for you. And I'm sure you can imagine other ways of interfering with the parent scope, but hopefully you see my point). So if you have:
<div ng-controller="Alpha">
<div ng-controller="Beta"/>
<div ng-controller="Beta"/>
<div ng-controller="Beta"/>
</div>
Then you have three instances of the "Beta" controller, and all of them have a parent scope created by the function "Alpha." So let's say you write the controller for alpha, like so:
function Alpha($scope) {
$scope.title = "Snow White and the Seven Dwarves";
$scope.dwarves = ['Sleepy','Sneezy','Dopey','Bashful',
'Grumpy','Doc','Happy'];
}
Then, in all three instances of "Beta", you gain access to that parent:
function Beta($scope) {
// This will write out "Snow White and the Seven Dwarves".
console.log($scope.title);
}
Now, Beta can always manipulate title, and within its own scope, title will change without affecting the other siblings. If it added a dwarf to the collection, though, then.. well... another dwarf would appear.
ng-repeat is an easy way to create lots of child scopes and then initialize each with a variable:
<div ng-controller="Alpha">
<div ng-repeat="dwarf in dwarves"/>
</div>
So each repeated div tag has its own child scope with the variable dwarf already set. It also has access to the title and even the dwarves collection.
Neat.
You can also attach to it another controller:
<div ng-controller="Alpha">
<div ng-controller="Beta" ng-repeat="dwarf in dwarves"/>
</div>
In which case, in addition to access to dwarf, dwarves, and title, it also runs the function Beta() in order to initialize whatever it needs to initialize.
I've attached a jsfiddle for you, so you can play around with a simple example:
https://jsfiddle.net/p6e0wr1y/
I hope this helps with your specific implementation. If you need me to address your stuff specifically, I will, but I'd like this answer to be valuable to anyone who finds themselves in a similar predicament.

Related

AngularJS directive not binding attributes correctly

I'm relatively new to angular and this is my first involved directive and I'm a little lost. I'm trying to create a re-usable list of items on separate tabs in my app. The list acts the same with the exception of how the items are displayed (handled by separate partials). I'm passing in attributes of many different types into the scope and I'm trying several different things based on what I've been reading. No matter what I've tried so far, I'm still having issues with the attributes binding correctly.
Below is my code and I'll try to explain it as best as possible, hopefully someone can tell me where I went wrong. The only things that appear to have bound correctly are the strings, the objects and functions are missing.
UPDATE: Turns out I needed to bind $scope.currentPage to the directive scope. Now ng-repeat is running, but other parts of the page that require access to the controller scope aren't working. I've updated the code below and continue looking into how to give access to the template.
Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function(scope, element, attributes){
scope.$watch("query.value", function(){
scope.filterFunction(); //pretty sure this never gets called on search
});
}
return {
restrict: 'E',
replace: 'true',
templateUrl: 'partials/directives/list-tab.html',
link: linkFunction,
scope: {
filterFunction: "&filterFunction",
searchPlaceholder: "#searchPlaceholder",
pagedItems: "=pagedItems",
clickFunction: "&clickFunction",
classString: "#classString",
infoTemplate: "#template",
currentPage: "=currentPage"
}
}
});
index.html
//pagedCars is an array of nested objects that gets used by the template to display the information
//filterCars is a function
//carSelected is a function
<div class="available-items">
<item-list filter-function="filterCars" search-placeholder="Search Cars" paged-items="pagedCars" current-page="currentPage" click-function="carSelected" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
list-tab.html
<div class="form-group">
<div class="search-field">
<label for="searchField" id="searchLabel">Search</label><br/>
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}"/>
</div>
<table class="table table-hover>
<tbody>
//currentPage is on the controller scope there's a separate control that allows the user to page through the pagedItems by updating the currentPage which would be reflected here
<tr ng-repeat="item in pagedItems[currentPage]" ng-click="clickFunction($index) ng-class="classString">
<td ng-include="infoTemplate"></td>
</tr>
</tbody>
</table>
</div>
cars-template.html
<div class="row form-inline" id="{{item.carId}}">
<div class="col-md-2">
//this uses a method on the controller scope to format the url
<img ng-src="{{retrieveIcon(item.iconUrl)}}" height="75px" width="75px"/>
<div class="col-md-10">
<div details-pane" id="carDetails" ng-include="'partials/cars/car-full-details.html'"></div>
<div class="item-title">{{item.name}}</div>
//the rest is just a table with more information about the item. item.description, item.mileage, etc...
</div>
</div>
Try passing through your functions with their parentheses
<div class="available-items">
<item-list filter-function="filterCars()" search-placeholder="Search Cars" paged-items="pagedCars" click-function="carSelected()" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
Also FYI if your variable has the same name in your HTML as you want in your directive's scope you can just use the pass method. e.g.
scope: {
filterFunction: "&",
searchPlaceholder: "#",
pagedItems: "#",
clickFunction: "&",
classString: "#",
infoTemplate: "#template"
}

Retrieving the id from Model to Controller in Angular JS

i have a table which is having the data retrieved from an api call from my memberController which is displayed inside ng-repeat and its working fine.
I need each Business Name of the member list to link to a separate page(edit_form.html) and display the id value, so that i can pass this along with the api call to get only this particular member detail. So i have added ng-init in my edit form page which calls the function test_funct when the page loads and retrieve each persons id there. unfortunately i am unable to retrieve the id value inside the function.
HTML Template
<div class="page" data-ng-controller="memberController">
<table>
<thead >
<tr>
<th>Business Name</th>
<th>Contact Name</th>
<th>Trade Balance</th>
<th>Cash Balance</th>
<th>Telephone</th>
<th>Account Number </th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="member in details | filter:search">
<td>{{member.businessname}}</td>
<td>{{member.person}}</td>
<td>{{member.balance_trade}}</td>
<td>{{member.balance_cash}}</td>
<td>{{member.telephone}}</td>
<td>{{member.accountnumber}}</td>
</tr>
</tbody>
</table>
</div>
I have the following controller
function memberController($scope, $http, $cookieStore) {
var token = $cookieStore.get('token');
var conId = $cookieStore.get('Cont_Id');
var exId = $cookieStore.get('ex_Id');
var member_list = "http://www.vb.com/functions/member_list.html?exchangeid=" + exId +
"&contactid=" + conId + "&token=" + token;
$http.get(member_list)
.success(function(response) {
$scope.details = response;
});
$scope.test_funct = function(id) {
$scope.myid = id;
alert($scope.myid); // getting undefined in alert, i expect the id(eg:1123)
}
}
edit_form.html
<div class="page" data-ng-controller="memberController">
<div class="panel-body" ng-init="test_funct()"></div>
</div>
Please assist me on this. Thanks in advance.
There are 2 things going on here.
First, you should separate controllers for the different views, so you end up with something like this:
<div class="page" data-ng-controller="memberController">
<table>
<!-- your whole big table here -->
</table>
</div>
And your editing form as follows:
<div class="page" data-ng-controller="editController">
<div class="panel-body"></div>
</div>
Notice that you now have two distinct controllers - your "editController" and your "memberController".
The second question then becomes, how do you transfer the selected ID from the list view ("memberController") to the edit view ("editController").
There are 2 ways of doing that.
First, you could use a service shared between the controller:
.factory('SelectedId',function() {
return {};
});
And then in your "member" view, you would set it upon clicking:
{{member.businessname}}
Notice the ng-click, which then needs a function in the memberController and the injected service:
.controller('memberController',function($scope,SelectedId) {
$scope.setId = function(id) {
SelectedId.id = id;
};
});
While the editController retrieves it:
.controller('editController',function($scope,SelectedId) {
$scope.id = SelectedId.id;
});
The above option works well, especially for complex things like shopping carts. If all you are doing is passing an ID, I would just stick it in the URL:
{{member.businessname}}
So that the ID is part of the URL. You then can retrieve it in the "editController":
.controller('editController',function($scope,$routeParams) {
$scope.id = $routeParams.member;
});
assuming you are using ng-route, and your route would look like:
$routeProvider.when('/pages/edit_form/:member',{templateUrl:'/route/to/template.html',controller:'editController'});
In html do that
<td>{{member.businessname}}</td>
...
In app.js or where you define route do that
.when('/edit/:memberid',
{
templateUrl:'partials/edit.html',
controller:'editController'
})
In controller you have to take this id by doing that
app.controller("editController",function($routeParams,$scope){
$scope.memberid= $routeParams.memberid;
//Now use this member id to fetch all data
});

Angular controller not display data from JSON

I am using Angular and TingoDB (Mongo) inside Node Webkit for a single page application. However I have a strange problem that I have been unable to resolve.
When I use an object literal (option 2) the data displays correctly in the html page. However changing the code to return data from the database (option 1) the results do not appear on the html page. I have converted both styles of data into the a JSON string to prove consistency and then using the angular.fromJSON to return an object. Both methods return the same JSON string in console.log and before anyone asks I have either Option 1 or Option 2 commented out so both are not running concurrently.
I have copied the JSON string based on the data passed from TingoDB into the console.log and re-entered it into the code below to ensure that no differences between the 2 versions of the data existed without changing any other code, but the problem still persists.
Can anyone shed light on why this occurs and how to fix it?
var app = angular.module('myApp', []);
var Engine = require('tingodb')(),
assert = require('assert');
var db = new Engine.Db('./db', {});
var collection = db.collection("clean.db");
app.controller('tingoDataCtrl', ['$scope', function($scope) {
function getData(callback) {
//Option 1
collection.find().toArray(function(err, docs){
callback (JSON.stringify(docs));
});
//Option 2
var docs = [
{name:"tingo1", description:"56",_id:2},
{name:"tingo2", description:"33",_id:3},
{name:"tingo3", description:"22",_id:4},
{name:"tingo4", description:"76",_id:5},
{name:"tingo5", description:"99",_id:6}
];
callback (JSON.stringify(docs));
}
function info(b) {
// I'm the callback
console.log(b);
$scope.items = angular.fromJson(b)
}
getData(info);
}]);
And the Html
<body ng-app="myApp" id="main">
<div class="page page-data ng-scope">
<section class="panel panel-default" ng-controller="tingoDataCtrl">
<div class="panel-heading"><span class="glyphicon glyphicon-th"></span> Tingo Data</div>
<table class="table">
<thead>
<th class="col-md-4">
Name
</th>
<th class="col-md-8">
Description
</th>
<th class="col-md-8">
ID
</th>
<th></th>
<tr>
</tr>
</thead>
<tbody>
<!-- <tr class="reveal-animation" ng-repeat="item in items | filter:query"> -->
<tr ng-repeat="item in items | filter:query">
<td>{{item.name}}</td>
<td>{{item.description}}</td>
<td>{{item._id}}</td>
</tr>
</tbody>
</table>
</section>
</div>
<script src="js/tingo_problem.js"></script>
</body>
TingoDB is an asynchronous API which will work in the background without stop your app. This means that a syncronous code have no time to wait for an answer and in return it gives undefined.
In your case, you have done a asynchronous call, and it returns correctly the answer to the memory, but too late, the DOM have been updated with undefined already even if your javascript has the data (try console.log to see that it was there).
Angular has a way to be forced to update again the DOM with the new elements of the controller. it is called $apply. And the best way to use it to avoid unexpected behaviours is:
function info(b) {
// I'm the callback
console.log(b);
$scope.items = angular.fromJson(b);
if (!$scope.$$phase) {
$scope.$apply(); //forces update the view
}
}//$scope is NECESARY to be defined in the controler, avoid using it with "ControlerAs"

AngularJS - ng-hide with different ng-controller

here is my problem :
When I double click in a row of an array, I want to make disappear several parts of my page. The problem is...I don't figure out how to do this.
Basically, here is my html file:
<div id="mainWindow" ng-hide="hideAlias" ng-controller="mainWindow">
...
<div id="table{{workspace.name}}" class="table" ng-controller="table" >
<table id="mainTable" class="mainTable">
<tr class="tableHeader">
<th>AA</th>
<th>BB</th>
<th>Options</th>
</tr>
<tr class="tableRows" id ="{{row}}" ng-repeat = "row in rowstable">
<td ng-dblclick="dblclick()" >{{row.AA}} </td>
<td>{{row.server}} <input type="button" ng-click="clickOnDeleteServer(row.BB)" value="X" style="float:right"/></td>
<td>
<input type="button" ng-click="clickOnView()" value="View"></input>
<input type="button" ng-click="clickOnDelete(row.AA)" value="Delete"></input>
</td>
</tr>
</table>
</div>
...
</div>
I have tried to do this, inside the controller "table" :
$scope.dblclick = function(){
mainWindow.hideAlias=!mainWindow.hideAlias
}
The value of hideAlias change from false to true when I double click, and vice-versa. However, nothing happens on the page (nothing gets hidden)
Any clue ? Thanks a lot
EDIT :
controller definition :
function table($scope, $http, $route){
the variable hideAlias doesn't exist on the mainWindow controller.
What you want to do is share data between the mainWindow controller and the table controller.
There's a few ways of doing this, I'll show you one
Sharing data between controllers via Event emmiters
At high level, controller Table will send data to Controller MainWindow, and controller Table is child of controller MainWindow, so here's how you do it with event emmiters:
Controller mainWindow:
$scope.$on('EventFromTableController',function(data){
$scope.hideAlias = data.hideAlias;
});
This will tell controller mainWindow to listen for the EventFromTableController event. That event will contain data attached. In this case it will hold the hideAlias value from the child controller.
Now at controller Table:
Controller table:
var tableHideAlias = true; // initialize it to true or false
$scope.dblclick = function(){
//change the local tableHideAlias state
tableHideAlias = !tableHideAlias;
// emit the new hideAlias value
$scope.$emit('EventFromTableController',{hideAlias: tableHideAlias});
}
so when dblclick executes, it will send the new hideAlias value to the parent controller (mainWindow).
This way, ng-hide will have a hideAlias scope variable to evaluate it's state.
You can achieve this in simple way.
In your case controller, mainWindow is the parent controller and controller, table is the child controller.
Create an object for the parent controller and access or change the value from child controller on double click event.
var app = angular.module('myapp',[]);
app.controller('mainWindow',function($scope){
$scope.parentobj = {};
$scope.parentobj.hideAlias = false;
});
app.controller('table',function($scope){
$scope.dblclicktest=function()
{
$scope.parentobj.hideAlias=true;
}
});
and use the parent object scope in html to hide Div
<div id="mainWindow" ng-hide="parentobj.hideAlias" ng-controller="mainWindow">
Here is the JSFiddle
In the JSFiddle, double click on AA will hide the div.

ng-class directive call executed twice

Simple html:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="customerSelectedClass(customer)">
<td>
{{customer.Name}}
</td>
</tr>
</table>
In my controller - two functions to select customer and return proper class to highlight a table row:
$scope.customerSelectedClass = function (customer) {
if (customer == $scope.selectedCustomer) {
console.log('returing info for ' + customer.Name);
return "info";
}
return "";
};
$scope.selectCustomer = function (customer) {
console.log('selecting ' + customer.Name);
$scope.selectedCustomer = customer;
}
I noticed that when I click on a customer link, customerSelectedClass function executes twice. selectCustomer function on ng-click directive executes once, as it should. Angular is only included once on the page. I wonder if this is a bug in Angular or something that I am doing wrong?
Behind the scenes, angular is setting up a $watch on the function that is resolving the class name. Because angular uses dirty checking to see if there has been a change, this method will be called twice during the $digest cycle. This is ok.
I would suggest that you don't add this code the the controller though, because if you are managing many css classes, you could be adding a lot of unnecessary code. Try something like this instead:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="{'info': customer == selectedCustomer}">
<td>
{{customer.Name}}
</td>
</tr>
</table>
Then, there is no need for a controller function customerSelectedClass. This will only add the info class if the right-hand side of the : resolves to true. And there is no problem resolving the correct customer in the ng-repeat.
Hope this helps.

Categories

Resources