Two-way binding with range and number input in AngularJS - angularjs

I'm just starting to play around with AngularJS and trying to understand the binding technique. For starters, I tried to make a simple conversion calculator (dozens to pieces, pieces to dozens). That worked well, but when I tried to bind both a range input and a number input to the same model property the number input does not update when the range value is adjusted. I have a jsfiddle showing the behavior:
common javascript for broken and working fiddles:
var myApp = angular.module('myApp', []);
myApp.controller('CalcCtrl', function ($scope) {
var num = 0.0;
$scope.qty = new Quantity(12);
$scope.num = num;
});
function Quantity(numOfPcs) {
var qty = numOfPcs;
var dozens = numOfPcs / 12;
this.__defineGetter__("qty", function () {
return qty;
});
this.__defineSetter__("qty", function (val) {
qty = val;
dozens = val / 12;
});
this.__defineGetter__("dozens", function () {
return dozens;
});
this.__defineSetter__("dozens", function (val) {
dozens = val;
qty = val * 12;
});
}
BROKEN FIDDLE
html:
<div ng-controller="CalcCtrl">
<form>
<label for="pcs">Pieces:</label>
<input type="number" min="0" ng-model="qty.qty" size="20" id="pcs"
/>
<input type="range" min="0" max="100" ng-model="qty.qty" />
<br/>
<label for="numOfDozens">Dozens</label>
<input type="number" min="0" ng-model="qty.dozens" size="20"
id="numOfDozens" />
</form>
</div>
However, binding two number inputs to the same model property seems to work fine as shown in this fiddle:
WORKING FIDDLE
html:
<div ng-controller="CalcCtrl">
<form>
<label for="pcs">Pieces:</label>
<input type="number" min="0" ng-model="qty.qty" size="20" id="pcs"
/>
<input type="number" min="0" max="100" ng-model="qty.qty" />
<br/>
<label for="numOfDozens">Dozens</label>
<input type="number" min="0" ng-model="qty.dozens" size="20"
id="numOfDozens" />
</form>
</div>
Any ideas how to get a range and number input bound to a single model property in AngularJS? Thanks.

The problem here is that the input type="range" works with Strings and not with Numbers (while input type="number" only works with Numbers).
http://www.w3.org/wiki/HTML/Elements/input/range
The range state represents a control for setting the element's value to
a string representing a number.
If you add val = parseInt(val) as your first instruction on the qty setter it should work:
this.__defineSetter__("qty", function (val) {
val = parseInt(val);
qty = val;
dozens = val / 12;
});
jsfiddle: http://jsfiddle.net/bmleite/2Pk3M/2/

I think this solution is more generic.
myApp.directive('input', function() {
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if ('type' in attrs && attrs.type.toLowerCase() === 'range') {
ngModel.$parsers.push(parseFloat);
}
}
};
});
full explanation

You can solve it using ng-model-options. I changed jsfiddle and here is the code:
html:
<div ng-controller="CalcCtrl">
<form>
<label for="pcs">Pieces:</label>
<input type="number" min="0" ng-model="qty.qty" size="20" id="pcs" ng-model-options="{ getterSetter: true }"/>
<input type="range" min="0" max="100" ng-model="qty.qty" ng-model-options="{ getterSetter: true }"/>
<br/>
<label for="numOfDozens">Dozens</label>
<input type="number" min="0" ng-model="qty.dozens" size="20"
id="numOfDozens" ng-model-options="{ getterSetter: true }"/>
</form>
</div>
js:
var myApp = angular.module('myApp', []);
myApp.controller('CalcCtrl', function ($scope) {
var num = 1.0;
$scope.qty = {
qty:function (val) {
return arguments.length ? (num = parseFloat(val)) : num;
},
dozens: function (val) {
return arguments.length ? (num = val*12) : num/12;
}
};
});

Related

angularjs directive link function not binding data from Controller

