Angular ng-show handling undefined variables - javascript

I've two directives which display based on a variable being configured.
This visually worked but during acceptance tests I found a problem. I attempted to assert that if the <settingElement> hasn't been clicked (configuring the var selected), neither directive is displayed. The test passed for <directiveA> but failed for <directiveB> leaving me a little confused.
An example of the code is below:
<settingElement ng-click="selected = trueOrFalse()"></settingElement>
<directiveA ng-show="selected"></directiveA>
<directiveB ng-show="!selected"></directiveB>

Probably a better way of doing this would be to define selected in a parent of both elements, such as:
<div ng-init="selected = false;">
<settingElement ng-click="selected = true"></settingElement>
<directiveA ng-show="selected"></directiveA>
<directiveB ng-show="!selected"></directiveB>
</div>
Another "shorter workaround" would be
<settingElement ng-click="selected = true"></settingElement>
<directiveA ng-show="!!selected"></directiveA>
<directiveB ng-show="!selected"></directiveB>

It is probably due to the expression triggering with undefined values
try with ng-if ="selected" or ng-show="selected==true"

Success! At least kinda, visually the code works so instead of testing for isDisplayed I simply asserted that the height and width are both 0... not ideal but works.

Related

JsFiddle Javascript not firing?

I've got a huge feeling I'm simply being idiotic here, but is it me or is Js not firing in JsFiddle?
I built this snippet here but can't seem to get it to fire. Im probably missing something super obvious, but would be grateful for any assistance.
HTML
<div id="col">
<h3 class="txt spacer">Dynamic input, based on select value...</h3>
<input type="text" name="field-one" class="txt stretch" />
<div class="boxes">
<input type="checkbox" id="box-2" onChange="myFunction()" checked>
<label for="box-2">Apply a name?</label>
</div>
</div>
</div>
Javascript
function myFunction() {
// Text field element.
var a = document.getElementByName('field-one');
// Checkbox element.
var b = document.getElementById('box-2');
if (b.checked) {
a.disabled = false;
a.placeholder = 'Not Applicable';
alert('Checkbox State Changed.');
} else {
a.disabled = true;
a.placeholder = 'Enter Your Full Name';
alert('Checkbox State Changed.');
}
}
JS FIDDLE
Thanks in advance for the assistance.
Regards,
-B.
EDIT
I'm an idiot. Thanks guys.
Couple of things here, it should be document.getElementsByTagName (plural) and you need to change the settings, that will embed the JS code in before the body closes.
var a = document.getElementsByName('field-one')[0];
I am using [0] here to access the very first element of the array as document.getElementsByName() will return you an array of elements if encountered multiple matching elements. If you want to select a specific one, make sure you select the DOM element in a more specific way.
And change the Load Type to
No wrap - in <body> (<head> will work as well)
Demo
The Javascript Load Type for your jsFiddle is set to onLoad. This will nest your myFunction function, and therefore won't be available in the global scope.
You can either change the Load Type, or set the variable on the window:
window.myFunction = function()....
NB: You then receive another error for getElementByName. This isn't a function of document. You're probably looking for getElementsByName.
Open the DevTools and you'll see
Uncaught ReferenceError: myFunction is not defined
at HTMLInputElement.onchange ((index):192)
Now why isn't the function defined? Perhaps it's not defined at the right spot?
Look at where you load your JavaScript and change it to:
Here's an updated fiddle. Note that I fixed a typo on line 3 getElementsByName.
First, set your seeting of javascript LOAD TYPE to head.
last, is getElementsByName not getElementByName and it would return array so you need to parse.
let a = document.getElementsByName('field-one')[0];
hope it's helpful.

by.buttonText('') does not return empty texted buttons as protractor locator

