PDA

View Full Version : New: Schedule Control



bwoody
9 Feb 2009, 10:59 AM
This is an alpha version of a Schedule control for Ext 2.0:

http://charlottesoftwaresystems.com/ext-2.2/examples/schedule/schedule.html

With some work, it could eventually be used like a Gantt chart. I am releasing early while some of the functionality is still missing to gather feedback and determine what features are most needed. I hope to have an update available in the next few weeks which takes care of some of the current limitations. Feedback is appreciated. It will help drive my todo list.

Bryan

http://charlottesoftwaresystems.com/ext-2.2/examples/schedule/schedule.jpg

alexb
9 Feb 2009, 7:34 PM
Nice one!

How about supporting time as columns as well as time as rows?
mon tue wed ...
8-00
9-20
10-30

Config support for time intervals?

MeDavid
10 Feb 2009, 12:30 AM
Looking nice! Looking forward to new versions

wm003
10 Feb 2009, 1:14 AM
Very nice one! Keep it up:)

bwoody
10 Feb 2009, 9:43 AM
Nice one!

How about supporting time as columns as well as time as rows?
mon tue wed ...
8-00
9-20
10-30

Config support for time intervals?

If you want to have times for the labels on each row, you could do that with this version by simply providing the times in the Store you provide to the schedule. If you have an example of what you would like to accomplish that you could point me to, I'd be happy to take a look and see how it could be supported.

garraS
10 Feb 2009, 8:15 PM
This is awesome!

Great plugin! Congrats!

aconran
10 Feb 2009, 9:22 PM
Nice extension, looking forward to watching it progress.

alexb
11 Feb 2009, 1:21 AM
If you want to have times for the labels on each row, you could do that with this version by simply providing the times in the Store you provide to the schedule. If you have an example of what you would like to accomplish that you could point me to, I'd be happy to take a look and see how it could be supported.

Well, I was thinking about a student schedule at a university.
Times to the left, days as column headers

At some universities classes start at 8-00, at some at 10-00 (let's say). Classes may end at different times as well. So StartTime, EndTime configs are required

Classes may last 60 mins, 80 mins, 120 mins, so TimeInterval is required

See the attached image.

Also have a look at this control: http://www.codeproject.com/KB/custom-controls/schedule.aspx
--------------------------
Edit:

Read the source code.
timeUnit: 'MINUTE',
interval: 10,

startDate: '1/29/2009 16:00:00',
endDate: '1/29/2009 23:00:00'

Maybe these configs will do the job...

moegal
11 Mar 2009, 4:49 PM
Great extension. How is the progress going?

Thanks, Marty

reel
12 Mar 2009, 1:13 AM
Wow! This is really a nice one! =D>

I'm also (very) interested in the progress ...

bwoody
23 Mar 2009, 9:47 AM
I wasn't able to work on the control last month, but plan on doing an update soon.

moegal
23 Mar 2009, 10:45 AM
Great, thanks!

Mabion
1 Apr 2009, 11:03 PM
Hi all,

I'm currently working with this Schedule Control and it works very good! Also i have managed to change the view to Daily, Weekly, Monthly and Yearly. This I achief by changing the interval, for instance:



<!-- monthly -->
interval: 43800,
renderer: function(date){ return date.format('F Y')},


But when i set the total range to '01/01/2009 00:00:00' - '12/31/2009 23:59:59' and i have an activity which is set for '01/01/2009 06:00:00' - '08/31/2009 23:59:59'.

When i view this, it will show me a double month "January 2009". I have tried to figure out what causes this, but with no success. This is when i set the view to Monthly, when i set it to weekly or daily it will display correctly.

Do you have any suggestions?

Thanks in advance.

Gr,
Michael

bwoody
2 Apr 2009, 6:43 AM
The TimelineModel is currently only setup to handle timelines based on a minute interval. Since the number of minutes in each month is different, you won't be able to get accurate results by changing the interval on the minute timeline.

The getMinuteTimelineConfig function in the TimelineModel object builds a config object that the ScheduleView uses to render the timeline. There will eventually be similar methods for getHourTimelineConfig, getMonthTimelineConfig, etc. These methods will calculate the actual amount of time between a given start date and end date.

Another reason the TimelineModel is written this way is because I plan to allow multiple timelines to be rendered at the same time with one on top of the other. This way, you might have a timeline with increment of minutes and another with increment of days on top of that.

I apologize for posting the control and not having updates recently. Paying work is taking all my time right now! I am using this control in an upcoming project and will get these additional features worked out asap.

There are two changes required to finish the other timelines like the month timeline: The methods in the TimelineModel like getMonthTimelineConfig must be implemented to calculate actual intervals between two dates. The renderTimeline method in the ScheduleView needs some slight modification. It is currently hard-coded to use the minute timeline only, but it can be generalized with only a few tweaks to the code.

Mabion
2 Apr 2009, 10:01 PM
The TimelineModel is currently only setup to handle timelines based on a minute interval. Since the number of minutes in each month is different, you won't be able to get accurate results by changing the interval on the minute timeline.

The getMinuteTimelineConfig function in the TimelineModel object builds a config object that the ScheduleView uses to render the timeline. There will eventually be similar methods for getHourTimelineConfig, getMonthTimelineConfig, etc. These methods will calculate the actual amount of time between a given start date and end date.

Another reason the TimelineModel is written this way is because I plan to allow multiple timelines to be rendered at the same time with one on top of the other. This way, you might have a timeline with increment of minutes and another with increment of days on top of that.

I apologize for posting the control and not having updates recently. Paying work is taking all my time right now! I am using this control in an upcoming project and will get these additional features worked out asap.

There are two changes required to finish the other timelines like the month timeline: The methods in the TimelineModel like getMonthTimelineConfig must be implemented to calculate actual intervals between two dates. The renderTimeline method in the ScheduleView needs some slight modification. It is currently hard-coded to use the minute timeline only, but it can be generalized with only a few tweaks to the code.

Hi bwoody,

Thanks for the quick response!
I understand the situation and I will just be patient then.

I appreciate the effort,
Michael

alexbanda1982
29 Apr 2009, 7:04 AM
This control will help a lot... but I have a trouble with loading data
Please help me!!




This is de code to load

var reader = new Ext.data.ArrayReader({}, [
{name: 'id'},
{name: 'person'},
{name: 'activity'},
{name: 'start'},
{name: 'end'},
{name: 'parent'}
]);

// get the data
var proxy = new Ext.data.HttpProxy({
//where to retrieve data
url: '../langstatics/proceso.asp?task=getHorarioAlumno&periodo=undefined&alumno=', //url to data object (server side script)
method: 'GET'
});

// create the data store.
var store = new Ext.data.Store({
proxy: proxy,
reader: reader

});


This is de data recieved from mi server


[[0, "Lunes", "", "", "", 1],[2, "Técnicas de Programación", "", "1/29/2009 12:30:00", "1/29/2009 15:00:00", 0]]


then I asign this created store


var schedule = new Ext.ux.SchedulePanel({
store: store,
timelineModel: timelineModel,
scheduleModel: scheduleModel,
selModel: new Ext.ux.ScheduleRowSelectionModel({singleSelect:false}),
frame:true,
layout:'fit',

height: 400
});

Then I get this message:

Webpage Script Errors

User Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; MEGAUPLOAD 1.0; InfoPath.2; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Timestamp: Wed, 29 Apr 2009 14:58:08 UTC


0.
Message: Invalid argument.
Line: 15
Char: 11500
Code: 0
URI: http://192.168.103.15/ext-2.2/examples/Schedule/Schedule.htm

1.
Message: Invalid argument.
Line: 15
Char: 11500
Code: 0
URI: http://192.168.103.15/ext-2.2/examples/Schedule/Schedule.htm




What I'm doing wrong??

PD: Sorry my English:">

Xander75
6 May 2009, 11:50 PM
Hi all,

I'm currently working with this Schedule Control and it works very good! Also i have managed to change the view to Daily, Weekly, Monthly and Yearly. This I achief by changing the interval, for instance:



<!-- monthly -->
interval: 43800,
renderer: function(date){ return date.format('F Y')},
But when i set the total range to '01/01/2009 00:00:00' - '12/31/2009 23:59:59' and i have an activity which is set for '01/01/2009 06:00:00' - '08/31/2009 23:59:59'.

When i view this, it will show me a double month "January 2009". I have tried to figure out what causes this, but with no success. This is when i set the view to Monthly, when i set it to weekly or daily it will display correctly.

Do you have any suggestions?

Thanks in advance.

Gr,
Michael

Michael,

As this is in very early stages of development and is really only available for the Minute timeline I also had the problem of duplicate columns when using this to display Years, for example it would display 2012 twice.

I required this to display 40 years from the current year, so to do this I manipulated the minute timeline model to fit the requirements for this by factoring in the extra minutes for the leap year, depending on when the next leap year falls from the current year.

This works perfectly well for my requirements so I hope this helps for the year view.



// Get the Current Year and Future Year 40 years out
var d = new Date();
var thisYear = d.getFullYear();
var futureYear = thisYear + 38 // Strangely this requires to be plus 38!?!?

// Set interval to be 525600 + 1440, this is the number of minutes in a 'NON' Leap Year plus 1 day in minutes
var interval = 527040;

// If the Current Year is not a Leap Year then get the correct interval to set up the Schedule control
var isLeap = new Date(thisYear,1,29).getDate() == 29;
if (isLeap == false){
// Get the next Leap Year
var leapYear = thisYear + (4 - (thisYear%4));

// Get the correct interval
switch (leapYear - thisYear){
case 1: interval = 526680; break;
case 2: interval = 526320; break;
case 3: interval = 525960;
}
}

//setup a timeline Model, currently only available in Minutes!
var timelineModel = new Ext.ux.TimelineModel([{
timeUnit: 'MINUTE',
interval: interval, // 525600 minutes in a year, 360 mins are added to take into account each leap year
renderer: function(date){ return date.format('Y')},
width:50
}]);

//setup a schedule Model
var scheduleModel = new Ext.ux.ScheduleModel({
activityDataIndex: 'activity',
activityRenderer: this.activityRenderer,
endDate: '12/31/'+String(futureYear)+' 23:59:59', // US Date Format!!!
endDateIndex: 'end',
idIndex: 'id',
labelDataIndex: 'label',
labelRenderer: null,
labelWidth: 300,
labelAlign: 'left',
parentDataIndex: 'parent',
startDate: '01/01/'+String(thisYear)+' 00:00:01', // US Date Format!!!
startDateIndex: 'start',
title: 'Recipes',
titleRenderer: null //Title can be rendered as plain text
});

//create a new schedule panel
var schedule = new Ext.ux.SchedulePanel({
store: scheduleStore,
timelineModel: timelineModel,
scheduleModel: scheduleModel,
selModel: new Ext.ux.ScheduleRowSelectionModel({singleSelect:false}),
border: false,
height: 400,
width:800,
tbar: [{
cls: 'x-btn-icon',
iconCls: 'RefreshIcon',
id: 'btnRefreshSchedule',
handler: function(btn){
clickRefreshSchedule(this)
}
}]
});

Mabion
13 May 2009, 3:14 AM
Hi Xander,

Thanks for your reply and example code.
I will implement it in my code, and I'll be patient and wait for the update ;).

Gr,
Michael

alindsay55661
25 May 2009, 8:59 PM
This looks promising. I may be able to contibute but I don't want to over step. Where are you at in development?

moegal
28 Jun 2009, 7:51 AM
Any progress? Any way I can help?

Thanks, Marty

liu_qchao
2 Jul 2009, 12:16 AM
only one Period of time can be at the same row in this SchedulePanel,right?

I think it would be better if it could contain multiple periods of time at the same row

Xouqoa
21 Jul 2009, 2:45 PM
Well done. I built something similar using a bunch of table layouts, but yours is much more responsive. :)

Are you still planning on updating it? I'm going to see about implementing it in my application.

Xouqoa
21 Jul 2009, 8:47 PM
When loading store data from an XHR request, the onDataChange handler in Ext.ux.ScheduleView.js fails with the error "this.refresh is not a function".

Just FYI, might be known.

I might try and figure out how to fix it later, but I'll work around it for now.

bzarzuela
21 Jul 2009, 9:26 PM
This looks promising, I'm looking for something similar for scheduling shifts of people and I'd be interested in finding out how this can handle lots of items on-screen.

mankz
19 Oct 2009, 4:00 AM
This thread may interest you if you need a scheduling/gantt like chart... http://www.extjs.com/forum/showthread.php?t=82377

http://mankz.com/sched.png

kozin
22 Oct 2009, 8:11 AM
now added: support multi activity per row
1 if 'id' field in data is duplicated activityes paint in one row , lables ignored
2 added color field to dataset

fixed:
minutes shift after 13 day of month
view in opera

discussion:
http://www.sql.ru/forum/actualthread.aspx?tid=700486

kozin
28 Oct 2009, 12:11 AM
http://www.extjs.com/forum/attachment.php?attachmentid=16973&d=1256717418
see attachment

stashx
16 Dec 2009, 10:56 AM
Great job

you've got a major bug that almost got me crazy... =P~

File: Ext.ux.TimelineModel.js Line 140-142

You have to change it into:


//setup the object to be returned
var newStartDate = new Date(sd.yy, sd.mm, sd.dd, sd.hh, sd.m);
var newEndDate = new Date(ed.yy, ed.mm, ed.dd, ed.hh, ed.m); //removes seconds, etc that might be present


The bug is that you where passing month instead of minutes at the last parameter of Date object creation and that was producing wrong column headers.

Cheers,
StashX

PS: i'm referring to the version you got on this site: http://charlottesoftwaresystems.com/ext-2.2/examples/schedule/schedule.html

[EDIT:]
PS2: It's working on Ext 3 too :)

kozin
17 Dec 2009, 1:24 AM
after:
schedule.render('schedule-example');

insert:
Ext.QuickTips.init();

Hobbs
28 Jan 2010, 9:09 AM
Great plugin by the way!
But the zip of the new version posted above will not unzip on my machine.
Is this a fault my end or a corrupt file?

moegal
28 Jan 2010, 6:20 PM
common problem with this forum, it does not like IE, try from an alternate browser.

Marty

heimo
28 Jan 2010, 6:38 PM
if this extension is free license?

thanks very much for your help !:>:D:D

mnask79
5 Apr 2010, 11:32 PM
this extension is really good , go on please

ajaxvador
6 Apr 2010, 1:26 AM
Hi,

How to create a Timeunit: 'MONTH' ?

thanks

Andrew Van Duivenbode
18 Nov 2010, 6:53 AM
23407Hi there,

Great work - this is an excellent extension.

I was wondering if anyone has successfully implemented it in a page which has a doctype such as xhtml transitional or strict?

I am currently using it on one page of a large Intranet site and would like to bring the page in line with the rest of the site so our styles are rendered consistently.

Including the xhtml doctype seems to do something with the positioning which stops the expand / contract functionality from working and generally messes up the grid of labels on the left.

marcoaaguiar
23 Nov 2010, 8:12 AM
Well, the refresh function in ScheduleView class was missing, i'm building it. I'm also adding some new features.
I'm not a organized programmer but the main idea is good.
I hope finish the code soon.
When it's done i will post it here

some new features will be:
*edit activities duration in the time line, using a resizable
*insert a new activity using a D&D
*auto-update on store change
*activties wont overlap in the timeline

tinabucur
5 Jan 2011, 8:36 AM
Hi Any news regarding the doctype , as I am facing the same problem ?

marcoaaguiar
21 Jan 2011, 12:47 PM
Well, after a while fighting against javascript here is a functional version.
It took me a lot more time than i tought but here is it
There are somethings missing, but i've to work in other part of the project so this will be in side for a while.
Any question email me


ScheduleActivityHandler:


/**
* @author Marco Aurélio de Aguiar
* marcoaaguiar@gmail.com
* Questions, new version or anything else, just email me.
*/

//
Ext.ux.ScheduleActivityHandler = function(config){
//to initialize pass the a dictionary with
//schedule: the schedulePanel reference
//snap: pixels to be snaped in resizer and slider
//drop_group: drop group that the row drop-target owns

Ext.apply(this, config);
this.initialize();
}
Ext.extend(Ext.ux.ScheduleActivityHandler, Ext.util.Observable, {
initialize: function(){
if (!this.snap) {
this.snap = 5;
}
if (!this.drop_group){
this.drop_group = 'row-dd';
}

this.resizers = {};
this.ddhandlers = {};
this.addDelEvent();
this.createResizers();
this.createDDProxies();
this.createRowDropTarget();
var view = this.schedule.getView();

//view.on('update', this.updateResizers, this);
view.on('refresh', this.updateResizers, this);
},
getRecordData: function (id) {
var records = this.getStoreRecords();
return records[id].data
},
getStoreRecords: function (){
return this.schedule.store.getRange();
},
updateStore: function (data, record_index) {
if (record_index) {
var records = this.getStoreRecords();
records[record_index].data = data;
this.schedule.store.removeAll(true);
this.schedule.store.add(records);
}
},
getRowCount:function (row) {
var records = this.getStoreRecords();
var rowCount = 0;
var data = null;
for (var i=0; i<records.length; i++) {
data = records[i].data;
if (data.id == row+"") {
rowCount++;
}
};
return rowCount
},

removeStore: function (id) {
var view = this.schedule.getView();
var data = this.getRecordData(id);
var row = data.id;
var count = this.getRowCount(row);
if (count > 1) {
this.schedule.store.removeAt(id);
}
else {
data.start = "";
data.end = "";
this.updateStore(data,id)
}
},
addStore: function (new_data) {
var records = this.getStoreRecords();
var id = null;
for (var i = 0; i < records.length; i++) {
var data = records[i].data;
if (data.id != new_data[0]) {
continue
}
new_data[1] = data.person;
if (data.start == "") {

this.updateStore(this.arrayToStoreKeys(new_data), i);
return true
}
var start = new Date(new_data[3]);
if (new Date(data.start)<start){
if (i<records.length-1) {
var nextdata = records[i+1].data;
if (nextdata.id == new_data[0]) {
if (start < new Date(nextdata.start)) {
id = i + 1;
break;
}
}
else {
id = i+1;
break
}
}
else {
id = i+1;
break;
}
}
else {
id = i;
break;
}
}
if (id==null) {id = records.length-1}

var record_data = this.arrayToStoreKeys(new_data)

var record = new Ext.data.Record(record_data);

this.schedule.store.insert(id,record);
var records = this.getStoreRecords();
},
arrayToStoreKeys: function (array) {
var keys = this.schedule.store.fields.keys;
var record_data = {};
for (var i=0; i<keys.length; i++) {
record_data[keys[i]] = array[i];
};
return record_data;
},
isAvailable: function (time, row) {
var records = this.getStoreRecords();

for (var i=0; i<records.length; i++) {
var data = records[i].data;
if (data.id != row+"" || data.start == ""){
continue
}
var start = new Date(data.start);
var end = new Date(data.end);
if (start<time && time< end) {
return false;
}
};
return true;
},
convertPositionToTime: function (posX, relative) {
if (relative == null) {
relative = true;
}
var sd = this.schedule.scheduleModel.startDate;
var time = new Date(sd);
var ppm = this.schedule.timelineModel.getPixelsPerMinute();
var pos = posX;
if (!relative) {
var timelineId = this.schedule.getView().timelineId;
var el = Ext.get(timelineId);

var offset = el.getX();
pos = pos-offset;
}
time.setUTCMinutes(time.getUTCMinutes() + pos/ppm);
return time;
},
roundTime:function (time) {
var minutes = time.getUTCMinutes();
var new_minutes = minutes- minutes%this.snap;
time.setUTCMinutes(new_minutes);
return time;
},
updateResizers: function(e){
this.destroyResizers();
this.destroyDDProxies();
this.destroyRowDropTarget();
this.createResizers();
this.createDDProxies();
this.createRowDropTarget();
//this.createToolbar();
},
addDelEvent: function () {
Ext.EventManager.on(document,'keypress', function (e) {
if (e.keyCode == 46) {
if (this.selected) {
this.removeStore(this.selected)
}
}
},this)
Ext.EventManager.on(document,'click', function (e) {
var el = Ext.get(e.target)
var id = this.schedule.getView().findActivityIndex(el);
if (id) {
this.selected = id;
}
else this.selected = null;
}, this)
},
createToolbar:function () {
this.toolbar = {
id: 'toolbar',
region: 'north',
elements: 'tbar,body',
title: '',
tbar:[{
text: 'Nova Configuracao',
icon: 'shared\icons\fam\add.gif'
},{
text: 'Novo Plano',
iconCls: 'silk-user'
},'-',
{
text: 'Editor de Planos',
iconCls: 'shared\icons\fam\application_view_list.gif'
},'-',{
text: 'Excluir',
iconCls: 'add'
}]
}
this.schedule.add(this.toolbar);
},
createResizers: function(){
this.resizers = {};
var view = this.schedule.getView();
var ids = view.ids;
var actv = Ext.select(".x-schedule-activity");
Ext.each(actv.elements, function(el, index, elements){
var j = index;
var id = el.id;
//Make Resiszers
resizerParams = {
id: id,
i: this.schedule.getView().getActivityIndex(el),
handles: 'e w',
minWidth: 15,
widthIncrement: this.snap,
minHeight: 50,
handler:this,
updateLimits: function(){
var records = this.handler.schedule.store.getRange();
var data = this.handler.getRecordData(this.i);
var ppm = this.handler.schedule.timelineModel.getPixelsPerMinute();
var schedule = this.handler.schedule;
//Right Limit
if (this.i > 1 && (this.i < records.length - 1) && (records[this.i].data['id'] == records[this.i + 1].data['id'])) {
var start = new Date(data['start']);
var next_start = new Date(records[this.i + 1].data['start']);
var dur = start.getElapsed(next_start) / 60000;
this.rigthLimit = dur * ppm;
}
else {
var start = new Date(data['start'])
var end = new Date(schedule.scheduleModel.endDate);
var dur = start.getElapsed(end) / 60000;

this.rigthLimit = dur * ppm;
}
if ((this.i > 1) && (records[this.i].data['id'] == records[this.i - 1].data['id'])) {
var start = new Date(data['start']);
var last_end = new Date(records[this.i - 1].data['end']);
var dur = last_end.getElapsed(start) / 60000;
this.leftLimit = dur * ppm;
}
else {
var start = new Date(data['start'])
var first_start = new Date(schedule.scheduleModel.startDate);
var dur = start.getElapsed(first_start) / 60000;
this.leftLimit = dur * ppm;
}
},
listeners: {
'beforeresize': function(){
this.updateLimits();
},
'resize': function(r, w, h, e){
var ppm = this.handler.schedule.timelineModel.getPixelsPerMinute();
var el = Ext.get(this.el);

var left = this.el.getAttribute("style")
var index = left.indexOf('left')

left = left.slice(index + 4)

var index2 = left.indexOf('px')
left = left.slice(1, index2)
left = parseInt(left);

var sd = this.handler.schedule.timelineModel.startDate;
var newStart = new Date(sd);
newStart.setUTCMinutes(left / ppm)
var newEnd = new Date(newStart)
newEnd.setUTCMinutes(newEnd.getUTCMinutes() + w/ppm);

//Update the schedule Store
var data = this.handler.getRecordData(this.i);
data['start'] = newStart.toString();
data['end'] = newEnd.toString();

this.handler.updateStore(data, this.i)
this.updateLimits();
}
},
onMouseMove: function(e){
if (this.enabled && this.activeHandle) {
try {// try catch so if something goes wrong the user doesn't get hung
if (this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
return;
}

//var curXY = this.startPoint;
var curSize = this.curSize || this.startBox, x = this.startBox.x, y = this.startBox.y, ox = x, oy = y, w = curSize.width, h = curSize.height, ow = w, oh = h, mw = this.minWidth, mh = this.minHeight, mxw = this.maxWidth, mxh = this.maxHeight, wi = this.widthIncrement, hi = this.heightIncrement, eventXY = e.getXY(), diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])), //Dif between box o size and min(minX, enventX)
diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])), pos = this.activeHandle.position, tw, rxl = this.rigthLimit, lxl = this.leftLimit, th;

switch (pos) {
case 'east':
w += diffX;
w = Math.min(Math.max(mw, w), mxw);

w = Math.min(rxl, w);
break;
case 'south':
h += diffY;
h = Math.min(Math.max(mh, h), mxh);
break;
case 'southeast':
w += diffX;
h += diffY;
w = Math.min(Math.max(mw, w), mxw);
h = Math.min(Math.max(mh, h), mxh);
break;
case 'north':
diffY = this.constrain(h, diffY, mh, mxh);
y += diffY;
h -= diffY;
break;
case 'west':
diffX = this.constrain(w, diffX, mw, mxw);

diffX = Math.max(diffX, -lxl);
x += diffX;
w -= diffX;

break;
case 'northeast':
w += diffX;
w = Math.min(Math.max(mw, w), mxw);
diffY = this.constrain(h, diffY, mh, mxh);
y += diffY;
h -= diffY;
break;
case 'northwest':
diffX = this.constrain(w, diffX, mw, mxw);
diffY = this.constrain(h, diffY, mh, mxh);
y += diffY;
h -= diffY;
x += diffX;
w -= diffX;
break;
case 'southwest':
diffX = this.constrain(w, diffX, mw, mxw);
h += diffY;
h = Math.min(Math.max(mh, h), mxh);
x += diffX;
w -= diffX;
break;
}

var sw = this.snap(w, wi, mw);
var sh = this.snap(h, hi, mh);
if (sw != w || sh != h) {
switch (pos) {
case 'northeast':
y -= sh - h;
break;
case 'north':
y -= sh - h;
break;
case 'southwest':
x -= sw - w;
break;
case 'west':
x -= sw - w;
break;
case 'northwest':
x -= sw - w;
y -= sh - h;
break;
}
w = sw;
h = sh;
}

if (this.preserveRatio) {
switch (pos) {
case 'southeast':
case 'east':
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
w = ow * (h / oh);
break;
case 'south':
w = ow * (h / oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w / ow);
break;
case 'northeast':
w = ow * (h / oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w / ow);
break;
case 'north':
tw = w;
w = ow * (h / oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w / ow);
x += (tw - w) / 2;
break;
case 'southwest':
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
tw = w;
w = ow * (h / oh);
x += tw - w;
break;
case 'west':
th = h;
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
y += (th - h) / 2;
tw = w;
w = ow * (h / oh);
x += tw - w;
break;
case 'northwest':
tw = w;
th = h;
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
w = ow * (h / oh);
y += th - h;
x += tw - w;
break;

}
}


this.proxy.setBounds(x, y, w, h);
if (this.dynamic) {
this.resizeElement();
}
}
catch (ex) {
}
}
},
}
this.resizers[id] = new Ext.Resizable(id, resizerParams);
this.resizers[id].updateLimits();
},this)
},
destroyResizers: function () {
for (key in this.resizers) {
this.resizers[key].destroy(true);
}
},
destroyDDProxies: function () {
for (key in this.ddhandlers) {
this.ddhandlers[key].destroy();
}
},
createDDProxies: function () {
this.ddproxies = {};
this.ddhandlers = {};
var actv = Ext.select(".x-schedule-activity")
Ext.each(actv.elements, function(el, index, elements){
this.ddproxies[el.id] = new Ext.dd.DDProxy(el.id, 'group');
Ext.apply(this.ddproxies[el.id], {
i: this.schedule.getView().getActivityIndex(el),
// runs on drag start
// create nice proxy and constrain it to body
rightLimit: 100,
leftLimit: 200,
handler: this,
startDrag: function(x, y){
var dragEl = Ext.get(this.getDragEl());
var el = Ext.get(this.getEl());
this.updateLimits();
el.hide()
dragEl.applyStyles({
"border-width": '1px',
'border-style': 'dashed',
"text-align": 'center',
'opacity': 0.6
});
dragEl.update(el.dom.innerHTML);
dragEl.addClass('dd-proxy');



}, // eo function startDrag
b4Drag: function(e) {
this.setDragElPos(e.getPageX(), e.getPageY());
this.updateLimits();
},
afterDrag: function(){
var el = Ext.get(this.getEl());
var dragEl = Ext.get(this.getDragEl());
var schedule = this.handler.schedule;

el.setLeft(el.getLeft(true));
var ppm = schedule.timelineModel.getPixelsPerMinute();
var left = el.getAttribute("style")
var left_index = left.indexOf('left')

left = left.slice(left_index + 4)

var px_index = left.indexOf('px')
left = left.slice(1, px_index)
left = parseInt(left);

var sd = schedule.timelineModel.startDate;
var newStart = new Date(sd);
newStart.setUTCMinutes(left / ppm);

var data = this.handler.getRecordData(this.i);

data['start'] = newStart.toString();
var newEnd = new Date(newStart)

newEnd.setUTCMinutes(newEnd.getUTCMinutes() + el.getWidth());
data['end'] = newEnd.toString();

this.handler.updateStore(data, this.i);
}, // eo function afterDrag
updateLimits: function(){
//Update this maxWidth
var el = Ext.get(this.getEl());
var dragEl = Ext.get(this.getDragEl());
schedule = this.handler.schedule;
records = schedule.store.getRange();
data = records[this.i].data;
ppm = schedule.timelineModel.getPixelsPerMinute();

//Right Limit
if (this.i > 1 && (this.i < records.length - 1) && (records[this.i].data['id'] == records[this.i + 1].data['id'])) {
end = new Date(data['end']);
next_start = new Date(records[this.i+1].data['start']);
dur = end.getElapsed(next_start) / 60000;

if (end>next_start) {
dur = -dur
}
this.rightLimit = dur * ppm;
}
else {
end = new Date(data['end'])
schedule_end = new Date(schedule.scheduleModel.endDate);
dur = end.getElapsed(schedule_end) / 60000;
this.rightLimit = dur * ppm;
}


if ((this.i > 1) && (records[this.i].data['id'] == records[this.i - 1].data['id'])) {
start = new Date(data['start']);
last_end = new Date(records[this.i - 1].data['end']);
dur = last_end.getElapsed(start) / 60000;
this.leftLimit = dur * ppm;
}
else {
start = new Date(data['start'])
first_start = new Date(schedule.scheduleModel.startDate);
dur = start.getElapsed(first_start) / 60000;
this.leftLimit = dur * ppm;
}
this.clearConstraints();
this.clearTicks();
this.setYConstraint(0, 0, 0);
timeline = Ext.get(this.handler.schedule.getView().timelineId);
offset = timeline.getX();

this.minX = el.getX() - this.leftLimit;
this.maxX = el.getX() + this.rightLimit;
this.setXTicks(offset, this.handler.snap);
newXTicks = []
for (var i=0; i<this.xTicks.length; i++) {
if (this.xTicks[i]<=this.maxX && this.xTicks[i] >=this.minX || this.xTicks[i] ==offset) {
newXTicks.push(this.xTicks[i]);
}
};
if (newXTicks[0]!= this.minX){newXTicks[0]= this.minX;}
if (newXTicks[newXTicks.length-1]!= this.maxX){newXTicks[newXTicks.length-1]= this.maxX;}
this.xTicks = newXTicks;
}
}) // eo apply
}, this)
},
createRowDropTarget: function () {
this.droptarget = {};
var actv = Ext.select(".x-schedule-row");
Ext.each(actv.elements, function(el, index, elements){
var row_id = parseInt(this.schedule.getView().findRowIndex(el));
this.droptarget[el.id] = new Ext.dd.DropTarget(el,{
// must be same as for schedule
ddGroup:this.drop_group,
row: index+1,
handler: this,
// what to do when user drops a node here
notifyDrop:function(dd, e, node) {
var start = this.handler.convertPositionToTime(e.xy[0],false);
start = this.handler.roundTime(start);

if (this.handler.isAvailable(start, this.row)) {
var label = "";
var activity = node.node.attributes.activity+"";
var end = new Date(start)
end.setUTCMinutes(end.getUTCMinutes() + 15);

var color = node.node.attributes.color+"";
var data = [this.row+"", label, activity, start+"", end+"", '1', color]
this.handler.addStore(data)
}

return true;
} // eo function notifyDrop
});
},this);
},
destroyRowDropTarget: function () {
for (key in this.droptarget) {
this.droptarget[key].destroy();
}
}
});
I had to fix some think in the ScheduleView, the russians forgot somethings, so I did it. By the way they did a good job.

