Setting up agenda events & holidays

We've already known that the 3rd parameter in the name & id of the calendar tag denotes a file named agenda.js. This parameter is optional. The calendar can work without it, only that there will be no holidays or events showing. Once this parameter is defined, you'll have to have a agenda.js file in place, which provides all agenda events as well as the holidays for the calendar engine. If this file is missing and the parameter is defined, you will get errors and the calendar may not work properly.

Before we go further with the agenda, let's take another look at the calendar tag, as following.

<iframe name="gToday:normal:agenda.js" id="gToday:normal:agenda.js" src="ipopeng.htm" scrolling="no" frameborder="0"
style="visibility:visible; z-index:999; position:absolute; left:-500px; top:0px;">
<LAYER name="gToday:normal:agenda.js" src="npopeng.htm">     </LAYER>
</iframe>

One thing need to be pointed out earlier is that the file doesn't have to be named as agenda.js. You may actually use any cgi program to generate this file on-the-fly, e.g. ASP, JSP or PHP, so that you can take advantage of it and retrieve whatever events from your backend server or database. We'll discuss about it later in this tutorial.

Now let's go through the agenda.js file in the HelloWorld demo directory and explain parts of it in detail.

What is an agenda event?

An agenda event is just a special day that you want to render it differently in the calendar, like appointments, meetings and/or other daily events. You may give them specific font colors, background colors, text tooltips, or even images and customized html code. All you need to do is to append a line in the agenda.js for each daily event you got. e.g.

fAddEvent(year, month, day, message, action, bgcolor, fgcolor, bgimg, boxit, html, etc);

The parameters are detailed as following:

Note: do not use onclick event in the html param, use onmousedown instead. It's because the engine uses onmousedown of date cells to create cross-browser drag&drop behavior, and it somehow masks all other onclick and onmouseup events inside, including the href of anchor tag. Therefore, if you want to use an anchor <a> link inside the html param, you should be aware that the href won't work, and you have to use javascript to make the jump. e.g.

fAddEvent(2004,1,18," Hello World! ", "", "#87ceeb", "dodgerblue", null, false,
"<a href='#' onmousedown='top.location=\"http://www.calendarxp.net\"'>...</a>");

Following please find some sample usages, and note the popup() is a pre-defined JavaScript utility function located in the plugins.js.

fAddEvent(2004,1,18," Hello World! ", "popup(\"/anypath/mypage.html\", \"_top\")", "#87ceeb", "dodgerblue", null, false);

fAddEvent(2004,1,19," Hello World! ", "", "#87ceeb", "dodgerblue", null, false, "<div align=left class='MsgBoard' onmousedown='popup(\"mailto:who@anyemail.address?subject=test\", \"_blank\")'>My Meeting</div>");

var anyURL="mailto:who@anyemail.address?subject=it's a sample for using quote";
fAddEvent(2004,1,20," Hello World! ", "", "#87ceeb", "dodgerblue", null, false, "<div align=left class='MsgBoard' onmousedown='popup(anyURL, \"_blank\")'>My Meeting</div><div align=left class='MsgBoard' onmousedown='alert(\"Take a break\")'>Lunch Time</div>");

Note: There is an important option called gAgendaMask in the theme-name.js file. It determines which parameter will be masked out - which one in use which one not. So be sure to check it before adapting your agendas and holidays, or just set all bit values to -1 to disable the mask.

Later, you read it out by calling the following and it will return any array in format [message, action, bgcolor, fgcolor, bgimg, boxit, html] or null if nothing found :

fGetEvent(year, month, day);

Or if you want to get rid of an event, you may simply call:

fRemoveEvent(year, month, day);

How can I have 2 or more events within the same day?

The agenda event is set per day by fAddEvent() call, which means if you call it for the same day twice only the latter call will be used to set the effects. If you want to show more than one events in the same day, you need to code your own functions to merge them into one.

For example, you may simply add the following function to plugins.js and replace all fAddEvent calls with fAppendEvent in agenda.js.

