Jump to content

TUniCalendar - highlight days and more


tappatappa

Recommended Posts

TUniCalendar:

 

1) Highlight specific days (for example: holidays, events etc..) during rendering, or (even better) dynamically, like the events on a calendarpanel

2) [bug/Missing Feature?] Change the Date attribute on month/year change: the "select" and "click" events are triggered only when the user clicks on a cell (a day), while visually the day does change

example: Date is Today 18 Oct 2016, the user clicks on the right arrow (next month), the component shows "18 November 2016" but server-side Date is still 18/10 until the user clicks on "18", then it updates to 18/11. This is confusing for the user, and we received more than one complaint for this. I did some research, this seems to be an ExtJS issue.

 

3) [Feature Request] Resizing the component. At run time the Calendar is completely different from what I see in the IDE and it stays the same, is it immutable?

4) [Feature Request] Switch to compact/week view, something like this

__________________________________

| <        October  2016        > |  ///change month
| <  M   T   W   T   F   S   S  > |
  ///change week
|   17  18  19  20  21  22  23    |  ///select day

|_________________________________|

Link to comment
Share on other sites

  • 2 weeks later...

Exactly that!

 

Hi,

 

For now can you try this implementation?!:

 

1.

uses ... DateUtils;

2.

type
  THighlightDates = record
    dt: TDate;
    title: string;
end;

3.

 ...
  public
    { Public declarations }
    HighlightDates: array of THighlightDates;
    procedure HighlightDatesPush(ADt: TDate; ATitle: string);
  end;

4.

procedure TMainForm.UniFormDestroy(Sender: TObject);
begin
  SetLength(HighlightDates, 0);
end;

5.

procedure TMainForm.HighlightDatesPush(ADt: TDate; ATitle: string);
var
  I, aLength: Integer;
  CalendarPickerJSName: string;
  dateExists: Boolean;
begin
  CalendarPickerJSName := UniCalendar1.JSName + '.items.items[0]';
  dateExists := False;

  for I := 0 to High(HighlightDates) do
    if CompareDate(HighLightDates[I].dt, ADt) = 0 then
    begin
      dateExists := True;
      Break;
    end;

  if dateExists then
  begin
    UniSession.AddJS(CalendarPickerJSName + '.highlightDates['+ IntToStr(I) +'].title = "' + ATitle + '";'+
                     CalendarPickerJSName + '.update(' + CalendarPickerJSName+ '.value, true);'
    );
  end
  else begin
    aLength := High(HighlightDates) + 1;
    SetLength(HighlightDates, aLength + 1);
    HighlightDates[aLength].dt := ADt;
    HighlightDates[aLength].title := ATitle;

    UniSession.AddJS(CalendarPickerJSName + '.highlightDates.push('+
      '{dt: new Date("'+FormatDateTime('mm"/"dd"/"yyyy', ADt)+'"), title: "'+ ATitle +'"}'+
      '); '+ CalendarPickerJSName + '.update(' + CalendarPickerJSName+ '.value, true);'
    );
  end;
end;

6.  UniCalendar -> ClientEvents -> UniEvents -> Ext.picker.Date [picker] -> beforeInit fn:

function picker.beforeInit(sender, config)
{
    config.highlightDates = [];
    
    Ext.override(Ext.DatePicker, {
        update: function(date, forceRefresh) {
            this.callParent(arguments);
            var me = this;
            me.highlightDates.forEach(function(dates) {
                me.cells.each(function(el) {
                    if (Ext.Date.clearTime(dates.dt, true).valueOf() == el.dom.firstChild.dateValue) {
                        if (el.hasCls(me.activeCls)) {
                            el.addCls('x-datepicker-highlight');
                        } else {
                            el.addCls('x-datepicker-highlight-prevnext');
                        }
                        el.dom.title = dates.title;
                        return false
                    };
                });
            })
        }
    })
}

7. UniServerModule -> CustomCSS:

.x-datepicker-highlight {
    background: #FFA500 none repeat scroll 0 0;
    border-color: #bfa52f;
}

.x-datepicker-highlight-prevnext {
    background: #fff4bf none repeat scroll 0 0;
    border-color: #bfa52f;
}

Best regards.

  • Upvote 1
Link to comment
Share on other sites

TUniCalendar:

 

2) [bug/Missing Feature?] Change the Date attribute on month/year change: the "select" and "click" events are triggered only when the user clicks on a cell (a day), while visually the day does change

example: Date is Today 18 Oct 2016, the user clicks on the right arrow (next month), the component shows "18 November 2016" but server-side Date is still 18/10 until the user clicks on "18", then it updates to 18/11. This is confusing for the user, and we received more than one complaint for this. I did some research, this seems to be an ExtJS issue.

 

Also for now you can try this:

 

UniCalendar -> ClientEvents -> UniEvents -> Ext.picker.Date [picker] -> beforeInit fn:

function picker.beforeInit(sender, config)
{
    config.highlightDates = [];
    
    Ext.override(Ext.DatePicker, {
        update: function(date, forceRefresh) {
            this.callParent(arguments);
            var me = this;
            me.highlightDates.forEach(function(dates) {
                me.cells.each(function(el) {
                    if (Ext.Date.clearTime(dates.dt, true).valueOf() == el.dom.firstChild.dateValue) {
                        if (el.hasCls(me.activeCls)) {
                            el.addCls('x-datepicker-highlight');
                        } else {
                            el.addCls('x-datepicker-highlight-prevnext');
                        }
                        el.dom.title = dates.title;
                        return false
                    };
                });
            });

            // add this:
            Ext.defer(function() {
                me.fireEvent("select");
            }, 200);
        }
    })
}
  • Upvote 1
Link to comment
Share on other sites

Hi,

 

Why do you SetLength(HighlightDates, 0) in destroy event? Aren't arrays are ref counted in Delphi or I'm missing something?

 

Yes sorry, you are right, this code is rather pointless...

 

We could use to clear the array when we need it, for example:

var
  CalendarPickerJSName: string;
begin
  SetLength(HighlightDates, 0);
  CalendarPickerJSName := UniCalendar1.JSName + '.items.items[0]';
  UniSession.AddJS(CalendarPickerJSName + '.highlightDates = [];'+
                   CalendarPickerJSName + '.update(' + CalendarPickerJSName+ '.value, true);'
  );
end;

Best regards.

Link to comment
Share on other sites

Finally I had a chance to test your code. It works great!

I have two questions:

1)

procedure TMainForm.HighlightDatesPush(ADt: TDate; ATitle: string);

what is the point of the string "Title"? In my code I removed it from server side and from JS and the days are still highlighted as expected. Is it the content of the tooltip when the user moves the mouse over the day?

 

2) In my project I actually load the days in blocks, so my procedure looks something like

 

void TForm1::highlightDates(std::vector<TDateTime>& dt_vec)
{
      for (size_t i = 0; i < dt_vec.size(); ++i)
      {
           UniSession->AddJS(.......);
      }
}

is there a way to shrink the js code instead of making n calls to highlightDates.push(...); picker.update()?

something like

picker.items.items[0].highlightDates.push(___vector_of_dates___); picker.items.items[0].update(....)

Link to comment
Share on other sites

1)

procedure TMainForm.HighlightDatesPush(ADt: TDate; ATitle: string);

what is the point of the string "Title"? In my code I removed it from server side and from JS and the days are still highlighted as expected. Is it the content of the tooltip when the user moves the mouse over the day?

 

Yes

Link to comment
Share on other sites

Hi,

 

2) In my project I actually load the days in blocks, so my procedure looks something like

void TForm1::highlightDates(std::vector<TDateTime>& dt_vec)
{
      for (size_t i = 0; i < dt_vec.size(); ++i)
      {
           UniSession->AddJS(.......);
      }
}

is there a way to shrink the js code instead of making n calls to highlightDates.push(...); picker.update()?

something like

picker.items.items[0].highlightDates.push(___vector_of_dates___); picker.items.items[0].update(....)

 

You can use something like this:

var
  I: Integer;
  mLength: Integer;
  str: string;
  CalendarPickerJSName: string;
begin
  CalendarPickerJSName := UniCalendar1.JSName + '.items.items[0]';

  mLength := High(HighlightDates);
  for I := 0 to mLength do
  begin
    str:=str+'{dt: new Date("'+FormatDateTime('mm"/"dd"/"yyyy', HighlightDates[I].dt)+'")}';
    if I<mLength then
      str:=str+',';
  end;

  if mLength>-1 then
    UniSession.AddJS(CalendarPickerJSName + '.highlightDates.push('+str+');'+
                     CalendarPickerJSName + '.update(' + CalendarPickerJSName+ '.value, true);');
end;

Best regards.

Link to comment
Share on other sites

The day highlight is just what I needed, thank you. I ported the code to my project and it works.

 

I noticed something odd with the addition of

// add this:
            Ext.defer(function() {
                me.fireEvent("select");
            }, 200);

It does fix the month change, but the regular day click fires twice, and when the component is first rendered also it fires the event multiple times.

Problem is I don't know what the snippet does, since I am quite an ExtJS newbie, so I am not capable of fixing it. :)

Can you confirm this?

Link to comment
Share on other sites

Hi,

 

I noticed something odd with the addition of

// add this:
            Ext.defer(function() {
                me.fireEvent("select");
            }, 200);

It does fix the month change, but the regular day click fires twice, and when the component is first rendered also it fires the event multiple times.

