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
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.