Angular click event not working, what gives? - javascript

I'm building a silly little calculator in an attempt to learn AngularJS. I'm attempting to use ng-click trigger events to update the "screen" of my calculator. Here's my code:
var calcApp = angular.module('NodeCalc', []);
calcApp.controller('CalcController', ['$scope', function ($scope) {
$scope.memory = {
recall: function() {
console.log('memory recall');
},
clear: function() {
console.log('memory clear');
},
add: function(value) {
console.log('memory add');
}
}
$scope.buttons = {
memory: [
{text: 'mrc', action: $scope.memory.recall},
{text: 'm-', action: $scope.memory.clear},
{text: 'm+', action: $scope.memory.add},
]
};
}]);
<body ng-app="NodeCalc" ng-controller="CalcController">
<form class="calc">
<p class="calc-display">
<input type="text" name="res" id="res" value="0" class="calc-display-input" onfocus="this.blur()">
</p>
<p class="calc-row">
<button ng-repeat="button in buttons.memory" type="button" class="calc-button calc-button-gray" ng-click="{{button.action}}">{{button.text}}</button>
<button type="button" class="calc-button calc-button-red calc-button-big" onclick="a('/')">/</button>
</p>
So, I know it has to do with the way I'm attaching my memory functions to my scope object. Can I not use scope in this way on the ng-click directive? I'm not really sure how else to achieve my goal here. The buttons render correctly, but I get a huge angular error-barf in my console related to:
Error: [$parse:syntax] http://errors.angularjs.org/1.5.8/$parse/syntax?p0=%7B&p1=invalid%20key&p2=2&p3=%7B%7Bbutton.action%7D%7D&p4=%7Bbutton.action%7D%7D

