Why can't I access sub-elements of an AngularJS controller? - javascript

I'm guessing this has probably been answered a million times, but I don't have the right language to describe this particular issue. Essentially I can't do something like $ctrl.thing.subelement. The following works just fine:
<div class="col-md-10" ng-repeat="p in $ctrl.patient">
<!--Body content-->
<p>{{p.name}}</p>
<p>{{p.id}}</p>
</div>
This prints the patient name and id -- the behavior I want -- except I don't have the need for a repeat. I want to be able to reference specific patient elements throughout the page.
Similarly, this will print all the patient's information as JSON plaintext (that's how everything is stored).
<div class="col-md-10">
<!--Body content-->
<p>{{$ctrl.patient}}</p>
</div>
What doesn't work is the following (which is what I really want)
<div class="col-md-10">
<!--Body content-->
<p>{{$ctrl.patient.name}}</p>
<p>{{$ctrl.patient.id}}</p>
</div>
What am I missing here? Why am I able to access $ctrl.patient in a div but not $ctrl.patient.name?

Simple because $ctrl.patient is not an object representing a single patient it is an array of patients – which is why you iterate over it. So you need to know which item in the array you want to access by index and then use:
{{$ctrl.patient[0].name}}
Where 0 is the index of the record you want.

Related

Save gridster layout using angularjs

I am creating dashboard application with Gridster: https://github.com/ManifestWebDesign/angular-gridster
I would like to save my layout to database using JSON. What I do know, is that I store charts array to database and fetch it. Is it possible to store widget col,row and size to specific widget so I could then give the size-x and size-y with angular style {{chart.xsize}}. When creating widget I could then assign default size values and save only after user has resized or dragged widget. Or is this completely wrong way to do this? How else I could store the widget sizes and positions to database?
I have ng-repeat for my widgets like this:
<div ng-if="chart.type === settings.types.LINEAR_GAUGE">
<div class="panel c8y" gridster-item size-x="2" size-y="1">
<div class="panel-heading">
<h3 class="panel-title">{{chart.title}}</h3>
<button class="btn btn-danger pull-right btn-xs" ng-click="onClickDelete($index)"><span class="glyphicon glyphicon-remove"/></button>
</div>
<div class="panel-body">
<c8y-linear-gauge dp="chart.dp" measurement="chart.data[0].measurement"/>
</div>
</div>
</div>
I've actually built several angular dashboards using angular-gridster, and save the layout back to the database. Here is how I do it:
<div gridster="gridsterOpts">
<ul>
<li
gridster-item
row="element.posY"
col="element.posX"
size-x="element.width"
size-y="element.height"
ng-repeat="element in elements track by $index">
<div class="element-title">
<!--some header stuff goes here-->
</div>
<div class="element-useable-area">
<!--Main widget stuff goes here-->
</div>
</li>
</ul>
</div>
So I have an array called elements where I am storing my widget objects. Each object in the array includes properties for the height and width and size. So, something like this:
{
posY: 0,
posX: 0,
width: 100,
height: 100,
templateUrl: ...,
data: ...
}
Fortunately because of angular binding, when the user changes the gidster item size or position, it changes the property value too! Its important to note that the elements array is appended to a $sessionStorage object from the ngStorage framework I use, so that all the changes will be persisted if the page refreshes.
Eventually, when the user saves the dashboard, I write an object to the database which includes the elements array. Thus saving all the location and size info. The next time you call that object from the database, and populate the elements array with its data, you get back the same dashboard.

Dynamically creating jQuery UI sliders: I want to set unique ids for both slider handles to give them custom text

