PDA

View Full Version : Timeline/Gantt Widget Feedback



bartonjd
21 Apr 2011, 2:51 PM
I have developed a Gantt timeline widget for Extjs that I feel would be valuable to other Sencha community members, also any feedback or improvements that others may have would be appreciated. An example can be viewed here (http://beta.climate.usurf.usu.edu/mapGUI/timeline.php)
http://beta.climate.usurf.usu.edu/mapGUI/timeline.php.

Source Code for DateRangeColumn


Ext.ns('wt.list');

wt.list.DateRangeColumn = Ext.extend(Ext.list.Column, {
cls: 'daterange',
constructor: function (c) {
try {
c.tpl = new Ext.XTemplate('<div class="buttonwrapper rect-{[values.color || this.getDateColor

(values.startDate,values.endDate,this.getSStart(),this.getSEnd())]}-left" style="overflow:visible;position:relative;left:

{[this.getDateOffset(values.startDate,this.getPStart(),this.getPEnd(),' + c.width + ')]}">', '<span class="rectbutton"

style="width:{[this.getDateLength(values.startDate,values.endDate,this.getPStart(),this.getPEnd(),' + c.width + ')]};">',

'<span ext:qtip=" {startDate} to {endDate}" class="rect-{[values.color || this.getDateColor

(values.startDate,values.endDate,this.getSStart(),this.getSEnd())]}-right"><img src="/img/map/s.gif" height="18"

width="1">', '</span></span>', '</div>', {
getPStart: function () {
return this.ownerCt.periodStart;
},
getPEnd: function () {
return this.ownerCt.periodEnd;
},
getSStart: function () {
return this.ownerCt.selectStart;
},
getSEnd: function () {
return this.ownerCt.selectEnd;
},
getFormatted: function (d) {
var dt = new Date();
dt = Date.parseDate(d, 'Y-m-d');
return dt;
},
getYear: function (y) {
return Ext.util.Format.date(y, 'o');
},
getIntDate: function (d) {
var dt = this.getFormatted(d);
var daysInYear = (dt.isLeapYear()) ? 366 : 365;
var retVal = dt.getFullYear() + (dt.getDayOfYear() / daysInYear);
return retVal;
},
getDateLength: function (dt1, dt2, p1, p2, colWidth) {
try {
var date1 = this.getIntDate(dt1);
var date2 = this.getIntDate(dt2);
var periodStart = this.getIntDate(p1);
var periodEnd = this.getIntDate(p2);
var months = 100 * (Math.abs(date2 - date1)) / (Math.abs(periodEnd - periodStart));

return (months) + '%';
} catch (ex) {
console.log([ex, 'gdtlen']);
}
},
getDateOffset: function (dt1, p1, p2, colWidth) {
var date1 = this.getIntDate(dt1);
var period1 = this.getIntDate(p1);
var period2 = this.getIntDate(p2);
var months = (Math.abs(date1 - period1)) / (Math.abs(period2 - period1));
return (months * 100) + '%';
},
getDateColor: function (dt1, dt2, sel1, sel2) {
var date1 = this.getFormatted(dt1);
var date2 = this.getFormatted(dt2);
var selected1 = this.getFormatted(sel1);
var selected2 = this.getFormatted(sel2);
if (selected1.between(date1, date2) || selected2.between(date1, date2) || ((selected1 < date1) &&

(selected2 > date1))) {
return "green"
} else {
return "gray";
}
}
});
wt.list.DateRangeColumn.superclass.constructor.call(this, c);
} catch (err) {
console.log([err, 'tpl']);
}
}
});

