From 75c3f7d32200526a416ae8afde5d668f2d082e14 Mon Sep 17 00:00:00 2001 From: Kyle Stetz Date: Sat, 14 Sep 2013 11:40:01 -0400 Subject: [PATCH] added grunt workflow for minification. added jquery plugin manifest file --- .gitignore | 2 + Gruntfile.js | 39 +++++ LICENSE.md | 22 +++ clndr.jquery.json | 33 ++++ example/site.js | 19 ++- package.json | 11 ++ src/clndr.js | 381 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 502 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 Gruntfile.js create mode 100644 LICENSE.md create mode 100644 clndr.jquery.json create mode 100644 package.json create mode 100644 src/clndr.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90157b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +.DS_Store \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..65c9715 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,39 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + uglify: { + full_src: { + options: { + beautify: true, + mangle: false, + banner: '/*! ~ CLNDR v<%= pkg.version %> ~ \n' + + ' * ============================================== \n' + + ' * https://github.com/kylestetz/CLNDR \n' + + ' * ============================================== \n' + + ' * created by kyle stetz (github.com/kylestetz) \n' + + ' * &available under the MIT license \n' + + ' * http://opensource.org/licenses/mit-license.php \n' + + ' * ============================================== \n' + + ' */\n' + }, + files: { + './<%= pkg.name %>.js': 'src/<%= pkg.name %>.js' + } + }, + mini_src: { + options: { + banner: '/*! <%= pkg.name %>.min.js v<%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' + }, + files: { + './<%= pkg.name %>.min.js': 'src/<%= pkg.name %>.js' + } + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-uglify'); + + grunt.registerTask('default', ['uglify']); + +}; \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cd0f5f1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) +--------------------- + +**Copyright (c) 2013 Kyle Stetz & P'unk Avenue LLC** + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/clndr.jquery.json b/clndr.jquery.json new file mode 100644 index 0000000..4b407e6 --- /dev/null +++ b/clndr.jquery.json @@ -0,0 +1,33 @@ +{ + "name": "clndr", + "title": "CLNDR", + "description": "CLNDR is a calendar plugin that uses HTML templates, allowing you to write custom markup and styles that have access to useful calendar data.", + "keywords": ["calendar", "ui", "date", "punkave"], + "version": "1.0.0", + "author": { + "name": "Kyle Stetz", + "url": "http:/github.com/kylestetz" + }, + "maintainers": [ + { + "name": "Kyle Stetz", + "email": "kyle@punkave.com", + "url": "http:/punkave.com" + } + ], + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/kylestetz/CLNDR/LICENSE.md" + } + ], + "bugs": "https://github.com/kylestetz/CLNDR/issues", + "homepage": "https://kylestetz.github.io/CLNDR/", + "docs": "https://github.com/kylestetz/CLNDR", + "download": "https://github.com/kylestetz/CLNDR", + "dependencies": { + "jquery": ">=1.9", + "underscore": ">=1.4.4", + "moment": ">=2.0.0" + } +} \ No newline at end of file diff --git a/example/site.js b/example/site.js index 1981acc..4c215cd 100644 --- a/example/site.js +++ b/example/site.js @@ -1,10 +1,14 @@ $(document).ready( function() { + // here's some magic to make sure the dates are happening this month. + var thisMonth = moment().format('YYYY-MM'); + + // Here's our events array. We could grab this via AJAX as well. var eventArray = [ - { date: "2013-07-24 07:52", title: "175 down, 190 to go", url: "http://google.com", time: "7:15PM" }, - { date: "2013-08-28", title: "August 28th Part 1", url: "http://www.google.com" }, - { date: "2013-08-28", title: "August 28th Part 2", arbitraryObject: 42 }, - { date: "2013-09-16", title: "Somebody's Birthday." } + { date: thisMonth + "-24 07:52", title: "This is an event title", url: "http://google.com", time: "7:15PM" }, + { date: thisMonth + "-28", title: "the 28th, Part 1", url: "http://www.google.com" }, + { date: thisMonth + "-28", title: "the 28th, Part 2", arbitraryObject: 42 }, + { date: thisMonth + "-16", title: "Another title", anotherObject: "clndr exposes whatever is in your event object" } ]; var clndr1 = $('.cal1').clndr({ @@ -19,7 +23,12 @@ $(document).ready( function() { var clndr2 = $('.cal2').clndr({ template: $('#template-calendar').html(), events: eventArray, - startWithMonth: moment().add('month', 1) + startWithMonth: moment().add('month', 1), + clickEvents: { + click: function(target) { + console.log(target); + } + } }); // bind both clndrs to the left and right arrow keys diff --git a/package.json b/package.json new file mode 100644 index 0000000..2c70296 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "clndr", + "version": "1.0.0", + "description": "Development environment for clndr.js", + "author": "Kyle Stetz", + "license": "MIT", + "devDependencies": { + "grunt": "*", + "grunt-contrib-uglify": "~0.2.4" + } +} \ No newline at end of file diff --git a/src/clndr.js b/src/clndr.js new file mode 100644 index 0000000..e148b4e --- /dev/null +++ b/src/clndr.js @@ -0,0 +1,381 @@ +/* + * ~ CLNDR ~ + * ============================================== + * https://github.com/kylestetz/CLNDR + * ============================================== + * created by kyle stetz (github.com/kylestetz) + * &available under the MIT license + * http://opensource.org/licenses/mit-license.php + * ============================================== + * + * This is the fully-commented development version of CLNDR. + * For dev and production versions, check out clndr.js and clndr.min.js + * at https://github.com/kylestetz/CLNDR + * + * This work is based on the + * jQuery lightweight plugin boilerplate + * Original author: @ajpiano + * Further changes, comments: @addyosmani + * Licensed under the MIT license + */ + +;(function ( $, window, document, undefined ) { + + // This is the default calendar template. This can be overridden. + var clndrTemplate = "
" + + "
previous
<%= month %>
next
" + + "
" + + "" + + "" + + "" + + "<% _.each(daysOfTheWeek, function(dayOfTheWeek) { %>" + + "" + + "<% }); %>" + + "" + + "" + + "" + + "<% for(var i = 0; i < numberOfRows; i++){ %>" + + "" + + "<% for(var j = 0; j < 7; j++){ %>" + + "<% var d = j + i * 7; %>" + + "" + + "<% } %>" + + "" + + "<% } %>" + + "" + + "
<%= dayOfTheWeek %>
<%= days[d].day %>" + + "
"; + + var pluginName = 'clndr'; + + var defaults = { + template: clndrTemplate, + weekOffset: 0, + startWithMonth: null, + clickEvents: { + click: null, + nextMonth: null, + previousMonth: null, + onMonthChange: null + }, + targets: { + nextButton: 'clndr-next-button', + previousButton: 'clndr-previous-button', + day: 'day', + empty: 'empty' + }, + events: [], + extras: null, + dateParameter: 'date', + doneRendering: null, + render: null + }; + + // The actual plugin constructor + function Clndr( element, options ) { + this.element = element; + + // merge the default options with user-provided options + this.options = $.extend({}, defaults, options); + + // if there are events, we should run them through our addMomentObjectToEvents function + // which will add a date object that we can use to make life easier. This is only necessary + // when events are provided on instantiation, since our setEvents function uses addMomentObjectToEvents. + if(this.options.events.length) { + this.options.events = this.addMomentObjectToEvents(this.options.events); + } + + // this object will store a reference to the current month. + // it's a moment object, which allows us to poke at it a little if we need to. + // this will serve as the basis for switching between months & is the go-to + // internally if we want to know which month we're currently at. + if(this.options.startWithMonth) { + this.month = moment(this.options.startWithMonth); + } else { + this.month = moment(); + } + + this._defaults = defaults; + this._name = pluginName; + + // Some first-time initialization -> day of the week offset, + // template compiling, making and storing some elements we'll need later, + // & event handling for the controller. + this.init(); + } + + Clndr.prototype.init = function () { + // shuffle the week if there's an offset + if(this.options.weekOffset) { + this.daysOfTheWeek = this.shiftWeekdayLabels(this.options.weekOffset); + } else { + this.daysOfTheWeek = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; + } + + // we can compile this to save time rendering, since we know it'll never change. + if(!this.options.render) { + this.compiledClndrTemplate = _.template(this.options.template); + } + + // create the parent element that will hold the plugin & save it for later + $(this.element).html("
"); + this.calendarContainer = $('.clndr', this.element); + + // do a normal render of the calendar template + this.render(); + }; + + Clndr.prototype.shiftWeekdayLabels = function(offset) { + var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; + for(var i = 0; i < offset; i++) { + days.push( days.shift() ); + } + return days; + }; + + // This is where the magic happens. Given a moment object representing the current month, + // an array of calendarDay objects is constructed that contains appropriate events and + // classes depending on the circumstance. + Clndr.prototype.createDaysObject = function(currentMonth) { + // this array will hold numbers for the entire grid (even the blank spaces) + daysArray = []; + var date = currentMonth.startOf('month'); + var now = moment(); + + // if diff is greater than 0, we'll have to fill in some blank spaces + // to account for the empty boxes in the grid. + // we also need to take into account the weekOffset parameter + var diff = date.day() - this.options.weekOffset; + if(diff < 0) diff += 7; + for(var i = 0; i < diff; i++) { + daysArray.push( this.calendarDay() ); + } + + // filter the events list (if it exists) to events that are happening this month + this.eventsThisMonth = []; + if(this.options.events.length) { + this.eventsThisMonth = _.filter(this.options.events, function(event) { + return event._clndrDateObject.format("YYYY-MM") == currentMonth.format("YYYY-MM"); + }); + } + + // now we push all of the days in a month + var numOfDays = date.daysInMonth(); + for(var i = 1; i <= numOfDays; i++) { + + var eventsToday = []; + _.each(this.eventsThisMonth, function(event) { + // keep in mind that the events here already passed the month/year test. + // now all we have to compare is the moment.date(), which returns the day of the month. + if(event._clndrDateObject.date() == i) { + eventsToday.push(event); + } + }); + + var currentDay = moment([currentMonth.year(), currentMonth.month(), i]); + + var extraClasses = ""; + if(now.format("YYYY-MM-DD") == currentDay.format("YYYY-MM-DD")) { + extraClasses += " today"; + } + if(eventsToday.length) { + extraClasses += " event"; + } + + daysArray.push( + this.calendarDay({ + day: i, + classes: this.options.targets.day + extraClasses, + id: "calendar-day-" + currentDay.format("YYYY-MM-DD"), + events: eventsToday, + date: currentDay + }) + ); + } + + // ...and if there are any trailing blank boxes, fill those in + // with blank days as well + while(daysArray.length % 7 !== 0) { + daysArray.push( this.calendarDay() ); + } + + return daysArray; + }; + + Clndr.prototype.render = function() { + // get rid of the previous set of calendar parts. + // TODO: figure out if this is the right way to ensure proper garbage collection? + this.calendarContainer.children().remove(); + // get an array of days and blank spaces + var days = this.createDaysObject(this.month); + // this is to prevent a scope/naming issue between this.month and data.month + var currentMonth = this.month; + + var data = { + daysOfTheWeek: this.daysOfTheWeek, + numberOfRows: Math.ceil(days.length / 7), + days: days, + month: this.month.format('MMMM'), + year: this.month.year(), + eventsThisMonth: this.eventsThisMonth, + extras: this.options.extras + }; + + // render the calendar with the data above & bind events to its elements + if(!this.options.render) { + this.calendarContainer.html(this.compiledClndrTemplate(data)); + } else { + this.calendarContainer.html(this.options.render(data)); + } + this.bindEvents(); + if(this.options.doneRendering) { + this.options.doneRendering(); + } + }; + + Clndr.prototype.bindEvents = function() { + // target the day elements and give them click events + $("." + this.options.targets.day, this.element).on("click", { context: this }, function(event) { + if(event.data.context.options.clickEvents.click) { + var target = event.data.context.buildTargetObject(event.currentTarget, true); + event.data.context.options.clickEvents.click(target); + } + }); + // target the empty calendar boxes as well + $("." + this.options.targets.empty, this.element).on("click", { context: this }, function(event) { + if(event.data.context.options.clickEvents.click) { + var target = event.data.context.buildTargetObject(event.currentTarget, false); + event.data.context.options.clickEvents.click(target); + } + }); + + // bind the previous and next buttons + $("." + this.options.targets.previousButton, this.element).on("click", { context: this }, this.backAction); + $("." + this.options.targets.nextButton, this.element).on("click", { context: this }, this.forwardAction); + } + + // If the user provided a click callback we'd like to give them something nice to work with. + // buildTargetObject takes the DOM element that was clicked and returns an object with + // the DOM element, events, and the date (if the latter two exist). Currently it is based on the id, + // however it'd be nice to use a data- attribute in the future. + Clndr.prototype.buildTargetObject = function(currentTarget, targetWasDay) { + // This is our default target object, assuming we hit an empty day with no events. + var target = { + element: currentTarget, + events: null, + date: null + }; + // did we click on a day or just an empty box? + if(targetWasDay) { + // extract the date from the id of the DOM element + var dateString = currentTarget.id.replace('calendar-day-', ''); + target.date = moment(dateString); + // do we have events? + if(this.options.events) { + // are any of the events happening today? + target.events = _.filter(this.options.events, function(event) { + // filter the dates down to the ones that match. + return event._clndrDateObject.format('YYYY-MM-DD') == dateString; + }); + } + } + + return target; + } + + Clndr.prototype.forwardAction = function(event) { + event.data.context.month.add('months', 1); + if(event.data.context.options.clickEvents.nextMonth) { + event.data.context.options.clickEvents.nextMonth(event.data.context.month); + } + if(event.data.context.options.clickEvents.onMonthChange) { + event.data.context.options.clickEvents.onMonthChange(event.data.context.month); + } + event.data.context.render(); + }; + + Clndr.prototype.backAction = function(event) { + event.data.context.month.subtract('months', 1); + if(event.data.context.options.clickEvents.previousMonth) { + event.data.context.options.clickEvents.previousMonth(event.data.context.month); + } + if(event.data.context.options.clickEvents.onMonthChange) { + event.data.context.options.clickEvents.onMonthChange(event.data.context.month); + } + event.data.context.render(); + }; + + Clndr.prototype.forward = function() { + this.month.add('months', 1); + this.render(); + } + + Clndr.prototype.back = function() { + this.month.subtract('months', 1); + this.render(); + } + + // alternate names for convenience + Clndr.prototype.next = function() { + this.forward(); + } + + Clndr.prototype.previous = function() { + this.back(); + } + + Clndr.prototype.setMonth = function(newMonth) { + // accepts 0 - 11 or a full/partial month name e.g. "Jan", "February", "Mar" + this.month.month(newMonth); + this.render(); + } + + Clndr.prototype.setYear = function(newYear) { + this.month.year(newYear); + this.render(); + } + + Clndr.prototype.nextYear = function() { + this.month.add('year', 1); + this.render(); + } + + Clndr.prototype.previousYear = function() { + this.month.subtract('year', 1); + this.render(); + } + + Clndr.prototype.setYear = function(newYear) { + this.month.year(newYear); + this.render(); + } + + Clndr.prototype.setEvents = function(events) { + // go through each event and add a moment object + this.options.events = this.addMomentObjectToEvents(events); + + calendar.render(); + }; + + Clndr.prototype.addMomentObjectToEvents = function(events) { + var self = this; + _.each(events, function(event) { + event._clndrDateObject = moment(event[ self.options.dateParameter ]); + }); + return events; + } + + Clndr.prototype.calendarDay = function(options) { + var defaults = { day: "", classes: this.options.targets.empty, events: [], id: "", date: null }; + return $.extend({}, defaults, options); + } + + $.fn.clndr = function(options) { + if( !$.data( this, 'plugin_clndr') ) { + var clndr_instance = new Clndr(this, options); + $.data(this, 'plugin_clndr', clndr_instance); + return clndr_instance; + } + } + +})( jQuery, window, document ); \ No newline at end of file