function fAppendEvent(y,m,d,message,action,bgcolor,fgcolor,bgimg,boxit,html) {
  var ag=fGetEvent(y,m,d);
  if (ag==null) fAddEvent(y,m,d,message,action,bgcolor,fgcolor,bgimg,boxit,html);
  else fAddEvent(y,m,d,message?ag[0]+"\n"+message:ag[0], action?action:ag[1],
bgcolor
?bgcolor:ag[2], fgcolor?fgcolor:ag[3], bgimg?bgimg:ag[4], boxit?boxit:ag[5], ag[6]+html);
}

In the above sample, the message and html params will be appended, while the action, bgcolor, fgcolor, boxit and bgimg params will be substituted by the subsequently added sub-events. But this is just our choice and you may modify it to implement your own solution.

What is a holiday?

A holiday, a.k.a. recurring/batch event, is nothing but a special agenda that will recur from time to time. Therefore, we made a function to define them more efficiently, as following: (Note it's optional.)

function fHoliday(y,m,d) {
  var rE=fGetEvent(y,m,d), r=null;

  if (m==1&&d==1)
    r=[" Jan 1, "+y+" \n Happy New Year! ", "", "steelblue"];
  }

  return rE?rE:r; // if returns null, the engine will just render it as a normal day.
}

Once defined, the calendar engine will call it each time when rendering a date cell. So if you have a lot of similar events or want to show the same agenda across a period of time, i.e. weeks or months, using the fHoliday() to return an agenda template is much viable than making tons of fAddEvent() calls. The plugin in range picker demo is a good example in reality.

This function should return an array as agenda template in the following format or a null value if nothing special.

[message, action, bgcolor, fgcolor, bgimg, boxit, html]

In the above sample, we take the following logic -- first look up for the event of date [y,m,d]. If we found one, simply return it to the engine because we think the event created by the fAddEvent is more important.

Of course, we can easily make the code continue to check holidays and merge the result of both together if we like. Or we may favor holidays over events by checking the holiday first. Anyway, it's all up to you since this function is totally under your control.

Here is a sample function that enables most of USA holidays, except Easter which we'll discuss later. Note that here we use a pre-defined function getDateByDOW(), which is by default located in the plugins.js. This function returns the date number of the 1st, 2nd ... or last - Monday, Tuesday ... or Sunday of any month. Using it will make certain holiday calculation much easier.

function fHoliday(y,m,d) {
var rE=fGetEvent(y,m,d), r=null;

// you may have sophisticated holiday calculation set here, following are only simple examples.
if (m==1&&d==1)
r=[" Jan 1, "+y+" \n Happy New Year! ",gsAction;,"skyblue","red"];
else if (m==12&&d==25)
r=[" Dec 25, "+y+" \n Merry X'mas! ",gsAction,"skyblue","red"];
else if (m==7&&d==1)
r=[" Jul 1, "+y+" \n Canada Day ",gsAction,"skyblue","red"];
else if (m==7&&d==4)
r=[" Jul 4, "+y+" \n Independence Day ",gsAction,"skyblue","red"];
else if (m==11&&d==11)
r=[" Nov 11, "+y+" \n Veteran's Day ",gsAction,"skyblue","red"];
else if (m==1&&d<25) {
var date=getDateByDOW(y,1,3,1); // Martin Luther King, Jr. Day is the 3rd Monday of Jan
if (d==date) r=[" Jan "+d+", "+y+" \n Martin Luther King, Jr. Day ",gsAction,"skyblue","red"];
}
else if (m==2&&d<20) {
var date=getDateByDOW(y,2,3,1); // President's Day is the 3rd Monday of Feb
if (d==date) r=[" Feb "+d+", "+y+" \n President's Day ",gsAction,"skyblue","red"];
}
else if (m==9&&d<15) {
var date=getDateByDOW(y,9,1,1); // Labor Day is the 1st Monday of Sep
if (d==date) r=[" Sep "+d+", "+y+" \n Labor Day ",gsAction,"skyblue","red"];
}
else if (m==10&&d<15) {
var date=getDateByDOW(y,10,2,1); // Thanksgiving is the 2nd Monday of October
if (d==date) r=[" Oct "+d+", "+y+" \n Thanksgiving Day (Canada) ",gsAction,"skyblue","red"];
}
else if (m==11&&d>15) {
var date=getDateByDOW(y,11,4,4); // Thanksgiving is the 4th Thursday of November
if (d==date) r=[" Nov "+d+", "+y+" \n Thanksgiving Day (U.S.) ",gsAction,"skyblue","red"];
}
else if (m==5&&d>20) {
var date=getDateByDOW(y,5,5,1); // Memorial day is the last Monday of May
if (d==date) r=[" May "+d+", "+y+" \n Memorial Day ",gsAction,"skyblue","red"];
}

 

return rE?rE:r; // favor events over holidays

}