wt.Tip = Ext.extend(Ext.Tip, {
minWidth: 10,
rz: null,
offsets: [20, 10],

initComponent: function () {

wt.Tip.superclass.initComponent.apply(this, arguments);
rz = this.rz; //reference to resizable parent
rz.on({
scope: this,
'beforeresize': this.onSize,
//equivalent to dragstart event
'mousemove': this.onSize,
//equivalent to drag event
'resize': this.hide,
// equivalent to dragend event
'select': this.onSelect // equivalent to dragend event
});
},

onSize: function (resizer, e) {
try {
this.show();
var xOffset = (Math.round(this.rz.parent.columns[0].width * (this.rz.parent.width -

this.rz.parent.scrollOffset))); //get left pixel offset from side of component
var xWidth = Ext.select('em.daterange').elements[0].offsetWidth;
var xPos = (e.getXY()[0] - (xOffset + this.rz.parent.getEl().getAnchorXY()[0]));
var handleDate = this.getText(xPos); // pass in x-coordinate, get date,add to tooltip,date-pickers;
if (resizer.activeHandle.position == 'east') {
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').setValue(handleDate);
} else {
Ext.getCmp(this.rz.parent.datePrefix + '_start-date').setValue(handleDate);
}
this.body.update(
handleDate);

this.doAutoWidth(); //Ensure content fits tooltip
this.el.alignTo(resizer.activeHandle.el, 'l-tl?', this.offsets); //Aligns left side of tooltip with top left

portion of handle(el)
} catch (ex) {
console.log([ex, 'onsz'])
}
},
getText: function (x) {
var xPos = x;
var perStart = Date.parseDate(this.rz.parent.columns[1].periodStart, 'Y-m-d').getTime() / 1000; //start of complete

period
var perEnd = Date.parseDate(this.rz.parent.columns[1].periodEnd, 'Y-m-d').getTime() / 1000; //end of complete

period
var xOffset = (Math.round(this.rz.parent.columns[0].width * (this.rz.parent.width - this.rz.parent.scrollOffset)) +

3); //get left pixel offset from side of component
var kWidth = this.rz.parent.width - xOffset - this.rz.parent.scrollOffset - 2;
var coeff = (xPos / kWidth); // percentage of period
var xDiff = Math.floor(perStart + ((perEnd - perStart) * coeff)); //seconds between period start and current handle

position
var dTemp = Date.parseDate(xDiff, 'U');
return String(Ext.util.Format.date(dTemp, 'Y-m-d'));
},
onSelect: function (r, s, end, e) {
r.parent.updateColors(s, end);
}
});


