How to monitor variable change in JavaScript - javascript

I receive value from server every 2 seconds. I want to run some code when the value will change. What is the best approach for this problem? Variable from server may change endlessly.
Thanks.

I'm assuming that, for whatever reason, you don't get to run code when you get the value from the server. If you do get to run code, then just compare what you get with what you have and take action if they're different
If you don't get to run code directly, you may be able to run code indirectly if you can control what the server writes to — specifically, if you can change it from yourVar = ... to obj.yourVar = .... If so, then instead of a variable, use an object property with a getter and setter. In the setter, compare the new value to the value you already have and, if it's different, take action:
let value = null;
const obj = {
get yourVar() {
return value;
},
set yourVar(newValue) {
if (value !== newValue) {
value = newValue;
// Take action here
}
}
};
Receive the value from the server using obj.yourVar = ....
I don't recommend it, but it's possible to do this using a global variable, which would let code using yourVar = ... trigger your handler, by creating the accessor property on the global object (which you can access via window on browsers):
let value;
Object.defineProperty(window, "yourVar", {
get() {
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
console.log("New value received: " + value);
}
}
});
// In the code writing to it that you don't control:
yourVar = 1; // Outputs "New value received: 1"
yourVar = 1; // No output, it's not different
yourVar = 2; // Outputs "New value received: 2"

Related

Is there a way to get data from user using browser's console

I would like to capture user's input through browser's console.
Currently I get input using JS's prompt, like so:
var a = prompt("Enter your numbers").split(",");
console.log(a);
But I would like data to be entered through the console.
Is there a way to do this with Javascript or Typescript?
EDIT
John Weisz gave good answer, this would normally work, it won't work with my problem.
You see, the user will use the console to enter a string of numbers which I will store into an array and check for duplicates...
Then print those duplicates with an object to the console...
var a;
window.cliApi = {
setValue: function (value) {
a = value;
}
}
var counts = {};
a.forEach(function(x){
counts[x] = (counts[x] || 0) +1; });
console.log(counts);
Sort of. You can create a globally exposed API that the user can use from the console, for example:
var a;
window.cliApi = {
setValue: function (value) {
a = value;
},
getValue: function () {
return a;
}
}
Then, in the browser console, you can type:
cliApi.setValue("hello world")
After which your variable will be populated.
Fiddle here.
Note, that with JSFiddle, you will need to set the console scope to the result frame instead of top (you do this inside your dev tools), as your code is running inside a frame. This is not required if you host your code yourself (and not from a frame).

js get set with dynamic variables in node-opcua

Examples on node-opcua # https://github.com/node-opcua/node-opcua say that I need to rewrite code for every variable added to the OPC server, this is achieved calling 'addressSpace.addVariable()'... But if I have 1000 variables it could be an hard task... and eventually each custom user want a code rewrite, it could be tedious... so I'm trying to do it dynamically.
The opc read 'tags' from another custom server (not OPC).
With this 'tags' the opc server needs to add them to node 'device'.
When the OPC server node-opcua find a get or set of a variable coming from the net, it call the get or set of the correct variable:
for (var i = 0; i < tags.GetTags.length; i++)
{
variables[tags.GetTags[i].Tag] = {"value" : 0.0, "is_set" : false};
addressSpace.addVariable({
componentOf: device, // Parent node
browseName: tags.GetTags[i].Tag, // Variable name
dataType: "Double", // Type
value: {
get: function () {
//console.log(Object.getOwnPropertyNames(this));
return new opcua.Variant({dataType: opcua.DataType.Double, value: variables[this["browseName"]].value }); // WORKS
},
set: function (variant) {
//console.log(Object.getOwnPropertyNames(this));
variables[this["browseName"]].value = parseFloat(variant.value); // this["browseName"] = UNDEFINED!!!
variables[this["browseName"]].is_set = true;
return opcua.StatusCodes.Good;
}
}
});
console.log(tags.GetTags[i].Tag);
}
As I say I tried to use the 'this' in get and set functions with half luck, the get has a 'this.browseName' (the tag name) property that can be used to dynamic read my variables and it currently works.
The problem is with the set, in set 'this.browseName' and 'this.nodeId' don't exist! So it gives 'undefined' error. It also doesn't exist in variant variable.
Do you know a work-around to use dynamic variables with the above code? I need to have one for loop with one get and one set definitions for all variables (tags), that read and write a multi-property object or an array of objects, like 1 get and 1 set definitions that write the right variable in a n records array.
PS: I found on stack overflow this:
var foo = {
a: 5,
b: 6,
init: function() {
this.c = this.a + this.b;
return this;
}
}
But in my case node-opcua Variable doesn't has a 'this' working like the example. In the 'set' (like init): this.browseName (like a) and this.nodeId (like b) are not reachable.
Gotcha,
you need to cast get and set properties as functions like:
addressSpace.addVariable({
componentOf: device,
browseName: _vars[i].Tag,
dataType: "Double",
value: {
get: CastGetter(i),
set: CastSetter(i)
}
});
with
function CastGetter(index) {
return function() {
return new opcua.Variant({dataType: opcua.DataType.Double, value: opc_vars[index].Value });
};
}
function CastSetter(index) {
return function (variant) {
opc_vars[index].Value = parseFloat(variant.value);
opc_vars[index].IsSet = true;
return opcua.StatusCodes.Good;
};
}
you will use an index to get and set values in the array, casting function like this will provide index to be "hard coded" in those get and set properties.

