SAPUI5 how to provide Busy Indicator for POST call in for loop - sapui5

I have an issue in displaying BusyIndicator for post call in for loop. Before posting this question i have explored all possible ways and didnt find any solution. Hence im posting here. Please apologize if this is silly question..
For one of the requirement, i have to call ODataModel POST call for 10 times in a for loop.
Here im able to get the results perfectly without any issue. But the problem is to fire all 10 services, it is taking nearly 20 seconds to finish execution of all services.
So i want to display BusyDialog before executing services. Below is my code..
var payload = {
"sectionIndex": 3,
"ratingType": "overall",
"userId": "",
"rating": "76",
"ratingKey": "wf_sect_3_rating"
};
//var busy = new sap.m.BusyDialog();
//busy.open();
for (var i = 0; i < 10; i++) {
//var busy = new sap.m.BusyDialog();
//busy.open();
var that = this;
oModel.create("/URL", payload, {
success: function (oData, oResponse) {
if (oData.results[0].status === "OK") {
that.getView().getModel("employeesModel").refresh(true);
} else {
MessageBox.error("Error : " + oData.results[0].message);
}
},
error: function (e) {
MessageBox.error("Error : " + e);
}
});
busy.close();
}
I also tried with sap.ui.core.BusyIndicator.show(), hide(), creating separate BusyDialog.fragment and calling explicitly in controller. But none of the ways worked.
But when i try to debug, then im not getting any issue. BusyIndicator is loading perfectly if im keeping breakpoint.
Can someone help me to load BusyDialog at the before calling POST call and close BusyDialog once the execution of services finished.

For this scenario I would recommend using Promises
var payload = {
sectionIndex: 3,
ratingType: "overall",
userId: "",
rating: "76",
ratingKey: "wf_sect_3_rating"
};
//var busy = new sap.m.BusyDialog();
//busy.open();
var aPromises = [];
// Open your busy dialog
for (var i = 0; i < 10; i++) {
//var busy = new sap.m.BusyDialog();
//busy.open();
var that = this;
var pCall = new Promise(function(resolve, reject) {
oModel.create("/URL", payload, {
success: function(oData, oResponse) {
resolve();
if (oData.results[0].status === "OK") {
that
.getView()
.getModel("employeesModel")
.refresh(true);
} else {
MessageBox.error("Error : " + oData.results[0].message);
}
},
error: function(e) {
reject();
MessageBox.error("Error : " + e);
}
});
aPromises.push(pCall);
}).bind(this);
Promise.all(aPromises).then(function() {
// Happy case
// Close your busy dialog
}, function() {
// At least one of the promises went wrong
});
//busy.close();
}
Note that once one of the Promises is rejected, it will go into the second function of Promise.all(), so maybe just use resolve()

Please use batch requests instead of single requests in loops.
var sError;
var aErrorMessages = [];
var payload = {
"sectionIndex": 3,
"ratingType": "overall",
"userId": "",
"rating": "76",
"ratingKey": "wf_sect_3_rating"
};
var busy = new sap.m.BusyDialog();
busy.open();
oModel.setUseBatch(true);
oModel.setDeferredGroups(["myBatchRequest"]);
for (var i = 0; i < 10; i++) { //Loop
oModel.create("/URL", payload, { //Create request without sending
groupId: "myBatchRequest",
changeSetId: "myChangeSet",
single: false,
success: function(oData, oResponse) { //Catch error for each request
if (oData.results[0].status !== "OK") {
aErrorMessages.push("Error : " + oData.results[0].message); //store error message for each request
}
}.bind(this),
error: function(e) {
aErrorMessages.push(e);
}
});
}
oModel.submitChanges({ //Send all requests at once
groupId: "myBatchRequest",
success: function(oResponse) {
if (aErrorMessages.length > 0) { //Are there any errors...
for (var i = 0; i < aErrorMessages.length; i++) {
sError += aErrorMessages[i]; //...put it in string
}
MessageBox.error(sError); //Show all errors
busy.close(); //Close Busy Dialog
} else {
this.getView().getModel("employeesModel").refresh(true);
}
}.bind(this),
error: function(oError) {
MessageBox.error(oError);
busy.close(); //Close Busy Dialog
}
});

You can solves it by using oData model method attachRequestCompleted. An easy way would be.
if(i == 10) {
oModel.attachRequestCompleted(function(oEvent) {
busy.close();
});
}
Of course that's not a perfect solution because it could be, that the 9th request is later finished than your 10th etc.
Normally you need to check every request and if every request is completed successfully, the busy dialog get closed. if one or more of them got an error, you can close if all 10 requests are completed you can close the busy dialog and you show an error message or sth.
so normally on every attachRequestCompleted you need to check your loop counter if it's size is 10 and if yes, you can close the dialog.

