67f84c41b56f2f2dfceb8021f31e15edc8f1a0cd.svn-base 13.8 KB
(function ($) {

	/**
	 * --------------------------------------------------------------------
	 * jQuery-Plugin "daterangepicker.jQuery.js"
	 * by Scott Jehl, scott@filamentgroup.com
	 * reference article: http://www.filamentgroup.com/lab/update_date_range_picker_with_jquery_ui/
	 * demo page: http://www.filamentgroup.com/examples/daterangepicker/
	 *
	 * Copyright (c) 2010 Filament Group, Inc
	 * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
	 *
	 * Dependencies: jquery, jquery UI datepicker, date.js, jQuery UI CSS Framework

	 *  12.15.2010 Made some fixes to resolve breaking changes introduced by jQuery UI 1.8.7
	 * --------------------------------------------------------------------
	 */
	$.fn.daterangepicker = function(settings){
		var rangeInput = $(this);

		//defaults
		var options = $.extend({
			presetRanges: [
				{text: 'Today', dateStart: 'today', dateEnd: 'today' },
				{text: 'Last 7 days', dateStart: 'today-7days', dateEnd: 'today' },
				{text: 'Month to date', dateStart: function(){ return Date.parse('today').moveToFirstDayOfMonth();  }, dateEnd: 'today' },
				{text: 'Year to date', dateStart: function(){ var x= Date.parse('today'); x.setMonth(0); x.setDate(1); return x; }, dateEnd: 'today' },
				//extras:
				{text: 'The previous Month', dateStart: function(){ return Date.parse('1 month ago').moveToFirstDayOfMonth();  }, dateEnd: function(){ return Date.parse('1 month ago').moveToLastDayOfMonth();  } }
				//{text: 'Tomorrow', dateStart: 'Tomorrow', dateEnd: 'Tomorrow' },
				//{text: 'Ad Campaign', dateStart: '03/07/08', dateEnd: 'Today' },
				//{text: 'Last 30 Days', dateStart: 'Today-30', dateEnd: 'Today' },
				//{text: 'Next 30 Days', dateStart: 'Today', dateEnd: 'Today+30' },
				//{text: 'Our Ad Campaign', dateStart: '03/07/08', dateEnd: '07/08/08' }
			],
			//presetRanges: array of objects for each menu preset.
			//Each obj must have text, dateStart, dateEnd. dateStart, dateEnd accept date.js string or a function which returns a date object
			presets: {
				specificDate: 'Specific Date',
				allDatesBefore: 'All Dates Before',
				allDatesAfter: 'All Dates After',
				dateRange: 'Date Range'
		},
		rangeStartTitle: 'Start date',
		rangeEndTitle: 'End date',
		nextLinkText: 'Next',
		prevLinkText: 'Prev',
		target: rangeInput,
		doneButtonText: 'Done',
		earliestDate: Date.parse('-15years'), //earliest date allowed 
		latestDate: Date.parse('+15years'), //latest date allowed 
		constrainDates: false,
		rangeSplitter: '-', //string to use between dates in single input
		dateFormat: 'm/d/yy', // date formatting. Available formats: http://docs.jquery.com/UI/Datepicker/%24.datepicker.formatDate
		closeOnSelect: true, //if a complete selection is made, close the menu
		arrows: false,
		appendTo: 'body',
		onClose: function(){},
		onOpen: function(){},
		onChange: function(){},
		datepickerOptions: null //object containing native UI datepicker API options
	}, settings);
	
	

	//custom datepicker options, extended by options
	var datepickerOptions = {
		onSelect: function(dateText, inst) {
			var range_start = rp.find('.range-start');
			var range_end = rp.find('.range-end');
				
			if(rp.find('.ui-daterangepicker-specificDate').is('.ui-state-active')){
				range_end.datepicker('setDate', range_start.datepicker('getDate') ); 
			}
			
			$(this).trigger('constrainOtherPicker');
			
			var rangeA = fDate( range_start.datepicker('getDate') );
			var rangeB = fDate( range_end.datepicker('getDate') );
			
			//send back to input or inputs
			if(rangeInput.length == 2){
				rangeInput.eq(0).val(rangeA);
				rangeInput.eq(1).val(rangeB);
			}
			else{
				rangeInput.val((rangeA != rangeB) ? rangeA+' '+ options.rangeSplitter +' '+rangeB : rangeA);
			}
			//if closeOnSelect is true
			if(options.closeOnSelect){
				if(!rp.find('li.ui-state-active').is('.ui-daterangepicker-dateRange') && !rp.is(':animated') ){
					hideRP();
				}

				$(this).trigger('constrainOtherPicker');

				options.onChange();
			}
		},
		defaultDate: +0
	};

		//change event fires both when a calendar is updated or a change event on the input is triggered
		rangeInput.bind('change', options.onChange);

		//datepicker options from options
		options.datepickerOptions = (settings) ? $.extend(datepickerOptions, settings.datepickerOptions) : datepickerOptions;

		//Capture Dates from input(s)
		var inputDateA, inputDateB = Date.parse('today');
		var inputDateAtemp, inputDateBtemp;
		if(rangeInput.size() == 2){
			inputDateAtemp = Date.parse( rangeInput.eq(0).val() );
			inputDateBtemp = Date.parse( rangeInput.eq(1).val() );
			if(inputDateAtemp == null){inputDateAtemp = inputDateBtemp;}
			if(inputDateBtemp == null){inputDateBtemp = inputDateAtemp;}
		}
		else {
			inputDateAtemp = Date.parse( rangeInput.val().split(options.rangeSplitter)[0] );
			inputDateBtemp = Date.parse( rangeInput.val().split(options.rangeSplitter)[1] );
			if(inputDateBtemp == null){inputDateBtemp = inputDateAtemp;} //if one date, set both
		}
		if(inputDateAtemp != null){inputDateA = inputDateAtemp;}
		if(inputDateBtemp != null){inputDateB = inputDateBtemp;}


		//build picker and
		var rp = $('<div class="ui-daterangepicker ui-widget ui-helper-clearfix ui-widget-content ui-corner-all"></div>');
		var rpPresets = (function(){
			var ul = $('<ul class="ui-widget-content"></ul>').appendTo(rp);
			$.each(options.presetRanges,function(){
				$('<li class="ui-daterangepicker-'+ this.text.replace(/ /g, '') +' ui-corner-all"><a href="#">'+ this.text +'</a></li>')
				.data('dateStart', this.dateStart)
				.data('dateEnd', this.dateEnd)
				.appendTo(ul);
			});
			var x=0;
			$.each(options.presets, function(key, value) {
				$('<li class="ui-daterangepicker-'+ key +' preset_'+ x +' ui-helper-clearfix ui-corner-all"><span class="ui-icon ui-icon-triangle-1-e"></span><a href="#">'+ value +'</a></li>')
				.appendTo(ul);
				x++;
			});

			ul.find('li').hover(
					function(){
						$(this).addClass('ui-state-hover');
					},
					function(){
						$(this).removeClass('ui-state-hover');
					})
				.click(function(){
					rp.find('.ui-state-active').removeClass('ui-state-active');
					$(this).addClass('ui-state-active');
					clickActions($(this),rp, rpPickers, doneBtn);
					return false;
				});
			return ul;
		})();

		//function to format a date string
		function fDate(date){
			 if(!date.getDate()){return '';}
			 var day = date.getDate();
			 var month = date.getMonth();
			 var year = date.getFullYear();
			 month++; // adjust javascript month
			 var dateFormat = options.dateFormat;
			 return $.datepicker.formatDate( dateFormat, date );
		}


		$.fn.restoreDateFromData = function(){
			if($(this).data('saveDate')){
				$(this).datepicker('setDate', $(this).data('saveDate')).removeData('saveDate');
			}
			return this;
		};
		$.fn.saveDateToData = function(){
			if(!$(this).data('saveDate')){
				$(this).data('saveDate', $(this).datepicker('getDate') );
			}
			return this;
		};

		//show, hide, or toggle rangepicker
		function showRP(){
			if(rp.data('state') == 'closed'){
				positionRP();
				rp.fadeIn(300).data('state', 'open');
				options.onOpen();
			}
		}
		function hideRP(){
			if(rp.data('state') == 'open'){
				rp.fadeOut(300).data('state', 'closed');
				options.onClose();
			}
		}
		function toggleRP(){
			if( rp.data('state') == 'open' ){ hideRP(); }
			else { showRP(); }
		}
		function positionRP(){
			var relEl = riContain || rangeInput; //if arrows, use parent for offsets
			var riOffset = relEl.offset(),
				side = 'left',
				val = riOffset.left,
				offRight = $(window).width() - val - relEl.outerWidth();

			if(val > offRight){
				side = 'right', val =  offRight;
			}

			rp.parent().css(side, val).css('top', riOffset.top + relEl.outerHeight());
		}



		//preset menu click events
		function clickActions(el, rp, rpPickers, doneBtn){

			if(el.is('.ui-daterangepicker-specificDate')){
				//Specific Date (show the "start" calendar)
				doneBtn.hide();
				rpPickers.show();
				rp.find('.title-start').text( options.presets.specificDate );
				rp.find('.range-start').restoreDateFromData().css('opacity',1).show(400);
				rp.find('.range-end').restoreDateFromData().css('opacity',0).hide(400);
				setTimeout(function(){doneBtn.fadeIn();}, 400);
			}
			else if(el.is('.ui-daterangepicker-allDatesBefore')){
				//All dates before specific date (show the "end" calendar and set the "start" calendar to the earliest date)
				doneBtn.hide();
				rpPickers.show();
				rp.find('.title-end').text( options.presets.allDatesBefore );
				rp.find('.range-start').saveDateToData().datepicker('setDate', options.earliestDate).css('opacity',0).hide(400);
				rp.find('.range-end').restoreDateFromData().css('opacity',1).show(400);
				setTimeout(function(){doneBtn.fadeIn();}, 400);
			}
			else if(el.is('.ui-daterangepicker-allDatesAfter')){
				//All dates after specific date (show the "start" calendar and set the "end" calendar to the latest date)
				doneBtn.hide();
				rpPickers.show();
				rp.find('.title-start').text( options.presets.allDatesAfter );
				rp.find('.range-start').restoreDateFromData().css('opacity',1).show(400);
				rp.find('.range-end').saveDateToData().datepicker('setDate', options.latestDate).css('opacity',0).hide(400);
				setTimeout(function(){doneBtn.fadeIn();}, 400);
			}
			else if(el.is('.ui-daterangepicker-dateRange')){
				//Specific Date range (show both calendars)
				doneBtn.hide();
				rpPickers.show();
				rp.find('.title-start').text(options.rangeStartTitle);
				rp.find('.title-end').text(options.rangeEndTitle);
				rp.find('.range-start').restoreDateFromData().css('opacity',1).show(400);
				rp.find('.range-end').restoreDateFromData().css('opacity',1).show(400);
				setTimeout(function(){doneBtn.fadeIn();}, 400);
			}
			else {
				//custom date range specified in the options (no calendars shown)
				doneBtn.hide();
				rp.find('.range-start, .range-end').css('opacity',0).hide(400, function(){
					rpPickers.hide();
				});
				var dateStart = (typeof el.data('dateStart') == 'string') ? Date.parse(el.data('dateStart')) : el.data('dateStart')();
				var dateEnd = (typeof el.data('dateEnd') == 'string') ? Date.parse(el.data('dateEnd')) : el.data('dateEnd')();
				rp.find('.range-start').datepicker('setDate', dateStart).find('.ui-datepicker-current-day').trigger('click');
				rp.find('.range-end').datepicker('setDate', dateEnd).find('.ui-datepicker-current-day').trigger('click');
			}

			return false;
		}


		//picker divs
		var rpPickers = $('<div class="ranges ui-widget-header ui-corner-all ui-helper-clearfix"><div class="range-start"><span class="title-start">Start Date</span></div><div class="range-end"><span class="title-end">End Date</span></div></div>').appendTo(rp);
		rpPickers.find('.range-start, .range-end')
			.datepicker(options.datepickerOptions);


		rpPickers.find('.range-start').datepicker('setDate', inputDateA);
		rpPickers.find('.range-end').datepicker('setDate', inputDateB);

		rpPickers.find('.range-start, .range-end')
			.bind('constrainOtherPicker', function(){
				if(options.constrainDates){
					//constrain dates
					if($(this).is('.range-start')){
						rp.find('.range-end').datepicker( "option", "minDate", $(this).datepicker('getDate'));
					}
					else{
						rp.find('.range-start').datepicker( "option", "maxDate", $(this).datepicker('getDate'));
					}
				}
			})
			.trigger('constrainOtherPicker');

		var doneBtn = $('<button class="btnDone ui-state-default ui-corner-all">'+ options.doneButtonText +'</button>')
		.click(function(){
			rp.find('.ui-datepicker-current-day').trigger('click');
			hideRP();
		})
		.hover(
				function(){
					$(this).addClass('ui-state-hover');
				},
				function(){
					$(this).removeClass('ui-state-hover');
				}
		)
		.appendTo(rpPickers);




		//inputs toggle rangepicker visibility
		$(this).click(function(){
			toggleRP();
			return false;
		});
		//hide em all
		rpPickers.hide().find('.range-start, .range-end, .btnDone').hide();

		rp.data('state', 'closed');

		//Fixed for jQuery UI 1.8.7 - Calendars are hidden otherwise!
		rpPickers.find('.ui-datepicker').css("display","block");

		//inject rp
		$(options.appendTo).append(rp);

		//wrap and position
		rp.wrap('<div class="ui-daterangepickercontain"></div>');

		//add arrows (only available on one input)
		if(options.arrows && rangeInput.size()==1){
			var prevLink = $('<a href="#" class="ui-daterangepicker-prev ui-corner-all" title="'+ options.prevLinkText +'"><span class="ui-icon ui-icon-circle-triangle-w">'+ options.prevLinkText +'</span></a>');
			var nextLink = $('<a href="#" class="ui-daterangepicker-next ui-corner-all" title="'+ options.nextLinkText +'"><span class="ui-icon ui-icon-circle-triangle-e">'+ options.nextLinkText +'</span></a>');

			$(this)
			.addClass('ui-rangepicker-input ui-widget-content')
			.wrap('<div class="ui-daterangepicker-arrows ui-widget ui-widget-header ui-helper-clearfix ui-corner-all"></div>')
			.before( prevLink )
			.before( nextLink )
			.parent().find('a').click(function(){
				var dateA = rpPickers.find('.range-start').datepicker('getDate');
				var dateB = rpPickers.find('.range-end').datepicker('getDate');
				var diff = Math.abs( new TimeSpan(dateA - dateB).getTotalMilliseconds() ) + 86400000; //difference plus one day
				if($(this).is('.ui-daterangepicker-prev')){ diff = -diff; }

				rpPickers.find('.range-start, .range-end ').each(function(){
						var thisDate = $(this).datepicker( "getDate");
						if(thisDate == null){return false;}
						$(this).datepicker( "setDate", thisDate.add({milliseconds: diff}) ).find('.ui-datepicker-current-day').trigger('click');
				});
				return false;
			})
			.hover(
				function(){
					$(this).addClass('ui-state-hover');
				},
				function(){
					$(this).removeClass('ui-state-hover');
				});

			var riContain = rangeInput.parent();
		}


		$(document).click(function(){
			if (rp.is(':visible')) {
				hideRP();
			}
		});

		rp.click(function(){return false;}).hide();
		return this;
	}

})(jQuery);