How to resolve conflicts between events and holidays?

By default, the fHoliday() in agenda.js will favor events over holidays, which means if you use fAddEvent() against a holiday day, all the holiday effects will be totally replaced by the event ones. But what if you only want to replace partial effects and keep other effects unchanged? Let's show you how to do it with an example.

Suppose we want to keep the font color of the date number as well as the background image from the holiday and all other effects from the conflicted event. To make it work, we need to modify the fHoliday() a little, as following:

function fHoliday(y,m,d) {
var rE=fGetEvent(y,m,d), r=null;

if (m==1&&d==1)
  r=[" Jan 1, "+y+" \n Happy New Year! ",gsAction,"skyblue","red"];
...

if (r&&rE) { rE[3]=r[3]; rE[4]=r[4]; return rE;}
else return rE?rE:r;
}

The above modification interprets as "get the agenda for event of day (y,m,d) and store it in rE; also get agenda into r if (y,m,d) is a holiday; if event conflicts with holiday, then replace the 3rd and 4th element (fgcolor and bgimg) of the event agenda with the values from holiday; or return the existing agenda if nothing conflicts."

Additional date calculations

In addition to do the simple comparison of year and month, you could use much more sophisticated formulas to check out the non-regular holidays like Easter, Thanksgiving or whatever holiday in your culture. There are so many standard calculations on the internet that can be used directly here.

You may just create some helper functions in plugins.js and then call them from within the fHoliday function to verify against the date passed in. For example, we have a function that calculates the Easter Day and want to use it in the fHoliday() call. We first add the following code to the plugins.js:

function getEaster(year) {
var a=year%19, b=Math.floor(year/100), c=year%100, d=Math.floor(b/4), e=b%4, f=Math.floor((b+8)/25), g=Math.floor((b-f+1)/3), h=(19*a+b-d-g+15)%30, i=Math.floor(c/4), k=c%4, l=(32+2*e+2*i-h-k)%7, m=Math.floor((a+11*h+22*l)/451), n=h+l-7*m+114;
return [Math.floor(n/31),n%31+1];
}

then append the following code to the fHoliday() in agenda.js:

function fHoliday(y,m,d) {
...
var easter=getEaster(y);
if (m==easter[0]&&d==easter[1]) r=[gMonths[m-1].substring(0,3)+" "+d+", "+y+" Easter Day ", gsAction,"skyblue","red"];

return rE?rE:r; // favor events over holidays

}

Retrieving agenda events from backend data source

Ever thought about making your agenda events manageable by using a backend server/database? No kidding, it's not only possible but always simple with CalendarXP.

