How to process an array (or manually process ng-repeat)? - angularjs

Using: AngularJS v1.3.15
Disclaimer: I know virtually nothing about angularjs. But I'm "forced" to use it because its being used in a framework that I am using.
I want to modify some html/angularjs that looks like this:
<ul>
<li ng-repeat="provider in model.externalProviders">
<a class="pure-button" href="{{provider.href}}">{{provider.text}}</a>
</li>
</ul>
I can see what is going on here... ng-repeat causes an iteration on the elements of the model.externalProviders collection/array. It works fine, but I have no control over content/styling individual <a> elements depending on the provider. I would like to change the content/appearance of the <a> element depending on type.
The relevant part of the model looks like this:
"externalProviders": [
{
"type": "Google",
"text": "Sign-in with Google",
"href": "https://localhost:44302/external?provider=Google&signin=04e029cf1018403f1757b097fbfb1ecb"
}
],
So I thought maybe there is a way to "select" or "pick" from externalProviders by type... If that type exists, then render the appropriate markup, e.g.:
<ul>
<!-- if model.externalProviders has item with type=="Google"... -->
<li>
<a class="pure-button button.google" href="<i class="fab fa-google"></i>{{provider.href}}">{{provider.text}}</a>
</li>
<!-- if model.externalProviders has item with type=="Facebook"... -->
<li>
<a class="pure-button button.facebook" href="<i class="fab fa-facebook"></i>{{provider.href}}">{{provider.text}}</a>
</li>
</ul>
Not sure what the proper search terms would be so I had trouble finding any info that might solve my problem. Is something like this possible with AngularJS? If so, how would I accomplish it?

As #Major Sam commented, the ngClass might work for less simple scenarios, but I don't even need to go that far. Luckily I have control over the type property and the css, so I can make my type and css selector match the font awesome icon class selector for the icon. This works:
<li ng-repeat="provider in model.externalProviders">
<a class="pure-button button-{{provider.type}}" href="{{provider.href}}"><i class="fab fa-{{provider.type}}"></i>{{provider.text}}</a>
</li>
Drawbacks: Doesn't allow you to change the actual markup (like, e.g., not include the icon if font awesome didn't have one for that provider).

Related

Scraping based on "nested property"

After having created a few different spiders I thought I could scrape practically anything, but I've hit a roadblock.
Given the following code snippet:
<div class="col-md-4">
<div class="tab-title">Homepage</div>
<p>
<a target="_blank" rel="nofollow"
href="http://www.bitcoin.org">http://www.bitcoin.org
</a>
</p>
</div>
How would you go about selecting the link that is in within <a ... </a> based on the text within the tab-title div?
The reason that I require that condition is because there are several other links that fit this condition:
response.css('div.col-md-4 a::attr(href)').extract()
My best guess is the following:
response.css('div.col-md-4 div.tab-title:contains("Homepage") a::attr(href)').extract()
Any insights are appreciated! Thank you in advance.
Note: I am using Scrapy.
How about this using XPath:
response.xpath('//div[#class="tab-title" and contains(., "Homepage")]/..//a/#href')
Find a div with class tab-title which contains Homepage inside, then step up to the parent and look for a child on any level.
EDIT:
Using CSS, you should be able to do it like this:
response.css('div.tab-title:contains("Homepage") ~ * a::attr(href)')

Bootstrap dropdown header using ngRepeat

I want to populate a BootStrap dropdown using ngRepeat. I can populate the items just fine but what if I want to have one header per iteration?
<ul class="dropdown-menu">
<li class="dropdown-header">Dropdown header 1</li>
<li ng-repeat="order in orders">{{order.name}}</li>
</ul>
With this I can get only one header understandably.
Use ngRepeatStart and ngRepeatEnd
<li ng-repeat-start="order in orders" class="dropdown-header">Dropdown header {{$index}}</li>
<li ng-repeat-end="order in orders">{{order.name}}</li>
You can use ng-repeat-start and ng-repeat-end.
<ul class="dropdown-menu">
<li ng-repeat-start="order in orders" class="dropdown-header">Dropdown header {{$index}}</li>
<li ng-repeat-end>{{order.name}}</li>
</ul>
You also don't have to write: ng-repeat-end='order in orders'. you can just mention ng-repeat-end
Read more about it here: ng-repeat start and end
You didn't specify, but I assume you're using the jQuery library that goes along with it. I would highly suggest using Angular UI Bootstrap instead. They've taken care of this for you. I'm sure there are other components you'd like to use as well.
https://angular-ui.github.io/bootstrap/#/dropdown
Update I now realize I misunderstood your question, but I would still take a look at the Angular UI Boostrap library if you're not using it already.

Angular Directive with Replace=false creates list html that is non semantic