wt.Resizable = Ext.extend(Ext.Resizable, {
constructor: function () {
try {

wt.Resizable.superclass.constructor.apply(this, arguments);
} catch (ex) {
console.log(ex);
}
this.tip = new wt.Tip({
rz: this
});
this.addEvents('mousemove', 'select');
},
onMouseMove: function (e) {


var xOffset = (Math.round(this.parent.columns[0].width * (this.parent.width - this.parent.scrollOffset)));
var curX = (e.getXY()[0] - (xOffset + this.parent.getEl().getAnchorXY()[0]));
var minX = this.getPixels(this.parent.columns[1].periodStart);
var maxX = this.getPixels(this.parent.columns[1].periodEnd);

if (this.fireEvent('mousemove', this, e) !== false) {
this.on('resize', function (r, w, h, e) {
var start = Ext.getCmp(this.parent.datePrefix + '_start-date').getRawValue();
var end = Ext.getCmp(this.parent.datePrefix + '_end-date').getRawValue();
this.fireEvent('select', this, start, end, e);
});
wt.Resizable.superclass.onMouseMove.apply(this, arguments);
}

if (curX < minX) {

Ext.getCmp(this.parent.datePrefix + '_start-date').setValue(this.parent.columns[1].periodStart);
this.setDates(this.parent.columns[1].periodStart, Ext.getCmp(this.parent.datePrefix + '_end-date').getRawValue

());
this.tip.body.update(
this.parent.columns[1].periodStart);
this.tip.doAutoWidth(); //Ensure content fits tooltip
this.tip.el.alignTo(this.activeHandle.el, 'l-tl?', this.tip.offsets); //Aligns left side of tooltip with top

left portion of handle(el)
}
if (curX > maxX) {
//if current x is greater than max X, slide the handle to periodStart
Ext.getCmp(this.parent.datePrefix + '_end-date').setValue(this.parent.columns[1].periodEnd);
this.setDates(Ext.getCmp(this.parent.datePrefix + '_start-date').getRawValue(), this.parent.columns

[1].periodEnd);
this.tip.body.update(
this.parent.columns[1].periodEnd);
this.tip.doAutoWidth(); //Ensure content fits tooltip
this.tip.el.alignTo(this.activeHandle.el, 'l-tl?', this.tip.offsets); //Aligns left side of tooltip with top

left portion of handle(el)
}
},
setDates: function (start, end) {
try {
if (Ext.isDate(Date.parseDate(start, 'Y-m-d')) && Ext.isDate(Date.parseDate(end, 'Y-m-d'))) {
this.parent.columns[1].selectStart = start;
this.parent.columns[1].selectEnd = end;

var xOffset = (Math.round(this.parent.columns[0].width * (this.parent.width - this.parent.scrollOffset)) +

3);
var perStart = this.getPixels(this.parent.columns[1].periodStart); //start of complete period
var perEnd = this.getPixels(this.parent.columns[1].periodEnd); //end of complete period
var selectStart = this.getPixels(start); //new end date of selection
var selectEnd = this.getPixels(end); //new end date of selection
var newWidth = (selectEnd - selectStart);

var xPos = selectStart;
this.el.setLeft(xPos + xOffset);
this.resizeTo(newWidth, this.el.getHeight());
}
} catch (ex) {
console.log(ex)
}
},
getFormatted: function (d) {
var dt = new Date();
dt = Date.parseDate(d, 'Y-m-d');
return dt;
},
getIntDate: function (d) {
var dt = this.getFormatted(d);
var daysInYear = (dt.isLeapYear()) ? 366 : 365;
var retVal = dt.getFullYear() + (dt.getDayOfYear() / daysInYear);
return retVal;
},
getPixels: function (dt) {
// returns percentage equivalent of a date
var xOffset = (Math.round(this.parent.columns[0].width * (this.parent.width - this.parent.scrollOffset)) + 3);

//get left pixel offset from side of component
var kWidth = this.parent.width - xOffset - this.parent.scrollOffset - 2;
var perStart = this.getIntDate(this.parent.columns[1].periodStart); //start of complete period
var perEnd = this.getIntDate(this.parent.columns[1].periodEnd); //end of complete period
var selectStart = this.getIntDate(dt); //new start date of selection
var xPos = ((Math.abs(selectStart - perStart) / Math.abs(perEnd - perStart))) * kWidth; // percentage of period
return (xPos);
},
destroy: function () {
this.tip.purgeListeners();
this.tip.destroy();
wt.Resizable.superclass.destroy.apply(this);
}
});
Ext.reg('wtresize', wt.Resizable);

var origOnHdClick = Ext.list.Sorter.prototype.onHdClick;
var origInit = Ext.list.Sorter.prototype.init;
Ext.override(Ext.list.Sorter, {
init: function () {
this.addEvents('sortchange');
origInit.apply(this, arguments);
},
onHdClick: function (e) {
var hd = e.getTarget('em', 3);
if (hd && !this.view.disableHeaders) {
var index = this.view.findHeaderIndex(hd);
if (this.view.getXType() !== 'listview') {
var sortIndex = this.view.defaultSortField;
} else {
var sortIndex = this.view.columns[index].dataIndex;
}
this.view.store.sort(sortIndex);
var state = this.view.store.getSortState();
this.fireEvent('sortchange', this, state);
}
}

});

