I have seen people using this
hideTooltip : function() {
var toolRef = 'population_tooltip';
if(Helper.tooltipObj[toolRef]){
Helper.tooltipObj[toolRef].hide();
}
}
The Helper.tooltipObj[toolRef] is an object. and i was wondering how is it working? can you put an object inside IF condition in Javascript(We are using AUI API)?
The function is called when someone clicks a button on a popup on the page(not the javascript alert type popup, kind of select some stuff and press OK popup)
if (expr) - any expression is valid. If it evaluates to "an object" it is true as all Objects evaluate to a 'true expression' in this context. The expression is considered 'false-y' only for undefined, null, false, "", 0, and NaN.
Thus the condition only runs if "an object" (hopefully with said method) is assigned to the property - it would evaluate to undefined if no value had been previously assigned.
This logic assumes that any other 'true' (or even 'false') value is a prior programming error/contract breach and is a fairly common idiom.
See also:
Is there a standard function to check for null, undefined, or blank variables in JavaScript?
All falsey values in JavaScript
Related
This is a question & answer case, because I struggled a bit with the issue, so I wish to keep track of it and its solution.
Hoping it can help somebody, and perhaps other people can provide alternative solutions.
My case: I had to work on a large AngularJS 1.4.6 project (I will name it prj), written in TypeScript, defining a number of directives (prj-ddd) with various attributes (prj-aaa), some of them being boolean.
They implemented these boolean attributes with string binding: prjEnabled: "#" or prjRequired: "#".
So they tested the values like so: if ($scope.prjEnabled === "false"), or in templates: <div ng-show="prjRequired === 'true'"> (yeah, no controllerAs usage as well...).
These attributes are optional: obviously, here, prjEnabled defaults to "true", and prjRequired defaults to "false", among others, because the tests are false when these values are undefined.
This is verbose, inelegant (IMO), prone to errors (lot of raw strings, implicit default value), and not very efficient (although probably not in a perceptible way).
So I started to replace these bindings with expression / two-way binding: prjEnabled: "=" and prjRequired: "=".
Thus, when AngularJS sees <prj-component prj-enabled="false">, it provides directly a boolean in the directive's scope.
Nice touch: with such literal values, it creates no binding, so no performance hit.
I have to cope with absent attribute, so I added in the link function of the directives something like:
if (scope.prjEnabled === undefined) {
scope.prjEnabled = true;
}
And the usages become: if (!$scope.prjEnabled), or in templates: <div ng-show="prjRequired">.
So far, so good. But we also have explicit bindings: <prj-component prj-enabled="{{someScopeValue}}"> or even <prj-component prj-enabled="{{foo.canEdit && bar.valid}}">.
Since we have two-way bindings, I just replaced them with: <prj-component prj-enabled="someScopeValue"> or <prj-component prj-enabled="foo.canEdit && bar.valid">.
Well, if someScopeValue is undefined, the directive will detect that and provide the default value, which will go up to the parent scope. Annoying, but in most cases, probably harmless. The real value is generally provided later (eg. after a network request).
But sometime, I have the following error in the console:
https://code.angularjs.org/1.4.6/docs/error/$compile/nonassign?p0=foo.canEdit%20%26%26%20bar.valid&p1=prjComponent
I followed the advice given in the corresponding page, replacing the bindings with "=?", but I still have the error.
Ideally, I would replace these bindings with "<?", avoiding the first issue (overwriting the value in the parent's scope) and the second one (no assignment to an expression).
But this needs AngularJS 1.5+, and I cannot upgrade the version in the current project.
So how can I solve this problem?
It was tricky and I had to try various solutions, until I saw the light and remembered that with "=?" binding, there is a way to distinguish an absent attribute from one with an undefined value.
I rewrote the default value assignment as such:
if (!("prjEnabled" in scope)) { scope.prjEnabled = true; }
If you are not familiar with the in operator, it is roughly equivalent to scope.hasOwnProperty("prjEnabled"), but it also checks the prototype hierarchy. I use it mostly because it is nicer / terser than the function call...
I made a Plunkr application (out of an official AngularJS one) to reproduce the error, to see when it happens, and to explore various cases: http://plnkr.co/edit/Z2AIag?p=preview
It shows that with "=" binding and assigning default value if undefined, the error appears when the attribute is empty (an unlikely case) or when AngularJS silently captures an exception (here wrong.scopeValue && wrong.unknown where $scope.wrong doesn't exist, causing the ReferenceError exception) and provides undefined as value.
With "=?", we still have the second issue (the empty attribute value case doesn't throw).
If we test with the "x" is scope method, we get rid of this annoying case, quite common when wrong is a variable filled later (network update, etc.).
The only case where the error can still happen is if we assign a value to the bound attribute, and only in the wrong case.
How to manually create one-way binding with default value:
app.directive("myDirective", function() {
return {
scope: {
//oneWay: "<?"
},
link: postLink
};
function postLink (scope, elem, attrs) {
if (!attrs.oneWay) {
scope.oneWay = defaultValue;
} else {
scope.$watch(attrs.oneWay, function(value) {
scope.oneWay = value;
});
};
}
})
For versions of AngularJS (<1.5) that do not have one-way binding, it can be implemented with a watcher.
I tried with below methods
1) using isEnabled api
expect(locator.isEnabled()).toEqual('false');
but it fails, as the API always returns true.
2) using disabled attribute
expect(locator.getAttribute('disabled')).toEqual('disabled');
but it fails, as the API returns null
The disabled attribute is of type boolean so will return true or false.
isEnabled schedules a command to query whether the DOM element represented by this instance is enabled, as dicted by the disabled attribute.
Therefore in your situation the following assertion should be used when using isEnabled:
expect(locator.isEnabled()).toBe(false);
getAttribute schedules a command to query for the value of the given attribute of the element. It will return the current value. getAttribute returns a string and as the disabled attribute is considered a boolean, it will return 'true' of type string or null.
When using getAttribute you should use the following assertion:
expect(locator.getAttribute('disabled')).toEqual('true');
As I learned, there are two ways to archive element disable in web development.
1) use disabled attribute, only work on Form Control element
Note: If a Form Control element defined disabled attribute, no matter the attribute value is 'disabled', 'true', 'false', '1234' or anything else. Selenium isEnabled() will always return true.
In such case you can use: expect(locator.isEnabled()).toEqual()
2) use CSS & JavaScript, work on any HTML element
Note: In most time, using CSS & JavaScript way to archive disable goal, developer will add class name like: 'invalid', 'disable', 'ng-invalid' in element attribute class.
In such case, you can use: expect(locator.getAttribute('class')).toContain('xxx')
You need to find out which way developer used on the element before you can decide how to write script to check it.
I'm really happy with the "new" $onChanges method you can implement in a component's controller. However it only seems to be triggered when the bound variable is overwritten from outside my component, not (for instance) when an item is added to an existing array
It this intended behaviour or a bug? Is there another way of listening to updates to my input bindings, besides doing a $scope.$watch on it?
I'm using Angular 1.5.3
First TL;DR
For an array that is bounded via one-way binding, a watch expression is added that does not check for object equality but uses reference checking. This means that adding an element to the array will never fire the '$onChanges' method, since the watcher will never be 'dirty'.
I've created a plnkr that demonstrates this:
http://plnkr.co/edit/25pdLE?p=preview
Click the 'add vegetable in outer' and 'change array reference in outer' and look at the 'Number of $onChanges invocation'. It will only change with the latter button.
Complete explanation
To fully grasp what is going on, we should check the angular code base. When a '<' binding is found, the following code is used to set up a watch expression.
case '<':
if (!hasOwnProperty.call(attrs, attrName)) {
if (optional) break;
attrs[attrName] = void 0;
}
if (optional && !attrs[attrName]) break;
parentGet = $parse(attrs[attrName]);
destination[scopeName] = parentGet(scope);
// IMPORTANT PART //
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
var oldValue = destination[scopeName];
recordChanges(scopeName, newParentValue, oldValue);
destination[scopeName] = newParentValue;
}, parentGet.literal);
// ------------- //
removeWatchCollection.push(removeWatch);
break;
The important part here is how the 'scope.$watch' expression is set up. The only parameters passed are the parsed expression and the listener function. The listener function is fired once the '$watch' is found dirty in the digest cycle. If it is fired, the listener will execute the 'recordChanges' method. This records an '$onChanges' callback task that will be executed in the '$postDigest' phase and notify all components that are listening for the '$onChanges' lifecycle hook to tell them if the value has changed.
What's important to keep in mind here, if the '$watcher' is never dirty, the '$onChanges' callback is not triggered. But even more importantly, by the way the '$watch' expression is created, it will NEVER be dirty, UNLESS the reference changes. If you wanted to check for equality between objects instead of reference, you should pass an extra third parameter that asks for this:
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression)
As this is not the case here with the way the one way binding is set up, it will ALWAYS check for reference.
This means, if you add an element to an array, the reference is not changed. Meaning the '$watcher' will never be dirty, meaning the '$onChanges' method will not be called for changes to the array.
To demonstrate this, I've created a plnkr:
http://plnkr.co/edit/25pdLE?p=preview
It contains two components, outer and inner.
Outer has primitive string value that can be changed through an input box and an array that can be extended by adding an element or have its reference changed.
Inner has two one-way bounded variables, the value and the array. It listens for all changes.
this.$onChanges = setType;
function setType() {
console.log("called");
vm.callCounter++;
}
If you type into the input field, the '$onChanges' callback is fired every time. This is logical and expected, since a string is primitive so it cannot be compared by reference, meaning the '$watcher' will be dirty, and the '$onChanges' lifecycle hook fired.
If you click the 'Add vegetable in outer', it will execute the following code:
this.changeValueArray = function() {
vm.valueArray.push("tomato");
};
Here we just add a value to the existing bounded array. We're working by reference here, so the '$watcher' is not fired and there is no callback. You will not see the counter increment or the 'called' statement in your console.
Note: If you click the 'Add something to the array' inside the inner component, the array in outer component also changes. This is logical, since we are updating the exact same array by reference. So even though it is a one-way binding, the array can be updated from inside the inner component.
If you change the reference in the outer component by clicking 'Change array reference in outer', the '$onChanges' callback is fired as expected.
As to answer your question: Is this intended behaviour or a bug? I guess this is intended behaviour. Otherwise they would have given you the option to define your '<' binding in a way that it would check for object equality. You can always create an issue on github and just ask the question if you'd like.
Why if I define a constant object like
app.constant('TEST', { title: 'Hello' });
and I make this in controller
var x = TEST;
x.title = 'Hello World';
console.log(TEST);
The value of constant has been change?? I read angular's doc and it says that constant cannot change their value.
The value of the constant can't be changed if it is a primitive value (ie, 1, true, "appName"). However it is an issue with Angular 1.x that if the constant is an object, it's properties remain assignable. Semantically, yes, we would expect these values to never change, but the framework is not doing any wrapping of the provided object to prevent a developer from mutating it.
You can read a bit more here:
http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/
I got an object like:
$scope.project = {name: 'whatever', description: 'blabla', another: 'another'};
To debug this, I enter in repl mode and try to see what "project" has.
When I define project variable as below, and call it, it returns my object, but when I try to access its keys (project.name), I get undefined. If I do Object.keys(project) I am getting the page object methods like click, getAttribute, etc.
Any ideas on how can I have access to the original object keys?
View side:
<h1 id="foo">{{project.name}}</h1>
Test side:
var project = element(by.id('foo')).evaluate('project');
evaluate uses executeScript behind the scenes. It returns an ElementFinder which resolves to the object you are looking for:
var project;
element(by.id('foo')).evaluate('project').then(function(value) {
project = value;
});
The documentation says:
which resolves to the evaluated expression for each underlying
element. The result will be resolved as in
webdriver.WebDriver.executeScript. In summary - primitives will be
resolved as is, functions will be converted to string, and elements
will be returned as a WebElement.
Also, check out Accessing Angular inside Protractor Test
Edit: syntax error