ng-model manipulating value when typing and displaying - javascript

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';
}

Related

Updating only the 'view' value using angular directive

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>

AngularJs Call function on hidden input on value change $watch

I want to use $watch in order to trigger a function each time one of those 3 values is changed :
html:
<input type="hidden" name="source_x" id="source_x" ng-model="source_x"/>
<input type="hidden" name="source_y" id="source_y" ng-model="source_y"/>
<input type="hidden" name="id" id="id" ng-model="id"/>
I just started angular and I want to use the $watch to trigger a function.
Those value are changed each time I drag one div with the draggable function below :
$("#div").draggable({
helper : 'clone',
stop:function(event,ui) {
var wrapper = $("#container-emote").offset();
var borderLeft = parseInt($("#container-emote").css("border-left-width"),10);
var borderTop = parseInt($("#container-emote").css("border-top-width"),10);
var pos = ui.helper.offset();
$("#source_x").val(pos.left - wrapper.left - borderLeft);
$("#source_y").val(-(pos.top - wrapper.top - borderTop)+185);
$("#id").val(2);
}
});
I started with this but I think it is not right because if I move one div I am going to call 3 times the function ? Moreover I don't know if I can use it with input hidden.. Thank you !
//Fonction
$scope.$watch($scope.source_x, createEmote);
$scope.$watch($scope.source_y, createEmote);
$scope.$watch($scope.id, createEmote);
function createEmote(newValue, oldValue, scope){
}
UPDATE : answer fiddle
I just add a function at the end of stop of drag
jsfiddle.net/5e7zbn5z/1/
You need to use your $scope.$watch like so:
$scope.$watch(function() {
return $scope.source_x + $scope.source_y + $scope.id;
}, function() {
$scope.createEmote();
});
And have your createEmote be a function on your $scope:
$scope.createEmote = function() {
// do something with $scope.source_x, etc
}
EDIT
As noted in the comments by #Sergey the exact watcher function will be dependent on your expected data. You could also just duplicate it and change which variable is returned (Similar to your existing code) if you want.

angular directive: switch between two templates dynamically

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>.

AngularJs directive that takes user input as string and reformats it to alternate upper lower case. E.g compter becomes CoMpUtEr

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.

angularJS how to change attrs in directive's link

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.

Categories