So I have a decimal value in controller like this:
// Controller
var MyController = function($scope) {
...
$scope.percentValue = 0.05; // can be stored
...
};
<!-- View -->
<span>{{percentValue}}</span>
<input ng-model="percentValue" />
With the above code, the value in the input element is 0.05 - however, I want to allow a user to enter an integer value like 5.
So if the $scope.percentValue is 0.05, I want to show it as 5 in the input element. And if a user enters 5, the $scope.percentValue should be 0.05.
However, the tricky thing here is I only want to update the view value - meaning that the span element should still show 0.05. Only the value in the input element should be 5.
I am trying to achieve this with ngModel, but I am still struggling.
This is what I have now:
var MyDirective = function() {
function link(scope, element, attrs, ngModel) {
ngModel.$render = function() {
element.val(ngModel.$viewValue || '');
};
ngModel.$formatters.push(function (value) {
return value * 100;
});
element.on('change blur', function() {
ngModel.$setViewValue(element.val());
});
}
return {
restrict: 'A',
require: '?ngModel',
scope: {},
link: link
};
};
Please advise!!
Including my comment as an answer because it seemed to help. :-)
To summarise: since you've already provided a $formatters function for your directive, which converts a model value ($modelValue) to displayed form ($viewValue), it's simply a matter of providing a $parsers function to do the reverse and convert any user input back to the model value.
Example Plunker
What you're trying to achieve is probably possible, but I would find it really confusing to read the code. The simplest solution that I think would solve your problem and maintain readability is to store an integer value (5) in $scope.percentValue, so that ng-model is always dealing with an integer when typing and displaying the value in the <input>. Then create a custom filter and use it to output the value as 0.05 in the <span>.
Edit: adding a concrete code example. Play with it here: https://plnkr.co/edit/C1cX2L9B2GM2yax1rw7Z?p=preview
JS:
var MyController = function ($scope) {
$scope.percentValue = 5;
};
function formatPercent (input) {
return input / 100;
}
var myApp = angular.module('MyApp', []);
myApp.filter('percent', function () { return formatPercent });
myApp.controller('MyController', ['$scope', MyController]);
HTML:
<body ng-controller="MyController">
<span>{{ percentValue | percent }}</span>
<input ng-model="percentValue">
</body>
I'd create a filter for percentage :
angular.module('myModule')
.filter('percentage', ['$filter', function($filter) {
return function(input, decimals) {
return $filter('number')(input*100, decimals)+'%';
};
}]);
The input will store integer (such as 5)
<input ng-model="percentValue" />
But I'll add a filter to the span part :
<span>{{percentValue | percentage:2}}</span>
Credit to https://stackoverflow.com/a/21727765/3687474 for the filter directive.
Other than creating a filter you can also calculate on the template
<span>{{percentValue * 100}}</span>
Related
I am trying to create a directive named availableTo that can switch between two different templates depending on some message. For example, if the field is an input with the ng-model directive, I would first need to change it to read-only using the <span> tag. So far, my code can switch the view to read-only, but I cannot seem to switch it back to input:
var directive = {
restrict: 'A',
require: '?ngModel',
link: linkerFn,
replace: true
};
function linkerFn(scope, element, attrs, ngModelCtrl) {
var clonedElement = angular.copy(element);
var preOuterHTML = clonedElement[0].outerHTML; //this can save the <input> field html code
scope.$on('mode_changed', function() {
var curUserRole = userservices.getUserRole();
if (attrs.availableTo == curUserRole) {
var e = $compile(preOuterHTML)(scope);
element.replaceWith(e);
} else {
var template = '<span>' + ngModelCtrl.$viewValue + '</span>';
var e = $compile(template)(scope);
element.replaceWith(e);
}
}); //scope.$on
} //linkerFn
For an input field:
<input name="test1" class="form-control" ng-model="name" placeholder="Name 1" available-to="ADMIN"/>
I also noticed that once I change the template in the else block above, the element re-renders, and the preOuterHTML does not contain the original element html any more. This seems to be mission impossible to me, but I would like to hear some expert opinions. Thanks
element.replaceWith(e); Don't do that. In Angular, if you find yourself attempting to modify the DOM directly, you are by definition doing it wrong. You gotta sit back and let Angular do the work.
If you need to replace a directive's entire template, a fairly straightforward approach is to use ng-include with a scope variable containing the desired conditional templateUrl, e.g.
var directive = {
// ...
template: '<div ng-include="myTemplateUrl"></div>',
link: function(scope, el) {
if (/* whatever */) {
scope.myTemplateUrl="templates/foo.html";
} else {
//...etc
}
},
};
(This does add an extra DOM node to the tree, but that's generally harmless.)
It sounds like in your case you may not need to go that far, though; a simple ng-if inside your template is probably enough to swap between your read-only <span> and <input>.
Hi I am new to AngularJs and trying hard to find a solution for this. I am just learning directives and have been able to get my directive to work showing a textbox for the user to type into. The logic to change to upper and lower case characters from a string is what I'm puzzled with.
I thought maybe something like:
if (inputValue % 2 == 0) {
//have the user input.ToUpperCase()
}
E.g if user types in computer the textbox would dynamically update as the user types to CoMpUtEr.
Any help is greatly appreciated.
The best you can do in this case is to write custom directive. You need to make sure that the model is properly transformed in all cases: through input in the field, as well as when the model changes in code like $scope.model = 'some' - then it's supposed to get translated to SoMe in the view.
Here is a basic directive I wrote to alternate characters case.
var app = angular.module('app', []);
app.directive('capitalizeAlternate', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
function formatter(value) {
if (value) {
for (var i = 0; i < value.length; i++) {
if (i % 2 == 0) {
value = value.substr(0, i) + value[i].toUpperCase() + value.substr(i + 1);
}
}
return value;
}
}
ngModelController.$parsers.push(function(value) {
if (value) {
ngModelController.$viewValue = formatter(value);
ngModelController.$render();
return ngModelController.$viewValue;
}
});
ngModelController.$formatters.push(formatter);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<input capitalize-alternate type="text" ng-model="input" /> {{input}}
</div>
On your input add the handler ng-change="unformatMyText". Then add this funtion to your view, like unformatMyText: function(txt){}. Within the function body add your code. This should do the trick every time.
I have the following variable:
$scope.pixelWidth = "30px";
And I have an input box like so:
<input ng-model="pixelWidth" />
I'd like for the input box to only have the numbers inside it but still insert the px into $scope.pixelWidth while typing.
Is there a way to accomplish this?
Yes, you need to create a directive and add formatters and parsers to the ngModelController. See working version on plunker
Directive:
app.directive('modelSuffix', [function() {
return {
restrict: 'AE',
require: '^ngModel',
link: function(scope, element, attributes, ngModelController) {
var suffix = attributes.modelSuffix;
// Pipeline of functions called to read value from DOM
ngModelController.$parsers.push(function(value) {
return value + suffix;
});
// Pipeline of functions called to display on DOM
ngModelController.$formatters.push(function(value) {
return value.replace(suffix, '');
});
}
}
}]);
And use it like so:
<input ng-model="pixelWidth" model-suffix="px"/>
<input type="text" name="userName" ng-model="pixel.value" ng-model-options="{ getterSetter: true }" />
var _myPixel = '0';
$scope.pixel = {
value: function(pixel) {`enter code here`
// Note that pixelcan be undefined for two reasons:
// 1. Because it is called as a getter and thus called with no arguments
// 2. Because the property should actually be set to undefined. This happens e.g. if the
// input is invalid
return arguments.length ? (_myPixel = pixel.split("px")[0]) : _myPixel + "px";
}
};
I'm removing the "px" in the setter and adding the "px" in the getter.
I hope this work for you!
You can do this by watch funciton.
$scope.$watch("pixelWidth",function(VariableValue){
// remove "px" from your variable and assign it again
$scope.pixelWidth=newValue;
});
I don't see any way that you can accomplish this without using a second variable in your controller. If you change $scope.pixelWidth to include the 'px', that's going to end up in your input box. That's the magic of two-way data binding, except that in this use case the result may be less than magical to you.
You'll probably need to do something like react to the ng-change event on the input box to change a second shadow variable.
<input ng-model='pixelWidth' ng-change='addPx(pixelWidth)'>
in controller JS
$scope.addPx = function(pw){
$scope.withPx = pw + 'px';
}
I set a progress in my app
I want to controll The progress in angular's directive
but how can I change data-value and data-total in directive's link func?
app.html
<div class="ui indicating small progress" data-value="39" data-total="50" plan-progress>
<div class="bar">
<div class="progress"></div>
</div>
</div>
In this html, I want change data-value and data-total
I try this:
app.js
todoApp.directive('planProgress', function() {
return {
link: function(scope, elem, attrs) {
attrs.value = 10
attrs.total = 20
elem.progress();
}
};
});
But it doesn't work
so I want to know how to change it in my directive?
Use attrs.$set() in your link function and recompile the element. Also, don't forget to inject the $compile service to your directive.
In your html you've added the directive as an attribute but didn't mention it in the restrict value in your directive definition. You need to mention it in directive definition.
See the code bellow:
todoApp.directive('planProgress', function($compile) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
attrs.$set('value', 10);
attrs.$set('total', 20);
$compile(elem)(scope);
}
};
});
Simply use :
attrs["data-value"] = 10;
attrs["data-total"] = 20;
You don't want to use attrs.data-total = 20 because the - will force a subtraction.
It's always legal in javascript to use x[keyName] instead of x.keyName, and you must use this second notation when keyName is a strange key such as **$^ùjsls* or data-value. A more useful case is when the key is a variable.
On last thing : as you do, you will always rewrite the coder's inputs. It may have sense, but it's not very elegant.
I want to implement a feature in table where user can set value of cell by clicking on it.
there can be say 3-4 states,also a ng-model attached to it.
I looked for the toggle button in angularjs but they are mere on/off type.
In short; Clicking on the button will set the value as: Active, Inactive, Excluded
Looking for solution with multiple state.
Any help with this is really appreciated.
Check the below working example :
http://jsfiddle.net/vishalvasani/ZavXw/9/
and controller code
function MyCtrl($scope) {
$scope.btnarr=0;
$scope.btnTxt=["Active","Inactive","Excluded"]
$scope.change=function(){
switch($scope.btnarr)
{
case 0:
$scope.btnarr=1;
break;
case 1:
$scope.btnarr=2
break;
case 2:
$scope.btnarr=0;
break;
}
}
}
OR
Shorter Version of Controller
function MyCtrl($scope) {
$scope.btnarr=0;
$scope.btnTxt=["Active","Inactive","Excluded"]
$scope.change=function(){
$scope.btnarr = ($scope.btnarr + 1) % $scope.btnTxt.length;
}
}
and HTML
<div ng-controller="MyCtrl">
<button ng-modle="btnarr" ng-Click="change()">{{btnTxt[btnarr]}}</button>
</div>
There isn't much to it.
When I make menus in Angular, on each item, I'll have a "select" function, which then selects that particular object, out of the list...
Making an iterable button is even smoother:
var i = 0;
$scope.states[
{ text : "Active" },
{ text : "Inactive" },
{ text : "Excluded" }
];
$scope.currentState = $scope.states[i];
$scope.cycleState = function () {
i = (i + 1) % $scope.states.length;
$scope.currentState = $scope.states[i];
// notify services here, et cetera
}
<button ng-click="cycleState">{{currentState.text}}</button>
The actual array of states wouldn't even need to be a part of the $scope here, if this was the only place you were using those objects -- the only object you'd need to have on the $scope would then be currentState, which you set when you call the cycleState method.
Here is a fiddle with two possibilities: selecting the state from a list or cycling by clicking the button itself.
http://jsfiddle.net/evzKV/4/
The JS code looks like this:
angular.module('test').directive('toggleValues',function(){
return {
restrict: 'E',
replace: true,
template: '<div>Set Status:<div ng-repeat="value in values" class="status" ng-click="changeTo($index)">{{value}}</div><span ng-click="next()">Current Status (click to cycle): {{values[selectedValue]}}</span></div>',
controller: ['$scope', '$element', function ($scope, $element) {
$scope.values = ["Active", "Inactive", "Pending"];
$scope.changeTo = function (index) {
$scope.selectedValue = (index < $scope.values.length) ? index : 0;
};
$scope.next = function () {
$scope.selectedValue = ($scope.selectedValue + 1) % $scope.values.length;
// the modulo is stolen from Norguard (http://stackoverflow.com/a/18592722/2452446) - brilliant idea
};
$scope.selectedValue = 0;
}]
};
});
HTML:
<div ng-app="test">
<toggle-values></toggle-values>
</div>