How do I implement a cancellable editable field in Angular? - javascript

In my model, I have a field that I need several controls to bind to. One of these controls is a text box. The text box should not directly edit the field, but instead it should allow the user to type and then either commit the changes or cancel. If any other operation occurs then it should overwrite any changes in the text field. One constraint is that there are other UI components that change the value and do not have access to the local scope.
I implemented the desired behavior with a directive: http://jsfiddle.net/fLxjjmb7/3/
It works as intended, but I feel that there must be a better way to do this. Any ideas?
<div ng-app="app" ng-controller="controller">
<div>{{foo}}</div>
<button ng-click="increment()">increment</button>
<button ng-click="decrement()">decrement</button>
<br />
<div shadow="foo">
<input type="text" ng-model="foo" />
<button ng-click="commit()">update</button>
<button ng-click="cancel()">cancel</button>
</div>
</div>
var app = angular.module('app', []);
var controller = app.controller('controller', function ($scope) {
$scope.foo = 1;
$scope.increment = function () { ++$scope.foo; };
$scope.decrement = function () { --$scope.foo; };
});
var directive = app.directive('shadow', function() {
return {
scope: true,
link: function(scope, el, att) {
scope.$parent.$watch(att.shadow, function (newValue) {
scope[att.shadow] = newValue;
});
scope.commit = function() {
scope.$parent[att.shadow] = angular.copy(scope[att.shadow]);
};
scope.cancel = function() {
scope[att.shadow] = angular.copy(scope.$parent[att.shadow]);
};
}
};
});

Think you are complicating this a bit :)
View:
<div ng-controller="ShadowController">
<h1>{{foo}}</h1>
<div>
<button ng-click="increment()">increment</button>
<button ng-click="decrement()">decrement</button>
</div>
<div>
<input type="text" ng-model="tempFoo" />
<button ng-click="commit()">update</button>
<button ng-click="cancel()">cancel</button>
</div>
</div>
Controller:
.controller('ShadowController', function ($scope) {
$scope.foo = 1;
$scope.increment = function () {
++$scope.foo;
$scope.cancel();
};
$scope.decrement = function () {
--$scope.foo;
$scope.cancel();
};
$scope.commit = function () {
$scope.foo = parseFloat($scope.tempFoo);
};
$scope.cancel = function () {
$scope.tempFoo = $scope.foo;
};
$scope.cancel();
});
An even fancier way would be to keep track of changes to the temporary value and only enable commit/cancel buttons if there is a diff between original and temp.
View:
<div ng-controller="ShadowControllerAdv">
<h1>{{data.original}}</h1>
<div>
<button ng-click="increment()">increment</button>
<button ng-click="decrement()">decrement</button>
</div>
<div>
<input type="text" ng-model="data.edit" />
<button ng-click="commit()" ng-disabled="!state.hasChanged">update</button>
<button ng-click="reset()" ng-disabled="!state.hasChanged">cancel</button>
</div>
</div>
Controller:
.controller('ShadowControllerAdv', function ($scope) {
var _dataWatcher;
$scope.data = {
original: 1
};
$scope.state = {
hasChanged: false
};
function _startWatcher() {
_dataWatcher = $scope.$watch('data.edit', function (newValue, oldValue) {
if (newValue !== oldValue) {
$scope.state.hasChanged = true;
} else {
$scope.state.hasChanged = false;
}
}, true);
}
function _stopWatcher() {
if (!_dataWatcher) {
return;
}
_dataWatcher();
}
$scope.reset = function () {
_stopWatcher();
$scope.data.edit = $scope.data.original;
$scope.state.hasChanged = false;
_startWatcher();
}
$scope.commit = function () {
_stopWatcher();
$scope.data.original = parseFloat($scope.data.edit);
$scope.reset();
}
$scope.increment = function () {
$scope.data.original = $scope.data.original + 1;
$scope.reset();
};
$scope.decrement = function () {
$scope.data.original = $scope.data.original - 1;
$scope.reset();
};
$scope.reset();
});

Related

Angular - Service to calculate sum of 2 scopes