Related

How can I make a REST XMLHttpRequest call from AFrame while using 8th Wall Web?

I am using 8th Wall SDK and trying to call an API. When I am attempting to do that from AFrame.registercomponent onclick method, the request is not getting sent.
I am new to AR. When I tried adding an alert messages for xhttp, it's empty.
What am I missing?
Is there an alternative to this?
By the way, I tried doing this with with an AR marker using Awe.js and it worked fine.
AFRAME.registerComponent('play-on-window-click', {
...
...
onClick: function(evt) {
var video = this.el.components.material.material.map.image;
// I'm sending a request from here - BEGIN
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.status == 200) {
this.responseText;
}
xhttp.open("GET", "https://myapi/rest/abc", true);
xhttp.send();
}
// END
video.play();
}
}
I expect the call is made successful to the API.
The xhttp.open and xhttp.send calls are inside the onreadystatechange handler so it will not get sent. Something like this should work:
AFRAME.registerComponent('play-on-window-click', {
...
...
onClick: function(evt) {
var video = this.el.components.material.material.map.image;
// I'm sending a request from here - BEGIN
var xhttp = new XMLHttpRequest();
http.onreadystatechange = function() {
if (this.status == 200) {
alert(this.responseText);
}
}
xhttp.open("GET", "https://myapi/rest/abc", true);
xhttp.send();
// END
video.play();
}
}

AWS Lambda - MongoDB resource optimization

I'm building facebook chatbot using AWS Lambda and MongoDB. At the moment, my application is pretty simple but I'm trying to nail down the basics before I move onto the complex stuff.
I understand AWS Lambda is stateless but I've read adding below line in handler along with variables initialized outside handler, I don't have to establish DB connection on every request.
context.callbackWaitsForEmptyEventLoop = false;
(I've read this from this article; https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs)
I'm adding my entire code below
'use strict'
const
axios = require('axios'),
mongo = require('mongodb'),
MongoClient = mongo.MongoClient,
assert = require('assert');
var VERIFY_TOKEN = process.env.VERIFY_TOKEN;
var PAGE_ACCESS_TOKEN = process.env.PAGE_ACCESS_TOKEN;
var MONGO_DB_URI = process.env.MONGO_DB_URI;
let cachedDb = null;
let test = null;
exports.handler = (event, context, callback) => {
var method = event.context["http-method"];
context.callbackWaitsForEmptyEventLoop = false;
console.log("test :: " + test);
if (!test) {
test = "1";
}
// process GET request --> verify facebook webhook
if (method === "GET") {
var queryParams = event.params.querystring;
var rVerifyToken = queryParams['hub.verify_token']
if (rVerifyToken === VERIFY_TOKEN) {
var challenge = queryParams['hub.challenge'];
callback(null, parseInt(challenge))
} else {
var response = {
'body': 'Error, wrong validation token',
'statusCode': 403
};
callback(null, response);
}
// process POST request --> handle message
} else if (method === "POST") {
let body = event['body-json'];
body.entry.map((entry) => {
entry.messaging.map((event) => {
if (event.message) {
if (!event.message.is_echo && event.message.text) {
console.log("BODY\n" + JSON.stringify(body));
console.log("<<MESSAGE EVENT>>");
// retrieve message
let response = {
"text": "This is from webhook response for \'" + event.message.text + "\'"
}
// facebook call
callSendAPI(event.sender.id, response);
// store in DB
console.time("dbsave");
storeInMongoDB(event, callback);
}
} else if (event.postback) {
console.log("<<POSTBACK EVENT>>");
} else {
console.log("UNHANDLED EVENT; " + JSON.stringify(event));
}
})
})
}
}
function callSendAPI(senderPsid, response) {
console.log("call to FB");
let payload = {
recipient: {
id: senderPsid
},
message: response
};
let url = `https://graph.facebook.com/v2.6/me/messages?access_token=${PAGE_ACCESS_TOKEN}`;
axios.post(url, payload)
.then((response) => {
console.log("response ::: " + response);
}).catch(function(error) {
console.log(error);
});
}
function storeInMongoDB(messageEnvelope, callback) {
console.log("cachedDB :: " + cachedDb);
if (cachedDb && cachedDb.serverConfig.isConnected()) {
sendToAtlas(cachedDb.db("test"), messageEnvelope, callback);
} else {
console.log(`=> connecting to database ${MONGO_DB_URI}`);
MongoClient.connect(MONGO_DB_URI, function(err, db) {
assert.equal(null, err);
cachedDb = db;
sendToAtlas(db.db("test"), messageEnvelope, callback);
});
}
}
function sendToAtlas(db, message, callback) {
console.log("send to Mongo");
db.collection("chat_records").insertOne({
facebook: {
messageEnvelope: message
}
}, function(err, result) {
if (err != null) {
console.error("an error occurred in sendToAtlas", err);
callback(null, JSON.stringify(err));
} else {
console.timeEnd("dbsave");
var message = `Inserted a message into Atlas with id: ${result.insertedId}`;
console.log(message);
callback(null, message);
}
});
}
I did everything as instructed and referenced a few more similar cases but somehow on every request, "cachedDb" value is not saved from previous request and the app is establishing the connection all over again.
Then I also read that there is no guarantee the Lambda function is using the same container on multiple requests so I made another global variable "test". "test" variable value is logged "1" from the second request which means it's using the same container but again, "cachedDb" value is not saved.
What am I missing here?
Thanks in advance!
In short AWS Lambda function is not a permanently running service of any kind.
So, far I know AWS Lambda works on idea - "one container processes one request at a time".
It means when request comes and there is available running container for the Lambda function AWS uses it, else it starts new container.
If second request comes when first container executes Lambda function for first request AWS starts new container.
and so on...
Then there is no guarantee in what container (already running or new one) Lambda function will be executed, so... new container opens new DB connection.
Of course, there is an inactivity period and no running containers will be there after that. All will start over again by next request.

UI5 Messagebox synchron

Hi i implemented a Messagebox in a for loop, but i know the messagebox works asynchron.
I want that the programm wait for every loop to the desicion of the user.
onBook: function(oEvent) {
var that = this;
for (var i = 0; i < Items.length; i++){
function message(innerArg) {
sap.m.MessageBox.confirm(
"Text", {
icon : sap.m.MessageBox.Icon.INFORMATION,
title : "Really",
actions : [ sap.m.MessageBox.Action.YES,
sap.m.MessageBox.Action.NO ],
onClose : function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
delete(i);
}else{
}
}
});
}
message(i);
}
that.do(oEvent);
The programm jumps in the "do" method before a user action is done
Edit:
for (var i = 0; i < Items.length; i++){
(function (innerArg) {
sap.m.MessageBox.confirm(
"Delete?", {
icon : sap.m.MessageBox.Icon.INFORMATION,
title : "Delete",
actions : [ sap.m.MessageBox.Action.YES,
sap.m.MessageBox.Action.NO ],
onClose : function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
delete(innerArg)
}}
});
})(i);
}
that.Save(oEvent);
When the box is open the entries are booked because the programm goes to the save method without wating of user action Whats wrong ?
Ah, the asynchronous-function-in-a-synchronous-loop anti-pattern ;-)
You can try using closures:
for (var i = 0; i < items; i++) {
// use self-executing function here
(function(innerArg) {
sap.m.MessageBox.confirm(
"Text", {
onClose: function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
// here I want to do something
console.log("Value: ", innerArg);
}
}
}
);
})(i);
}
EDIT: Update with Promises
Based on your updated question, I have provided a more-or-less sorta-kinda working example (it may not work flawless, but it should show the design pattern you should follow)
You wrap the message box responses into a Promise resolve, and store these into an array. You then feed that array to Promise.all() in order to proceed with your save functionality
processData: function() {
var promises = [],
self = this;
for (var i = 0; i < items; i++) {
promises.push(this.doMessageboxAction(i));
}
Promise.all(promises).then(function(aData) {
aData.forEach(function(oData) {
self.save(oData);
});
}).catch(function(err) {
console.log(err);
});
}
doMessageboxAction: function(item) {
return new Promise(function(resolve, reject) {
sap.m.MessageBox.confirm(
"Text", {
onClose: function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
//do something
//etc
resolve(item); // or some other variable
}
else {
//do something else
//etc
resolve(item); // or some other variable
}
}
}
);
});
}

