Finally, we will call a new method on theparkData
provider (park-data.ts) to do the actual filtering based on thesearch string, and set the results to the parks:
getFilteredParks(queryString) {
return this.load().then(Parks =
>
{
let theFilteredParks: any = [];
for (let thePark of Parks) {
if (thePark.name.toLowerCase().indexOf(queryString.toLowerCase())
>
-1) {
theFilteredParks.push(thePark);
}
}
return theFilteredParks;
});
}
We first make sure we have the master park data again, then we define a new empty array that will push any matching parks onto it. The code then loops through each park and compares the park’s name against the query string. This code does take an additional step, forcing both the park name and query string to lowercase before testing if it can find a match. If a match is found, it is pushed totheFilteredParks
array. Once all the parks have been examined, this array is returned and our displayed list automatically updated.
Our search is still not quite functionally complete. The clear button is not working. Although we bound theionClear
event to aresetList
function, we haven’t written it yet. The function is actually quite simple; we just need to reset ourparks
array back to the full list:
resetList(event) {
// Reset items back to all of the items
this.parkData.getParks().then(theResult =
>
{
this.parks = theResult;
})
}
With that, we should have a fully functioning search bar in our app.
Theming Our Application
Now that we have a functioning app, it certainly could use a touch of color to brighten it up from the default look. There is nothing special about styling an Ionic-based application. The same techniques used in styling a web app or web page apply here.
Ionic uses Sass or Syntactically Awesome Style Sheets as the CSS pre-processor solution. If you have not used a CSS pre-processor before, one of the main reasons to do so is it gives you the ability to abstract your CSS in reusable parts. For example, you can define a variable that is the app’s primary color, and let the preprocessor apply it throughout the CSS. So, if you need to change it for some reason, you change it in one place.
Ionic breaks down its Sass files into two files;_src/app/app.scss_for any global app specific styling, and_src/theme/variables.scss_for the predefined theme colors.
The first styling change to make is to assign a forest green color to the header. There are several ways accomplish this: we could directly style each specific component, or modify one of the prebuilt themes. For these components, let’s choose the latter solution.
In_variables.scss_file, replace the hex color associated with the primary color with#5a712d
. Since we did not assign a theme to either the header or the tabs components, we need to do so. In each of the three pages, update the<ion-navbar>
to<ion-navbar color="primary">
.
Intabs.html
, replace<ion-tabs>
with<ion-tabs color="primary">
. Saving all these files, run$ ionic serve
. The headers and tabs should now have a forest green background.
Now let’s turn our attention to styling the various content elements in the app. Let’s change the general<ion-content>
background color to a light tan. In the_app.scss_file, add the following CSS:
ion-content {background-color: #f4efdd;}
ion-card-header {background-color: #cfcbbb; font-weight: bold;}
ion-card-content{margin-top: 1em;}
ion-item-divider.item {background-color: #ab903c; color: #fff;
font-size: 1.8rem; font-weight: bold !important;}
.item {background-color: #f4efdd;}
.toolbar-background {background-color: #706d61;}
.searchbar-input-container {background-color: #fff;}
As you can see this CSS is a mix of styling the Ionic components directly, such as theion-content
andion-card-header
, but also setting specific CSS classes. By setting this in theapp.scss, these will be applied throughout the app. If you needed to set the style of a specific page or component, then you would do that within the_.scss_file for that item. Let’s do that now.
The park’s name on the Park Details page is a bit too far down from the header photo, and we need to make sure our header image fills the entire width:
page-park-details {
h1{margin-top: 0;}
img{width: 100%;}
}
Now the<h1>
tag only on the Park Details page will have its top margin set to zero, leaving any other<h1>
tags we might use styled as they normally would be (seeFigure 8-10).
Figure 8-10.Our styled national parks app
The Ionic Framework actually exposes quite a few variables in theirSCSS that you can override.
TIP
If you are ever having trouble applying a style, remember this is all just CSS, and the web inspector in your browser can assist in finding either the target you need to address or uncover a cascading issue.
Virtual Scrolling
One of the greatest impacts on the performance of a hybrid application is the number of DOM elements on a given page. Usually this manifests as an issue when scrolling lists of content. Most users can spot a hybrid application by the poor scrolling performance. Knowing this issue, the team at Ionic focused its efforts on creating a scrolling experience that is as native as possible.
Our app only has 59 items, and each row is rather straightforward in the elements it contains. If we expanded our app to include all the national monuments, our list would exceed 400. At this value, we probably would start to see some stutter and jerkiness in the scrolling.
To address this issue, the Ionic Framework introduced a special set of directives, known collectively as Virtual Scrolling.
WHAT ABOUT COLLECTION REPEAT?
If you used Ionic 1.x, you might be familiar with Collection Repeat. This was the system that was introduced to solve the problem of scrolling large datasets. It has been replaced with the Virtual Scrolling solution.
Instead of creating DOM elements for every item in the list, only a small subset of records (enough to fill the viewport) are rendered and reused as the user scrolls. The Virtual Scroller manages this completely behind the scenes for us.
There are a few differences between a standard list and one that uses Virtual Scrolling. First, the<ion-list>
now has[virtualScroll]
binding to our data. The data given to thevirtualScroll
property must be an array. Second, the<ion-item>
now has a*virtualItem
property, which references the individual item that will be passed into the template.
It is probably easier if you see the revised code for a park’s list:
<
ion-list [virtualScroll]="parks"
>
<
ion-item *virtualItem="let park" (click)="goParkDetails(park)" detail-push
>
<
ion-thumbnail item-left
>
<
img src="assets/img/thumbs/{{park.image}}"
>
<
/ion-thumbnail
>
<
h2
>
{{park.name}}
<
/h2
>
<
p
>
{{park.state}}
<
/p
>
<
/ion-item
>
<
/ion-list
>
Other than replacing the*ngFor="let park of parks"
with the twovirtualScroll
properties, the code remains the same. However, there is one more change we should make to improve our list’s scrolling performance, and that is to replace the<img>
tag with<ion-img>
:
<
ion-img src="assets/img/thumbs/{{park.image}}"
>
<
/ion-img
>
This tag is designed for use specifically with the Virtual Scrolling system. The<ion-img>
tag manages HTTP requests and image rendering. Additionally, it includes a customizable placeholder element which shows before the image has finished loading. While scrolling through items quickly,<ion-img>
knows not to make any image requests, and only loads the images that are viewable after scrolling.
Here are some additional performance tweaks that you might consider making to improve performance:
- Do not change the size of the image once it has loaded.
- Provide an approximate width and height so the virtual scroll can best calculate the cell height.
- Avoid changing the dataset, as it requires the entire virtual scroll to be reset, which is an expensive operation.
Custom List Headers
The Virtual Scrolling system also supports dynamic headers and footers. In this sample, our list will have a header inserted after every 20th record:
<
ion-list [virtualScroll]="items" [headerFn]="customHeaderFn"
>
<
ion-item-divider *virtualHeader="let header"
>
{{ header }}
<
/ion-item-divider
>
<
ion-item *virtualItem="let item"
>
Item: {{ item }}
<
/ion-item
>
<
/ion-list
>
and the supporting function would be:
customHeaderFn(record, recordIndex, records) {
if (recordIndex % 20 === 0) {
return 'Header ' + recordIndex;
}
return null;
}
When applied to our list of national parks, our<ion-list>
becomes:
<
ion-list [virtualScroll]="parks" [headerFn]="customHeaderFn"
>
<
ion-item-divider *virtualHeader="let header"
>
{{ header }}
<
/ion-item-divider
>
<
ion-item *virtualItem="let park" (click)="goParkDetails(park)" detail-push
>
<
ion-thumbnail item-left
>
<
ion-img src="assets/img/thumbs/{{park.image}}"
>
<
/ion-img
>
<
/ion-thumbnail
>
<
h2
>
{{park.name}}
<
/h2
>
<
p
>
{{park.state}}
<
/p
>
<
/ion-item
>
<
/ion-list
>
And in our_park-list.ts_file, we can add the following function to insert the first letter of the park’s name into our custom header:
customHeaderFn(record, recordIndex, records) {
if ( recordIndex
>
0) {
if ( record.name.charAt(0) !== records[recordIndex-1].name.charAt(0)) {
return record.name.charAt(0);
} else {
return null;
}
} else {
return record.name.charAt(0);
}
}
One last piece will be to provide a little bit of styling onion-item-divider
. Make the change in the_park-list.scss_to:
page-park-list {
ion-item-divider {
background-color: #ad8e40;
font-weight: bold;
}
}
So, we now have an app that looks likeFigure 8-11.
Figure 8-11.Virtual Scrolling with dynamic headers applied
Summary
With this app we have explored how to work with a tab-based design, used a data provider, integrated a Google map, and applied some basic theming. If you want to extend this app some, here are a couple of ideas that you can try.
Add a park specific map to the page details screen.
Look at adding a photo slide show to each park. Ionic has an
<ion-slides>
component that can do the trick.If you want a real challenge, look at calculating the distance to each park. There is a distance property already in the_data.json_file. You can leverage the Geolocation plug-in to find your current latitude and longitude, then use the Haversine formula to calculate the distance.