Assigning an object attribute in JavaScript changes object in a weird way

I'm debugging a complex JS client side framework based on Ext. I stumbled upon a line that gives results that I fail to explain in any way. Here is the line (me is actually just an alias for this):
me.displayTplData = displayTplData;
Some values before executing it:
me.value: "87172981"
displayTplData: Array[1] (this is a local variable)
me.displayTplData: undefined
After the line (F11, "step into next function call"):
me.value: null
displayTplData: Array[1] (stays as before)
me.displayTplData: null
Not only the assignment apparently didn't happen, this also altered value assigned to an unrelated attribute value... The only way I could think of is if displayTplData has an associated setter (similar to descriptors in Python?). But on the other hand, JS debugger doesn't step into any code when executing the line. Also, this framework works on IE8+, so it certainly doesn't use any recent JS developments.
This happens both with FireFox and Chrome, so it must be some "this is supposed to work this way", but I completely don't understand what's going on.
Can someone guess what might be the reason of it? Sorry, I cannot reduce it to a standalone example.
EDIT:
Here is the full function, as a context.
setValue: function(value, doSelect) {
var me = this,
valueNotFoundText = me.valueNotFoundText,
inputEl = me.inputEl,
i, len, record,
dataObj,
matchedRecords = [],
displayTplData = [],
processedValue = [];
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
me.value = value;
me.setHiddenValue(me.value);
return me;
}
// This method processes multi-values, so ensure value is an array.
value = Ext.Array.from(value);
// Loop through values, matching each from the Store, and collecting matched records
for (i = 0, len = value.length; i < len; i++) {
record = value[i];
if (!record || !record.isModel) {
record = me.findRecordByValue(record);
}
// record found, select it.
if (record) {
matchedRecords.push(record);
displayTplData.push(record.data);
processedValue.push(record.get(me.valueField));
}
// record was not found, this could happen because
// store is not loaded or they set a value not in the store
else {
// If we are allowing insertion of values not represented in the Store, then push the value and
// create a fake record data object to push as a display value for use by the displayTpl
if (!me.forceSelection) {
processedValue.push(value[i]);
dataObj = {};
dataObj[me.displayField] = value[i];
displayTplData.push(dataObj);
// TODO: Add config to create new records on selection of a value that has no match in the Store
}
// Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
else if (Ext.isDefined(valueNotFoundText)) {
displayTplData.push(valueNotFoundText);
}
}
}
// Set the value of this field. If we are multiselecting, then that is an array.
me.setHiddenValue(processedValue);
me.value = me.multiSelect ? processedValue : processedValue[0];
if (!Ext.isDefined(me.value)) {
me.value = null;
}
me.displayTplData = displayTplData; //store for getDisplayValue method <------- this is the line
me.lastSelection = me.valueModels = matchedRecords;
if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
inputEl.removeCls(me.emptyCls);
}
// Calculate raw value from the collection of Model data
me.setRawValue(me.getDisplayValue());
me.checkChange();
if (doSelect !== false) {
me.syncSelection();
}
me.applyEmptyText();
return me;
},
Sometimes the debugger provides false information. It is strange that both Firefox's and Chrome's debugger produces the same (wrong) inspection, but if you want to be sure about those values, just put console.log(me.value) before and after the statement, and see what gets printed.

Overriding a breeze entity value getter setter seems to break change tracking

