Custom Carousel for Ionic Framework using angular directives

By: Ryan Wong at

I couldn’t find a carousel plugin for ionic framework which can create a lightbox that lets you swipe through images. So I made my own.

Template HTML

1
<carousel images="images" show="show"></carousel>

Page Controller

1
2
3
4
5
6
7
8
9
10
11
function PageCtrl($s){
$s.show = false;
$s.images = [
'http://lorempixel.com/300/200/',
'http://lorempixel.com/300/200/food',
'http://lorempixel.com/300/200/nature',
'http://lorempixel.com/300/200/sports',
];
}
angular.module('controller',[])
.controller('PageCtrl', ['$scope', PageCtrl]);

To trigger carousel, you should bind a ng-click event somewhere to toggle show.

Directive HTML
I called it carousel.html .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="carousel-container" ng-show="show">
<div class="carousel-close" ng-click="close()"><i class="ion-close"></i></div>
<div class="carousel">

<ul class="carousel-list" on-swipe-right="nextSlide()" on-swipe-left="prevSlide()">
<li class="carousel-image" ng-repeat="oneImage in images" style="{{imageStyle()}}">
<img ng-src="{{oneImage}}">
</li>
</ul>

<div class="carousel-arrow-left" ng-click="prevSlide();"><i class="ion-arrow-left-a"></i></div>
<div class="carousel-arrow-right" ng-click="nextSlide();"><i class="ion-arrow-right-a"></i></div>
</div>
</div>

SCSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
.carousel-container{
width: 100%;
position: fixed;
background-color: rgba(0,0,0,0.4);
z-index: 100;
height: 100%;
overflow-x:hidden;
overflow-y:hidden;
.carousel-close{
position: absolute;
right: 5px;
top: 5px;
color: white;
}
.carousel{
margin-top:33%;
position:relative;
min-height: 400px;
overflow: hidden;
margin-left: auto;
margin-right: auto;

ul{
list-style: none;
list-style-type: none;
padding-left: 0px;
position: absolute;
li {
float:left;
position: relative;
img {
width: 90%;
max-width: 90%;
margin-left: auto;
margin-right: auto;
display: block;
}
}
}
.carousel-arrow-left{
width: 20px;
height: 20px;
position: absolute;
top: 25%;
left: 10%;
position: absolute;
z-index: 102;
opacity: 0.5;
i {
font-size: 32px;
color: white;
}
}
.carousel-arrow-right{
width: 20px;
height: 20px;
top: 25%;
right: 10%;
position: absolute;
z-index: 102;
opacity: 0.5;
i {
font-size: 32px;
color: white;
}
}
}
}

Directive Js code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function carousel() {
return {
restrict: 'E',
templateUrl: 'directive/carousel.html',
scope: {
images: "=",
show: "="
},
link: function(scope) {
scope.index = 1;
scope.max = scope.images.length;
scope.firstTime = true;
scope.ratio = Math.round(100/scope.max);
scope.imageWidth = window.innerWidth;
var carouselObj = angular.element(document.querySelector('.carousel'));
var carouselListObj = angular.element(document.querySelector('.carousel-list'));
var carouselImageObj = angular.element(document.querySelector('.carousel-image'));
carouselObj.css('width', scope.imageWidth + 'px');
carouselListObj.css('width', (100 * scope.max) + '%');

scope.nextSlide = function(){
if (scope.index == scope.max){
scope.index = 1;
carouselListObj.css('transform', 'translateX(0%)');
} else {
carouselListObj.css('transform', 'translateX(-' + scope.index * scope.ratio + '%)');
scope.index++;
}
};
scope.prevSlide = function(){
if (scope.index == 1){
carouselListObj.css('transform', 'translateX(0%)');
scope.index = scope.max;
if (scope.firstTime){
scope.index--;
carouselListObj.css('transform', 'translateX(-' + scope.index * scope.ratio + '%)');
scope.firstTime = false;
}
} else {
scope.index--;
carouselListObj.css('transform', 'translateX(-' + scope.index * scope.ratio + '%)');
}
};
scope.imageStyle = function(){
return 'width:' + scope.imageWidth + 'px;';
};
scope.close = function() {
scope.show = false;
scope.index = 1;
scope.firstTime = true;
};
}
};
}
angular.module('directives', [])
.directive('carousel', [carousel]);

In my code, I pulled a database row which contains images in the html.
I then had to bind ng-clicks to each image in the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$s.content = databaseRow.text;
var imageTag = new RegExp('<img .*alt="" />','g');
var srcTag = new RegExp('src=".*" alt','g');
$s.imageList = $s.content.match(imageTag);
if (!$s.imageList){
$s.imageList = [];
}
$s.imageList = $s.imageList.map(function(oneSrc){
$s.content = $s.content.replace(oneSrc, oneSrc.replace('alt=""',
' alt="" ng-click="openCarousel()"'));
var oneImage = oneSrc.match(srcTag);
return oneImage[0].replace("src=\"", "").replace("\" alt", "");
});
$s.carousel = function(){
$s.show = true;
}

When displaying content in the html, I used my dynamic directive to rebind the new images ng-click.

Code I added to carousel directive

1
2
3
4
5
6
7
8
9
10
11
scope.$watch('images', function(images) {
scope.images = images;
scope.index = 1;
scope.max = images.length;
scope.firstTime = true;
scope.ratio = Math.round(100/scope.max);
scope.imageWidth = window.innerWidth;
carouselObj.css('width', scope.imageWidth + 'px');
carouselListObj.css('width', (100 * scope.max) + '%');
scope.imageStyle();
});

dynamic directive

1
2
3
4
5
6
7
8
9
10
11
12
function dynamic ($c) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$c(ele.contents())(scope);
});
}
};
}

Hope this will help you out.

Infinite Scroll in Ionic Framework Pulling Real Data By Month

By: Ryan Wong at

Here’s my tutorial for creating an infinite scroll querying by month and year.

Basically, we have a header bar above ion-content where it displays the month and year.

If user clicks the prev button, it requery the server with the correct month and year.

If user clicks the next button, it requery the server with the correct month and year.

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div class="calendar-switch">
<div class="left-calendar" ng-click="prev()"><</div>
<div class="calendar-month"> {{date | date:'MMMM'}} {{date | date:'y'}}</div>
<div class="right-calendar" ng-click="next()">></div>
</div>
<ion-content class="has-calendarheaders">
<ion-refresher
pulling-text="Pull to refresh..."
on-refresh="doRefresh()">
</ion-refresher>
<ul class="calendar-listing">
<li ng-repeat="oneItem in items">
<a class="calendar-row" href="#calendarView/{{oneItem.id}}">
<div class="calendar-detail">
{{oneItem.name}}<br/>
{{oneItem.location}}<br/>
{{oneItem.postalCode}}<br/>
{{oneItem.startTime}} to {{oneItem.endTime}}
</div>
</a>
</li>
</ul>
<ion-infinite-scroll ng-if="!noMoreItemsAvailable" on-infinite="loadMore()" distance="10%">
</ion-infinite-scroll>
<div class="noSourceFound" ng-if="noItemFound">
<p>Sorry No Event Found</p>
</div>
</ion-content>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.has-calendarheaders{
top: 186px;
}
.calendar-switch{
height: 50px;
text-align: center;
top: 138px;
position: fixed;
width: 100%;
z-index: 4;
padding: 10px 0;
.left-calendar {
float:left;
width:10%;
font-weight: bold;
}
.calendar-month {
float:left;
width: 80%;
}
.right-calendar {
float: right;
width: 10%;
font-weight: bold;
}
}

JS Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function CalendarLatestCtrl($s, cS) {
$s.items = [];
$s.date = new Date();
$s.page = 1;
$s.noMoreItemsAvailable = false;
$s.noItemFound = false;
$s.next = function(){
$s.date.setMonth($s.date.getMonth() + 1);
$s.items = [];
$s.page = 1;
$s.noItemFound = false;
$s.noMoreItemsAvailable = false;
}
$s.prev = function(){
$s.date.setMonth($s.date.getMonth() - 1);
$s.items = [];
$s.page = 1;
$s.noItemFound = false;
$s.noMoreItemsAvailable = false;
}
$s.loadMore = function(){
cS.getCalendarAll($s.page, $s.date.getMonth() + 1, $s.date.getFullYear()).then(function(items) {
if (items.numPages == items.page){
$s.noMoreItemsAvailable = true;
$s.$broadcast('scroll.infiniteScrollComplete');
}
if (items.numPages == 0 || items.total == 0){
$s.noMoreItemsAvailable = true;
$s.noItemFound = true;
}
items.list = items.list.map(function(oneEvent){
oneEvent.dateObject = new Date(oneEvent.date);
return oneEvent;
});
$s.page++;
$s.items = $s.items.concat(items.list);
$s.$broadcast('scroll.infiniteScrollComplete');
});
};

$s.doRefresh = function(){
$s.items = [];
$s.page = 1;
$s.noMoreItemsAvailable = false;
$s.loadMore();
$s.$broadcast('scroll.refreshComplete');
};
}
angular.module('controller', [])
.controller('CalendarLatestCtrl', ['$scope', 'calendarService', CalendarLatestCtrl]);