wt.TimeLine = Ext.extend(Ext.list.ListView, {

initComponent: function () {
this.rzId = 'splitter_' + Ext.id();
var constraintWidth = (this.columns[1].width * (this.width - this.scrollOffset));
try {
this.tpl = new Ext.XTemplate('<div id="' + this.rzId + '_constraint" tabIndex="-1" style="position: absolute;

left: 0px; height: 500px; z-index: 998;width:' + constraintWidth + 'px; float: left; display: inline;"></div>', '<div id="'

+ this.rzId + '" tabIndex="-1" class="x-timeline-bg" style="position: absolute; left: 0px; height: 500px; z-index:

999;width:5px; float: left; display: inline;"></div>', '<tpl for="rows">', '<dl>', '<tpl for="parent.columns">', '<dt

style="width:{[values.width*100]}%;text-align:{align};">', '<em unselectable="on"<tpl if="cls"> class="{cls}</tpl>">',

'{[values.tpl.apply(parent)]}', '</em>', '</dt>', '</tpl>', '<div class="x-clear"></div>', '</dl>', '</tpl>');
wt.TimeLine.superclass.initComponent.apply(this);
tmLn = this;
Ext.each(this.columns, function (i) {
i.tpl.ownerCt = i;
i.ownerCt = tmLn;
});
} catch (ex) {
for (var i in ex) {
console.log(ex);
}
}

},
setDates: function (start, end) {
this.columns[1].selectStart = start;
this.columns[1].selectEnd = end;
this.rz.setDates(start, end);
},
setStart: function (newVal) {
this.columns[1].selectStart = newVal;
this.rz.setDates(newVal, this.columns[1].selectEnd);
},
setEnd: function (newVal) {
this.columns[1].selectEnd = newVal;
this.rz.setDates(this.columns[1].selectStart, newVal);
},
refreshView: function () {
try {
this.refresh();
this.rz.destroy();
xCoord = this.columns[0].width * (this.getWidth() - this.scrollOffset);
xWidth = (this.columns[1].width * (this.getWidth() - this.scrollOffset));
Ext.get(this.rzId + "_constraint").setLeft(xCoord);
Ext.get(this.rzId + "_constraint").setWidth(xWidth);
Ext.get(this.rzId).setLeft(xCoord);
Ext.get(this.rzId).setWidth(xWidth);

var rz = new wt.Resizable(this.rzId, {
handles: 'w e',
dynamic: true,
parent: this,
resizeRegion: new Ext.lib.Region(0, xCoord, 1000, xCoord + xWidth),
// tlbr
constrainTo: this.rzId + "_constraint",
minHeight: 500,
pinned: true
});
this.rz = rz;
rz.minX = rz.getPixels(this.columns[1].periodStart);


Ext.getCmp(this.rz.parent.datePrefix + '_start-date').setValue(this.columns[1].selectStart);
Ext.getCmp(this.rz.parent.datePrefix + '_start-date').minValue = this.getFormatted(this.columns

[1].periodStart);
Ext.getCmp(this.rz.parent.datePrefix + '_start-date').maxValue = this.getFormatted(this.columns[1].periodEnd);
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').setValue(this.columns[1].selectEnd);
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').minValue = this.getFormatted(this.columns[1].periodStart);
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').maxValue = this.getFormatted(this.columns[1].periodEnd);

this.rz.setDates(this.columns[1].selectStart, this.columns[1].selectEnd);
var top = (this.rz.parent.getHeight() / 2) - 10;


var west = {
tag: 'img',
style: 'position:relative;top:' + top + 'px;left:-11px',
src: '/support/climateExtjs/images/knob-west.png'
};

var east = {
tag: 'img',
style: 'left:2px;position:relative;top:' + top + 'px;left:2px',
src: '/support/climateExtjs/images/knob-east.png'
};

Ext.DomHelper.append(this.rz.west.el.dom.id, west);
Ext.DomHelper.append(this.rz.east.el.dom.id, east);

this.rz.west.el.set({
'style': 'overflow:visible;position:relative'
}, true);
this.rz.east.el.set({
'style': 'overflow:visible;'
}, true);
} catch (ex) {
console.log(ex);
}
},
render: function (c, p) {
try {
wt.TimeLine.superclass.render.apply(this, arguments);

if (!Ext.isDefined(this.rz)) {
xCoord = this.getEl().getAnchorXY('l')[0] + this.columns[0].width * this.getWidth();
xWidth = this.columns[1].width * this.getWidth() - this.scrollOffset;
Ext.get(this.rzId + "_constraint").setLeft(xCoord);
Ext.get(this.rzId + "_constraint").setWidth(xWidth);
Ext.get(this.rzId).setLeft(xCoord);
Ext.get(this.rzId).setWidth(xWidth);
var rz = new wt.Resizable(this.rzId, {
handles: 'w e',
dynamic: true,
resizeRegion: new Ext.lib.Region(0, xCoord, 4000, xCoord + xWidth),
// tlbr
constrainTo: this.rzId + "_constraint",
parent: this,
minHeight: 500,
pinned: true
});
}

this.rz = rz;
rz.minX = rz.getPixels(this.columns[1].periodStart);
var coords = Ext.get(this.all.elements[0].children[1]).getXY();

Ext.getCmp(this.rz.parent.datePrefix + '_start-date').setValue(this.columns[1].selectStart);
Ext.getCmp(this.rz.parent.datePrefix + '_start-date').minValue = this.getFormatted(this.columns

[1].periodStart);
Ext.getCmp(this.rz.parent.datePrefix + '_start-date').maxValue = this.getFormatted(this.columns[1].periodEnd);
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').setValue(this.columns[1].selectEnd);
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').minValue = this.getFormatted(this.columns[1].periodStart);
Ext.getCmp(this.rz.parent.datePrefix + '_end-date').maxValue = this.getFormatted(this.columns[1].periodEnd);

this.rz.setDates(this.columns[1].selectStart, this.columns[1].selectEnd);

} catch (ex) {
console.log(ex);
}

},
getFormatted: function (d) {
var dt = new Date();
dt = Date.parseDate(d, 'Y-m-d');
return dt;
},
updateColors: function (selStart, selEnd) {
try {
var timeLine = this;
this.store.each(function (item, i) {
var dt1 = item.data.startDate;
var dt2 = item.data.endDate;

var date1 = timeLine.getFormatted(dt1);
var date2 = timeLine.getFormatted(dt2);
var selected1 = timeLine.getFormatted(selStart);
var selected2 = timeLine.getFormatted(selEnd);
if (selected1.between(date1, date2) || selected2.between(date1, date2) || ((selected1 < date1) &&

(selected2 > date1))) {
Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).removeClass(['rect-gray-left',

'rect-green-left']);
Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).addClass('rect-green-left');
Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).removeClass(['rect-

gray-right', 'rect-green-right']);
Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).addClass('rect-green-

right');

} else {
Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).removeClass(['rect-gray-left',

'rect-green-left']);
Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).addClass('rect-gray-left');
Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).removeClass(['rect-

gray-right', 'rect-green-right']);
Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).addClass('rect-gray-

right');

}


});

} catch (err) {
console.log(ex)
}
}
});
Ext.reg('timeline', wt.TimeLine);
CSS