I have to test an application that has some buttons with no text, only graphics (these are < and > buttons on a datepicker, for instance, not in my control).
If I try to find these by:
elm = element.all(by.buttonText(''));
backButton = elm.get(1);
I get no immediate error, but if I say backButton.click() I get:
Error: Index out of bound. Trying to access element at index: 1, but there are only 0 elements that match locator by.buttonText("").
On the other hand if I say
element.all(by.tagName("BUTTON")).filter(function (elem) {
return elem.getText().then(function (text) {
//find the empties
return !(text);
//
});
}).then(function (EmptyButtons) {
EmptyButtons[1].click()
I get a fun error suggesting I found one that I clicked did not pop up yet:
JavascriptError: Element is not currently visible and may not be manipulated (status: 11)
but that is on me to figure out. If I track the text as it comes through the filter, I find there are multiple text-less buttons placed in the array.
I am sorry I do not yet have the skills to generate and test a small example, but I hope perhaps some helpful reader does.
Anyway, I suppose it is possible I am missing something subtle about promise resolution or that the desired behavior is you cannot find a textless button by looking for empty text. I have a workaround, too (actually multiple ones - see my comment below). But I would welcome some clarification here if someone can give it.
Best,
Jeremy Kahan
p.s. I do not see this as a duplicate of "How to click a button with no text in protractor" inasmuch as that wrestles with distinguishing textless buttons, and my question is not how to find and distinguish them, but why searching for text '' does not find them. [My code even suggests how that could have been or could still be implemented, I think.]
From what I understand, you have good workarounds and ways to solve the problem without using by.buttonText and only need an explanation why by.buttonText('') does not work.
If we look into how the by.buttonText is implemented, we would see it first finds only the following "button" variations:
button, input[type="button"], input[type="submit"]
Which means that if the element you are looking for is not the button or appropriate input element, you would not get it matched by the locator.
And, judging by the workaround, the desired elements in your case are actually span elements.
Aside, from that, matching by the empty "button text" should work.
Tested on the datepicker used in our internal application under test - worked for me. Here is how the "<" button looks in our case:
<button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1" ap-tab-nav-idx="1.2" ap-tab-nav-child="true">
<i class="glyphicon glyphicon-chevron-left"></i>
</button>
And, if I mimic the "by.buttonText" client side script logic in the console, I would get this element passing all the conditions which determine whether to pick an element or not:
> var element = $("button[ng-click='move(-1)']")[0]
> elementText = element.textContent || element.innerText || '';
> elementText.trim() === ""
true

Polymer - Get data-bound attribute value in repeating template

I'm having a bit of an issue here. I had a small amount of success with event.target.templateInstance.model.thing syntax to get the value of attributes from within a repeating template but I keep getting back undefined from this bit of code I have here:
downloadFunction: function (e) {
console.log("dl function clicked");
//get particular id of thing
var fu = e.target.templateInstance.model.s.soundId;
console.log(fu);
//^ returns "TypeError: Cannot read property 'soundId' of undefined"
}
And my repeating template is here:
<div layout horizontal wrap center center-justified>
<template repeat="{{s in carddata}}">
<sound-card image="{{s.imgurl}}"
quotetext="{{s.quote}}"
soundsrc="{{s.soundurl}}"
soundref="{{s.soundId}}"
downloadfunction="{{downloadFunction}}">
</sound-card>
</template>
</div>
Where carddata is just an array with my data in it. All of the values are generated fine so I know it's not an issue with my array. I'm just confused how exactly I'm supposed to target someting from within the repeating template? Am I calling it at the wrong time? Or am I messing up the syntax of the templateInstance bit?
If it matters, I'm trying to get it to work in an Android 4.4 webView using Apache Cordova. 4.4 webView doesn't appear to enjoy the shadowDOM terribly much.
Thanks!
edit: After some jiggery pokery with console logs, it appears that the sender value is referring to the div that I apply the on-click="{{downloadFunction}} to. Here's the template that I am repeating, if this provides any insight.
<div class="soundcard-container" vertical layout>
//can't target this one either on WebView 4.4, works on ChromeOS
<img src="{{image}}" on-tap="{{playAudio}}">
<div class="soundcard-bottom-container" horizontal layout center justified>
<span>{{quotetext}}</span>
//I have an 'a' tag for desktop browsers and the div tag is targeting my Android/iOS
//apps that I am exporting as a webView to using Apache Cordova. Webonly is hidden
//at the point where I'm trying to get my downloadfunction to work.
//console.log(sender) in my downloadfunction returns this div v
<div on-tap="{{downloadfunction}}" class="mobileonly"></div>
</div>
//just a hidden audio thing for web
<div style="display: none">
<audio id="{{soundref}}" src="{{soundsrc}}" controls preload="auto"></audio>
</div>
</div>
edit2 some console logs..
console.log(sender) and console.log(event.target) are both the same div that has the on-click event for my downloadFunction.. not sure if this should be the case.
console.log(e.target.templateInstance.model) returns my <sound-card> object, I believe like it should(?)
It's just when I add the specific .s.soundId that it's undefined. I'm not sure why it's unable to find it.. Maybe there's another way to get the specific soundId (or s.soundId rather) of that particular <sound-card> object?
I'll bet you want to refer to the "sender" of the event—not e.target. See the part about inSender at https://www.polymer-project.org/0.5/docs/polymer/polymer.html#declarative-event-mapping:
inSender: A reference to the node that declared the handler. This is
often different from inEvent.target (the lowest node that received the
event) and inEvent.currentTarget (the component processing the event),
so Polymer provides it directly.
This might fix it:
downloadFunction: function (e, detail, sender) {
console.log("dl function clicked");
//get particular id of thing
var fu = sender.templateInstance.model.s.soundId;
console.log(fu);
}
Alright I was able to fit this in a different way. I wasn't able to get e.target.templateInstance.model.s.soundId bit to work, so instead on the div that I call the event on (event.target) I gave it an attribute called data-soundid and passed it {{soundref}} from my original template and then where I repeat that template I simply made a function like so:
downloady: function (e) {
console.log(e.target.getAttribute('data-soundurl'));
}
Ta da! Very nice solution. Thanks to Eyal who suggested this to me in a previous question. It works like a charm. :-)
Here is working example of using templateInstance, with included selecting by dynamic ID: Plunk .
As for your code, can't tell why it's not working.
handleEvent: function(e, detail, sender) {
console.log('handleEvent');
console.log(sender.id);
//How to catch full_obj here,
//..as if first item is clicked: full_obj = {"firstName":"John", "lastName":"Doe"}
//console.log(e);
console.log(e.target.templateInstance.model.item.firstName);
//console.log(detail);
//console.log(sender);
this.instance_firstName = e.target.templateInstance.model.item.firstName;
this.instance_lastName = e.target.templateInstance.model.item.lastName;
//Selecting by dynamic ID
var clicked_element = this.shadowRoot.querySelector('#'+this.instance_firstName);
console.log('Selecting');
console.log(clicked_element);
//var second_element = sender.templateInstance.model.querySelector('my-second-element');
//var second_element = this.$.second_element;
}
Edit:
Event handling and data binding Docs

Change the contents of the element's onclick function

I know it is possible to change different parts of an element such as class, value and html content.
What I am looking for is a way to change the values of the function call itself.
document.getElementById(id).onclick = "javascript:FUNCTION('"+id+"','0');";
The second pass value is what I will be changing. So value "0" can be for example "1" or "2"
Is this possible?
EDIT:
In case this gets going down the wrong road.....
This is what I will be changing with that code.
So then the page ( and script ) is ready to run again with new values
<div id="id" class="stuff" onclick="javascript:function('id','0');" style="cursor:pointer;">
hope that clarifies things a bit more.
RE-EDIT:
Well seems this might not be possible. So will start looking at ways round.
It depends on what you are trying to do, but here is a way of doing it which would not even require the changing of the function.
var myValue = 0
document.getElementById("test").onclick = function (aEvent) {
myValue++;
alert(myValue);
}
See fiddle: http://jsfiddle.net/AqZHZ/

JSF Set CSS Style Using Javascript?

I have been looking with no success to see if I can dynamically apply a css style to JSF component or div using javascript. Is this possible.
This is pseudo code
<div style="myJSStyleFunction("#{myBean.value}")"> stuff </div>
And the function would return something like "position:relative;left:25px;"
I've had no luck and maybe it can't be done but would like a second opinion.
Edit:
I'm trying to see if I can keep a separation / reduce the coupling between the presentation/view and the model/controller. This is for indenting commenting or product reviews (to nest replies to comments or reviews). The most I really want to track is an integer on how deep a reply is. First level = 0 second level = 1, and so on. So a comment or product review would be 0 deep, a reply to the comment or review would be 1 and so on.
Then in the EL I wanted to call a javascript function and do something like
<script>
myJSStyleFunction(depth){
if(depth<=5){
var nest=20*depth;
var style="position:relative;left:" + nest + "px;";
return style;
}
}
</script>
And then then say for a third level comment (a reply to a reply) it would look like this:
<div style="position:relative;left:40px;"> stuff </div>
where
#{myBean.value}
evaluates to 2
I suspect like Daniel says I'll have to tightly couple the view but I'd rather not have to. I'd think there has to be a way. But maybe not.
I don't know where there are cleaner solutions for this. However this is one suggestion.
Assume your page looks like below and myBean.getValue() method returns an integer.
<h:form id="frm">
<div style="#{myBean.value}"> div1 </div>
<div style="#{myBean.value}"> div2 </div>
</h:form>
So you can do something like this at 'window.onload'.
<head>
<script>
window.onload = function() {
var childList = document.forms['frm'].childNodes;
for(var i = 0; i < childList.length; i++) {
if(childList[i].nodeName == 'DIV') {
var _div = childList[i];
var depth = _div.getAttribute('style');
_div.setAttribute('style', 'position:relative;left:' +(depth *20)+ 'px;');
}
}
}
</script>
</head>
Note: 1. In above sample code I assume all the DIVs inside the form should be indented.
2. For IE you may need to use _div.style.setAttribute('cssText','position:relative;left:' +(depth *20)+ 'px;')
3. Another solution for your question is using <script> tags immediately after your divs and putting the js part inside them. In this way you don't have to use fake styling style="#{myBean.value}" or window.onload event because you can directly call #{myBean.value} in your script.
I decided to skip the javascript approach and settled on a simpler and I think cleaner method to create the dynamic css classes for my situation. I already capture/calculate the depth value for each comment when it is entered. So I am just returning that value in EL and concatenating it to a 'base name' for the css class like so:
<div class="indent_#{(comment.commentDepth le 5) ? comment.commentDepth : 5}" >
comment comment blah blah blah
</div>
"indent_" is the base name for the css class. So for a 0 level comment it will have a class="indent_0". A reply to that comment will have class="indent_1".
I use the ternary so that if there are lot of replies under a given comment it doesn't indent right off the right hand side of the page. Even though you can keep going deeper, it will only indent up to 5 levels.
For my case at the moment, this is a simpler and cleaner method of adding some dynamically generated css class names. Right now I have to define 6 classes for this in the css file, but perhaps I'll figure out how to nest the boxes but it isn't a priority this works just fine for me for now.

Categories

Resources