Protected: Follett Flash Widgets
Mar 31
Feb 18
This is actually the third version, as the first two didn’t quite hit the mark on the requirements… This jQuery dateslider is based on the slick and cool Ajaxorized version, originally written for Prototype.js. What I didn’t like was that it’s zoom in/out feature didn’t really scale very well (what unit I am looking at? and why don’t these lines line up?), and made it hard to pick a date and time.
I used jQueryUI because it handily had all the things I need: draggable, resizable things. The hardest part was, of course, the actual date-times (as anyone who has to work with Javascript dates knows). I used the super handy date.js like the original as well.
To use it, include jquery, jqueryui, date.js, and the plugin:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script src="date.js"></script>
<script src="jquery.dateRanger.js" type="text/javascript"></script>
Then, initalize the plugin:
The options:
<script type="text/javascript">
$(function(){
$("#my-date-slider").dateRanger({
"dateFormat": "MMM dd yyyy HH:mm:ss",
"timeStart": Date.today().set({
day: 1,
month: 0,
year: 2011
}),"timeEnd": Date.today().set({
day: 30,
month: 11,
year: 2012
}),
"startDate": Date.parse("1/17/2011"),
"endDate": Date.parse("4/2/2011"),
"zoomLevel": "month",
"unitwidth": 20
})
});
</script>
Download plugin: jquery.dateRanger.js:
(function($){
$.fn.dateRanger = function(options){
var settings = {
"startDate": Date.parse('-7days'),
"endDate": Date.parse('-1days'),
"dateFormat": 'MMM dd yyyy HH:mm:ss',
"timeStart": Date.today().set({
day: 1,
month: 0,
year: 2011
}).clearTime(),
"timeEnd": Date.today().set({
day: 31,
month: 11,
year: 2012
}).clearTime(),
"zoomLevel": "day",
"unitwidth": 20
};
var config = $.extend(settings, options);
var zoom = config.zoomLevel, rangeStart = config.startDate, rangeStop = config.endDate, timeStart = config.timeStart, timeEnd = config.timeEnd, f = config.dateFormat, w = config.unitwidth, one = new Array();
one["second"] = 1000;
one["minute"] = 1000 * 60;
one["hour"] = 1000 * 60 * 60;
one["day"] = 1000 * 60 * 60 * 24;
one["month"] = 1000 * 60 * 60 * 24 * 30;
one["year"] = 1000 * 60 * 60 * 24 * 365;
function mapDates(ui){
var range = ui.width() / w;
var s = ui.position().left / w;
var e = s + range - 1;
var startDate = $j(".day").eq(s).attr("data-date");
var endDate = $j(".day").eq(e).attr("data-date");
$j("#datestart").val(startDate.toString(f))
$j("#dateend").val(inclusiveRange(endDate, zoom).toString(f))
centerSelection();
}
function centerSelection(){
$j("#dragger").css('left', -1 * ($j("#resizer").position().left - .5 * ($j("#mask").width() - $j("#resizer").width())))
}
function difference(a, b, z){
A = a.getTime() / one[z];
B = b.getTime() / one[z];
return Math.floor(B - A);
}
function unitsbetween(a, b, z){
A = a.getTime() / one[z];
B = b.getTime() / one[z];
return (B - A);
}
function inclusiveRange(d, zoom){
var $fullrange = (Date.parse(d)) ? Date.parse(d) : Date.parse(d.substring(0, d.length - 1));
var compare = $fullrange.clone();
switch (zoom) {
case "year":
return compare.set({
month: 0,
day: 1
}).clearTime().add(1).years().add(-1).seconds();
break;
case "month":
return compare.moveToLastDayOfMonth().add(1).days().add(-1).seconds();
break;
case "day":
return compare.add(1).days().add(-1).seconds();
break;
case "hour":
return compare.add(1).hours().add(-1).seconds();
break;
case "minute":
return compare.add(1).minute().add(-1).seconds();
break;
case "second":
return compare.add(1).seconds().add(-1).milliseconds();
break;
default:
return $fullrange;
break;
}
}
function buildDates(zoom){
$j("#dragger").css('left', '0')
$j("#dragger .day").remove();
$j("#resizer").css({
left: 0,
width: w + "px"
})
var visibleArea, format1, format2, tempDate;
switch (zoom) {
case "hour":
var tempDateHS = Date.parse($j("#datestart").val()).clone().add(-1).days();
var tempDateHE = Date.parse($j("#dateend").val()).clone().clearTime().add(2).days();
visibleArea = difference(tempDateHS, tempDateHE, zoom);
format1 = "MMM dd yy ht";
format2 = "ht";
tempDate = tempDateHS.clone()
break;
case "day":
visibleArea = difference(config.timeStart, timeEnd, zoom);
format1 = "MMM dd yy";
format2 = " dd";
tempDate = timeStart.clone()
break;
case "month":
visibleArea = difference(config.timeStart, timeEnd, zoom);
format1 = "MMM 'yy";
format2 = "MMM 'yy";
tempDate = timeStart.clone()
break;
case "year":
visibleArea = Math.max(timeEnd.getFullYear() - config.timeStart.getFullYear(), 1);
format1 = "yyyy";
tempDate = timeStart.clone()
break;
}
$j("#dragger").css('width', visibleArea * w)
for (i = 0; i < visibleArea; i++) {
if ((zoom == "day" && tempDate.getDate() == 1) || (zoom == "hour" && tempDate.getHours() == 0)) {
format = format1;
cl = " fdotm"
}
else
if (zoom == "month" && tempDate.getMonth() == 0) {
format = format1;
cl = " fmoty"
}
else
if (zoom == "year") {
format = format1;
cl = " yrot"
}
else {
format = format2;
cl = "";
}
$j("#dragger").append('<div data-date="' + tempDate.toString(f) + '">' + tempDate.toString(format) + '</div>')
tempDate.add(1)[zoom]()
}
}
function setScroller(s, r){
if (r > 1)
r = Math.ceil(r);
else
r = Math.round(r);
$j("#resizer").css({
left: $j(".day[data-date='" + s + "']").position().left,
width: r * w + "px"
})
}
function u2Date(u){
if (u.indexOf("Z") != -1)
return Date.parse(u.substring(0, u.length - 1));
else
return Date.parse(u);
}
$j("#datestart").live("change", function(e){
if (Date.parse($j(this).val())) {
var inpS = Date.parse($j(this).val());
var inpE = u2Date($j("#dateend").val())
if (inpS.compareTo(inpE) == 1) {
inpE = inpS;
}
$j("#datestart").val(inpS.toString(f));
setScroller($j("#datestart").val(), Math.max(difference(inpS, inpE, zoom), 1))
mapDates($j("#resizer"));
}
else {
$j(this).addClass("error").effect("pulsate", {
times: 2
}, 500, function(){
$j(this).removeClass("error").focus()
});
}
});
$j("#dateend").live("change", function(e){
if (Date.parse($j(this).val())) {
var inpE = Date.parse($j(this).val());
var inpS = u2Date($j("#datestart").val())
if (inpS.compareTo(inpE) == 1) {
inpE = inpS;
}
$j("#dateend").val(inpE.toString(f));
setScroller($j("#datestart").val(), Math.max(difference(inpS, inpE, zoom), 1))
mapDates($j("#resizer"));
}
else {
$j(this).addClass("error").effect("pulsate", {
times: 2
}, 500, function(){
$j(this).removeClass("error").focus()
});
}
});
$j("div.zoomMenu div").live("click", function(){
$t = $j(this);
zoom = $t.html();
$j("div.zoomMenu div").removeClass("selectedZoom")
$t.addClass("selectedZoom");
buildDates(zoom)
inpS = u2Date($j("#datestart").val());
inpE = u2Date($j("#dateend").val());
switch (zoom) {
case "hour":
inpS.set({
"hour": 0
});
inpE = inclusiveRange(inpE, zoom);
break;
case "day":
inpS.set({
"hour": 0
});
inpE = inclusiveRange(inpE, zoom);
break;
case "month":
inpS.moveToFirstDayOfMonth();
inpE.moveToLastDayOfMonth();
break;
case "year":
inpS.set({
month: 0,
day: 1
});
inpE.set({
month: 0,
day: 1
}).add(1).years().add(-1).seconds();
break;
}
$j("#datestart").val(inpS.toString(f));
$j("#dateend").val(inpE.toString(f));
setScroller($j("#datestart").val(), difference(inpS, inpE, zoom))
mapDates($j("#resizer"));
});
return this.each(function(){
var zooms = {
"year": "yyyy",
"month": "MMM",
"day": "dd",
"hour": "HH"
};
var structure = '<div id="range">';
structure += '<div id="dragDate">';
structure += '<label for="datestart">Start</label><input id="datestart" name="datestart" type="text" /> ';
structure += '<label for="dateend">Stop</label><input id="dateend" type="text" name="dateend"/>';
structure += '</div>';
structure += '<div id="mask"><div id="dragger"><div id="resizer"></div></div>';
structure += '</div>';
structure += '<div>';
var cla = '';
for (b in zooms) {
cla = (b == zoom) ? '' : '';
structure += '<div' + cla + '>' + b + '</div>';
}
structure += '</div></div>';
$j(this).append(structure);
buildDates(zoom);
$j("#dragger").draggable({
grid: [w, 0],
axis: 'x',
addClasses: false
});
$j("#resizer").resizable({
handles: 'e, w',
grid: [w, 0],
containment: '#dragger',
stop: function(event, ui){
if (ui.position.left < 0) {
$j("#resizer").css('left', '0px')
}
mapDates($j(this))
},
addClasses: false
}).draggable({
grid: [w, 0],
axis: 'x',
containment: '#dragger',
stop: function(event, ui){
mapDates($j(this))
},
addClasses: false
}).css({
left: $j(".day[data-date='" + rangeStart.toString(f) + "']").position().left,
width: difference(rangeStart, rangeStop, zoom) * w + "px"
});
mapDates($j("#resizer"));
centerSelection();
});
};
})(jQuery);
And some useful CSS to finish up with:
#range {
position: relative;
}
.day {
color: #8997a5;
padding: 10px 0 0;
font-size: 9px;
width: 19px;
float: left;
height: 25px;
border-left: 1px solid #eee;
text-align: center;
}
.fdotm {
padding: 1px 0 0 0;
border-left: 1px solid #bbb;
height: 35px;
}
.fmoty {
padding: 10px 0 0 0;
border-left: 1px solid #888;
height: 25px;
}
.yrot {
font-size: 8px
}
#mask {
width: 100%;
overflow: hidden;
height: 80px;
position: absolute;
left: 0;
top: 50px;
}
#dragger{
width: 10000em;
position: absolute;
left: 0;
top: 10px;
z-index: 2;
height: 75px;
cursor: move;
padding: 10px 0 0 0;
background: none;
border: none;
color: #000;
}
#dragger #resizer{
background: rgba(0, 137, 206, 0.2);
width: 200px;
position: absolute;
top: 10px;
z-index: 6;
height: 40px;
cursor: move;
padding: 0;
margin: 0;
}
.error {
background: #ff0013;
}
.zoomMenu {
padding: 10px 0 0 0;
text-align: center;
}
.zoomMenu div {
display: inline;
color: #0089ce;
text-align: center;
cursor: pointer;
margin-right: 5px;
padding: 2px 6px;
position: relative;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
}
.zoomMenu div:hover {
background: #e5f3fb;
color: #333;
}
.zoomMenu div.selectedZoom {
background: #0089ce;
color: #fff;
}
Aug 04
Ok, so I’ve been playing more and more with jQuery. Here’s my take on showing off my portfolio, where I give a brief on each project and include a screen-shot. This also uses the cool reflection.js for the images, and provides a simple progress stepper.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">// <![CDATA[
$(function() {
$('#mainContent img').addClass("reflect rheight10");
$('#mainContent img').css("float","right"); //reflect messes up floats, so add back in here
$(".H").append('<span>« Prev</span> | <span>Next »</span>');
var g=1;
$(".H").each(function(){
perc = (g/$(".H").length)*100;
$(this).append('
<div>
<div style="width:'+perc+'%"></div>
</div>
')
g++;
});
$(".next").css('cursor','pointer').click(function(){ slideShow(); })
$(".prev").css('cursor','pointer').click(function(){ slideShowPrev();})
$(".H").filter(':not(:last)').hide();
$(".H").filter(':last').addClass('show'); //'show' the last slide because when we call slideshow(), it will loop around to the first slide
$('#port a').attr('target','_blank');
slideShow();
});
function slideShow() {
var current = $('#port .show');
var next = current.next().length ? current.next() : current.parent().children(':first');
current.fadeOut('slow').removeClass('show');
next.fadeIn('slow').addClass('show');
}
function slideShowPrev() {
var current = $('#port .show');
var prev = current.prev().length ? current.prev() : current.parent().children(':first');
current.fadeOut('slow').removeClass('show');
prev.fadeIn('slow').addClass('show');
}
// ]]></script>
<script src="scripts/reflection/reflection.js" type="text/javascript"></script>
This works with the structure I already had–classed div’s holding each project.
May 05
Jan 25
Sorry, no updates for a while! Been busy… Anyhow, my wife and I are having a baby. First question: what gender is it? Boy. Second question: do you have a name yet? Nope.
To either help or hinder this problem, I am going to be working on a name picker. I know, there are TONS of apps, databases, etc. But, what most do is look for a name based on a starting letter (‘male name starting with W’), or based on the keyword of the meaning (‘female name meaning Hope’). What if you have a name, and want a name to go with it? And what does that even mean? For me, it means the ending syllable doesn’t match in the names. “Jordan [ Boran | Darren | Norin ]” don’t quite work, you know?
So I am starting with a list of 59,266 names, a syllable counter (which I think can be modified) from this clever fellow, and my crazy idea.
How is it going to work? Well, I think first I am going to modify the syllable counter to give me back the syllables it’s calculated for each of the names. Probably going to save those back to the data source (because calculating that each time would be nuts!). Then, I’ll take user input on any existing names…maybe some kind of thing where you can add more blank fields (some kids have like 10 names), and then the application will figure out which ending syllables it can’t use, then select names that are OK. I guess I can have a toggle for selecting ‘like’ endings (maybe your kid needs to be Jordan Norin!).
Should be cool.
Oct 12
Well, Zillow still hasn’t gotten back to me with a way to show the region’s listings, so Relocator will just have to exist as a “Job Near Friends Finder” (or just as “Jobs on a Map”). Still using the LinkedIn, Indeed and Facebook API’s. If you use it and have some feedback, drop a comment below.
Happy hunting!
Sep 08
I’ve had the Noobslide (slideshow “class” for Mootools (great tutorial here) for awhile, but I needed to added some basic functionality: when you hover (or mouseover) a slide, it stops the show so you can read it. When you rollout (mouseleave or mouseout), the show starts up again. Great!
Note that this assumes you have your slides in generic <span> tags inside something with an id of ‘box1′.
window.addEvent('domready',function(){
var nS1 = new noobSlide({
box: $('box1'),
items: [0,1,2,3,4],
size: 400,
autoPlay: true,
interval: 8000,
fxOptions: {
duration: 1000,
transition: Fx.Transitions.Expo.easeInOut,
wait: false
}
});
//add mousein/out behaviors for all slides
$$('#box1 span').addEvents({
'mouseover':function(){
nS1.stop();
},
'mouseleave':function(){
//make sure the first argument matches your 'interval' value above
//this will set the delay,go to the next slide, without waiting for the delay (rolloff, immediately transitions to next slide)
nS1.play(8000,"next",false);
}
});
//end domready
});
Cool. Another thing I wanted was some text links to jump around the slide show. I’ve got my slides in a container with an id of’slidefloater.’ Here it is: (this would be inserted before the last ‘});’, inside the domready function )
var clinker=new Element('div',{'id':'clinker','styles':{'width':400}});
clinker.inject($('slidefloater'));
var labels = Array("label 0","label 1","label 2","label 3","label 4");
var i=0;
labels.each(function(el){
var linkto=new Element('a',{'html':el,'href':'#','styles':{'margin-right':4}});
linkto.j = i; //localize
linkto.addEvent('click', function(){
nS1.stop();
nS1.walk(this.j);
nS1.play(8000,"next",true); //go to next slide after normal interval (wait to go)
} );
i++;
linkto.inject($('clinker'));
});
Good times!
Sep 01
OK, so now the Facebook API is integrated, and seems to be working as expected. You plug in a city/state or ZIP, and the app finds folks you know in a 150 mile radius. It’s a little slow because I am loading all friends right now. It also only knows what people have put in their ‘current location,’ so you won’t see anyone who hasn’t provided that info. Still pretty good.
Also cleaned up some links with some logic (i.e. no longer gives a link to the next page of job search when there are no results!).
Found a nice function I adapted for PHP to compare two geocodes:
function ToRadian( $val) { return $val * ( pi() / 180); }
function DiffRadian( $val1, $val2) { return ToRadian($val2) - ToRadian($val1); }
function CalcDistance( $lat1, $lng1, $lat2, $lng2, $m='') {
$radius = ($m == 'km')? 6367.0:3956.0;
return $radius * 2 * asin( min(1, sqrt( ( pow(sin((DiffRadian($lat1, $lat2)) / 2.0), 2.0) + cos(ToRadian($lat1)) * cos(ToRadian($lat2)) * pow(sin((DiffRadian($lng1, $lng2)) / 2.0), 2.0) ) ) ) );
}
Which I found at this nice person’s blog (thanks to Chris Pietschmann!)
I am using it to compare the friend’s location with the city/state/zip of the job search. So far, so good!
Aug 27
OK, so now I’ve got jobs being listed… and mapped. And a bonus from LinkedIn, an easy-to-install, no login required widget. The user will have to be logged in on LinkedIn to use it, but it will show any connections to companies that come up in the job search. Now if only Zillow would let me show more than one address’ listing at a time…
Next up: adding in the address “not really a search” box.
Aug 24
Fun and troublesome!
Mashup in progress
Issues:
function getCache($address) {
if(!isset($this->dsn)) return false;
$_ret = array();
// PEAR MDB2
require_once("MDB2.php");
$_db =& MDB2::connect($this->dsn);
if (PEAR::isError($_db)) {
die($_db->getMessage() . ', ' . $_db->getDebugInfo());
}
if (PEAR::isError($_db)) {
die($_db->getMessage(). ', ' . $_db->getDebugInfo());
}
$_res =& $_db->query("SELECT lon,lat FROM {$this->_db_cache_table} where address = '$address'");
if (PEAR::isError($_res)) {
die($_res->getMessage(). ', ' . $_res->getDebugInfo());
}
if($_row = $_res->fetchRow()) {
$_ret['lon'] = $_row[0];
$_ret['lat'] = $_row[1];
}
$_db->free();
return !empty($_ret) ? $_ret : false;
}function putCache($address, $lon, $lat) {
if(!isset($this->dsn) || (strlen($address) == 0 || strlen($lon) == 0 || strlen($lat) == 0))
return false;
// PEAR MDB2
require_once("MDB2.php");
$_db =& MDB2::connect($this->dsn);
if (PEAR::isError($_db)) {
die($_db->getMessage() . ', ' . $_db->getDebugInfo());
}
$_res =& $_db->query("insert into ".$this->_db_cache_table." values ('$address', $lon, $lat)");
if (PEAR::isError($_res)) {
die($_res->getMessage());
}
$_db->free();
return true;
}Stay tuned for more developments…
To do: