diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d31019
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.sass-cache/
diff --git a/config.rb b/config.rb
new file mode 100644
index 0000000..8a28a4e
--- /dev/null
+++ b/config.rb
@@ -0,0 +1,23 @@
+# Require any additional compass plugins here.
+
+# Set this to the root of your project when deployed:
+http_path = "/"
+css_dir = "css"
+sass_dir = "sass"
+images_dir = "images"
+javascripts_dir = "js"
+
+output_style = :expanded
+environment = :production
+
+relative_assets = true
+
+line_comments = false
+color_output = false
+
+
+# If you prefer the indented syntax, you might want to regenerate this
+# project again passing --syntax sass, or you can uncomment this:
+# preferred_syntax = :sass
+# and then run:
+# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
diff --git a/css/ting_search_carousel.css b/css/ting_search_carousel.css
index 60aa3fa..9fd8040 100644
--- a/css/ting_search_carousel.css
+++ b/css/ting_search_carousel.css
@@ -1,195 +1,121 @@
-.ting-search-carousel {
- width: 100%;
-}
-
-.ting-search-carousel .search-controller {
- height: 20px;
- list-style: none outside none;
- margin: 0;
- padding: 0;
- width: 100%;
-}
-
-.ting-search-carousel .search-controller li.active {
- background: none repeat scroll 0 0 #918F91;
-}
-
-.ting-search-carousel .search-controller li {
- background: none repeat scroll 0 0 #D1D9D7;
- float: left;
- font-size: 11px;
- height: 18px;
- line-height: 15px;
- margin: -1px 2px 0 0;
- padding: 0 17px;
-}
-
-.ting-search-carousel .search-controller li a {
- font-size: 11px;
- line-height: 15px;
-}
-
-.ting-search-carousel .ting-search-results {
+/* ==========================================================================
+ ting search carousel styling
+ ========================================================================== */
+.rs-carousel,
+.rs-carousel-mask,
+.rs-carousel-runner {
position: relative;
- height: 200px;
}
-.ting-search-carousel .ting-search-results .subtitle {
- font-size: 42px;
- font-weight: normal;
- line-height: 0.9;
- margin: 10px 0 0 40px;
+.rs-carousel-mask,
+.rs-carousel-runner {
overflow: hidden;
- padding: 10px 0 10px 10px;
- text-transform: uppercase;
- width: 250px;
}
-.ting-search-carousel .ting-search-results .info {
- height: 39px;
- margin-top: -3px;
+.rs-carousel-inner {
overflow: hidden;
- width: 100px;
-}
-
-.ting-search-carousel .ting-search-results .info span {
- display: block;
- font-size: 10px;
- line-height: 1.25;
- text-align: center;
}
-.ting-search-carousel .ting-rs-carousel, .subtitle {
- float: left;
+rs-carousel-runner {
+ -moz-transform: translate3d(0, 0, 0);
+ -webkit-transform: translate3d(0, 0, 0);
+ -o-transform: translate3d(0, 0, 0);
+ -ms-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
}
-/* carousel */
-.ting-search-carousel .rs-carousel {
- opacity: 1 !important;
- overflow: hidden;
- width: 580px;
+.rs-carousel-transition .rs-carousel-runner {
+ -moz-transition: -moz-transform .400s ease;
+ -webkit-transition: -webkit-transform .400s ease;
+ -o-transition: -o-transform .400s ease;
+ -ms-transition: -ms-transform .400s ease;
+ transition: transform .400s ease;
}
-.ting-search-carousel .rs-carousel .rs-carousel-mask {
- position:relative;
- height: 200px;
-}
-
-.ting-search-carousel .rs-carousel .rs-carousel-runner {
- left: 0;
- list-style: none;
- margin: 0;
+/* Pagination and previous and next buttons
+ ========================================================================== */
+.rs-carousel-action-prev,
+.rs-carousel-action-next {
+ background-image: url('../images/arrows.png');
+ height: 55px;
overflow: hidden;
- padding: 0;
+ margin-top: -27px;
position: absolute;
- width: 580px;
-}
-
-.ting-search-carousel .rs-carousel .rs-carousel-item img {
- max-width: 112px;
- height: 140px;
-}
-.ting-search-carousel .no-js .rs-carousel .rs-carousel-runner {
- position: static;
-}
-
-.ting-search-carousel .rs-carousel .rs-carousel-item {
- float: left;
- height: 180px;
- overflow: hidden;
- padding-top: 20px;
- text-align: center;
- padding-right: 16px;
- width: 100px;
-}
-
-.ting-search-carousel .rs-carousel.rs-carousel-vertical .rs-carousel-item {
- float: none;
+ text-indent: -1000px;
+ top: 50%;
+ width: 55px;
}
-/* pagination */
-.ting-search-carousel .rs-carousel-pagination {
- list-style: none;
- margin: 0;
- padding: 0;
+.rs-carousel-action-next {
+ background-position: right 0;
+ right: 0;
}
-.ting-search-carousel .rs-carousel-disabled .rs-carousel-pagination {
+.rs-carousel-disabled .rs-carousel-action-prev,
+.rs-carousel-disabled .rs-carousel-action-next {
display: none;
}
-.ting-search-carousel .rs-carousel-pagination-link {
- display: inline;
-}
-
-.ting-search-carousel .rs-carousel-pagination-link a {
- padding: 2px 6px;
-}
-.ting-search-carousel .rs-carousel-pagination-link a:hover {
- text-decoration: none;
-}
-
-.ting-search-carousel .rs-carousel-pagination-link-active a {
- background: #444;
-}
-
-/* next & prev actions */
-.ting-search-carousel .rs-carousel-disabled .rs-carousel-action-next,
-.ting-search-carousel .rs-carousel-disabled .rs-carousel-action-prev {
- display: none;
-}
-
-.ting-search-carousel .rs-carousel-action-next,
-.ting-search-carousel .rs-carousel-action-prev {
- background-image: url('../images/buttons.png');
+/* Carousel content, mask, title etc.
+ ========================================================================== */
+.ajax-loader {
+ background: transparent url('../images/ajax-loader.gif') no-repeat center center;
+ left: 50%;
+ height: 20px;
+ margin-top: -10px;
+ margin-left: -110px;
position: absolute;
- top: 86px;
- height: 35px;
- outline: none;
- text-indent: -9999px;
- width: 35px;
+ top: 50%;
+ width: 220px;
}
-.ting-search-carousel .rs-carousel-action-prev {
- left: -15px;
+.rs-carousel-mask,
+.rs-carousel-mask ul {
+ display: inline-block;
+ vertical-align: top;
}
-.ting-search-carousel .rs-carousel-action-next {
- right: -15px;
- background-position: 0 -35px;
+.rs-carousel-mask {
+ width: 100%;
}
-.ting-search-carousel .rs-carousel-action-prev:hover {
- background-position: 70px 0;
+.rs-carousel-mask ul {
+ margin: 0;
+ list-style: none;
}
-.ting-search-carousel .rs-carousel-action-next:hover {
- background-position: 70px -35px;
+.rs-carousel-mask li {
+ float: left;
+ width: 100px;
+ text-align: center;
}
-.ting-search-carousel .rs-carousel-action-disabled {
- background-position: 35px 0 !important;
+.rs-carousel-item-image {
+ display: block;
}
-.ting-search-carousel .rs-carousel-action-prev + .rs-carousel-action-disabled {
- background-position: 35px -35px !important;
+.rs-carousel-item-image img {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
}
-.ting-search-carousel .rs-carousel-action-disabled {
- color: gray;
- cursor: default;
-}
-.ting-search-carousel .rs-carousel-pagination {
- display: none;
+/* Carousel tabs
+ ========================================================================== */
+.rs-carousel-tabs ul {
+ margin: 0;
+ position: relative;
+ text-align: center;
}
-.search-carousel-query {
- float: left;
+.rs-carousel-tabs ul li {
+ display: table-cell;
+ vertical-align: top;
+ text-align: center;
}
-.sort-hidden, .search-query-hidden {
- display: none;
+.rs-carousel-tabs ul li.active a {
+ position: relative;
}
-
diff --git a/images/arrows.png b/images/arrows.png
new file mode 100644
index 0000000..0fa4570
Binary files /dev/null and b/images/arrows.png differ
diff --git a/images/buttons.png b/images/buttons.png
deleted file mode 100644
index e2b5ab6..0000000
Binary files a/images/buttons.png and /dev/null differ
diff --git a/js/jquery.rs.carousel.js b/js/jquery.rs.carousel.js
index 93e8b29..146ec2c 100644
--- a/js/jquery.rs.carousel.js
+++ b/js/jquery.rs.carousel.js
@@ -1,7 +1,13 @@
+/*global jQuery */
+/*jshint bitwise: true, camelcase: true, curly: true, eqeqeq: true, forin: true,
+immed: true, indent: 4, latedef: true, newcap: true, nonew: true, quotmark: single,
+undef: true, unused: true, strict: true, trailing: true, browser: true */
+
/*
- * jquery.rs.carousel.js v0.8.1
+ * jquery.rs.carousel.js 1.0.1
+ * https://github.com/richardscarrott/jquery-ui-carousel
*
- * Copyright (c) 2011 Richard Scarrott
+ * Copyright (c) 2013 Richard Scarrott
* http://www.richardscarrott.co.uk
*
* Dual licensed under the MIT and GPL licenses:
@@ -9,56 +15,65 @@
* http://www.gnu.org/licenses/gpl.html
*
* Depends:
- * jquery.js v1.4+
+ * jquery.js v1.8+
* jquery.ui.widget.js v1.8+
- *
*/
-
+
(function ($, undefined) {
- var _super = $.Widget.prototype,
- horizontal = {
- pos: 'left',
- pos2: 'right',
- dim: 'width'
- },
- vertical = {
- pos: 'top',
- pos2: 'bottom',
- dim: 'height'
- };
-
+ 'use strict';
+
+ var _super = $.Widget.prototype;
+
$.widget('rs.carousel', {
+ version: '1.0.1',
+
options: {
- itemsPerPage: 'auto',
+ // selectors
+ mask: '> div',
+ runner: '> ul',
+ items: '> li',
+
+ // options
itemsPerTransition: 'auto',
orientation: 'horizontal',
- pagination: true,
- insertPagination: null,
+ loop: false,
+ whitespace: false,
nextPrevActions: true,
- insertNextAction: null,
- insertPrevAction: null,
+ insertPrevAction: function () {
+ return $('Prev').appendTo(this);
+ },
+ insertNextAction: function () {
+ return $('Next').appendTo(this);
+ },
+ pagination: true,
+ insertPagination: function (pagination) {
+ return $(pagination).insertAfter($(this).find('.rs-carousel-mask'));
+ },
speed: 'normal',
easing: 'swing',
- startAt: null,
- nextText: 'Next',
- prevText: 'Previous',
- create_: null, // widget factory uses create, but doesn't provide any useful data...
- beforeAnimate: null,
- afterAnimate: null
+ fx: 'slide',
+ translate3d: false,
+
+ // callbacks
+ create: null,
+ before: null,
+ after: null
},
_create: function () {
- this.page = 0;
+ // widget factory 1.8.* backwards compat
+ this.widgetFullName = this.widgetFullName || this.widgetBaseClass;
+ this.eventNamespace = this.eventNamespace || '.' + this.widgetName;
+
+ this.index = 0;
this._elements();
- this._defineOrientation();
+ this._setIsHorizontal();
this._addMask();
this._addNextPrevActions();
- // pass in false to avoid re-caching items again
this.refresh(false);
- this._trigger('create_', null, this._getData());
return;
},
@@ -67,71 +82,43 @@
_elements: function () {
var elems = this.elements = {},
- baseClass = '.' + this.widgetBaseClass;
-
- elems.mask = this.element.children(baseClass + '-mask');
- elems.runner = this.element.find(baseClass + '-runner').first();
- elems.items = elems.runner.children(baseClass + '-item');
- elems.pagination = undefined;
- elems.nextAction = undefined;
- elems.prevAction = undefined;
-
- return;
- },
+ fullName = this.widgetFullName;
- _addClasses: function () {
+ this.element.addClass(fullName);
- if (!this.oldClass) {
- this.oldClass = this.element[0].className;
- }
-
- this._removeClasses();
-
- var baseClass = this.widgetBaseClass,
- classes = [];
+ elems.mask = this.element
+ .find(this.options.mask)
+ .addClass(fullName + '-mask');
- classes.push(baseClass);
- classes.push(baseClass + '-' + this.options.orientation);
- classes.push(baseClass + '-items-' + this.options.itemsPerPage);
+ elems.runner = (elems.mask.length ? elems.mask : this.element)
+ .find(this.options.runner)
+ .addClass(fullName + '-runner');
- this.element.addClass(classes.join(' '));
+ elems.items = elems.runner
+ .find(this.options.items)
+ .addClass(fullName + '-item');
return;
},
- // removes rs-carousel* classes
- _removeClasses: function () {
-
- var self = this,
- widgetClasses = [];
+ _setIsHorizontal: function () {
- this.element.removeClass(function (i, classes) {
-
- $.each(classes.split(' '), function (i, value) {
-
- if (value.indexOf(self.widgetBaseClass) !== -1) {
- widgetClasses.push(value);
- }
-
- });
-
- return widgetClasses.join(' ');
-
- });
-
- return;
- },
+ var elems = this.elements,
+ fullName = this.widgetFullName;
- // defines obj to hold strings based on orientation for dynamic method calls
- _defineOrientation: function () {
+ this.element
+ .removeClass(fullName + '-horizontal')
+ .removeClass(fullName + '-vertical');
if (this.options.orientation === 'horizontal') {
this.isHorizontal = true;
- this.helperStr = horizontal;
+ this.element.addClass(fullName + '-horizontal');
+ elems.runner.css('top', '');
}
else {
this.isHorizontal = false;
- this.helperStr = vertical;
+ this.element.addClass(fullName + '-vertical');
+ elems.runner.css('left', '');
}
return;
@@ -148,7 +135,7 @@
}
elems.mask = elems.runner
- .wrap('
')
+ .wrap('')
.parent();
// indicates whether mask was dynamically added or already existed in mark-up
@@ -157,37 +144,6 @@
return;
},
- // sets runners width
- _setRunnerWidth: function () {
-
- if (!this.isHorizontal) {
- return;
- }
-
- var self = this;
-
- this.elements.runner.width(function () {
- return self._getItemDim() * self.getNoOfItems();
- });
-
- return;
- },
-
- // sets itemDim to the dimension of first item incl. margin
- _getItemDim: function () {
-
- // is this ridiculous??
- return this.elements.items
- ['outer' + this.helperStr.dim.charAt(0).toUpperCase() + this.helperStr.dim.slice(1)](true);
-
- },
-
- getNoOfItems: function () {
-
- return this.elements.items.length;
-
- },
-
// adds next and prev links
_addNextPrevActions: function () {
@@ -198,45 +154,156 @@
var self = this,
elems = this.elements,
opts = this.options,
- baseClass = this.widgetBaseClass;
-
+ eventNamespace = this.eventNamespace;
+
this._removeNextPrevActions();
- elems.prevAction = $('' + opts.prevText + '')
- .bind('click.' + this.widgetName, function (e) {
+ elems.prevAction = opts.insertPrevAction.apply(this.element[0])
+ .bind('click' + eventNamespace, function (e) {
e.preventDefault();
self.prev();
- });;
+ });
- elems.nextAction = $('' + opts.nextText + '')
- .bind('click.' + this.widgetName, function (e) {
+ elems.nextAction = opts.insertNextAction.apply(this.element[0])
+ .bind('click' + eventNamespace, function (e) {
e.preventDefault();
self.next();
});
- $.isFunction(opts.insertPrevAction) ?
- opts.insertPrevAction.apply(elems.prevAction[0]) : elems.prevAction.appendTo(this.element);
-
- $.isFunction(opts.insertNextAction) ?
- opts.insertNextAction.apply(elems.nextAction[0]) : elems.nextAction.appendTo(this.element);
-
+ return;
},
_removeNextPrevActions: function () {
-
+
var elems = this.elements;
-
+
if (elems.nextAction) {
elems.nextAction.remove();
elems.nextAction = undefined;
- }
-
+ }
+
if (elems.prevAction) {
elems.prevAction.remove();
elems.prevAction = undefined;
}
-
- return;
+
+ return;
+ },
+
+ // refresh carousel
+ refresh: function (recache) {
+
+ // undefined should pass condition
+ if (recache !== false) {
+ this._recacheItems();
+ }
+
+ this._setPages();
+ this._addPagination();
+ this._setRunnerWidth();
+ this.index = this._makeValid(this.index);
+ this.goToPage(this.index, false, undefined, true);
+ this._checkDisabled();
+
+ return;
+ },
+
+ // re-cache items in case new items have been added,
+ // moved to own method so continuous can easily override
+ // to avoid clones
+ _recacheItems: function () {
+
+ this.elements.items = this.elements.runner
+ .find(this.options.items)
+ .addClass(this.widgetFullName + '-item');
+
+ return;
+ },
+
+ // sets pages array - [jQuery(li, li, li), jQuery(li, li, li, li), jQuery(li, li, li), jQuery(li, li)]
+ _setPages: function () {
+
+ var self = this,
+ itemIndex = 0,
+ lastItemIndex = isNaN(this.options.itemsPerTransition) ? undefined : this._getLastItemIndex(),
+ start;
+
+ this.pages = [];
+
+ while (itemIndex < this.getNoOfItems()) {
+
+ // if itemsPerTransition isn't a number we need to get the visible
+ // items at each item index
+ if (isNaN(this.options.itemsPerTransition)) {
+ this.pages.push(self._getVisibleItems(itemIndex));
+ itemIndex += this.pages[this.pages.length - 1].length;
+ }
+ // otherwise simply slice up the items based on itemsPerTransition
+ else {
+ // making sure we don't go past the lastItemIndex
+ if (itemIndex >= lastItemIndex) {
+ this.pages.push(this.elements.items.slice(itemIndex));
+ break;
+ }
+ start = itemIndex;
+ // #37 - allow `itemsPerTransition` to be passed in as string
+ itemIndex += parseInt(this.options.itemsPerTransition, 10);
+ this.pages.push(this.elements.items.slice(start, itemIndex));
+ }
+ }
+
+ return;
+ },
+
+ // returns last logical item index
+ _getLastItemIndex: function () {
+
+ if (this.options.whitespace) {
+ return;
+ }
+
+ return this.elements.items.index(this._getVisibleItems(0, true).last());
+ },
+
+ // returns a jquery object containing the visible items where the `itemIndex`
+ // is considered to be the first visible item. Passing in reverse as true allows
+ // us to, for example, return the visible items from the last item backwards.
+ _getVisibleItems: function (itemIndex, reverse) {
+
+ var self = this,
+ page = [],
+ items = !reverse ? this.elements.items.slice(itemIndex) : [].reverse.apply($(this.elements.items)).slice(itemIndex),
+ maskDim = this._getMaskDim(),
+ dim = 0;
+
+ items
+ .each(function () {
+ dim += self.isHorizontal ? $(this).outerWidth(true) : $(this).outerHeight(true);
+ if (dim > maskDim) {
+ // if no items have been pushed to page then it means the
+ // first item is larger than the mask so we still need to push before
+ // breaking.
+ if (page.length === 0) {
+ page.push(this);
+ }
+ return false;
+ }
+ page.push(this);
+ });
+
+ return $(page);
+ },
+
+ _getMaskDim: function () {
+
+ return this.elements.mask[this.isHorizontal ? 'width' : 'height']();
+
+ },
+
+ getNoOfItems: function () {
+
+ return this.elements.items.length;
+
},
// adds pagination links and binds associated events
@@ -247,217 +314,317 @@
}
var self = this,
- elems = this.elements,
- opts = this.options,
- baseClass = this.widgetBaseClass,
+ fullName = this.widgetFullName,
+ pagination = $(''),
links = [],
- noOfPages = this._getNoOfPages(),
+ noOfPages = this.getNoOfPages(),
i;
-
+
this._removePagination();
- for (i = 1; i <= noOfPages; i++) {
- links[i] = '';
+ for (i = 0; i < noOfPages; i++) {
+ links[i] = '';
}
- elems.pagination = $('')
+ pagination
.append(links.join(''))
- .delegate('a', 'click.' + this.widgetName, function (e) {
+ .delegate('a', 'click' + this.eventNamespace, function (e) {
e.preventDefault();
-
self.goToPage(parseInt(this.hash.split('-')[1], 10));
-
});
- $.isFunction(opts.insertPagination) ?
- opts.insertPagination.apply(elems.pagination[0]) : elems.pagination.insertAfter(elems.mask);
-
+ this.elements.pagination = this.options.insertPagination.call(this.element[0], pagination);
+
return;
},
_removePagination: function () {
-
+
if (this.elements.pagination) {
this.elements.pagination.remove();
this.elements.pagination = undefined;
}
-
- return;
- },
-
- // sets array of pages
- _setPages: function () {
-
- var index = 1,
- page = 0;
-
- this.pages = [];
-
- while (page < this._getNoOfPages()) {
-
- // if index is greater than total number of items just go to last
- if (index > this.getNoOfItems()) {
- index = this.getNoOfItems();
- }
-
- this.pages[page] = index;
- index += this._getItemsPerTransition(); // this._getItemsPerPage(index);
- page++;
- }
return;
},
- // gets noOfPages
- _getNoOfPages: function () {
+ // returns noOfPages
+ getNoOfPages: function () {
- return Math.ceil((this.getNoOfItems() - this._getItemsPerPage()) / this._getItemsPerTransition()) + 1;
+ return this.pages.length;
},
- // gets options.itemsPerPage. If set to not number it's calculated based on maskdim
- _getItemsPerPage: function () {
+ // if no of items is less than items on first page then the
+ // carousel should be disabled.
+ _checkDisabled: function () {
- // if itemsPerPage of type number don't dynamically calculate
- if (typeof this.options.itemsPerPage === 'number') {
- return this.options.itemsPerPage;
+ if (this.getNoOfPages() <= 1) {
+ this.disable();
+ this._disabled = true;
+ }
+ // only enable if carousel was disabled internally.
+ else if (this._disabled) {
+ this.enable();
+ this._disabled = false;
}
-
- return Math.floor(this._getMaskDim() / this._getItemDim());
+ return;
},
- _getItemsPerTransition: function () {
+ _setRunnerWidth: function () {
- if (typeof this.options.itemsPerTransition === 'number') {
- return this.options.itemsPerTransition;
+ var elems = this.elements,
+ width = 0;
+
+ // reset width in case orientation has been changed
+ elems.runner.width('');
+
+ if (!this.isHorizontal) {
+ return;
}
- return this._getItemsPerPage();
-
- },
+ elems.runner
+ .width(function () {
+ elems.items
+ .each(function () {
+ width += $(this).outerWidth(true);
+ });
- _getMaskDim: function () {
-
- return this.elements.mask[this.helperStr.dim]();
+ return width;
+ });
+ return;
},
next: function (animate) {
-
- this.goToPage(this.page + 1, animate);
+
+ var index = this.index + 1;
+
+ if (this.options.loop && index >= this.getNoOfPages()) {
+ index = 0;
+ }
+
+ this.goToPage(index, animate, 'carousel:next');
return;
},
prev: function (animate) {
-
- this.goToPage(this.page - 1, animate);
+
+ var index = this.index - 1;
+
+ if (this.options.loop && index < 0) {
+ index = this.getNoOfPages() - 1;
+ }
+
+ this.goToPage(index, animate, 'carousel:prev');
return;
},
- // shows specific page (one based)
- goToPage: function (page, animate) {
+ goToPage: function (index, animate, /* INTERNAL */ eventName, /* INTERNAL */ silent) {
+
+ // undefined should pass
+ animate = animate === false ? false : true;
- if (!this.options.disabled && this._isValid(page)) {
- this.oldPage = this.page;
- this.page = page;
- this._go(animate);
+ if (!this.options.disabled && this._isValid(index)) {
+ this.prevIndex = this.index;
+ this.index = index;
+ // calling `this._slide` using `options.fx` to easily override within extensions
+ this['_' + this.options.fx]($.Event(eventName ? eventName : 'carousel:gotopage', {
+ animate: animate,
+ speed: animate ? this.options.speed : 0
+ }), silent);
}
-
+
+ // make sure updateUi is called even when disabled
+ this._updateUi();
+
return;
},
- // returns true if page index is valid, false if not
- _isValid: function (page) {
-
- if (page <= this._getNoOfPages() && page >= 1) {
- return true;
+ // `index` can be $obj, element or 0 based index
+ goToItem: function (index, animate) {
+
+ var page,
+ pageLength,
+ item,
+ itemLength;
+
+ // if a number get the element
+ if (!isNaN(index)) {
+ index = this.elements.items.eq(index);
}
-
- return false;
- },
- // returns valid page index
- _makeValid: function (page) {
-
- if (page < 1) {
- page = 1;
+ if (index.jquery) {
+ // unwrap from jquery object
+ index = index[0];
}
- else if (page > this._getNoOfPages()) {
- page = this._getNoOfPages();
+
+ // find item in pages array
+ pages:
+ for (page = 0, pageLength = this.getNoOfPages(); page < pageLength; page++) {
+ for (item = 0, itemLength = this.getPage(page).length; item < itemLength; item++) {
+ if (this.getPage(page)[item] === index) {
+ break pages;
+ }
+ }
}
- return page;
+ this.goToPage(page, animate);
+
+ // return item as jquery object
+ return $(index);
},
- // returns obj with useful data to be passed into callback events
- _getData: function () {
-
- return {
- page: this.page,
- oldPage: this.oldPage,
- noOfItems: this.getNoOfItems(),
- noOfPages: this._getNoOfPages(),
- elements: this.elements
- };
-
+ // returns true if index is valid, false if not
+ _isValid: function (index) {
+
+ if (index < this.getNoOfPages() && index >= 0) {
+ return true;
+ }
+
+ return false;
},
- // abstract _slide to easily override within extensions
- _go: function (animate) {
-
- this._slide(animate);
+ // returns valid page index
+ _makeValid: function (index) {
- return;
+ if (index < 0) {
+ index = 0;
+ }
+ else if (index >= this.getNoOfPages()) {
+ index = this.getNoOfPages() - 1;
+ }
+
+ return index;
},
- _slide: function (animate) {
-
- this._trigger('beforeAnimate', null, this._getData());
+ // if silent is true callbacks won't be fired
+ _slide: function (e, silent) {
var self = this,
- speed = animate === false ? 0 : this.options.speed, // default to animate
animateProps = {},
lastPos = this._getAbsoluteLastPos(),
-
- pos = this.elements.items
- .eq(this.pages[this.page - 1] - 1) // arrays and .eq() are zero based, carousel is 1 based
- .position()[this.helperStr.pos];
+ page = this.getPage(),
+ pos = page.first().position()[this.isHorizontal ? 'left' : 'top'],
+ eventNamespace = this.eventNamespace,
+ fullName = this.widgetFullName,
+ transitionEndEvent;
+
+ // if before returns false return and revert index back to prevIndex
+ if (!silent && !this._trigger('before', e, this._getEventData())) {
+ this.index = this.prevIndex;
+ return;
+ }
// check pos doesn't go past last posible pos
if (pos > lastPos) {
pos = lastPos;
}
- animateProps[this.helperStr.pos] = -pos;
- this.elements.runner
- .stop()
- .animate(animateProps, speed, this.options.easing, function () {
+ if (this.options.translate3d) {
- self._trigger('afterAnimate', null, self._getData());
+ transitionEndEvent = [
+ 'transitionend' + eventNamespace,
+ 'webkitTransitionEnd' + eventNamespace,
+ 'oTransitionEnd' + eventNamespace
+ ];
- });
-
- this._updateUi();
+ if (e.animate) {
+ this.element.addClass(fullName + '-transition');
+ }
+
+ this.elements.runner
+ .unbind(transitionEndEvent.join(' '))
+ .on(transitionEndEvent.join(' '), function (e) {
+ self.element.removeClass(fullName + '-transition');
+ if (!silent) {
+ self._trigger('after', e, self._getEventData());
+ }
+ })
+ .css('transform', 'translate3d(' + (this.isHorizontal ? -pos + 'px, 0, 0' : '0, ' + -pos + 'px, 0') + ')');
+
+ // if we're not animating the after callback should still be called
+ if (!e.animate) {
+ self.element.removeClass(fullName + '-transition');
+ if (!silent) {
+ self._trigger('after', e, self._getEventData());
+ }
+ }
+
+ }
+ else {
+
+ animateProps[this.isHorizontal ? 'left' : 'top'] = -pos;
+ this.elements.runner
+ .stop()
+ .animate(animateProps, e.speed, this.options.easing, function () {
+ if (!silent) {
+ self._trigger('after', e, self._getEventData());
+ }
+ });
+
+ }
return;
},
- // gets lastPos to ensure runner doesn't move beyond mask (allowing mask to be any width and the use of margins)
+ // gets lastPos to ensure runner doesn't move beyond mask
_getAbsoluteLastPos: function () {
-
- var lastItem = this.elements.items.eq(this.getNoOfItems() - 1);
-
- return lastItem.position()[this.helperStr.pos] + this._getItemDim() -
- this._getMaskDim() - parseInt(lastItem.css('margin-' + this.helperStr.pos2), 10);
+
+ if (this.options.whitespace) {
+ return;
+ }
+
+ var lastPos,
+ lastItem = this.elements.items.eq(this.getNoOfItems() - 1),
+ lastItemPos = lastItem.position()[this.isHorizontal ? 'left' : 'top'],
+ lastItemDim = lastItem[this.isHorizontal ? 'outerWidth' : 'outerHeight'](true);
+
+ lastPos = lastItemPos + lastItemDim - this._getMaskDim();
+
+ // if lastPos is less than 0 it means there aren't enough items to fill the entire mask
+ return lastPos < 0 ? undefined : lastPos;
+ },
+
+ // returns jQuery object of items on page
+ getPage: function (index) {
+
+ return this.pages[(typeof index !== 'undefined' ? index : this.index)] || $([]);
},
- // updates pagination, next and prev link status classes
+ // returns pages array
+ getPages: function () {
+
+ return this.pages;
+
+ },
+
+ _getEventData: function () {
+
+ return {
+ page: this.getPage(),
+ prevPage: this.getPage(this.prevIndex),
+ elements: this.elements
+ };
+
+ },
+
+ _getCreateEventData: function () {
+
+ return this._getEventData();
+
+ },
+
+
+ // updates pagination, next and prev link state classes
_updateUi: function () {
+ this._updateActiveItems();
+
if (this.options.pagination) {
this._updatePagination();
}
@@ -469,84 +636,85 @@
return;
},
- _updatePagination: function () {
-
- var baseClass = this.widgetBaseClass,
- activeClass = baseClass + '-pagination-link-active';
+ _updateActiveItems: function () {
- this.elements.pagination
- .children('.' + baseClass + '-pagination-link')
- .removeClass(activeClass)
- .eq(this.page - 1)
- .addClass(activeClass);
+ var fullName = this.widgetFullName,
+ activeClass = fullName + '-item-active';
+
+ this.elements.items
+ .removeClass(activeClass);
+
+ this.getPage()
+ .addClass(activeClass);
return;
},
- _updateNextPrevActions: function () {
-
- var elems = this.elements,
- page = this.page,
- disabledClass = this.widgetBaseClass + '-action-disabled';
+ _updatePagination: function () {
- elems.nextAction
- .add(elems.prevAction)
+ var fullName = this.widgetFullName,
+ activeClass = fullName + '-pagination-link-active',
+ disabledClass = fullName + '-pagination-disabled',
+ pagination = this.elements.pagination
.removeClass(disabledClass);
-
- if (page === this._getNoOfPages()) {
- elems.nextAction.addClass(disabledClass);
- }
- else if (page === 1) {
- elems.prevAction.addClass(disabledClass);
+
+ if (this.options.disabled) {
+ pagination.addClass(disabledClass);
}
+ pagination
+ .children('.' + fullName + '-pagination-link')
+ .removeClass(activeClass)
+ .eq(this.index)
+ .addClass(activeClass);
+
return;
},
- // formalise appending items as continuous adding complexity by inserting
- // cloned items
- add: function (items) {
+ _updateNextPrevActions: function () {
- this.elements.runner.append(items);
- this.refresh();
+ var elems = this.elements,
+ actions = elems.nextAction.add(elems.prevAction),
+ index = this.index,
+ fullName = this.widgetFullName,
+ activeClass = fullName + '-action-active',
+ disabledClass = fullName + '-action-disabled';
+
+ actions
+ .addClass(activeClass)
+ .removeClass(disabledClass);
+
+ if (this.options.disabled) {
+ actions.addClass(disabledClass);
+ }
- return;
- },
+ if (!this.options.loop) {
- remove: function (selector) {
-
- if (this.getNoOfItems() > 0) {
+ if (index === this.getNoOfPages() - 1) {
+ elems.nextAction
+ .removeClass(activeClass);
+ }
- this.elements.items
- .filter(selector)
- .remove();
+ if (index === 0) {
+ elems.prevAction
+ .removeClass(activeClass);
+ }
- this.refresh();
}
return;
},
- // handles option updates
_setOption: function (option, value) {
- var requiresRefresh = [
- 'itemsPerPage',
- 'itemsPerTransition',
- 'orientation'
- ];
-
_super._setOption.apply(this, arguments);
switch (option) {
case 'orientation':
-
- this.elements.runner
- .css(this.helperStr.pos, '')
- .width('');
- this._defineOrientation();
+ this._setIsHorizontal();
+ this.refresh();
break;
@@ -573,88 +741,102 @@
}
break;
- }
- if ($.inArray(option, requiresRefresh) !== -1) {
+ case 'loop':
+ case 'disabled':
+
+ this._updateUi();
+
+ break;
+
+ case 'itemsPerTransition':
+ case 'whitespace':
+
this.refresh();
+
+ break;
}
return;
},
- // if no of items is less than items per page we disable carousel
- _checkDisabled: function () {
-
- if (this.getNoOfItems() <= this._getItemsPerPage()) {
- this.elements.runner.css(this.helperStr.pos, '');
- this.disable();
- }
- else {
- this.enable();
- }
+ add: function (items) {
+
+ this.elements.runner.append(items);
+ this.refresh();
return;
},
- // refresh carousel
- refresh: function (recache) {
+ remove: function (selector) {
- // assume true (undefined should pass condition)
- if (recache !== false) {
- this.recacheItems();
- }
+ if (this.getNoOfItems() > 0) {
- this._addClasses();
- this._setPages();
- this._addPagination();
- this._checkDisabled();
- this._setRunnerWidth();
- this.page = this._makeValid(this.page);
- this.goToPage(this.page, false);
+ this.elements.items
+ .filter(selector)
+ .remove();
+
+ this.refresh();
+ }
return;
},
- // re-cache items in case new items have been added,
- // moved to own method so continuous can easily override
- // to avoid clones
- recacheItems: function () {
+ // returns current index
+ getIndex: function () {
- this.elements.items = this.elements.runner
- .children('.' + this.widgetBaseClass + '-item');
+ return this.index;
+
+ },
+
+ // returns prev index
+ getPrevIndex: function () {
+
+ return this.prevIndex;
- return;
},
// returns carousel to original state
destroy: function () {
var elems = this.elements,
+ fullName = this.widgetFullName,
cssProps = {};
this.element
- .removeClass()
- .addClass(this.oldClass);
-
+ .removeClass(fullName)
+ .removeClass(fullName + '-horizontal')
+ .removeClass(fullName + '-vertical');
+ elems.mask
+ .removeClass(fullName + '-mask');
+ elems.runner
+ .removeClass(fullName + '-runner');
+ elems.items
+ .removeClass(fullName + '-item');
+
if (this.maskAdded) {
elems.runner
- .unwrap('.' + this.widgetBaseClass + '-mask');
+ .unwrap();
}
- cssProps[this.helperStr.pos] = '';
- cssProps[this.helperStr.dim] = '';
- elems.runner.css(cssProps);
-
+ cssProps[this.isHorizontal ? 'left' : 'top'] = '';
+ cssProps[this.isHorizontal ? 'width' : 'height'] = '';
+ cssProps.transform = '';
+ elems.runner
+ .css(cssProps);
+
this._removePagination();
this._removeNextPrevActions();
-
+
+ elems.runner
+ .unbind(this.eventNamespace);
+
_super.destroy.apply(this, arguments);
return;
}
});
-
- $.rs.carousel.version = '0.8.1';
})(jQuery);
+
diff --git a/js/jquery.ui.touch-punch.min.js b/js/jquery.ui.touch-punch.min.js
new file mode 100644
index 0000000..33d6f97
--- /dev/null
+++ b/js/jquery.ui.touch-punch.min.js
@@ -0,0 +1,11 @@
+/*
+ * jQuery UI Touch Punch 0.2.2
+ *
+ * Copyright 2011, Dave Furfero
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Depends:
+ * jquery.ui.widget.js
+ * jquery.ui.mouse.js
+ */
+(function(b){b.support.touch="ontouchend" in document;if(!b.support.touch){return;}var c=b.ui.mouse.prototype,e=c._mouseInit,a;function d(g,h){if(g.originalEvent.touches.length>1){return;}g.preventDefault();var i=g.originalEvent.changedTouches[0],f=document.createEvent("MouseEvents");f.initMouseEvent(h,true,true,window,1,i.screenX,i.screenY,i.clientX,i.clientY,false,false,false,false,0,null);g.target.dispatchEvent(f);}c._touchStart=function(g){var f=this;if(a||!f._mouseCapture(g.originalEvent.changedTouches[0])){return;}a=true;f._touchMoved=false;d(g,"mouseover");d(g,"mousemove");d(g,"mousedown");};c._touchMove=function(f){if(!a){return;}this._touchMoved=true;d(f,"mousemove");};c._touchEnd=function(f){if(!a){return;}d(f,"mouseup");d(f,"mouseout");if(!this._touchMoved){d(f,"click");}a=false;};c._mouseInit=function(){var f=this;f.element.bind("touchstart",b.proxy(f,"_touchStart")).bind("touchmove",b.proxy(f,"_touchMove")).bind("touchend",b.proxy(f,"_touchEnd"));e.call(f);};})(jQuery);
\ No newline at end of file
diff --git a/js/ting_search_carousel.js b/js/ting_search_carousel.js
index ca3f09e..f361be7 100644
--- a/js/ting_search_carousel.js
+++ b/js/ting_search_carousel.js
@@ -1,39 +1,235 @@
+/**
+ * @file
+ * Handles the carousels loading of content and changes between tabs. There are
+ * two selectors to change tabs based on breaks points (which is handle by the
+ * theme).
+ *
+ * For large screens the normal tab list (ul -> li) is used while on small
+ * screens (mobile/tables) a select dropdown is used.
+ *
+ */
(function ($) {
- var carousel = false;
-
- Drupal.behaviors.tingSearchCarousel = {
- attach: function(context) {
- carousel_init(0);
-
- $('.search-controller li').click(function() {
- $(this).parent().find('li').removeClass('active');
- $(this).addClass('active');
-
- carousel_init($(this).index());
-
- return false;
- });
+ "use strict";
+
+ var TingSearchCarousel = (function() {
+
+ var cache = [];
+ var carousel;
+ var current_tab = 0;
+ var navigation;
+
+ /**
+ * Private: Ensures that the tabs have the same size. This is purly a design
+ * thing.
+ */
+ function _equale_tab_width() {
+ // Get the list of tabs and the number of tabs in the list.
+ var tabsList = $('.rs-carousel-list-tabs');
+ var childCount = tabsList.children('li').length;
+
+ // Only do somehting if there actually is tabs
+ if (childCount > 0) {
+
+ // Get the width of the list element.
+ var parentWidth = tabsList.width();
+
+ // Calculate the width of the - 's.
+ var childWidth = Math.floor(parentWidth / childCount);
+
+ // Calculate the last
- width to combined childrens width it self not
+ // included.
+ var childWidthLast = parentWidth - ( childWidth * (childCount -1) );
+
+ // Set the tabs css widths.
+ tabsList.children().css({'width' : childWidth + 'px'});
+ tabsList.children(':last-child').css({'width' : childWidthLast + 'px'});
+ }
}
- }
-
- carousel_init = function(index) {
- $.ajax({
- type: 'get',
- url : Drupal.settings.basePath + 'ting_search_carousel/results/ajax/' + index,
- dataType : 'json',
- success : function(msg) {
- $('.ting-search-carousel .subtitle').html(msg.subtitle);
-
- if (!carousel) {
- $('.ting-search-carousel .ting-rs-carousel .rs-carousel-runner').html(msg.content);
- carousel = $('.ting-search-carousel .ting-rs-carousel').carousel();
- }
- else {
- carousel.carousel('destroy');
- $('.ting-search-carousel .ting-rs-carousel .rs-carousel-runner').html(msg.content);
- carousel.carousel();
+
+ /**
+ * Private: Handler activated when the user changes tab.
+ */
+ function _change_tab(index) {
+ // Remove navigation selection.
+ navigation.find('.active').removeClass('active');
+ navigation.find(':selected').removeAttr('selected');
+
+ // Add new navigation seletions.
+ $(navigation.find('li')[index]).addClass('active');
+ $(navigation.find('option')[index]).attr('selected', true);
+
+ // Remove current content and show spinner.
+ $('.rs-carousel-title').html('');
+ $('.rs-carousel .rs-carousel-runner').children().remove();
+ $('.rs-carousel-inner .ajax-loader').removeClass('element-hidden');
+
+ // Hide navigation arrows.
+ $('.rs-carousel-action-prev').hide();
+ $('.rs-carousel-action-next').hide();
+
+ current_tab = index;
+ _update(current_tab);
+ }
+
+ /**
+ * Private: Check is the device have support for touch events.
+ */
+ function _is_touch_device() {
+ // First part work in most browser the last in IE 10.
+ return !!('ontouchstart' in window) || !!('onmsgesturechange' in window);
+ }
+
+ /**
+ * Private: Enable draggable touch support to the carousel, but only if the
+ * device is touch enabled.
+ */
+ function _add_touch_support() {
+ if (_is_touch_device()) {
+ // Add support for touch displays (requires jQuery Touch Punch).
+ $('.rs-carousel-runner').draggable({
+ axis: "x",
+ stop: function() {
+ var left = $('.rs-carousel-runner').position().left;
+
+ // Left side reached.
+ if (left > 0) {
+ carousel.carousel('goToPage', 0);
+ }
+
+ // Right side reached.
+ if ($('.rs-carousel-mask').width() - $('.rs-carousel-runner').width() > left) {
+ var lastIndex = carousel.carousel('getNoOfPages') - 1;
+ carousel.carousel('goToPage', lastIndex);
+ }
+ }
+ });
+
+ // Hide navigation arrows.
+ $('.rs-carousel-action-prev').hide();
+ $('.rs-carousel-action-next').hide();
+ }
+ }
+
+ /**
+ * Private: Start the tables and attach event handler for click and change
+ * events.
+ */
+ function _init_tabs() {
+ // Select navigation wrapper.
+ navigation = $('.rs-carousel-tabs');
+
+ // Sett equal with on the tab navigation menu.
+ _equale_tab_width();
+
+ // Attach click events to tabs.
+ $('.rs-carousel-list-tabs').on("click", "li", (
+ function(e) {
+ e.preventDefault();
+ _change_tab($(this).index());
+ return false;
}
+ ));
+
+ // Add change event to tabs.
+ $('.rs-carousel-select-tabs').live('change', function() {
+ _change_tab($(this).find(':selected').index());
+ });
+ }
+
+ /**
+ * Private: Updates the content when the user changes tabs. It will fetch
+ * the content from the server if it's not fetched allready.
+ */
+ function _update(index) {
+ // Get content from cache, if it have been fetched.
+ if (!(index in cache)) {
+ _fetch(index);
+
+ // Return as the fetch will call update once more when the Ajax call
+ // have completed.
+ return;
}
- });
- }
+
+ var data = cache[index];
+
+ // Remove spinner.
+ $('.rs-carousel-inner .ajax-loader').addClass('element-hidden');
+
+ // Update content.
+ $('.rs-carousel-title').html(data.subtitle);
+ $('.rs-carousel .rs-carousel-runner').append(data.content);
+
+ // Show navigation arrows.
+ $('.rs-carousel-action-prev').show();
+ $('.rs-carousel-action-next').show();
+
+ // Get the carousel running.
+ carousel.carousel('refresh');
+ carousel.carousel('goToPage', 0);
+ }
+
+ /**
+ * Private: Makes an ajax call to the server to get new content for the
+ * active navigation tab.
+ */
+ function _fetch(index) {
+ $.ajax({
+ type: 'get',
+ url : Drupal.settings.basePath + 'ting_search_carousel/results/ajax/' + index,
+ dataType : 'json',
+ success : function(data) {
+ cache[index] = {
+ 'subtitle' : data.subtitle,
+ 'content' : data.content
+ };
+
+ // If we still are on the same tab update it elese the content have
+ // been saved to the cache.
+ if (current_tab == data.index) {
+ _update(index);
+ }
+ }
+ });
+ }
+
+ /**
+ * Public: Init the carousel and fetch content for the first tab.
+ */
+ function init() {
+ // Select the carousel element.
+ carousel = $('.rs-carousel-items');
+
+ // Fix the tables and fetch the first tabs content.
+ _init_tabs();
+
+ // Start the carousel.
+ carousel.carousel({
+ noOfRows: 1,
+ orientation: 'horizontal',
+ itemsPerTransition: 'auto'
+ });
+
+ // Maybe add support for touch devices (will only be applied on touch
+ // enabled devices).
+ _add_touch_support();
+
+ // Will get content for the first tab.
+ _change_tab(0);
+ }
+
+ /**
+ * Expoes public functions.
+ */
+ return {
+ name: 'ting_search_carousel',
+ init: init
+ };
+ })();
+
+ /**
+ * Start the carousel when the document is ready.
+ */
+ $(document).ready(function() {
+ TingSearchCarousel.init();
+ });
})(jQuery);
diff --git a/plugins/content_types/carousel.inc b/plugins/content_types/carousel.inc
index 31dbdab..4dc8021 100644
--- a/plugins/content_types/carousel.inc
+++ b/plugins/content_types/carousel.inc
@@ -1,5 +1,4 @@
content = theme('ting_search_carousel', array('searches' => $searches));
+ $block->content = theme('ting_search_carousel', array('searches' => $searches, 'tab_position' => $tab_position));
return $block;
}
diff --git a/templates/ting_search_carousel.tpl.php b/templates/ting_search_carousel.tpl.php
new file mode 100644
index 0000000..1c61ba5
--- /dev/null
+++ b/templates/ting_search_carousel.tpl.php
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+ 1): ?>
+
+
+
+
+
+
+
+
+
diff --git a/templates/ting_search_carousel_collection.tpl.php b/templates/ting_search_carousel_collection.tpl.php
new file mode 100644
index 0000000..950f145
--- /dev/null
+++ b/templates/ting_search_carousel_collection.tpl.php
@@ -0,0 +1,10 @@
+
+ -
+
+ title); ?>
+
diff --git a/ting_search_carousel.admin.inc b/ting_search_carousel.admin.inc
index ef97494..00459f3 100644
--- a/ting_search_carousel.admin.inc
+++ b/ting_search_carousel.admin.inc
@@ -18,6 +18,39 @@ function ting_search_carousel_admin_page() {
return drupal_get_form('ting_search_carousel_admin_form');
}
+/**
+ * Admin settings form for frontend display of carousel.
+ */
+function ting_search_carousel_settings_admin_form($form, &$form_state) {
+ $form = array();
+
+ $form['ting_search_carousel_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Frontend settings'),
+ );
+
+ $form['ting_search_carousel_settings']['ting_search_carousel_tabs_position'] = array(
+ '#title' => t('Position of navigational tabs'),
+ '#type' => 'select',
+ '#options' => array(
+ 'bottom' => t("Bottom"),
+ ),
+ '#default_value' => variable_get('ting_search_carousel_tabs_position', 'bottom'),
+ '#required' => TRUE,
+ '#description' => t('Select where you want the tabs for displaying the various searches to be placed in frontend.'),
+ );
+
+ $form['ting_search_carousel_settings']['ting_search_carousel_description_toggle'] = array(
+ '#title' => t('Show descriptions for navigational tabs'),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('ting_search_carousel_description_toggle', 0),
+ '#required' => FALSE,
+ '#description' => t('Toggle whether descriptions of the various searches are displayed or not.'),
+ );
+
+ return system_settings_form($form);
+}
+
/**
* Search queries admin form.
*
@@ -289,9 +322,15 @@ function ting_search_carousel_search_submit($form, &$form_state) {
}
}
+ /**
+ * @TODO: Add clear cache button and detect changes in the query input fields
+ * so only partial cache can be rebuild. This will slow down the submit but
+ * may speed up the presentation for the users by kick starting the cache.
+ */
+
// Save the queries as a persistent variable.
variable_set('ting_carousel_search_queries', $searches);
- // Make ting search requests.
- ting_search_carousel_do_request();
+ // Clear carousel search cache
+ cache_clear_all('ting_search_carousel_result', 'cache');
}
diff --git a/ting_search_carousel.install b/ting_search_carousel.install
index 1f22cc1..8e30b55 100644
--- a/ting_search_carousel.install
+++ b/ting_search_carousel.install
@@ -1,7 +1,7 @@
'Ting search carousel',
- 'description' => 'Manage frontpage carousel',
- 'page callback' => 'ting_search_carousel_admin_page',
- 'page arguments' => array(),
- 'access arguments' => array('access administration pages'),
- 'file' => 'ting_search_carousel.admin.inc',
- 'weight' => 30,
- 'position' => 'left',
- );
-
$items['ting_search_carousel/results/ajax'] = array(
'title' => 'Show search carousel results',
'page callback' => 'ting_search_carousel_result',
@@ -29,58 +18,91 @@ function ting_search_carousel_menu() {
'type' => MENU_CALLBACK,
);
- return $items;
-}
+ $items['admin/config/ting/ting_search_carousel'] = array(
+ 'title' => 'Ting search carousel',
+ 'description' => 'Manage content for frontpage carousel.',
+ 'page callback' => 'ting_search_carousel_admin_page',
+ 'page arguments' => array(),
+ 'access arguments' => array('configure carousel'),
+ 'file' => 'ting_search_carousel.admin.inc',
+ );
-/**
- * Implements hook_block_info().
- */
-function ting_search_carousel_block_info() {
- $blocks = array();
+ $items['admin/config/ting/ting_search_carousel/settings'] = array(
+ 'title' => 'Content',
+ 'description' => 'Manage content for frontpage carousel.',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
- $blocks['ting_search_carousel'] = array(
- 'info' => 'Ting search carousel'
+ $items['admin/config/ting/ting_search_carousel/frontend_settings'] = array(
+ 'title' => 'Frontend settings',
+ 'description' => 'Manage settings for the display of frontpage carousel',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ting_search_carousel_settings_admin_form'),
+ 'access arguments' => array('configure carousel'),
+ 'file' => 'ting_search_carousel.admin.inc',
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 0,
);
- return $blocks;
+ return $items;
}
/**
- * Implements hook_block_view().
+ * Implements hook_permission().
*/
-function ting_search_carousel_block_view($delta) {
- $block = array();
-
- switch ($delta) {
- case 'ting_search_carousel':
- $searches = variable_get('ting_carousel_search_queries', array());
- $block['content'] = theme('ting_search_carousel', array('searches' => $searches));
- break;
- }
-
- return $block;
+function ting_search_carousel_permission() {
+ return array(
+ 'configure carousel' => array(
+ 'title' => t('Configure carousel'),
+ 'description' => t('Allow role to configure carousel.'),
+ ),
+ );
}
/**
* Implements hook_theme().
*/
function ting_search_carousel_theme($existing, $type, $theme, $path) {
- $hooks = array();
- $hooks['ting_search_carousel'] = array(
- 'variables' => array('searches' => NULL),
- 'template' => 'ting_search_carousel'
+ return array(
+ 'ting_search_carousel' => array(
+ 'variables' => array('searches' => NULL, 'tab_position' => NULL),
+ 'template' => 'templates/ting_search_carousel',
+ 'file' => 'ting_search_carousel.theme.inc',
+ ),
+ 'ting_search_carousel_collection' => array(
+ 'variables' => array('collection' => NULL),
+ 'template' => 'templates/ting_search_carousel_collection',
+ ),
+ 'ting_search_carousel_admin_form' => array(
+ 'render element' => 'form',
+ ),
);
+}
- $hooks['ting_search_carousel_collection'] = array(
- 'variables' => array('collection' => NULL),
- 'template' => 'ting_search_carousel_collection'
+/**
+ * Implements hook_block_info().
+ */
+function ting_search_carousel_block_info() {
+ return array(
+ 'ting_search_carousel' => array(
+ 'info' => 'Ting search carousel, block',
+ ),
);
+}
- $hooks['ting_search_carousel_admin_form'] = array(
- 'render element' => 'form',
+/**
+ * Implements hook_block_view().
+ *
+ * We ignore delta in this block view, as we only have one block.
+ */
+function ting_search_carousel_block_view($delta) {
+ return array(
+ 'content' => theme('ting_search_carousel', array(
+ 'searches' => variable_get('ting_carousel_search_queries', array()),
+ 'tab_position' => variable_get('ting_search_carousel_tabs_position'),
+ )),
);
-
- return $hooks;
}
/**
@@ -92,138 +114,212 @@ function ting_search_carousel_ctools_plugin_directory($module, $plugin) {
}
}
-/**
- * Implements hook_cron().
- */
-function ting_search_carousel_cron() {
- ting_search_carousel_do_request(TRUE);
-}
-
/**
* Perform a ting search, retrieve covers and store some of data in cache.
+ *
+ * @param int $index
+ * Search carsousel tab index.
*/
-function ting_search_carousel_do_request($full = FALSE) {
- $search_queries = variable_get('ting_carousel_search_queries', array());
- $search_items = array();
-
- // There are any queries set.
- if (count($search_queries) > 0) {
+function ting_search_carousel_do_request($index) {
+ // Get query based in the index parameter.
+ $queries = variable_get('ting_carousel_search_queries', array());
+ $query = isset($queries[$index]) ? $queries[$index] : FALSE;
+
+ // If a query is defined, do a look-up else do nothing.
+ if ($query) {
+ // Load the ting client.
module_load_include('client.inc', 'ting');
- module_load_include('pages.inc', 'ting_covers');
-
- $service = new AdditionalInformationService(variable_get('addi_wsdl_url'), variable_get('addi_username'), variable_get('addi_group'), variable_get('addi_password'));
- $j = 0;
-
- // Search for each query up to 100 items.
- foreach ($search_queries as $item) {
- $result = '';
-
- // Whether we need to fetch all items, perform indepth search.
- // Seek first 100 collections otherwise.
- if ($full) {
- $page = 1;
- $collections = array();
- // Make recursive ting calls
- while ($result = ting_do_search($item['query'], $page++, 50, array('facets' => array(), 'enrich' => TRUE, 'allObjects' => FALSE))) {
- if ($result->numTotalCollections == 0) {
- break;
- }
-
- $collections[] = $result;
- }
-
- // Rearrange fetched results, as if they were retrieved via single call
- $result = new stdClass();
- $result->collections = array();
- foreach ($collections as $collection) {
- foreach ($collection->collections as $item) {
- $result->collections[] = $item;
- }
- }
+
+ // Try to fetch results 50 at a time.
+ $result = NULL;
+ $page = 1;
+ $collections = array();
+ while ($result = ting_do_search($query['query'], $page++, 50, array('facets' => array(), 'enrich' => TRUE, 'allObjects' => FALSE))) {
+ $collections[] = $result;
+
+ if (!$result->more) {
+ // If no more result exists break the loop.
+ break;
}
- else {
- $result = ting_do_search($item['query'], 1, 100, array('facets' => array(), 'enrich' => TRUE, 'allObjects' => FALSE));
+ }
+ }
+
+ // Loop over the search results collecting basic object information.
+ $local_ids = array();
+ $objects = array();
+ foreach ($collections as $collection) {
+ foreach ($collection->collections as $ting_collection) {
+ foreach ($ting_collection->reply->objects as $object) {
+ $local_ids[] = $object->localId;
+ $objects[$object->localId] = array(
+ 'id' => $object->id,
+ 'title' => isset($object->record['dc:title'][''][0]) ? $object->record['dc:title'][''][0] : '',
+ 'creator' => isset($object->record['dc:creator']['oss:aut'][0]) ? $object->record['dc:creator']['oss:aut'][0] : '',
+ );
}
+ }
+ }
+
+ // Load helper function form the covers module.
+ module_load_include('pages.inc', 'ting_covers');
+
+ // Get cover service client.
+ $service = new AdditionalInformationService(
+ variable_get('addi_wsdl_url'),
+ variable_get('addi_username'),
+ variable_get('addi_group'),
+ variable_get('addi_password'));
+
+ // Initialize items array.
+ $items = array();
+
+ // Get covers for the ids.
+ $covers = $service->getByFaustNumber($local_ids);
- // For each found collection, find a related item with cover.
- foreach ($result->collections as $collection) {
- foreach ($collection->reply->objects as $object) {
- $path = 'public://ting_search_carousel/' . md5($object->localId) . '.jpg';
-
- $ting_item_cover = '';
- $file = '';
-
- // If image is not present, try fetch it
- if (!file_exists($path)) {
- $ting_item_cover = $service->getByFaustNumber($object->localId);
-
- // No cover found, go to next item in collection
- if (count($ting_item_cover) == 0) {
- break;
- }
-
- $k = array_keys($ting_item_cover);
- $source_url = '';
-
- if ($ting_item_cover[$k[0]]->thumbnailUrl) {
- $source_url = $ting_item_cover[$k[0]]->thumbnailUrl;
- }
- elseif ($ting_item_cover[$k[0]]->detailUrl) {
- $source_url = $ting_item_cover[$k[0]]->detailUrl;
- }
-
- // Saves and returns new image file object
- $file = _ting_covers_pages_fetch_image('public://ting_search_carousel/' . md5($object->localId) . '.jpg', $source_url);
- }
-
- $item = new stdClass();
- $item->id = $object->id;
- $item->creator = isset($object->record['dc:creator']['oss:aut'][0]) ? $object->record['dc:creator']['oss:aut'][0] : '';
- $item->title = isset($object->record['dc:title'][''][0]) ? $object->record['dc:title'][''][0] : '';
- $item->image = image_style_url('ting_search_carousel', isset($file->uri) ? $file->uri : $path);
-
- $search_items[$j][] = $item;
- break;
- }
+ // Loop over the fetched covers and build items.
+ foreach ($covers as $local_id => $cover) {
+ // Build cover filename.
+ $filename = 'public://ting_search_carousel/' . md5($local_id) . '.jpg';
+
+ // Check if file have been downloaded.
+ if (!file_exists($filename)) {
+ // Extract the image source url.
+ $source_url = FALSE;
+ if ($cover->detailUrl) {
+ $source_url = $cover->detailUrl;
+ }
+ elseif ($cover->thumbnailUrl) {
+ $source_url = $cover->thumbnailUrl;
}
- $j++;
+ // Download the cover image and get local uri.
+ $file = _ting_covers_pages_fetch_image($filename, $source_url);
+ $filename = isset($file->uri) ? $file->uri : $filename;
+ }
+
+ // Build object with the information collected.
+ $item = new stdClass();
+ $item->id = $objects[$local_id]['id'];
+ $item->title = $objects[$local_id]['title'];
+ $item->creator = $objects[$local_id]['creator'];
+ $item->image = image_style_url('ting_search_carousel', $filename);
+
+ $items[] = $item;
+ }
+
+ // Update the cache with the fetched items.
+ _ting_search_carousel_set_cache($index, $items);
+}
+
+/**
+ * Returns the content for a search carousel tab. It utilizes both static and
+ * database cache to optimize the cover page search. It will slowly fill the
+ * cache based on the tab index provide. This index matches the search carousel
+ * query index set in the administration backend, when defining the tabs.
+ *
+ * If no cache is found it tries to search the data well and calls it self once
+ * more to fetch the newly fetch data. If data still do not exists in the cache
+ * an empty string is returned.
+ *
+ * @param int $index
+ * Search carsousel tab index.
+ *
+ * @param bool $search
+ * If FALSE an attempt to fetch data _not_ be tried. Defaults to TRUE.
+ *
+ * @return mixed
+ * Returns an array with ting covers or the empty string if non is found.
+ */
+function _ting_search_carousel_get_content($index, $search = TRUE) {
+ // Utilize the static cache.
+ $static = &drupal_static(__FUNCTION__);
+ if (isset($static[$index]) && !empty($static[$index])) {
+ return $static[$index];
+ }
+
+ // Check if there is a database cache of the search results.
+ if ($cache = cache_get('ting_search_carousel_result')) {
+ // Get cached data and store it in the static cache.
+ $data = $cache->data;
+ $static = $data;
+
+ // Check if data exists for the index.
+ if (isset($data[$index]) && !empty($data[$index])) {
+ return $data[$index];
}
}
- // Clear & set the cache.
- cache_clear_all('ting_search_result', 'cache');
- cache_set('ting_search_result', serialize($search_items), 'cache');
+ // No cache found for that index, so try getting information from the
+ // data well and cover service.
+ if ($search) {
+ ting_search_carousel_do_request($index);
+ return _ting_search_carousel_get_content($index, FALSE);
+ }
+
+ // No form for cached data was found.
+ return '';
+}
+
+/**
+ * Set database cache base on tab index in the front-end.
+ *
+ * @param int $index
+ * Search carousel tab index.
+ *
+ * @param mixed $items
+ * Search carousel items, containing id, title, creator and image url.
+ */
+function _ting_search_carousel_set_cache($index, $items) {
+ $data = array();
+ if ($cache = cache_get('ting_search_carousel_result')) {
+ $data = $cache->data;
+ }
+ $data[$index] = $items;
+ cache_set('ting_search_carousel_result', $data, 'cache');
}
/**
* Implements hook_image_default_styles().
*/
function ting_search_carousel_image_default_styles() {
- $styles = array();
-
- $styles['ting_search_carousel'] = array(
- 'name' => 'ting_search_carousel',
- 'effects' => array(
- 0 => array(
- 'label' => 'Scale',
- 'help' => 'Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.',
- 'effect callback' => 'image_scale_effect',
- 'dimensions callback' => 'image_scale_dimensions',
- 'form callback' => 'image_scale_form',
- 'summary theme' => 'image_scale_summary',
- 'module' => 'image',
- 'name' => 'image_scale',
- 'data' => array(
- 'width' => '',
- 'height' => '140',
- 'upscale' => 0,
+ return array(
+ 'ting_search_carousel' => array(
+ 'name' => 'ting_search_carousel',
+ 'effects' => array(
+ array(
+ 'label' => 'Scale and crop',
+ 'effect callback' => 'image_scale_and_crop_effect',
+ 'dimensions callback' => 'image_scale_dimensions',
+ 'module' => 'image',
+ 'name' => 'image_scale_and_crop',
+ 'data' => array(
+ 'width' => '200',
+ 'height' => '290',
+ ),
+ 'weight' => '1',
),
- 'weight' => '1',
),
),
);
+}
- return $styles;
+/**
+ * Wrapper function that adds the modules JavaScript.
+ */
+function _ting_search_carousel_add_javascript() {
+ $path = drupal_get_path('module', 'ting_search_carousel');
+
+ drupal_add_library('system', 'ui.widget');
+ drupal_add_library('system', 'ui.draggable');
+ drupal_add_js($path . '/js/jquery.ui.touch-punch.min.js');
+ drupal_add_js($path . '/js/jquery.rs.carousel.js');
+ drupal_add_js($path . '/js/ting_search_carousel.js');
}
+/**
+ * Wrapper function that adds the modules css.
+ */
+function _ting_search_carousel_add_css() {
+ $path = drupal_get_path('module', 'ting_search_carousel');
+ drupal_add_css($path . '/css/ting_search_carousel.css');
+}
diff --git a/ting_search_carousel.pages.inc b/ting_search_carousel.pages.inc
index 22a0202..4178cd6 100644
--- a/ting_search_carousel.pages.inc
+++ b/ting_search_carousel.pages.inc
@@ -1,36 +1,39 @@
data);
- $ting_seach_queries = variable_get('ting_carousel_search_queries', array());
+ // Set default return values.
+ $collections = _ting_search_carousel_get_content($index);
$subtitle = '';
+ // Get configuration.
+ $ting_seach_queries = variable_get('ting_carousel_search_queries', array());
if (isset($ting_seach_queries[$index])) {
$subtitle = $ting_seach_queries[$index]['subtitle'];
- foreach ($cache_ting_search_result[$index] as $k => $v) {
- $result .= theme('ting_search_carousel_collection', array('collection' => $v));
+
+ // If cache have been found theme search carousel pages.
+ if (!empty($collections)) {
+ foreach ($collections as $collection) {
+ $content .= theme('ting_search_carousel_collection', array('collection' => $collection));
+ }
}
}
- echo drupal_json_encode(array('subtitle' => $subtitle, 'content' => $result));
- exit();
+ // Return JSON output.
+ drupal_json_output(array(
+ 'subtitle' => $subtitle,
+ 'content' => $content,
+ 'index' => $index,
+ ));
}
diff --git a/ting_search_carousel.theme.inc b/ting_search_carousel.theme.inc
new file mode 100644
index 0000000..abcb732
--- /dev/null
+++ b/ting_search_carousel.theme.inc
@@ -0,0 +1,19 @@
+
-
-
- $search) : ?>
- -
-
-
-
-
-
-
-
-
-
-
--
-
-
-
-
-
-
-
-
-
diff --git a/ting_search_carousel_collection.tpl.php b/ting_search_carousel_collection.tpl.php
deleted file mode 100644
index 500c5dc..0000000
--- a/ting_search_carousel_collection.tpl.php
+++ /dev/null
@@ -1,15 +0,0 @@
-
--
-
-
-
- creator); ?>
- title); ?>
-
-
-