Fahrenheit and Celsius Bidirectional Conversion in AngularJS - data-binding

In AngularJS, it is straightforward how to $watch a $scope variable and use that to update another. But what is the best practice if two scope variables need to watch each other?
I have as an example a bidirectional converter that will convert Fahrenheit to Celsius, and vice versa. It works okay if you type "1" into the Fahrenheit, but try "1.1" and Angular will bounce around a little before overwriting Fahrenheit that you just entered to be a slightly different value (1.1000000000000014):
function TemperatureConverterCtrl($scope) {
$scope.$watch('fahrenheit', function(value) {
$scope.celsius = (value - 32) * 5.0/9.0;
});
$scope.$watch('celsius', function(value) {
$scope.fahrenheit = value * 9.0 / 5.0 + 32;
});
}
Here's a plunker: http://plnkr.co/edit/1fULXjx7MyAHjvjHfV1j?p=preview
What are the different possible ways to stop Angular from "bouncing" around and force it to use the value you typed as-is, e.g. by using formatters or parsers (or any other trick)?

I think the simplest, fastest, and most correct solution is to have a flag to track which field is being edited, and only allow updates from that field.
All you need is to use the ng-change directive to set the flag on the field being edited.
Working Plunk
Code changes necessary:
Modify the controller to look like this:
function TemperatureConverterCtrl($scope) {
// Keep track of who was last edited
$scope.edited = null;
$scope.markEdited = function(which) {
$scope.edited = which;
};
// Only edit if the correct field is being modified
$scope.$watch('fahrenheit', function(value) {
if($scope.edited == 'F') {
$scope.celsius = (value - 32) * 5.0/9.0;
}
});
$scope.$watch('celsius', function(value) {
if($scope.edited == 'C') {
$scope.fahrenheit = value * 9.0 / 5.0 + 32;
}
});
}
And then add this simple directive to the input fields (using F or C as appropriate):
<input ... ng-change="markEdited('F')/>
Now only the field being typed in can change the other one.
If you need the ability to modify these fields outside an input, you could add a scope or controller function that looks something like this:
$scope.setFahrenheit = function(val) {
$scope.edited = 'F';
$scope.fahrenheit = val;
}
Then the Celsius field will be updated on the next $digest cycle.
This solution has a bare minimum of extra code, eliminates any chance of multiple updates per cycle, and doesn't cause any performance issues.

This is a pretty good question and I'm answering it even though it's already accepted :)
In Angular, the $scope is the model. The model is a place to store data you might want to persist or use in other parts of the app, and as such, it should be designed with a good schema just as you would in a database for example.
Your model has two redundant fields for temperature, which isn't a good idea. Which one is the "real" temperature? There are times when you want to denormalize a model, just for access efficiency, but that's only really an option when the values are idempotent, which as you found, these aren't due to floating point precision.
If you wanted to continue using the model it would look something like this. You'd pick one or the other as the "source of truth" temperature, then have the other input as a convenience entry box with a formatter and parser. Let's say we want Fahrenheit in the model:
<input type="number" ng-model="temperatureF">
<input type="number" ng-model="temperatureF" fahrenheit-to-celcius>
And the conversion directive:
someModule.directive('fahrenheitToCelcius', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$formatters.push(function(f) {
return (value - 32) * 5.0 / 9.0;
});
ngModel.$parsers.push(function(c) {
return c * 9.0 / 5.0 + 32;
});
}
};
});
With this, you'd avoid the "bouncing around" because $parsers only run on user action, not on model changes. You'd still have long decimals but that could be remedied with rounding.
However, it sounds to me like you shouldn't be using a model for this. If all you want is "each box updates the other box", you can do exactly that and not even have a model. This assumes the input boxes start out blank and it's just a simple tool for users to convert temperatures. If that's the case, you have no model, no controller, and aren't even hardly using Angular at all. It's a purely view-based widget at that point, and it's basically just jQuery or jQLite. It's of limited usefulness though, since with no model it can't effect anything else in Angular.
To do that, you could just make a temperatureConverter directive that has a template with a couple of input boxes, and watches both boxes and sets their values. Something like:
fahrenheitBox.bind('change', function() {
celciusBox.val((Number(fahrenheitBox.val()) - 32) * 5.0 / 9.0);
});

Related

How to write if statement comparing numbers that creates objects conditionally in QML