.buttonwrapper{ /* Container you can use to surround a CSS button to clear float */
overflow: hidden;
width: 100%;
}
.rect-gray-left{
overflow:visible;
background: transparent url(/images/rect-gray-left.png) no-repeat top left;
}
.rect-green-left{
overflow:visible;
background: transparent url(/images/rect-green-left.png) no-repeat top left;
}
.rect-green-right{
background: transparent url(/images/rect-green-right.png) no-repeat top right;
}
.rect-gray-right{
background: transparent url(/images/rect-gray-right.png) no-repeat top right;
}

span.x-large-heading {
color:#15428B;
font-weight:bold;
font-size: 14px;
font-family: tahoma,arial,verdana,sans-serif;
}

span.rectbutton{
display: block;
font: normal 13px Tahoma; /* Change 13px as desired */
line-height: 16px; /* This value + 4px + 4px (top and bottom padding of SPAN) must equal height of button background

(default is 24px) */
height: 17px; /* Height of button background height */
padding-left: 2px; /* Width of left menu image */
text-decoration: none;
}

span.rectbutton span{
display: block;
padding: 4px 0px 4px 0; /*Set 11px below to match value of 'padding-left' value above*/
}

.knob-east {
background:transparent url(/images/knob-east.png) no-repeat scroll left center !important;
}

.knob-west {
background:transparent url(/images/knob-west.png) no-repeat scroll left center !important;
}

greyknght1
22 Apr 2011, 7:52 AM
Looks great! I went to USU for a bit. Is the quad still a frozen wasteland? Feb 7 am classes were a nightmare, though I do miss the creamery and old main.

bartonjd
28 Jul 2011, 6:47 AM
Any feedback or suggestions for improvement would be greatly appreciated.

lindsey33
9 Sep 2011, 12:07 AM
well, it looks nice without buy levitra (http://www.1st-levitra-pharmacy.com) any improvement I believe ;)

cammie82
18 Oct 2011, 5:39 AM
well, it looks nice without any improvement I believe ;)
Quite agree, I like it