I am using breeze to communicate with Web.API 2.1
In my backend I save some values as a list of strings (instead of saving one-to-many relations). In the front end I want to break these values, edit them, put them back together and persist them to the DB.
emailsString is the actual property that is persisted to the DB and exists in the model.
fullName acts as an "interface" to reading and modifying the first and last name properties.
I have the following:
function registerUserProfile(metadataStore) {
metadataStore.registerEntityTypeCtor('UserProfile', profile, profileInitializer);
function profile() {
this.fullName = '';
this.emails = [];
}
function profileInitializer(newItem) {
if (!newItem.emailsString || newItem.emailsString.length === 0) newItem.emails = [{ email: '' }];
}
Object.defineProperty(profile.prototype, 'fullName', {
get: function() {
var fn = this.firstName;
var ln = this.lastName;
return ln ? fn + ' ' + ln : fn;
},
set: function (value) {
var parts = value.split(' ');
this.firstName = parts.shift();
this.lastName = parts.shift() || '';
}
});
Object.defineProperty(profile.prototype, 'emailsString', {
get: function () {
return objectToStringArray(this.emails, 'email');
},
set: function (value) {
this.emails = stringToObjArray(value, 'email');
}
});
function objectToStringArray(objectArray, objectValueKey) {
var retVal = '';
angular.forEach(objectArray, function (obj) {
retVal += obj[objectValueKey] + ';';
});
if (retVal.length > 0)
retVal = retVal.substring(0, retVal.length - 1); //remove last ;
return retVal;
}
function stringToObjArray(stringArray, objectValueKey) {
var objArray = [];
angular.forEach(stringArray.split(';'), function (str) {
var item = {};
item[objectValueKey] = str;
objArray.push(item);
});
return objArray;
}
If I modify the emailString value and call saveChanges on breeze nothing happens. If I modify the fullName property ALL changes are detected and saveChanges sends the correct JSON object for saving (including emailString value).
From what I understand, overriding the emailString property I somehow break the change tracking for this property. fullName is not a mapped property and thus is not overriding anything so it works. Am I going the correct way? If so is there a way to notify breeze that the overriden property has changed?
In general, Breeze takes over each property on an object and insures that internally it is informed about any changes to each property. How this is done is different depending on whether you are using Angular, Knockout or Backbone ( or a custom modelLibrary adapter).
But if you plan on modifying the property yourself to do something similar you need to insure that breeze is still getting notified.
Based on your posted code I'm assuming that you are using Angular. In that case you first need to determine whether your code is getting executed before or after Breeze's code.
My guess is that if you make your changes early enough then Breeze will be able to wrap them successfully. However, if your changes occur after Breeze's then you need to insure that Breeze's code is invoked as well. Debugging into the source for this is probably your best bet. And the Breeze Angular adapter is a good source as a example of how to wrap a property that might already be wrapped with another defineProperty.

Define setter for hash member in JavaScript

I have hash defined like this:
var props = {
id: null,
title: null,
status: null
};
I'd like to define setter for status field (and only for it) doing it as following:
props.__defineSetter__("status", function(val){
//Checking correctness of val...
status = val;
});
But it doesn't work :(
So, what's the right way to do it?
Simple, you need to use
this.status = val;
Otherwise you are just setting an unrelated global variable status equal to val.
And as already noted, setters/getters are not implemented in IE.
Also, I'm not sure about how wise it is to have a setter that is the same name as the property it sets. Not sure if this will result in a conflict, but it does seem like a bad idea yes? Ideally the variable that would be set should be hidden in a closure
var props = {
id: null,
title: null
};
(function() {
var status;
props.__defineSetter__("status", function(val){
//Checking correctness of val...
status = val;
});
props.__defineGetter__('status', function() { return status; });
}());
This way, status is fully protected from direct access, which is the point of using setters and getters.
The first thing is what MooGoo has pointed out. But also, you can not assign a property setter to an object using the same name as an existing variable in the object.
So your code would have to be something arround this:
var props = {
id: null,
title: null,
hStatus: null,
};
props.__defineSetter__("status", function(v){
this.hStatus = v;
});
props.__defineGetter__("status", function(){
return this.hStatus;
});
[edit]
Yeah, MooGoo eddited his answer faster then time time I took to write this 8(.
This should work:
props.__setStatus__ = function(val) {
// Check correctness of val
this.status = val;
}
Usage:
props.__setStatus__('Alive');

Categories

Resources