I'm working with QML and Python3.6 + PySide2 and I'm trying to write script in QML that takes two integers from a connection in python and compares them to decide what image background to use for the window.
There are a few things I'm struggling with. First, I am unsure how to compare my numeric (sunset and sunrise) variables. Second, I don't know how to write an if statement-esque part that produces a background image conditionally. Third, I don't think it's best to do this under Connections, and maybe even in my QML, but I'm not sure how to move my variables somewhere else.
I really appreciate any pointers or help!!
The data I'm drawing from looks like this:
"sunrise":1592565499,"sunset":1592617094
The QML pseudo-ish code:
Connections {
target: weather
function onDataChanged(){
if(!weather.hasError()){
var sunrise = weather.data['dt']['sunrise']
var sunset = weather.data['dt']['sunset']
if (sunrise <= sunset)
Image {
source: "night.png"}
else
Image {
course: "day.png"}
}
You cannot create QML-items from if-statements like that (neither from State's). You can call Qt.createComponent if you like, however, it is rather overkill in this example. You should directly set the source property of your image:
Image {
id: image_tod
}
Connections {
target: weather
function onDataChanged(){
if(!weather.hasError()){
var sunrise = weather.data['dt']['sunrise']
var sunset = weather.data['dt']['sunset']
if (sunrise <= sunset)
image_tod.source = "night.png"
else
image_tod.source = "day.png"
}
}
}
Looking at the code, you might actually be able to bind it directly to the source property (not sure what your model exactly looks like):
Image {
source: {
if(weather.data['dt']['sunrise'] <= weather.data['dt']['sunset'])
return "night.png"
else
return "day.png"
}
}
This works because when compiling the QML, the engine creates a dependency from every referenced variable (weather and data in this case), and re-evaluates the whole binding if any of them signals a change. To make fully use of this, you should also expose hasError as a property rather than a function (and emit whenever it changes).
Follow-up update
Yes, you can make it as wild as you want. I think you mean this:
Image {
source: {
if(weather.data['dt']['sunrise'] <= weather.data['dt']['sunset'])
return "night.png"
else if(weather.data['dt']['sunrise'] > weather.data['dt']['sunset'])
return "day.png"
else
return "" //means no image
}
}

How to bind a SAPUI5 control property with data out of a binding?

From time to time I have the requirement to bind a control property to based on data out of model A to another model B.
For example the syntax could look like this (but will not work):
text : "{B>/rootB/{A>someValue}/propertyB}"
I normally solve this problem by "misusing" an unused control property in combination with the format function. It would look like this:
tooltip : {
path : "A>someValue",
formatter : function(oValue) {
// do some checks on oValue
var path = "B>/rootB/"+oValue+"/propertyB";
this.bindProperty("text", path);
return undefined; // because tooltip is not used
}
The benefit of this, each time "A>someValue" will be changed the binding of "text" will be updated automatically.
It is also possible to do this in template code (like items aggregations).
But you may smell the code ;)
Any suggestions to make it cleaner?
As far as I know, there is no such possibility in UI5 (yet). I always use a formatter function as you already mentioned. I say not YET, because developers seem to be aware of this feature request: see on GitHub
BUT, you dont need to missuse a random control property! Just use the formatter to read the needed values from any model you have access to:
text : {
path : "A>someValue1",
formatter : function(oValue) {
// read model B to get someValue2 (based on someValue1)
var path = "B>/rootB/"+oValue+"/propertyB";
var B = getModel("someModel");
var someValue2 = B.getProperty(path);
return someValue2
}

Creating Flex Elements Server Side

i wonder if it is possible to pre-configurate Flex Elements on the Server. I have the Problem with a custom ItemRenderer which turns out to be very slow. It would be very cool to pre-process such an element on the server instead in the clients browser... somehow? Maybe it is possible to produce the MXML dynamically on the server for that.
This is it basically. I create a Label for each data entry in an array list. This entry is added to a BorderContainer and this goes to the containing element as a whole here. Sometimes i add 200 - 300 items this way which is costing very high computing cost at client side. So i wonderd if i could just pass this as a whole dynamic mxml element to the client.
override public function set data(value:Object):void {
_data = value as WordResultObject;
var data:WordResultObject = _data as WordResultObject;
this.removeAllElements();
if(_data!=null)
{
_l.text = data.wordform;
_l.setStyle("fontSize", data.fontSize);
_l.setStyle("color", data.color);
_l.toolTip = "Frequency: " + data.freq;
if(data.date != null)
{
_l.toolTip += "\nDate: " + AppUtils.TimeString(data.date as Date);
_l.addClickEvent(data.id as int, data.date as Date);
}
_border.addElement(_l);
this.addElement(_border);
}
}
Thank you
Andreas
I wonder if it is possible to
pre-configurate Flex Elements on the
Server.
Not that I know of. Perhaps if you go back to Flex 1 / 1.5 which was primarily a server based platform. I do not expect rolling your code back to an "old" server would improve efficiency at all, though. How would you expect this work? What benefit are you expecting to receive.
I have the Problem with a custom
ItemRenderer which turns out to be
very slow.
Show your code; and perhaps we can help you with writing your renderer to be more efficient.

Resetting target values in a composite effect

We need to be able to handle a "playable" (play/pause/seek) effect in which the nature of the effect cannot be determined at compile time.
The problem we are running into is resetting the target(s) state after the effect has completed. If we manually drag the seek slider back to the beginning, everything works fine. However, if we set the playheadTime of the composite effect back to 0, the effected targets retain their original value until the playheadTime gets to the correct position to effect the target.
Here is a simplified (as much as I could) test case with view source enabled:
http://www.openbaseinteractive.com/_tmp/PlayableEffectTest/
The problem is demonstrated if you let it play to the end, and then hit the play button to start it over.
What is the best way to go about manually resetting the target values given that the exact nature of the effect is unknown?
Many thanks for your time!
edit
I forgot to mention we are using Flex 4.5 preview release.
Have you tried:
effect.reverse()
More info
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/effects/IEffect.html
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/effects/IEffect.html#reverse()
Well it's a little kludgy, but I was able to accomplish this by calling some internal methods on the effect to capture the start values, then assigned those values to the targets on a reset.
import mx.core.mx_internal;
use namespace mx_internal;
private var _propertyChangesArray:Array;
protected function captureStartValues(effect:Object):void
{
effect.captureStartValues();
_propertyChangesArray = effect.propertyChangesArray;
}
protected function reset(effect:Object):void
{
for each(var change:PropertyChanges in _propertyChangesArray)
{
var target:Object = change.target;
for(var p:String in change.start)
{
if(target.hasOwnProperty(p))
{
var startVal:* = change.start[p];
var endVal:* = target[p];
if(!isNaN(startVal) && startVal != endVal)
{
target[p] = startVal;
}
}
}
}
effect.playheadTime = 0;
}
I don't know if this is the best way to accomplish this, but it seems to be working so far. I am absolutely open to suggestions for a better method.
Cheers!

ASP.Net Auto-populate field based on other fields

I've just moved to web development and need to know how i can implement below requirement using asp.net and vb.net.
I have three fields in a form which are filled by users. Based on these three values, i need to auto-populate the 4th field. I have planned to implement this in the following way
Write a separate class file with a function to calculate the possible values for the 4th fields based on 1st 3 inputs. This function can return some where between 1-10 values. So I've decided to use drop-down for 4th field, and allow users to select the appropriate value.
Call the above function in onchange function of 3rd field and take and use the return values to populate the 4th field. I'm planning to get the return values in array field.(Does this need a post back?)
Please let me know how if there is better way to implement this.
Thanks.
You may want to consider doing this with Javascript. You could read and control the fields pretty easily with pure Javascript, or using a nice library like jQuery (my favorite). If you did it this way, no post-back would be required and the 4th field would update immediately. (Nice for your users)
You can also do it with ASP.NET for the most part. "onchange" in ASP.NET still requires Javascript as far as I know, it just does some of it for you. A post-back will definitely happen when you change something.
You need javascript or to set autopostback=true on your form elements.
From a user perspective the best thing is to use javascript to populate the field for display, BUT when the form is submitted use your backend function to validate it. This will make sure the user didn't change the value.
An easy way is to use jQuery for the UI (that way you don't have to worry about long winded javascript and deal with browser compatibility as it's already taken care of for you) and have it call to the server for the data. For the server, your easiest route is to return JSON for looping values.
Include your jQuery:
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
Then add in a handle for the JavaScript:
<script type="text/javascript">
function autoPopulate() {
var value1 = $('#ddl1').val();
var value2 = $('#ddl2').val();
var value3 = $('#ddl3').val();
var url = 'path/to/your/file.aspx?value1=' + value1 + '&value2=' + value2 + '&value3=' + value3;
$.getJSON(url, function(data) {
data == null ? return false : data = eval(data);
var ddl = $('#ddl4')[0];
for (i = 0; i < data.length; i++) {
var option = new Option(data[i][0], data[i][1]);
if ($.browser.msie) {
ddl.add(option);
} else {
ddl.add(option, null);
}
}
}
}
</script>
(Yes, I know I used a native loop but I'm little lazy here today :) )
Now, for your server side code you'll want your code your page to return data in the format of:
[['value1','text1'],['value2','text2'],['value3','value3']]
so something like:
<script type="vb" runat="server">
Private Sub Page_Init()
// get your data
// loop through it and add in values
// ex.
Dim result As String = "[" //start multi-dimensional array
For Each Item As String In data
result += String.Format("['{0}','{1}'],", _value, _text)
Next
result = result.SubString(0, result.Length - 1) // removes trailing comma
result += "]" // closes off m-array
Response.Write(result)
Response.Flush()
End Sub
</script>

Resources