ScheduleView:



/**
* @class Ext.ux.ScheduleView
* @extends Ext.util.Observable
* <p>This class encapsulates the user interface of an {@link Ext.ux.SchedulePanel}.
* Methods of this class may be used to access user interface elements to enable
* special display effects. Do not change the DOM structure of the user interface.</p>
* <p>This class does not provide ways to manipulate the underlying data. The data
* model of a Schedule is held in an {@link Ext.data.Store}.</p>
* @constructor
* @param {Object} config
*/
Ext.ux.ScheduleView = function(config){

Ext.apply(this, config);
// These events are only used internally by the schedule components
this.addEvents(
/**
* @event beforerowremoved
* Internal UI Event. Fired before a row is removed.
* @param {Ext.grid.GridView} view
* @param {Number} rowIndex The index of the row to be removed.
* @param {Ext.data.Record} record The Record to be removed
*/
"beforerowremoved",
/**
* @event beforerowsinserted
* Internal UI Event. Fired before rows are inserted.
* @param {Ext.grid.GridView} view
* @param {Number} firstRow The index of the first row to be inserted.
* @param {Number} lastRow The index of the last row to be inserted.
*/
"beforerowsinserted",
/**
* @event beforerefresh
* Internal UI Event. Fired before the view is refreshed.
* @param {Ext.grid.GridView} view
*/
"beforerefresh",
/**
* @event rowremoved
* Internal UI Event. Fired after a row is removed.
* @param {Ext.grid.GridView} view
* @param {Number} rowIndex The index of the row that was removed.
* @param {Ext.data.Record} record The Record that was removed
*/
"rowremoved",
/**
* @event rowsinserted
* Internal UI Event. Fired after rows are inserted.
* @param {Ext.grid.GridView} view
* @param {Number} firstRow The index of the first inserted.
* @param {Number} lastRow The index of the last row inserted.
*/
"rowsinserted",
/**
* @event rowupdated
* Internal UI Event. Fired after a row has been updated.
* @param {Ext.grid.GridView} view
* @param {Number} firstRow The index of the row updated.
* @param {Ext.data.record} record The Record backing the row updated.
*/
"rowupdated",
/**
* @event refresh
* Internal UI Event. Fired after the GridView's body has been refreshed.
* @param {Ext.grid.GridView} view
*/
"refresh"
);

Ext.ux.ScheduleView.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.ScheduleView, Ext.util.Observable, {
/**
* @cfg {String} emptyText Default text to display in the grid body when no rows are available (defaults to '').
*/
/**
* @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
*/
/**
* The amount of space to reserve for the scrollbar (defaults to 19 pixels)
* @type Number
*/
scrollOffset: 19,
/**
* The width of the area reserved for resizing the label width (defaults to 2 pixels)
* @type Number
*/
resizerWidth: 2,
/**
* The height of a line/row in the schedule (defaults to 21 pixels)
* @type Number
*/
lineHeight: 21,
/**
* @cfg {String} activitySelector The selector used to find activities internally
*/
activitySelector: '.x-schedule-activity',
/**
* @cfg {Number} activitySelectorDepth The number of levels to search for activities in event delegation (defaults to 4)
*/
activitySelectorDepth: 4,
/**
* @cfg {String} activitySelector The selector used to find activities internally
*/
labelSelector: '.x-schedule-lc',
/**
* @cfg {Number} labelSelectorDepth The number of levels to search for labels in event delegation (defaults to 4)
*/
labelSelectorDepth: 4,

//internal used to determine if the dataset is sorted for a hierarchical view
dsSorted: false,

//internal used for the width of the expand/collapse icon in the tree
treeNodeWidth: 18,


/*************************************************************
control initalization
*************************************************************/

// called by the SchedulePanel to initialize the templates, data and UI
init: function(schedule){
this.schedule = schedule;
this.initTemplates();
this.initData(schedule.store);
this.tm = schedule.timelineModel;
this.sm = schedule.scheduleModel;
this.labelWidth = this.sm.labelWidth;
this.initUI(schedule);
},

// define the templates that are used to generate the schedule's UI
initTemplates : function(){
var ts = this.templates || {};
if(!ts.master){
ts.master = new Ext.Template(
'<div class="x-schedule" hidefocus="true">',
'<div class="x-schedule-sidePanel" style="position: absolute; top:0; left:0; width:{labelWidth};" >',
'<div class="x-schedule-title" ><div class="x-schedule-title-inner" style="width:{titleWidth};">{title}</div><div class="x-schedule-resizer" style="left:{titleWidth};"></div></div>',
'<div class="x-schedule-label"><div class="x-schedule-label-inner" style="width:{labelWidth};"><div class="x-schedule-label-offset">{label}</div></div><div class="x-clear"></div></div>',
"</div>",
'<div id={timelineId} class="x-schedule-timeline" style="left:{timelinePosition};">',
'<div class="x-schedule-viewport">',
'<div class="x-schedule-header" ><div class="x-schedule-header-inner"><div class="x-schedule-header-offset">{timelineheader}</div></div><div class="x-clear"></div></div>',
'<div class="x-schedule-scroller"><div class="x-schedule-body" >{timelinebody}</div><a href="#" class="x-schedule-focus" tabIndex="-1"></a><div class="x-schedule-activities" >{activities}</div><div class="x-schedule-ct" style="left: {ctLeft}; background-color: {ctColor};"></div> </div>',
"</div>",
"</div>",
'<div class="x-schedule-resize-marker"> </div>',
'<div class="x-schedule-resize-proxy"> </div>',
"</div>"
);
}

if(!ts.title){
ts.title = new Ext.Template('{title}');
}

if(!ts.header){
ts.header = new Ext.Template(
'<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
'<thead><tr class="x-schedule-hd-row">{cells}</tr></thead>',
"</table>"
);
}

if(!ts.hcell){
ts.hcell = new Ext.Template(
'<td class="x-schedule-hd-cell x-schedule-td-{id}" style="{style}" {colspan}><div {tooltip} class="x-schedule-hd-inner x-schedule-hd-{id}" unselectable="on" style="{istyle}">',
'{value}</div></td>'
);
}

if(!ts.label){
ts.label = new Ext.Template('<div class="x-schedule-lc x-schedule-lc-{id} {alt}" style="{style} height:{height};">{treeNode}<div class="x-schedule-lc-inner" style="left:{lleft};">{value}</div></div>');
}

if(!ts.treeNode){
ts.treeNode = new Ext.Template('<div class="x-schedule-lc-tree x-schedule-lc-tree-{tree}" style="left:{tleft}; "> </div>');
}


if(!ts.bstripe){
ts.bstripe = new Ext.Template(
'<div class="x-schedule-bstripe x-schedule-bstripe-{id} {css}" style="left: {left}; width: {width}; {style}">{content}</div>'
);
}

if(!ts.row){
ts.row = new Ext.Template(
'<div class="x-schedule-row x-schedule-row-{id}" style="height: {height};">{activities}</div>'
);
}

if(!ts.activity){
ts.activity = new Ext.Template(
'<div class="x-schedule-activity x-schedule-activity-{id}" id={id} idIndex={idIndex} start="{start}" style="left:{left}; width: {width}; height: {height}; {cstyle}">',
'<div {tooltip} class="x-schedule-activity-inner {cls}" style="height: {iheight}; {style}">{value}</div>',
'</div>'
);
}

for(var k in ts){
var t = ts[k];
if(t && typeof t.compile == 'function' && !t.compiled){
t.disableFormats = true;
t.compile();
}
}

this.templates = ts;
this.actRe = new RegExp("x-schedule-activity-([^\\s]+)", ""); //activity regular expression
this.tsRe = new RegExp("x-schedule-td-([^\\s]+)", ""); //timeline segment regular expression
this.lRe = new RegExp("x-schedule-lc-([^\\s]+)", ""); //label regular expression
this.rowRe = new RegExp("x-schedule-row-([^\\s]+)", ""); //timeline row regular expression

},

// disconnects event handlers from an existing dataStore and adds event handlers to the store passed in
initData : function(dataStore){
if(this.ds){
this.ds.un("load", this.onLoad, this);
this.ds.un("datachanged", this.onDataChange, this);
this.ds.un("add", this.onAdd, this);
this.ds.un("remove", this.onRemove, this);
this.ds.un("update", this.onUpdate, this);
this.ds.un("clear", this.onClear, this);
}
if(dataStore){
dataStore.on("load", this.onLoad, this);
dataStore.on("datachanged", this.onDataChange, this);
dataStore.on("add", this.onAdd, this);
dataStore.on("remove", this.onRemove, this);
dataStore.on("update", this.onUpdate, this);
dataStore.on("clear", this.onClear, this);
}
this.ds = dataStore;
},

//Connect event handlers to events on the SchedulePanel
//(SchedulePanel) sp The schedule parent that owns this view
initUI : function(sp){
//sp.on("headerclick", this.onHeaderClick, this);

if(sp.trackMouseOver){
sp.on("mouseover", this.onRowOver, this);
sp.on("mouseout", this.onRowOut, this);
}
},

/*************************************************************
Rendering
*************************************************************/

// called by the SchedulePanel to render the UI
render : function(){
//perform any pre-processing based on user configuration if needed here
this.renderUI();
},

// renders the control
renderUI : function() {
//generate the html for each portion of the control
var title = this.renderTitle();
var timeline = this.renderTimeline();
//var timelineBody = this.renderTimelineBody();
var label = this.renderLabels();
var activities = this.renderActivities();
this.timelineId = this.schedule.id+"-timeline";
//generate the control's html by sending each portion to the master template
var controlHtml = this.templates.master.apply({
title: title,
timelineheader: timeline.timelineheader,
timelinebody: timeline.timelinebody,
label: label,
labelWidth: this.sm.labelWidth,
titleWidth: this.sm.labelWidth - this.resizerWidth,
timelineWidth: this.timelineWidth,
timelinePosition: this.sm.labelWidth + 1, //leave space for a 1px border
timelineId: this.timelineId,
activities: activities,
ctLeft: -1, //current time marker
ctColor: '#00BB00' //green
});
//stopit();
//set the controls html
this.schedule.getScheduleEl().dom.innerHTML = controlHtml;
//initialize the control's elements
this.initElements();

//add event handlers, etc to the rows
this.processRows();

this.scroller.on('scroll', this.syncScroll, this);

//initalize the current time indicator
this.updateCurrentTimeLocation();
this.startCurrentTimeUpdater();

//Create a drag zone for resizing the label width
new Ext.ux.ScheduleView.SplitDragZone(this.schedule, this.resizer.dom);
},


renderTitle : function() {
var sm = this.sm, //schedule model
ts = this.templates, //templates
tt = ts.title; //title template

var title = sm.title;
if(sm.titleRenderer){
title = sm.titleRenderer(title);
}

return tt.apply({title: title});
},

//This function is currently hard-coded to use a minute timeline. This function should be re-written to
//get an arbitrary number of timelines from the timeline model and render them
renderTimeline : function() {

var tm = this.tm, //timeline model
sm = this.sm, //schedule model
ts = this.templates, //templates
sd = this.tm.startDate, //schedule start date
ed = this.tm.endDate; //schedule end date

var tb = [[],[],[],[],[]]; //timeline buffer holds the html markup for each of the 5 possible timelines

//get the minute timeline config
var mc = tm.getMinuteTimelineConfig(sm.startDate, sm.endDate);

var hcb = [], //header cell buffer
bcb = [], //body cell buffer
hc = {}, //header cell config object
bc = {}, //body cell config object
sd, //start date
cs, //colspan
tw = 0, //total width of timeline - calculated during rendering
cw = 0; //cumulative width

for(var i = -1; i<mc.nextDateCallCount; i++){ //start at -1 to account for the start date
if(i == -1){ //start date
sd = mc.startDate;
cs = mc.startSpan;
}
else if(i == mc.nextDateCallCount - 1){ //last date
sd = mc.nextDateFn();
cs = mc.lastDateSpan;
}
else{ //all other dates
sd = mc.nextDateFn();
cs = mc.nextDateSpan;
}

//configure the object representing the header cell
hc.id = 'tl-min-' + sd.format('mdYHi'); //month, day, year, hour, min
hc.style = 'width:'+ (mc.unitWidth + (Ext.isGecko || Ext.isOpera ? -2 : 0)) +'px;overflow:hidden; ';
hc.colspan = cs == 1 ? '' : 'colspan="' + cs + '"';
hc.tooltip = 'ext:qtip="' + sd.format('m/d/Y H:i') + '"';
hc.istyle = '';
hc.value = tm.timelines[0].renderer ? tm.timelines[0].renderer(sd) : sd.getMinutes();

hcb[hcb.length] = ts.hcell.apply(hc); //add the cell
tw += mc.unitWidth;

//configure the object representing the body stripe
bc.id = 'body-' + sd.format('mdYHi'); //month, day, year, hour, min
bc.width = mc.unitWidth;
bc.left = ((i + 1) * bc.width) + 1; //add pixel for border
bc.style = 'border-right: 1px solid #EBEFF2; ';
bc.style += (i % 2 == 0) ? 'background-color: #FAFAFA;' : '';
// var day = sd.getDay();
// if(day === 0 || day === 6) bc.style += ' background-color: #f1f5f9;';

bcb[bcb.length] = ts.bstripe.apply(bc); //add the cell

}
this.timelineWidth = tw;

var returnObject = {
timelineheader: ts.header.apply({cells: hcb.join(""), tstyle:'width:'+tw+'px;'}),
timelinebody: bcb.join("")
}

return returnObject;

},


renderLabels: function() {
if(this.ds.getCount() < 1){
return "";
}

var sm = this.sm, //schedule model
ds = this.ds, //data store
ts = this.templates, //templates
startRow = 0,
endRow = ds.getCount()-1,
rs = ds.getRange(startRow, endRow), //get the records from the store
pi = sm.parentDataIndex, //parent index
ii = sm.idIndex, //id index
n = false, //nesting disabled (enabled later if detected in data store)
ind = -1, //indention level starts at -1. moves to 0 for first root level node
hc = false, //has children - indicates that the current node has child nodes
ps = [], //parent node stack - (used when determining indention level)
r, //current row
lo = {}, //label object - used to parameterize label template
lb = [], //label buffer - holds all the rendered labels
rc = 0, //row count
nx, //next record index
tn, //tree node object - used to parameterize treeNode template
tnw = this.treeNodeWidth; //width of the treenode icon

//If the Store Model specifies a parentDataIndex, assume we are using a tree layout for labels
if(pi && this.schedule.enableNesting === true) {
n = true;
//ensure that the data store is sorted correctly
if(this.dsSorted == false){
this.treeSort();
}
}

var previd = ''; // store id field value for ignoring duplicates
for(var j = 0, len = rs.length; j < len; j++){
r = rs[j];

if (previd != r.data[sm.idIndex]) // ignore rows with duplicate id
{
previd = r.data[sm.idIndex];

rc++;
//handle indention and expand/collapse if enabled
if(n){
if(ps.length == 0) { //parent node stack is empty
ps.push(r);
ind++;
}
else{
//parent node should be in the stack.
var found = false;
for(var k=ps.length-1; k>=0; k--){
if(ps[k].data[ii] == r.data[pi]){ //if node is current node's parent
ps.push(r);
ind++;
found = true;
break;
}
else{
ps.pop();
ind--;
}
}

if(!found){
//if we get here, the parent was not present, so this is a root node
ps.push(r);
ind++;
}
}
}

//see if this record has children
nx = j+1;
if(nx != len && rs[nx].data[pi] == r.data[ii]){
hc = true;
}
else{
hc = false;
}

//build the label object
lo.id = j; //set the id to the index of the record (NOTE: fix this if using a start point other than 0)
lo.value = sm.labelRenderer ? sm.labelRenderer(r.data[sm.labelDataIndex]) : r.data[sm.labelDataIndex];
lo.style = '';
lo.height = this.lineHeight;
if(lo.value == undefined || lo.value === "") lo.value = " ";

//add a tree expand/collapse icon if the node has children
if(hc){
tn = {
tree : 'col',
tleft: ind * tnw //indention level * treeNode width
};
lo.lleft = tn.tleft + tnw;
lo.treeNode = ts.treeNode.apply(tn);
}
else{
lo.treeNode = null;
lo.lleft = (ind * tnw) + tnw;
}

lb[lb.length] = ts.label.apply(lo);
}
} // end dulicate row if


this.bodyHeight = rc * this.lineHeight;

return lb.join("");
},
renderActivities : function() {
if(this.ds.getCount() < 1){
return "";
}
this.ids = [];
var tm = this.tm, //timeline model
sm = this.sm, //schedule model
ds = this.ds, //data store
ts = this.templates, //templates
startRow = 0,
endRow = ds.getCount()-1,
rs = ds.getRange(startRow, endRow),
r, //current row
ha, //has activity
ao = {}, //activity object
ab = [], //label buffer
id, //id of the activity
value, //holds the value of the activity
p, //position of activity
lh = this.lineHeight,
pm = tm.getPixelsPerMinute(), //pixels per minute
tls = tm.startDate, //timeline start date
em, //minutes elapsed between timeline start date and activity start date
am, //minutes elapsed between activity start and activity end date
sd, //activity start date
ed, //activity end date
id;

rowac = [];
var previd = ''

rowIndex = 0;
for(var j = 0, len = rs.length; j < len; j++){

r = rs[j];

sd = r.data[sm.startDateIndex];
ed = r.data[sm.endDateIndex];
ha = false;

if (previd != r.data[sm.idIndex] && j!=0 ) // if id no duplicated build prev row
{
var row = {};
row.id = rowIndex++;
row.height = lh+ (Ext.isGecko || Ext.isOpera ? 1 : 0) + 'px';
row.activities = rowac.length !=0 ? rowac.join('') : '';
ab[ab.length] = ts.row.apply(row);
rowac = [];
}
previd = r.data[sm.idIndex];
//if start or end date is not defined, continue
if(sd && sd != '' && ed && ed != ''){

ha = true; //indicates this row has an activity

if(!Ext.isDate(sd)) sd = new Date(sd);
if(!Ext.isDate(ed)) ed = new Date(ed);

//multiple activities, change the -0 to be the index of the activity in the row
ao.id = (row.id+1)+"-"+j; //get id from datastore or use the loop counter
ao.top = j * lh;

em = tls.getElapsed(sd) / 60000; //minutes between timeline start and activity start
ao.left = em * pm;
am = sd.getElapsed(ed) / 60000; //activity duration in minutes
ao.width = (am * pm)+'px';
if(Ext.isGecko || Ext.isOpera ){ ao.height = (lh-1)+'px'; ao.iheight = (lh-2)+'px'; }else{ ao.height = lh+'px'; ao.iheight = (lh-1)+'px'; }
// if(Ext.isOpera){ ao.height = (lh+2)+'px'; ao.iheight = (lh+1)+'px'; }
ao.cstyle = 'text-align: center'; //container style
ao.cls = r.data[sm.colorDataIndex]!= '' ? 'act-' + r.data[sm.colorDataIndex] : 'act-blue' ; //'act-blue';
value = r.data[sm.activityDataIndex];
ao.value = sm.activityRenderer ? sm.activityRenderer(value, r, j) : value;
if(ao.value == undefined || ao.value === "") ao.value = " ";
ao.tooltip = 'ext:qtip="' + value + '"';


rowac[rowac.length] = ts.activity.apply(ao);

}
//Generate the row object
}
var row = {};
row.id = rowIndex++;
row.height = lh+ (Ext.isGecko ? 1 : 0) + 'px';
row.activities = rowac.length !=0 ? rowac.join('') : '';
ab[ab.length] = ts.row.apply(row); // build last row

return ab.join("");
// return '<div style="position: absolute; left:100; top:0; width:200px;">' +
// ' <div style="background-color:Red; color: white; margin-top: 3px; margin-bottom: 3px; margin-left:1px;">Test</div>' +
// '</div>' +
// '<div style="position: absolute; left:0; top:19; width:150;">' +
// ' <div style="background-color:Blue; color: white; margin-top: 3px; margin-bottom: 3px; margin-left:1px;">On Time</div>' +
// '</div>';

},

// create references to the dom elements that contain the major portions of the control
initElements : function(){
var E = Ext.Element;

var scheduleElement = this.schedule.getScheduleEl().dom.firstChild; //x-schedule
var scheduleChildren = scheduleElement.childNodes;
this.el = new E(scheduleElement,true);

//Get the side panel elements
this.sidePanel = new E(scheduleChildren[0],true); //sidepanel that holds the title area and the label area
this.mainTitle = new E(this.sidePanel.dom.childNodes[0],true); //title area
this.mainLabel = new E(this.sidePanel.dom.childNodes[1],true); //label area

//get the timeline area elements
this.timelineArea = new E(scheduleChildren[1],true); //panel that holds the timeline header and main body
this.viewport = new E(this.timelineArea.dom.childNodes[0],true); //viewport
this.resizeMarker = new E(scheduleChildren[2],true);
this.resizeProxy = new E(scheduleChildren[3],true);

//get the header elements
this.mainHeader = new E(this.viewport.dom.childNodes[0],true); //title area
this.innerHeader = this.mainHeader.dom.firstChild; //header-inner
this.headerOffset = new E(this.innerHeader.firstChild,true); //header-offset

//Get the title elements
this.titleInner = new E(this.mainTitle.dom.childNodes[0],true);
this.resizer = new E(this.mainTitle.dom.childNodes[1],true);
this.innerLabel = new E(this.mainLabel.dom.childNodes[0],true);
this.labelOffset = new E(this.innerLabel.dom.childNodes[0],true);

//get the timeline body elements
this.scroller = new E(this.viewport.dom.childNodes[1],true); //scroller
this.mainBody = new E(this.scroller.dom.childNodes[0],true);
this.focusEl = new E(this.scroller.dom.childNodes[1],true);
this.activitiesArea = new E(this.scroller.dom.childNodes[2],true);
this.currentTimeMarker = new E(this.scroller.dom.childNodes[3],true);

//the focus element is here to allow this control to have focus. swallow all events
this.focusEl.swallowEvent("click", true);

//hide the headers if configured to do so
if(this.schedule.hideHeaders){
this.mainHeader.setDisplayed(false); //hide the header
this.mainTitle.setDisplayed(false); //hide the title area
}
},

//If the schedule does not end in the past, a task will run on the configured interval
//to update the position of the timeline marker
startCurrentTimeUpdater : function(){
if(new Date() < this.sm.endDate){
Ext.TaskMgr.start({
run: function(){
this.updateCurrentTimeLocation();
},
scope: this,
interval: 60000
});
}
},

//sets the size of the control's components based on the size of the control's container
layout : function(){
if(!this.mainBody){
return; // not rendered yet
}
var s = this.schedule,
c = s.getScheduleEl(),
csize = c.getSize(true),
vw = csize.width,
lw =this.labelWidth;

if(vw < 20 || csize.height < 20){ // display: none?
return;
}
this.el.setSize(csize.width, csize.height);
var hdHeight = this.mainHeader.getHeight();
var vh = csize.height - hdHeight
var sw = vw - lw;
this.scroller.setSize(sw, vh);
//heights need to be set based on if the horizontal scrollbar is present
var ih = vh; //inner height
if(this.timelineWidth > sw){
ih -= 16; //assuming 16 is horizontal scrollbar height
}

var bh = Math.max(ih, this.bodyHeight);
this.mainBody.setHeight(bh);
this.activitiesArea.setSize(this.timelineWidth, bh);
this.currentTimeMarker.setHeight(bh);

if(this.innerHeader){
this.innerHeader.style.width = (vw)+'px';
}

this.sidePanel.setWidth(lw);
this.innerLabel.setSize(lw, ih);

var tw = lw - this.resizerWidth;
this.titleInner.setWidth(tw);
this.mainTitle.setHeight(hdHeight);

this.resizer.setLeft(tw);
this.timelineArea.setLeft(lw + 1); //1 pixel for the border

this.syncHeaderScroll();
this.onLayout(vw, vh);
},

// template function for subclasses and plugins
onLayout : function(width, height){
// do nothing
},

refresh: function() {
this.renderUI();
this.layout();
var sw = 843;
var vh = 339;

this.fireEvent('refresh');
},

/*************************************************************
scrolling control
*************************************************************/
//returns an object containing a left and top property to indicate the scroll position of the schedule body
getScrollState : function(){
var sb = this.scroller.dom;
return {left: sb.scrollLeft, top: sb.scrollTop};
},

// moves the schedule body to the 0,0 position
restoreScroll : function(state){
var sb = this.scroller.dom;
sb.scrollLeft = state.left;
sb.scrollTop = state.top;
},

/*
* Scrolls the grid to the top
*/
scrollToTop : function(){
this.scroller.dom.scrollTop = 0;
this.scroller.dom.scrollLeft = 0;
},

// private
syncScroll : function(){
this.syncHeaderScroll();
var mb = this.scroller.dom;
this.schedule.fireEvent("bodyscroll", mb.scrollLeft, mb.scrollTop);
},

// scrolls the timeline header to stay synchronized with the schedule body
syncHeaderScroll : function(){
var mb = this.scroller.dom;
this.innerHeader.scrollLeft = mb.scrollLeft;
this.innerHeader.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)

this.innerLabel.dom.scrollTop = mb.scrollTop;
this.innerLabel.dom.scrollTop = mb.scrollTop;
},

/*************************************************************
Get/Find functions
*************************************************************/

//Returns an array of row dom nodes which hold the activities in the schedule
getActivityRows : function(){
return this.hasRows() ? this.activitiesArea.dom.childNodes : [];
},

//Returns an array of label dom nodes which hold the labels for the schedule
getLabelRows : function(){
return this.hasRows() ? this.labelOffset.dom.childNodes : [];
},

// //Finds the activity that contains the element passed in
// findActivity : function(el){
// if(!el){
// return false;
// }
// return this.fly(el).findParent(this.activitySelector, this.activitySelectorDepth);
// },

//Finds the label that contains the element passed in
findLabel : function(el){
if(!el){
return false;
}
return el.findParent(this.labelSelector, this.labelSelectorDepth);
},

//Return the dom node representing the row in the timeline at the specified index
getRow : function(index){
var a = this.getActivityRows();
if(a.length > index)
return a[index];

return null;
},

//Return the dom node representing the label at the specified index
getLabel : function(index){
var a = this.getLabelRows();
if(a.length > index)
return a[index];

return null;
},

//Returns the label row and activity row at the specified index
getFullRow : function(index){
return {
labelRow: this.getLabel(index),
activityRow: this.getRow(index)
};
},

//returns true if the control has rows
hasRows : function(){
var fc = this.activitiesArea.dom.firstChild;
return fc && fc.className != 'x-schedule-empty';
},

//returns the dom node representing the activity passed in
getActivity : function(rowIndex, activityIndex){
var rowEl = this.getRow(rowIndex), activity;
if(rowEl){
activity = rowEl.childNodes[activityIndex];
}

return activity || false;
},

//returns the id of the timeline header segment that contains the dom element passed in
findTimelineHeaderId : function(el){
var cell = Ext.fly(el).findParent('.x-schedule-hd-cell');
if(cell){
return this.getTimelineHeaderId(cell);
}

return false;
},

//returns the id of the timeline segement represented by the dom element passed in
getTimelineHeaderId : function(domEl){
if(domEl){
var m = domEl.className.match(this.tsRe);
if(m && m[1]){
return m[1];
}
}
return false;
},

//returns the id of the label that contains the dom element passed in
findLabelIndex : function(domEl){
var cell = Ext.fly(domEl).findParent('.x-schedule-lc');
if(cell){
return cell.rowIndex || this.getLabelIndex(cell);
}

return false;
},

//returns the id of the label represented by the dom element passed in
getLabelIndex : function(domEl){
if(domEl){
var m = domEl.className.match(this.lRe);
if(m && m[1]){
return m[1];
}
}
return false;
},

findRowIndex : function(domEl){
var row = Ext.fly(domEl).findParent('.x-schedule-row');
if(row){
return row.rowIndex || this.getRowIndex(row);
}

return false;
},

getRowIndex : function(domEl){
if(domEl){
var m = domEl.className.match(this.rowRe);
if(m && m[1]){
return m[1];
}
}
return false;
},

findActivityIndex : function(domEl){
var a = Ext.fly(domEl).findParent('.x-schedule-activity');
if(a){

return this.getActivityIndex(a);
}
return false;
},

//returns the index of the activity represented by the domEl passed in
//{Boolean} returnObj If true, returns an object with two properties: rowIndex, activityIndex
// If false, returns only a Number representing the activityIndex
getActivityIndex : function(domEl, returnObj){
if(domEl){
var m = domEl.className.match(this.actRe);
if(m && m[1]){
//the id should be in the form: rowIndex-activityIndex. break it apart
var di = m[1].indexOf('-'),
aIndex = m[1].substring(di+1);

if(returnObj === true){
var rowIndex = m[1].substring(0, di);
return {
rowIndex: parseInt(rowIndex),
activityIndex: parseInt(aIndex)
};
}
return parseInt(aIndex);
}
}
return false;
},


/*************************************************************
Utility functions
*************************************************************/

//returns a flyweight for the given element
fly : function(el){
if(!this._flyweight){
this._flyweight = new Ext.Element.Flyweight(document.body);
}
this._flyweight.dom = el;
return this._flyweight;
},

//depth first sort of dataset
treeSort : function() {
this.dsSorted = true;
},

//moves the current time marker based on the current time
updateCurrentTimeLocation : function(){
var sm = this.sm,
tm = this.tm,
now = new Date();

if(now.between(sm.startDate, sm.endDate)){
var mins = now. getElapsed(sm.startDate) / 60000;
this.currentTimeMarker.setLeft(mins * tm.getPixelsPerMinute());
}
else{
this.currentTimeMarker.setLeft(-1);
}
},

// private
addRowClass : function(row, cls){
var r = this.getRow(row);
if(r){
this.fly(r).addClass(cls);
}
},

// private
removeRowClass : function(row, cls){
var r = this.getRow(row);
if(r){
this.fly(r).removeClass(cls);
}
},

//add event handlers and classes to rows
processRows : function() {
var rows = this.getLabelRows(),
ctrl = this,
tn; //tree node

//add event handlers to the tree expand/collapse icons
for(var i=0; i<rows.length; i++){
rows[i].rowIndex = i;
tn = Ext.fly(rows[i]).child('.x-schedule-lc-tree');
if(tn){
tn.on('click', function(event){
ctrl.onTreeNodeClicked(this); //pass the div that fired the click
event.stopEvent(); //prevent the schedule from searching for what was clicked
});

rows[i].hasChildren = true;
}
}

//add a mouse over class to rows in the timeline
rows = this.getActivityRows();
for(var i=0; i<rows.length; i++){
rows[i].rowIndex = i;
Ext.fly(rows[i]).addClassOnOver('x-schedule-row-over');
}
},

//collapse the row for the given record
//{Record / Number} record Record from data store or index of record
collapseRow : function(record) {

var index = this.getRecordIndex(record);
var label = this.getLabel(index); //returns a dom node that contains the row's label
label.isExpanded = false;
//Change the expand icon to a collapse icon
tn = Ext.fly(label).child('.x-schedule-lc-tree'); //get the div that contains the tree icon
if(tn){
tn.removeClass('x-schedule-lc-tree-col');
tn.addClass('x-schedule-lc-tree-exp');
}

//hide this row - this will also recursively hide child rows
this.changeChildRowDisplay(index, 'none');
},

//expand the row for the given record
//{Record / Number} record Record from data store or index of record
expandRow : function(record) {

var index = this.getRecordIndex(record);
var label = this.getLabel(index); //returns a dom node that contains the row's label
label.isExpanded = true;
//Change the expand icon to a collapse icon
var tn = Ext.fly(label).child('.x-schedule-lc-tree'); //get the div that contains the tree icon
if(tn){
tn.removeClass('x-schedule-lc-tree-exp');
tn.addClass('x-schedule-lc-tree-col');
}

//show this row - this will also hide child rows
//send in a condition function that will check to see if the row was previously expanded
this.changeChildRowDisplay(index, 'block', function(l){ return (l.hasChildren && l.isExpanded === true) });
},

//changes the display property of the row's children recursively
changeChildRowDisplay : function(index, display, conditionFn){
var cr = this.getChildRecords(index);
if(!cr) return;
for(var i=0; i<cr.length; i++){
index = this.ds.indexOf(cr[i]);
row = this.getFullRow(index);
Ext.fly(row.labelRow).setStyle('display', display);
Ext.fly(row.activityRow).setStyle('display', display);
if(!conditionFn || conditionFn(row.labelRow)){
this.changeChildRowDisplay(index, display);
}
}
},

//returns the direct descendants of the record at the given index
//(Number) index - the index of the parent record in the data store
//returns an array of records
getChildRecords : function(index){
var ds = this.ds,
sm = this.sm, //schedule model
pi = sm.parentDataIndex,//index of the parentId data
ii = sm.idIndex, //index of the id field in the data
e = ds.getCount()-1, //end row
rs = ds.getRange(index, e),
len = rs.length, //lenght of the record set
r = [], //records to return
pid, //parent id
r; //current row

if(len <= 1) //If this is the last record, then there are no child nodes
return null;

pid = rs[0].data[ii]; //get the id of the parent record

//this is currently a brute force search. for large datasets, this code should be modified to
//track parent nodes in a stack so that it can break early at the end of the tree rooted by the parent
for(var i=1; i < len; i++){
if(rs[i].data[pi] == pid){
r.push(rs[i]);
}
}

return r;
},


//Returns the index of the given record. If the record passed in is a number, it will be returned
getRecordIndex : function(record){
var ds = this.ds,
index; //index of the record

if(typeof record === 'number'){
return record;
}
else{
return ds.indexOf(record);
}
},

//Adds the class to the dom node representing the activity.
addActivityClass : function(row, activity, cls){
var a = getActivity(row, activity);
if(a){
this.fly(a).addClass(cls);
}
},

//Removes the class from the dom node representing the activity.
removeActivityClass : function(row, activity, cls){
var a = getActivity(row, activity);
if(a){
this.fly(a).removeClass(cls);
}
},

/**
* Focuses the specified row.
* @param {Number} row The row index
*/
focusRow : function(row){
this.focusActivity(row, 0, false, false);
},

/**
* Focuses the specified activity.
* @param {Number} row The row index
* @param {Number} activity The activity index
* @param {Boolean} hscroll Scroll Horizontally to put the
*/
focusActivity : function(row, activity, hscroll){
row = Math.min(row, Math.max(0, this.getActivityRows().length-1));
var xy = this.ensureVisible(row, activity, hscroll);
this.focusEl.setXY(xy||this.scroller.getXY());

if(Ext.isGecko){
this.focusEl.focus();
}else{
this.focusEl.focus.defer(1, this.focusEl);
}
},

// private
ensureVisible : function(row, activity, hscroll){
if(!this.ds){
return;
}
if(typeof row != "number"){
row = row.rowIndex;
}
if(row < 0 || row >= this.ds.getCount()){
return;
}
activity = (activity !== undefined ? activity : 0);

var rowEl = this.getRow(row), activityEl;
if(!(hscroll === false && activity === 0)){
activityEl = this.getActivity(row, activity);
}
if(!rowEl){
return;
}

var c = this.scroller.dom;

var ctop = 0;
var p = rowEl, stop = this.el.dom;
while(p && p != stop){
ctop += p.offsetTop;
p = p.offsetParent;
}
ctop -= this.mainHeader.dom.offsetHeight;

var cbot = ctop + rowEl.offsetHeight;

var ch = c.clientHeight;
var stop = parseInt(c.scrollTop, 10);
var sbot = stop + ch;

if(ctop < stop){
c.scrollTop = ctop;
}else if(cbot > sbot){
c.scrollTop = cbot-ch;
}

if(hscroll !== false){
var cleft = parseInt(activityEl.offsetLeft, 10);
var cright = cleft + activityEl.offsetWidth;

var sleft = parseInt(c.scrollLeft, 10);
var sright = sleft + c.clientWidth;
if(cleft < sleft){
c.scrollLeft = cleft;
}else if(cright > sright){
c.scrollLeft = cright-c.clientWidth;
}
}
return activityEl ? Ext.fly(activityEl).getXY() : [c.scrollLeft+this.el.getX(), Ext.fly(rowEl).getY()];
},

/*************************************************************
Event Handlers
*************************************************************/

onActivitySelect : function(activity){
this.addActivityClass(activity, 'act-selected');
},

onActivityDeselect : function(activity){
this.removeActivityClass(activity, 'act-selected');
},

//Called when the user resizes the label area
//paramter: (Number) diff difference in width from previous
onColumnSplitterMoved : function(diff){
this.userResized = true;

//set the new width
this.labelWidth = this.labelWidth + diff - this.resizerWidth;

this.layout();
this.schedule.fireEvent("labelresize", this.labelWidth);
},

// event handler for the data store's load event
onLoad : function(){

this.scrollToTop();
},

// event handler for the data store's datachanged event
onDataChange : function(){
this.refresh();
},

// event handler for the data store's add event
onAdd : function(ds, records, index){
this.refresh();
//this.insertRows(ds, index, index + (records.length-1));
},

// event handler for the data store's remove event
onRemove : function(ds, record, index, isUpdate){
// if(isUpdate !== true){
// this.fireEvent("beforerowremoved", this, index, record);
// }
// this.removeRow(index);
// if(isUpdate !== true){
// this.processRows(index);
// this.applyEmptyText();
// this.fireEvent("rowremoved", this, index, record);
// }
this.refresh();
},

// event handler for the data store's update event
onUpdate : function(ds, record){
// this.refreshRow(record);

},

// event handler for the data store's clear event
onClear : function(){
this.refresh();
},

// event handler for the SchedulePanel headerclick event
onHeaderClick : function(s, index){

},

// event handler for the SchedulePanel mouseover event
onRowOver : function(e, t){

},

// event handler for the SchedulePanel mouseout event
onRowOut : function(e, t){

},

onTreeNodeClicked : function(treeNode){

//find the label
var label = this.findLabel(treeNode);
var i = label.rowIndex;
if(Ext.fly(treeNode).hasClass('x-schedule-lc-tree-col'))
this.collapseRow(i);
else
this.expandRow(i);
},

// private
onRowSelect : function(row){
this.addRowClass(row, "x-schedule-row-selected");
},

// private
onRowDeselect : function(row){
this.removeRowClass(row, "x-schedule-row-selected");
},

// private
onActivitySelect : function(row, col){
var activity = this.getActivity(row, col);
if(activity){
this.fly(activity).addClass("x-schedule-act-selected");
}
},

// private
onActivityDeselect : function(row, col){
var activity = this.getActivity(row, col);
if(activity){
this.fly(activity).removeClass("x-schedule-act-selected");
}
},

onResize : function(){
this.layout();
}

}); //End Ext.extend





