Skip to content

Instantly share code, notes, and snippets.

@draeton
Created April 12, 2013 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save draeton/93c00021798c6048e954 to your computer and use it in GitHub Desktop.
Save draeton/93c00021798c6048e954 to your computer and use it in GitHub Desktop.
Ford » Ratings and Reviews code sample
/*global jQuery,ngbs*/
(function ($, ngbs) {
"use strict";
/**
* ngbs.widget: ratings-and-reviews-pagination
*
* Copyright (c) 2012, Team Detroit
* All rights reserved
*
* @desc Pagination widget setup and event handlers
* @dependencies jQuery, ngbs
*/
ngbs.widget("ratings-and-reviews-pagination", function (message_bus, $elements) {
var PaginationWidget = function (element) {
this.$element = $(element);
this.$pagination = this.$element.find(".rr-pagination");
this.baseURL = null;
this.currentPage = null;
this.totalPages = null;
this.search = null;
this.initialize();
};
PaginationWidget.prototype = {
/**
* initialize
*/
initialize: function () {
this.setPageData();
this.proxyHandlers();
this.bindHandlers();
},
/**
* Set the current page values from the data-* attributes
*/
setPageData: function () {
var data = this.$element.data();
this.baseurl = data.baseUrl;
this.currentpage = Number(data.currentPage);
this.totalpages = Number(data.totalPages);
this.search = data.search;
},
/**
* proxyHandlers
*/
proxyHandlers: function () {
this.clickHandler = $.proxy(this.clickHandler, this);
this.updateView = $.proxy(this.updateView, this);
},
/**
* bindHandlers
*/
bindHandlers: function () {
this.$element.on("click", "a", this.clickHandler);
message_bus.listen(document, "view.update", this.updateView);
},
/**
* clickHandler
*/
clickHandler: function (e) {
e.stopPropagation();
e.preventDefault();
var $anchor = $(e.currentTarget);
var isDisabled = $anchor.is(".disabled");
var page = Number($anchor.data("page"));
if (!isDisabled && page !== this.currentPage) {
this.currentPage = page;
// message bus
message_bus.send(this.$element, "page.change", {
page: page
});
}
},
/**
* Update the pagination based on the JSON response from the API
*/
updateView: function (e, data) {
var results = "";
if (data && data.wrapper) {
// update pagination widget content
this.currentPage = data.wrapper.currentPage;
this.totalPages = data.wrapper.totalPages;
this.renderView();
this.renderDisplayResults(data.wrapper);
}
},
/**
* Calculcate start, end, next and prev based on current and total pages
*/
calculatePages: function () {
var currentPage = this.currentPage;
var totalPages = this.totalPages;
// calc
var startPage = currentPage - ((currentPage - 1) % 3);
var endPage = totalPages <= (startPage + 2) ? totalPages : (startPage + 2);
var prevPage = startPage > 1 ? (startPage - 1) : "";
var nextPage = endPage < totalPages ? (endPage + 1) : "";
return {
start: startPage,
end: endPage,
prev: prevPage,
next: nextPage
};
},
/**
* Get the anchor href based on page number
*/
getHref: function (page) {
var href = this.baseURL + page + this.search;
if (String(page) === "1") {
href = this.baseURL + this.search;
}
return href;
},
/**
* Get a list item string
*/
getListItem: function (page, className, content) {
var href = this.getHref(page);
return "<li><a href='" + href + "' data-page='" + page + "' class='" + className + "'>" + content + "</a></li>";
},
/**
* Render the page number display
*/
renderView: function () {
var pages = this.calculatePages();
var lis = "";
// render prev
lis += this.getListItem(pages.prev, (pages.prev === "" ? "prev disabled" : "prev"), "<span class='ui-arrow-s ui-arrow-s-blue-l-png'></span>PREV");
// render pages
var page;
for (page = pages.start; page <= pages.end; page++) {
lis += this.getListItem(page, (page === this.currentPage ? "selected" : ""), page);
}
// render next
lis += this.getListItem(pages.next, (pages.next === "" ? "next disabled" : "next"), "NEXT<span class='ui-arrow-s ui-arrow-s-blue-r-png'></span>");
// place in dom
var $lis = $(lis);
this.$element.find("ul").empty().append($lis);
},
/**
* Render display resutls string
*/
renderDisplayResults: function (wrapper) {
var currentPage = wrapper.currentPage;
var reviewsPerPage = wrapper.reviewsPerPage;
var totalReviews = wrapper.totalReviews;
var start = totalReviews ? ((currentPage - 1) * reviewsPerPage) + 1 : 0;
var end = Math.min(currentPage * reviewsPerPage, totalReviews);
var results = start + "-" + end + " of " + totalReviews;
// message bus
message_bus.send(document, "results.update", results);
}
};
// initialization for elements not included on page load
message_bus.listen(document, "ratings-and-reviews-pagination.add", function (e, data) {
var element = e.target;
var paginationWidget = new PaginationWidget(element);
});
// get started
message_bus.send($elements, "ratings-and-reviews-pagination.add");
});
}(jQuery, ngbs));
/*global jQuery,ngbs*/
(function ($, ngbs) {
"use strict";
/**
* ngbs.widget: ratings-and-reviews-radio
*
* Copyright (c) 2012, Team Detroit
* All rights reserved
*
* @desc Radio widget setup and event handlers
* @dependencies jQuery, ngbs
*/
ngbs.widget("ratings-and-reviews-radio", function (message_bus, $elements) {
var RadioWidget = function (element) {
this.$element = $(element);
this.$radio = this.$element.find(".rr-radio");
this.$input = this.$element.find("input");
this.$buttons = this.$element.find(".rr-radio-button");
this.initialize();
};
RadioWidget.prototype = {
/**
* initialize
*/
initialize: function () {
this.setWidgetData();
this.proxyHandlers();
this.bindHandlers();
},
/**
* setWidgetData
*/
setWidgetData: function () {
var data = this.$element.data();
this.name = data.name;
this.label = data.label;
},
/**
* proxyHandlers
*/
proxyHandlers: function () {
this.clickButtonHandler = $.proxy(this.clickButtonHandler, this);
this.updateHandler = $.proxy(this.updateHandler, this);
},
/**
* bindHandlers
*/
bindHandlers: function () {
this.$element.on("click", "div.rr-radio-button", this.clickButtonHandler);
message_bus.listen(this.$element, "input.update", this.updateHandler);
},
/**
* clickButtonHandler
*/
clickButtonHandler: function (e) {
var $button = $(e.currentTarget);
var data = $button.data();
var key = data.key;
if ($button.not(".selected")) {
this.$buttons.removeClass("selected");
$button.addClass("selected");
this.$input.val(key);
}
// message bus
this.sendData();
},
/**
* updateHandler
*/
updateHandler: function (e, data) {
if (data) {
this.updateView(data.key);
}
},
/**
* Reset all buttons and then set one to match key
*/
updateView: function (key) {
var self = this;
// reset all buttons
this.$buttons.removeClass("selected");
this.$input.val("");
// set matched key
if (key) {
this.$input.val(key);
this.$buttons.filter("[data-key=" + key + "]").addClass("selected");
}
// message bus
this.sendData();
},
/**
* sendData
*/
sendData: function () {
var $selected = this.$buttons.filter(".selected");
var value = "";
if ($selected.length) {
value = $selected.data("value");
}
message_bus.send(this.$element, "input.change", {
id: this.name,
key: this.$input.val(),
label: this.label,
value: value
});
}
};
// initialization for elements not included on page load
message_bus.listen(document, "ratings-and-reviews-radio.add", function (e, data) {
var element = e.target;
var radioWidget = new RadioWidget(element);
});
// get started
message_bus.send($elements, "ratings-and-reviews-radio.add");
});
}(jQuery, ngbs));
/*global jQuery,ngbs*/
(function ($, ngbs) {
"use strict";
/**
* debounce
*
* Adapted from http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
*/
var debounce = function (func, threshold, execAsap) {
var timeout;
return function () {
var context = this;
var args = arguments;
var delayed = function () {
if (!execAsap) {
func.apply(context, args);
}
timeout = null;
};
if (timeout) {
window.clearTimeout(timeout);
} else if (execAsap) {
func.apply(context, args);
}
timeout = setTimeout(delayed, threshold || 50);
};
};
/**
* ngbs.page: ratings-and-reviews
*
* Copyright (c) 2012, Team Detroit
* All rights reserved
*
* @desc Handles event binds and API calls for ratings and reviews
* @dependencies jQuery, ngbs
*/
ngbs.page(function (message_bus) {
var Reviews = function (element) {
this.$element = $(element);
this.$loading = $(".rr-reviews-content-loading");
this.$content = $(".rr-reviews-content");
this.$title = $(".rr-reviews-header h2");
this.$displayResults = $(".rr-reviews-display-results");
this.$searchForm = this.$element.find(".rr-reviews-search");
this.$billboard = this.$element.find(".rr-billboard");
this.baseURL = null;
this.avatarURL = null;
this.model = null;
this.page = null;
this.filter = {};
this.order = null;
this.initialize();
};
Reviews.prototype = {
/**
* initialize
*/
initialize: function () {
this.setPageData();
this.proxyHandlers();
this.bindHandlers();
this.updateShare();
this.updateAvatar();
this.getReviews = debounce(this.getReviews, 500);
},
/**
* Set the current page values from the data-* attributes
*/
setPageData: function () {
var data = this.$element.find(".rr-data").data();
this.baseURL = data.baseUrl;
this.avatarURL = data.avatarUrl;
this.category = data.category;
this.model = data.model;
this.page = data.page;
this.order = data.order;
this.query = data.query;
},
/**
* proxyHandlers
*/
proxyHandlers: function () {
this.filterHandler = $.proxy(this.filterHandler, this);
this.pageHandler = $.proxy(this.pageHandler, this);
this.orderHandler = $.proxy(this.orderHandler, this);
this.reviewsHandler = $.proxy(this.reviewsHandler, this);
this.updateTitle = $.proxy(this.updateTitle, this);
this.updateDisplayResults = $.proxy(this.updateDisplayResults, this);
this.clickSearchHandler = $.proxy(this.clickSearchHandler, this);
this.blurSearchHandler = $.proxy(this.blurSearchHandler, this);
},
/**
* bindHandlers
*/
bindHandlers: function () {
message_bus.listen(this.$element, "filter.update", this.filterHandler);
message_bus.listen(this.$element, "page.update", this.pageHandler);
message_bus.listen(this.$element, "order.update", this.orderHandler);
message_bus.listen(this.$element, "reviews.update", this.reviewsHandler);
message_bus.listen(document, "title.update", this.updateTitle);
message_bus.listen(document, "results.update", this.updateDisplayResults);
this.$searchForm.on("click", "label", this.clickSearchHandler);
this.$searchForm.on("blur", "input", this.blurSearchHandler);
},
/**
* Update the current filters
*/
filterHandler: function (e, data) {
var self = this;
if (data) {
if (data.id) {
this.filter[data.id] = data.key;
}
$.each(this.filter, function (i, value) {
if (value === "") {
delete self.filter[i];
}
});
if (!data.silent) {
this.page = 1;
message_bus.send(this.$element, "reviews.update");
}
}
},
/**
* Update the current sort order
*/
orderHandler: function (e, data) {
if (typeof data === "object") {
this.order = data.key;
}
this.page = 1;
message_bus.send(this.$element, "reviews.update");
},
/**
* Update the current page number
*/
pageHandler: function (e, data) {
if (typeof data === "object") {
this.page = data.page;
}
message_bus.send(this.$element, "reviews.update");
},
/**
* Start processing for udpating on page reviews
*/
reviewsHandler: function (e, data) {
var self = this;
var titleOffset = this.$title.offset();
// hide content
this.$content.hide();
this.$loading.show();
// scroll to top of content
$('html, body, document').animate({scrollTop: titleOffset.top + 'px'}, 350);
// render callback function
var callback = function (response) {
self.updateContent(response);
};
// select api call
if (this.query) {
this.searchReviews(callback);
} else {
this.getReviews(callback);
}
},
/**
* Make an API call for reviews based on current settings
*/
getReviews: function (callback) {
var params = this.getReviewsParams();
// load reviews data
$.get("/core-services/reviews.json", params).done(function (response) {
message_bus.send(document, "view.update", response);
});
// return promsie for reviews content
$.get("/core-services/reviews.html", params).done(callback);
// metrics
message_bus.send(this.$element, "click.metrics", {
name: "filter"
});
},
/**
* Serialize filters and return params object
*/
getReviewsParams: function () {
var filters = [];
$.each(this.filter, function (index, filter) {
filters.push(index + "=" + filter);
});
return {
model: this.category,
page: this.page,
filter: filters.join(","),
order: this.order
};
},
/**
* Make an API call to search for reviews
*/
searchReviews: function (callback) {
var params = {
model: this.category,
query: this.query,
page: this.page
};
// load reviews data
$.get("/core-services/reviews/search.json", params).done(function (response) {
message_bus.send(document, "view.update", response);
});
// return promsie for reviews content
$.get("/core-services/reviews/search.html", params).done(callback);
// metrics
message_bus.send(this.$element, "click.metrics", {
name: "filter"
});
},
/**
* Update the on-page title
*/
updateTitle: function (e, data) {
var title = data;
title = title.replace("%nameplate%", this.model);
this.$title.html(title);
},
/**
* Update the on-page display results
*/
updateDisplayResults: function (e, data) {
var results = data;
this.$displayResults.html(results);
},
/**
* Update and reveal reviews content
*/
updateContent: function (content) {
// change content
this.$content.html(content);
// update avatars
this.updateAvatar();
// update share
this.updateShare();
//reveal
this.$loading.hide();
this.$content.show();
},
/**
* Update avatar images
*/
updateAvatar: function () {
var self = this;
this.$element.find("img.avatar").each(function () {
var $img = $(this);
var id = $img.data("id");
var src = id ? self.avatarURL + "&id=" + id : self.avatarURL;
$img.attr("src", src).show();
});
},
/**
* Update share buttons
*/
updateShare: function () {
var self = this;
var image = this.$billboard.data("image");
// share buttons
this.$element.find("a.share").each(function () {
var $anchor = $(this);
var path = $anchor.data("path");
var href = self.baseURL + path;
$anchor.attr("href", href);
$anchor.data("image", image);
// send to ImportShareThisWidget queue
message_bus.send($anchor, "queue");
});
},
/**
* Upon clicking on the search form, hide the label
*/
clickSearchHandler: function (e) {
var $label = $(e.currentTarget);
$label.hide();
},
/**
* On blur of the search input, show the label again if it is blank
*/
blurSearchHandler: function (e) {
var $input = $(e.currentTarget);
var $label = this.$searchForm.find("label");
if ($input.val() === "") {
$label.show();
}
}
};
// get started
var reviews = new Reviews(window.document.body);
});
}(jQuery, ngbs));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment