INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!
  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Jobs

non jquery date picker with limitations

non jquery date picker with limitations

(OP)
Hi all - so I'm being asked to make a date picker that will somehow stop more than 3 people picking the same day before that day becomes unpickable for anyone else. I assume I can at least wait until a submit button is pressed in order for someone to count as one of the 3. So I will have to find a way to record the date to a flatfile or db and then check it (probably more ajax) and apply any future limitations to the datepicker when it's opened.
Ignoring those complications, I've just been trying to find a basic datepicker that works nicely and can be default disable 2 days a week (sun/mon) before I get into the (only 3 per day) limitations.
I've looked at this one
http://www.mattkruse.com/javascript/calendarpopup/
but it's started to open my div one about 200px lower than the div it's supposed to appear by for no reason.
So I've started looking for others but have just ended up with lots of options and many don't have the limitation power I need or the pages are dead or they use jquery or they're not free :/
http://www.bitrepository.com/a-collection-of-free-...
http://www.hongkiat.com/blog/useful-calendar-date-...
Any thoughts anyone?

_________________________________
Leozack

CODE

MakeUniverse($infinity,1,42); 

RE: non jquery date picker with limitations

Quote:


to the datepicker when it's opened
not just when it is opened, but I suspect you will have to check every few seconds to make the user experience ok. and of course when a date is actually selected (before the submit button is pressed).

what is the business requirement to avoid jQuery? or rather why does such a requirement exist? Without some kind of framework you risk a greater development and testing cycle to ensure cross-browser compatibility. Given cacheing and browser speed, most people find that is not a sensible trade off these days.

If the concern is over clashing with another library/framework then this is easily avoided with compatibility mode.

In terms of springboarding your design - an idea might be to take the jquery date picker, render a page and then capture the source. that would give you a starting block with a known good css + html structure. you could then add on the javascript functionality depending on what you actually need. That would be pretty straight forward. Do make sure you include appropriate copyright acknowledgments.

Ignoring the complexities of cross-browser compatibility, it's relatively simple to craft a calendar object that renders nicely. You need to take an early design choice as to whether you should use a table or not, for the calendar. As a calendar is at least quasi-tabular probably the purists would not be too put out by using a calendar. But it is perfectly feasible to do so. check this out for an example.

If you don't add too many runtime options in, you could probably get a fully functional version sorted in an hour or so. post back if you want me to post a proof of concept for you to build on.

Tips:

1. If you use rem for the outer div measurement and em for all measurements inside the outer layer, you end up with a very flexible design that will scale with the font-size of the page. Assuming you are using a strict doctype, anyway.

2. encode the year, month and day of each calendar entry in the data attributes of the box. that way you can get at them more easily than parsing a complex id.

3. consider using moment.js as the base for your date manipulation and output. It has neat functions that will make iterating and plotting the dates easier.


RE: non jquery date picker with limitations

i had a spare hour so knocked up something that the OP/others may find useful.

the server is a very trivial counter written in php

CODE --> server

<?php
$pdo = new pdo('sqlite:myDatabase.sqlite');
class availability{
	
	public function getAvailability($year, $month, $day){
		global $pdo;
		$s = $pdo->prepare('select ifnull(clicks,0) as c from clickTable where d=?');
		if($s === false):
			print_r($pdo->errorInfo());
		endif;
		$result = $s->execute(array("$year-$month-$day"));
		if($result === false):
			print_r($s->errorInfo());
		endif;
		$obj = $s->fetchObject();
		return $obj ? intval($obj->c) : 0;
	}
	function getMonthAvailability($year,$month){
		$data = array();
		$date = new datetime();
		$date->setDate((int) $year, (int) $month, 1);
		$month = $date->format('m');
		while($month == $date->format('m')):
			$data[$date->format('Y')][$date->format('n') - 1][$date->format('j')]
								= $this->getAvailability(
													$date->format('Y'),
													$date->format('m'),
													$date->format('d'));
			$date->modify('+1 day');
		endwhile;
		return $data;
	}
	public function addClick($year,$month,$day){
		global $pdo;
		$c = $this->getAvailability(	$year,
										str_pad($month,2,"0", STR_PAD_LEFT),
										str_pad($day,2,"0",STR_PAD_LEFT)
										);
		if($c >= 3):
			return false;
		endif;
		$s = $pdo->prepare(<<<SQL
INSERT OR REPLACE
INTO clickTable ( clicks, d)
VALUES(?, date(?))
SQL
);
		$s->execute(array($c+1,	"$year-" .
								str_pad($month,2,"0", STR_PAD_LEFT) . '-' .
								str_pad($day,2,"0",STR_PAD_LEFT)
						)
				);
		return true;
	}
}
date_default_timezone_set('UTC');
$return = array();
$a = new availability();
switch($_REQUEST['action']):
case 'getAvailability':
	$data = $a->getMonthAvailability($_POST['year'], $_POST['month'] + 1);
	$return = array('result'=>'ok', 'data'=>$data);