Problem is I don't know what the snippet does, since I am quite an ExtJS newbie, so I am not capable of fixing it. :)

Can you confirm this?

 

Then try this:

function picker.beforeInit(sender, config)
{
    config.highlightDates = [];

    Ext.override(Ext.DatePicker, {
        isAnotherMonthView: function(date) {
            var activeDate = this.activeDate || date;
            return date.getYear() != activeDate.getYear() || date.getMonth() != activeDate.getMonth();
        },

        update: function(date, forceRefresh) {
            var me = this;
            monthViewChange = me.isAnotherMonthView(date);

            this.callParent(arguments);

            me.highlightDates.forEach(function(dates) {
                me.cells.each(function(el) {
                    if (Ext.Date.clearTime(dates.dt, true).valueOf() == el.dom.firstChild.dateValue) {
                        if (el.hasCls(me.activeCls)) {
                            el.addCls('x-datepicker-highlight');
                        } else {
                            el.addCls('x-datepicker-highlight-prevnext');
                        }
                        el.dom.title = dates.title;
                        return false
                    };
                });
            });

            if (monthViewChange) {
                Ext.defer(function() {
                    me.fireEvent("select");
                }, 200);
            }
        }
    })
}

Best regards.

Link to comment
Share on other sites

Found a solution:

 

1) Put this early in the application (for instance at the construction/show of the main form)


Ext.override(Ext.DatePicker, {
        highlightDates:[],
        enablePickerHighlight:false,
        isAnotherMonthView: function(date) {
            var activeDate = this.activeDate || date; 
            return date.getYear() != activeDate.getYear() || date.getMonth() != activeDate.getMonth();
        },

        update: function(date, forceRefresh) {
            var me = this;
            monthViewChange = me.isAnotherMonthView(date);

            this.callParent(arguments);

            if(me.enablePickerHighlight){
                me.highlightDates.forEach(function(dates) {
                    me.cells.each(function(el) {
                        if (Ext.Date.clearTime(dates.dt, true).valueOf() == el.dom.firstChild.dateValue) { 
                            if (el.hasCls(me.activeCls)) { 
                                el.addCls('x-datepicker-highlight');
                            } else { 
                                el.addCls('x-datepicker-highlight-prevnext'); 
                            }
                            //enable this if you wish custom title
                            /*el.dom.title = dates.title;*/
                            return false; 
                        };
                    });
                });
    
                if (monthViewChange) {
                     Ext.defer(function() {
                     me.fireEvent('select');
                     }, 200);
                }
            }
        }
    });

2) in the particular picker, beforeInit

function picker.beforeInit(sender, config){
    config.highlightDates = [];
    config.enablePickerHighlight=true;
}

Thanks for your patience, I learned a lot, actually.

Link to comment
Share on other sites

In order to make it work you need to separate two steps:

1) the Ext.override code and the beforeInit

....let the JS component render

 

2) UniSession->AddJS(...)

 

This is necessary because if you fire both at the same time (for instance at form first Show) the JS code can't find the ExtJS DatePicker.

 

In practice, try to execute

 TMainForm.HighlightDatesPush(ADt: TDate; ATitle: string)

after a Timer fires (once) or on button click

Link to comment
Share on other sites

Found a solution:

 

1) Put this early in the application (for instance at the construction/show of the main form)


Ext.override(Ext.DatePicker, {
        highlightDates:[],
        enablePickerHighlight:false,
        isAnotherMonthView: function(date) {
            var activeDate = this.activeDate || date; 
            return date.getYear() != activeDate.getYear() || date.getMonth() != activeDate.getMonth();
        },

        update: function(date, forceRefresh) {
            var me = this;
            monthViewChange = me.isAnotherMonthView(date);

            this.callParent(arguments);

            if(me.enablePickerHighlight){
                me.highlightDates.forEach(function(dates) {
                    me.cells.each(function(el) {
                        if (Ext.Date.clearTime(dates.dt, true).valueOf() == el.dom.firstChild.dateValue) { 
                            if (el.hasCls(me.activeCls)) { 
                                el.addCls('x-datepicker-highlight');
                            } else { 
                                el.addCls('x-datepicker-highlight-prevnext'); 
                            }
                            //enable this if you wish custom title
                            /*el.dom.title = dates.title;*/
                            return false; 
                        };
                    });
                });
    
                if (monthViewChange) {
                     Ext.defer(function() {
                     me.fireEvent('select');
                     }, 200);
                }
            }
        }
    });

2) in the particular picker, beforeInit

function picker.beforeInit(sender, config){
    config.highlightDates = [];
    config.enablePickerHighlight=true;
}

Thanks for your patience, I learned a lot, actually.

 

It works ! Thx !!

Link to comment
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...