this is a kind of embarrassing question but I'm stuck.
My background is managed code and I never learned JavaScript but yet I want to implement a tiny project.
The script is running on SharePoint 2010, queries items from a custom list using the JavaScript Object Model and populates a Google chart or table respectively.
With the help of MSDN and Google Developer I was able to query data from one list and visualize it.
However, I'm unable to transfer the concept to query multiple lists, combine result sets and finally pass to Google API.
In my code I created a chain of callbacks like showChart->loadListData->drawChart. This proves to be bad design since it's inflexible and cannot be extended. All API methods are asynchronous and don't have return values but expect method names to call once finished. This is what get's me stuck and where I lack knowledge.
I'm very happy for every comment and answer, also I can provide actual source code if requested. Thank you in advance, Toby
UPDATE as asked for by #Utkanos:
var listItems;
$(document).ready(function() {
ExecuteOrDelayUntilScriptLoaded(loadChartData, "sp.js");
});
function loadChartData() {
var camlQuery = SP.CamlQuery.createAllItemsQuery();
camlQuery.set_viewXml("<View><Query><Where><Eq><FieldRef Name='Year'/><Value Type='Text'>2015</Value></Eq></Where></Query></View>");
loadListData('CustomList', camlQuery, drawChart, readListItemFailed);
}
function loadListData(listTitle, camlQuery, onSuccess, onFail) {
context = SP.ClientContext.get_current();
var list = context.get_web().get_lists().getByTitle(listTitle);
var listItems = list.getItems(camlQuery);
context.load(listItems);
context.executeQueryAsync(function(sender, args){onSuccess(listItems);}, onFail);
}
function drawDpOverviewChart(listItems) {
var data;
var enumerator = listItems.getEnumerator();
data = new google.visualization.DataTable();
data.addColumn('string', 'Column1');
data.addColumn('number', 'Column2');
var listItem;
while (enumerator.moveNext()) {
listItem = enumerator.get_current();
data.addRow([listItem.get_item('Title'), Math.round(listItem.get_item('Balance')/10000)/100]);
}
var options = {'title':'Pretty Chart'};
var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
function readListItemFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
if using SP 2010 on a typical .aspx page, you have some tools available,
such as MicrosoftAjax.js and _spPageContextInfo
using the REST API, you can join lists on lookup fields and include fields from both lists in one query
following is an example url for a call to rest...
'/_vti_bin/ListData.svc/MI_Projects?$expand=ModifiedBy&$filter=ModifiedBy/Id eq 738'
this call actually "joins" the list MI_Projects to the UserInformationList by "expanding" ModifiedBy
so when the data returns, you can access any of the user info fields, i.e.
row.ModifiedBy.Name
this can be done with lookup fields on custom lists as well
to make the call, you can use the Sys.Net.WebRequest class from MicrosoftAjax
this class also allows you to pass variables to the callback
see following snippet...
function makeCall() {
// Sys.Net.WebRequest is from MicrosoftAjax.js
var webRequest = new Sys.Net.WebRequest();
webRequest.get_headers()['Cache-Control'] = 'no-cache';
webRequest.get_headers()['Accept'] = 'application/json';
webRequest.get_headers()['Content-Type'] = 'application/json';
webRequest.set_url(_spPageContextInfo.webServerRelativeUrl + '/_vti_bin/ListData.svc/MI_Projects?$expand=ModifiedBy&$filter=ModifiedBy/Id%20eq%20738');
// use the 'user context' to pass variables you want available in the callback
webRequest.set_userContext({
Title: 'variable to pass to completed callback'
});
webRequest.add_completed(restComplete);
webRequest.invoke();
}
// the first argument of callback is the Sys.Net.WebRequestExecutor class
function restComplete(executor, eventArgs) {
if (executor.get_responseAvailable()) {
if (executor.get_statusCode() === 200) {
// get variable passed via user context
var variablePassed = executor.get_webRequest().get_userContext().Title;
// i.e. -- build google table
// add rows received from rest (forEach is from MicrosoftAjax.js)
// list results array = executor.get_object().d.results
Array.forEach(executor.get_object().d.results, function (row) {
data.addRow(row.Title, row.Id, row.ModifiedBy.Name);
}, this);
}
}
}
Related
Im in javascript on a HTML page trying to use Solr query data in a table.
So what I want to do is get a bunch of different responses to solr queries, and store them in an array as they come in. Then return each of these results to their relevant spot in a graphic chart to display my information.
What I am having trouble doing is storing the result from JSON in a variable for later use. However it won't save, as the results show up as undefined when checked. Yet if I assign the response to some HTML location like with $('#yoyo') from within function on_data(data), it seems to work just fine.
So I can get results live, but I can't store them for some reason?
From reading lots of other posts it seems it could be something to do with it being asynchronous. It feels to me like functions are running out of time. Like its trying to return the answer before it's assigned any value or something.
Can somebody show me where I'm going wrong here?
if (typeof variable !== 'undefined') {
var global_tick = -1
}
var results = []
var testB = 'query response failed';
$('#testB').prepend('<div>' + testB + '</div>');
function on_data(data) {
results[global_tick] = parseInt(data.response.numFound);
$('#yoyo').prepend('<div>' + results[global_tick] + '</div>');
global_tick = global_tick + 1;
}
function partyQuery(party, region){
pQuery(party, region)
var res = pResult()
return res
}
function pQuery(party, region){
var url='http://localhost:8983/solr/articles/select?q=text:'+party+'+AND+region:'+region+'&wt=json&callback=?&json.wrf=on_data';
$.getJSON(url);
}
function pResult(){
return results[global_tick]
}
//Parties
var pqFG = partyQuery("\"Fine Gael\"", 'ROI');
var pqFF = partyQuery("\"Fianna Fail\"", 'ROI');
// Load the Visualization API and the corechart package.
google.charts.load('current', {'packages':['corechart']});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
// Party data table.
var data_party = new google.visualization.DataTable();
data_party.addColumn('string', 'Party');
data_party.addColumn('number', 'Mentions');
data_party.addRows([
['Fine Gael', pqFG],
['Fianna Fáil', pqFF],
]);
Hi I am trying to get all documents library only created by the logged users. With the following code I get also libraries which was not created from a user. Thank you.
function GetAllLibraries() {
var listCollection = lists.getEnumerator();
while (listCollection.moveNext()) {
var listName = listCollection.get_current().get_title('Title');
document.getElementById('leftDiv').innerHTML += "<b>" + listName + "<b/>" + "<br />";
}
}
Since you are utilizing SharePoint JavaScript API (a.k.a JSOM) it is a bit tricky since SP.List object does not expose Author property to determine who created this object. But the good news that Author property could be extracted from SP.List.schemaXml property as demonstrated below
Here is a complete example how to retrieve lists created by current user
var ctx = SP.ClientContext.get_current();
var allLists = ctx.get_web().get_lists();
var currentUser = ctx.get_web().get_currentUser();
ctx.load(allLists,'Include(SchemaXml)');
ctx.load(currentUser);
ctx.executeQueryAsync(
function(){
var lists = allLists.get_data().filter(function(list){
var listProperties = schemaXml2Json(list.get_schemaXml());
var listAuthorId = parseInt(listProperties.Author);
return listAuthorId == currentUser.get_id();
});
console.log("The amount of lists created by current user: " + lists.length);
},
logError);
}
function schemaXml2Json(schemaXml)
{
var jsonObject = {};
var schemaXmlDoc = $.parseXML(schemaXml);
$(schemaXmlDoc).find('List').each(function() {
$.each(this.attributes, function(i, attr){
jsonObject[attr.name] = attr.value;
});
});
return jsonObject;
}
function logError(sender,args){
console.log(args.get_message());
}
If you want to know who created list or library, you need to get property SPList.Author. As i know, you can't get it by JSOM.
My advice for you is to develop your own http hanlder with logic on server-side and call it by ajax. For example, you pass arguments into handler like web url (_spPageContextInfo.webAbsoluteUrl), current user login or id (_spPageContextInfo.userId), and in handler iterate lists on web, compare current user and list creator. Finally, return needed lists info.
Or just develop web part and do the same: iterate lists and compare it with SPContext.Current.Web.CurrentUser
UPDATE:
Example of c# code. You can put it in your web part or event handler. In this code we iterate all lists on SPWeb and save lists title created by current user.
private void GetLists()
{
using (SPSite site = new SPSite("{site_url}"))
{
using (SPWeb web = site.OpenWeb())
{
SPListCollection listCol = web.Lists;
List<string> currentUserLists = new List<string>();
foreach(SPList list in listCol)
{
if (list.Author.ID == SPContext.Current.Web.CurrentUser.ID)
{
currentUserLists.Add(list.Title);
}
}
}
}
}
In my Sharepoint 2010 Web Part, I've got this Javascript:
function getListItemID(username, payeename, oList) {
var arrayListEnum = oList.getEnumerator();
...which is called by this:
function upsertPostTravelListItemTravelerInfo1() {
var clientContext = SP.ClientContext.get_current();
var oList =
clientContext.get_web().get_lists().getByTitle('PostTravelFormFields');
this.website = clientContext.get_web();
currentUser = website.get_currentUser();
var itemCreateInfo = new SP.ListItemCreationInformation();
this.oListItem = oList.addItem(itemCreateInfo);
var travelersEmail = $('traveleremail').val();
/* If this is an update, the call to getListItemID() will return a val; otherwise (an insert), get from newly instantiated ListItem. */
listId = getListItemID(currentUser, travelersEmail, oList);
I got the basis for this code from here.
But got the err listed above ("Uncaught TypeError: oList.getEnumerator is not a function");
One answer said I needed to add this:
<script type="text/javascript" src="/_layouts/15/sp.js" ></script>
...which I changed from "15" to "14" as that is the folder/version we're using.
That not only didn't work, but was unrecognized. I then found a clue here, namely to add this:
$(document).ready(function () { ExecuteOrDelayUntilScriptLoaded(CustomAction, "sp.js"); });
...but that only an error prior to the one already shown, namely, "Uncaught ReferenceError: CustomAction is not defined"
So what's the scoop? What is required to getEnumerator(), or otherwise retreive the ID val I need?
Here is the full code to that method, to show what I'm trying to accomplish, and how:
function getListItemID(username, payeename, oList) {
var arrayListEnum = oList.getEnumerator();
while (arrayListEnum.moveNext()) {
var listItem = arrayListEnum.get_current();
if (listItem.get_item("ptli_formPreparedBy") === username &&
listItem.get_item("ptli_TravelersEmail") === payeename &&
listItem.get_item("ptli_formCompleted") == false) {
return listItem.get_id();
}
}
return '';
}
UPDATE
When I tried this (first and third lines are new):
<SharePoint:ScriptLinkID="ScriptLink1" Name="SP.js" runat="server" OnDemand="false" LoadAfterUI="true" Localizable="false"></SharePoint:ScriptLink>
<script type="text/javascript">
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sharePointReady);
...which was inspired by a cat here, I got, "System.Web.HttpParseException was unhandled by user code
Message=The server tag is not well formed."
Personally, I don't think Sharepoint is very well formed. But that's (right) beside the point (no pun intended).
Problem 1: You're calling getEnumerator on a list instead of a list item collection
getEnumerator() can only be called on a list item collection (not on a List object), and only after it's been populated with items by running clientContext.executeQueryAsync()
Problem 2: You need to call executeQueryAsync to populate the list item collection
When using the SharePoint JavaScript client object model, your code needs to be broken up into two parts: the first part specifies what you want to get, and involves you loading queries and commands into an SPClientContext object; the second part lets you manipulate the results of the query to SharePoint, and runs as an asynchronous callback of the query execution.
Create your context, specify which lists you want to access, etc.
Run clientContext.executeQueryAsync() (where clientContext is an SP.ClientContext object), and pass in delegate functions to run on success or failure
In your "onSuccess" delegate function, you can work with the results of the commands you loaded up in step 1
Problem 3: You won't be able to return values directly from an asynchronously executing function
Because step 3 above runs asynchronously, you can't get a return value from it. Any logic that depends on the results that you get in step 3 needs to be moved forward in the execution chain, using function delegation and callbacks.
Problem 4: Inefficient filtering of list items
This is really more of a design flaw than a show-stopping problem, but instead of having your code return every item in the list, and then using JavaScript to enumerate through the results to see if the item you want is in there, you should tell SharePoint what filter options you want before it even executes the query. Then it'll only give you items that match your query.
Use a CAML query for this; CAML (Collaborative Application Markup Language) is an XML-based query language that SharePoint uses extensively. There are plenty of resources and tools for composing CAML queries, and you can even steal the CAML query from a SharePoint list view web part if you've already created a view that matches your query.
Example of how to query a SharePoint list using JavaScript CSOM
Here's an example using parts of your code:
/*
ExecuteOrDelayUntilScriptLoaded(yourcode,"sp.js") makes sure
your code doesn't run until SP.js (the SharePoint JavaScript CSOM)
has been loaded
*/
ExecuteOrDelayUntilScriptLoaded(function(){
var payeename = $('traveleremail').val();
var clientContext = SP.ClientContext.get_current();
var oList = clientContext.get_web().get_lists().getByTitle('PostTravelFormFields');
/* Use a CAML query to filter your results */
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ptli_TravelersEmail\' /><Value Type=\'Text\'>'+payeename+'</Value></Eq></Where></Query></View>');
/* get the list item collection from the list */
var oListItems = oList.getItems(camlQuery);
/* tell SharePoint to load the list items */
clientContext.load(oListItems);
/* execute the query to get the loaded items */
clientContext.executeQueryAsync(
/* onSuccess Function */
Function.createDelegate(this,function(){
/*
now that the query has run, you can get an enumerator
from your list item collection
*/
var arrayListEnum = oListItems.getEnumerator();
var ids = [];
while(arrayListEnum.moveNext()){
var listItem = arrayListItem.get_current();
ids.push(listItem.get_id());
}
alert(ids.length > 0 ? "IDs of matching items: " + ids : "No matching items found!");
}),
/*onFailure Function*/
Function.createDelegate(this,function(sender,args){
alert("Whoops: " + args.get_message() + " " + args.get_stackTrace());
})
);
},"sp.js");
The CAML query in the example code only filters on the ptli_TravelersEmail column; you'd need to add some <And> elements to capture the other two filter conditions you want.
This is what finally worked for me, thanks to Thriggle:
function setListItemID(username, payeename) {
var clientContext = new SP.ClientContext.get_current();
var oList = clientContext.get_web().get_lists().getByTitle('PostTravelFormFields');
/* Use a CAML query to filter your results */
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ptli_TravelersEmail\' /><Value Type=\'Text\'>' + payeename + '</Value></Eq></Where></Query></View>');
/* get the list item collection from the list */
var oListItems = oList.getItems(camlQuery);
/* tell SharePoint to load the list items */
clientContext.load(oListItems);
/* execute the query to get the loaded items */
clientContext.executeQueryAsync(
/* onSuccess Function */
Function.createDelegate(this, function () {
/*
now that the query has run, you can get an enumerator
from your list item collection
*/
var arrayListEnum = oListItems.getEnumerator();
var ids = [];
while (arrayListEnum.moveNext()) {
var listItem = arrayListItem.get_current();
ids.push(listItem.get_id());
}
if (ids.length > 0) {
listId = ids[0];
}
else {
listId = '';
}
}),
/*onFailure Function*/
Function.createDelegate(this, function (sender, args) {
alert("Whoops: " + args.get_message() + " " + args.get_stackTrace());
})
);
}
I have a Single Page Application that is working pretty well so far but I have run into an issue I am unable to figure out. I am using breeze to populate a list of projects to be displayed in a table. There is way more info than what I actually need so I am doing a projection on the data. I want to add a knockout computed onto the entity. So to accomplish this I registered and entity constructor like so...
metadataStore.registerEntityTypeCtor(entityNames.project, function () { this.isPartial = false; }, initializeProject);
The initializeProject function uses some of the values in the project to determine what the values should be for the computed. For example if the Project.Type == "P" then the rowClass should = "Red".
The problem I am having is that all the properties of Project are null except for the ProjNum which happens to be the key. I believe the issue is because I am doing the projection because I have registered other initializers for other types and they work just fine. Is there a way to make this work?
EDIT: I thought I would just add a little more detail for clarification. The values of all the properties are set to knockout observables, when I interrogate the properties using the javascript debugger in Chrome the _latestValue of any of the properties is null. The only property that is set is the ProjNum which is also the entity key.
EDIT2: Here is the client side code that does the projection
var getProjectPartials = function (projectObservable, username, forceRemote) {
var p1 = new breeze.Predicate("ProjManager", "==", username);
var p2 = new breeze.Predicate("ApprovalStatus", "!=", "X");
var p3 = new breeze.Predicate("ApprovalStatus", "!=", "C");
var select = 'ProjNum,Title,Type,ApprovalStatus,CurrentStep,StartDate,ProjTargetDate,CurTargDate';
var isQaUser = cookies.getCookie("IsQaUser");
if (isQaUser == "True") {
p1 = new breeze.Predicate("QAManager", "==", username);
select = select + ',QAManager';
} else {
select = select + ',ProjManager';
}
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select);
if (!forceRemote) {
var p = getLocal(query);
if (p.length > 1) {
projectObservable(p);
return Q.resolve();
}
}
return manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
var list = partialMapper.mapDtosToEntities(
manager,
data.results,
model.entityNames.project,
'ProjNum'
);
if (projectObservable) {
projectObservable(list);
}
log('Retrieved projects using breeze', data, true);
}
};
and the code for the partialMapper.mapDtosToEntities function.
var defaultExtension = { isPartial: true };
function mapDtosToEntities(manager,dtos,entityName,keyName,extendWith) {
return dtos.map(dtoToEntityMapper);
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
extendWith = $.extend({}, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
function mapToEntity(entity, dto) {
for (var prop in dto) {
if (dto.hasOwnProperty(prop)) {
entity[prop](dto[prop]);
}
}
return entity;
}
}
EDIT3: Looks like it was my mistake. I found the error when I looked closer at initializeProject. Below is what the function looked like before i fixed it.
function initializeProject(project) {
project.rowClass = ko.computed(function() {
if (project.Type == "R") {
return "project-list-item info";
} else if (project.Type == "P") {
return "project-list-item error";
}
return "project-list-item";
});
}
the issue was with project.Type I should have used project.Type() since it is an observable. It is a silly mistake that I have made too many times since starting this project.
EDIT4: Inside initializeProject some parts are working and others aren't. When I try to access project.ProjTargetDate() I get null, same with project.StartDate(). Because of the Null value I get an error thrown from the moment library as I am working with these dates to determine when a project is late. I tried removing the select from the client query and the call to the partial entity mapper and when I did that everything worked fine.
You seem to be getting closer. I think a few more guard clauses in your initializeProject method would help and, when working with Knockout, one is constantly battling the issue of parentheses.
Btw, I highly recommend the Knockout Context Debugger plugin for Chrome for diagnosing binding problems.
Try toType()
You're working very hard with your DTO mapping, following along with John's code from his course. Since then there's a new way to get projection data into an entity: add toType(...) to the end of the query like this:
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select)
.toType('Project'); // cast to Project
It won't solve everything but you may be able to do away with the dto mapping.
Consider DTOs on the server
I should have pointed this out first. If you're always cutting this data down to size, why not define the client-facing model to suit your client. Create DTO classes of the right shape(s) and project into them on the server before sending data over the wire.
You can also build metadata to match those DTOs so that Project on the client has exactly the properties it should have there ... and no more.
I'm writing about this now. Should have a page on it in a week or so.
I have a datasource that gets data from the server. It is then used in a datatable. I want to be able to filter the data in the table client-side, without making another call to the server.
// Data source definition
myDataSource = new YAHOO.util.DataSource("myurl");
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
myDataSource.connXhrMode = "queueRequests";
myDataSource.responseSchema = {
resultsList: "ResultSet.Result",
fields: ["field1","field2"]
}
// Datatable definition
myDataTable = new YAHOO.widget.DataTable("container", myColumnDefs,myDataSource, {});
Subclass DataSource and override the sendRequest method so that you call the passed-in callback with your own filtered result set as the results argument.
filterDataSource=function(arg) {
filterDataSource.superclass.constructor.call(this,arg);
}
YAHOO.extend(filterDataSource,YAHOO.util.XHRDataSource);
filterDataSource.prototype.sendRequest=function(request, callback) {
var wrapCallBack=function (request,results,error) {
// !!! do filtering on results here !!!
callback.success.call(this,request,results,error);
};
filterDataSource.superclass.sendRequest.call(this,request, {
success: wrapCallBack, argument: callback.argument
});
}
And make your myDataSource a new filterDataSource instead of a new Yahoo.util.DataSource.
Disclaimer: this code probably doesn't work as written; I ripped it out of some old working code and quite likely skipped over some critical piece. Still, I hope it conveys the basic idea.