break;
case 'addClick':
	$result = $a->addClick($_POST['year'],$_POST['month'] + 1, $_POST['day']);
	if($result):
		$data = $a->getMonthAvailability($_POST['year'], $_POST['month'] + 1);
		$return = array('result'=>'ok', 'data'=>$data);
	else:
		$return = array('result'=>'error', 'data'=>$data);
	endif;
break;
endswitch;
echo json_encode($return);
die;
?> 

the ui relies only on moment.js which is loaded via a cdn. you can see that the mark up and styling are very minimal so the css can be very easily tweaked to make it look how you like. To change the size of the calendar just change the font-size of the container.

you can, of course, have multiple calendars but then you will want to tweak the ajax methods so that the ui identifies the calendar being clicked to the server. each calendar needs to be instantiated separately.

nb not all browsers are compliant with calc() in css. so you might want to move that style declaration to the js plugin instead if you are worried about old browsers.

i've not included a 'key' but hopefully obvious that reddish squares are not available and greenish squares are ok. the script updates every 30 seconds - which can be parameterised through the options object.

CODE

<!DOCTYPE HTML>
	<meta charset="utf-8" />
	<head>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment.min.js"></script>
		<script>
Element.prototype.datepicker = function( option ){
	
	var _this;
	var options = {
		ajaxServer: 'debug.php',
		refreshInterval: 30000,
		maskDays:[6,7],
		alreadyTakenMessage: 'Sorry, that day is no longer available',
		successMessage: 'Thank you, your click has been logged',
		startMonth: -24,
		endMonth: 24
	}
	var timer = {
		start: function(){
			timer.timer = window.setInterval(events.tick, im.getOption('refreshInterval'));
		},
		stop: function(){
			window.clearInterval(timer.timer);
		},
		timer: ''
	}
	
	var ajaxRequest; 
	
	var ajax = {
		r: '',
		post: function(data, callback, async){
			if(typeof async == 'undefined'){
				async = true;
			}
			ajax.connect();
			ajax.r.onreadystatechange = function(){
				if(ajax.r.readyState == 4){
					callback(JSON.parse(ajax.r.responseText));
				}
			}
			ajax.r.open('POST', im.getOption('ajaxServer'), async);
			ajax.r.setRequestHeader("Content-type","application/x-www-form-urlencoded");
			ajax.r.send(im.serialize(data));
		},
		get: function(data, callback, async){
			if(typeof async == 'undefined'){
				async = true;
			}
			ajax.connect();
			ajax.r.onreadystatechange = function(){
				if(ajax.r.readyState == 4){
					callback(JSON.parse(ajax.r.responseText));
				}
			}
			ajax.r.open('GET', im.getOption('ajaxServer') + '?' + im.serialize(data), async);		
			ajax.r.send();
		},
		connect: function(){
			try{
				ajax.r = new XMLHttpRequest();
			} catch (e){
				try{
					ajax.r = new ActiveXObject("Msxml2.XMLHTTP");
				} catch (e) {
					try{
						ajax.r = new ActiveXObject("Microsoft.XMLHTTP");
					} catch (e){
						// Something went wrong
						alert("Your browser broke!");
						return false;
					}
				}
			}
		}
	}
	var events = {
		dayClick: function(e){
			var that = e.target;
			if(!im.isAvailable(that)){
				return;
			}
			im.addClick(that.dataset.year, that.dataset.month, that.dataset.day, that);
		},
		mouseover: function(e){
			var that = e.target;
			that.className = that.className + " hover";
			that.addEventListener('mouseout', events.mouseleave);
		},
		mouseleave: function(e){
			var that = e.target;
			that.classList.remove('hover');
			that.removeEventListener('mouseout', events.mouseover);
		},
		buttonClick: function(e){
			var that = e.target;
			that.preventDefault();
		},
		tick: function(){
			im.refreshAvailability();
		},
		prevClick: function(e){
			timer.stop();
			e.preventDefault();
			var cM = im.getCurrentMonth();
			var m = new moment({year:cM.year, month:cM.month, day:1});
			m.add(-1,'months');
			methods.load( m.get('year'), m.get('month'));
		},
		nextClick: function(e){
			timer.stop();
			e.preventDefault();
			var cM = im.getCurrentMonth();
			var m = new moment({year:cM.year, month:cM.month, day:1});
			m.add(1,'months');
			methods.load(m.get('year'), m.get('month'));
		},
		selectChange: function(e){
			var that = e.target;
			var bits = that.value.split('-');
			methods.load(bits[0],bits[1]);
		}
	}
	
	var im = {
		setOptions: function(opts){
			for(var i in opts) options[i] = opts[i];
		}
	}
	
	var methods = {
		init: function(){
			_this = this;
			im.setOption('fontSize', parseFloat(window.getComputedStyle(_this,null).getPropertyValue('font-size')));
			im.doSelect();
			methods.load();
		},
		load: function(year, month, day ){
			im.empty(_this);
			container = im.getContainer();
			var div = document.createElement('div');
			div.classList.add('heading');
			div.appendChild(im.getOption('prev'));
			div.appendChild(im.getOption('select'));
			div.appendChild(im.getOption('next'))
			container.appendChild(div);
			var days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
			for (var i = 0; i < 7; i++) {
				var cell = im.getBlankDay();
				var t = document.createTextNode(days[i ]);
				cell.appendChild(t);
				cell.classList.remove('blankDay');
				cell.classList.add(days[i].toLowerCase());
				cell.classList.add('heading')
				container.appendChild(cell);
			}	 
			if(year){
				var m = new moment({year:year, month:month, day:1});
			} else {
				var m = new moment().set({date:1});
				year = m.get('year');
				month = m.get('month');
				day = m.get('date');
			}
			var d = m.isoWeekday();
			var startingMonth = m.month();
			first = true;
			while (m.month() == startingMonth) {
				for (var i = 1; i <= 7; i++) {
					if (i < d && first){
						var cell = im.getBlankDay();
					} else if(m.month() == startingMonth){
						var cell = im.getDay(m.year(), m.month(), m.date());
						m.add(1, 'days');
					} else {
						var cell = im.getBlankDay();
					}
					container.appendChild(cell);
				}
				first = false;
			}
			_this.appendChild(container);
			im.setSelectValue(year,month);
			im.refreshAvailability(year,month);
		}	
	}
	
	var im = { //internal only methods
		serialize: function(obj){
			var str = [];
			for (var p in obj) {
				if (obj.hasOwnProperty(p)) {
					str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
				}
			}
			return str.join("&");
		},
		s4: function(){
			return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
		},
		empty: function(elem){
			while (elem.firstChild) {
				elem.removeChild(elem.firstChild);
			}
		},
		guid: function(){
			return im.s4() + im.s4() + '-' + im.s4() + '-' + im.s4() + '-' + im.s4() + '-' + im.s4() + im.s4() + im.s4();
		},
		setOption: function(option, value){
			options[option] = value;
		},
		getOption: function(option){
			return options[option] ? options[option] : null;
		},
		getContainer: function(){
			var c = document.createElement('div');
			c.className = "calendarContainer";
			c.id = im.guid();
			im.setOption('container', c.id);
			var width = (im.convertToRem(im.getOption('fontSize')) * 17) + 'rem';
			console.log(width);
			c.style.width = width;
			return c;
		},
		convertToRem: function(value){
			console.log('value',value);
			var aRem = im.convertFromRem(1);
			var m = parseFloat(getComputedStyle(document.documentElement).fontSize);
			console.log('conversion', value/m);
			return value / m;
		},
		convertFromRem: function(value) {
			var v = parseFloat(getComputedStyle(document.documentElement).fontSize);
    		return value * v;
		},
		getDay: function(year, month, day){
			var m = new moment({
				year: year,
				month: month,
				day: day
			});
			var d = document.createElement('span');
			d.dataset.year = year;
			d.dataset.month = month;
			d.dataset.day = day;
			d.dataset.blank = false;
			d.classList.add("day");
			d.classList.add(m.format('ddd').toLowerCase());
			if (m.isSame(new Date(), "day")) {
				d.classList.add('today');
			}
			d.appendChild(document.createTextNode(day));
			d.addEventListener('click', events.dayClick);
			d.addEventListener('mouseover', events.mouseover);
			return d;
		},
		getBlankDay: function(){
			var d = document.createElement('span');
			d.text = ' ';
			d.className = 'day blankDay';
			d.dataset.blank = true;
			return d;
		},
		addClick: function(year, month, day, elem){
			timer.stop();
			ajax.post({
				action: 'addClick',
				year: year,
				month: month,
				day: day
			}, function(data){
				if (data.result == 'ok') {
					im.setAvailability(data);
					alert(im.getOption('successMessage'));
				}
				else {
					im.setAvailability(data);
					alert(im.getOption('alreadyTakenMessage'));
				}
			}, false);
		},
		checkAvailability: function(year, month, elem){
			im.refreshAvailability(year, month, true);
			if (!im.isAvailable(elem)) {
				alert('Too late.  That date currently has no availability');
			}
		},
		isAvailable: function(elem){
			if (elem.dataset.clicks >= 3) {
				return false;
			}
			else {
				var m = new moment({
					year: elem.dataset.year,
					month: elem.dataset.month,
					day: elem.dataset.day
				});
				if (im.getOption('maskDays').indexOf(m.isoWeekday()) > -1) {
					return false;
				}
				else {
					return true;
				}
			}
		},
		
		refreshAvailability: function(year, month){
			timer.stop();
			if (typeof year == 'undefined') {
				var e = _this.querySelector('.day:not(.blankDay):not(.heading)');
				year = e.dataset.year;
				month = e.dataset.month;
			}
			ajax.post({
				action: 'getAvailability',
				year: year,
				month: month
			}, im.setAvailability, true);
		},
		setAvailability: function(obj){
			if (obj.data) {
				[].forEach.call(_this.querySelectorAll('.day:not(.blankDay):not(.heading)'), function(el){
					el.dataset.clicks = obj.data[el.dataset.year][el.dataset.month][el.dataset.day];
					var a = im.isAvailable(el);
					if (a) {
						el.classList.add('available');
						el.classList.remove('unavailable');
					}
					else {
						el.classList.add('unavailable');
						el.classList.remove('available');
					}
				});
			}
			timer.start();
		},
		doSelect: function(){
			var m = new moment().add(im.getOption('startMonth'), 'months');
			var cM = im.getCurrentMonth();
			var t = new moment({
				year: cM.year,
				month: cM.month,
				day: cM.day
			});
			var s = document.createElement('select');
			for (var i = 0; i < Math.abs(im.getOption('startMonth')) + im.getOption('endMonth'); i++) {
				var opt = document.createElement('option');
				opt.text = m.format('MMMM, YYYY');
				opt.value = m.format('YYYY-MM');
				if (m.isSame(t, 'month')) {
					opt.selected = true;
				}
				m.add(1, 'months');
				s.add(opt);
			}
			s.id = im.guid();
			s.classList.add('selectBox');
			s.addEventListener('change', events.selectChange);
			
			var prev = document.createElement('a');
			prev.classList.add('button');
			prev.text = '<<';
			prev.addEventListener('click', events.prevClick);
			var next = document.createElement('a');
			next.classList.add('button');
			next.text = '>>';
			next.addEventListener('click', events.nextClick);
			
			im.setOption('select', s);
			im.setOption('prev', prev);
			im.setOption('next', next);
		},
		getCurrentMonth: function(){
			var elem = _this.querySelector('.day:not(.blankDay):not(.heading)');
			if (elem == null) {
				var m = new moment();
				return {
					year: m.get('year'),
					month: m.get('month')
				}
			}
			return {
				year: elem.dataset.year,
				month: elem.dataset.month
			}
		},
		setSelectValue: function(year, month){
			month = month + 1;
			if (month <= 9) 
				month = '0' + month;
			var val = year + '-' + month;
			var elem = _this.querySelector("select");
			elem.value = val;
		}
	}
	if(methods[option]){
		return methods[option].apply( this, Array.prototype.slice.call( arguments, 1 ));
	} else if ( typeof option === 'object' || ! option ) {
		return methods.init.apply( this, arguments );
    } else {
		return false;
	}
}


		</script>
		<style>