// private
// This is a support class used internally by the Schedule components
Ext.ux.ScheduleView.SplitDragZone = function(schedule, r){
this.schedule = schedule;
this.view = schedule.getView();
this.marker = this.view.resizeMarker;
this.proxy = this.view.resizeProxy;
this.resizer = r;
Ext.ux.ScheduleView.SplitDragZone.superclass.constructor.call(this, r,
"scheduleSplitters" + this.schedule.getScheduleEl().id, {
dragElId : Ext.id(this.proxy.dom), resizeFrame:false
});
this.scroll = false;
this.hw = 5;
};
Ext.extend(Ext.ux.ScheduleView.SplitDragZone, Ext.dd.DDProxy, {

b4StartDrag : function(x, y){
//this.view.headersDisabled = true;
var h = this.view.el.getHeight();
this.marker.setHeight(h);
this.marker.show();
this.marker.alignTo(this.resizer, 'tl-tr');
this.proxy.setHeight(h);

var minx = this.view.labelWidth - this.schedule.minLabelWidth;;
this.resetConstraints();
this.setXConstraint(minx, 1000);
this.setYConstraint(0, 0);
this.minX = x - minx;
this.maxX = x + 1000;
this.startPos = x;
Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
},

endDrag : function(e){
this.marker.hide();
var v = this.view;
var endX = Math.max(this.minX, e.getPageX());
var diff = endX - this.startPos;

v.onColumnSplitterMoved(diff);
},

autoOffset : function(){
this.setDelta(0,0);
}
});
I dont know if i change the main SchedulePanel, but if i did here is the code:

