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.
Related
Here is what I'm trying to do:
Start a looping (10x)
Select on sql to return 1 register (select top 1 where 'running' is null)
Sql update 'running' status to 'running'
If the record is null, I access an API and get some data
The result is updated on the initial sql record (set running = 'ok')
End looping (start over)
Thing is, node.js does not wait for start over, it does everything at the same time. That way, 'running' is always null.
var express = require('express');
var app = express();
var c_MyApi = require('./controller/call_MyApi');
var mongo = require('./controller/crud_mongo');
var c_email = require('./controller/api_email_verify');
var c_sql = require('./controller/consulta_sql');
var MyLink = '',
id = 0;
for( var i = 0 ; i < 10 ; i++){
c_sql.busca().then(function(res) {
MyLink = res[0].MyLink;
id = res[0].id;
c_sql.marca(id).then(
c_MyApi.busca(MyLink, function(a) {
if (a == 0) {
c_sql.atualiza(id, 'sem_email', 's/e');
}
if (a == 1) {
c_sql.atualiza(id, 'link_errado', 'l/e');
} else {
for (var i = 0; i < a.length; i++) {
var email = a[i].address;
c_email.busca(email, function(e_valid) {
c_sql.atualiza(id, email, e_valid)
})
}
}
})
)
})
}
}
//consulta_sql.js
var sql = require("seriate");
var search = function() {
var exec = 'select top 1 MyLink, id from sys_robo_fila where done is null';
sql.setDefaultConfig(config);
return sql.execute({
query: exec
});
}
var usado = function(id) {
var exec = "update sys_robo_fila set done = 'r' where id = " + id + "";
sql.setDefaultConfig(config);
return sql.execute({
query: exec
});
}
var update = function(id, email, valid) {
var exec = "update sys_robo_fila set email = '" + email + "' , validacao = '" + valid + "' , done = 'ok' where id = " + id + "";
sql.setDefaultConfig(config);
return sql.execute({
query: exec
});
}
module.exports = {
busca: search,
atualiza: update,
marca: usado
}
Any sugestions?
The call to c_sql.busca() is returning a Promise immediately and then continuing on to the next loop before then() is called, which is why they seem to run at the same time (they are actually just running very quickly, but before the Promise is resolved.
If you want this to run synchronously, one loop at a time, I would recommend using a recursive function to not start the loop again until the Promises have resolved.
let count = 0;
function doSomething() {
// this returns immediately, before .then() executes
return c_sql.busca()
.then(() => {
// do some more stuff after c_sql.busca() resolves
return c_sql.busca();
})
.then(() => {
// increment your count
count += 1
if (count<10) {
// less than 10 runs call the function again to start over
return doSomething();
}
});
}
This article might be helpful in understanding Promises: https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
I recently stating coding in node.js and might be a very simple question.
Trying to write a XML parser/validator to validate xml schema and values against values/ xpath stored in an excel sheet.
Now once the validation function is complete I want to call a printResult function to print final result. However if I try to call the function immediately after the first function .. its printing variables initial values and if called within the first which is iterating though the number of xpaths present in excel sheet and printing result with increments.
var mocha = require('mocha');
var assert = require('chai').assert;
var fs = require('fs');
var parseString = require('xml2js').parseString;
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;
var XLSX = require('xlsx');
var Excel = require("exceljs");
var should = require('chai').should();
var HashMap = require('hashmap');
var colors = require('colors');
require('/xmlValidator/dbConnect.js');
var map = new HashMap();
var elementMap = new HashMap();
var resultValue;
//console.log('hello'.green);
map.set("PASS", 0);
map.set("FAIL", 0);
map.set("INVALID_PATH", 0);
function computeResult(elementPath, result) {
var pass = map.get("PASS");
var fail = map.get("FAIL");
var invalidPath = map.get("INVALID_PATH");
elementMap.set(elementPath, result);
if (result == "PASS") {
pass++;
map.set("PASS", pass);
} else if (result == "FAIL") {
fail++;
map.set("FAIL", fail);
} else {
invalidPath++;
map.set("INVALID_PATH", invalidPath)
}
printResult();
}
function printResult() {
var pass = map.get("PASS");
var fail = map.get("FAIL");
var invalidPath = map.get("INVALID_PATH");
console.log(("PASS Count :" + pass).green);
console.log(("FAIL Count :" + fail).red);
console.log(("Inavlid Path :" + invalidPath).yellow);
elementMap.forEach(function(value, key) {
if (value == "INVALID_PATH")
console.log((key + ":" + value).yellow);
else if (value == "FAIL")
console.log((key + ":" + value).red);
else
console.log(key + ":" + value);
});
}
var workbook = new Excel.Workbook();
workbook.xlsx.readFile('utils/' + process.argv[2])
.then(function() {
var worksheet = workbook.getWorksheet(1);
worksheet.eachRow(function(row, rowNumber) {
//console.log(rowNumber);
var row = worksheet.getRow(rowNumber);
var dataPath1 = row.getCell("A").value;
var dataPath2 = row.getCell("B").value;
var dataPath = dataPath1 + dataPath2;
//console.log(dataPath);
var dataValue = row.getCell("D").value;
var flag = row.getCell("E").value;
//console.log(flag)
//console.log(dataValue);
if (!flag)
validate(dataPath, dataValue, rowNumber);
//else console.log("NOT EXECUTED" + rowNumber)
});
})
function validate(dataPath, dataValue, rowNumber) {
var fail = 0;
fs.readFile('utils/' + process.argv[3], 'utf8', function(err, data) {
if (err) {
console.log("ERROR ERROR ERROR ERROR ");
return console.log(err);
}
var doc = new dom().parseFromString(data);
var subId = String(xpath.select1(dataPath, doc));
if (subId == "undefined") {
/*console.log('undefined caught');
console.log("row number :" + rowNumber);*/
var resultValue = "INVALID_PATH";
computeResult(dataPath, resultValue);
} else {
var subId = xpath.select1(dataPath, doc);
var value = subId.lastChild.data;
/*console.log("row number :" + rowNumber);
console.log("actual value: " + value);
console.log("expected value:" + dataValue );*/
if (dataValue == null) {
assert.notEqual(value, dataValue, "value not found");
resultValue = "PASS";
computeResult(dataPath, resultValue);
} else {
if (value == dataValue)
resultValue = "PASS";
else resultValue = "FAIL";
computeResult(dataPath, resultValue);
}
}
});
}
In the code above i want to call printResult() function after validate function is completely executed (workbook.xlsx.readFile)
Can some one please help me out how to use done () function or make sync call ?
If fs.readFileAsync('utils/' + process.argv[3], 'utf8') can be executed once, then validate() will be completely synchronous and calling printResult() after the verification loop will be trivial.
In the main routine, you can simply aggregate two promises ...
var promise1 = workbook.xlsx.readFile();
var promise2 = fs.readFileAsync(); // requires `fs` to be promisified.
... before embarking on the verification loop.
Promise.all([promise1, promise2]).spread(/* verify here */);
Also a whole bunch of tidying can be considered, in particular :
establishing "PASS", "FAIL" and "INVALID_PATH" as constants to avoid lots of repetitive string creation,
using js plain objects in lieu of hashmap,
building map from elementMap inside the print function.
having validate() return its result and building elementMap in the main routine
Here's the whole thing, about as tight as I can get it. I may have made a few assumptions but hopefully not too many bad ones ...
var mocha = require('mocha');
var assert = require('chai').assert;
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs")); // allow Bluebird to take the pain out of promisification.
var parseString = require('xml2js').parseString;
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;
var XLSX = require('xlsx');
var Excel = require("exceljs");
var should = require('chai').should();
// var HashMap = require('hashmap');
var colors = require('colors');
require('/xmlValidator/dbConnect.js');
const PASS = "PASS";
const FAIL = "FAIL";
const INVALID_PATH = "INVALID_PATH";
function printResult(elementMap) {
var key, result,
map = { PASS: 0, FAIL: 0, INVALID_PATH: 0 },
colorNames = { PASS: 'black', FAIL: 'red', INVALID_PATH: 'yellow' };
for(key in elementMap) {
result = elementMap[key];
map[(result === PASS || result === FAIL) ? result : INVALID_PATH] += 1;
console.log((key + ": " + result)[colorNames[result] || 'black']); // presumably colors can be applied with associative syntax? If so, then the code can be very concise.
}
console.log(("PASS Count: " + map.PASS)[colorNames.PASS]);
console.log(("FAIL Count: " + map.FAIL)[colorNames.FAIL]);
console.log(("Inavlid Path: " + map.INVALID_PATH)[colorNames.INVALID_PATH]);
}
function validate(doc, dataPath, dataValue) {
var subId = xpath.select1(dataPath, doc),
value = subId.lastChild.data,
result;
if (String(subId) == "undefined") {
result = INVALID_PATH;
} else {
if (dataValue === null) {
assert.notEqual(value, dataValue, "value not found"); // not too sure what this does
result = PASS;
} else {
result = (value === dataValue) ? PASS : FAIL;
}
}
return result;
}
//Main routine
var workbook = new Excel.Workbook();
var promise1 = workbook.xlsx.readFile('utils/' + process.argv[2]); // from the question, workbook.xlsx.readFile() appears to return a promise.
var promise2 = fs.readFileAsync('utils/' + process.argv[3], 'utf8');
Promise.all([promise1, promise2]).spread(function(data2, data3) {
var worksheet = workbook.getWorksheet(1),
doc = new dom().parseFromString(data3),
elementMap = {};
worksheet.eachRow(function(row, rowNumber) {
// var row = worksheet.getRow(rowNumber); // row is already a formal variable ???
var dataPath, dataValue;
if (!row.getCell('E').value)
dataPath = row.getCell('A').value + row.getCell('B').value;
dataValue = row.getCell('D').value;
elementMap[dataPath] = validate(doc, dataPath, dataValue);
});
printResult(elementMap);
});
Untested so may not run but at least you can raid the code for ideas.
I am enumerating all terms using SP.Taxonomy.js JSOM in SharePoint.While enumerating I want to check if currentTerm has children or not.I need some property to check like children count.How can I do this with minimum round trip to server.
I am using following code get taxonomy and it is working fine.
Please help
$(document).ready(function () {
ExecuteOrDelayUntilScriptLoaded(function () {
SP.SOD.registerSod('sp.taxonomy.js', "/_layouts/15/sp.taxonomy.js");
`SP.SOD.executeFunc('sp.taxonomy.js', false, Function.createDelegate(this,`
function () {
var context = SP.ClientContext.get_current();
var taxonomySession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
var termStore = taxonomySession.get_termStores().getByName("Taxonomy_qeFlDdEX32yZ3Q7EpEIeMQ==");
var termSet = termStore.getTermSet("ed6d3beb-6a49-4444-bc5d-456f747e139d");
var terms = termSet.getAllTerms();
context.load(terms);
context.executeQueryAsync(Function.createDelegate(this, function (sender, args) {
var termsEnumerator = terms.getEnumerator();
var menuItems = new Array();
while (termsEnumerator.moveNext()) {
var currentTerm = termsEnumerator.get_current();
var targetGroups = document.getElementById("selectTaxonomy");
var taxoGroup = document.createElement("option");
taxoGroup.text = currentTerm.get_name();
targetGroups.add(taxoGroup);
}
}), Function.createDelegate(this, function (sender, args) {
alert('The error has occured: ' + args.get_message());
}));
}));
},"sp.js")
});
You could use SP.Taxonomy.Term.termsCount property to get the number of child Term objects, for example:
var termSetId = '--guid goes here--';
getTerms(termSetId,
function(terms){
for(var i = 0;i < terms.get_count();i++){
var term = terms.get_item(i);
var hasChildTerms = (term.get_termsCount() > 0);
//...
}
},
function(sender,args)
{
console.log(args.get_message());
});
where
function getTerms(termSetId,success,error) {
var context = SP.ClientContext.get_current();
var taxSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
var termStore = taxSession.getDefaultSiteCollectionTermStore();
var termSet = termStore.getTermSet(termSetId);
var terms = termSet.getAllTerms();
context.load(terms);
context.executeQueryAsync(function () {
success(terms)
},
error);
}
Some recommendations
1) Prefer SP.SOD.loadMultiple function for loading multiple libraries, for example
SP.SOD.registerSod('SP.ClientContext', SP.Utilities.Utility.getLayoutsPageUrl('sp.js'));
SP.SOD.registerSod('SP.Taxonomy.TaxonomySession', SP.Utilities.Utility.getLayoutsPageUrl('sp.taxonomy.js'));
SP.SOD.loadMultiple(['SP.ClientContext', 'SP.Taxonomy.TaxonomySession'], function(){
//your code goes here
});
2) Avoid any hard-coding, for example:
/_layouts/15/sp.taxonomy.js -> SP.Utilities.Utility.getLayoutsPageUrl('sp.taxonomy.js')
Replace the line:
var termStore = taxonomySession.get_termStores().getByName("Taxonomy_qeFlDdEX32yZ3Q7EpEIeMQ==");
with this one:
var termStore = taxonomySession.getDefaultSiteCollectionTermStore();
Function.createDelegate Function could be avoided in most cases
jsfiddle link: http://jsfiddle.net/T8ee7/
When I call Knockout's subscribe method is there a way I can get both the previous and new value? Right now, I can only call get these values separately.
I want to trigger some code if the old and new value are different.
I suppose I could do the following, but it can get messy...
(http://jsfiddle.net/MV3fN/)
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
this.pageNumber = ko.observable(pageNumber || 1);
this.numberOfPages = ko.observable(1);
this.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
};
var _pagedRequest = new sv.PagedRequest();
var oldValue;
_pagedRequest.pageNumber.subscribe(function (previousValue) {
console.log("old: " + previousValue);
oldValue = previousValue;
}, _pagedRequest, "beforeChange");
_pagedRequest.pageNumber.subscribe(function (newValue) {
console.log("new: " + newValue);
if (oldValue != newValue) {
console.log("value changed!");
}
});
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
I prefer using an observable extender.
http://jsfiddle.net/neonms92/xybGG/
Extender:
ko.extenders.withPrevious = function (target) {
// Define new properties for previous value and whether it's changed
target.previous = ko.observable();
target.changed = ko.computed(function () { return target() !== target.previous(); });
// Subscribe to observable to update previous, before change.
target.subscribe(function (v) {
target.previous(v);
}, null, 'beforeChange');
// Return modified observable
return target;
}
Example Usage:
// Define observable using 'withPrevious' extension
self.hours = ko.observable().extend({ withPrevious: 1 });
// Subscribe to observable like normal
self.hours.subscribe(function () {
if (!self.hours.changed()) return; // Cancel if value hasn't changed
print('Hours changed from ' + self.hours.previous() + ' to ' + self.hours());
});
This seems to work for me
ko.observable.fn.beforeAndAfterSubscribe = function (callback, target) {
var _oldValue;
this.subscribe(function (oldValue) {
_oldValue = oldValue;
}, null, 'beforeChange');
this.subscribe(function (newValue) {
callback.call(target, _oldValue, newValue);
});
};
See more at: http://ideone.com/NPpNcB#sthash.wJn57567.dpuf
http://jsfiddle.net/MV3fN/3/
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
var self = this;
self.pageNumber = ko.observable(pageNumber || 1);
self.numberOfPages = ko.observable(1);
self.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
self.pageNumber.subscribe(function (previousValue) {
console.log(previousValue);
console.log(self.pageNumber.arguments[0]);
if (previousValue != _pagedRequest.pageNumber.arguments[0]) {
console.log('value changed');
}
else {
//This won't get executed because KO doesn't
//call the function if the value doesn't change
console.log('not changed');
}
}, _pagedRequest, "beforeChange");
};
var _pagedRequest = new sv.PagedRequest();
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(5);
I don't know if you're really supposed to use arguments[0], but it seems to work.
You could also set up your own method to accomplish this in a much cleaner way:
http://jsfiddle.net/PXKgr/2/
...
self.setPageNumber = function(page) {
console.log(self.pageNumber());
console.log(page);
if (self.pageNumber() != page) {
console.log('value changed');
}
else {
console.log('not changed');
}
self.pageNumber(page);
};
...
_pagedRequest.setPageNumber(10);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(5);
How can i get list item approval status value using client object model in sharepoint?
Here is my sample code on fetching other attribute values.
ClientContext.Load(listItems,
items => items.Include(
item => item.Id,
item => item.DisplayName,
item => item.FileSystemObjectType,
item => item.HasUniqueRoleAssignments));
Here is the full code that gets and sets (optional) approval status (Possible values for this.oListItem.get_item('_ModerationStatus'): 0 - "Approved", 1 - "Denied", 2- "Pending"):
<script type="text/javascript" src="/jquery-1.10.2.min.js"></script>
<script src="/jquery.SPServices-2013.02a.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () { ExecuteOrDelayUntilScriptLoaded(loadConstants, "sp.js"); });
function loadConstants() {
var userid= _spPageContextInfo.userId;
var requestUri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/getuserbyid(" + userid + ")";
var requestHeaders = { "accept" : "application/json;odata=verbose" };
$.ajax({
url : requestUri,
contentType : "application/json;odata=verbose",
headers : requestHeaders,
success : onSuccess,
error : onError
});
function onSuccess(data, request){
var loginName = data.d.Title;
//get current (selected) list item id
var docurl = document.URL;
var beginindex = docurl.indexOf('?ID=') + 4;
var endindex = docurl.indexOf('&Source=');
var itemid = docurl.substring(beginindex, endindex);
var ctx = new SP.ClientContext("your site url");
var oList = ctx.get_web().get_lists().getByTitle('your list name');
this.oListItem = oList.getItemById(itemid);
var appStatus = "";
ctx.load(this.oListItem);
ctx.executeQueryAsync(Function.createDelegate(this, function () {
//get approval status
appStatus = this.oListItem.get_item('_ModerationStatus');
//set approval status to Approved (0)
this.oListItem.set_item('_ModerationStatus', 0);
this.oListItem.update();
ctx.executeQueryAsync(
Function.createDelegate(this, this.onQuerySucceeded),
Function.createDelegate(this, this.onQueryFailed)
);
}), function (sender, args) { alert('Error occured' + args.get_message());});
}
function onError(error) {
alert("error");
}
}
</script>
You can get approve status using like this
ClientContext.Load(listItems,
items => items.Include(
item => item.Id,
item => item.DisplayName,
item => item["Status"]));
You can get any of field custom or Sharepoint default fields value like this.