Access Sharepoint (sp.js) from an Outlook 365 addin - sharepoint

Goal
Access Sharepoint via JSOM from within an Office 365 Outlook Add-in.
Problem
I tried multiple things, most of them lead to a "403 - forbidden".
Code
// The Office initialize function must be run each time a new page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
var element = document.querySelector('.ms-MessageBanner');
messageBanner = new fabric.MessageBanner(element);
messageBanner.hideBanner();
loadProps();
//app.initialize();
//Load everything required for sharepoint
// Get the URI decoded URLs.
var hostweburl = "https://-tenant here-.sharepoint.com";
// The js files are in a URL in the form:
// web_url/_layouts/15/resource_file
var scriptbase = hostweburl + "/_layouts/15/";
// Load the js files and continue to
// the execOperation function.
$.getScript(scriptbase + "SP.Runtime.js",
function () {
$.getScript(scriptbase + "SP.js", execOperation);
}
);
});
};
// Function to execute basic operations.
function execOperation() {
//Assure it is running - Doesn't work: SP.SOD does not exist.
//SP.SOD.executeFunc('sp.js', 'SP.ClientContext', Office.initialize);
var context = new SP.ClientContext("https://-that same tenant-.sharepoint.com"); // I tired: Get_current(), but I get an error that there is no "current".
var web = context.get_web();
var user = web.get_currentUser();
context.load(user);
context.executeQueryAsync(Function.createDelegate(this, this.onGetUserQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
debugger;
}
function onGetListQuerySucceeded() {
console.log("Hello" + user.get_title());
console.log(user);
debugger;
}
function onQueryFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

Related

Angular download file from byte array sent from node.js

I think I'm very close to what I want to do. I have the following api get method in node.js that is retrieving a file varbinary(MAX) from an SQL Server database. It was converted from a base64 encoded string before inserted so the Content Type information was stripped from the string.
node.js
router.get('/getFile', (req, res) => {
console.log("Calling getFile for file " + req.query.serialNumber + ".")
var serialNumber = req.query.serialNumber;
let request = new sql.Request(conn);
request.query('SELECT FileName + \'.\' + FileExtension AS \'File\', FileType, ContentType, SerialNumber, Chart ' +
'FROM dbo.ChangeFiles ' +
'WHERE SerialNumber = ' + serialNumber)
.then(function (recordset) {
log("Successfully retrieved file " + recordset[0].SerialNumber + " from database.");
log("Length of blob " + recordset[0].File + " is " + recordset[0].Chart.length)
res.status(200);
res.setHeader('Content-Type', recordset[0].ContentType);
res.setHeader('Content-Disposition', 'attachment;filename=' + recordset[0].File);
res.end(Buffer.from((recordset[0].Chart)));
}).catch(function (err) {
log(err);
res.status(500).send("Issue querying database!");
});
});
That works fine, but what to do in Angular to retrieve it and prompt for a download for the user has not been clear for me, nor has there been a lot as far as help/resources online. Here is what I have so far in my service class.
fileDownload.service.ts
downloadFile(serialNumber: string): Observable<any> {
return this.http.get(this.baseURL + '/getFile', { params: { serialNumber: serialNumber } })
.map(this.extractFile);
}
private extractFile(response: Response) {
const file = new Blob([response.blob]);
FileSaver.saveAs(file);
// const url = window.URL.createObjectURL(file);
// window.open(url);
return file;
}
As you can see I've tried a couple of approaches. The commented out portion of the extractFile method didn't work at all, and using the FileSaver.saveAs function produces a file download of an unknown type, so the headers sent from node.js didn't seem to affect the file itself.
Would someone be able to advise how to proceed in Angular with what is successfully being sent from node.js so that I can successfully download the file, regardless of type?
Thanks so much in advance.
I got it working afterall. I had to rework the api call so that it sent all of the file information separately so that the MIME type, and file name can be assigned to the file on the client side in the component class. For some reason when I tried to do so all in the api, it wouldn't work so that was my work around. So here is what works for me.
node.js api
router.get('/getFile', (req, res) => {
console.log("Calling getFile for file " + req.query.serialNumber + ".")
var serialNumber = req.query.serialNumber;
let request = new sql.Request(conn);
request.query('SELECT FileName + \'.\' + FileExtension AS \'File\', FileType, ContentType, SerialNumber, Chart ' +
'FROM dbo.ChangeFiles ' +
'WHERE SerialNumber = ' + serialNumber)
.then(function (recordset) {
log("Successfully retrieved file " + recordset[0].SerialNumber + " from database.");
log("Length of blob " + recordset[0].File + " is " + recordset[0].Chart.length)
res.send(recordset[0]);
}).catch(function (err) {
log(err);
res.status(500).send("Issue querying database!");
});
});
component class
downloadFile(serialNumber: string): void {
this.changeService.downloadFile(serialNumber).subscribe((res: any) => {
const ab = new ArrayBuffer(res.Chart.data.length);
const view = new Uint8Array(ab);
for (let i = 0; i < res.Chart.data.length; i++) {
view[i] = res.Chart.data[i];
}
const file = new Blob([ab], { type: res.ContentType });
FileSaver.saveAs(file, res.File);
console.log(res);
});
}
service class
downloadFile(serialNumber: string): Observable<any> {
return this.http.get(this.baseURL + '/getFile', { params: { serialNumber: serialNumber } })
.map(this.extractFile);
}
private extractFile(response: Response) {
// const file = new Blob([response.blob]);
// FileSaver.saveAs(file);
// const url = window.URL.createObjectURL(file);
// window.open(url);
const body = response.json();
return body || {};
}
Update your code to call subscribe instead of map

Nested ClientContext executeQueryAsync()

I have a requirement to change the value of a SharePoint List Workflow Status Column.
Lets assume we could have 2 to many workflow columns in one list (The below code works when a list has 1 workflow status column).
The issue is when it has multiple Workflow Status Column, it updates only the last column as there are nested executeQueryAsync calls.
I have tried the same with Deferred/Promise as well - see the second code block.
<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(initialize, "sp.js");
//Get our objects
function initialize() {
var t0 = performance.now();
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getById(_spPageContextInfo.pageListId)
var fieldCollection = list.get_fields();
var spField;
context.load(fieldCollection);
context.executeQueryAsync(Function.createDelegate(this, onFieldCollectionSucceeded), Function.createDelegate(this, onListDataFailed));
function onFieldCollectionSucceeded(sender, args) {
var fieldEnumerator = fieldCollection.getEnumerator();
while (fieldEnumerator.moveNext()) {
var field = fieldEnumerator.get_current();
var fType = field.get_fieldTypeKind();
//Value 28 correspond to SPWorkflowStatus
if (fType === 28) {
console.log(fieldEnumerator.get_current().get_title() + ": " + fType);
spField = field;
context.load(spField, "SchemaXml");
context.executeQueryAsync(Function.createDelegate(this, onFieldSucceeded), Function.createDelegate(this, onListDataFailed));
}
}
}
function onFieldSucceeded(sender, args) {
console.log("Schema changes processing for column: " + spField.get_title());
var schema = spField.get_schemaXml();
var newSchema = schema.replace('Abgelehnt', 'Rejected');
spField.set_schemaXml(newSchema);
spField.update();
context.executeQueryAsync(
Function.createDelegate(this, schemaChanged),
Function.createDelegate(this, onListDataFailed));
};
function schemaChanged(sender, args) {
console.log('Field Schema Updated');
}
function onListDataFailed(sender, args) {
console.log('List Data fetch failed. ' + args.get_message() + 'n' + args.get_stackTrace());
};
var t1 = performance.now();
console.log("Total Execution Time " + (t1 - t0) + " milliseconds.")
};
<script type="text/javascript">
//http://johnliu.net/blog/2015/12/convert-sharepoint-jsoms-executequeryasync-to-promise-in-the-prototype
ExecuteOrDelayUntilScriptLoaded(registerJsomPromise, "sp.js");
ExecuteOrDelayUntilScriptLoaded(initialize, "sp.js");
function initialize() {
var t0 = performance.now();
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getById(_spPageContextInfo.pageListId)
var fieldCollection = list.get_fields();
var spField;
context.load(fieldCollection);
context.executeQueryAsync(Function.createDelegate(this, onFieldCollectionSucceeded), Function.createDelegate(this, onListDataFailed));
};
function onFieldCollectionSucceeded(sender, args) {
var fieldEnumerator = fieldCollection.getEnumerator();
while (fieldEnumerator.moveNext()) {
var field = fieldEnumerator.get_current();
var fType = field.get_fieldTypeKind();
//Value 28 correspond to SPWorkflowStatus
if (fType === 28) {
console.log(fieldEnumerator.get_current().get_title() + ": " + fType);
spField = field;
context.load(spField, "SchemaXml");
var promise = context.executeQuery();
setTimeout(function () { }, 500);
promise.done(function () {
console.log("Schema changes processing for column: " + spField.get_title());
var schema = spField.get_schemaXml();
var newSchema = setSchema(schema);
spField.set_schemaXml(newSchema);
spField.update();
context.load(spField);
context.executeQueryAsync(
Function.createDelegate(this, schemaChanged),
Function.createDelegate(this, onListDataFailed));
});
promise.then(function (sArgs) {
console.log('Field Schema Updated');
//sArgs[0] == success callback sender
//sArgs[1] == success callback args
}, function (fArgs) {
//fArgs[0] == fail callback sender
//fArgs[1] == fail callback args.
//in JSOM the callback args aren't used much -
//the only useful one is probably the get_message()
//on the fail callback
var failmessage = fArgs[1].get_message();
});
//context.executeQueryAsync(Function.createDelegate(this, onListDataSucceeded), Function.createDelegate(this, onListDataFailed));
}
}
}
function schemaChanged(sender, args) {
console.log('Field Schema Updated');
}
function onListDataFailed(sender, args) {
console.log('List Data fetch failed. ' + args.get_message() + 'n' + args.get_stackTrace());
};
var t1 = performance.now();
console.log("Total Execution Time " + (t1 - t0) + " milliseconds.")
function registerJsomPromise() {
SP.ClientContext.prototype.executeQuery = function () {
var deferred = $.Deferred();
this.executeQueryAsync(
function () { deferred.resolve(arguments); },
function () { deferred.reject(arguments); }
);
return deferred.promise();
};
}
The execute query async changes the state of iterator. This is why you have a weird behavior. You should load your fields, update those, and do one single execute at the end out of the while to actually update stuff. The only issue with that approach is that if you're code fails at some point, all the updates will be pending. So careful error handling is required.

Getting User Level Permissions on List in sharepoint online using Javascript and Rest API

I have implemented sharepoint hosted app for getting user level permissions on a list using javascript and rest api.
Here is my code,
'use strict';
var hostweburl;
var appweburl;
var context = SP.ClientContext.get_current();
var user = context.get_web().get_currentUser();
// This code runs when the DOM is ready and creates a context object which is needed to use the SharePoint object model
$(document).ready(function () {
hostweburl = _spPageContextInfo.siteAbsoluteUrl;
appweburl = _spPageContextInfo.siteServerRelativeUrl;
alert(hostweburl);
alert(appweburl);
getListUserEffectivePermissions();
});
function getListUserEffectivePermissions() {
var listTitle = 'MyList_Deepa';
//var account = 'i%3A0%23.f%7Cmembership%7Cuser%40abhishek#sarasamerica.com&#target=';
var endpointUrl = "'" + appweburl + "'/_api/SP.AppContextSite(#target)/web/lists/getbytitle('" + listTitle + "')/getusereffectivepermissions(#user)?#user='i%3A0%23.f%7Cmembership%7Cuser%40abhishek#sarasamerica.com&#target='" + hostweburl + "'";
//var endpointUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + MyList_Deepa + "')/getusereffectivepermissions(#u)?#u='" + encodeURIComponent(account) + "'";;
return $.ajax({
url: endpointUrl,
dataType: 'json',
headers: {
"accep": "application/json;odata=verbose",
"X-RequestDigest": $("#_REQUESTDIGEST").val()
}
});
}
function parseBasePermissions(value) {
var permissions = new SP.BasePermissions();
permissions.initPropertiesFromJson(value);
var permLevels = [];
for (var permLevelName in SP.PermissionKind.prototype) {
if (SP.PermissionKind.hasOwnProperty(permLevelName)) {
var permLevel = SP.PermissionKind.parse(permLevelName);
if (permissions.has(permLevel)) {
permLevels.push(permLevelName);
}
}
}
return permLevels;
}
getListUserEffectivePermissions().done(function (data) {
var roles = parseBasePermissions(data.d.GetUserEffectivePermissions);
console.log(roles);
});
Error: Failed to load resource: the server responded with a status of 404 (Not Found).
Please anybody can give solution to resolve the problem.

Automate login + posting to forum using nodejs + phantomjs

I am trying to automate login to a forum ( yet another forum , test forum available here: http://testforum.yetanotherforum.net/ ) using node-phantom.
This is my code:
/**
* Yet Another Forum Object
*
*
*/
var yaf = function() {}; //
module.exports = new yaf();
var phantom = require('node-phantom');
//var sleep = require('sleep');
var configTestForum = {
loginUrl: "http://testforum.yetanotherforum.net/login",
loginFormDetail: {
usernameBox: 'forum_ctl03_Login1_UserName', // dom element ID
passwordBox: 'forum_ctl03_Login1_Password',
submitButton: 'forum_ctl03_Login1_LoginButton'
},
loginInfo: {
username: 'testbot',
password: 'testbot123'
}
};
var config = configTestForum;
// program logic
yaf.prototype.login = function(username, password, cb) {
var steps = [];
var testindex = 0;
var loadInProgress = false; //This is set to true when a page is still loading
/*********SETTINGS*********************/
phantom.create(function(error, ph) {
ph.createPage(function(err, page) {
page.set('settings', {
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0",
javascriptEnabled: true,
loadImages: false,
});
phantom.cookiesEnabled = true;
phantom.javascriptEnabled = true;
/*********SETTINGS END*****************/
console.log('All settings loaded, start with execution');
/**********DEFINE STEPS THAT FANTOM SHOULD DO***********************/
steps = [
//Step 1 - Open Amazon home page
function() {
console.log('Step 1 - Open Login Page');
page.open(config.loginUrl, function(status) {
});
},
function() {
console.log('Step 2 - Populate and submit the login form');
var submitForm = function(config) {
console.log('Form Submit 1 ( putting login )');
document.getElementById(config.loginFormDetail.usernameBox).value = config.loginInfo.username;
console.log('Form Submit 2 ( putting pass )');
//jQuery('#' + config.loginFormDetail.passwordBox).val(config.loginInfo.password);
//jQuery('#' + config.loginFormDetail.usernameBox).val(config.loginInfo.password);
document.getElementById(config.loginFormDetail.passwordBox).value = config.loginInfo.password;
console.log('Form Submit 3 ( clicking button ) ');
document.getElementById(config.loginFormDetail.submitButton).click();
//var clickElement = function (el) {
// var ev = document.createEvent("MouseEvent");
// ev.initMouseEvent(
// "click",
// true /* bubble */, true /* cancelable */,
// window, null,
// 0, 0, 0, 0, /* coordinates */
// false, false, false, false, /* modifier keys */
// 0 /*left*/, null
// );
// el.dispatchEvent(ev);
// console.log('dispatched!');
//};
//document.getElementById(config.loginFormDetail.submitButton).click();
//clickElement(jQuery("#forum_ctl03_Login1_LoginButton")[0]);
//
//var form = document.getElementById('form1');
////var list = function(object) {
//// for(var key in object) {
//// console.log(key);
//// }
////};
////list(form);
//
//
//// jQuery('#form1').submit();
//
//form.submit();
//
//document.forms[0].submit();
//HTMLFormElement.prototype.submit.call(jQuery('form')[0]);
console.log('Form Has Been Submitted <-----------------');
};
var subittedForm = function(err, retVal) {
console.log('Form Submit error : ' + err);
console.log('Form Submit returned : ' + retVal);
};
//page.evaluateJavaScript(jsCode);
page.evaluate(submitForm, subittedForm, config);
},
//Step 3 - wait for submit form to finish loading..
function() {
//sleep.sleep(5);
console.log("Step 3 - wait for submit form to finish loading..");
//sleep.sleep(3);
page.render('loginComplete.png');
page.get('cookies', function(err, cookies) {
// console.log(cookies);
});
page.evaluate(function() {
console.log(document.URL);
});
},
function() {
console.log('Exiting');
}
];
/**********END STEPS THAT FANTOM SHOULD DO***********************/
//Execute steps one by one
interval = setInterval(executeRequestsStepByStep, 500);
function executeRequestsStepByStep() {
if (loadInProgress == false && typeof steps[testindex] == "function") {
//console.log("step " + (testindex + 1));
steps[testindex]();
testindex++;
}
if (typeof steps[testindex] != "function") {
console.log("test complete!");
ph.exit();
// cb(ph);
cb('done');
}
}
page.onLoadStarted = function() {
loadInProgress = true;
console.log('Loading started');
};
page.onLoadFinished = function() {
loadInProgress = false;
console.log('Loading finished');
};
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.onError = function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function+'")' : ''));
});
}
console.error('\n' + msgStack.join('\n'));
};
page.onResourceError = function(resourceError) {
console.error('Unable to load resource (#' + resourceError.id + ' URL:' + resourceError.url + ')');
console.error('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
};
page.onResourceTimeout = function(msg) {
console.error('onResourceTimeout!!>' + msg);
};
page.onAlert = function(msg) {
console.error('onAlert!!> ' + msg);
};
});
});
// var page = webPage.create();
};
Output of the code is :
Step 1 - Open Login Page
Loading started
Loading finished
Step 2 - Populate and submit the login form
Form Submit 1(putting login)
Form Submit 2(putting pass)
Form Submit 3(clicking button)
Form Has Been Submitted < -----------------
Form Submit error: null
Form Submit returned: null
Unable to load resource(#14 URL:http://testforum.yetanotherforum.net/login?g= login &= )
Error code: 5.Description: Operation canceled
ERROR: TypeError: 'undefined'
is not an object(evaluating 'Sys.WebForms.Res.PRM_TimeoutError'), [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
Step 3 - wait
for submit form to finish loading..
http: //testforum.yetanotherforum.net/login
Exiting
test complete!
done
Option client store expiration is not valid.Please refer to the README.
Process finished with exit code 0
What it attempts to do is :
Open login page : http://testforum.yetanotherforum.net/login?returnurl=%2fcategory%2f1-Test-Category
Try to login using login name / password and then clicking the button.
Take a screenshot and Retrieve the cookies ( containing the auth data )
Currently it is getting stuck in step 2. It can populate the login and password box, but no kind of clicking or submitting the form is working.Basically it is stuck as shown:
(as you can see the login / password are filled correctly ).
By looking at step 2 ( Step 2 - Populate and submit the login form ) in my code, do you notice anything obvious? or am I doing something wrong in the page settings?

How to get with SharePoint client APIs all user profiles?

For implementing a birthday's SharePoint 2013 app I need to get all user profiles from a site collection. For this purpose I'd like to use a (or multiple) client API(s). See http://msdn.microsoft.com/en-us/library/jj163800.aspx#bkmk_APIversions.
Unfortunately I couldn't find in the APIs description an equivalent of Microsoft.Office.Server.UserProfiles. There are in Microsoft.SharePoint.Client.UserProfiles.PeopleManager two methods, GetUserProfilePropertiesFor and GetUserProfilePropertyFor, that only get a single user profile.
So my question is: how to get with CSOM, JSOM, REST (or any client side technology) all user profiles in site collection?
Since CSOM provides methods for operations related to people per user scope, you could retrieve all site users first using SP.Web.siteUsers property. and then use
SP.UserProfiles.PeopleManager.getUserProfilePropertyFor Method to get BirthDay property as demonstrated below:
//Get Birthday User Profile Property for Site Users
function getUsersBirthdays(Success,Error) {
var clientContext = new SP.ClientContext.get_current();
var web = clientContext.get_web();
var users = web.get_siteUsers();
clientContext.load(users);
clientContext.executeQueryAsync(
function() {
var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);
var personsProperties = [];
for(var i = 0; i < users.get_count();i++)
{
var user = users.getItemAtIndex(i);
var personBirthday = peopleManager.getUserProfilePropertyFor(user.get_loginName(),'SPS-Birthday');
personsProperties.push(personBirthday);
}
clientContext.executeQueryAsync(
function() {
Success(personsProperties);
},
Error);
},
Error);
}
//Usage
var scriptbase = _spPageContextInfo.webAbsoluteUrl + '/_layouts/15/';
$.getScript(scriptbase + 'SP.js', function () {
$.getScript(scriptbase + 'SP.UserProfiles.js', function () {
getUsersBirthdays(function(usersProperties){
for(var i = 0; i < usersProperties.length;i++)
{
console.log(usersProperties[i].get_value());
}
},
function(sender,args){
console.log(args.get_message());
});
});
});
This also should work for SP2013
function GetUsersGroups(){
ClientContext context = new Microsoft.SharePoint.Client.ClientContext("http://SPSite");
GroupCollection groupCollection = context.Web.SiteGroups;
context.Load(groupCollection,
groups = > groups.Include(group = > group.Users));
context.ExecuteQuery();
foreach (Group group in groupCollection)
{
UserCollection userCollection = group.Users;
foreach (User user in userCollection)
{
MessageBox.Show("User Name: " + user.Title + " Email: " + user.Email + " Login: " + user.LoginName);
}
}
//Iterate the owners group
Group ownerGroup = context.Web.AssociatedOwnerGroup;
context.Load(ownerGroup);
context.Load(ownerGroup.Users);
context.ExecuteQuery();
foreach (User ownerUser in ownerGroup.Users)
{
MessageBox.Show("User Name: " + ownerUser.Title + " Email: " + ownerUser.Email + " Login: " + ownerUser.LoginName);
}
context.Dispose();
}

Resources