I have a directive that makes use of jquery events on the element parameter of the link function, this directive has an input that is binding to a value that is obtained from the main controller of the page, passed through nested directives in a isolated scope , but when changing the value in the input is not reflected in the original object from controller.
The object has the following structure:
Invoice 1:
- Product 1
- Product 2
Invoice 2:
- Product 3
- Product 4
When I change the amount of the invoice, the value is updated in the main controller, but when I change the amount of the product the change is not reflected.
This is my directive, what you should do is that when the user clicks on the value an input appears to be able to edit the value of the model:
eFieldTemplate.html
<div>
<div ng-if="IsMouseIn">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
<div ng-if="IsMouseOut" ng-click="OnMouseClick()">
{{value}}
</div>
<div ng-if="MouseClick">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
eFieldDirective.js
angular.module("appDirectives").directive("eField", function () {
return {
restrict: "E",
templateUrl: "eFieldTemplate.html",
scope: {
value: "="
},
controller: function ($scope) {
$scope.IsMouseOut = true;
$scope.IsMouseIn = false;
$scope.MouseClick = false;
$scope.OnMouseEnter = function () {
if (!$scope.MouseClick) {
$scope.IsMouseOut = false;
$scope.IsMouseIn = true;
$scope.MouseClick = false;
}
}
$scope.OnMouseLeave = function () {
if (!$scope.MouseClick) {
$scope.IsMouseOut = true;
$scope.IsMouseIn = false;
$scope.MouseClick = false;
}
}
$scope.OnMouseClick = function () {
$scope.IsMouseOut = false;
$scope.IsMouseIn = false;
$scope.MouseClick = true;
}
$scope.EndEdit = function () {
$scope.IsMouseOut = true;
$scope.IsMouseIn = false;
$scope.MouseClick = false;
}
},
link: function (scope, el, attrs) {
el.on("mouseenter", function () {
scope.OnMouseEnter();
scope.$apply();
});
el.on("mousemove", function () {
scope.OnMouseEnter();
scope.$apply();
});
el.on("mouseleave", function () {
scope.OnMouseLeave();
scope.$apply();
});
el.on("click", function () {
scope.OnMouseClick();
if (el[0].querySelector('input'))
el[0].querySelector('input').select();
scope.$apply();
});
}
};
});
Any Suggestions?
I give the example here: Plunker
UPDATED
I found a solution using ngIf, and is to reference a variable from the parent scope using $ parent.value. Eg.
<Input type="text" ng-model="$parent.value" class="form-control input-sm" />
Or also referring to another object eg.
<input type="text" ng-model="value">
<div ng-if="IsMouseIn">
<input type="text" ng-model="value">
</div>
Here is the reference link: what is the difference between ng-if and ng-show/ng-hide
using ng-if makes it create/destroy new html nodes and it seems to be unable to cope with that. change to ng-show and it will work. i also added a body mouse capture so it ends the edit.
<div>
<div ng-show="IsMouseIn">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
<div ng-show="IsMouseOut" ng-click="OnMouseClick()">
{{value}}
</div>
<div ng-show="MouseClick">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
view plunker
If you want to use ng-if not ng-show still, define $scope.values and $scope.config and use like this. To avoid the ng-if problem you should define an object.
<div>
<div ng-if="config.IsMouseIn">
<input type="text" ng-model="values.value" class="form-control input-sm" />
</div>
<div ng-if="config.IsMouseOut" ng-click="OnMouseClick()">
{{values.value}}
</div>
<div ng-if="config.MouseClick">
<input type="text" ng-model="values.value" class="form-control input-sm" />
</div>

How to use one controller for multiple inputs with the same logic?

