Change color of past events in Fullcalendar - fullcalendar

I'm trying to implement this solution to "grey out" past events in Fullcalendar, but I'm not having any luck. I'm not too well versed in Javascript, though, so I assume I'm making some dumb mistakes.
I've been putting the suggested code into fullcalendar.js, inside the call for daySegHTML(segs) around line 4587.
I added the first two lines at the end of the function's initial var list (Why not, I figured)—so something like this:
...
var leftCol;
var rightCol;
var left;
var right;
var skinCss;
var hoy = new Date;// get today's date
hoy = parseInt((hoy.getTime()) / 1000); //get today date in unix
var html = '';
...
Then, just below, I added the other two lines inside the loop:
for (i=0; i<segCnt; i++) {
seg = segs[i];
event = seg.event;
classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
if (isEventDraggable(event)) {
classes.push('fc-event-draggable');
}
unixevent = parseInt((event.end.getTime()) / 1000); //event date in Unix
if (unixevent < hoy) {classes.push('fc-past');} //add class if event is old
if (rtl) {
if (seg.isStart) {
classes.push('fc-corner-right');
}
...
Running this code results in a rendered calendar with no events displayed and an error message: Uncaught TypeError: Cannot call method 'getTime' of null
The "null" being referred to is, apparently, event.end.getTime(). But I'm not sure I understand what exactly is going wrong, or how things are being executed. As written, it seems like it should work. At this point in the code, from what I can tell, event.end contains a valid IETF timecode, but for some reason it's "not there" when I try to run it through getTime()?
This isn't a mission-critical tweak for me, but would still be nice—and I'd like to understand what's going on and what I'm doing wrong, as well! Any help greatly appreciated!

If you are using FullCalendar2 with Google Calendar, you will need to use the version of the code below. This uses Moment.js to do some conversions, but since FC2 requires it, you'll be using it already.
eventRender: function(event, element, view) {
var ntoday = new Date().getTime();
var eventEnd = moment( event.end ).valueOf();
var eventStart = moment( event.start ).valueOf();
if (!event.end){
if (eventStart < ntoday){
element.addClass("past-event");
element.children().addClass("past-event");
}
} else {
if (eventEnd < ntoday){
element.addClass("past-event");
element.children().addClass("past-event");
}
}
}

As per FullCalendar v1.6.4
Style past events in css:
.fc-past{background-color:red;}
Style future events in css:
.fc-future{background-color:red;}

There's no need to fiddle with fullcalendar.js. Just add a callback, like:
eventRender: function(calev, elt, view) {
if (calev.end.getTime() < sometime())
elt.addClass("greyclass");
},
you just have to define the correct CSS for .greyclass.

Every event has an ID associated with it. It is a good idea to maintain your own meta information on all events based on their ids. If you are getting the events popupated from a backend database, add a field to your table. What has worked best for me is to rely on callbacks only to get the event ids and then set/reset attributes fetched from my own data store. Just to give you some perspective, I am pasting below a section of my code snippet. The key is to target the EventDAO class for all your needs.
public class EventDAO
{
//change the connection string as per your database connection.
//private static string connectionString = "Data Source=ASHIT\\SQLEXPRESS;Initial Catalog=amit;Integrated Security=True";
//this method retrieves all events within range start-end
public static List<CalendarEvent> getEvents(DateTime start, DateTime end, long nParlorID)
{
List<CalendarEvent> events = new List<CalendarEvent>();
// your data access class instance
clsAppointments objAppts = new clsAppointments();
DataTable dt = objAppts.SelectAll( start, end);
for(int i=0; i<dt.Rows.Count; ++i)
{
CalendarEvent cevent = new CalendarEvent();
cevent.id = (int)Convert.ToInt64(dt.Rows[i]["ID"]);
.....
Int32 apptDuration = objAppts.GetDuration(); // minutes
string staffName = objAppts.GetStaffName();
string eventDesc = objAppts.GetServiceName();
cevent.title = eventDesc + ":" + staffName;
cevent.description = "Staff name: " + staffName + ", Description: " + eventDesc;
cevent.start = (DateTime)dt.Rows[i]["AppointmentDate"];
cevent.end = (DateTime) cevent.start.AddMinutes(apptDuration);
// set appropriate classNames based on whatever parameters you have.
if (cevent.start < DateTime.Now)
{
cevent.className = "pastEventsClass";
}
.....
events.Add(cevent);
}
}
}
The high level steps are as follows:
Add a property to your cevent class. Call it className or anything else you desire.
Fill it out in EventDAO class while getting all events. Use database or any other local store you maintain to get the meta information.
In your jsonresponse.ashx, retrieve the className and add it to the event returned.
Example snippet from jsonresponse.ashx:
return "{" +
"id: '" + cevent.id + "'," +
"title: '" + HttpContext.Current.Server.HtmlEncode(cevent.title) + "'," +
"start: " + ConvertToTimestamp(cevent.start).ToString() + "," +
"end: " + ConvertToTimestamp(cevent.end).ToString() + "," +
"allDay:" + allDay + "," +
"className: '" + cevent.className + "'," +
"description: '" +
HttpContext.Current.Server.HtmlEncode(cevent.description) + "'" + "},";

Adapted from #MaxD The below code is what i used for colouring past events grey.
JS for fullcalendar pulling in Json
events: '/json-feed.php',
eventRender: function(event,element,view) {
if (event.end < new Date().getTime())
element.addClass("past-event");
},
other options ....
'event.end' in my Json is a full date time '2017-10-10 10:00:00'
CSS
.past-event.fc-event, .past-event .fc-event-dot {
background: #a7a7a7;
border-color: #848484
}

eventDataTransform = (eventData) => {
let newDate = new Date();
if(new Date(newDate.setHours(0, 0, 0, 0)).getTime() > eventData.start.getTime()){
eventData.color = "grey";
}else{
eventData.color = "blue";
}
return eventData;
}
//color will change background color of event
//textColor to change the text color

Adapted from #Jeff original answer just simply check to see if an end date exists, if it does use it otherwise use the start date. There is an allDay key (true/false) but non allDay events can still be created without an end date so it will still throw an null error. Below code has worked for me.
eventRender: function(calev, elt, view) {
var ntoday = new Date().getTime();
if (!calev.end){
if (calev.start.getTime() < ntoday){
elt.addClass("past");
elt.children().addClass("past");
}
} else {
if (calev.end.getTime() < ntoday){
elt.addClass("past");
elt.children().addClass("past");
}
}
}

Ok, so here's what I've got now, that's working (kind of):
eventRender: function(calev, elt, view) {
var ntoday = new Date();
if (calev.start.getTime() < ntoday.getTime()){
elt.addClass("past");
elt.children().addClass("past");
}
}
In my stylesheet, I found I needed to restyle the outer and inner elements to change the color; thus the elt.children().addclass addition.
The only time check I could get to work, lacking an end time for all day events, was to look at the start time - but this is going to cause problems with multi-day events, obviously.
Is there another possible solution?

Related

XPages: convert DateTime value to string using browser's locale

A similar question to a previous one I asked, but the difference being that this not for direct rendering from an underlying field - it's instead part of a some SSJS.
This is for a view column which displays the result of a SSJS function, which returns HTML that gets rendered. This HTML includes a date from a DateTime field, which gets converted to text using #Text. The problem I have with this is, #Text converts dates using the locale settings of the server, not the browser.
Is there an alternative to #Text(dateValue,"D0S0") that's browser locale aware?
The most "XPagey" way to do this is to use a date/time converter. For example (using a stand-in for the computed value):
<xp:viewColumn columnName="">
<xp:this.value><![CDATA[#{javascript:
new java.util.Date()
}]]></xp:this.value>
<xp:this.converter>
<xp:convertDateTime type="both"/>
</xp:this.converter>
</xp:viewColumn>
That "convertDateTime", with its built-in formats, will respect the browser's provided locale. If you set the option in the Xsp Properties to use the browser's time zone and "Round trip", it should also respect the user's time zone.
I've managed to get round this by using DateFormat.getDateInstance. The only problem with this is it doesn't return a short date in the same format as the XPage date converter (no leading zeros and a 2-figure year). I've got round this though with some fiddling around with the string after.
Here's the full function:
function returnLocalShortDate(ndtDate) {
// Receives NotesDateTime object, Java date or string; returns localised date string in XPages short date format
importPackage(java.text);
if (#IsText(ndtDate)) { // string
var jsDate = #TextToTime(ndtDate);
} else if (ndtDate instanceof Date) { // Java date
var jsDate:Date = ndtDate;
} else if (#IsTime(ndtDate)) { // Notes date/time
var jsDate:Date = ndtDate[0].toJavaDate();
} else {
return("");
}
var strDate:String = java.text.DateFormat.getDateInstance(DateFormat.SHORT, context.getLocale()).format(jsDate);
var strYear = jsDate.getFullYear();
var strDateArray = strDate.split("/");
strDate = ('0' + strDateArray[0]).slice(-2) + '/' + ('0' + strDateArray[1]).slice(-2) + '/' + strYear;
return(strDate);
}
Actually, if you know the format you want, rather than what the user might want via their browser settings, you should use the SimpleDateFormatter class. You can supply the format in accordance with whatever pattern you want from the javadocs for that class. If you supply the NotesDocument object and the field name, this returns the date in dd-MMM-yyyy format.
function getFormattedDate ( doc:NotesDocument, fieldName:String ) {
importPackage(java.text);
var dateFormatter:java.text.SimpleDateFormat = new SimpleDateFormat("dd-MMM-yyyy");
var d:Date = new Date(#Today());
if ( doc.hasItem (fieldName) ) {
var valueVector:java.util.Vector = doc.getItemValueDateTimeArray(fieldName);
var iterator = valueVector.iterator();
while (iterator.hasNext()) {
var itemvalue = iterator.next();
if ((typeof(itemvalue)).endsWith("DateTime")) {
d = new Date(itemvalue.toJavaDate());
return dateFormatter.format(d);
}
}
} else {
return fieldName + " is not on the document"
}
}
I owe credit to Declan Lynch's blog entry on date formatting, which takes a little debugging because SSJS returns the date value as an Vector now.

Add itemEditorValidatorFunction with Pop up window confirmation to Flexicious Grid

I am trying to have my Flexicious DataGrid ask for confirmation of a change when I click in a cell to edit a value and enter a new value which deviates from the original by a certain percentage. I cannot see an easy way to do this. Initially, I tried to write a itemEditorValidatorFunction, which returns a boolean. This works perfectly for a hard coded return value, but if I try to take the return value from the CloseEvent of an Alert, that value is ignored:
protected function validateGcCap(editor:UIComponent):Boolean{
var warningBPDiffVal:Number = Number(5);
var warningPerCentDiffVal:Number = Number(warningBPDiffVal / 1000);
var allowChange:Boolean = true;
var origGcCapVal:Number = Number(managerGrid.getCurrentEditingCell().text);
var newGcCapVal:Number = Number((editor as TextInput).text);
var diffVal:Number = Number(newGcCapVal - origGcCapVal);
if (origGcCapVal > newGcCapVal) {
diffVal = origGcCapVal - newGcCapVal;
}
if (diffVal > warningPerCentDiffVal) {
//Alert.show("you changed the gccap from " + origGcCapVal + " to " + newGcCapVal + " by " + diffVal);
function alertCloseHandler(event:CloseEvent):void{
if (event.detail == Alert.CANCEL) {
allowChange = false;
}
};
var alert:Alert = Alert.show("Are you sure that you want to update gcCap% by more than " + warningBPDiffVal + "bps?",
"Please Confirm", (Alert.OK | Alert.CANCEL),
this, alertCloseHandler);
}
return allowChange;
}
I also tried to write a itemEditor for the grids:FlexDataGridColumn, where I extended com.flexicious.controls.TextInput, but I could not work out which method to override. I wanted to override the method and only make the call to super if the Alert was clicked OK, but I could not see which method I should override. I tried override protected function onTextInput(textEvent:TextEvent):void, but this did nothing.
I would be grateful for any insight into this problem.
Not sure why someone decided to downvote your question, it seems quite valid. From looking at this, the best way for you would be to "undo" the edit when the user selects no on the box. If you have enableTrackChanges on, all you have to do is to remove that change from the dgGrid.changes collection and call dgGrid.refreshCells(). If you dont have enableTrackChanges, all you need to do is to update the dataProvider row with the old value, call dgGrid.refreshCells() and you should be set.
This is what works:
private function validateGcCap(editor:UIComponent):Boolean{
var warningBPDiffVal:Number = Number(5);
var cell:IFlexDataGridCell = managerGrid.getCurrentEditingCell();
var warningPerCentDiffVal:Number = Number(warningBPDiffVal / 1000);
var origGcCapVal:Number = Number(cell.text);
var newGcCapVal:Number = Number((editor as TextInput).text);
var diffVal:Number = Number(newGcCapVal - origGcCapVal);
if (origGcCapVal > newGcCapVal){
diffVal = origGcCapVal - newGcCapVal;
}
if (diffVal > warningPerCentDiffVal){
function alertCloseHandler(event:CloseEvent):void{
if (event.detail == Alert.CANCEL) {
IAParamsVO(cell.rowInfo.data).gcCapWrapper = origGcCapVal;
managerGrid.refreshCells();
}
}
Alert.show("Are you sure that you want to update gcCap% by more than "
+ warningBPDiffVal + "bps?", "Please Confirm", (Alert.OK | Alert.CANCEL),
this, alertCloseHandler);
}
return true;
}

Full calendar ResourceView horizontal and vertical axis

I'm using the fullcalendar resourceviews fork version 1.6.1.6...
I used an older version which had the resources on the top and the times on the left axis.
But now it is different. The times are on the top and the resources are on the left axis. It's not that good anymore. Is there a way to change it?
I need the newer version of it because of the refetchResources function.
I modified the resource object (using Ike Lin fullCalendar) and added an array which includes the number of the day, start time and end time like 0 -> 09:00 -> 12:00, 1 10:00 -> 15:30 ...
Then I changed the fullcalendar.js
function updateCells() {
var i;
var headCell;
var bodyCell;
var date;
var d;
var maxd;
var today = clearTime(new Date());
for (i=0; i<colCnt; i++) {
date = resourceDate(i);
headCell = dayHeadCells.eq(i);
if(resources[i].anwesenheit[date.getDay()-1] != null){
var von = resources[i].anwesenheit[date.getDay()-1].von;
var _von = von.substring(0, 5);
var bis = resources[i].anwesenheit[date.getDay()-1].bis;
var _bis = bis.substring(0, 5);
headCell.html(resources[i].name + "<p style='font-weight: normal; font-size: 11px;'>" + _von + " - " + _bis + " Uhr</p>");
} else {
headCell.html(resources[i].name);
}
headCell.attr("id", resources[i].id);
bodyCell = dayBodyCells.eq(i);
if (+date == +today) {
bodyCell.addClass(tm + '-state-highlight fc-today');
}else{
bodyCell.removeClass(tm + '-state-highlight fc-today');
}
setDayID(headCell.add(bodyCell), date);
}
}
This shows the work time from each resource right unter the name of the resource.
Also I added a serverside function to the select function which checks if the resource is available. If yes, then the event will be created, else the event won't be created and I get an error message.
Now I can work with it. It's not exactly what I wanted, but it's nice to use now. It updates the times under the resource name on every day change so I have an overview when a resource is available and when it's not available.

google api .net client v3 getting free busy information

I am trying to query free busy data from Google calendar. Simply I am providing start date/time and end date/time. All I want to know is if this time frame is available or not. When I run below query, I get "responseOBJ" response object which doesn't seem to include what I need. The response object only contains start and end time. It doesn't contain flag such as "IsBusy" "IsAvailable"
https://developers.google.com/google-apps/calendar/v3/reference/freebusy/query
#region Free_busy_request_NOT_WORKING
FreeBusyRequest requestobj = new FreeBusyRequest();
FreeBusyRequestItem c = new FreeBusyRequestItem();
c.Id = "calendarresource#domain.com";
requestobj.Items = new List<FreeBusyRequestItem>();
requestobj.Items.Add(c);
requestobj.TimeMin = DateTime.Now.AddDays(1);
requestobj.TimeMax = DateTime.Now.AddDays(2);
FreebusyResource.QueryRequest TestRequest = calendarService.Freebusy.Query(requestobj);
// var TestRequest = calendarService.Freebusy.
// FreeBusyResponse responseOBJ = TestRequest.Execute();
var responseOBJ = TestRequest.Execute();
#endregion
Calendar API will only ever provide ordered busy blocks in the response, never available blocks. Everything outside busy is available. Do you have at least one event on the calendar
with the given ID in the time window?
Also the account you are using needs to have at least free-busy access to the resource to be able to retrieve availability.
I know this question is old, however I think it would be beneficial to see an example. You will needed to actually grab the Busy information from your response. Below is a snippet from my own code (minus the call) with how to handle the response. You will need to utilized your c.Id as the key to search through the response:
FreebusyResource.QueryRequest testRequest = service.Freebusy.Query(busyRequest);
var responseObject = testRequest.Execute();
bool checkBusy;
bool containsKey;
if (responseObject.Calendars.ContainsKey("**INSERT YOUR KEY HERE**"))
{
containsKey = true;
if (containsKey)
{
//Had to deconstruct API response by WriteLine(). Busy returns a count of 1, while being free returns a count of 0.
//These are properties of a dictionary and a List of the responseObject (dictionary returned by API POST).
if (responseObject.Calendars["**YOUR KEY HERE**"].Busy.Count == 0)
{
checkBusy = false;
//WriteLine(checkBusy);
}
else
{
checkBusy = true;
//WriteLine(checkBusy);
}
if (checkBusy == true)
{
var busyStart = responseObject.Calendars["**YOUR KEY HERE**"].Busy[0].Start;
var busyEnd = responseObject.Calendars["**YOUR KEY HERE**].Busy[0].End;
//WriteLine(busyStart);
//WriteLine(busyEnd);
//Read();
string isBusyString = "Between " + busyStart + " and " + busyEnd + " your trainer is busy";
richTextBox1.Text = isBusyString;
}
else
{
string isFreeString = "Between " + startDate + " and " + endDate + " your trainer is free";
richTextBox1.Text += isFreeString;
}
}
else
{
richTextBox1.Clear();
MessageBox.Show("CalendarAPIv3 has failed, please contact support\nregarding missing <key>", "ERROR!");
}
}
My suggestion would be to break your responses down by writing them to the console. Then, you can "deconstruct" them. That is how I was able to figure out "where" to look within the response. As noted above, you will only receive the information for busyBlocks. I used the date and time that was selected by my client's search to show the "free" times.
EDIT:
You'll need to check if your key exists before attempting the TryGetValue or searching with a keyvaluepair.

How can I make my AS3/Air code better?

Hello everyone this is my little Frankenstein code, don't make fun of it, it works!
So you would pass in the table name and a data as an Associative array which are objects.
I'm pretty sure this is not good code as I was and still am learning ActionScript. So what can I change or how would you guys make it better?
public function save(table:String,data:Object):void
{
var conn:SQLConnection = new SQLConnection();
var folder:File = File.applicationStorageDirectory;
var dbFile:File = folder.resolvePath("task.db");
conn.open(dbFile);
var stat:SQLStatement=new SQLStatement();
stat.sqlConnection=conn;
//make fields and values
var fields:String="";
var values:String="";
for(var sRole:String in data)
{
fields=fields+sRole+",:";
stat.parameters[":"+sRole]=data[sRole];
}
//trim off white space
var s:String=new String(fields);
var cleanString:String=s.slice( 0, -2 );
//over here we add : infront of the values I forget why
var find:RegExp=/:/g;
var mymyField:String=new String(cleanString.replace(find,""));
cleanString=":"+cleanString;
var SQLFields:String=mymyField;
var SQLValues:String=cleanString;
stat.text="INSERT INTO "+table+" ("+SQLFields+")VALUES("+SQLValues+")";
stat.execute();
}
The part where you build your query is quite a horror, to be honest. Half the code removes junk you added just a few lines before. This makes it hard to read and understand. Which is a sign of poor code quality. The following is far shorter and simpler:
//make fields and values
var fields:Array = [];
for(var field:String in data) {
fields.push(field);
stat.parameters[":"+field]=data[fieldName];
}
var sqlFields:String = fields.join(",");
var sqlValues:String = ":"+fields.join(",:");
stat.text="INSERT INTO "+table+" ("+sqlFields+")VALUES("+sqlValues+")";
stat.execute();
Someone once told me that a stupid idea that works isn't stupid. As programmer's our first goal is (often) to solve business issues; and as long as our code does that then we are successful. You don't need to apologize for code that works.
In terms of what I would do to change your snippet; I might just encapsulate it a bit more. Can the folder, dbFile, and db file name (task.db) be added either as properties to your class or arguments to the method?
Can you separate out the creation of the SQL Statement from the connection handling from your data parsing?
Some remarks,
As said before you can factorise out all the db connection so you can reuse the function without rewriting it if you need to change the db name.
Don't use new String() you can avoid it
it's not usefull to clean white space between your field :a, :b is the same as :a,:b
Some convention don't begin your local var name with an uppercase, and it's not usefull to reassign them to another var
If i don't get wrong after your //make fields and values can be rewritten for example as :
//make fields and values
var fields:String = "";
var values:String = "";
var fieldSeparator:String = "";
for(var sRole:String in data)
{
fields += fieldSeparator + sRole;
var paramName:String = ":" + sRole;
values += fieldSeparator + paramName;
stat.parameters[paramName] = data[sRole];
fieldSeparator = ",";
}
stat.text = "INSERT INTO " + table +" (" + fields + ") VALUES (" + values + ")";
stat.execute();

Resources