Project: Discussion extractor from JIVE using v3 API

Tweet about this on TwitterShare on FacebookShare on Google+
Jive discussion extractor

Jive discussion extractor

I recently did a small project on Jive API, which extracts discussions marked as questions from a Jive place and downloads to user’s machine as an excel file. This has been particularly useful for people, who track questions they have posted and can filter the results on excel.

Coming to the technology behind the project. It’s an Express – NodeJS application, where in I’m using request to make REST calls to get the Jive response.

request.get(url, {
    'auth': {
        'user': cred.uname,
        'pass': cred.password
    },
    'timeout': 8000
}, function(error, response, body) {
    if (error) {
        console.log("ERROR: " + error);
        throw error;
    }
    var formattedBody = body.replace("throw 'allowIllegalResourceCall is false.';", "");
    callback(JSON.parse(formattedBody));
});

Some of the response, especially the question content, contains HTML response. To extract the text from I’m using a sweet HTML parsing library called Cheerio, this brings all of jQuery features on server side.

// Get's text from a input (just a string or html)
getHTMLText: function(input) {
    if (!!input) {
        $ = cheerio.load(input);
        return $.root().text();
    }
},

Finally to get the response into excel, there is an excellent utility called excel-export, this allows you to put formatted data to excel as well as style your excel file using the styles.xml (In this case, I’ve not used it).

/**
 * Excel creation logic
 */
generateExcel: function(req, res) {
    /**
     * Excel Creation
     */
    var conf = {};
 
    // Excel Headers
    conf.cols = [{
        caption: 'Subject',
        type: 'string',
        beforeCellWrite: function(row, cellData) {
            return util.getHTMLText(cellData);
        },
        width: 70
    }, {
        caption: 'Description',
        type: 'string',
        beforeCellWrite: function(row, cellData) {
            return util.getHTMLText(cellData);
        },
        width: 70
    }, {
        caption: 'Reply Count',
        type: 'number',
        width: 15
    }, {
        caption: 'Status',
        type: 'string',
        width: 15
    }, {
        caption: 'Resolved',
        type: 'string',
        width: 15
    }, {
        caption: 'Updated Date',
        type: 'string',
        beforeCellWrite: function(row, cellData) {
            var date = new Date(cellData);
            return date.toLocaleDateString()
        },
        width: 30
    }, {
        caption: 'Tags',
        type: 'string',
        beforeCellWrite: function(row, cellData) {
            if (cellData) {
                return cellData;
            } else {
                return "";
            }
        },
        width: 60
    }, {
        caption: 'Question Author',
        type: 'string',
        width: 30
    }, {
        caption: 'Answer Description',
        type: 'string',
        beforeCellWrite: function(row, cellData) {
            if (cellData) {
                return util.getHTMLText(cellData);
            } else {
                return "";
            }
        },
        width: 70
    }, {
        caption: 'Answer Updated Date',
        type: 'string',
        beforeCellWrite: function(row, cellData) {
            if (cellData) {
                var date = new Date(cellData);
                return date.toLocaleDateString()
            } else {
                return "";
            }
        },
        width: 30
    }, {
        caption: 'Answer Author',
        type: 'string',
        width: 30
    }];
 
    // Excel Rows
    conf.rows = [];
 
    orc_resp.forEach(function(item) {
        if (item.answer) {
            conf.rows.push([item.subject, item.description, item.replyCount, item.status, item.resolved, item.updatedDate, item.tags, item.questionAuthor, item.answer.description, item.answer.updatedDate, item.answer.answerAuthor]);
        } else {
            conf.rows.push([item.subject, item.description, item.replyCount, item.status, item.resolved, item.updatedDate, item.tags, item.questionAuthor, "", "", ""]);
        }
    });
 
    conf.rows = util.filterPrimitiveFromArray(conf.rows);
 
    var result = nodeExcel.execute(conf);
    res.setHeader('Content-Type', 'application/vnd.openxmlformats');
    res.setHeader("Content-Disposition", "attachment; filename=" + "Discusssions.xlsx");
    res.end(result, 'binary');
}

The code is available to download at: https://github.com/ShivrajRath/JiveDiscussionQsExtractor

Tweet about this on TwitterShare on FacebookShare on Google+

Cross domain communication using postMessage and local storage

Tweet about this on TwitterShare on FacebookShare on Google+

There could be scenarios like having an iFrame from sister application, which is hosted on a different domain within a host application. If data needs to be passed between these applications and server side way is ruled out, then here’s a safe way to do so entirely on client side.

AT HOST

window.postMessage API is safest way to conduct cross-origin communication. It is always recommended to give the targeted domain, which can receive the message. If you don’t want to mention it, “*” would apply to all.

/**
 * Post message to the other window and restricted to some domain
 * @param  {[type]} win    [Other window]
 * @param  {[type]} msg    [Message to be sent]
 * @param  {[type]} domain [Targeted domain]
 */
function postCrossDomainMessage(win, msg, domain) {
    if (domain) {
        win.postMessage(msg, domain);
    } else {
        win.postMessage(msg, "*");
    }
}

I’d suggest the message which is posted, should be in a particular format for the target. In this example all the messages which we’ll send would be a serialized JSON.

/**
 * This creates a message in a suitable format for reading at other window
 * @return {[string]} [Message to be sent]
 */
function createMessage(key, msg) {
    return "{\"postedmessage\":{\""+key+"\":\""+msg+"\"}}";
}

Now, the target domain, which our host want’s to communicate can be a iFrame or a pop-up window. In either case, we’d need the window object of the target. For example sake I’ve considered an iFrame to be our target window here. This is how we’d post the message:

/**
 * Initiating a message
 */
var win = document.getElementById('ifr').contentWindow;
var msg = createMessage("localstoragekey", "this is some random localstoragevalue " + Math.random().toString(36).substring(7));
postCrossDomainMessage(win, msg);

One key thing to note here is the message should be passed to the target window only once the window (here iFrame) has completely finished loading.

AT TARGET

postMessage fires a “message” event on the target. The event.data property contains the data sent by the host. To keep the message persisted on to the target domain, we’d put the message against the key, which we had set at the host.

var PERMITTED_DOMAIN = "http://example.com";
 
/**
 * Receiving message from other domain
 */
window.addEventListener('message', function(event) {
	if (event.origin === PERMITTED_DOMAIN) {
        var msg = JSON.parse(event.data).postedmessage;
        var msgKey = Object.keys(msg)[0];
        localStorage.setItem(msgKey, msg[msgKey]);
    }
});

Again it is adviced to use event.origin to permit only allowed domains. This will avoid cross site scripting hacks.

One final thing, which is optional is to write local storage event handlers. This event would let the target domain open in other window/tab about the local storage changes.

/**
 * Local storage event capture to detect changes
 */
window.addEventListener('storage', function(event) {
	if (event.key === 'localstoragekey') {
		console.log(localStorage.getItem(event.key));
		//doSomething
	}
}, false);
Tweet about this on TwitterShare on FacebookShare on Google+

Local storage service for AngularJS

Tweet about this on TwitterShare on FacebookShare on Google+

I loved the concept of Angular services. It helps in writing cleaner code, which can be shared across the app.

We use local storage a lot these days and there are plenty of wrapper around local storage. I wrote this wrapper in form of angular service. It provides a bit improved way to access the APIs.

Tweet about this on TwitterShare on FacebookShare on Google+