I have the following scenario, one form with multiple inputs and i need to calculate every input the same way but return the values to different fields
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorFOR" placeholder="" ng-change="findModifier()" ng-model="atrb.for">
<p>{{mod.for}}</p>
</div>
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorDES" placeholder="" ng-change="findModifier()" ng-model="atrb.des">
<p>{{mod.des}}</p>
</div>
the controller:
app.controller('atributosCtrl', function($scope){
findModifier = function() {
if ($scope.atrb > 1 && $scope.atrb <10)
{
if ($scope.atrb % 2 == 0)
{
$scope.mod = (($scope.atrb / 2) - 5);
}
}
};
$scope.$watch('atrb', findModifier); });
I want to change the value of mod.for or mod.des without having to write a controller for each input. but i don't how to pass the name of the model from the input that i'm modifying
I don't know what exactly you want, but I made some changes on your code to make it working. Please tell me what you want in comments here and I'll can help you.
Your HTML modified:
<body ng-controller="atributosCtrl">
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorFOR" placeholder="" ng-change="findModifier('for')" ng-model="atrb.for">
<p>{{mod.for}}</p>
</div>
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorDES" placeholder="" ng-change="findModifier('des')" ng-model="atrb.des">
<p>{{mod.des}}</p>
</div>
</body>
Your JS modified:
app.controller('atributosCtrl', function($scope){
$scope.atrb = {
for: null,
des: null
};
$scope.mod = {
for: null,
des: null
};
$scope.findModifier = function(type) {
$scope.mod[type] = null;
if ($scope.atrb[type] > 1 && $scope.atrb[type] <10)
{
if ($scope.atrb[type] % 2 === 0)
{
$scope.mod[type] = (($scope.atrb[type] / 2) - 5);
}
}
}
});
Plunker:
https://plnkr.co/edit/aCNJQyfYXZ5vU1rc381S
I think you are expecting somethin like this. You can write a custom directive with a link function like below
(function () {
"use strict";
angular.module("app").directive("notifypropertychanged", notifypropertychanged);
function notifypropertychanged() {
var directive = {
require: "ngModel",
link: function ($scope, element, attrs, ngModel) {
$scope.$watch(attrs["notifypropertychanged"], function (newVal, oldVal) {
var initialValue = attrs["oldvalue"];
});
}
};
return directive;
}
})();
Apply this directive on your input
<input type="number" min="1" class="form-control" notifypropertychanged="atrb.des" oldvalue=" {{::atrb.des}} " id="InputValorDES" placeholder="" ng-model="atrb.des">
whenever the value change it will hit on custom watch
I hope this helps

Auto tab in input type text field not working in Ionic

Currently what I'm doing is like this for HTML,
<label class="item item-input">
<input type="text" maxlength="1" ng-model="user.name" maxlength="1" input-move-next-on-maxlength></input>
</label>
<label class="item item-input">
<input type="text" maxlength="1" ng-model="user.email" maxlength="1" input-move-next-on-maxlength></input>
</label>
and the .js looks like this
.directive("inputMoveNextOnMaxlength", function() {
return {
restrict: "A",
link: function($scope, element) {
element.on("label", function(e) {
if(element.val().length == element.attr("maxlength")) {
var $nextElement = element.next();
if($nextElement.length) {
$nextElement[0].focus();
}
}
});
}
}
})
But, the result is the auto tab is not working. I also tried removing the <label>, that worked. I'm wondering does it happen because of the input is at other class or does my code have an error.
Try with this solution, It worked for me
HTML:
<div ng-app="autofocus">
<label>Name:</label>
<input ng-model="maxLengthReach"></input>
<br/><br/>
<label>Title:</label>
<input autofocus-when></input>
</div>
Javascript:
var app = angular.module('autofocus', []);
app.directive('autofocusWhen', function () {
return function (scope, element, attrs) {
scope.$watch('maxLengthReach', function(newValue){
if (newValue.length >= 5 ) {
element[0].focus();
}
});
}
});
Adjust the length based upon your requirement

Angular directive for horizontal Bootstrap form