AJAX Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"page" : 1,
"numPages": 7,
"total": 10,
"list" : [{
"id" : 1,
"date" : "2015-05-02 12:34:32",
"blah": "blah"
},{
"id" : 1,
"date" : "2015-05-01 12:34:32",
"blah": "blah"
}]
}

Angular Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function calendarService(baseUrl, $q, $http){
this.someUrl = baseUrl() + '/api/v1/someurl';

this.getCalendarAll = function(page, month, year){
var deferred = $q.defer();
$http({
method: 'GET',
url: someUrl + '?page=' + page + '&month=' + month + '&year=' + year
})
.success(function(data) {
return deferred.resolve(data);
})
.error(function(err,status) {
return deferred.reject(err);
});
return deferred.promise;
}};

angular.module('services', [])
.service('calendarService', ['baseUrl', '$q', '$http', calendarService])

Infinite Scroll in Ionic Framework Pulling Real Data

By: Ryan Wong at

I’ve looked at lots of tutorials on the internet, and none of they show you a fully functional
infinite scroll.

Heres My full Example:

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<ion-content>
<ion-refresher
pulling-text="Pull to refresh..."
on-refresh="doRefresh()">

</ion-refresher>
<ul class="listing">
<li ng-repeat="oneItem in items">
<div class="stat-row">
<div class="username">{{oneItem.username}}</div>
<div class="rank">{{oneItem.rank | number:0}}</div>
</div>
</li>
</ul>
<ion-infinite-scroll ng-if="!noMoreItemsAvailable"
on-infinite="loadMore()" distance="10%"></ion-infinite-scroll>

<div class="noSourceFound" ng-if="noItemFound">
<p>Sorry No Media Found</p>
</div>
<br/>
<br/>
</ion-content>

JS Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
'use strict';

function ListAllCtrl($s, lS) {
$s.items = [];
$s.page = 1;
$s.limit = 10;
$s.noMoreItemsAvailable = false;
$s.noItemFound = false;

$s.loadMore = function(){
lS.getRows($s.page, $s.limit).then(function(items) {
if (items.numPages == items.page){
$s.noMoreItemsAvailable = true;
$s.$broadcast('scroll.infiniteScrollComplete');
}
if (items.numPages == 0 || items.total == 0){
$s.noMoreItemsAvailable = true;
$s.noItemFound = true;
}
$s.page++;
$s.items = $s.items.concat(items.list);
$s.$broadcast('scroll.infiniteScrollComplete');
});
};

$s.doRefresh = function(){
$s.items = [];
$s.page = 1;
$s.noMoreItemsAvailable = false;
$s.loadMore();
$s.$broadcast('scroll.refreshComplete');
};
}

angular.module('controllers',[])
.controller('ListAllCtrl', ['$scope', 'listService', ListAllCtrl]);

AJAX Request

1
2
3
4
5
6
7
8
9
10
11
12
{
"page" : 1,
"numPages": 7,
"total": 10,
"list" : [{
"id" : 1,
"blah": "blah"
},{
"id" : 1,
"blah": "blah"
}]
}

Angular Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function listService(baseUrl, $q, $http){
this.someUrl = baseUrl() + '/api/v1/someurl';

this.getRows = function(page, limit){
var deferred = $q.defer();
$http({
method: 'GET',
url: someUrl + '?page=' + page + '&limit=' + limit
})
.success(function(data) {
return deferred.resolve(data);
})
.error(function(err,status) {
return deferred.reject(err);
});
return deferred.promise;
}
};

angular.module('services', [])
.service('listService', ['baseUrl', '$q', '$http', listService])

