I put together a jQuery plugin that displays a month of dates using underscore templates. When the template has been populated and displayed in the UI, a user can click on a date which sends a request to the api(with the selected date parameter) to give a list of available deals on that date. I then display these deals in another underscore template.
So far, it all seems to be working well. But as this is my first jQuery, I have a couple of concerns and would appreciate any feedback or advise on how to improve it...
1. How I am getting the data from the API with getJson and callbacks. I'm not sure if I am doing it efficiently.
2. I am using the jQuery .delegate to attach events to future elements in the templates. Perhaps there is a better way of doing this or is this ok?
3. The structure of my jQuery plugin. As I mentioned its my first one and would like to hear how I could improve it.
(function( $ ){
$.fn.Calendar = function(options) {
var settings = $.extend({
dealID : null,
calendarTitle: 'Choose arrival date'
}, options);
var el = this;
$(el).addClass('deal-calendar');
var Templates = {};
// templates for the UI using underscore templates
Templates.calendarDay = [
'<h2>Choose arrival date</h2>',
'<a class="clear-selection">Clear selection</a>',
'<div class="deal-calendar-header">',
'<a id="prev-month" class="change-month left" data-change-month="<%= allDaysData.getYearMonth(allDaysData.data.currentYear, allDaysData.data.currentMonth, false)%>">< <%= allDaysData.data.previousMonth %></a>',
'<span class="month"><%= allDaysData.data.currentMonth %></span><span class="year"><%= allDaysData.data.currentYear %></span>',
'<a id="next-month" class="change-month right" data-change-month="<%= allDaysData.getYearMonth(allDaysData.data.currentYear, allDaysData.data.currentMonth, true)%>"><%= allDaysData.data.nextMonth %> ></a>',
'</div>',
'<table class="deal-calendar-dates">',
'<thead>',
'<tr>',
'<th>Monday</th><th>Tuesday</th><th>Wednesday</th><th>Thursday</th><th>Friday</th><th>Saturday</th><th>Sunday</th>',
'</tr>',
'</thead>',
'<tr>',
'<% _.each(allDaysData.data.calendarDateView, function(item, index){ %>',
'<td data-arrival-Date="<%= item.dateString %>" class="day <%= (item.dateUnavailable == true) ? "unavailable": "available" %>">',
'<span class="date"><%= allDaysData.ordinalSuffix(item.dateString) %></span>',
'<span class="from"><%= (item.dateUnavailable == true) ? "Not available": "From" %></span>',
'<span class="price"><%= (item.dateUnavailable == true) ? "for arrival ": "£" + item.minPrice %></span>',
'</td>',
'<% if ((index + 1) % 7 == 0) { %>',
'</tr>',
'<tr>',
'<% }}) %>',
'</tr>',
'</table>',
'<div id="js-day-products"></div>'
].join("\n");
Templates.dealProducts = [
'<h2>Choose stay length</h2>',
'<% _.each(productsData.data.calendarProductView,function(item){ %>',
'<div class="product-container">',
'<h2><%= item.friendlyName %></h2>',
'<div class="deal-details">',
'<div class="stay">',
'<div><span class="label">Check in: </span><%= item.checkInDate %></div>',
'<div><span class="label">Check out: </span><%= item.checkOutDate %></div>',
'</div>',
'<div class="choose-quantity">',
'<a class="choose-product">Choose</a>',
'<% if (item.showQuantity) { %>',
'<div class="quantity-container">',
'<label for="<%= item.dealProductId %>-product-quantity">Persons:</label>',
'<a class="decrement quantity">-</a>',
'<input id="<%= item.dealProductId %>-product-quantity" name="quantity" class="person-quantity" type="text" max="<%= item.userPurchaseCap %>" value="1">',
'<a class="increment quantity">+</a>',
'</div>',
'<% } %>',
'</div>',
'<div class="savings">',
'<div class="was">',
'<span class="label">Was:</span>',
'<span>£<%= item.originalPrice %></span>',
'</div>',
'<div class="now">',
'<span class="label">Now:</span>',
'<span class="price">£<%= item.price %></span>',
'</div>',
'<div class="save">',
'<span class="label">Save:</span>',
'<span><%= item.discountPercentage %>%</span>',
'</div>',
'</div>',
'</div>',
'<div class="purchase" id="<%= item.dealProductId %>-purchase">',
'<a name="checkout" class="checkout" data-dealProductId="<%= item.dealProductId %>" data-checkInDate="<%= item.checkInDate %>">Checkout</a>',
'</div>',
'</div>',
' <% }) %>'
].join("\n");
//helpers that are called from the templates
var viewHelpers = {
getYearMonth: getYearMonth,
ordinalSuffix: ordinalSuffix,
orderURL: orderURL
};
//return the number of the month
function getMonthNumber(monthName){
var monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
return monthNames.indexOf(monthName) + 1
}
// build the url for the API call (yyyy/mm)
function getYearMonth(year, month, isNextMonth){
var currentMonthNumber = getMonthNumber(month)
var nextMonth = currentMonthNumber + 1;
// if current month is december, increment the year and set the month to 1 (january)
if(currentMonthNumber == 12 && isNextMonth == true){
nextMonth = 1;
year = year + 1;
}
// if its previous month
if(!isNextMonth){
nextMonth = currentMonthNumber - 1;
}
// if current month is january, decrement the year and set month to 12 (december)
if(currentMonthNumber == 1 && isNextMonth == false){
nextMonth = 12;
year = year - 1;
}
return nextMonth + "-" + year;
}
// add the ordinals for the UI
function ordinalSuffix(dateString) {
var parts = dateString.split('-');
i = parts[0];
var j = i % 10,
k = i % 100;
if (j == 1 && k != 11) {
return i + "st";
}
if (j == 2 && k != 12) {
return i + "nd";
}
if (j == 3 && k != 13) {
return i + "rd";
}
return i + "th";
}
// build the order url
function orderURL(dealProductId, checkInDate, quantity){
checkInDate = checkInDate.replace(/-|\//g, "");// remove the slashes form the date
return( '/' + dealProductId + '?qty=' + quantity + '&checkin=' + checkInDate);
}
// Set previous/next month functionality
function setMonth(month){
getMonthData(month, function (data) {
_.extend(data, viewHelpers);
$(el)
.empty()
.append(_.template(Templates.calendarDay)({allDaysData: data}));
});
};
// Get the products for the selected date
function getProducts(arrivalDate){
getDayProductsData(arrivalDate, function (data) {
$('#js-day-products')
.empty()
.append(_.template(Templates.dealProducts)({productsData: data}))
.delegate(".reserve-checkout", "click", function() {
var dealProductId = $(this).attr("data-dealProductId");
var checkInDate = $(this).attr("data-checkInDate");
var productQuantityID = $(this).attr("data-dealProductId") + "-product-quantity";
var quantity = $("#" + productQuantityID).val();
var ourl = viewHelpers.orderURL(dealProductId, checkInDate, quantity);
//do stuff with the ourl
})
.delegate(".choose-product", "click", function() {
$('.product-container').removeClass('selected');
$(this).parents('.product-container').addClass('selected');
})
.delegate(".increment", "click", function() {
var quantity = $(this).siblings('.person-quantity');
if($(quantity).val() != $(quantity).attr('max')){
$(quantity).val(parseInt($(quantity).val())+1);
}
})
.delegate(".decrement", "click", function() {
var quantity = $(this).siblings('.person-quantity');
if($(quantity).val() != 1){
$(quantity).val(parseInt($(quantity).val())-1);
}
});
});
}
//api calls
var getAllDaysData = function(callback) {
$.getJSON('/calendar/' + settings.dealID, function (data) {
callback(data);
});
};
var getDayProductsData = function(productsDate, callback) {
$.getJSON('/calendar/'+ settings.dealID + '/products/' + productsDate, function (data) {
callback(data);
});
};
var getMonthData = function(monthDay, callback) {
$.getJSON('/calendar/' + settings.dealID +' /' + monthDay, function (data) {
callback(data);
});
};
return this.each( function() {
// Get the calendar days on initial load
getAllDaysData(function (data) {
_.extend(data, viewHelpers);
$(el)
.append(_.template(Templates.calendarDay)({allDaysData: data}))
.delegate(".change-month", "click", function() {
setMonth($(this).attr("data-change-month"));
})
.delegate("td.available", "click", function() {
getProducts($(this).attr("data-arrival-Date"));
$('td.available').removeClass('selected');
$(this).addClass('selected');
})
.delegate(".clear-selection", "click", function() {
$('td.available').removeClass('selected');
$('#js-day-products').empty();
});
})
});
};
})(jQuery);
$(document).ready(function() {
$( "#js-calendar" ).Calendar({
dealID : 157460
});
});