Here we'll show you an example of how to generate a JavaScript file via JSP, which is capable of retrieve data from backend database. It's almost the same if you work with ASP, PHP or other cgi techniques, you may easily figure it out provided you know how to code in that language.

  1. Suppose that you have got a table named "MY_AGENDAS" in your database, and you have already defined a datasource named "jdbc/myDataSource" (you should have set up db user/password in the datasource).
  2. Suppose there are 10 columns in table "MY_AGENDAS" in accordance with the agenda format, as following: ag_year,ag_month,ag_day,ag_message,ag_action,ag_bgcolor,ag_fgcolor,ag_bgimg,ag_boxit,ag_html.
  3. Build a JSP file named agenda.jsp as following:

    <%@ page contentType="text/javascript" %>
    <% // The above JSP code set the generated page to be recognized as a javascript source. %>

    <% java.sql.Connection conn=null;
    try {
    // Establish database connection
    javax.naming.Context ctx = new javax.naming.InitialContext();
    javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup("jdbc/myDataSource");
    conn = ds.getConnection();

    // Execute SQL and get a result set
    java.sql.Statement stmt = conn.createStatement();
    java.sql.ResultSet rs = stmt.executeQuery("SELECT * FROM MY_AGENDAS");

    // Loop through the result set to generate multiple fAddEvent() functions
    while (rs.next()) {
    %>

    fAddEvent(<%=rs.getString("ag_year")%>,<%=rs.getString("ag_month")%>,<%=rs.getString("ag_day")%>,
    '<%=rs.getString("ag_message")%>','<%=rs.getString("ag_action")%>','<%=rs.getString("ag_bgcolor")%>',
    '<%=rs.getString("ag_fgcolor")%>','<%=rs.getString("ag_bgimg")%>',<%=rs.getString("ag_boxit")%>,
    '<%=rs.getString("ag_html")%>');

    <% }

    // Close db connection and error handling
    rs.close();
    stmt.close();
    } catch (Exception e) {
    System.out.pringln("Service Error: "+e);
    } finally {
    if (conn!=null)
    try { conn.close() } catch (Exception ignore) {};
    }
    %>

    function fHoliday(y,m,d) {
    ... the javascript code for fHoliday ...
    }

  4. Place agenda.jsp in your JSP server, suppose the absolute URL path is /myweb/agenda.jsp. Change the 3rd parameter in the name&id of the calendar tag, as following:
    <iframe width=174 height=189 name="gToday:normal:/myweb/agenda.jsp" id="gToday:normal:/myweb/agenda.jsp" src="ipopeng.htm" scrolling="no" frameborder="0">
    <layer name="gToday:normal:/myweb/agenda.jsp" src="npopeng.htm">     </layer>
    </iframe>

That's all. Now all the agenda events should be loaded directly from your backend database table MY_AGENDAS. If you come across any problem, be sure to point your browser to /myweb/agenda.jsp first and retrieve the generated page source. Open it with a notepad and check carefully to see if anything were wrong in the JavaScript syntax.

Note: You should pay attention to using the quote char in ag_html, be sure to escape it if needed.
Note: The <%@ page contentType="text/javascript" %>    is JSP coding. If you are using other CGI programming language, like php or asp, you should reference to its own way for how to set the content-type in the http header.
Note: The JSP code will print out "null" when the column value is NULL in DB, but other cgi like ASP and PHP might not work the same way and you should pay attention to dealing with the null value. The calendar engine will convert a "null" string into a null value automatically, also please remember that the boxit param is required to be a boolean value thus shouldn't be surrounded by the quotes.

How to pass parameters to the script that generates the agenda file?

If you want to create a calendar that belongs to a user, you may need to pass the user id to the script that generates the agenda file so that the calendar only shows up the events related to that specific user.

CalendarXP has already taken it into account and all you need is set the url in the name & id of the calendar tag, as following:

<iframe name="gToday:normal:agenda.jsp?userId=<%=obj.getUserID()%>" id="gToday:normal:agenda.jsp?userId=<%=obj.getUserID()%>"
...></iframe>

The above example is using JSP to generate the agenda file, of course you may use Perl, ASP, PHP or whatever else instead.

Note: you must escape any non-alphabetical chars in the URL, especially the colon char. Running the parameter string through an URL encoding filter and using the transformed string in the name & id of the calendar tag is always a safe way to go.

Share agendas among multiple calendars

Sometimes when you have more than 1 calendar on the same web page, especially when PopCalendar and FlatCalendar are working together, you may need to share the agenda events among them. Since version 7.0 this can be done by setting the agenda parameter of other-than-the-first calendar to "share[<context-name>]" instead of "agenda.js". In addition, the gbShareAgenda theme option of all shared calendars must be set to true. The following is a good example of agenda sharing between PopCalendarXP and FlatCalendarXP.

...

<iframe name="gToday:normal:share[gfPop]:gfFlat" id="gToday:normal:share[gfPop]:gfFlat"
src="iflateng.htm" scrolling="no" frameborder="0">
<a name="gfFlat_spacer"><img width=172 height=180></a></iframe>

...