We are thinking about moving to directives that use replace=false, as I hear replace=true is being deprecated. One problem I cant figure out how to fix, is for all directives that are based on lists, the resultant html is not semantic.
For example a list directive might be something like:
<tg-list tg-compact="true">
<tg-list-item>foo</tg-list-item>
<tg-list-item>bar</tg-list-item>
</tg-list>
this will currently (with replace=true) compile to:
<ul class="o-list o-list--compact">
<li class="o-list-item">
foo
</li>
<li class="o-list-item">
bar
</li>
</ul>
with replace=false we will end up with either:
<tg-list class="o-list-icon o-list-icon--compact">
<ul>
<tg-list-item>
<li class="o-list-item">foo</li>
</tg-list-item>
<tg-list-item>
<li class="o-list-item">bar</li>
</tg-list-item>
</ul>
</tg-list>
or:
<tg-list class="o-list-icon o-list-icon--compact">
<tg-list-item>foo</tg-list-item>
<tg-list-item>bar</tg-list-item>
</tg-list>
The first is bad because it is definitely non semantic, the second is bad because although it is "semantic" (as in html5, custom tags are ok), we lose anything that ul>li would have given us (a screen reader, for example, may have special ways to treat them).
Perhaps we can use the html5 'role' attributes? But if so i think the list item would need add the role to the element in the link function?
Dunno, but how have you lot solved this?
You can configure directives to have restrict set to A which stands for attribute. That way you can use it as:
<ul tg-list tg-list-compact=true>
<li tg-list-item>
foo
</li>
<li tg-list-item>
bar
</li>
</ul>
So what I did in the end is make the list-item wrap itself in an "li" in its link function.
The list-item link:
public link = ($scope: angular.IScope, $element: angular.IAugmentedJQuery) => {
$element.wrap('<li></li>'); }
Pretty simple in the end, but makes the html much more symantic so i am happy. Shout out to Sander for the help

selecting the search result in watir

Currently i need to search a text and select the expected result from the search list.Search result displays with li tag and the text resides under span tag. Searching text will be like this Phone,Phone-audio,Phone-video .My source code is displaying like below..Please help me to select either Phone-audio or Phone-video.
<ul>
<li class="Searchitem"></li>
<span value="AL">Phone</span>
<li class="Searchitem"></li>
<span value="AL">Phone</span>-audio
<li class="Searchitem"></li>
<span value="AL">Phone</span>-video
</ul>
That html doesn't make any sense, and definitely isn't per standard. Neither the span nor the -audio or -video are inside the li tags. I'm not even sure what element you need to click.
If it was html was compliant it would be as easy as:
browser.span(text: 'Phone-video').when_present.click
or maybe this would work, depending on the rest of the site's html, but it would be very flaky if anything changed:
browser.ul.span(index: 1).when_present.click

correct strategy to generate html dynamically with angular

I have a huge JSON object tree with two levels. First level has around 500 elements, and each element contains an average of 100 child elements.
I want to display the first level of the tree and I am doing it with a simple ng-repeat. When the user clicks on the element I want to display the child elements of that element. If I use a span ng-switch or a ng-show to show/hide child elements when the page first renders it freezes for around 10 seconds while generating all the HTML.
It doesn't sound like the right solution. There must be a different way of doing it, but I can't figure out. Anyone knows?
I have explained most in my comment, and here is a working plunker:
http://plunker.co/edit/RSZwfLlsCJ68MUkACbdp?p=preview
the new ng-if directive will do what you want
<h1>ng-if</h1> <h5>Click on the level to expand</h5>
<div class="well">
<ul class="nav nav-list" ng-repeat="(attr,element) in tree">
<li ng-click="expand=!expand" ng-class="{'active':expand}"><a>{{element.name}}</a></li>
<ul ng-if="expand" class="nav nav-list">
<li ng-repeat="item in element.items">{{item.name}}</li>
</ul>
</ul>
</div>
you can also do this in the "old-way" with ng-show using new ternary operator or its alternative expr && if_true || if_false
<h1>old-way</h1> <h5>Click on the level to expand</h5>
<small>use ternary operator or <pre>expand && element.items || []</pre></small>
<div class="well">
<ul class="nav nav-list" ng-repeat="(attr,element) in tree">
<li ng-click="expand=!expand" ng-class="{'active':expand}"><a>{{element.name}}</a></li>
<ul ng-show="expand" class="nav nav-list">
<li ng-repeat="item in (expand ? element.items : [])">{{item.name}}</li>
<!--<li ng-repeat="item in (expand && element.items || [])">{{item.name}}</li>-->
</ul>
</ul>
</div>
See this answer on ng-repeat performance. Essentially, it just takes a long time since Angular's ng-repeat, and basically all other directives, are set up to always look for updates in the whole JSON structure. So if you have lots of data and don't need live updates in the HTML view when changing the JSON, I wouldn't recommend using AngularJS. Generally, AngularJS performance also depends a lot on the browser and its JavaScript engine.
Alternatively, you could divide your JSON into subparts and then use pagination to display it.
I would recommend to fetch the data gradually from the server. Use server-side pagination and retrieve only the fields that you are going to display. Then, when a user clicks on one of the first level items, you can do another XHR to the server with the new data. I had similar requirements for a project and that solved the latency issue.
Regards,
Agustin.

Resources