Learning how to use services but really having a hard time. Trying to make a simple calculator to provide the sum of 2 scope value (2 inputs). My functions seems to be taking the values and sending them okay. But my result scope dosent seem to be changing or updating at all when I fire my functions.
Here what I got so far:
Jsfiddle:
https://jsfiddle.net/9bz4Lwxa/528/
HTML:
<body ng-app="myApp" ng-controller="myController">
Val1: <input ng-model="val1"> {{val1}} <br>
Val2: <input ng-model="val2"> {{val2}} <br><br>
Total result: <input ng-model="result"> {{result}} <br>
<input type="button" ng-click="calculate_controller()" value="Calculate"><br>
</body>
SCRIPT:
angular.module('myApp', [])
.controller('myController', function($scope, businessRepository) {
$scope.result = businessRepository.result_service();
$scope.val1 = "";
$scope.val2 = "";
$scope.calculate_controller = function() {
console.log("Hello");
businessRepository.calculate_service($scope.val1, $scope.val2)
};
})
.factory('businessRepository', function($http) {
return {
result_service: function(data){
console.log("test function launched", data)
return data;
},
calculate_service: function(val1, val2){
var result = val1 + val2;
this.result_service(result);
}
};
});
You need to return data from the service businessRepository and need to consume it in the controller myController. Also you need to convert val1 and val2 to Number. Here in snippet I have used + to achieve it.
angular.module('myApp', [])
.controller('myController', function ($scope, businessRepository) {
$scope.calculate_controller = function () {
$scope.result = businessRepository.calculate_service(+$scope.val1, +$scope.val2)
};
})
.factory('businessRepository', function ($http) {
return {
result_service: function (data) {
return data || 0;
},
calculate_service: function (val1, val2) {
var result = val1 + val2;
//Return the data from
return this.result_service(result);
}
};
});
angular.module('myApp', [])
.controller('myController', function($scope, businessRepository) {
$scope.calculate_controller = function() {
$scope.result = businessRepository.calculate_service(+$scope.val1, +$scope.val2)
};
})
.factory('businessRepository', function($http) {
return {
result_service: function(data) {
return data;
},
calculate_service: function(val1, val2) {
var result = val1 + val2;
return this.result_service(result);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController">
Val1:
<input ng-model="val1">
<br>Val2:
<input ng-model="val2">
<br>
<br>Total result:
<input ng-model="result">{{result}}
<br>
<input type="button" ng-click="calculate_controller()" value="Calculate">
<br>
</div>
You need to change the calculate_service by :
calculate_service: function (val1, val2) {
var result = parseInt(val1) + parseInt(val2);
return this.result_service(result);
}
And calculate_controller by :
$scope.calculate_controller = function () {
$scope.result = businessRepository.calculate_service($scope.val1, $scope.val2)
};

Increment count with click of button while decrementing another count and vice versa without reseting to default score

I have three .js files here. This code works fine except the count scores don't correlate with the other clicking of the button. I would like to add these requirements to my code as well: Each service will store the counter that displays above/below the buttons as a property on the service. Each service will have at least 3 methods: increment, decrement, and reset, which resets the counter to 100.
The counter property in the services must NOT be directly manipulated by a controller - you should create public methods in your services to perform the operations instead, which are called by the controller.
//home.js
var app = angular.module('MyApp');
app.controller("HomeController", ['$scope', 'RedService', 'BlueService', function ($scope, $rs, $bs) {
$scope.title = "The Mighty Clicker";
$scope.redOutput = 100;
$scope.blueOutput = 100;
$scope.countRed = function () {
$rs.countUp++;
$scope.redOutput = $rs.countUp;
$bs.coundDown--;
$scope.blueOutput = $bs.coundDown;
}
$scope.countBlue = function () {
$bs.countUp++;
$scope.blueOutput = $bs.countUp;
$rs.countDown--;
$scope.redOutput = $rs.countDown;
}
}]);
//blueService.js
var app = angular.module("MyBlueService", []);
app.service("BlueService", function () {
this.countUp = 100;
this.coundDown = 100;
})
//redService.js
var app = angular.module("MyRedService", []);
app.service("RedService", function() {
this.countUp = 100;
this.countDown = 100;
})
here is my HTML code
//html
<div class="row">
<div class="col-xs-12 buttons">
<h1 class='title'>{{title}}</h1>
<button class="btn red" ng-click="countRed()">Button</button>
<h1>{{redOutput}}</h1>
<br><br><br><br><br><br>
<button class="btn blue" ng-click="countBlue()">Button</button>
<h1>{{blueOutput}}</h1>
</div>
</div>
enter image description here
Not exactly sure what the rules are but from what I understand I made a plunker:
https://plnkr.co/edit/lrMgM8lcm0FtCIQbZLlf?p=preview
It looks like the code works without needing change except for the typos :D
$scope.blueOutput = blueService.countDown;
You mispelled countDown with coundDown
As #gyc mentioned in his post, there was a typo. I have created the plunkr with the same design architecture (3 modules and each of them with a service).
RedApp and BlueApp modules are added to MainApp module as dependencies and used their in myApp's controller.
var myApp = angular.module("MainApp", ["RedApp", "BlueApp"]);
myApp.controller("MyAppController", ["$scope", "RedAppService", "BlueAppService", function($scope, $rs, $bs) {
$scope.title = "The Mighty Clicker";
$scope.redOutput = 100;
$scope.blueOutput = 100;
$scope.countRed = function() {
$rs.countUp++;
$scope.redOutput = $rs.countUp;
$bs.countDown--;
$scope.blueOutput = $bs.countDown;
}
$scope.countBlue = function() {
$bs.countUp++;
$scope.blueOutput = $bs.countUp;
$rs.countDown--;
$scope.redOutput = $rs.countDown;
}
}]);
var redApp = angular.module("RedApp", []);
var blueApp = angular.module("BlueApp", []);
redApp.service("RedAppService", function() {
this.countUp = 100;
this.countDown = 100;
});
blueApp.service("BlueAppService", function() {
this.countUp = 100;
this.countDown = 100;
});
As I understand You need to have two buttons, if click first -> it counter gets up and counter second one gets down. I have done it by starting from Your code, but I've simplified the solution, so both services has only one counter and I set services directly into scope to avoid many assignments and variables. Check my working example:
//home.js
var app = angular.module('MyApp',[]);
app.controller("HomeController", ['$scope', 'RedService', 'BlueService', function ($scope, $rs, $bs) {
$scope.title = "The Mighty Clicker";
//services to scope directly
$scope.$rs=$rs;
$scope.$bs=$bs;
$scope.countRed = function () {
$rs.count++;
$bs.count--;
}
$scope.countBlue = function () {
$bs.count++;
$rs.count--;
}
}]);
//blueService.js
app.service("BlueService", function () {
this.count = 100;//single counter
})
//redService.js
app.service("RedService", function() {
this.count = 100; //single counter
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp" ng-controller="HomeController" class="row">
<div class="col-xs-12 buttons">
<h1 class='title'>{{title}}</h1>
<button class="btn red" ng-click="countRed()">Button</button>
<h1>{{$rs.count}}</h1>
<button class="btn blue" ng-click="countBlue()">Button</button>
<h1>{{$bs.count}}</h1>
</div>
</div>
EDIT. AFTER COMMENT.
//home.js
var app = angular.module('MyApp',[]);
app.controller("HomeController", ['$scope', 'RedService', 'BlueService', function ($scope, $rs, $bs) {
$scope.title = "The Mighty Clicker";
//services to scope directly
$scope.$rs=$rs;
$scope.$bs=$bs;
$scope.countRed = function () {
$rs.increment();
$bs.decrement();
}
$scope.countBlue = function () {
$bs.increment();
$rs.decrement();
}
$scope.reset=function(){
$bs.reset();
$rs.reset();
}
}]);
//return constructor
//create for DRY
app.service("Counter",function(){
return function(){
this.count = 100;//single counter
this.increment=function(){
this.count++;
};
this.decrement=function(){
this.count--;
};
this.reset=function(){
this.count=100;
};
};
});
//blueService.js
app.service("BlueService", ["Counter",function ($cf) {
return new $cf;
}]);
//redService.js
app.service("RedService", ["Counter",function ($cf) {
return new $cf;
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp" ng-controller="HomeController" class="row">
<div class="col-xs-12 buttons">
<h1 class='title'>{{title}}</h1>
<button class="btn red" ng-click="countRed()">Button</button>
<h1>{{$rs.count}}</h1>
<button class="btn blue" ng-click="countBlue()">Button</button>
<h1>{{$bs.count}}</h1>
<button ng-click="reset()">Reset counters</button>
</div>
</div>

Angular service with different angular app

Here i am using angular service.In my case i am getting value for first app but not for second .please help me .
thank you.
here is my html:-
<div ng-app="mainApp" ng-controller="CalcController">
<p>Enter a number: <input type="number" ng-model="number" />
<button ng-click="multiply()">X<sup>2</sup></button>
<p>Result: {{result}}</p>
</div>
<div ng-app="myApp2" ng-controller="myController2">
<p>Enter a number: <input type="number" ng-model="numberSecond" />
<button ng-click="multiplyValue()">X<sup>2</sup></button>
<p>Result: {{result2}}</p>
</div>
here is js:-
angular.module('myReuseableMod',[]).factory('$myReuseableSrvc',function()
{
// code here
var factory = {};
factory.multiply = function(a)
{
return a * a
}
return factory;
});
var mainApp = angular.module("mainApp", ['myReuseableMod']);
mainApp.controller('CalcController',['$scope', '$myReuseableSrvc',function($scope, $myReuseableSrvc) {
alert("inside controller");
$scope.multiply = function()
{
alert("hello1");
$scope.result = $myReuseableSrvc.multiply($scope.number);
}
}]);
var mainApp2 = angular.module("myApp2", ['myReuseableMod']);
mainApp.controller('myController2',['$scope', '$myReuseableSrvc',function($scope, $myReuseableSrvc) {
alert("inside controller");
$scope.multiplyValue = function()
{
alert("hello1");
$scope.result2 = $myReuseableSrvc.multiply($scope.numberSecond);
}
}]);
Your 'myController2' is in the wrong app
mainApp.controller('myController2'
Should be:
mainApp2.controller('myController2'
EDIT:
Ah yes I see the problem. You cannot use ng-app twice like that. If you want what you are trying to achieve which is multiple applications you have to 'bootstrap' the second one:
plunk here:
http://plnkr.co/edit/qfllLO9uy6bC5OLkHnYZ?p=preview
angular.module('myReuseableMod',[]).factory('$myReuseableSrvc',function() {
var factory = {};
factory.multiply = function(a) {
return a * a
}
return factory;
});
var mainApp = angular.module("mainApp", ["myReuseableMod"]);
mainApp.controller('CalcController', ['$scope', '$myReuseableSrvc',function($scope, $myReuseableSrvc) {
$scope.multiply = function() {
$scope.result = $myReuseableSrvc.multiply($scope.number);
}
}]);
var mainApp2 = angular.module("mainApp2", []);
mainApp2.controller("MyController2", function($scope, $myReuseableSrvc) {
console.log('init B');
$scope.multiplyValue = function() {
$scope.result2 = $myReuseableSrvc.multiply($scope.numberSecond);
}
});
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById("myDiv2"), ["mainApp2", "myReuseableMod"]);
});
This is a good post to read:
http://www.simplygoodcode.com/2014/04/angularjs-getting-around-ngapp-limitations-with-ngmodule/

Controller hiding $scope.var in another controller with angular

I have 2 controllers. I want to make a simple toggle where if a function is called it hides code in the other controller. Here is what I have...
Angular:
var app = angular.module('plunker', []);
app.factory('data', function () {
var fac = [];
fac.hideIt = function (hide) {
console.log(hide)
if (hide != null)
return true;
else
return false;
};
return fac;
});
app.controller('MainCtrl', function($scope, data) {
$scope.name = 'World';
console.log(data.hideIt()); //its false
$scope.hide = data.hideIt();
});
app.controller('SecCtrl', function($scope, data) {
$scope.hideAbove = function () {
var hide = true;
data.hideIt(hide);
console.log(data.hideIt(hide)) //now it is true
}
});
HTML:
<div ng-controller="MainCtrl">
<div ng-if="hide == false">
<p>Hello {{name}}!</p>
</div>
</div>
<div ng-controller="SecCtrl">
<div ng-click="hideAbove()">CLICK HERE </div>
</div>
Link to Plunkr:
http://plnkr.co/edit/zOAf5vGMTAd8A10NGiS1?p=preview
Is there no way to use a controller to hide code that is in another controller?
You dont need to use $emit, $rootScope.$broadcast or something else
in your code you asked to the factory the value of a local variable, you cant updates it because each time you start the method a new variable was created;
Here is a working example, hope it will help you
http://plnkr.co/edit/jBc3DJnzXNJUiVVwRAPw?p=preview
The factory declare some useful methods like updates and gets hide value
app.factory('HideFactory', function () {
var prototype = {};
var hide = false;
prototype.getDisplayMode = function() {
return hide;
}
prototype.hideIt = function (val) {
hide = typeof val == 'boolean' ? val : false;
return val;
};
return prototype;
});
The controllers declare some variables which are a reference to the factory methods
app.controller('MainCtrl', ['$scope', 'HideFactory',function($scope, HideFactory) {
$scope.name = 'World';
$scope.isHide = HideFactory.getDisplayMode;
}]);
app.controller('SecCtrl', ['$scope', 'HideFactory', function($scope, HideFactory) {
$scope.isHide = HideFactory.getDisplayMode;
$scope.hideAbove = function() {
HideFactory.hideIt(true);
}
}]);
And the html, the ng-if directive call the isHide method, linked to the getDisplayMode method of the factory
<body>
<div ng-controller="MainCtrl">
<div ng-if="!isHide()">
<p>Hello {{name}}!</p>
</div>
</div>
<div ng-controller="SecCtrl">
<div ng-click="hideAbove()">CLICK HERE </div>
</div>
</body>
You're about halfway there with your factory, you have most of a setter but not a getter. Here's what I'd change.
Factory:
app.factory('data', function () {
var fac = [];
var state = false;
fac.hideIt = function (hide) {
state = hide;
};
fac.hidden = function() {
return state;
}
return fac;
});
Controller:
app.controller('MainCtrl', function($scope, data) {
$scope.name = 'World';
$scope.hide = data.hidden;
});
HTML:
<div ng-controller="MainCtrl">
<div ng-hide="hide()">
<p>Hello {{name}}!</p>
</div>
</div>
Forked Plunker
please see here: http://plnkr.co/edit/3NEErc0zUpXlb1LarXar?p=preview
var app = angular.module('plunker', []);
app.factory('data', function() {
var fac = [];
var _hide = {};
hideIt = function(hide) {
console.log("from fact " + hide)
if (hide !== null) {
_hide.state = true;
return _hide;
} else
_hide.state = false;
return _hide;
};
return {
fac: fac,
hideIt: hideIt,
hide: _hide
};
});
app.controller('MainCtrl', function($scope, data) {
$scope.name = 'World';
//console.log(data.hideIt()); //its false
$scope.hide = data.hide;
});
app.controller('SecCtrl', function($scope, data) {
$scope.hideAbove = function() {
var hide = true;
data.hideIt(hide);
}
});
HTML:
<div ng-if="hide.state != true">
<p>Hello {{name}}!</p>
</div>
</div>
<div ng-controller="SecCtrl">
<div ng-click="hideAbove()">CLICK HERE</div>
</div>
</body>
You want to use $emit.
function firstCtrl($scope){
$scope.$on('someEvent', function(event, data) { console.log(data); });
}
function secondCtrl($scope){
$scope.$emit('someEvent', [1,2,3]);
}

Integrate paper.js into angular.js

I have this problem with integrating paper.js into angular.js. My issue is that I simply doesn't know where to load the paperscript part.
I have a view and a controller and I can't really place the code in the controller for what I know and it wont load if it is placed in the view.
My controller look like this:
var hash = window.location.hash.split('/');
$scope.status = 'Loading';
var request = jQuery.ajax(system.server, {
'url': system.server,
'headers': {
'Accept': 'application/json',
'Request': system.request + '/hcbs/allocations/resident/' + hash[3],
'Username': system.username,
'Password': system.password
}
})
.always(function(response)
{
signature.constructor();
switch(response.status)
{
case 200:
case undefined:
$scope.$apply(function()
{
$scope.status = '';
var res = JSON.parse(response);
$scope.hcbsAllocations = res.hcbsServiceAllocations;
$scope.change = function(allocationId)
{
console.log(jQuery('[data='+ allocationId +'][name=startDate]').val());
console.log(jQuery('[data='+ allocationId +'][name=endDate]').val());
}
$scope.submit = function(allocationId)
{
// Validate dates
// Make signature popup
$scope.signaturePop = true;
}
});
break;
case 404:
console.log('error ' + response.status);
$scope.$apply(function()
{
$scope.status = 'Problems loading ressource at the moment';
});
default:
console.log(response);
}
});
My view looks like this:
<div id="app-content">
<div consumer />
<h1>Consumer</h1>
<div ng-show="status">
<div class="notice">
<p>{{status}}</p>
</div>
</div>
<form name="attendance">
<table class="hcbs">
<tr ng-repeat="allocation in hcbsAllocations">
<td class="first"><h3>{{allocation.type}}</h3></td>
<td class="middle">
<input type="datetime-local" name="startDate" ng-model="startDate" ng-change="change(allocation.id)" data="{{allocation.id}}" placeholder="Choose start date" />
<input type="datetime-local" name="endDate" ng-model="endDate" ng-change="change(allocation.id)" data="{{allocation.id}}" placeholder="Choose end date" />
</td>
<td class="last">
<span class="btn" class="submit" data="{{allocation.id}}" ng-click="submit(allocation.id)"><i class="icon-ok icon-large"></i></span>
</td>
</tr>
</table>
</form>
</div>
The directive approach to this:
Check out the jsFiddle
HTML:
<canvas id="canvas" resize draw></canvas>
directive:
app.directive('draw', function () {
return {
restrict: 'A',
link: function postLink(scope, element, attrs) {
var path;
var drag = false;
function mouseUp(event) {
//Clear Mouse Drag Flag
drag = false;
}
function mouseDrag(event) {
if (drag) {
path.add(new paper.Point(event.layerX, event.layerY));
path.smooth();
}
}
function mouseDown(event) {
//Set flag to detect mouse drag
drag = true;
path = new paper.Path();
path.strokeColor = 'black';
path.add(new paper.Point(event.layerX, event.layerY));
}
function initPaper() {
paper.install(window);
paper.setup('canvas');
}
element.on('mousedown', mouseDown).on('mouseup', mouseUp).on('mousemove', mouseDrag);
initPaper();
}
};
});
There is no need to load paperscript. You can use javascript directly
<body ng-controller="PaperController">
<canvas id="canvas" resize ng-mousedown="mouseDown($event)" ng-mousemove="mouseDrag($event)" ng-mouseup="mouseUp()"></canvas>
</body>
<script type="text/javascript">
function PaperController($scope){
var path;
var drag = false;
$scope.mouseUp = function(){
//Clear Mouse Drag Flag
drag = false;
};
$scope.mouseDrag = function(event){
if(drag){
path.add(new paper.Point(event.x, event.y));
path.smooth();
}
};
$scope.mouseDown = function(event){
//Set flag to detect mouse drag
drag = true;
path = new paper.Path();
path.strokeColor = 'black';
path.add(new paper.Point(event.x, event.y));
};
init();
function init(){
paper.install(window);
paper.setup('canvas');
}
}
</script>
The previous solutions doesn't work for me, and they are also with a lot of redundant code and IDs.
jsFiddle
I just have my element, suppose
<canvas draw></canvas>
And a simple directive like this one:
app.directive('draw', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var path;
paper.setup(element.get(0));
var tool = new paper.Tool();
tool.onMouseDown = function (event) {
path = new paper.Path();
path.strokeColor = 'black';
};
tool.onMouseDrag = function (event) {
path.add(event.point);
};
tool.onMouseUp = function (event) {
//nothing special here
};
}
};
});

Categories

Resources