ng-click expression should be ng-click="button.action()"
While using ng-* directive, one is not suppose to wrap it with mustache, passed expression will be parsed by angular
var calcApp = angular.module('NodeCalc', []);
calcApp.controller('CalcController', ['$scope',
function($scope) {
$scope.memory = {
recall: function() {
console.log('memory recall');
},
clear: function() {
console.log('memory clear');
},
add: function(value) {
console.log('memory add');
}
}
$scope.buttons = {
memory: [{
text: 'mrc',
action: $scope.memory.recall
}, {
text: 'm-',
action: $scope.memory.clear
}, {
text: 'm+',
action: $scope.memory.add
}, ]
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="NodeCalc" ng-controller="CalcController">
<form class="calc">
<p class="calc-display">
<input type="text" name="res" id="res" value="0" class="calc-display-input" onfocus="this.blur()">
</p>
<p class="calc-row">
<button ng-repeat="button in buttons.memory" type="button" class="calc-button calc-button-gray" ng-click="button.action()">{{button.text}}</button>
<button type="button" class="calc-button calc-button-red calc-button-big" onclick="alert('/')">/</button>
</p>

One idea is just calling a function defined on $scope in the ng-click (the normal way) and passing the $index from the ng-repeat.
ng-click="clicked($index)"
$scope.clicked = function(index) {
var fn = $scope.buttons.memory[index].action;
fn();
}

Related

How do I use an attribute directive inside an element directive template in angularjs?

I have a need to show a list of check boxes on my site listing products and other items. The checklist-model attribute directive works well for this because I can bind it to a list of items that relates to the items selected.
All this works fine when I simply use this code in my angular controller. However, I have several list boxes that need to be displayed the same way with the same "select all" and "select none" buttons for each list. I don't want to repeat this code and layout so I've created my own directive for the entire list.
The problem is when I use my own directive it doesn't bind correctly back to my data, the select all only works once, and the select none doesn't work at all.
I suspect it has something to do with how I'm passing the scope around, and the two directives are not working well together.
Why does this not work inside a directive?
Here is a jsfiddle: https://jsfiddle.net/fande455/m9qhnr9c/7/
HTML
<section ng-app="myApp" ng-controller="couponEditController">
<script type="text/ng-template" id="checkboxlist.template.html">
<div>
<div class="form-input form-list">
<label ng-repeat="item in valuelist | orderBy:value">
<input type="checkbox" checklist-model="model" checklist-value="item" /> {{item[value]}}
<br />
</label>
</div>
<button class="btn btn-default" style="float:left; margin-bottom: 5px;margin-left: 10px;margin-right:10px" ng-click="selectNone()">Select None</button>
<button class="btn btn-default" style="float:left; margin-bottom: 5px;" ng-click="selectAll()">Select All</button>
<div class="cleared"></div>
</div>
</script>
<div>
<checkboxlist model="coupon.Products" value="Name" valuelist="productsList"></checkboxlist>
</div>
</section>
JS
var myApp = angular.module('myApp', ['checklist-model']);
myApp.directive('checkboxlist', [function() {
return {
restrict: 'E',
templateUrl: 'checkboxlist.template.html',
controller: 'checkboxlistController',
scope: {
model: "=",
value: "#",
valuelist: "="
},
require: '^checklist-model'
}
}]);
myApp.controller('checkboxlistController', ['$scope', function($scope) {
$scope.selectAll = function() {
$scope.model = angular.copy($scope.valuelist);
};
$scope.selectNone = function() {
$scope.model = [];
};
}]);
myApp.controller('couponEditController', ['$scope', function($scope) {
$scope.coupon =
{"Id": 1,
"Name": "My Coupon",
"Products": []
}
;
$scope.productsList = [{
"Id": 1,
"Name": "Product 1"
}, {
"Id": 2,
"Name": "Product 2"
}];
}]);
From documentation:
Instead of doing checklistModelList = [] it is better to do
checklistModelList.splice(0, checklistModelList.length)
In your code you should do
$scope.selectAll = function() {
$scope.selectNone();
$scope.model.push.apply($scope.model, $scope.valuelist);
};
$scope.selectNone = function() {
$scope.model.length = 0;
};
Here's the updated fiddle: https://jsfiddle.net/m9qhnr9c/9/
The idea is not to replace the array with a new one, but modify only its elements.

Call Angular nested functions

I'm trying to call a nested function in Angular. I've formatted the functions in such a way in order to neaten up code, yet when invoking the function through an ng-click it doesnt seem to work.
In my case, a scope conflict occurs because the variable name is taken by the local scope, so I've named a controller and invoked it as a child property of the controller, but no success.
I've created a jsfiddle to demonstrate what I mean:
https://jsfiddle.net/838L40hf/16/
HTML:
<div class="InviteAppContainer" ng-app="InviteApp">
<div ng-controller="InviteController as cntrl">
<button ng-click="alert()">
Alert, standard
</button>
<div ng-repeat="invite in invites">
<button ng-click="cntrl.invite.alert(invite.name)">
Alert, {{invite.name}}
</button>
</div>
</div>
</div>
JS:
var InviteApp = angular.module('InviteApp',[])
.controller('InviteController', function($scope) {
$scope.invites = {
0 : {
"name":"invite 1",
},
1 :{
"name" : "invite 2"
}
};
$scope.invite = {
alert : function(name) {
alert(name);
}
};
$scope.alert = function() {
alert("alert2!");
};
});
If you're using the controller as syntax, you should be binding things to this instead of $scope if you wish to access them through the aliased controller name.
Simply changing the binding from $scope.invite to this.invite will do the trick.
https://jsfiddle.net/pL4wc10n/
You should use this
Javascript
var InviteApp = angular.module('InviteApp',[])
.controller('InviteController', function($scope) {
// controllerAs : cntrl
var that = this;
$scope.invites = {
0 : {
"name":"invite 1",
},
1 :{
"name" : "invite 2"
}
};
// USING THIS you have cntrl.function
that.invite = {
alert : function(name) {
alert(name);
}
};
$scope.alert = function() {
alert("alert2!");
};
});
var InviteApp = angular.module('InviteApp',[])
.controller('InviteController', function($scope) {
$scope.invites = {
0 : {
"name":"invite 1",
},
1 :{
"name" : "invite 2"
}
};
$scope.invite = {
alert : function(name) {
alert(name);
}
};
$scope.alert = function() {
alert("alert2!");
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="InviteAppContainer" ng-app="InviteApp">
<div ng-controller="InviteController as cntrl">
<button ng-click="alert()">
Alert, standard
</button>
<div ng-repeat="i in invites">
<button ng-click="invite.alert(i.name)">
Alert, {{i.name}}
</button>
</div>
</div>
</div>
What I edit:
<div ng-repeat="i in invites">
<button ng-click="invite.alert(i.name)">
Alert, {{i.name}}
</button>
</div>

reset ng-model from controller in ng-repeat

I am trying to create a list of editable inputs from a list of items. I want the user to be able to edit any of the items, but if they change there mind they can click a button and reset it back to the way it was.
So far I have everything working except the reset.
my html
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" ng-click="copy(item)"/>
<button ng-click="reset(item)">
x
</button>
</div>
{{list}}<br>
{{selected_item_backup}}
</div>
my controller
function TestController($scope) {
$scope.selected_item_backup = {};
$scope.list = [ { value: 'value 1' }, { value: 'value 2' }, { value: 'value 3' } ];
$scope.reset = function (item) {
// i know this wont work for many reasons, it is just an example of what I would like to do
item = $scope.selected_item_backup;
};
$scope.copy = function (item) {
angular.copy(item, $scope.selected_item_backup);
};
}
and here is a fiddle
http://jsfiddle.net/ryanmc/1ab24o4t/1/
Keep in mind that this is a simplified version of my real code. My objects will have many editable fields each. Also they are not indexed, and so the index cannot be relied on. I just want to be able to assign the original item on top of the new and have it replaced.
This is work solution jsfiddle
function TestController($scope) {
$scope.list = [{
value: 'value 1'
}, {
value: 'value 2'
}, {
value: 'value 3'
}];
var orgiinalList = [];
angular.forEach($scope.list, function(item) {
orgiinalList.push(angular.copy(item));
});
$scope.reset = function(index) {
$scope.list[index] = angular.copy(orgiinalList[index]);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" />
<button ng-click="reset($index)">
x
</button>
</div>
{{list}}
<br>
</div>
Changing your reset function so that it looks like this:
$scope.reset = function(index) {
$scope.list[index].value = "value "+ (index+1);
};
will make it so that your 'reset' buttons restore what would be those original values...
Angular controller:
function TestController($scope) {
$scope.selected_item_backup = {};
$scope.list = [{
value: 'value 1'
}, {
value: 'value 2'
}, {
value: 'value 3'
}];
$scope.reset = function(index) {
$scope.list[index].value = "value " + (index+1);
};
$scope.copy = function(item) {
angular.copy(item, $scope.selected_item_backup);
};
}
HTML:
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" ng-click="copy(item)" />
<button ng-click="reset($index)">
x
</button>
</div>
</div>
The values of your array are directly modeled by those inputs - therefore as a user types changes into those inputs they are directly manipulating the $scope.list array so you cant really use it as a reference... Hope that makes sense.
You can't use global functions as controllers in newer versions of angular but you can register your controllers in your application:
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" ng-click="copy($index,item)"/>
<button ng-click="reset($index,item)">
x
</button>
</div>
{{list}}<br>
{{selected_item_backup}}
function TestController($scope) {
$scope.selected_item_backup = [];
$scope.list = [ { value: 'value 1' }, { value: 'value 2' }, { value: 'value 3' } ];
$scope.reset = function (index) {
$scope.list[index].value = $scope.selected_item_backup[index];
};
$scope.copy = function (index,item) {
// backup the old value
$scope.selected_item_backup[index] = item.value;
};
}

How to make saved object immediately appear in a ng-repeat?

JS:
$scope.addPano = function () {
var Pano = AV.Object.extend("Panorama"),
pano = new Pano()
var json = {
'name': 'test3',
'index': 0,
'Type': 'real',
'version': 0,
'buildingCode': $scope.buildingId
}
pano.save(json, {
success: function(object) {
console.log('PANO: ', object)
$scope.building.pano.push(json)
$scope.$digest()
},
error: function(object, error) {
console.log('Failed to create new object, with error message: ' + error.message);
}
})
}
HTML:
<div ng-repeat="pano in building.pano">
<p><strong>{{pano.name}}</strong></p>
<div ng-repeat="panodata in pano.panoData">
<p>{{panodata.name}}</p>
</div> <a class="btn btn-default" href="javascript:;" ng-click="addPanodata(pano.objectId)">Add panodata</a> </div> <a class="btn btn-default btn-lg" href="javascript:;" ng-click="addPano()">Add pano</a>
</div>
Right now the only way to make it appear is with:
$scope.building.pano.push(json)
$scope.$digest()
I thought Angular did this automatically?
I think you can try this type But In your code either you reload data after save method calling successfully then you can easily appear easily..
function myctrl($scope){
$scope.data =[{name:'abc'}];
$scope.pushdata = function(name){
$scope.data.push({name});
$scope.name ='';
}
}
<html ng-app=''>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
</head>
<body ng-controller='myctrl'>
<input type='text' name='name' ng-model='name'/>
<button name='clickme' type=submit ng-click='pushdata(name)'>save</button>
<div ng-repeat='obj in data'>
{{obj.name}}
</div>
</body>
</html>
The official way to do this is to write your pano service to support angular. So where you call the success callback in the service:
.factory('pano', function() {
return {
save: function(x, options) {
setTimeout(function() {
options.success(123);
}, 1000);
}
};
});
You should wrap it in a $scope.$apply so angular has an opportunity to run dirty-checking:
.factory('pano', function($rootScope) {
return {
save: function(x, options) {
setTimeout(function() {
$rootScope.$apply(function() {
options.success(123);
});
}, 1000);
}
};
});
The same applies for the error handling callback.
Note: The builtin angular services, like $http, $q, $timeout do this automatically.

How to add a todo from another Controller

I have a todo list in AngularJS that looks like this
.controller('TodoCtrl', function ($scope) {
$scope.todos = [
{text:'Ask smth on Stackoverflow', done:false},
{text: 'Resolve this', done:false}
];
$scope.getTotalTodos = function () {
return $scope.todos.length;
};
$scope.addTodo = function () {
$scope.todos.push({text:$scope.formTodoText, done:false});
$scope.formTodoText = '';
};
$scope.clearCompleted = function () {
$scope.todos = _.filter($scope.todos, function(todo){
return !todo.done;
});
};
})
And I would like to add a Todo (with a text, and a boolean 'done') from another controller that is launched when I click a button.
How can I do that ?
A big THANKS to who will help me
Typically services are used to pass information back and forth. Create a service and store your TODO list inside there. Inject that service into both controllers. Each controller can now act on the items in the list
I will append Scotts answer with some shorted Code.
Like he said, the best is to use a Service ;)
The Service:
.factory('todoService', function() {
var todolist = [];
return {
getTodoList: function() {
return todolist;
}
addTodo: function(todo) {
todolist.push(todo);
},
getTotalTodos: function() {
return todolist.length;
},
// some other
}
});
Now you can inject the service into any controller via
.controller('TodoCtrl', function ($scope, todoService)
and then you can call the functions of the service in the controller, e.g.
$scope.addTodo = function () {
todoService.addTodo({text:$scope.formTodoText, done:false});
$scope.formTodoText = '';
};
By using Angular Services:
I've made a simple demo.
Hope this helps.
(function() {
var app = angular.module("myApp", []);
// myService service.- This service contains an array, called «todos» wich contains data.
app.service("myService", function() {
return {
todos: [{
"text": "Ask smth on Stackoverflow",
"done": false
}, {
"text": "Resolve this",
"done": false
}]
};
});
// Add the dependecy in the controller.
app.controller("Controller", ["$scope", "myService",
function($scope, myService) {
$scope.title = "TODOS";
// This function returns the data stored in the service.
$scope.getTodos = function() {
return myService.todos;
}();
$scope.getTotalTodos = function() {
return myService.todos.length;
};
// This functions contains the object with the values from the form.
$scope.addTodo = function(model) {
myService.todos.push({
text: model.text,
done: model.done
});
$scope.model.text = "";
};
}
]);
})();
<html data-ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>Demo</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body data-ng-controller="Controller">
<h3>{{title}}</h3>
<form id="myForm" ng-submit="addTodo(model)">
<label>
Todo
<input type="text" data-ng-model="model.text" />
</label>
<br />
<label>
Done
<input type="checkbox" data-ng-model="model.done" />
</label>
<br/>
<button type="submit">Add</button>
<hr />
<ul>
<li data-ng-repeat="todo in getTodos">
{{todo.text}} ({{todo.done}})
<input type="checkbox" data-ng-model="todo.done" />
</li>
</ul>
</form>
</body>
</html>
Update: Using the service in multiple controllers.
(function() {
var example = angular.module("starter", [])
example.service("todoService", function() {
return {
todos: [],
addTodo: function($text, $classe) {
this.todos.push({
text: $text,
done: false,
});
}
};
});
example.controller('nationsLeaguesCtrl', function($scope, todoService) {
$scope.randomNationsLeagues = function() {
var text = "Text 1";
todoService.addTodo(text, null);
};
})
example.controller('statsCtrl', function($scope, todoService) {
$scope.randomStats = function() {
var text = "Text 2";
todoService.addTodo(text, null);
};
})
example.controller('specialCtrl', function($scope, todoService) {
$scope.randomSpecial = function() {
var text = "Text 3";
todoService.addTodo(text, null);
};
})
example.controller('TodoCtrl', function($scope, todoService) {
$scope.getTodos = function() {
return todoService.todos;
}();
$scope.getTotalTodos = function() {
return todoService.todos.length;
};
$scope.clearCompleted = function() {
$scope.todos = _.filter($scope.todos, function(todo) {
return !todo.done;
})
};
});
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="starter">
<button class="button button-full button-light" ng-controller="nationsLeaguesCtrl" ng-click="randomNationsLeagues()">Nations & Leagues</button>
<button class="button button-full button-light" ng-controller="statsCtrl" ng-click="randomStats()">Stats</button>
<button class="button button-full button-light" ng-controller="specialCtrl" ng-click="randomSpecial()">Special</button>
<div ng-controller="TodoCtrl">
<ul>
<li ng-repeat="todo in getTodos">{{todo.text}}
<input type="checkbox" name="checkboxG1" id="checkboxG1" ng-model="todo.done" class="css-checkbox" />
<label for="checkboxG1" class="css-label" style="font-family:checkboxFont; color:#ffffff;"><span class="done-{{todo.done}}"></span>
</label>
</li>
</ul>
</div>
</body>

Categories