Results 1 to 5 of 5

Thread: Timeline/Gantt Widget Feedback

  1. #1
    Sencha User
    Join Date
    Jan 2009
    Posts
    61

    Default Timeline/Gantt Widget Feedback

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

    Source Code for DateRangeColumn
    Code:
    Ext.ns('wt.list');
    
    wt.list.DateRangeColumn = Ext.extend(Ext.list.Column, {
        cls: 'daterange',
        constructor: function (c) {
            try {
                c.tpl = new Ext.XTemplate('<div class="buttonwrapper rect-{[values.color || this.getDateColor
    
    (values.startDate,values.endDate,this.getSStart(),this.getSEnd())]}-left" style="overflow:visible;position:relative;left:
    
    {[this.getDateOffset(values.startDate,this.getPStart(),this.getPEnd(),' + c.width + ')]}">', '<span class="rectbutton" 
    
    style="width:{[this.getDateLength(values.startDate,values.endDate,this.getPStart(),this.getPEnd(),' + c.width + ')]};">', 
    
    '<span ext:qtip=" {startDate} to  {endDate}" class="rect-{[values.color || this.getDateColor
    
    (values.startDate,values.endDate,this.getSStart(),this.getSEnd())]}-right"><img src="/img/map/s.gif" height="18" 
    
    width="1">', '</span></span>', '</div>', {
                    getPStart: function () {
                        return this.ownerCt.periodStart;
                    },
                    getPEnd: function () {
                        return this.ownerCt.periodEnd;
                    },
                    getSStart: function () {
                        return this.ownerCt.selectStart;
                    },
                    getSEnd: function () {
                        return this.ownerCt.selectEnd;
                    },
                    getFormatted: function (d) {
                        var dt = new Date();
                        dt = Date.parseDate(d, 'Y-m-d');
                        return dt;
                    },
                    getYear: function (y) {
                        return Ext.util.Format.date(y, 'o');
                    },
                    getIntDate: function (d) {
                        var dt = this.getFormatted(d);
                        var daysInYear = (dt.isLeapYear()) ? 366 : 365;
                        var retVal = dt.getFullYear() + (dt.getDayOfYear() / daysInYear);
                        return retVal;
                    },
                    getDateLength: function (dt1, dt2, p1, p2, colWidth) {
                        try {
                            var date1 = this.getIntDate(dt1);
                            var date2 = this.getIntDate(dt2);
                            var periodStart = this.getIntDate(p1);
                            var periodEnd = this.getIntDate(p2);
                            var months = 100 * (Math.abs(date2 - date1)) / (Math.abs(periodEnd - periodStart));
    
                            return (months) + '%';
                        } catch (ex) {
                            console.log([ex, 'gdtlen']);
                        }
                    },
                    getDateOffset: function (dt1, p1, p2, colWidth) {
                        var date1 = this.getIntDate(dt1);
                        var period1 = this.getIntDate(p1);
                        var period2 = this.getIntDate(p2);
                        var months = (Math.abs(date1 - period1)) / (Math.abs(period2 - period1));
                        return (months * 100) + '%';
                    },
                    getDateColor: function (dt1, dt2, sel1, sel2) {
                        var date1 = this.getFormatted(dt1);
                        var date2 = this.getFormatted(dt2);
                        var selected1 = this.getFormatted(sel1);
                        var selected2 = this.getFormatted(sel2);
                        if (selected1.between(date1, date2) || selected2.between(date1, date2) || ((selected1 < date1) && 
    
    (selected2 > date1))) {
                            return "green"
                        } else {
                            return "gray";
                        }
                    }
                });
                wt.list.DateRangeColumn.superclass.constructor.call(this, c);
            } catch (err) {
                console.log([err, 'tpl']);
            }
        }
    });
    
    wt.Tip = Ext.extend(Ext.Tip, {
        minWidth: 10,
        rz: null,
        offsets: [20, 10],
    
        initComponent: function () {
    
            wt.Tip.superclass.initComponent.apply(this, arguments);
            rz = this.rz; //reference to resizable parent
            rz.on({
                scope: this,
                'beforeresize': this.onSize,
                //equivalent to dragstart event
                'mousemove': this.onSize,
                //equivalent to drag event
                'resize': this.hide,
                // equivalent to dragend event
                'select': this.onSelect // equivalent to dragend event
            });
        },
    
        onSize: function (resizer, e) {
            try {
                this.show();
                var xOffset = (Math.round(this.rz.parent.columns[0].width * (this.rz.parent.width - 
    
    this.rz.parent.scrollOffset))); //get left pixel offset from side of component
                var xWidth = Ext.select('em.daterange').elements[0].offsetWidth;
                var xPos = (e.getXY()[0] - (xOffset + this.rz.parent.getEl().getAnchorXY()[0]));
                var handleDate = this.getText(xPos); // pass in x-coordinate, get date,add to tooltip,date-pickers;
                if (resizer.activeHandle.position == 'east') {
                    Ext.getCmp(this.rz.parent.datePrefix + '_end-date').setValue(handleDate);
                } else {
                    Ext.getCmp(this.rz.parent.datePrefix + '_start-date').setValue(handleDate);
                }
                this.body.update(
                handleDate);
    
                this.doAutoWidth(); //Ensure content fits tooltip
                this.el.alignTo(resizer.activeHandle.el, 'l-tl?', this.offsets); //Aligns left side of tooltip with top left 
    
    portion of handle(el)
            } catch (ex) {
                console.log([ex, 'onsz'])
            }
        },
        getText: function (x) {
            var xPos = x;
            var perStart = Date.parseDate(this.rz.parent.columns[1].periodStart, 'Y-m-d').getTime() / 1000; //start of complete 
    
    period
            var perEnd = Date.parseDate(this.rz.parent.columns[1].periodEnd, 'Y-m-d').getTime() / 1000; //end of complete 
    
    period
            var xOffset = (Math.round(this.rz.parent.columns[0].width * (this.rz.parent.width - this.rz.parent.scrollOffset)) + 
    
    3); //get left pixel offset from side of component
            var kWidth = this.rz.parent.width - xOffset - this.rz.parent.scrollOffset - 2;
            var coeff = (xPos / kWidth); // percentage of period
            var xDiff = Math.floor(perStart + ((perEnd - perStart) * coeff)); //seconds between period start and current handle 
    
    position
            var dTemp = Date.parseDate(xDiff, 'U');
            return String(Ext.util.Format.date(dTemp, 'Y-m-d'));
        },
        onSelect: function (r, s, end, e) {
            r.parent.updateColors(s, end);
        }
    });
    
    
    wt.Resizable = Ext.extend(Ext.Resizable, {
        constructor: function () {
            try {
    
                wt.Resizable.superclass.constructor.apply(this, arguments);
            } catch (ex) {
                console.log(ex);
            }
            this.tip = new wt.Tip({
                rz: this
            });
            this.addEvents('mousemove', 'select');
        },
        onMouseMove: function (e) {
    
    
            var xOffset = (Math.round(this.parent.columns[0].width * (this.parent.width - this.parent.scrollOffset)));
            var curX = (e.getXY()[0] - (xOffset + this.parent.getEl().getAnchorXY()[0]));
            var minX = this.getPixels(this.parent.columns[1].periodStart);
            var maxX = this.getPixels(this.parent.columns[1].periodEnd);
    
            if (this.fireEvent('mousemove', this, e) !== false) {
                this.on('resize', function (r, w, h, e) {
                    var start = Ext.getCmp(this.parent.datePrefix + '_start-date').getRawValue();
                    var end = Ext.getCmp(this.parent.datePrefix + '_end-date').getRawValue();
                    this.fireEvent('select', this, start, end, e);
                });
                wt.Resizable.superclass.onMouseMove.apply(this, arguments);
            }
    
            if (curX < minX) {
    
                Ext.getCmp(this.parent.datePrefix + '_start-date').setValue(this.parent.columns[1].periodStart);
                this.setDates(this.parent.columns[1].periodStart, Ext.getCmp(this.parent.datePrefix + '_end-date').getRawValue
    
    ());
                this.tip.body.update(
                this.parent.columns[1].periodStart);
                this.tip.doAutoWidth(); //Ensure content fits tooltip
                this.tip.el.alignTo(this.activeHandle.el, 'l-tl?', this.tip.offsets); //Aligns left side of tooltip with top 
    
    left portion of handle(el)
            }
            if (curX > maxX) {
                //if current x is greater than max X, slide the handle to periodStart
                Ext.getCmp(this.parent.datePrefix + '_end-date').setValue(this.parent.columns[1].periodEnd);
                this.setDates(Ext.getCmp(this.parent.datePrefix + '_start-date').getRawValue(), this.parent.columns
    
    [1].periodEnd);
                this.tip.body.update(
                this.parent.columns[1].periodEnd);
                this.tip.doAutoWidth(); //Ensure content fits tooltip
                this.tip.el.alignTo(this.activeHandle.el, 'l-tl?', this.tip.offsets); //Aligns left side of tooltip with top 
    
    left portion of handle(el) 
            }
        },
        setDates: function (start, end) {
            try {
                if (Ext.isDate(Date.parseDate(start, 'Y-m-d')) && Ext.isDate(Date.parseDate(end, 'Y-m-d'))) {
                    this.parent.columns[1].selectStart = start;
                    this.parent.columns[1].selectEnd = end;
    
                    var xOffset = (Math.round(this.parent.columns[0].width * (this.parent.width - this.parent.scrollOffset)) + 
    
    3);
                    var perStart = this.getPixels(this.parent.columns[1].periodStart); //start of complete period
                    var perEnd = this.getPixels(this.parent.columns[1].periodEnd); //end of complete period
                    var selectStart = this.getPixels(start); //new end date of selection
                    var selectEnd = this.getPixels(end); //new end date of selection
                    var newWidth = (selectEnd - selectStart);
    
                    var xPos = selectStart;
                    this.el.setLeft(xPos + xOffset);
                    this.resizeTo(newWidth, this.el.getHeight());
                }
            } catch (ex) {
                console.log(ex)
            }
        },
        getFormatted: function (d) {
            var dt = new Date();
            dt = Date.parseDate(d, 'Y-m-d');
            return dt;
        },
        getIntDate: function (d) {
            var dt = this.getFormatted(d);
            var daysInYear = (dt.isLeapYear()) ? 366 : 365;
            var retVal = dt.getFullYear() + (dt.getDayOfYear() / daysInYear);
            return retVal;
        },
        getPixels: function (dt) {
            // returns percentage equivalent of a date
            var xOffset = (Math.round(this.parent.columns[0].width * (this.parent.width - this.parent.scrollOffset)) + 3); 
    
    //get left pixel offset from side of component
            var kWidth = this.parent.width - xOffset - this.parent.scrollOffset - 2;
            var perStart = this.getIntDate(this.parent.columns[1].periodStart); //start of complete period
            var perEnd = this.getIntDate(this.parent.columns[1].periodEnd); //end of complete period
            var selectStart = this.getIntDate(dt); //new start date of selection
            var xPos = ((Math.abs(selectStart - perStart) / Math.abs(perEnd - perStart))) * kWidth; // percentage of period
            return (xPos);
        },
        destroy: function () {
            this.tip.purgeListeners();
            this.tip.destroy();
            wt.Resizable.superclass.destroy.apply(this);
        }
    });
    Ext.reg('wtresize', wt.Resizable);
    
    var origOnHdClick = Ext.list.Sorter.prototype.onHdClick;
    var origInit = Ext.list.Sorter.prototype.init;
    Ext.override(Ext.list.Sorter, {
        init: function () {
            this.addEvents('sortchange');
            origInit.apply(this, arguments);
        },
        onHdClick: function (e) {
            var hd = e.getTarget('em', 3);
            if (hd && !this.view.disableHeaders) {
                var index = this.view.findHeaderIndex(hd);
                if (this.view.getXType() !== 'listview') {
                    var sortIndex = this.view.defaultSortField;
                } else {
                    var sortIndex = this.view.columns[index].dataIndex;
                }
                this.view.store.sort(sortIndex);
                var state = this.view.store.getSortState();
                this.fireEvent('sortchange', this, state);
            }
        }
    
    });
    
    wt.TimeLine = Ext.extend(Ext.list.ListView, {
    
        initComponent: function () {
            this.rzId = 'splitter_' + Ext.id();
            var constraintWidth = (this.columns[1].width * (this.width - this.scrollOffset));
            try {
                this.tpl = new Ext.XTemplate('<div id="' + this.rzId + '_constraint" tabIndex="-1"  style="position: absolute; 
    
    left: 0px; height: 500px; z-index: 998;width:' + constraintWidth + 'px; float: left; display: inline;"></div>', '<div id="' 
    
    + this.rzId + '" tabIndex="-1" class="x-timeline-bg" style="position: absolute; left: 0px; height: 500px; z-index: 
    
    999;width:5px; float: left; display: inline;"></div>', '<tpl for="rows">', '<dl>', '<tpl for="parent.columns">', '<dt 
    
    style="width:{[values.width*100]}%;text-align:{align};">', '<em unselectable="on"<tpl if="cls"> class="{cls}</tpl>">', 
    
    '{[values.tpl.apply(parent)]}', '</em>', '</dt>', '</tpl>', '<div class="x-clear"></div>', '</dl>', '</tpl>');
                wt.TimeLine.superclass.initComponent.apply(this);
                tmLn = this;
                Ext.each(this.columns, function (i) {
                    i.tpl.ownerCt = i;
                    i.ownerCt = tmLn;
                });
            } catch (ex) {
                for (var i in ex) {
                    console.log(ex);
                }
            }
    
        },
        setDates: function (start, end) {
            this.columns[1].selectStart = start;
            this.columns[1].selectEnd = end;
            this.rz.setDates(start, end);
        },
        setStart: function (newVal) {
            this.columns[1].selectStart = newVal;
            this.rz.setDates(newVal, this.columns[1].selectEnd);
        },
        setEnd: function (newVal) {
            this.columns[1].selectEnd = newVal;
            this.rz.setDates(this.columns[1].selectStart, newVal);
        },
        refreshView: function () {
            try {
                this.refresh();
                this.rz.destroy();
                xCoord = this.columns[0].width * (this.getWidth() - this.scrollOffset);
                xWidth = (this.columns[1].width * (this.getWidth() - this.scrollOffset));
                Ext.get(this.rzId + "_constraint").setLeft(xCoord);
                Ext.get(this.rzId + "_constraint").setWidth(xWidth);
                Ext.get(this.rzId).setLeft(xCoord);
                Ext.get(this.rzId).setWidth(xWidth);
    
                var rz = new wt.Resizable(this.rzId, {
                    handles: 'w e',
                    dynamic: true,
                    parent: this,
                    resizeRegion: new Ext.lib.Region(0, xCoord, 1000, xCoord + xWidth),
                    // tlbr
                    constrainTo: this.rzId + "_constraint",
                    minHeight: 500,
                    pinned: true
                });
                this.rz = rz;
                rz.minX = rz.getPixels(this.columns[1].periodStart);
    
    
                Ext.getCmp(this.rz.parent.datePrefix + '_start-date').setValue(this.columns[1].selectStart);
                Ext.getCmp(this.rz.parent.datePrefix + '_start-date').minValue = this.getFormatted(this.columns
    
    [1].periodStart);
                Ext.getCmp(this.rz.parent.datePrefix + '_start-date').maxValue = this.getFormatted(this.columns[1].periodEnd);
                Ext.getCmp(this.rz.parent.datePrefix + '_end-date').setValue(this.columns[1].selectEnd);
                Ext.getCmp(this.rz.parent.datePrefix + '_end-date').minValue = this.getFormatted(this.columns[1].periodStart);
                Ext.getCmp(this.rz.parent.datePrefix + '_end-date').maxValue = this.getFormatted(this.columns[1].periodEnd);
    
                this.rz.setDates(this.columns[1].selectStart, this.columns[1].selectEnd);
                var top = (this.rz.parent.getHeight() / 2) - 10;
    
    
                var west = {
                    tag: 'img',
                    style: 'position:relative;top:' + top + 'px;left:-11px',
                    src: '/support/climateExtjs/images/knob-west.png'
                };
    
                var east = {
                    tag: 'img',
                    style: 'left:2px;position:relative;top:' + top + 'px;left:2px',
                    src: '/support/climateExtjs/images/knob-east.png'
                };
    
                Ext.DomHelper.append(this.rz.west.el.dom.id, west);
                Ext.DomHelper.append(this.rz.east.el.dom.id, east);
    
                this.rz.west.el.set({
                    'style': 'overflow:visible;position:relative'
                }, true);
                this.rz.east.el.set({
                    'style': 'overflow:visible;'
                }, true);
            } catch (ex) {
                console.log(ex);
            }
        },
        render: function (c, p) {
            try {
                wt.TimeLine.superclass.render.apply(this, arguments);
    
                if (!Ext.isDefined(this.rz)) {
                    xCoord = this.getEl().getAnchorXY('l')[0] + this.columns[0].width * this.getWidth();
                    xWidth = this.columns[1].width * this.getWidth() - this.scrollOffset;
                    Ext.get(this.rzId + "_constraint").setLeft(xCoord);
                    Ext.get(this.rzId + "_constraint").setWidth(xWidth);
                    Ext.get(this.rzId).setLeft(xCoord);
                    Ext.get(this.rzId).setWidth(xWidth);
                    var rz = new wt.Resizable(this.rzId, {
                        handles: 'w e',
                        dynamic: true,
                        resizeRegion: new Ext.lib.Region(0, xCoord, 4000, xCoord + xWidth),
                        // tlbr
                        constrainTo: this.rzId + "_constraint",
                        parent: this,
                        minHeight: 500,
                        pinned: true
                    });
                }
    
                this.rz = rz;
                rz.minX = rz.getPixels(this.columns[1].periodStart);
                var coords = Ext.get(this.all.elements[0].children[1]).getXY();
    
                Ext.getCmp(this.rz.parent.datePrefix + '_start-date').setValue(this.columns[1].selectStart);
                Ext.getCmp(this.rz.parent.datePrefix + '_start-date').minValue = this.getFormatted(this.columns
    
    [1].periodStart);
                Ext.getCmp(this.rz.parent.datePrefix + '_start-date').maxValue = this.getFormatted(this.columns[1].periodEnd);
                Ext.getCmp(this.rz.parent.datePrefix + '_end-date').setValue(this.columns[1].selectEnd);
                Ext.getCmp(this.rz.parent.datePrefix + '_end-date').minValue = this.getFormatted(this.columns[1].periodStart);
                Ext.getCmp(this.rz.parent.datePrefix + '_end-date').maxValue = this.getFormatted(this.columns[1].periodEnd);
    
                this.rz.setDates(this.columns[1].selectStart, this.columns[1].selectEnd);
    
            } catch (ex) {
                console.log(ex);
            }
    
        },
        getFormatted: function (d) {
            var dt = new Date();
            dt = Date.parseDate(d, 'Y-m-d');
            return dt;
        },
        updateColors: function (selStart, selEnd) {
            try {
                var timeLine = this;
                this.store.each(function (item, i) {
                    var dt1 = item.data.startDate;
                    var dt2 = item.data.endDate;
    
                    var date1 = timeLine.getFormatted(dt1);
                    var date2 = timeLine.getFormatted(dt2);
                    var selected1 = timeLine.getFormatted(selStart);
                    var selected2 = timeLine.getFormatted(selEnd);
                    if (selected1.between(date1, date2) || selected2.between(date1, date2) || ((selected1 < date1) && 
    
    (selected2 > date1))) {
                        Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).removeClass(['rect-gray-left', 
    
    'rect-green-left']);
                        Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).addClass('rect-green-left');
                        Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).removeClass(['rect-
    
    gray-right', 'rect-green-right']);
                        Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).addClass('rect-green-
    
    right');
    
                    } else {
                        Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).removeClass(['rect-gray-left', 
    
    'rect-green-left']);
                        Ext.get(Ext.get(timeLine.getNode(i)).child('div.buttonwrapper', true)).addClass('rect-gray-left');
                        Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).removeClass(['rect-
    
    gray-right', 'rect-green-right']);
                        Ext.get(Ext.get(timeLine.getNode(i)).child('span.rectbutton', true).children[0]).addClass('rect-gray-
    
    right');
    
                    }
    
    
                });
    
            } catch (err) {
                console.log(ex)
            }
        }
    });
    Ext.reg('timeline', wt.TimeLine);
    CSS
    Code:
    .buttonwrapper{ /* Container you can use to surround a CSS button to clear float */
    overflow: hidden;
    width: 100%;
    }
    .rect-gray-left{
    overflow:visible;
    background: transparent url(/images/rect-gray-left.png) no-repeat top left;
    }
    .rect-green-left{
    overflow:visible;
    background: transparent url(/images/rect-green-left.png) no-repeat top left;
    }
    .rect-green-right{
    background: transparent url(/images/rect-green-right.png) no-repeat top right;
    }
    .rect-gray-right{
    background: transparent url(/images/rect-gray-right.png) no-repeat top right;
    }
    
    span.x-large-heading {
        color:#15428B;
        font-weight:bold;
        font-size: 14px;
        font-family: tahoma,arial,verdana,sans-serif;
    }
    
    span.rectbutton{
    display: block;
    font: normal 13px Tahoma; /* Change 13px as desired */
    line-height: 16px; /* This value + 4px + 4px (top and bottom padding of SPAN) must equal height of button background 
    
    (default is 24px) */
    height: 17px; /* Height of button background height */
    padding-left: 2px; /* Width of left menu image */
    text-decoration: none;
    }
    
    span.rectbutton span{
    display: block;
    padding: 4px 0px 4px 0; /*Set 11px below to match value of 'padding-left' value above*/
    }
    
    .knob-east {
        background:transparent url(/images/knob-east.png) no-repeat scroll left center !important;
    }
    
    .knob-west {
        background:transparent url(/images/knob-west.png) no-repeat scroll left center !important;
    }
    Attached Files Attached Files

  2. #2

    Default

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

  3. #3
    Sencha User
    Join Date
    Jan 2009
    Posts
    61

    Default

    Any feedback or suggestions for improvement would be greatly appreciated.

  4. #4

    Default

    well, it looks nice without buy levitra any improvement I believe

  5. #5

    Default

    Quote Originally Posted by lindsey33 View Post
    well, it looks nice without any improvement I believe
    Quite agree, I like it

Similar Threads

  1. Ext Gantt - A Gantt Charting Component
    By mankz in forum Community Discussion
    Replies: 31
    Last Post: 24 Dec 2010, 10:07 AM
  2. GXT Gantt available
    By Roddarn in forum Ext GWT: User Extensions and Plugins
    Replies: 0
    Last Post: 3 Sep 2010, 1:24 PM
  3. looking for rating widget and timeline widget
    By whimsica in forum Ext 3.x: Help & Discussion
    Replies: 1
    Last Post: 22 Aug 2010, 1:53 AM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •