I've been trying to modify the sample dashboard widget at this location
https://learn.microsoft.com/en-us/vsts/extend/develop/add-dashboard-widget?view=vsts#part-2-hello-world-with-vsts-rest-api
However, reluctantly have to admit I simply can't understand the structure required to extend it
Near the end, it uses "load: function" and returns the outputs of a REST API call, which I can consume however I want
However, I need to make more than one different REST call, and I simply cannot figure out how to get that info usable in my function
I modified the code so it starts like this:
VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/Work/RestClient","VSS/Service", "TFS/WorkItemTracking/RestClient" ],
I then created a handle for the other call I want to make like this:
var queryClient = VSS_Service.getCollectionClient(TFS_Wit_QueryAPI.WorkItemTrackingHttpClient);
var queryResults = queryClient.getQuery(projectId, "Shared Queries/My Bugs");
However, I cannot consume the contents of queryResults - I know it's working up to a point as if I put in an invalid URL it will error as it knows it can't access anything there. If the URL is correct, no matter what I've tried - even stringify just to see what comes back - I get 'undefined' or something similar (it's definitely a valid JavaScript object)
The key seems to be right at the end when you have "load: function" except that only allows one thing to be returned? The reason I know this is if I change the function that it returns to be the one I've written rather than the one from the sample, it works fine - but the problem remains the same in that I can only process the results of one API call.
You can call more than one APIs, the code in that article is just the simple sample.
For Widget extension, you just need to return the status (e.g. Success()) in load function, so you can return status at the end of the function. For example:
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to VSTS
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName: "<unknown>") ));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return true;
}, function (error) {
// Use the widget helper and return failure as Widget Status
console.log(error);
return false;
});
}
var getAnOhterQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to VSTS
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Bug")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName: "<unknown>") ));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return true;
}, function (error) {
// Use the widget helper and return failure as Widget Status
console.log(error);
return false;
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
var r1= getQueryInfo(widgetSettings);
var r2=getAnOhterQueryInfo(widgetSettings);
if(r1==true && r2==true){
return WidgetHelpers.WidgetStatusHelper.Success();
}else{
return WidgetHelpers.WidgetStatusHelper.Failure("failed, check error in console");
}
}
So this post gives an answer to how to send a post for downloading a file using forms. But my API endpoint isn't recognizing the form data.
[HttpPost]
public async Task<IHttpActionResult> DownloadPdfs([FromBody] IEnumerable<int> ids)
{
if (ids == null || ids.Count() < 1)
{
return BadRequest("No ids supplied.");
}
...
'ids' always has a count of 0.
Here's the JS code:
function downloadFile(ids) {
var win = 'w' + Math.floor(Math.random() * 1000000000000);
window.open('', win, 'width=250, height=100');
var f = $('<form></form>')
.attr({ target: win, method: 'post', action: 'api/pdfs' })
.appendTo(document.body);
_.each(ids, function (id) {
$('<input></input>')
.attr({ type: 'hidden', name: 'ids', value: id })
.appendTo(f);
});
f[0].submit();
f.remove();
}
Initially, I just had it the same as the other linked answer which was just one input appended to the form, in which I set the value to 'ids'. I tried this next, but that still didn't work.
Does anyone know how to adapt this to still use post data to provide my ids, which are needed to create and supply the downloaded file? Or is there a better way to do this?
I've tried doing it just using Ajax, which returns the file content in the responseText, but I can't make use of it in there and I can't get the browser to open it.
Edit 1
I also just tried setting the value to this:
value: '{ids: [' + ids.toString() + ']}'
That didn't work.
Edit 2
Just tried changing the endpoint parameter to this:
([FromBody] int[] ids)
...to no avail.
So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});
I got a table with remote datasource. in one cell I got the userID. Because I want to show the username instead of the user ID I made a custom template function:
function getUserName(pmcreator){
var user = '';
var data = ''
ds_userList.fetch(function(){
var data = this.data();
for(var i = 0, length = data.length; i < length; i++){
if(data[i].uID == pmcreator){
console.log(data[i].uLastname)
user = data[i].uLastname
}
}
});
return user
}
But its not working as it should, the cells stay empty. I got no errors but I see that the remote request to fetch the usernames is not completed before the grid is filled out. I thought the custom function of fetch is waiting for the results to return but it don't seems so.
Any Idea? I find thousends of examples but all with static local data. I need one with both remote, the grid conent and the template data.
This is probably due the fact that when yuo call the dataSource.fetch it fires off an async function, which causes the thread running the template to continue on. According to kendo you will need to return a control, then set the content of that control inside the callback.
Quick sample using Northwind categories...
Here is the template function
function getDetails(e) {
$.getJSON("http://services.odata.org/V3/Northwind/Northwind.svc/Categories", null, function(data) {
var category = data.value.filter(function(item, i) {
return item.CategoryID === e.CategoryID;
});
$("#async_" + e.CategoryID).html(category[0].Description);
});
return "<div id='async_" + e.CategoryID + "'></div>";
}
http://jsbin.com/ODENUBe/2/edit
I kept getting a recursive error maximum call stack when I just tried to fetch the dataSource, so I switched to a simple getJSON, but it should work pretty much the same.
i need a script that copies all my selected list items to an other (custom) list. I found nice solution for documents:
var context = SP.ClientContext.get_current();
var web = context.get_web();
context.load(web);
var _destinationlib = web.get_lists().getByTitle('DestinationLibrary');
context.load(_destinationlib);
var notifyId;
var currentlibid = SP.ListOperation.Selection.getSelectedList();
var currentLib = web.get_lists().getById(currentlibid);
var selectedItems = SP.ListOperation.Selection.getSelectedItems(context);
var count = CountDictionary(selectedItems);
for(var i in selectedItems)
{
alert('Now copying ' + i);
var currentItem = currentLib.getItemById(selectedItems[i].id);
context.load(currentItem);
var File = currentItem.get_file();
context.load(File);
//Excecuting executeQueryAsync to get the loaded values
context.executeQueryAsync
(
function (sender, args) {
if(File != null) {
var _destinationlibUrl = web.get_serverRelativeUrl() + _destinationlib.get_title() + '/' + File.get_name();
File.copyTo(_destinationlibUrl, true);
notifyId = SP.UI.Notify.addNotification('Moving fileā¦' + File.get_serverRelativeUrl() + 'to' + _destinationlibUrl, true);
//Excecuting executeQueryAsync to copy the file
context.executeQueryAsync(
function (sender, args) {
SP.UI.Notify.removeNotification(notifyId);
SP.UI.Notify.addNotification('File copied successfully', false);
},
function (sender, args) {
SP.UI.Notify.addNotification('Error copying file', false);
SP.UI.Notify.removeNotification(notifyId);
showError(args.get_message());
});
}
},
function (sender, args) {
alert('Error occured' + args.get_message());
}
);
}
I dont know what i have to change to get it working for normal list items. I tried to exchange
var File = currentItem.get_file();
context.load(File);
with
var title = currentItem.get_Title();
context.load(title);
var number = currentItem.get_item('number');
context.load(number);
but it dosnt work. It would be great if somebody can give me a hint what i have to do.
many thx
Fabulus
It looks like that you took code above from here.
Try to be attentively. This code copies selected files (not list items!) to another document library!
For your needs better try to code your own solution. See SharePoint JavaScript Class Library for details. You can have two possible architectures:
Make all work from JavaScript. And the first your step will be addItem method of SP.List.
Make processing of selection on client in JavaScript and call your custom server-side component (may be an application page) for items copying (creating copies in new list of already existed items from initial list.). See this for example.
Also be careful with context.load. It's recommended to write all next code in context.executeQueryAsync. Use Firebug in FF and developer tools in Chrome for debugging your code and to find what is wrong.