In AngularJS , I am trying to make and editable TreeView from JSON data. I want to get a nodevalue from JSON and edit it using forms. While editing, the original JSON should change immediately. I created below plunkr for this but not getting an idea, how to achieve this.
Plunkr
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
//replace: true,
scope: {collection: '='},
//controller: 'TreeController',
//bindToController: true,
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
app.directive('member', function ($compile) {
var linkerfunc = function(scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
$compile(collectionSt)(scope, function(cloned, scope) {
element.append(cloned);
});
scope.ShowDetailsFunc = function() {
console.log('in function scope showdetails')
scope.ShowDetailsCtrlFunc(element,event);
}
}
return {
restrict: "E",
//replace: true,
scope: {member: '=', ShowDetailsCtrlFunc : '&'},
template: "<li><span ng-click=ShowDetailsCtrlFunc($event)>{{member.NodeName}}</span></li>",
controller: 'MainCtrl',
//controllerAs: 'MainCtrl',
//bindToController: true,
link: linkerfunc
}
})
app.controller('MainCtrl', function ($scope,$timeout) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "NodePath": "1", "children": [ { "NodeName": "mychild", "NodePath": "1.1", "children": [ { "NodeName": "chld1", "NodePath": "1.1.1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
//console.log($scope.testdata) //This one is showing
$scope.changename = $scope.testdata[0].children[0].children[0].NodeName;
}
$timeout(function(){ $scope.Intialfunc(); }, 1000)
$scope.ShowDetailsCtrlFunc = function(event) {
console.log("in function ShowDetailsCtrlFunc");
//event.stopImmediatePropagation();
};
});
I tried angular.copy but that needs the portion of JSON data in a $scope variable and updates that variable as expected.
But my JSON data going to be a huge and i dont know how to update it without using variable. Please help.
You can use ng-change to perform an action when an input changes:
<input type="text" ng-model="changename" ng-change="inputChange()">
Then, just create a function in your controller that updates what you need it to:
$scope.inputChange = function() {
// do stuff here
$scope.testdata[0].children[0].children[0].NodeName = $scope.changename;
// write object to JSON
var JSONstring = $scope.testdata.toJSON();
}
I would also recommend looking at ng-model-options and changing the debounce settings so that you aren't constantly toJSONing with every keystroke.
http://plnkr.co/edit/dPWsowm4Puwajgk6CfvA?p=preview
Related
In angularjs I have been trying to access main controller $scope variable in my directive isolated scope.
My html code,
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata'>{{testdata}}</collection>
</div>
</body>
My directive code,
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {collection: '='},
//controller: 'TreeController',
//bindToController: true,
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
app.directive('member', function ($compile) {
var linkerfunc = function(scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
$compile(collectionSt)(scope, function(cloned, scope) {
element.append(cloned);
});
}
return {
restrict: "E",
replace: true,
scope: {member: '=', ShowDetailsCtrlFunc : '&'},
template: "<li><span ng-click=ShowDetailsCtrlFunc()>{{member.NodeName}}</span></li>",
controller: 'MainCtrl',
//controllerAs: 'MainCtrl',
//bindToController: true,
link: linkerfunc
}
})
My controller code,
app.controller('MainCtrl', function ($scope) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "children": [ { "NodeName": "mychild", "children": [ { "NodeName": "chld1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
console.log($scope.testdata) //This one is showing
}
$scope.ShowDetailsCtrlFunc = function(element,event) {
console.log("in function ShowDetailsCtrlFunc"); // coming to this fucntion on click.
console.log($scope.testdata) // but this one is not showing . shows undefined.
//event.stopImmediatePropagation();
};
});
it is coming to the function but not showing the controller $scope. I have created a plunker ,
plunker
Please help me. I have been struggling for many days.
You need to add a function expression to both of your directives' isolate scopes in order to properly call a function in your parent scope. Taking your original code, it should look something like this:
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
//replace: true, <- this is deprecated and should no longer be used
scope: {
collection: '=',
onMemberClick: '&'
},
template: "<ul><member ng-repeat='member in collection' member='member' on-click='onMemberClick()'></member></ul>"
}
})
app.directive('member', function ($compile) {
return {
restrict: "E",
//replace: true, <- this is deprecated and should no longer be used
scope: {
member: '=',
onClick : '&'
},
template: "<li><span ng-click='onClick()'>{{member.NodeName}}</span></li>"
}
});
And you original html should look something like this:
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata' on-member-click='ShowDetailsCtrlFunc ()'>{{testdata}}</collection>
</div>
</body>
Argument binding
If you would like to actually know which member was clicked, you'll need to bind arguments to your function calls.
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
scope: {
collection: '=',
onMemberClick: '&'
},
template: "<ul><member ng-repeat='member in collection' member='member' on-click='onMemberClick({member: member})'></member></ul>"
}
})
app.directive('member', function ($compile) {
return {
restrict: "E",
scope: {
member: '=',
onClick : '&'
},
template: "<li><span ng-click='onClick({member: member})'>{{member.NodeName}}</span></li>"
}
});
Html:
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata' on-member-click='ShowDetailsCtrlFunc (member)'>{{testdata}}</collection>
</div>
</body>
MainCtrl:
app.controller('MainCtrl', function ($scope) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "children": [ { "NodeName": "mychild", "children": [ { "NodeName": "chld1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
console.log($scope.testdata) //This one is showing
}
$scope.ShowDetailsCtrlFunc = function(member) {
console.log("In show details function");
console.log(member);
};
});
plunker
Lets Begin with the query you have. You want to call a function from link inside the directive even when the scope is isolated. It's simple you want to access parent scope.
Here's the code you can use to access parent scope.
scope.$parent.yourFun();
//or you can do this by the code give below.
//Inside Directive Use this.
scope:{
fun:"&"
},
//now you can call this function inside link
link:function(scope, element,attr){
scope.fun();
}
In your app.directive, just put scope : false.
Your directive will use the same scope as his parent scope.
I am trying to create a toolbar and toolbaritem directives. Basically toolbar is a container and holds toolbaritems(buttons in this case). The information about the number of toolbaritems, text on each item and their behavior upon click are external to toolbaritem directive and are associated using a model. I'm able to set the text on the toolbaritem, but unable to add behavior dynamically. Below is the code:
index.html
<body ng-app="app" ng-controller="myActionCtrl">
<action-toolbar actions="actionItems"></action-toolbar>
</body>
Controller.js
app.controller('myActionCtrl', ['$scope', function ($scope) {
var actionItems = [{ "name": "Post", "action": "Post()" },
{ "name": "Recall", "action": "Recall()" },
{ "name": "Signoff", "action": "Signoff()" },
{ "name": "Attach", "action": "Attach()" }
];
$scope.actionItems = actionItems;
$scope.Post = function () { alert('Post clicked'); }
$scope.Recall = function () { alert('Recall clicked'); }
$scope.Signoff = function () { alert('Signoff clicked'); }
$scope.Attach = function () { alert('Attach clicked'); }
}]);
directive.js
app.directive('actionToolbar', function () {
return {
restrict: 'E',
scope: {actions:'='},
template: '<div ng-repeat="item in actions">'+
'<action-item name={{item.name}} action={{item.action}}> </action-item>' + '</div>'
};
});
app.directive('actionItem', function () {
return {
restrict: 'E',
scope:{name:'#',action:'#'}, //will discuss on this below
template: "<button>{{name}}</button>",
link: function (scope, element) {
element.bind('click', scope.action);
}
}
});
With the above code, it gives me an error that "Undefined is not a function". I assume this because scope.action is read as a string in the actionItem isolated scope.
With this error, tried modifying the isolatedscope param to scope: {name:'#',action:'&'}, but then it complaints
[$parse:syntax] Syntax Error: Token 'item.action' is unexpected, expecting [:] at column 3 of the expression [{{item.action}}] starting at [item.action}}]
Unless you have a reason to put your Post, Recall, Signoff, and Attach functions on the scope, don't. The objects in your actionItems array can store direct references to those functions like this
app.controller('myActionCtrl', ['$scope', function ($scope) {
var actionItems = [{ "name": "Post", "action": Post },
{ "name": "Recall", "action": Recall },
{ "name": "Signoff", "action": Signoff },
{ "name": "Attach", "action": Attach }
];
$scope.actionItems = actionItems;
function Post () { alert('Post clicked'); }
function Recall () { alert('Recall clicked'); }
function Signoff () { alert('Signoff clicked'); }
function Attach () { alert('Attach clicked'); }
}]);
Your action attribute on your toolbar directive should be an expression that will invoke the function you want to invoke like this
app.directive('actionToolbar', function () {
return {
restrict: 'E',
scope: {actions:'='},
template: '<div ng-repeat="item in actions">'+
'<action-item name={{item.name}} action="item.action()">' +
'</action-item>' +
'</div>'
};
});
Then in your actionItem directive you can bind to that expression in your isolate scope object with the & strategy, and set that function as a callback function in your linking function like this
app.directive('actionItem', function () {
return {
restrict: 'E',
scope:{
name:'#',
action:'&'
},
template: "<button>{{name}}</button>",
link: function (scope, element) {
element.bind('click', scope.action)
}
}
});
Working fiddle here http://jsfiddle.net/r9rve69p/
I'm trying to create a directive to load select items(from server) for the lists that I need using a Key for each list, but it seams that model is bound before the items get a chance to load, and the desired item is not get selected, any suggestion?
Services:
arsinServices.factory('GeneralProperties', ['$resource', function ($resource) {
return $resource('/api/GeneralProperties/');
}]);
Directive:
directives.directive('ngarGpSelect', ['GeneralProperties',
function (GeneralProperties) {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '='
},
controller: function ($scope, $attrs, GeneralProperties) {
$scope.gpItems = GeneralProperties.query({ key: $attrs.gpkey });
},
templateUrl: '/templates/gp-select.html',
require: 'ngModel',
}
}]);
Template:
<select ng-options="gp.Index as gp.Name for gp in gpItems"></select>
Contoller:
arsinControllers.controller('RequestEditCtrl', ['$scope', 'request',
function ($scope, request) {
$scope.request = request;
}
View:
<ngar-gp-select gpKey="RequestType" ng-model="request.RequestTypeIndex"/>
Update
Here is a fiddle thanks to #Nix.
Update 2:
If I replace the line which set gpItems in directive controller whith something like this:
$scope.gpItems = [{ Index: 1, Name: 'option1' }, { Index: 2, Name: 'option2' }, { Index: 3, Name: 'option3' }];
It will work as expected, and the item get selected.
You have yet to really show an "issue"... I dont understand what you are trying to do but if you initialize $scope.request in the fiddle Nick will be selected.
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.request = {
RequestTypeIndex: 1
}
$scope.RequestType = 1
}
Then in your directive you need to use the promise(although your example should work):
function (GeneralProperties) {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '='
},
controller: function ($scope, $attrs, GeneralProperties) {
GeneralProperties.query({ key: $attrs.gpkey }).then(function(data){
$scope.gpItems = data
}, function(error){
alert(error);
})
},
templateUrl: '/templates/gp-select.html',
require: 'ngModel',
}
}]);
Utilize a callback from the query:
GeneralProperties.query({ key: $attrs.gpkey }, function(data) {
$scope.gpItems = data;
});
Finally I located the problem, it was because of items from my service. the Index property was of type string instead of int, I changed the type in my server side interface and it worked. I have had found similar cases in SO mentioning this but I overlooked them. I hope it will help someone else.
I have just came up with a directive that loads a dropdown box according to a list coming from an API call ($resource).
Controller:
App.controller(
'TestCtrl', [
'$scope', 'countriesFactory',
function($scope, countriesFactory){
/* Call API */
countriesFactory().then(function(data){
$scope.countryList = data;
});
}])
The API call returns:
{"country":[{"code":"ABW","label":"Aruba"},{"code":"AFG","label":"Afghanistan"},{"code":"AGO","label":"Angola"}]}
Template:
<input-select model-ref-list="countryList"></input-select>
Directive:
App
.directive("inputSelect"
, function() {
var Template =
'<select ng-options="item.label for item in modelRefList" required></select>';
return {
restrict: 'EA',
template: Template,
scope: {
modelRefList: '='
},
link: function(scope){
console.log(scope.modelRefList);
}
};
}
);
First of all: I simplified a lot the overall issue, so that it looks that the directive is completely overkill in that situation, but in the end, it is not :D.
Problem: My console.log is always undefined.
I made a bit of research and realized that I needed to play with promises to wait for my country list to appear to be actually given to the directive.
So I tried modifying my controller and not use the result of the API call promise, but directly the resource itself:
New Controller:
App.controller(
'TestCtrl', [
'$scope', 'countriesFactory',
function($scope, countriesFactory){
/* Call API */
$scope.countryList = resourceAPICall();
}])
But still undefined :/.
How can I pass direclty the resource (containing the promise I can then use to defer the load of the select) to the directive?
SOLUTION FOR ANGULARJS 1.2:
Directive:
App
.directive("inputSelect"
, function() {
var Template =
'<select ng-options="item.label for item in modelRefList" required></select>';
return {
restrict: 'EA',
template: Template,
scope: {
modelRefList: '='
},
link: function(scope){
scope.modelRefList.$promise.then(function(data){
console.log(data);
}
};
}
);
To pass a API call result to a directive, you need to pass its resource and play with its promise inside the directive itself.
Thanks everybody for the help.
Here we simulated async call factory by using wrapper with $q.
We changed modelReflist to modelRefList
added ng-model="item" to template
HTML
<div ng-controller="TestCtrl">
<input-select model-ref-list="countryList"></input-select>
</div>
JS
var App = angular.module('myModule', ['ngResource']);
App.controller(
'TestCtrl', [
'$scope', 'countriesFactory',
function ($scope, countriesFactory) {
/* Call API */
countriesFactory.resourceAPICall().then(function (data) {
$scope.countryList = data.country;
console.log($scope.countryList);
});
}])
App.$inject = ['$scope', 'countriesFactory'];
App.directive("inputSelect", function () {
var Template = '<select ng-model="item" ng-options="item.label as item.label for item in modelRefList" required></select>';
return {
restrict: 'EA',
template: Template,
scope: {
modelRefList: '='
},
link: function (scope) {
console.log(scope.countryList);
}
};
});
App.factory('countriesFactory', ['$resource', '$q', function ($resource, $q) {
var data = {
"country": [{
"code": "ABW",
"label": "Aruba"
}, {
"code": "AFG",
"label": "Afghanistan"
}, {
"code": "AGO",
"label": "Angola"
}]
};
var factory = {
resourceAPICall: function () {
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
Demo Fiddle
modelReflist needs to be fully camel-cased in your directive scope. modelRefList.
I have a custom directive that uploads a file to amazon and contains a callback(onComplete).
When the callback is complete, I would like to attach a value to the $scope of the controller in which the directive is created. In this case, the scope of Invite.
Both Invite and fineUploader extend the same angular module.
HTML(simplified):
<div ng-controller="Invite" class="apply">
<div fine-uploader ng-switch-when="file" upload-extensions="jpg,jpeg,png,gif"></div>
</div>
Directive:
directive('fineUploader', function() {
return {
restrict: 'A',
require: '?ngModel',
link: function($scope, element, attributes, ngModel) {
$scope.uploader = new qq.s3.FineUploader({
debug: true,
element: element[0],
request: {
endpoint: 'ballentines-bar-project.s3.amazonaws.com',
accessKey: 'AKIAIPT6J4T6XZXV3VWA'
},callbacks: {
onComplete: function(id, fileName, responseJSON) {
if (responseJSON.success === true) {
console.log(this.getKey(id));
console.log($scope);
$scope.test = this.getKey(id);
}
}
},
signature: {
endpoint: '/s3/'
},
iframeSupport: {
localBlankPagePath: '/success.html'
},
retry: {
enableAuto: true // defaults to false
},
deleteFile: {
enabled: false
},
text: {
uploadButton: '<p>Upload File</p>'
},
template:
'<div class="qq-uploader">' +
'<div class="qq-upload-button btn btn-info">{uploadButtonText}</div>' +
'<ul class="qq-upload-list" ><h2>Your files</h2></ul>' +
'</div>',
});
}
};
}).
Controller
controller('Invite', function(
$scope,
$localStorage,
$http
){
$scope.$storage = $localStorage.$default({
"formkey": "1MRSAWTRl5-PnVEoy3tD63BL3q_v2mnAhtqa9bdZk-zg",
"draftResponse": "[]",
"pageHistory": "0",
});
$scope.liking = liking;
$scope.post = function(){
$http.post('/signup.php', $scope.$storage).
success(function(data, status, headers, config){
console.log(data);
});
};
FB.Event.subscribe('edge.create',
function(href, widget) {
liking = true;
}
);
})
You either need to pass the items from the parent scope to the directive (through isolated scope). Or, do as #MaximShoustin says and remove the isolated scope from your directive.
So, option 1:
scope: { directiveProperty: '=nameOfAttributeThatContainsParentProperty' },
Or, option 2:
Remove the isolated scope declaration scope: {}, from the directive. This will allow the directive to extend the scope of it's containing scope.
I would try at least two options:
[1]
change scope: {}, in directive to:
`scope: { test: '#'},`
This makes the test method visible in the private scope.
[2]
The second option try removing the isolate scope a.e: scope: {},