This is the BEST Angular Carousel on the Web -- Try It and See for Yourself
![]() |
|
I wanted to add a Carousel to my AngularJS Shopping Cart that you can see in my article Responsive Mobile Shopping Cart Using AngularJS that I posted here on CodeProject last month. However I wanted a carousel that can be used either inside or outside of a shopping cart and that had the following features:
- Uses AngularJS Dynamic Templates for Pills: Images, Videos and Notes
- Plays Videos from ALL Tubes Sites that allow EMBED, e.g. YouTube, YouKu, Vimeo, etc.
- Ability to easily create an AngularJS Directive for the Carousel
- Fully Responsive for Mobile. Scales with its container.
- Separate settings per breakpoint
- Uses CSS3 when available. Fully functional when not.
- Swipe Enabled for Mobile. Or disabled, if you prefer.
- Desktop mouse dragging
- Infinite looping.
- Fully accessible with arrow key navigation
- Add, remove, filter & unfilter slides
- Autoplay, dots, arrows, callbacks, etc...
To accomplish this I looked at a lot of carousels and the nicest looking carousel with the above features was a JQuery plugin-in by Ken Wheeler called Slick. You can see the JQuery plugin-in at: https://github.com/kenwheeler/slick And there was already an AngularJS Directive for the Slick Carousel by Vasyl Stanislavchuk that I found at: https://github.com/vasyabigi/angular-slick
The project in this article is a modified version I created of Vasyl Stanislavchu's Angular Directive for the Slick Carousel that includes other features I added that are needed to allow dynamic creation of the carousel on window resize, pill blocks with images, videos, text and Bootstrap 3 buttons with gradients that can be individually animated with hover effects. The AngularJS Super Slick Carousel in this article can be used as either a stand-alone carousel with or without products or you can add it to my AngularJS Shopping Cart by just dropping in the code. I wanted to keep the code for the carousel separate from just including it in the shopping cart because there are a lot of people who believe that carousels kill sales. I am not going to get involved in teh debate or pro or con carousels.
The full collection of features in the Super Slick Carousel are too numerous to list but you can see all of the included features and how to set them here.
I used Bootstrap 3 but NOT ui.bootstrap because ui.bootstrap gives me headaches trying to keep up with their changes. Bootstrap 3 has navbars where it easy to change the look-and-feel of the navbars from in side your app using AngularJS as demonstrated below in the shopping cart. To create the gradient in these navbars I used the gradient editor at: https://www.colorzilla.com/gradient-editor/.
There are several ways to setup the carousel's default setting. In the html code we can hard code settings, or set them to a variable as in teh case of using
// In config.js file we set the value: 'CF_CAROUSEL_AUTO_PLAY': true,
as used in the Slick declaration below in the html. where the value of CAROUSEL_AUTO_PLAY is set in the config,js file in the ac_products directory. Please note that you can add any other setup parameters you want to this config.js file and then use them in the html declaration like I did for autoplay="CAROUSEL_AUTO_PLAY" as shown below.
<slick id="storeslider" ng-if="dataLoaded" autoplay="CAROUSEL_AUTO_PLAY" init-onload="false" data="dataLoaded" slick-apply='slickApply' slides-to-show=3 slides-to-scroll=1 class="col-md-12 slider responsive multiple-items center-wrapper"> ...
I added another way of setting the defaults the directive as shown below for responsive setting to allow the AngularJS to display correctly on mobile devices. As shown below you can see how to set the responsive proties you want based upon window size.
// Now you can set RESPONSIVE properties based on window size in the DIRECTIVE code as seen below: onReInit: attrs.onReInit ? scope.onReInit : void 0, onSetPosition: attrs.onSetPosition ? scope.onSetPosition : void 0, pauseOnHover: scope.pauseOnHover !== 'false', /* responsive: scope.responsive || void 0, */ responsive: [ { breakpoint: 1024, settings: { slidesToShow: 3, slidesToScroll: 1, infinite: true, dots: false } }, { breakpoint: 600, settings: { slidesToShow: 2, slidesToScroll: 1 } }, { breakpoint: 480, settings: { dots: false, slidesToShow: 1, slidesToScroll: 1 } }],
The other changes that are important to improve responsiveness and use in mobile devices either as as a web app on a server or inside a compiled native PhoneGap/Cordova Mobile App are:
storeApp.controller('storeController', function ($scope, $filter, $routeParams, $location, DataService, $sce, $timeout, CONFIG) { $scope.dataLoaded = false; /*##################### CONFIG ######################*/ /* our global variabls */ $scope.STORE_ID = CONFIG.CF_STORE_ID; $scope.STORE_PAGE = CONFIG.CF_STORE_PAGE; $scope.STORE_BG_IMAGE = CONFIG.CF_STORE_BG_IMAGE; $scope.DISTRIBUTOR_ID = CONFIG.CF_DISTRIBUTOR_ID; $scope.PAYMENT_PAYPAL_BUYNOW = CONFIG.CF_PAYMENT_PAYPAL_BUYNOW; $scope.PAYMENT_GOOGLE_WALLET_ID = CONFIG.CF_PAYMENT_GOOGLE_WALLET_ID; $scope.PAYMENT_STRIPE = CONFIG.CF_PAYMENT_STRIPE; $scope.PRODUCTS_FOLDER = CONFIG.CF_PRODUCTS_FOLDER; $scope.PRODUCTS_FILE = CONFIG.CF_PRODUCTS_FILE; $scope.NAVBAR_THEME = CONFIG.CF_NAVBAR_THEME; $scope.NAVBAR_LOGO_TEXT = CONFIG.CF_NAVBAR_LOGO_TEXT; $scope.NAVBAR_LOGO_LINK = CONFIG.CF_NAVBAR_LOGO_LINK; $scope.INSIDE_HEADER_SHOW = CONFIG.CF_INSIDE_HEADER_SHOW; $scope.INSIDE_HEADER_LINK = CONFIG.CF_INSIDE_HEADER_LINK; $scope.INSIDE_HEADER_IMAGE = CONFIG.CF_INSIDE_HEADER_IMAGE; $scope.INSIDE_HEADER_TITLE = CONFIG.CF_INSIDE_HEADER_TITLE; $scope.CAROUSEL_SHOW = CONFIG.CF_CAROUSEL_SHOW; $scope.CAROUSEL_AUTO_PLAY = CONFIG.CF_CAROUSEL_AUTO_PLAY; $scope.AN_CAROUSEL_IMG_VIDEO = CONFIG.CF_AN_CAROUSEL_IMG_VIDEO; $scope.AN_CAROUSEL_PILL = CONFIG.CF_AN_CAROUSEL_PILL; $scope.AN_STORE_IMG_VIDEO = CONFIG.CF_AN_STORE_IMG_VIDEO; $scope.AN_STORE_PILL = CONFIG.CF_AN_STORE_PILL; $scope.CAROUSEL_IMAGE_BORDER = CONFIG.CF_CAROUSEL_IMAGE_BORDER; $scope.STORE_IMAGE_BORDER = CONFIG.CF_STORE_IMAGE_BORDER; $scope.SYSTEM_NAME = CONFIG.CF_SYSTEM_NAME; $scope.SYSTEM_LANGUAGE = CONFIG.CF_SYSTEM_LANGUAGE; $scope.BASE_URL = CONFIG.CF_BASE_URL; $scope.API_URL = CONFIG.CF_API_URL; $scope.GOOGLE_ANALYTICS_ID = CONFIG.CF_GOOGLE_ANALYTICS_ID; $scope.products = []; $scope.slides = []; $scope.fetchContent = function () { DataService.getData().then(function (result) { $scope.products = result.data; for (var i = 0, len = $scope.products.length; i < len; i++) { var prod = $scope.products[i]; if (prod.imagename.length < 1) { prod.imagename = "nopic.png"; } if (prod.carousel) { $scope.slides.push(prod); } } // We use: ng-if="dataLoaded" init-onload="false" data="dataLoaded" // in the timeout function in order to get the old elements completly removed. // otherwise the old elements stay in the directive and the carousel breaks $timeout(function () { $scope.dataLoaded = true; }); }); }; $scope.fetchContent(); });
I am using promises with the $http.get call to fetch our content from our products file.
AngularJS Dynamic Templates
I decided to re-write the code using Dynamic Templates to allow users to easily customize the the look and feel of the carousel. I create 3 temples as shown below.
Our data models are differentiated by displayType, namely, videoTemplate, noteTemplate, and imageTemplate which are stored in a text file, i.e., templates.txt, as follows:
{ "imageTemplate": "<div class='carousel_pill thumbnail {{AN_CAROUSEL_PILL}}'><div class='carousel_img_video {{AN_CAROUSEL_IMG_VIDEO}}'><a href='#/products/{{content.sku}}'><img class='carousel_imgborder {{CAROUSEL_IMAGE_BORDER}}' ng-src='{{PRODUCTS_FOLDER}}/{{content.display_data}}' alt='{{content.productname}}' style='display: block;height: 100%;max-height:200px' /></a></div><div class='carousel_bottom'><div class='carousel_caption'><h3 ng-bind-html='content.carousel_caption | unsafe'></h3></div><div style='display:inline !important;font-size: 1.4em !important;' class='center-wrapper'><div style='display:inline !important'><a ng-href='affilate_ad.html?distid={{DISTRIBUTOR_ID}}&sku={{content.sku}}' target='_blank'><div class='btn btn-x-blue'>Details</div></a> <a ng-href='https://www.software-rus.com/storefront.html?distid={{DISTRIBUTOR_ID}}&sku={{content.sku}}#/cart' target='_blank' class='btn btn-x-success'>Buy </a> <span class='content_price'>{{content.unitprice | currency}}</span></div></div></div></div>", "videoTemplate": "<div class='carousel_pill thumbnail {{AN_CAROUSEL_PILL}}'><div class='carousel_img_video {{AN_CAROUSEL_IMG_VIDEO}}'><div class='video'><iframe ng-src='{{content.display_data}}' frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div></div><div class='carousel_bottom'><div class='carousel_caption'><h3 ng-bind-html='content.carousel_caption | unsafe'></h3></div><div style='display:inline !important;font-size: 1.4em !important;' class='center-wrapper'><div style='display:inline !important'><a ng-href='affilate_ad.html?distid={{DISTRIBUTOR_ID}}&sku={{content.sku}}' target='_blank'><div class='btn btn-x-blue'>Details</div></a> <a ng-href='https://www.software-rus.com/storefront.html?distid={{DISTRIBUTOR_ID}}&sku={{content.sku}}#/cart' target='_blank' class='btn btn-x-success'>Buy </a> <span class='content_price'>{{content.unitprice | currency}}</span></div></div></div></div>", "noteTemplate": "<div class='carousel_pill_note thumbnail {{AN_CAROUSEL_PILL}}'><div class='carousel_note'><div class='note_header {{AN_CAROUSEL_IMG_VIDEO}}'><div ng-bind-html='content.header | unsafe'></div></div><div class='note_shortdesc' ng-bind-html='content.shortdesc | unsafe'></div></div><div class='carousel_note_controls'><div style='display:inline !important;font-size: 1.4em !important;' class='center-wrapper'><div style='display:inline !important'><a ng-href='http:\/\/www.webmd.com\/eye-health\/macular-degeneration\/news\/20150410\/calcium-supplements-amd' target='_blank'><div class='btn btn-x-blue'>Read More</div></a></div></div></div></div>" }
And I created a Template Service to retrieve the templates form templates.txt as follows.
storeApp.factory('TemplateService', function ($http, URL) { var getTemplates = function () { return $http.get(URL + 'templates.txt'); }; return { getTemplates: getTemplates }; });
I created a directive called pillContent restricted to an element with an isolated scope that is bound to the content property and a linker function to tie everything together as shown here.
storeApp.directive('pillContent', function ($compile, TemplateService, CONFIG) { var getTemplate = function (templates, displayType) { var template = ''; switch (displayType) { case 'image': template = templates.imageTemplate; break; case 'video': template = templates.videoTemplate; break; case 'note': template = templates.noteTemplate; break; } return template; }; var linker = function (scope, element, attrs) { /*##################### CONFIG ######################*/ /* our global variabls */ scope.STORE_ID = CONFIG.CF_STORE_ID; scope.STORE_PAGE = CONFIG.CF_STORE_PAGE; scope.STORE_BG_IMAGE = CONFIG.CF_STORE_BG_IMAGE; scope.DISTRIBUTOR_ID = CONFIG.CF_DISTRIBUTOR_ID; scope.PAYMENT_PAYPAL_BUYNOW = CONFIG.CF_PAYMENT_PAYPAL_BUYNOW; scope.PAYMENT_GOOGLE_WALLET_ID = CONFIG.CF_PAYMENT_GOOGLE_WALLET_ID; scope.PAYMENT_STRIPE = CONFIG.CF_PAYMENT_STRIPE; scope.PRODUCTS_FOLDER = CONFIG.CF_PRODUCTS_FOLDER; scope.PRODUCTS_FILE = CONFIG.CF_PRODUCTS_FILE; scope.NAVBAR_THEME = CONFIG.CF_NAVBAR_THEME; scope.NAVBAR_LOGO_TEXT = CONFIG.CF_NAVBAR_LOGO_TEXT; scope.NAVBAR_LOGO_LINK = CONFIG.CF_NAVBAR_LOGO_LINK; scope.INSIDE_HEADER_SHOW = CONFIG.CF_INSIDE_HEADER_SHOW; scope.INSIDE_HEADER_LINK = CONFIG.CF_INSIDE_HEADER_LINK; scope.INSIDE_HEADER_IMAGE = CONFIG.CF_INSIDE_HEADER_IMAGE; scope.INSIDE_HEADER_TITLE = CONFIG.CF_INSIDE_HEADER_TITLE; scope.CAROUSEL_SHOW = CONFIG.CF_CAROUSEL_SHOW; scope.CAROUSEL_AUTO_PLAY = CONFIG.CF_CAROUSEL_AUTO_PLAY; scope.AN_CAROUSEL_IMG_VIDEO = CONFIG.CF_AN_CAROUSEL_IMG_VIDEO; scope.AN_CAROUSEL_PILL = CONFIG.CF_AN_CAROUSEL_PILL; scope.AN_STORE_IMG_VIDEO = CONFIG.CF_AN_STORE_IMG_VIDEO; scope.AN_STORE_PILL = CONFIG.CF_AN_STORE_PILL; scope.CAROUSEL_IMAGE_BORDER = CONFIG.CF_CAROUSEL_IMAGE_BORDER; scope.STORE_IMAGE_BORDER = CONFIG.CF_STORE_IMAGE_BORDER; scope.SYSTEM_NAME = CONFIG.CF_SYSTEM_NAME; scope.SYSTEM_LANGUAGE = CONFIG.CF_SYSTEM_LANGUAGE; scope.BASE_URL = CONFIG.CF_BASE_URL; scope.API_URL = CONFIG.CF_API_URL; scope.GOOGLE_ANALYTICS_ID = CONFIG.CF_GOOGLE_ANALYTICS_ID; scope.rootDirectory = scope.PRODUCTS_FOLDER + '/'; TemplateService.getTemplates().then(function (response) { var templates = response.data; element.html(getTemplate(templates, scope.content.display_type)); $compile(element.contents())(scope); }); }; return { restrict: 'E', link: linker, scope: { content: '=' } }; });
The idea is that we get the desired template and add it to the DOM via element.html() and then show(). We use the $compile service that compiles an HTML string into a template and template function, which we use to link scope and the template together.
The entire code for our Slider HTML is just:
<slick id="storeslider" ng-if="dataLoaded" autoplay={{CAROUSEL_AUTO_PLAY}} init-onload="false" data="dataLoaded" slick-apply='slickApply' slides-to-show=3 slides-to-scroll=1 class="col-md-12 slider responsive multiple-items center-wrapper"> <div class="carousel_block" ng-repeat="slide in slides"> <pill-content content="slide"></pill-content> </div> </slick>
And I added a class to the imageTemplate to customize adding different borders to images as follows:
Playing Video from Any Source That Allows Embed
I wanted to be able to play videos from any Tube Site that allows you to embed video in the Pills so I decided to use, $sceDelegateProvider, as follows:
storeApp.config(function ($sceDelegateProvider) { $sceDelegateProvider.resourceUrlWhitelist(['self', '**']); });
As an example, you can play videos from these Tube Sites that allow you to enbed video. I included a "products" source file called "video.txt" that displays videos form all of these Tube Sites in the carousel.
Hover Effects Library
I decided to add a collection of hover animations that you can apply from the Bootstrap 3 navbar in the sample project that you can apply to different objects in the Slick Carousel. The Hover Library is called Hover by Ian Lunn which you can explore on his GitHub at: https://github.com/IanLunn
You can apply the effects to see what they look like using the Effects Tab in the menu.
Here is how I setup the hover animations to work. First the default values for the hover effects are read from the config.js file and applied to the div tags for the pil and im_vide. Then you can use the effects menu in the navbar to apply different effects dynamically to the pil and img/video of the carousel and to the pil and img/video in the shopping cart if you use this carousel in the shopping cart as shown below.
// In the config.js file:
'CF_AN_CAROUSEL_IMG_VIDEO': 'hvr-pulse-grow',
'CF_AN_CAROUSEL_PILL': 'hvr-wobble-to-top-right',
// in the storeController:
storeApp.controller('storeController', function (
$scope, $filter, $routeParams, $location, DataService, $sce, CONFIG) {
$scope.AN_CAROUSEL_IMG_VIDEO = CONFIG.CF_AN_CAROUSEL_IMG_VIDEO;
$scope.AN_CAROUSEL_PILL = CONFIG.CF_AN_CAROUSEL_PILL;
// Hover effects for pill & img/video classes are applied on startup.
<div class="carousel_pill thumbnail {{AN_CAROUSEL_PILL}}">
<div class="carousel_img_video {{AN_CAROUSEL_IMG_VIDEO}}">
// And animation hover effects are applied from 'Effects" Menu.
$scope.changeAnimation = function (effect_name) {
var e = '';
if ($scope.myModel === 'carousel_img_video') {
e = '.note_header';
$(e).removeClass(function (index, css) {
return (css.match(/(^|\s)hvr-\S+/g) || []).join(' ');
});
$(e).addClass(effect_name);
e = '.carousel_img_video';
}
else if ($scope.myModel === 'carousel_pill') {
e = '.carousel_pill_note';
$(e).removeClass(function (index, css) {
return (css.match(/(^|\s)hvr-\S+/g) || []).join(' ');
});
$(e).addClass(effect_name);
e = '.carousel_pill';
}
else if ($scope.myModel === 'store_img_video') {
e = '.store_img_video';
}
else if ($scope.myModel === 'store_pill') {
//e = '.nav-pills li';
e = '.store_pill';
}
if (e.length > 0) {
$(e).removeClass(function (index, css) {
return (css.match(/(^|\s)hvr-\S+/g) || []).join(' ');
});
$(e).addClass(effect_name);
}
};
I don't like the falt look of default Bootstrap 3 buttons so I decided to give them some depth as shown below. To do this I used a really cool Bootstrap 3 Editor that creates buttons with a gradient and mouse over and mousedown eeffects with a single block of CSS code at: https://charliepark.org/bootstrap_buttons/
I kept the menu control for our Bootstrap 3 menu very simple as you can see below. To set the active tab I used ng-controller to run a single controller outside of the ng-view as shown below.
<li ng-class="{ active: isActive('/store')}"><a ng-href="storefront.html#/store">Store </a></li>
and...
storeApp.controller('MyMenu', function ($scope, $filter, $location, CONFIG) { $scope.name = 'MyMenu'; $scope.isCollapsed = false; $scope.dataLoaded = false; /*##################### CONFIG ######################*/ /* our global variabls */ $scope.STORE_ID = CONFIG.CF_STORE_ID; $scope.STORE_PAGE = CONFIG.CF_STORE_PAGE; $scope.STORE_BG_IMAGE = CONFIG.CF_STORE_BG_IMAGE; $scope.DISTRIBUTOR_ID = CONFIG.CF_DISTRIBUTOR_ID; $scope.PAYMENT_PAYPAL_BUYNOW = CONFIG.CF_PAYMENT_PAYPAL_BUYNOW; $scope.PAYMENT_GOOGLE_WALLET_ID = CONFIG.CF_PAYMENT_GOOGLE_WALLET_ID; $scope.PAYMENT_STRIPE = CONFIG.CF_PAYMENT_STRIPE; $scope.PRODUCTS_FILE = CONFIG.CF_PRODUCTS_FILE; $scope.PRODUCTS_FOLDER = CONFIG.CF_PRODUCTS_FOLDER; $scope.NAVBAR_THEME = CONFIG.CF_NAVBAR_THEME; $scope.NAVBAR_LOGO_TEXT = CONFIG.CF_NAVBAR_LOGO_TEXT; $scope.NAVBAR_LOGO_LINK = CONFIG.CF_NAVBAR_LOGO_LINK; $scope.INSIDE_HEADER_SHOW = CONFIG.CF_INSIDE_HEADER_SHOW; $scope.INSIDE_HEADER_LINK = CONFIG.CF_INSIDE_HEADER_LINK; $scope.INSIDE_HEADER_IMAGE = CONFIG.CF_INSIDE_HEADER_IMAGE; $scope.INSIDE_HEADER_TITLE = CONFIG.CF_INSIDE_HEADER_TITLE; $scope.CAROUSEL_SHOW = CONFIG.CF_CAROUSEL_SHOW; $scope.CAROUSEL_AUTO_PLAY = CONFIG.CF_CAROUSEL_AUTO_PLAY; $scope.AN_CAROUSEL_IMG_VIDEO = CONFIG.CF_AN_CAROUSEL_IMG_VIDEO; $scope.AN_CAROUSEL_PILL = CONFIG.CF_AN_CAROUSEL_PILL; $scope.AN_STORE_IMG_VIDEO = CONFIG.CF_AN_STORE_IMG_VIDEO; $scope.AN_STORE_PILL = CONFIG.CF_AN_STORE_PILL; $scope.SYSTEM_NAME = CONFIG.CF_SYSTEM_NAME; $scope.SYSTEM_LANGUAGE = CONFIG.CF_SYSTEM_LANGUAGE; $scope.BASE_URL = CONFIG.CF_BASE_URL; $scope.API_URL = CONFIG.CF_API_URL; $scope.GOOGLE_ANALYTICS_ID = CONFIG.CF_GOOGLE_ANALYTICS_ID; if ($scope.CAROUSEL_SHOW) { $('#storeslider_wrapper').css('display', 'block'); } else { $('#storeslider_wrapper').css('display', 'none'); } if ($scope.INSIDE_HEADER_SHOW) { $('.inside_header').css('display', 'block'); } else { $('.inside_header').css('display', 'none'); } if ($scope.STORE_BG_IMAGE.length > 0) { $("body").css('background-image', ''); $("body").css("background", "#ffffff url(" + $scope.STORE_BG_IMAGE + ") no-repeat center center fixed"); localStorage['bg_cart'] = $scope.STORE_BG_IMAGE; } _navbar_theme = "navbar_gray_gradient"; if (localStorage["navbar_theme"]) { _navbar_theme = localStorage["navbar_theme"]; } else { _navbar_theme = "navbar_gray_gradient"; localStorage["navbar_theme"] = "navbar_gray_gradient"; } var _path = "ac_css/" + _navbar_theme + ".css"; $("#link_index").attr("href", _path); $scope.NAVBAR_THEME = _navbar_theme; $scope.showHideCarousel = function (event) { //event.stopPropagation(); event.preventDefault(); if ($('#storeslider_wrapper').css('display') === 'block') { $('.carousel_trim').css('display', 'none'); $('#storeslider_wrapper').css('display', 'none'); } else { $('.carousel_trim').css('display', 'block'); $('#storeslider_wrapper').css('display', 'block'); $("#storeslider").slick('slickPrev'); $("#storeslider").slick('slickNext'); } } $scope.changeBackgroundImage = function (event) { //event.stopPropagation(); event.preventDefault(); var x = 0 for (x = 0; x < arBGs.length; x++) { if (_bgImage === arBGs[x]) { break; } } if (x + 1 < arBGs.length) { _bgImage = arBGs[x + 1]; } else { x = 0; _bgImage = arBGs[x]; } $("body").css('background-image', ''); if (_bgImage === 'ac_img/bg0.jpg') { $("body").css("background-color", "#ffffff"); } else { $("body").css("background", "#ffffff url(" + _bgImage + ") no-repeat center center fixed"); } localStorage['bg_cart'] = _bgImage; } $scope.changeNavBar = function (css_name) { //event.stopPropagation(); event.preventDefault(); var _path = "ac_css/" + css_name + ".css"; _navbar_theme = css_name; localStorage["navbar_theme"] = _navbar_theme; $("#link_index").attr("href", _path); return false; }; $scope.changeProducts = function (products_file) { //event.stopPropagation(); event.preventDefault(); $scope.PRODUCTS_FILE = products_file; CONFIG.CF_PRODUCTS_FILE = products_file; localStorage["products_file"] = products_file; window.location.reload(); return false; }; $scope.changeCarouselImageBorder = function (cibClassName) { var e = '.carousel_imgborder'; $(e).removeClass(function (index, css) { return (css.match(/(^|\s)cib-\S+/g) || []).join(' '); }); if (cibClassName.length > 0) { $(e).addClass(cibClassName); } }; $scope.changeAnimation = function (effect_name) { var e = ''; if ($scope.myModel === 'carousel_img_video') { e = '.note_header'; $(e).removeClass(function (index, css) { return (css.match(/(^|\s)hvr-\S+/g) || []).join(' '); }); $(e).addClass(effect_name); e = '.carousel_img_video'; } else if ($scope.myModel === 'carousel_pill') { e = '.carousel_pill_note'; $(e).removeClass(function (index, css) { return (css.match(/(^|\s)hvr-\S+/g) || []).join(' '); }); $(e).addClass(effect_name); e = '.carousel_pill'; } else if ($scope.myModel === 'store_img_video') { e = '.store_img_video'; } else if ($scope.myModel === 'store_pill') { //e = '.nav-pills li'; e = '.store_pill'; } if (e.length > 0) { $(e).removeClass(function (index, css) { return (css.match(/(^|\s)hvr-\S+/g) || []).join(' '); }); $(e).addClass(effect_name); } }; // Author: Bill SerGio - An elegant way to set the active tab is to use ng-controller // to run a single controller outside of the ng-view as shown below. $scope.isActive = function (viewLocation) { return viewLocation === $location.path(); }; //initiate an array to hold all active tabs $scope.activeTabs = []; //check if the tab is active $scope.isOpenTab = function (tab) { //event.stopPropagation(); event.preventDefault(); //check if this tab is already in the activeTabs array if ($scope.activeTabs.indexOf(tab) > -1) { //if so, return true return true; } else { //if not, return false return false; } } //function to 'open' a tab $scope.openTab = function (tab) { event.preventDefault(); //check if tab is already open if ($scope.isOpenTab(tab)) { //if it is, remove it from the activeTabs array $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1); } else { $scope.activeTabs = []; //if it's not, add it! $scope.activeTabs.push(tab); } return false; } // create a radioButtonGroup for our apply effects options $scope.optActions = [ { id: 'apply', name: 'apply effect', disabled: false, showinfo: '' }, { id: 'remove', name: 'remove effect', disabled: false, showinfo: '' } ]; $scope.modelAction = 'apply'; $scope.idProperty = "id"; $scope.nameProperty = "name"; $scope.bootstrapSuffix = "x-success"; $scope.disabledProperty = false; $scope.showinfoProperty = ""; $scope.myOptions = [ { id: 'store_img_video', name: 'store img', disabled: false, showinfo: 'You need to download and install the AngularJS Shopping Cart to apply effects to the shopping cart!' }, { id: 'store_pill', name: 'store pill', disabled: false, showinfo: 'You need to download and install the AngularJS Shopping Cart to apply effects to the shopping cart!' }, { id: 'carousel_img_video', name: 'carousel img', disabled: false, showinfo: '' }, { id: 'carousel_pill', name: 'carousel pill', disabled: false, showinfo: '' } ]; $scope.myModel = 'carousel_img_video'; $scope.idProperty = "id"; $scope.nameProperty = "name"; $scope.bootstrapSuffix = "xs-success"; $scope.disabledProperty = false; $scope.showinfoProperty = ""; });
The next thing that I think every shopping cart needs is the ability to give out Distributor Links to your distributors so that a distributor can place a link on their non-Angular website or in a PhoneGap or Cordova mobile app that will pass the Distributor ID Code into your cart AND add that product into the cart on the checkout page if and only if it is NOT already in our cart. I added this feature by using url parameters like the link shown below.:
<a href="https://www.your_site.com/storefront.html?
distid=SOME_DIST_ID
&sku=SOME_PRODUCT_SKU#/cart"
target="_blank" class="btn btn-sm btn-default">Purchase Cart Now</a>
This link will paas in the distid and sku as url parameters.
$scope.getUrlVar = function (key) {
var result = new RegExp(key + "=([^&]*)", "i").exec(window.location.search);
return result && unescape(result[1]) || "";
}
var _sku = $scope.getUrlVar('sku');
if (_sku.length > 0) {
for (var i = 0, len = $scope.products.length; i < len; i++) {
var prod = $scope.products[i];
if (prod.sku === _sku) {
DataService.cart.addItemUrl(prod.sku, prod.productname, prod.unitprice, +1);
}
}
}
var _distid = $scope.getUrlVar('distid');
if (_distid.length > 0) {
// Do something with _distid so you can pay the commissions you owe!
}
You can eithher add this carousel to my AngularJS shopping cart or use it as a separte app that can add a product to teh shopping cart by calling shoppingCart.js in the installed shopping. Thie code below would be in the shopping cart and NOT in this carousel.
// THIS CODE IS NOT IN THE CAROUSEL BUT IN THE SHOPPING CART!!!!
// adds an item to the cart from non-angular page using url parameters
shoppingCart.prototype.addItemUrl = function (sku, productname, unitprice, quantity) {
var quantity = this.toNumber(quantity);
var unitprice = this.toNumber(unitprice);
if (unitprice > 0) {
// update quantity for existing item
var found = false;
for (var i = 0; i < this.items.length && !found; i++) {
var item = this.items[i];
if (item.sku == sku) {
found = true;
//item.quantity = this.toNumber(item.quantity + quantity);
//if (item.quantity <= 0) {
// this.items.splice(i, 1);
//}
}
}
// WE ONLY ADD ITEM TO CART IF IT IS NOT ALREADY IN THE CART !!!
// new item, add now
if (!found) {
var item = new cartItem(sku, productname, unitprice, quantity);
this.items.push(item);
}
// save changes
this.saveItems();
}
else {alert("It's FREE, no need to add to cart!");}
}
Below you can see that when I use the carousel by itself outside of the shopping cart view the buy button does not indicate that the item has been added to the shopping cart because this view isn't inside any shopping cart. Instead the button will pass the distributor id used for paying commissions and the product sku to the url of a shopping cart that can be on any server anywhere on the web. In my opinion based on a lot of testing we have done the best thing to do when a person clicks on a details link or the product image they should be taken to a full-page ad for that product with a coupon in teh upper right-hand corner of the page.
I decided to use a JSON format to storte the products and their properties and retrieve them using AJAX as shown below.
[ { "productid": "7D6A083B-01C4-4E74-9F10-2916543188B8", "sku": "WildWorkout", "productname": "WildWorkout®", "storeid": "7cc6cb94-0938-4675-b84e-6b97ada53978", "categoryname": "software", "header": "In the Wild Workout® Mobile App You Watch Wild Animals execising!", "shortdesc": "In the Wild Workout® Mobile App you watch Wild Animals execising! And see the amazing results people are getting doing this Wild Workout.", "description": "Exercises based on the principles of how the wild animals stay in shape. In the Wild Workout® Mobile App we selected wild animals with massive strength in certain areas of their bodies to develop a total body workout of 45 muscle building, fat burning, body shaping exercises like no other that will have the jaws of others dropping in disbelief and envy.", "link": "https://www.software-rus.com/simulator2.html?app=wildworkout", "linktext": "try it", "imageurl": "", "imagename": "ww.gif", "carousel": true, "carousel_caption": "Watch Wild Animals Exercise!", "display_type" : "video", "display_data" : "https://www.youtube.com/embed/YyZNIarRYSc", "tubeid": "youtube", "videoid": "YyZNIarRYSc", "showvideo": true, "unitprice": 0, "saleprice": 0, "unitsinstock": 22, "unitsonorder": 0, "reorderlevel": 0, "expecteddate": null, "discontinued": null, "note": "", "faux": null, "sortorder": 1 }, ...
You can easily create different products files and switch between them as show below. The 'products.txt' file is a typical shopping cart and the 'videos.txt' file is a demo of pulling videos from many different Tube Sites.
And the code that swtiches the source files is as follows:
$scope.changeProducts = function (products_file) { event.preventDefault(); $scope.PRODUCTS_FILE = products_file; CONFIG.CF_PRODUCTS_FILE = products_file; localStorage["products_file"] = products_file; window.location.reload(); return false; };
Conclusion
In my experience a carousel is a nice way without using a shopping cart to present a few products that may be on sale. However, there are many people who disagree with using any form of carousel in a website as studies has shown that it is basically not a good way to present products to potential customers. I am not going to discuss the merits of carousels. This article was to simply present the very popular Slick Carousel in an AngularJS Directive. The Slick Carousel was the most mobile friendly carousel Ifound because it includes Swipe which seems to work nicely on the mobile devices I tested it on.