#calendar{
	font-size:0.8rem;
}
.calendarContainer {
    padding-top: 1rem; 
}
.calendarContainer .day {
	box-sizing:border-box;
	-moz-box-sizing:border-box;
	-webkit-box-sizing: border-box;
	font-size: inherit;
    float: left;
    width: calc((100%/7) - 0.25rem);
    padding: 0;
    margin: 0.125rem;
    text-align: center;
    border: 1px solid silver;
	display:block;
	vertical-align: middle;
}
.calendarContainer .today{
	border-color: #5881fc;
}
.calendarContainer .blankDay{
	border-color: transparent;
}
.calendarContainer, calendarContainer .sun {
    clear: left;
}
.calendarContainer hover{
	backgroundColor: #fbffd2;
}
.calendarContainer .available{
	background-color: #b3ffd7;
	cursor:pointer;
}
.calendarContainer .unavailable{
	background-color: #ffccc9;
}

.calendarContainer .heading{
	text-align:center;
	margin-bottom: 0.5rem;
}
.calendarContainer .heading .button{
	margin-left: 0.5rem;
	margin-right: 0.5rem;
	text-decoration: none;
	cursor: pointer;
}
		</style>
	</head>
	<body>
	<div id="calendar"></div>	
	</body>
	<script>
		document.querySelector('#calendar').datepicker();
	</script> 