SchedulePanel



/**
* @class Ext.ux.SchedulePanel
* @extends Ext.Panel
* This class represents the primary interface of a component based schedule control.
* <br><br>Usage:
* <pre><code>var schedule = new Ext.ux.SchedulePanel({
store: new Ext.data.Store({
reader: reader,
data: xg.dummyData
}),
timelineModel: new Ext.ux.TimelineModel({

}),
viewConfig: {
forceFit: true
},
selModel: new Ext.ux.TimelineActivitySelectionModel({singleSelect:true}),
width:600,
height:300,
frame:true
});</code></pre>
* <b>Notes:</b><ul>
* <li>Although this class inherits many configuration options from base classes, some of them
* (such as autoScroll, layout, items, etc) are not used by this class, and will have no effect.</li>
* <li>A schedule <b>requires</b> a width in which to scroll its columns, and a height in which to scroll its rows. The dimensions can either
* be set through the {@link #height} and {@link #width} configuration options or automatically set by using the schedule in a {@link Ext.Container Container}
* who's {@link Ext.Container#layout layout} provides sizing of its child items.</li>
* <li>To access the data in a Schedule, it is necessary to use the data model encapsulated
* by the {@link #store Store}. See the {@link #activityclick} event.</li>
* </ul>
* @constructor
* @param {Object} config The config object
*/
Ext.ux.SchedulePanel = Ext.extend(Ext.Panel, {

/**
* @cfg {Ext.data.Store} store The {@link Ext.data.Store} the schedule should use as its data source (required).
*/
/**
* @cfg {Object} timelineModel The {@link Ext.ux.TimelineModel} to use when rendering the grid.
* (defaults to {@link Ext.ux.TimelineModel} if not specified).
*/
/**
* @cfg {Object} selModel Any subclass of AbstractTimelineSelectionModel that will provide the selection model for
* the schedule (defaults to {@link Ext.ux.TimelineActivitySelectionModel} if not specified).
*/
/**
* @cfg {Boolean} disableSelection True to disable selections in the schedule (defaults to false). - ignored if a SelectionModel is specified
*/
/**
* @cfg {Object} viewConfig A config object that will be applied to the schedule's UI view. Any of
* the config options available for {@link Ext.ux.ScheduleView} can be specified here.
*/
/**
* @cfg {Boolean} hideHeaders True to hide the schedule's header (defaults to false).
*/
/**
* @cfg {Number} minColumnWidth The minimum width a time unit column can be resized to. Defaults to 25.
*/
minColumnWidth : 25,
/**
* @cfg {Number} minLabelWidth The minimum width the label column can be resized to. Defaults to 30.
*/
minLabelWidth: 50,
/**
* @cfg {Boolean} disableNesting When the parentIndexId parameter is defined in the ScheduleModel, the view
* will display labels in a tree structure. Setting this parameter to false will disable that feature and
* all rows will be displayed in a flat struccture with the same indention level. Default is true
*/
enableNesting: true,
/**
* @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is true.
*/
trackMouseOver : true,
/**
* @cfg {Object} loadMask An {@link Ext.LoadMask} config or true to mask the grid while loading (defaults to false).
*/
loadMask : false,

//private variables
rendered : false,
viewReady: false,
stateEvents: ["timeresize"],

// private
initComponent : function(){
Ext.ux.SchedulePanel.superclass.initComponent.call(this);

// override any provided value since it isn't valid
this.autoScroll = false;
this.autoWidth = false;

//get the data store
this.store = Ext.StoreMgr.lookup(this.store);

this.addEvents(
// raw events
/**
* @event click
* The raw click event for the entire schedule.
* @param {Ext.EventObject} e
*/
"click",
/**
* @event dblclick
* The raw dblclick event for the entire schedule.
* @param {Ext.EventObject} e
*/
"dblclick",
/**
* @event contextmenu
* The raw contextmenu event for the entire grid.
* @param {Ext.EventObject} e
*/
"contextmenu",
/**
* @event mousedown
* The raw mousedown event for the entire grid.
* @param {Ext.EventObject} e
*/
"mousedown",
/**
* @event mouseup
* The raw mouseup event for the entire grid.
* @param {Ext.EventObject} e
*/
"mouseup",
/**
* @event mouseover
* The raw mouseover event for the entire grid.
* @param {Ext.EventObject} e
*/
"mouseover",
/**
* @event mouseout
* The raw mouseout event for the entire grid.
* @param {Ext.EventObject} e
*/
"mouseout",
/**
* @event keypress
* The raw keypress event for the entire grid.
* @param {Ext.EventObject} e
*/
"keypress",
/**
* @event keydown
* The raw keydown event for the entire grid.
* @param {Ext.EventObject} e
*/
"keydown"
);

},

// private
onRender : function(ct, position){
Ext.ux.SchedulePanel.superclass.onRender.apply(this, arguments);

this.el.addClass('x-schedule');

var view = this.getView();
view.init(this);

//handle events on the body element
var c = this.body;
c.on("mousedown", this.onMouseDown, this);
c.on("click", this.onClick, this);
c.on("dblclick", this.onDblClick, this);
c.on("contextmenu", this.onContextMenu, this);
c.on("keydown", this.onKeyDown, this);

this.relayEvents(c, ["mousedown","mouseup","mouseover","mouseout","keypress"]);

this.getSelectionModel().init(this);
this.view.render();
},

// private
initEvents : function(){
Ext.ux.SchedulePanel.superclass.initEvents.call(this);

if(this.loadMask){
this.loadMask = new Ext.LoadMask(this.bwrap,
Ext.apply({store:this.store}, this.loadMask));
}
},

// private
afterRender : function(){
Ext.ux.SchedulePanel.superclass.afterRender.call(this);
this.view.layout();
this.viewReady = true;
/*
if (this.resizer) {
this.resizer.initialize(this);
}
*/
},

// private
onKeyDown : function(e){
this.fireEvent("keydown", e);
},

// private
onDestroy : function(){
if(this.rendered){
if(this.loadMask){
this.loadMask.destroy();
}
var c = this.body;
c.removeAllListeners();
this.view.destroy();
c.update("");
}
this.timelineModel.purgeListeners();
Ext.ux.SchedulePanel.superclass.onDestroy.call(this);
},

// fires the specified mouse related event from this control and then fires a similar
// event from either the timeline or the activity that was a target of the mouse event
processEvent : function(name, e){
this.fireEvent(name, e);
var t = e.getTarget();
var v = this.view;

var label = v.findLabelIndex(t);
if(label !== false){
this.fireEvent("label" + name, this, label, e);
return;
}

var tr = v.findRowIndex(t);
if(tr !== false){
this.fireEvent("row" + name, this, tr, e);

var a = v.findActivityIndex(t); //returns object with rowIndex and activityIndex
if(a !== false){
this.fireEvent("activity" + name, this, {rowIndex: tr, activityIndex: a}, e);
}
}

var header = v.findTimelineHeaderId(t);
if(header !== false){
this.fireEvent("header" + name, this, header, e);
return;
}
},

// private
onClick : function(e){
this.processEvent("click", e);
},

// private
onMouseDown : function(e){
this.processEvent("mousedown", e);
},

// private
onContextMenu : function(e, t){
this.processEvent("contextmenu", e);
},

// private
onDblClick : function(e){
this.processEvent("dblclick", e);
},

// private
getSelections : function(){
return this.selModel.getSelections();
},

// private
onResize : function(){
Ext.ux.SchedulePanel.superclass.onResize.apply(this, arguments);
if(this.viewReady){
this.view.layout();
}
},

/**
* Returns the schedule's underlying element.
* @return {Element} The element
*/
getScheduleEl : function(){
return this.body;
},

/**
* Returns the schedule's SelectionModel.
* @return {SelectionModel} The selection model
*/
getSelectionModel : function(){
if(!this.selModel){
this.selModel = new Ext.ux.ScheduleActivitySelectionModel();
}
return this.selModel;
},

/**
* Returns the grid's data store.
* @return {DataSource} The store
*/
getStore : function(){
return this.store;
},

/**
* Returns the schedule's TimelineModel.
* @return {TimelineModel} The timeline model
*/
getTimelineModel : function(){
if(!this.timelineModel){
this.timelineModel = new Ext.ux.TimelineModel();
}
return this.timelineModel;
},

/**
* Returns the schedule's ScheduleView object.
* @return {ScheduleView} The schedule view
*/
getView : function(){
if(!this.view){
this.view = new Ext.ux.ScheduleView(this.viewConfig);
}
return this.view;
}
});
Ext.reg('schedule', Ext.ux.SchedulePanel);
At the end the code with the treepanel to drag and drop activities to the schedule