for loop not waiting for function to end

I have 5 links on a page and i have to check if all are links are working or not. Here is the code
// iterate through each link and check if ti works.
for(var i=0; i < 5; i++) {
var ifLinkWorks = verifyLinkWorks(links[i]);
if(ifLinkWorks){ OK }
else{ error }
}
This is verifyLinkWorks function. It opens a link. After it get opened, it checks if the page is loaded properly
function verifyLinkWorks(link) {
return winjs.Promise(function(complete) {
link.click();
// wait for page to load
return winjs.promise.timeout(4000).then(function () {
// check if page is loaded
var islinkOK = IsPageLoaded();
complete(islinkOK); // i want verifyLinkWorks to return this value
});
});
}
After reaching link.click(), it is not waiting for page to load. Instead it jumps to the if condtion in outer for loop (which makes linkWorks = undefined therefore,gives Error). How to make it wait in the verfifyLinkWorks function.
Thanks in advance...
You'll need to wait for the results of each promise, either all at once, or individually. As the actions are all async in nature, the code can't wait, but it can call a function when it completes all of the work.
Here, I've created an array which will hold each Promise instance. Once the loop has completed, the code waits for all to complete, and then using the array that is passed, checking the result at each index.
// iterate through each link and check if it works.
var verifyPromises = [];
for(var i=0; i < 5; i++) {
verifyPromises.push(verifyLinkWorks(links[i]));
}
WinJS.Promise.join(verifyPromise).done(function(results) {
for(var i=0; i < 5; i++) {
var ifLinkWorks = results[i];
if (ifLinkWorks) { /* OK */ }
else { /* error */ }
}
});
In case the link.click() call fails, I've wrapped it in a try/catch block:
function verifyLinkWorks(link) {
return WinJS.Promise(function(complete, error) {
try {
link.click();
} catch (e) {
complete(false); // or call the error callback ...
}
// wait for page to load, just wait .. no need to return anything
WinJS.Promise.timeout(4000).then(function () {
// check if page is loaded
var islinkOK = IsPageLoaded();
// finally, call the outer promise callback, complete
complete(islinkOK);
});
});
}
If you want to check the validity of a URL, I'd suggest you consider using WinJS.xhr method to perform a HEAD request instead (rfc). With each link variable, you can use a timeout to validate that there's a reasonable response at the URL, without downloading the full page (or switch to a GET and check the response body).
WinJS.Promise.timeout(4000,
WinJS.xhr({
type: 'HEAD',
url: link
}).then(function complete(result) {
var headers = result.getAllResponseHeaders();
}, function error(err) {
if (err['name'] === 'Canceled') {
}
if (err.statusText) {
}
})
);
Ok heres the link to the msdn code sample for win js promise object.
Promise winjs
now going through the code
<button id="start">StartAsync</button>
<div id="result" style="background-color: blue"></div>
<script type="text/javascript">
WinJS.Application.onready = function (ev) {
document.getElementById("start").addEventListener("click", onClicked, false);
};
function onClicked() {
addAsync(3, 4).then(
function complete(res) {
document.getElementById("result").textContent = "Complete";
},
function error(res) {
document.getElementById("result").textContent = "Error";
},
function progress(res) {
document.getElementById("result").textContent = "Progress";
})
}
function addAsync(l, r) {
return new WinJS.Promise(function (comp, err, prog) {
setTimeout(function () {
try {
var sum = l + r;
var i;
for (i = 1; i < 100; i++) {
prog(i);
}
comp(sum);
}
catch (e) {
err(e);
}
}, 1000);
});
}
</script>
you will see the addAsync(3,4).then() function. So all the code is to be kept inside that function in order to have a delayed response . Sorry m using a tab so cannot write it properly.
Also go through link then for winjs promise

