1
\$\begingroup\$

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
});
});
asked Dec 10, 2015 at 14:43
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
  1. How I am getting the data from the API with getJson and callbacks. I'm not sure if I am doing it efficiently.

I don't know what you mean with efficiency , the getJson is a built-in and you are using as the doc say.

The only improvemnt you can do is avoid 1 function, and do smething like:

 $.getJSON('/calendar/' + settings.dealID, callback);
  1. 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?

Yes, is ok.

  1. The structure of my jQuery plugin. As I mentioned its my first one and would like to hear how I could improve it.

Since you should have a dependency in your plug-in, you should add support for dependency resolver, or include a freeze version of them.

You have hardcode words in your template,you can't internationalizate your plugin.

Javascript is messy with the calculations of dates, check if you are using the correct calculus for next and previous months, days, numbers, etc. Considere use a 3party library to fix this (momentjs works nice).

Also, i don't want to disappoint you, but there are 300.000 billions of plugins for calendars dates and stuff. If you are doing something 4thelulz is ok. if not, try to use someone else.

answered Dec 10, 2015 at 21:25
\$\endgroup\$
1
  • \$\begingroup\$ Hey Vmariano, thanks for the answer. I made the change to the api calls and will look into a dependency resolver. I am aware of available plugins that can create calendars, but they did not suit the business requirements of this project. I agree with you about the dates. Itsthe part of the code I am most unhappy about. Its messy as you say. But the API formats are not the best so I had to do stuff like find the month number and add ordinals etc. I will take a look at momentjs today and see if it is something I can use. thanks! \$\endgroup\$ Commented Dec 11, 2015 at 8:47

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.