1
votes

Using the iNotesCalendar view in my Bootstrap enabled web application provides a not so appealing calendar (see below)

enter image description here

Is there a way to wrap some bootstrap styling around the calendar? I couldn't find anything about bootstrap calendars in XPages4Bootstrap or on the Bootstrap page.

1
I would not use the iCalendar thing.. I would use something like fullcalendar.io I've not done it myself but Declan did in out company and it works well in XPages. - David Leedy
That sounds like a good idea. I had started to look at it already. Thanks David. - Bryan Schmiedeler
@BryanSchmiedeler, shameless plug, but I have blogged about it, elstarit.nl/2015/08/27/… - Frank van der Linden
Frank, thanks that is awesome! - Bryan Schmiedeler
@BryanSchmiedeler I would recommend adapting Frank's approach to your needs. If you come up with a nicely formed answer to your question, it's entirely legitimate to answer your own Q and it could prove useful to any others looking for the same information. - Eric McCormick

1 Answers

1
votes

As Eric suggested I am going to answer my own question. A very good starting point is Frank's excellent post.

I will post a lot of code and as much explanation as I can.

First you need to get the files and add them to the WebContent folder of your NSF, which you can find in the Package Explorer. I made a sub folder named "FullCalendar" to keep things orderly.

enter image description here

Create a custom control for your calendar. [Code for this at the bottom of the this entry.]

There a few things in my custom control that I had to add to Frank's explanation that were particular to my environment - they may be true of yours as well.

First, notice that I set the xsp.resources.aggregate property to "true", which overrides the database setting of false. I do not remember why I had to do this, but my calendar did not work unless I did.

Note: I found the code and the reason in this post.

Next, I add three resources, 3 that are related to FullCalendar (the fourth is some common layout css). The ordering is very important here. jQuery must be loaded before moment.min.js, which must be loaded before fullcalendar.min.js. Don't see jQuery there though? jQuery is already loaded in my theme, don't want to load it again.

Notice that moment is loaded with some unfamiliar syntax using a head tag and attributes. I posted a question about using Bootstrap with Full Calendar. Long story short you must also fix the AMD issue (see the post), and load resources as I did to get this to work, although I imagine I am doing something wrong!

There is some standard type of code for buttons and so on, and a div container. The real work is in the script block, and the important part is calling the rest service. I tried to make this fairly standard - I always put rest elements in a design element called XpRest.xsp and then put a specific name on each of the elements, this one being CalendarEvents.

This rest service element calls a java Rest service. The code for the rest service extension library design element is:

<xe:restService
    id="restService2"
    pathInfo="calendarEvents"
    ignoreRequestParams="false"
    state="false"
    preventDojoStore="true">
    <xe:this.service>
        <xe:customRestService
            contentType="application/json"
            serviceBean="com.XXXX.rest.CalendarEvents">
        </xe:customRestService>
    </xe:this.service>
</xe:restService> 

So this is going to call a java rest service, and the code for this is...

package com.XXXXX.rest;

import java.io.IOException;
import java.io.Writer;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.openntf.domino.Database;
import org.openntf.domino.Session;
import org.openntf.domino.View;
import org.openntf.domino.ViewEntry;
import org.openntf.domino.ViewNavigator;
import org.openntf.domino.utils.Factory;
import org.openntf.domino.DateTime;

import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.util.JsonWriter;
import com.ibm.domino.services.ServiceException;
import com.ibm.domino.services.rest.RestServiceEngine;
import com.ibm.xsp.extlib.component.rest.CustomService;
import com.ibm.xsp.extlib.component.rest.CustomServiceBean;
import com.scoular.cache.CacheBean;

public class CalendarEvents extends CustomServiceBean {

    @SuppressWarnings("unused")
    private Database dataDB;

    @Override
    public void renderService(CustomService service, RestServiceEngine engine) throws ServiceException {

        try {

            HttpServletRequest request = engine.getHttpRequest();
            HttpServletResponse response = engine.getHttpResponse();

            response.setHeader("Content-Type", "application/json; charset=UTF-8");
            response.setContentType("application/json");
            response.setHeader("Cache-Control", "no-cache");
            response.setCharacterEncoding("utf-8");
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Access-Control-Allow-Credentials", "true");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST");
            response.addHeader("Access-Control-Allow-Headers", "Content-Type");
            response.addHeader("Access-Control-Max-Age", "86400");

            String method = request.getMethod();

            if (method.equals("GET")) {
                this.doGet(request, response);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, JsonException {

        try {

            Integer cnt = 0;

            ViewNavigator nav;
            View chgView;
            DateTime tmpDte;
            Date tmpDte2; 

            Database DB = this.getDataDB();

            chgView = DB.getView("(xpViewCalendar01)");

            nav = chgView.createViewNav();

            Writer out = response.getWriter();
            JsonWriter writer = new JsonWriter(out, false);
            writer.isCompact();

            writer.startArray();

            for (ViewEntry entry : nav) {
                //Vector<?> columnValues = entry.getColumnValues();

                cnt = cnt + 1;

                writer.startArrayItem();
                writer.startObject();

                //Event Title
                writer.startProperty("title");
                writer.outStringLiteral(String.valueOf(entry.getColumnValues().get(0)));
                writer.endProperty();

                //Change id
                writer.startProperty("id");
                writer.outStringLiteral(cnt.toString());
                writer.endProperty();

                //Start Date and Time
                writer.startProperty("start");
                tmpDte = (DateTime) entry.getColumnValues().get(4);
                tmpDte2 = tmpDte.toJavaDate();
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); 
                String tmpStr = df.format(tmpDte2);
                writer.outStringLiteral(tmpStr);
                writer.endProperty();

                //End Date and Time (same as start)
                writer.startProperty("end");
                writer.outStringLiteral(tmpStr);
                writer.endProperty();

                writer.endObject();
                writer.endArrayItem();
            }

            writer.endArray();
            writer.flush();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    public Database getDataDB() {
        Session session = Factory.getSession();
        Database DataDB  = session.getDatabase(CacheBean.get().getAppDataDBPath());
        return DataDB;
    }

    public void setDataDB(Database dataDB) {
        this.dataDB = dataDB;
    }

}

This rest service is not totally completed yet, as I am not grabbing the "end" date nor grabbing the allDay element, although I have put in hooks for them in the entry form. But I think that would be pretty easy to add to this code.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
    xmlns:xe="http://www.ibm.com/xsp/coreex">

    <xp:this.properties>
        <xp:parameter name="xsp.resources.aggregate" value="true" />
    </xp:this.properties>

    <xp:this.resources>
        <xp:headTag tagName="script">
            <xp:this.attributes>
                <xp:parameter name="type" value="text/javascript" />
                <xp:parameter name="src"
                    value="FullCalendar/moment.min.js" />
            </xp:this.attributes>
        </xp:headTag>
        <xp:script src="FullCalendar/fullcalendar.min.js"
            clientSide="true">
        </xp:script>
        <xp:styleSheet href="FullCalendar/fullcalendar1.min.css"></xp:styleSheet>
        <xp:styleSheet href="/cc_CommonGrid.css"></xp:styleSheet>
    </xp:this.resources>

    <!--The Container-->
    <div class="container-fluid">

        <!--The Button Bar-->
        <div class="toolbar" style="width: 100% !important">
            <div class="row">
                <span style="margin-right:10px">
                    <button type="button" id="newDoc"
                        class="btn btn-primary">
                        Add Event
                    </button>
                </span>

                <span style="float: right">
                    <div class="input-group" style="width:300px">
                        <input type="text" id="searchInput"
                            class="form-control"
                            style="border-radius: 5px; border-bottom-right-radius:0px ;border-top-right-radius: 0px"
                            placeholder="Search for..." />
                    </div>
                </span>
            </div>

        </div>
        <!--The Button Bar-->
<!--The Grid-->
<div 
    id="div1"  
    class="row"
    style="margin-top:15px">

        <!--The Grid-->
        <xp:div 
            id="grid"
            style="background-color:rgb(255,255,255)"
            styleClass="cal">
        </xp:div>
        <!--The Grid-->
</div>
<!--The Grid-->

    </div>
    <!--The Container-->

    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[// Add Document
$('#newDoc').click(function(event){
    var url = "xpFormEvent.xsp";
    window.open(url,"_self");
});

$(document).ready(function() {

    //Get URL for web serice
    var b1 = "#{javascript:context.getUrl().getAddress().replace(view.getPageName(), '');}"
    var b2 = b1 + "/xpRest.xsp/calendarEvents";
    var calCon = $(".cal");
    calCon.fullCalendar({

    header: {
                left:   'prevYear,nextYear',
                center: 'title',
                right:  'today,month,prev,next'
    },

    eventSources: [
{
    url: b2
}
            ]

    });
})

]]></xp:this.value>
    </xp:scriptBlock>
</xp:view>

OK so here is the code for the ccCalendarView01:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
    xmlns:xe="http://www.ibm.com/xsp/coreex">

    <xp:this.properties>
        <xp:parameter name="xsp.resources.aggregate" value="true" />
    </xp:this.properties>

    <xp:this.resources>
        <xp:headTag tagName="script">
            <xp:this.attributes>
                <xp:parameter name="type" value="text/javascript" />
                <xp:parameter name="src"
                    value="FullCalendar/moment.min.js" />
            </xp:this.attributes>
        </xp:headTag>
        <xp:script src="FullCalendar/fullcalendar.min.js"
            clientSide="true">
        </xp:script>
        <xp:styleSheet href="FullCalendar/fullcalendar1.min.css"></xp:styleSheet>
        <xp:styleSheet href="/cc_CommonGrid.css"></xp:styleSheet>
    </xp:this.resources>

    <!--The Container-->
    <div class="container-fluid">

        <!--The Button Bar-->
        <div class="toolbar" style="width: 100% !important">
            <div class="row">
                <span style="margin-right:10px">
                    <button type="button" id="newDoc"
                        class="btn btn-primary">
                        Add Event
                    </button>
                </span>

                <span style="float: right">
                    <div class="input-group" style="width:300px">
                        <input type="text" id="searchInput"
                            class="form-control"
                            style="border-radius: 5px; border-bottom-right-radius:0px ;border-top-right-radius: 0px"
                            placeholder="Search for..." />
                    </div>
                </span>
            </div>

        </div>
        <!--The Button Bar-->
<!--The Grid-->
<div 
    id="div1"  
    class="row"
    style="margin-top:15px">

        <!--The Grid-->
        <xp:div 
            id="grid"
            style="background-color:rgb(255,255,255)"
            styleClass="cal">
        </xp:div>
        <!--The Grid-->
</div>
<!--The Grid-->

    </div>
    <!--The Container-->

    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[// Add Document
$('#newDoc').click(function(event){
    var url = "xpFormEvent.xsp";
    window.open(url,"_self");
});

$(document).ready(function() {

    //Get URL for web serice
    var b1 = "#{javascript:context.getUrl().getAddress().replace(view.getPageName(), '');}"
    var b2 = b1 + "/xpRest.xsp/calendarEvents";
    var calCon = $(".cal");
    calCon.fullCalendar({

    header: {
                left:   'prevYear,nextYear',
                center: 'title',
                right:  'today,month,prev,next'
    },

    eventSources: [
{
    url: b2
}
            ]

    });
})

]]></xp:this.value>
    </xp:scriptBlock>
</xp:view>