<iframe name="gToday:supermini:agenda.js:gfPop" id="gToday:supermini:agenda.js:gfPop" src="ipopeng.htm" scrolling="no" frameborder="0" style="visibility:visible; z-index:999; position:absolute; left:-500px; top:0px;">
<LAYER name="gToday:supermini:agenda.js:gfPop" src="npopeng.htm"> </LAYER>
</iframe>
<LAYER name="gToday:normal:share[gfPop]:gfFlat" src="nflateng.htm"> </LAYER>
</BODY>
</HTML>

Note that the 3rd parameter of the second calendar tag is share[gfPop], which means to share the agenda loaded by gfPop. Should the agenda get changed by any run-time scripts both calendars would always get the same update automatically, and all you need to do is to call the fRepaint() afterwards to reveal the change in the calendar panel.

Another advantage of sharing is that the agenda.js will be loaded only once, no matter how many calendar tags are embedded on the same page. It could save a lot if you had a huge agenda database.

Note: When sharing the agenda.js file, it's important to know that any function definition, except fHoliday(), will not be shared, which means you should NOT declare any other functions in the agenda.js. If you really need an utility function, define it in the plugins.js instead.

Load agenda events per month instead of altogether

In certain situation, you may have intensive events in a calendar which may results in a huge agenda.js file. e.g. You have a table in DB that stores everyday events for each user for years, and you only want the calendar to load the events in current month so as not to generate too much network load. How to do it then?

Since version 8.2.202, PopCalendarXP has introduced an easy solution for it, as following:

  1. Create a JSP/ASP/PHP page, here we assume using JSP solution to create one as /jsp/agenda.jsp, that generates the javascript source of the agenda.js by retrieving the events detail from the backend DB. Please refer to the above DB related section for how-to.
  2. Find the fOnChange in the plugins.js and append the following code:

    function fOnChange(y,m,d,e) {
      if (gCurMonth[1]!=m||gCurMonth[0]!=y) fLoadEvents(y,m); // MUST be the first line of fOnChange
      ...
    }

    function fLoadEvents(y,m) {
      if (fGetEvent(y,m,0)==null) {
        fAddEvent(y,m,0,"Loaded"); // mark the month as loaded
        fLoadScript("/jsp/agenda.jsp?year="+y+"&month="+m);
      }
    }

    ...

    // The 2 lines below MUST sit at page bottom and be the last 2 lines of the plugins.js !!!
    if (gbShareAgenda&&!eval(gsAgShared)) eval(gsAgShared+"=[]");
    fLoadEvents(gCurMonth[0],gCurMonth[1]); // load data for the initial month

  3. In the /jsp/agenda.jsp you may have your own code to read the year and month parameters from the http request, and use them to constraint the result set loaded from the backend DB so that only events of the specific month will get generated.
  4. Append "if(fRepaint)fRepaint();" to /jsp/agenda.jsp file and make sure it gets generated as the last line of the javascript source. i.e. the generated source (you may test by pointing your browser directly to /jsp/agenda.jsp and select "view page source" ) should look like the following:
  5. fAddEvent(...);
    fAddEvent(...);
    ...

    fHoliday(y,m,d) {...}


    if (fRepaint) fRepaint(); // must be the last line of the generated agenda source

  6. Modify the calendar tag to remove the "agenda.js" from the name & id, because after the above steps the agenda is loaded per request and we don't need it any more.
  7. <iframe name="gToday:normal::gfPop" id="gToday:normal::gfPop" ... ></iframe>

That's it! Now every time you try to switch a month, the Server Page /jsp/agenda.jsp will be visited with 2 http parameters - year and month. And the value of the year and month represent the month that the calendar will be switching to. All you need is to parse the month value and then load corresponding events from DB table and generate corresponding fAddEvent() calls.

This should be easy, isn't it? However, the fLoadScript() call approach currently is only supported by IE5+(except Mac), NS6+, Mozilla and Opera7+. For those unsupported browsers you may consider using server-side browser detection to set the agenda param in the name & id (hint: name='gToday:normal:<%=isIE4||isNN4?"agendaFull.jsp":""%>:gfFlat' ) so as to load everything at one time.


Copyright© 2003-2004 Idemfactor Solutions, Inc. All rights reserved.