RE: non jquery date picker with limitations

(OP)
Wow that's a lot you've knocked up there mate! I look forward to grinding through it as soon as I get the chance - just looked at your demo page and it seems you've got it all working nicely first time - always a winner! Thanks again I'll let you know how I get on

_________________________________
Leozack

CODE

MakeUniverse($infinity,1,42); 

RE: non jquery date picker with limitations

Definitely use the demo page version as I have not reposted some of the js tweaks and css fixes here.
All js and css is on one page. I may have slightly changed the php and will post the source on the demo page too.

RE: non jquery date picker with limitations

how are you getting on with this?

RE: non jquery date picker with limitations

(OP)
Tbh I've not had the time to work on this which is annoying and tonight doesn't look free yet either but I'll let you know. Process-wise my only concern for the intended use is that anyone viewing can click-happy on the calendar and use up all the days. I'm wondering considering limiting click-booking based on IP perhaps or tie it into some form submission for an order - which they only want 3 to be allowed per day. But since they've got a disconnected method of payment that has no part in this process, it is all a little fluid for my liking. But I'll see what I can come up with when I get some actual time to work on this :o

_________________________________
Leozack

CODE

MakeUniverse($infinity,1,42); 

RE: non jquery date picker with limitations

you can limit by session id more easily perhaps than id.
you'd need to add a table column and tweak the two sql queries slightly.

