Search

Rss Posts

Rss Comments

Login

 

Protected: Follett Flash Widgets

Mar 31

This content is password protected. To view it please enter your password below:

dateSlider jQuery plugin

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:


<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>
The options:

  • dateFormat : a string or date.js format specified (check it!) (i.e. “MMM dd”, “u”, “yyyy-MM-d ht”)
  • timeStart: the first available date; You can pass in anything recognized by date.js (i.e. “today”, “yesterday”, “+2weeks”, “-18years”, etc.)
  • timeEnd: the last available date
  • startDate: beginning of the selected range
  • endDate: the end of the selected range
  • zoomLevel: what time unit you see on load: accepts “year”,”month”,”day”, or “hour”
  • unitwidth: how wide is a unit? If you change this, be sure to change the css for .day to match

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;
}

jQuery quick slideshow

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>&laquo; Prev</span> | <span>Next &raquo;</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.

Redesigned my main site

May 05

Did a quick redo on the site and redid my logo. Looks better, I think!

Naming App

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.

Relocator Released!

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!

http://6flex.com/relocator

Mootools Noobslide Hover/mouseover/mouseenter good times

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!

Mo’ Mashup

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!

More Mashup Progress

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.

Relocator

Creating a mashup

Aug 24

Fun and troublesome!
Mashup in progress

Issues:

  1. Installing PEAR on shared GoDaddy server, thanks to Abby and Win’s Blog
  2. Getting Zillow data (easy!)
  3. Altering GoogleMapAPI.class.php (from phpinsider.com)
    1. Moving from PEAR DB to MDB2 (what I used for reference because DB is deprecated in the latest version of PEAR)
    2. changed function getCache:
      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;
      }
    3. changed function putCache:
      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;
      }
    4. GoogleMap API (easy once I figured it liked addresses better than the lat/long Zillow was returning…)

Stay tuned for more developments…

To do:

  1. fix line-height of superscripted registration mark
  2. integrate multiple listings
  3. integrate search
  4. integrate 2 other (maybe 3?) API’s to be named later