I'm dynamically creating jQuery UI sliders, using the following code in a forEach loop:
// Sets an id for the slider handles, gives them a custom class (makes them appear as circles, centers them)
$("#slideRange" + i).find(".ui-slider-handle").attr("id","slideHandle" + i).addClass("customSliderHandle");
// These two lines set the times below the slider handles.
$("#timeSlider" + i).find(".slideStartLabel").text(startTime);
$("#timeSlider" + i).find(".slideEndLabel").text(endTime);
This successfully makes the following slider, with variable times, for every iteration:
What I'd like to do, instead of showing the times below the circular handles, is have the times appear in the middle of the handles. The end result would look like this (photoshopped):
The times will then update, according to the slider values, within the "slide" event callback.
My question is: how do I assign unique id's to the two slider handles, so that I can then set their text independently in the "slide" callback? The code I wrote above gives both handles the same id. Using this id to set text to a handle will only apply to the first handle, leaving me no way to change the text of the second. Using find(), get(), or just $(".ui-slider-handle") are all ways of getting an array of the slider handles, but when I try attr("id","uniqueId") on one of the elements of that array, I get "(...).method(...) is not a method".
Or is assigning unique id's the wrong approach, here? Is there a jQuery or vanilla Js way of setting attributes of one element at a time, when searching by class and getting potentially multiple results?
Also, for context: I'm using find() because I'm using clone() on a markup shell, then appending it to a central div. Here's the shell:
<!-- This hidden div will be cloned, customized, and appended onto .modal-body -->
<div id="timeslotShell" class="timeslot row" style="display:none">
<div class="container-fluid slotContents" id="slotContents">
<span class="col-md-4"> Listing <div class="slotNumber listNum">*Listing No.*</div>: From <div class="slotNumber startNumber slotStartShell"> *Start Time* </div> until
<div class="slotNumber endNumber slotEndShell"> *End Time* </div> </span>
<div class="col-md-8">
<button type="button" class="btn btn-md btn-info slotButton">
Choose A Time From Within These Hours
</button>
</div>
<div id="timeSliderShell" class="collapse timeSlider">
<br><br>
<div id="slider-range" class="slideRange row"></div>
<div id="slideLabels" class="slideLabels slideRange row">
<div class="slideStartLabel"></div><div class="slideEndLabel"></div>
</div>
</div>
</div>
<br><br>
</div>
Cool -- as luck with have it, I found the answer shortly after posting this. Check this post if you're dealing with a similar issue:
Slider Value Display with jQuery UI - 2 handles
All that's needed, in the .find() calls, is
.find(".ui-slider-handle:first").text(value1);
for the first handle and then
.find(".ui-slider-handle:last").text(value2);
for the second.

AngularJS: open and close DOM element during different cycles of ng-repeat

I wonder if it is possible to open DOM element during first cycle of ng-repeat and close it during the second. Let's talk about some list of element, and I want to output it by 2 (or may be 3, 4... it does not matter) in a row. So result html will be like this:
<div class="row"> <!-- add this line during 1sr cycle -->
<div class="element">...element1...</div>
<div class="element">...element2...</div>
</div> <!-- add this line during 2nd cycle -->
<div class="row"> <!-- add this line during 3rd cycle -->
<div class="element">...element3...</div>
<div class="element">...element4...</div>
</div><!-- add this line during 4th cycle -->
The main problem that ng-repeat does not allows me to put line <div class="row"> on first cycle and put closing </div> on second cycle. ng-repeat add both of them during every cycle. Is it possible to find some workaround for this case?
It's not possible with ng-repeat. You have to either prepare the data structure in your controller (multi-dimensional array) or write a custom directive that would take and additional parameter of how many elements of the array take for the current iteration.
UPD: You can use lodash' chunk function to split the array into smaller arrays https://lodash.com/docs#chunk
As you said, you can't output only half an element (e.g. the opening tag without the closing tag) in one repeat cycle.
You can however do different things in one cycle based on the $index property, like showing or hiding a specific element in the first cycle ($index: 0).
Alternatively, have a look at ng-repeat-start and ng-repeat-end to output more than one element in a cycle. More info: https://docs.angularjs.org/api/ng/directive/ngRepeat
I have found simple solution out of the box in angularJs:
<div class="row" ng-repeat="element in elements" ng-if="$index%2==0">
<div class="element" ng-repeat="element in elements|limitTo:2:$index">...</div>
</div>
It will output 2 elements in a row, if you need more, you should adjust ng-if and limitTo argument.

Protractor - How to find an element inside an element when sub element is also a main element somewhere else in a page

<div class="base-view app-loaded" data-ng-class="cssClass.appState">
<div class="ng-scope" data-ng-view="">
<div class="ng-scope" data-ng-include="'partial/navigation/navigation.tpl.html'">
<div class="feedback-ball feedback-ball-show feedback-ball-big" data-ng-class="feedback.cls" data-ng-click="outside($event)" data-feedback-ball="">
<span class="close-button"></span>
<h2 class="ng-binding">Welcome to Garbo</h2>
<div class="ng-scope ng-binding" data-ng-bind-html="feedback.html" data-ng-if="feedback.html">
<p>Here you can play in style in a safe and secure environment.</p>
<p>
<a class="btn" href="/account">My Account</a>
<a class="btn" href="/deposit">Deposit</a>
</p>
</div>
</div>
</div>
I want to find and click /account button inside data-ng-bind-html="feedback.html", I can find data-ng-bind-html="feedback.html" but I could not find account button inside it. when I try to find account button, it gives me error that page has multiple account button so be more specific.
I tried element.().element() but it didnt work, please help
The problem is that webDriver is finding more than one element that matches. You have element for finding just one, and element.all for taking an array of elements, then you can use .get() and the index of the element, or first() or last(). You can do,
element(by.css('[data-ng-bind-html="feedback.html"]')
.element(by.cssContainingText('.btn', 'My account'));
If it doesn't work then you might have more than one, if so, you can use,
element(by.css('[data-ng-bind-html="feedback.html"]')
.all(by.cssContainingText('.btn', 'My account')).first();
But there you will have more than one button in your HTML, webDriver will get only one,
another thing, is to use the count() that gives you the length of the array of elements, and you can know how much you have.
element calls can be chained to find elements inside other elements, so your element().element() solution should work.
Alternatively, you can construct an xpath expression to reach the link inside the appropriate div:
element(by.xpath('//div[#data-ng-bind-html = "feedback.html"]//a[#href = "/account"]'))

If element found, hide only the element after

So let's say I have this scenario of articles:
I have a photo in the left and the content of the article right after the image.
In the content area I have a reservation button.
If the article is reserved, then it will be displayed a small image over the bottom of the photo (transparent written "Reserved").
This stuff is all done.
What I want to do next is to remove the hyperlink-button "Reserve" from the article if it's reserved. Should look like this:
-NormalIMG- [Reservation-Button]
-NormalIMG- [Reservation-Button]
-ReservedIMG- *
-NormalIMG- [Reservation-Button]
-ReservedIMG- *
-NormalIMG- [Reservation-Button]
and so on.
*here's no reservation button
So it's something like this:
Reserve
<!-- reserved article -->
<div class="article">
<div class="image"></div>
<div class="image-reserved"><img src="reserved.jpg" /></div>
<div class="content">
Reserve
</div>
</div>
<!-- reserved article //-->
<!-- unreserved article -->
<div class="article">
<div class="image"></div>
<div class="image-reserved"></div>
<div class="content">
Reserve
</div>
</div>
<!-- unreserved article //-->
<!-- reserved article -->
<div class="article">
<div class="image"></div>
<div class="image-reserved"><img src="reserved.jpg" /></div>
<div class="content">
Reserve
</div>
</div>
<!-- reserved article //-->
I tried with jQuery something like this:
if(!($('.image-reserved').find(img))) {
$('.reserveLink').addCSS('display', 'none');
}
But I got all the "Reserve" links removed...
I realized that I need something that should apply that CSS attribute only after the element 'img' was found.
After that, it should continue the search and apply it when it has to.
I lost all my day trying to figure out a way to get out of this by implementing different structures (using find, has, next, etc.) similar to the above example... but no success.
I'm posting here as a last resort, my hope is completely lost to something that seemed to be so easy to implement...
IMPORTANT NOTE: I know the structure looks weird and it might be really hard for what I want to be implemented, but I am not allowed to modify any code that was written already.
You shoud iterate over each image-reserved :
// For each image reserved
$(".image-reserved").each(function(){
// Count the children
var count = $(this).children("img").length;
// If there's a child (The reserved img), then we delete the following links
if(count > 0){
$(this).next().children(".reserveLink").hide();
}
});
$('.image-reserved').next().hide()
I'd suggest:
$('.content').filter(function(){
return $(this).prev('div.image-reserved').find('img').length;
}).find('a').remove();
JS Fiddle demo.
References:
filter().
find().
prev().
remove().
$('div.image-reserved:not(:empty)+.content a.reserveLink') will find all .image-reserved divs that have content, and select the .reserveLink links in the .content element after them.

Categories