I'm trying to build a directive for my Angular to help with the integration of form fields. I've implemented Scott Allens solution from his Angular playbook, and it works fine for a normal stacked form.
I need however to adapt it to a horizontal form instead. Here's my code:
Markup
<div form-group>
<label for="name">Name</label>
<input type="text" id="name" ng-model="vm.name">
</div>
formGroup directive
function link(scope, element) {
setupDom(element[0]);
}
function setupDom(element) {
var label = element.querySelector("label");
label.classList.add("control-label");
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox"){
input.classList.add("form-control");
}
element.classList.add("form-group");
}
function formGroup() {
return {
restrict: "A",
link: link
}
}
The output becomes:
<div form-group="" class="form-group">
<label for="name" class="control-label">Name</label>
<input type="text" id="name" ng-model="vm.name" class="form-control">
</div>
And that's fine for stacked form. Since I need a horizontal form, my output needs to look like this:
<div form-group="" class="form-group">
<label for="name" class="control-label col-sm-3">Name</label>
<div class="col-sm-9">
<input type="text" id="name" ng-model="vm.name" class="form-control">
</div>
</div>
I've tried many solutions and I can get it work with single elements like an input, textarea or a select. It becomes much more tricky when I have something like two radio buttons inside my markup like this:
<div form-group>
<label>Active</label>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="true" ng-model="vm.active"> Yes
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="false" ng-model="vm.active"> No
</label>
</div>
</div>
The desired output of the above mentioned code should be:
<div form-group class="form-group">
<label class="control-label col-sm-3">Active</label>
<div class="col-sm-9">
<div class="radio">
<label>
<input type="radio" name="active" ng-value="true" ng-model="vm.active"> Yes
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="false" ng-model="vm.active"> No
</label>
</div>
</div>
</div>
Please notice that the input(s) in the form-group is not fixed. It can be either a single input, textarea, select, a group of radio buttons or checkboxes. I'm lost for how I can make that happen. Any help is appreciated. Thanks!
UPDATE
I made some small changes to Mark Veenstra's code to make it (sort of) working:
function setupDom(element) {
element.classList.add("form-group");
var label = element.querySelector("label");
label.classList.add("control-label", "col-sm-3");
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox"){
input.classList.add("form-control");
angular.element(input).wrap(angular.element('<div class="col-sm-9"></div>'));
}
var div_radio = element.querySelector("div[class='radio']");
angular.element(div_radio).wrap(angular.element('<div class="col-sm-9"></div>'));
}
This does not work completely as intended with multiple radio inputs since it only wraps the <div> on the first radio input element.
The output from radio button example in my original post using Marks code is:
<div form-group="" class="form-group">
<label class="control-label col-sm-3">Active</label>
<div class="col-sm-9">
<div class="radio">
<label>
<input type="radio" name="active" ng-value="true" ng-model="vm.active" value="true"> Yes
</label>
</div>
</div>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="false" ng-model="vm.active" value="false"> No
</label>
</div>
</div>
SOLUTION
Check out the Plunker with the final result: http://plnkr.co/edit/Wv6V86hHTCz3URS9DhdU?p=preview
In the angular.element documentation you can find the method wrap() to be able to wrap HTML around a selected element. Or see this direct link.
So what you could do in your directive is change the setupDom() function to match your requirements per type of form element.
function link(scope, element) {
setupDom(element[0]);
}
function setupDom(element) {
element.classList.add("form-group");
var label = element.querySelector("label");
label.classList.add("control-label col-sm-3");
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox"){
input.classList.add("form-control");
input.wrap(angular.element('<div class="col-sm-9"></div>'));
}
var div_radio = element.querySelectorAll("div[class='radio']");
div_radio.wrap(angular.element('<div class="col-sm-9"></div>'));
}
function formGroup() {
return {
restrict: "A",
link: link
}
}
NOTE: This code is not tested, maybe there are some minor mistakes, but I guess you'll get the point now.
Mark's suggestion came close, but it didn't solve my problem completely. I ended up using the following code in my formGroup directive:
(function (module) {
"use strict";
function link(scope, element) {
setupDom(element[0]);
}
function setupDom(element) {
element.classList.add("form-group");
var children = angular.element(element).children();
var labels = children.splice(0, 1);
// Set label classes
labels[0].classList.add("control-label", "col-sm-3");
// Wrap children in div
angular.element(children).wrapAll(angular.element("<div class='col-sm-9'></div>"));
// Handle inputs
var inputs = element.querySelectorAll("input, textarea, select");
for (var i = 0, len = inputs.length; i < len; i++) {
var input = inputs[i],
type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox") {
input.classList.add("form-control");
}
}
}
function formGroup() {
return {
restrict: "A",
link: link
}
}
module.directive("formGroup", formGroup);
}(angular.module("app.core")));
Check out this Plunker to see it in action: http://plnkr.co/edit/Wv6V86hHTCz3URS9DhdU?p=preview

How to set scope variable value in angularjs template

I want to set value when query textbox is empty.
I want to do like this:
$scope.flag=0;
This is query textbox:
<input type="text" ng-model="query" ng-change="search()" />
When user types something, I set $scope.flag=1 in js file.
Hope you catch my problem. Please do needfull.
HTML
<div ng-controller="myCtrl">
<input type="text" ng-model="query" ng-change="search()" />
{{ flag }}
</div>
JS
app.controller('myCtrl', function($scope){
$scope.flag=0;
$scope.search = function() {
$scope.flag= $scope.query.length >0 ? 1:0;
}
});
Solved. I did it in this way:
<input type="text" ng-model="query" ng-change="search()" />
{{query=='' ? flag=0 : flag=1}}
You could use the ng-init directive:
<input type="text" ng-model="query" ng-init="flag=0" ng-change="search()" />
You can do it like this
$scope.search = function () {
if ($scope.query.length) {
$scope.flag = 1;
} else {
$scope.flag = 0;
}
}

Resources