but first you'd need the business rules articulated by your client as it's a mug's game developing any solution without a hard input specification.

RE: non jquery date picker with limitations

(OP)
Yeah it turns out that he's reviewed the process and has tried to streamline payment into it more without the calendar booking - so now you just choose a date then get the payment options and if there is a problem with the order he'll contact them and hopefully won't have more than 3 a day.
I don't mind as it means I don't have to implement this and thus keeps it DB free.
But thanks for your input and well done on making such a great date picker which I'm sure people will be glad to find when searching online!

_________________________________
Leozack

CODE

MakeUniverse($infinity,1,42); 

RE: non jquery date picker with limitations

thanks for posting back @Leozack.

I will rehash the script into a pure date picker and perhaps post here or as a FAQ.

as an aside - you didn't give any business rationale as to why jQuery was banned for this task. It would be interesting to hear the answer as I have come across the 'no Frameworks' shtick twice in recent weeks. Were I an IT director I'd be saying the exact opposite. I'm eager to learn why I might be wrong.

RE: non jquery date picker with limitations

(OP)
The answer is simply that I've made do without JS and not taken the time to learn it - not because it's a problem :P I'm aware that nowadays I might aswell learn it and utilise all the stuff people have made in it but hey, I prefer to just get stuff done without if I can #oldfashioned

_________________________________
Leozack

CODE

MakeUniverse($infinity,1,42); 

Red Flag This Post

Please let us know here why this post is inappropriate. Reasons such as off-topic, duplicates, flames, illegal, vulgar, or students posting their homework.

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Reply To This Thread

Posting in the Tek-Tips forums is a member-only feature.

Click Here to join Tek-Tips and talk with other members!

Resources

Close Box

Join Tek-Tips® Today!

Join your peers on the Internet's largest technical computer professional community.
It's easy to join and it's free.

Here's Why Members Love Tek-Tips Forums:

Register now while it's still free!

Already a member? Close this window and log in.

Join Us             Close