// vim: sw=4:ts=4:nu:nospell:fdc=4
/**
* Simplest Border Layout Example
*
5. * @author Ing. Jozef Sakáloš
6. * @copyright (c) 2008, by Ing. Jozef Sakáloš
7. * @date 10. April 2008
8. * @version $Id: simplestbl.js 17 2008-04-24 14:57:16Z jozo $
9. *
10. * @license simplestbl.js is licensed under the terms of the Open Source
11. * LGPL 3.0 license. Commercial use is permitted to the extent that the
12. * code/component(s) do NOT become part of another Open Source or Commercially
13. * licensed development library or toolkit without explicit permission.
14. *
15. * License details: http://www.gnu.org/licenses/lgpl.html
16. */

/*global Ext */

//Ext.BLANK_IMAGE_URL = './ext/resources/images/default/s.gif';



var tree = new Ext.tree.TreePanel({
root:{text:'Lista de Planos', id:'root', expanded:true, children:[{
text:'Configuracao 1'
,data:'Child 1 additional data'
,children:[{
text:'Plano 1'
,data:'Some additional data of Child 1 Subchild 1'
,activity: "Plano 1"
,color: 'green'
,leaf:true
},{
text:'Plano 2'
,data:'Some additional data of Child 1 Subchild 2'
,activity: "Plano 1"
,color: 'yellow'
,leaf:true
}]
},{
text:'Plano Emergencial'
,leaf:true
,data:'Last but not least. Data of Child 2'
,activity: "Plano Emergencial"
,color: 'red'
}]}
,loader:new Ext.tree.TreeLoader({preloadChildren:true})
,enableDrag:true
,ddGroup:'row-dd'
,region:'west'
,title:'Tree'
,layout:'fit'
,width:200
,split:true
,collapsible:true
,autoScroll:true
,listeners:{
startdrag:function() {
//var t = Ext.getCmp('x-schedule-activities').body.child('div.drop-target');
//if(t) {
// t.applyStyles({'background-color':'#f0f0f0'});
//}
}
,enddrag:function() {
//var t = Ext.getCmp('x-schedule-activities').body.child('div.drop-target');
//if(t) {
// t.applyStyles({'background-color':'white'});
//}
//alert("!!")
}
}
});
// application main entry point
Ext.onReady(function() {

Ext.QuickTips.init();
//janela = new PlanWindowHandler();

janela = new Ext.Panel({
title:Ext.getDom('page-title').innerHTML,
id:'tree2divdrag',
border:false,
layout:'border',
width:1280,
height:400,
renderTo:Ext.getBody(),
items:[
new ScheduleWindowHandler().schedule,
tree
]
})

}); // eo function onReady

// eof

marcoaaguiar
21 Jan 2011, 12:51 PM
One more point, you can delete activities by click on the activity and then pressing "Delete" on the keyboard.