Some Issues I faced when using other methods are:

  • double loading row when there is only 1 row
  • double loading no results found
  • loading uncontrollably
  • if loading lots of rows, the last row will appear below your tabs if you have them. The solution is add <br/> to the bottom of <ion-content>

Some features you may consider that aren’t shown here are:

  • what to show when you have no internet connection
  • what to show when server is down

Also make sure you add br to the end of ion-content or padding bottom since the scroll view will cut off the last row in the list on your phone. You won’t see this error in your browser but it will happen on your phone.
Hope this helps you out

Passing variables into directives or elements through attributes

By: Ryan Wong at

I got confuse which notation we should use to input variables into attributes.

Do we use variable or ‘{ { variable } }’ .

So I decided to make my own list so I don’t forget.

#Use Variable

  • ng-click
  • ng-repeat
  • ng-show
  • ng-hide
  • ng-class
  • ng-if
  • ng-checked
  • single attribute
    #Use ‘{ { variable } }’
  • href
  • ng-src
  • elements
  • ng-style
  • ng-disabled
  • dynamic attribute

Keep localhost hostname and still have virtual host for specific projects

By: Ryan Wong at

Today I had someone ask me how to setup a virtual host for their laravel project and still keep
their localhost for htdocs folder to do wordpress work.

The Solution we came up with was:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<VirtualHost *:80>
DocumentRoot /Applications/MAMP/htdocs/virtualhostproject
ServerName www.virtualhostproject.com

<Directory /Applications/MAMP/htdocs/virtualhostproject>
Options All
AllowOverride All
</Directory>

<VirtualHost *:80>
DocumentRoot /Applications/MAMP/htdocs
ServerName localhost

<Directory /Applications/MAMP/htdocs>
Options All
AllowOverride All
</Directory>
</VirtualHost>

I thought this was an interesting way to preserve your localhost.

Hope it helps you out.

How to make custom header in Ionic Framework

By: Ryan Wong at

In my startup application, I didn’t like the standard look of ionic header bar so I had to make it ourself.

What I wanted was 1 icon button on the very left, 1 icon button on the very right and our app logo in the middle.

To implement this I did the following:

1.In the index.html file, I used ion-view.

1
2
3
4
5
6
<body ng-app="myapp" animation="no-animation">
<ion-nav-view>
<ion-view></ion-view>
</ion-nav-view>
<!-- scripts -->
</body>

  1. In our template page, I did the following:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <ion-header-bar class="bar-positive bargrey header-spacing">
    <div class="header-section homepost">
    <button class="button button-clear no-animation" >
    <i class="icon ion-ios-home-outline"></i>
    </button>
    </div>
    <div class="header-section">
    <img src="/img/logo.png"/>
    </div>
    <div class="header-section notify">
    <button class="button button-clear no-animation" >
    <i class="icon ion-android-star"></i>
    </button>
    </div>
    </ion-header-bar>

SCSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.header-spacing{
width: 100%;
.header-section{
float:left;
width: 33%;
text-align: center;

img{
width: 68px;
height: 24px;
}
}
.notify {
text-align: right;
}
.homepost{
text-align: left;
}
}

Hope it helps you out.

Centering image in a div CSS

By: Ryan Wong at

One Way is the following:

1
2
3
<div class="container">
<img src="blah">
</div>

CSS

1
2
3
4
5
6
7
8
9
10
.container{
width: 100%;
}

.container img{
display:block;
width: 80%;
margin-left: auto;
margin-right: auto;
}

The other way is:

1
2
3
4
5
6
7
8
.container {
text-align: center;
width:100%;
}

.container img {
display: inline-block;
}

Hope it helps you out.

How to add categories to the home page in Magento

By: Ryan Wong at

When I started my first magento(ver. 1.9.1.0) project, I got really confused as to how to add categories to the home page.

Here’s what I did to solve it:

1.Go to admin panel > Catalog > Manage Categories. Add your categores as sub categories under Default Category.

2.Click into the Category you added and make sure the setting ‘Is Active’ and ‘Include in Navigation Menu’ are yes.

3.Go to Catalog > Manage Products > Add Product. While filling in the product information, make sure that you set the following:

  • category for the product
  • under Inventory set Qty and Stock Availability

If you did all the above steps, you should see your category and products under it. You must have a product to see the category.
If you don’t see the category, you can do the following:

  • System > Index Management > Reindex Data on all Indexes

Hope This helps you out.