How to solve requirement of "On/Off publishing" for 'read article' action

Now facebook require to have these options when you want to use bulit-in actions like READ.
"A clear way for users to control the publishing of their actions back to Open Graph. In the example News app below, a clear "On/Off" switch is provided that applies to all future Read actions within the app. When a user toggles the switch to "Off", this prevents the app from publishing Read actions from that point on. Toggling the switch back to "On" reenables the publishing. "
"A clear way for users to remove articles that were just shared through your app. In the example below, a user can easily remove a recent article that was shared as a result of a Read action that was published by the app."
sample pictures are here: https://developers.facebook.com/docs/opengraph/actions/builtin/#read under title "Publish Awareness"
How to create easy button to stop sharing and to remove shared articles? The best one will
I was searching for solution for one week without results...
Your question has two parts to it, one is storing the user preference and the other is share/unshare option to be given on the article page. The first part of the question is simple and can be achieved by simply having a small database table with minimum of two columns (for simplicity sake), userid (a varchar or long int) and share (a bool or a bit). Give an option to the user to toggle this share bit by giving an on/off button which changes the share value from 1 to 0 (true to false) and vice versa for a specified userid. Now before taking a social action (like read), check for this share bit in database for the logged facebook user and perform actions accordingly.
Now to answer the second part of your question, you may use the Facebook JavaScript SDK to make api calls to news.read action and provide a callback to store the returned id of the shared article. Use the same id to then provide option of unshare for the user. Assuming that you have jQuery included in your page, something like below should work (I wrote and tested it in a jiffy, should work in most cases)
Include the below script in your page
//set this at page load or from authResponse object
var fbDataObj = {
user_id : <some user id>,
access_token: <some access token>
};
//set currentPostFBUrl to your article url
var currentPostFBUrl = "http://www.yourpageurl.com";
var publishPost = function (articleLink, callback) {
FB.api(
'/' + fbDataObj.user_id + '/news.reads',
'post',
{ article: articleLink,
access_token: fbDataObj.access_token },
function(response) {
if (!response || response.error) {
//alert('Error occured');
if (typeof callback === 'function') {
callback({text: "error", id: ""});
}
} else {
//alert('Share was successful! Action ID: ' + response.id);
if (typeof callback === 'function') {
callback({text: "success", id: response.id});
}
}
});
};
var deletePost = function (postId, callback) {
FB.api(
postId,
'delete',
{ access_token: fbDataObj.access_token },
function(response) {
if (!response || response.error) {
//alert('Error occured');
if (typeof callback === 'function') {
callback({text: "error", id: ""});
}
} else {
//alert('Unshare was successful!');
if (typeof callback === 'function') {
callback({text: "success", id: ""});
}
}
});
};
var publishOrDeleteArticle = function (btn_obj) {
var btn_name = btn_obj.attr("name");
if (isNaN(parseInt(btn_name, 10))) {
publishPost(currentPostFBUrl, function(status){
if (status.text === "success") {
btn_obj.attr("name", status.id);
btn_obj.text("Unshare");
}
});
}
else {
deletePost(btn_name, function(status){
if (status.text === "success") {
btn_obj.attr("name", "share");
btn_obj.text("Share")
}
});
}
};
Now in your page do something like this
Edit:
(Also set currentPostFBUrl to your article url like below)
var currentPostFBUrl = "http://www.yourpageurl.com";
//within script tag
$(document).ready(function(){
$("#btn_share").click(function(e){
e.preventDefault();
publishOrDeleteArticle($(this));
});
});
//your actual share/unshare button
<a id="btn_share" name="share" href="#">Share</a>
On the final note I have written one wrapper js class for one of the recent facebook application that I was working on. With this you can read/unread an article with just one line of code. There are other WordPress wrappers inside as well but that can be left alone in this case, at the simplest you may use the object as below after providing the initial configuration and init (check the attached code below). There might be a few bugs in the code and the methods might not be complete and extensive because I'm still working on it but for the time being it should solve the purpose. The below wrapper methods can also be used in the above mentioned code for clarity. Feel free to use/modify the code, give feedback and comments and also reply back in case any issues are identified.
/*!
* --------------------------------------------------------------------------------------
* Utility Library for Facebook Application
* Contains wrapper methods for WordPress JSON API (named WPJsonApi)
* and for Facebook Javascript SDK (named FBJsWrapper)
* Dependency : jQuery, Facebook JavaScript SDK
* Author : Emad Alam
* Date: Thu Jun 07 21:11:03 2012 +0530
* --------------------------------------------------------------------------------------
* Notes:
* Including this script adds a global object called FBAppUtil to the window object.
* You may initialize the object with init method, providing init time configurations.
* Once FBAppUtil object is initted, you get two sub-ojects, WPJsonApi & FBJsWrapper.
* These two objects can be initted individually or while initing the main FBAppUtil.
* Both the objects have a buffer that stores the last fetched data and responses.
* Methods are provided to access these buffers individually.
* Once the sub-objects are configured, their methods can be called from their references.
*
* Example Usage:
* //main object init. config can be given at this time
* FBAppUtil.init();
*
* var wpJsonApiConfig = {
* apiUrl : "http://www.example.com/wordpress/api",
* permalinkEnabled : true,
* crossDomain : true
* };
* FBAppUtil.WPJsonApi.init(wpJsonApiConfig);
*
* // now you may use all the methods of FBAppUtil.WPJsonApi
* FBAppUtil.WPJsonApi.getRecentPosts(someParams, someCallback);
* FBAppUtil.WPJsonApi.getPost(someIdOrSlug, someCallback);
* var data = FBAppUtil.WPJsonApi.lastFetched();
* var response = FBAppUtil.WPJsonApi.lastResponse();
*
* // other facebook related scripts and sdks initializations
* // at this point you should be having the FB object initialized
* // you may pass the authResponse object to init the FBJsWrapper or
* // populate one of your own to pass it to the FBJsWrapper.init(authResponse)
*
* var fbJsWrapperConfig = {
* userID : <logged in userId>,
* accessToken : <user's accessToken>,
* signedRequest : <from authResponse object>,
* expiresIn : <from authResponse object>
* };
* FBAppUtil.FBJsWrapper.init(fbJsWrapperConfig);
*
* // now you may use all the methods of FBAppUtil.FBJsWrapper
* FBAppUtil.FBJsWrapper.sendAppRequest("some message", someCallback);
* FBAppUtil.FBJsWrapper.share(someArticleUrl, someCallback);
* FBAppUtil.FBJsWrapper.unshare(someId, someCallback);
* var fbdata = FBAppUtil.FBJsWrapper.dataFromLastCall();
* var fbresponse = FBAppUtil.FBJsWrapper.responseFromLastCall();
*/
(function (window) {
/** Local helper Buffer Class - Start **/
var LocalBuffer = function (size) {
//enforce 'new' - object creation pattern
if (!(this instanceof LocalBuffer)) {
return new LocalBuffer(size);
}
//private variables
var _buffer = {
data : [], //data fetched from the last successfull call
response : [] //response from the last call
},
_size = (function (size){
var n = parseInt(size || 10, 10);
return isNaN(n) ? 10 : n;
}(size)); //default buffer size
var _pushToBuffer = function (name, data) {
if (typeof _buffer[name][_size-1] !== 'undefined') {
_buffer[name].shift(); //remove the first element in case the buffer is full
}
_buffer[name].push(data);
};
var _readFromBuffer = function (name) {
var len = _buffer[name].length;
return len === 0 ? {} : _buffer[name][len-1]; //read the last inserted without popping
};
var _getDataFromBuffer = function () {
return _readFromBuffer("data");
};
var _getResponseFromBuffer = function () {
return _readFromBuffer("response");
};
//expose methods
this.pushToBuffer = _pushToBuffer,
this.readFromBuffer = _readFromBuffer,
this.getDataFromBuffer = _getDataFromBuffer,
this.getResponseFromBuffer = _getResponseFromBuffer
};
/** Local helper Buffer Class - End **/
/** WordPress JSON API Plugin wrapper - Start **/
var WPJsonApi;
(function () {
var instance;
WPJsonApi = function (config) {
if (!(this instanceof WPJsonApi)) {
return new WPJsonApi(config);
}
if (instance) {
return instance;
}
instance = this;
//config variables
var apiUrl, //complete url for the api
cross_domain, //jsonp cross domain calls
permalink_enabled, //whether permalink enabled
templates, //TODO: templating system
buffer_size; //size of the buffer
//private variables
var _buffer; //the LocalBuffer object
//form the final api url string for the json call
var _getControllerUrl = function (controller_name) {
var url = apiUrl; //base url
if (!permalink_enabled) {
url += "/?json=" + controller_name;
if (cross_domain) {
url += "&callback=?";
}
} else {
url += "/" + controller_name;
if (cross_domain) {
url += "/?callback=?";
}
}
return url;
};
//fetch posts using the jQuery getJSON
//push data and response to buffer
//on successfull fetch, return array of post objects to the callback
var _getRecentPosts = function (paramObj, callback) {
var url = _getControllerUrl("get_recent_posts"); //base url for the specified controller
if (typeof paramObj === 'function') {
callback = paramObj; //no parameters provided only callback
paramObj = {};
}
paramObj = paramObj || {};
$.getJSON(url, paramObj, function(data) {
if (data.status === "ok") {
_buffer.pushToBuffer("response",
{
status : "ok",
success : "Successfully fetched the post for the specified id/slug."
}
);
_buffer.pushToBuffer("data", data);
if (typeof callback === 'function') {
callback(data.posts);
}
} else if (data.status === "error") {
_buffer.pushToBuffer("response",
{
status: "error",
error : data.error
}
);
} else {
_buffer.pushToBuffer("response",
{
status: "error",
error : "Unknown error!"
}
);
}
}
);
};
//fetch post by it's id or slug using the jQuery getJSON
//push data and response to buffer
//on successfull fetch, return the post object to the callback
var _getPost = function (paramObj, callback) {
var url = _getControllerUrl("get_post"), //base url for the specified controller
id = parseInt(paramObj, 10); //assume the parameter to be id
paramObj = paramObj || {};
if (typeof paramObj !== 'object') {
if (typeof paramObj === 'number' || !isNaN(id)) {
paramObj = {id : id};
} else if (typeof paramObj === 'string') {
paramObj = {slug : paramObj};
}
}
if (isNaN(parseInt(paramObj.id, 10)) && !paramObj.slug) {
throw {
status: "error",
error : "Provide a valid id or slug to get a post."
};
}
//TODO: Avoid server hit by searching and returning the post
// from the local buffer for the specified id/slug
$.getJSON(url, paramObj, function(data) {
if (data.status === "ok") {
_buffer.pushToBuffer("response",
{
status : "ok",
success : "Successfully fetched the post for the specified id/slug."
}
);
_buffer.pushToBuffer("data", data);
if (typeof callback === 'function') {
callback(data.post);
}
} else if (data.status === "error") {
_buffer.pushToBuffer("response",
{
status: "error",
error : data.error
}
);
} else {
_buffer.pushToBuffer("response",
{
status: "error",
error : "Unknown error!"
}
);
}
}
);
};
//initialize the object and add methods to it
var _init = function (config) {
if (typeof config === 'undefined') {
throw {
status: "error",
error : "Provide a valid configuration object to initialize WPJsonApi."
};
}
apiUrl = config.apiUrl || "/api", //assume base url relative to current page
cross_domain = config.crossDomain || false, //jsonp cross domain calls
permalink_enabled = config.permalinkEnabled || true, //assume permalink enabled
templates = config.templates || {}, //TODO: templating mechanisms
buffer_size = config.bufferSize || 10, //assume buffer size to be 10
_buffer = new LocalBuffer(buffer_size); //new buffer object
//expose the methods and variables
this.getRecentPosts = _getRecentPosts; //method for fetching recent posts
this.getPost = _getPost; //method to fetch the post by id or slug
this.lastFetched = _buffer.getDataFromBuffer; //last fetched data from the buffer
this.lastResponse = _buffer.getResponseFromBuffer; //response from the last roundtrip to server
};
//init the object if config is provided while creating
if (typeof config !== 'undefined') {
_init(config);
}
//expose init
this.init = _init;
};
}());
/** WordPress JSON API Plugin wrapper - End **/
/** FB JavaScript SDK wrapper - Start **/
var FBJsWrapper;
(function () {
var instance;
FBJsWrapper = function (config) {
if (!(this instanceof FBJsWrapper)) {
return new FBJsWrapper(config);
}
if (instance) {
return instance;
}
instance = this;
//config variables
var access_token, //user access token
expires_in, //time to expire
signed_request, //the signed request object
user_id; //user id of the current connected user
//private variables
var _buffer, //the LocalBuffer object
_token_valid = true; //assume the access token to be valid
var _isTokenValid = function () {
//TODO: Implement the method to check for invalid access tokens or
// invalid calls to FB APIs
return _token_valid;
};
var _read = function (article, callback) {
//TODO: Avoid repeated code, make a generic function
var paramObj = {}; //start with an empty parameter
paramObj.article = article; //add article to the parameter object
//if token is invalid, no further calls are possible, so return
if (!_isTokenValid()) {
//TODO: Provide a better way of handling this
throw {
status: "error",
error : "Provide a valid configuration object to initialize FBJsWrapper."
};
}
if (!(!access_token || 0 === access_token.length)) {
paramObj.access_token = access_token; //access token not empty, add it to the parameter object
}
//TODO: Make a generic function to handle this call
FB.api(
'/' + user_id + '/news.reads',
'post',
paramObj,
function(response) {
var i,
message, // response error message
exists = false, //assume the words don't exist in the message
probable_words = [ "session has expired",
"session has been invalidated",
"session is invalid",
"has not authorized" ]; //list of words that may denote an invalid token
//no response, return
if (!response) {
_buffer.pushToBuffer("response",
{
status : "error",
error : "No response returned by the server!"
}
);
return;
}
//some error
if (response.error) {
message = response.error.message.toLowerCase(); //case insensetive match
for (i in probable_words) {
if (message.indexOf(probable_words[i]) > -1) {
exists = true;
break;
}
}
if (exists) {
_token_valid = false; //denotes invalid token
}
_buffer.pushToBuffer("response",
{
status : "error",
error : exists ? "Invalid access token!" : response.error.message
}
);
} else {
_buffer.pushToBuffer("response",
{
status : "ok",
success : "Successfully read the specified article."
}
);
_buffer.pushToBuffer("data", response);
if (typeof callback === 'function') {
callback(response.id);
}
}
});
};
var _unread = function (articleId, callback) {
//TODO: Avoid repeated code, make a generic function
var paramObj = {}; //start with an empty parameter
//if token is invalid, no further calls are possible, so return
if (!_isTokenValid()) {
//TODO: Provide a better way of handling this
throw {
status: "error",
error : "Provide a valid configuration object to initialize FBJsWrapper."
};
}
if (!(!access_token || 0 === access_token.length)) {
paramObj.access_token = access_token; //access token not empty, add it to the parameter object
}
//TODO: Make a generic function to handle this call
FB.api(
articleId,
'delete',
paramObj,
function(response) {
var i,
message, // response error message
exists = false, //assume the words don't exist in the message
probable_words = [ "session has expired",
"session has been invalidated",
"session is invalid",
"has not authorized" ]; //list of words that may denote an invalid token
//no response, return
if (!response) {
_buffer.pushToBuffer("response",
{
status : "error",
error : "No response returned by the server!"
}
);
return;
}
//some error
if (response.error) {
message = response.error.message.toLowerCase();//case insensetive match
for (i in probable_words) {
if (message.indexOf(probable_words[i]) > -1) {
exists = true;
break;
}
}
if (exists) {
_token_valid = false; //denotes invalid token
}
_buffer.pushToBuffer("response",
{
status : "error",
error : exists ? "Invalid access token!" : response.error.message
}
);
} else {
_buffer.pushToBuffer("response",
{
status : "ok",
success : "Successfully unread the specified article."
}
);
_buffer.pushToBuffer("data", response);
if (typeof callback === 'function') {
callback();
}
}
});
};
var _sendAppRequest = function (message, callback) {
var paramObj = {}; //start with an empty parameter
if (typeof message === 'function') { //no message only callback provided
callback = message;
message = 'Invite friends to this app.';
}
paramObj.method = 'apprequests';
paramObj.message = message.toString();
if (!(!access_token || 0 === access_token.length)) {
paramObj.access_token = access_token; //access token not empty, add it to the parameter object
paramObj.display = 'iframe'; //access token provided, iframe can be used
} else {
paramObj.display = 'popup'; //no access token present, use popup dialog
}
FB.ui(paramObj, function (request, to) {
//TODO: Handle the error conditions
_buffer.pushToBuffer("response",
{
status : "ok",
success : "Successfully sent the app request."
}
);
_buffer.pushToBuffer("data", { request: request, to: to });
if (typeof callback === 'function') {
callback(request, to);
}
});
};
var _init = function (config) {
if (typeof config === 'undefined') {
throw {
status: "error",
error : "Provide a valid configuration object to initialize FBJsWrapper."
};
}
access_token = config.accessToken || "", //assume a blank access token, will try to call FB.api without it
expires_in = config.expiresIn || 0, //jsonp cross domain calls
signed_request = config.signedRequest || {}, //signed request parameter
user_id = config.userID || 'me', //assume 'me' (for a user it's user_id but for pages and apps it might be something else)
buffer_size = config.bufferSize || 10, //default buffer size
_buffer = new LocalBuffer(buffer_size); //local buffer object
//expose the methods and variables
this.sendAppRequest = _sendAppRequest; //method for sending the app request from a dialog
this.share = _read; //method to read an article, news.read
this.unshare = _unread //method to unread a previously read article
this.dataFromLastCall = _buffer.getDataFromBuffer; //last fetched data from the buffer
this.responseFromLastCall = _buffer.getResponseFromBuffer; //response from the last roundtrip to server
};
//init the object if config is provided while creating
if (typeof config !== 'undefined') {
_init(config);
}
//expose init
this.init = _init;
};
}());
/** FB JavaScript SDK wrapper - End **/
/** The main Utility Class - Start **/
var FBAppUtil;
(function () {
var instance;
FBAppUtil = function () {
if (!(this instanceof FBAppUtil)) {
return new FBAppUtil();
}
if (instance) {
return instance;
}
instance = this;
var _init = function (config) {
if (typeof config !== 'undefined'){
if (typeof config.WPJsonApi !== 'undefined') {
this.WPJsonApi = new WPJsonApi(config.WPJsonApi); //WPJsonApi configuration provided while init
} else {
this.WPJsonApi = new WPJsonApi();
}
if (typeof config.FBJsWrapper !== 'undefined') {
this.FBJsWrapper = new FBJsWrapper(config.FBJsWrapper); //FBJsWrapper configuration provided while init
} else {
this.FBJsWrapper = new FBJsWrapper();
}
} else {
this.WPJsonApi = new WPJsonApi();
this.FBJsWrapper = new FBJsWrapper();
}
};
//expose the methods and variables
this.init = _init;
};
})();
/** The main Utility Class - End **/
// Expose the Utility to the global object
window.FBAppUtil = new FBAppUtil();
})(window);
FBAppUtil.FBJsWrapper.share(someArticleUrl, someCallback);
FBAppUtil.FBJsWrapper.unshare(someId, someCallback);
With articles, facebook recommend that you place the trigger on a 'readmore' or some other kind of 'next page' button/link so that there is a very high probability that the user is actually reading the article.
To achieve this, you could either;
place a small form in/on every article, with a radio button/ckeckbox
asking do you want to publish this (Yes/No) -- OR
add a section to your user preferences on your site with the same
type of form as in 1. Then you could take this a bit further, giving
them the option to set a preference for each section, category or
page of your site.
Either way, however you decide to invoke the action (readmore or next page etc.), that needs to submit the form, check whether the answer is yes/no (publish or not) then publish the action accordingly.
Using session variables might be an option too! It would be pretty neat to offer users the ability to publish/not publish actions for their current session. This could be handled in the same form being presented to the user at the start of each session and even give them a 3rd option, 'ask me everytime'.
As far as deleting an existing action is concerned, this can be done by getting the instance of the action and running the delete command in a similar way to that of creation. As I said, this depends on how you're triggering - CURL, js api etc... Check the FB dev docs for the method you're using.
Hope this helps!
Gez
On/Off publishing is not related to Facebook Graph API, you have to implement this on your side. there should be a flag viz is_publish related to user table. If user give permission by click on the user On/Off button than you can publish the action, and one thing more action can be published only if user stay on article page for at-least 10 seconds and for this you can do
setTimeout('readArticle()', 10000);
Hope this helps!