Get Excel range using JavaScript API for Office - javascript

I am building an App for Office (for desktop Excel) and I am looking for a function in JavaScript API Office version 1.1 that will return the addresses of the column(s) and the row(s) of a user selection. A result like "A1:C3".
I tried with Office.context.document.getSelectedDataAsync() but it only gets me the values. I need to know their address so I can display it in my app. My code is like this:
Office.context.document.getSelectedDataAsync(Office.CoercionType.Matrix, function (asyncResult) {
console.log(asyncResult.value);
});
The asyncResult only gets me an array values. I cannot find any useful help on MSDN or Google. Any help is appreciated.

This is very late but I hope this alternative can be useful to people working with Excel 2016. You can use the getSelectedRange function on the workbook to get the currently selected range and then load the address property like below.
Excel.run(function (ctx) {
var selectedRange = ctx.workbook.getSelectedRange();
selectedRange.load('address');
return ctx.sync().then(function () {
//selectedRange.address is now available to use
}).catch(function (error) {
//handle
});
}).catch(function (error) {
//handle
});

Here's the a complete working example function: Just add a test button to this function. You also need a Div to write your results using the writeToPage function (or amend to your own output area.)
function get_rangecoords() {
Office.context.document.bindings.addFromPromptAsync(Office.BindingType.Matrix,
{ id: "MyMatrixBinding" },
function (asyncResult) {
//NOW DO OUTPUT OR ERROR
if (asyncResult.status === "failed") {
writeToPage("Error get_rangecoords. " + asyncResult.error.message, 3);
}
else {
writeToPage("Added new binding with type: " + asyncResult.value.type + " and id: " + asyncResult.value.id, 1);
}
});
Office.select("bindings#MyMatrixBinding", onBindingNotFound).
addHandlerAsync(Office.EventType.BindingSelectionChanged,
onBindingSelectionChanged,
function (AsyncResult) {
writeToPage("Event handler was added successfully! Change the matrix current selection to trigger the event", 1);
});
//Trigger on selection change, get partial data from the matrix
function onBindingSelectionChanged(eventArgs) {
eventArgs.binding.getDataAsync({
CoercionType: "matrix",
startRow: eventArgs.startRow,
startColumn: eventArgs.startColumn,
rowCount: 1, columnCount: 1
},
function (asyncResult) {
//NOW DO OUTPUT OR ERROR
if (asyncResult.status === "failed") {
writeToPage("Error asyncResult: " + asyncResult.error.message, 3);
}
else {
writeToPage('Start Row:' + eventArgs.startRow + ' Start Col:' + eventArgs.startColumn + '\nSelected Row count:' + eventArgs.rowCount + ', Col Count:' + eventArgs.columnCount + '\nFirst Cell Value:' + asyncResult.value[0].toString(), 1);
}
});
}
//Show error message in case the binding object wasn"t found
function onBindingNotFound() {
writeToPage("The binding object was not found. Please return to previous step to create the binding", 3);
}
}
function writeToPage(text, varimportance) {
if (varimportance == "") {
document.getElementById('Notificationarea').style.color = "black";
}
if (varimportance == 1) {
document.getElementById('Notificationarea').style.color = "darkgreen";
}
if (varimportance == 2) {
document.getElementById('Notificationarea').style.color = "darkorange";
}
if (varimportance == 3) {
document.getElementById('Notificationarea').style.color = "red";
}
document.getElementById('Notificationarea').innerText = text;
}
For more information, see http://microsoft-office-add-ins.com

The trick is to create a named item to the whole sheet first and then attach a SelectionChanged handler to it. In its arguments you will get the column, row, height, and width of the selection inside that named item.
There is an example by the Microsoft Dev team here:
https://code.msdn.microsoft.com/office/Apps-for-Office-Get-51cc1aac

Related

Office.js outlook add-in issue

I'm trying to get the Body in Outlook and then update/set it with categories. My issue is this - when I debug it - it works fine. But when I don't debug from function to function - it gets all the way to the last function and just stops - updateBody(). What's really strang is if I remove the breakpoints on each function and just set a breakpoint on last function - never gets hit, but console will write out "Starting update body". All the console.logs are writing out data as expected. Not sure what is going on. Appreciate any help! Thanks.
"use strict";
var item;
var response;
var tags;
var updatedBody;
Office.initialize = function () {
$(document).ready(function () {
// The document is ready
item = Office.context.mailbox.item;
debugger;
getBodyType();
});
}
function getBodyType() {
item.body.getTypeAsync(
function (resultBody) {
if (resultBody.status == Office.AsyncResultStatus.Failed) {
write(resultBody.error.message);
} else {
response = resultBody;
console.log('Successfully got BodyType');
console.log(response.value);
getCategories();
}
});
}
function getCategories() {
tags = "";
// Successfully got the type of item body.
// Set data of the appropriate type in body.
item.categories.getAsync(function (asyncResult) {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
console.log("Action failed with error: " + asyncResult.error.message);
} else {
var categories = asyncResult.value;
console.log("Categories:");
categories.forEach(function (item) {
var tag = item.displayName;
tags += '#' + tag.replace(/\s/g, "") + ' ';
});
console.log('Successfully got tags');
console.log(tags);
getBody();
}
});
}
function getBody() {
var body = "";
updatedBody = "";
console.log("Starting get body");
if (response.value == Office.MailboxEnums.BodyType.Html) {
item.body.getAsync(
Office.CoercionType.Html,
{ asyncContext: "This is passed to the callback" },
function (result) {
//Replace all the # tags and update again.
body = result.value.replaceAll(/#(\w)+/g, "").trimEnd();
var domParser = new DOMParser();
var parsedHtml = domParser.parseFromString(body, "text/html");
$("body", parsedHtml).append("<div>" + tags + "</div>");
var changedString = (new XMLSerializer()).serializeToString(parsedHtml);
if (changedString != "") {
updatedBody = changedString;
}
console.log(updatedBody);
updateBody();
});
}
}
function updateBody() {
console.log("Starting update body");
item.body.setAsync(
updatedBody,
{ coercionType: Office.CoercionType.Html },
function (result2) {
console.log("Body updated");
});
}
Image - With breakpoints on each function - works as expected
Image - Without breakpoints - gets to updateBody() function.
But the string updatedBody isn't logged. It somehow skips over that
even though it's called before updateBody() on getBody()
Image - Same code run via Script Lab - works just fine as well.

Why is bookmarkItem.url returning as undefined while bookmarkItem.id is working fine?

I'm working on a small browser extension (currently targeted for Firefox using the WebExtensions API. The first step was to have it strip ?utm_source=... from a url whenever a new bookmark was added. This works.
function bookmarkCreated(id, bookmarkInfo) {
console.log(`Bookmark ID: ${id}`);
console.log(`Bookmark URL: ${bookmarkInfo.url}`);
currentURL = bookmarkInfo.url;
var strippedURL = currentURL.replace(/\?utm_source=.*/, "");
var newURL = browser.bookmarks.update(id, {
url: strippedURL
});
}
Now I'm working on adding functionality to iterate through all existing bookmarks and strip them of ?utm_source=... This is not working.
I used some example code from MDN that iterates through the bookmarks and outputs the values to the console. This code works fine:
function makeIndent(indentLength) {
return ".".repeat(indentLength);
}
function logItems(bookmarkItem, indent) {
if (bookmarkItem.url) {
console.log(makeIndent(indent) + bookmarkItem.url);
} else {
console.log(makeIndent(indent) + "Folder");
indent++;
}
if (bookmarkItem.children) {
for (child of bookmarkItem.children) {
logItems(child, indent);
}
}
indent--;
}
function logTree(bookmarkItems) {
logItems(bookmarkItems[0], 0);
}
function onRejected(error) {
console.log(`An error: ${error}`);
}
var gettingTree = browser.bookmarks.getTree();
gettingTree.then(logTree, onRejected);`
I added within logItems above a call to bookmarkCreated (first snippet above) - thinking that this should update the url. It seems to pull the bookmarkItem.id fine, but gets the bookmarkItem.url as undefined.
if (bookmarkItem.url) {
console.log(makeIndent(indent) + bookmarkItem.url);
bookmarkCreated(bookmarkItem.id, bookmarkItem.url);
} else {
console.log(makeIndent(indent) + "Folder");
indent++;
}
You are expecting a bookmarkItem as your second paramater, but there is url instead.
Either change signature of bookmarkCreated or change second paramater to bookmarkItem.

JavaScript Loop doesn't exit

I've tried everything I can think of.
I'm building a sort of chat bot for IMVU, using injected JavaScript on the IMVU mobile website. I have a loop to crawl the messages received, and search for certain key terms, like a message beginning with a slash (/) to indicate a command to the bot.
When certain commands are used, I have a problem that the bot seems to get stuck in the loop, almost as if the index of the for loop is being modified inside the loop. The code is included below.
If you need more, ask, and if you find something that might be causing the problem, please let me know. I'm at my wit's end.
Just for a note: jQuery is properly injected, all my variables are there, no errors in the debug console, and running under Chrome 41.0.2272.101m on Windows 7 x64.
function verifyCommand() {
if (document.getElementsByClassName("message-list-item").length > last_cmd_count && !processing_commands) {
var new_length = $('.message-list .message-list-item').length;
console.log("Begin processing commands... ** SYSTEM LOCK **");
console.log(new_length);
for (var i = last_cmd_count; i < (new_length); i++) {
processing_commands = true;
try {
var callinguser = $('.message-list .message-list-item .header .username .username-text')[i].innerText.replace("Guest_", "");
var messagetext = $('.message-list .message-list-item .message .message-text')[i].innerText
if (callinguser != "USERNAME REMOVED") {
if (messagetext.substr(0, 1) == "/") {
if (strContains(callinguser, "IMVU User")) {
die();
}
processCommand(messagetext.substr(1), callinguser);
} else {
if (messagetext.toLowerCase().indexOf('roomgreet') > -1 || messagetext.toLowerCase().indexOf('room greet') > -1) {
if (detectFlirt()) {
sendMsgRaw('Please do not hit on me, ' + callinguser + '.');
if (!isAdmin(callinguser)) {
logIdiot(callinguser);
}
} else if (strContains(messagetext, 'what is ')) {
sendMsgRaw('Please use /solve or /advsolve for math.');
} else {
if (callinguser != "USERNAME REMOVED") {
ident();
}
}
}
if (strContains(messagetext, 'free') && strContains(messagetext, 'credits') && strContains(messagetext, 'http://')) {
sendMsgFrom("*** SCAM ALERT ***", callinguser);
}
}
}
} catch (ex) {} finally {}
}
processing_commands = false;
last_cmd_count = new_length;
console.log("Finish processing commands... ** SYSTEM FREE **");
if (monitoring) {
verifyUserMessageCount();
}
}
}
HTML of the IMVU Mobile messages can be found at http://common.snftech.tk/imvu/roomgreet-html-sample.htm
Try changing your function to use each() to loop through each element instead of the loop you have. Once an element has been processed, add a "processed" class to the element so we dont look at them again later. This should be more stable than forcing our logic to keep up with what ones have been processed already.
Here is a jsFiddle,, throw in the html from your page that actually causes the problem and see if it still occurs
function verifyCommand() {
//fixed some logic in here
if ($(".message-list-item").length > last_cmd_count && !processing_commands) {
processing_commands = true; // you should set this immediately
var new_length = $('.message-list-item').length;
console.log("Begin processing commands... ** SYSTEM LOCK **");
console.log('Last command count: '+ last_cmd_count +', New Length: '+new_length);
var newMessages = $('.message-list-item:not(.processed)'); // get all of the message elements that do not have the class "processed"
// loop through each of the message elements
newMessages.each(function(index, element){
console.log('Processing new element at index '+index );
try {
var callinguser = $(this).find('.username-text').text().replace("Guest_", "");
var messagetext = $(this).find('.message-text').text();
$(this).addClass('processed'); // add processed class to the element so we know not to process it again later
if (callinguser != "RoomGreet") {
if (messagetext.match(/^\//)) {
if (callinguser.match(/IMVU User/)) {
die();
}
processCommand(messagetext.substr(1), callinguser);
}
else {
if (detectFlirt(messagetext)) {
if (!isAdmin(callinguser)) {
sendMsgRaw('Please do not hit on me, ' + callinguser + '.');
logIdiot(callinguser);
}
}
else if (messagetext.match('what is ')) {
sendMsgRaw('Please use /solve or /advsolve for math.');
}
else {
if (callinguser != "Nezzle" && !isAdmin(callinguser)) {
ident();
}
}
if (strContains(messagetext,"imvu") && strContains(messagetext,"credits") && strContains(messagetext,"http://")) {
sendMsgFrom("*** SCAM ALERT ***", callinguser);
}
}
}
}
catch (ex) {
console.log('caught error');
}
finally {
}
});
last_cmd_count = new_length;
console.log("Finish processing commands... ** SYSTEM FREE **");
processing_commands = false;
if (monitoring) {
verifyUserMessageCount();
}
}
}
I think your problem is this
if (messagetext.substr(0,1) == "/") {
if the user has a space in front of the "/" then it will not interpret as a command so you need to process
var messagetext = $('.message-list .message-list-item .message .message-text')[i].innerText
remove all white space from message text like this
messagetext.text().replace(" ", "");
you should also have more error catching in
if (messagetext.substr(0,1) == "/") {

Toggling Button States

I am attempting to implement toggling functionality into a program I am working on. Specifically, there are 3 possible scenarios when a user clicks a button.
Tool clicked while no other tool is currently active.
Tool clicked while another tool is currently active
Same tool is clicked to toggle it on/off
I am having trouble implementing this. Here is my code so far:
var toolState = {
img_draw_point: false,
img_draw_line: false,
img_draw_rectangle: false,
img_draw_ellipse: false,
img_draw_FreehandPolygon: false,
img_draw_FreehandPolyline: false,
img_draw_text: false,
img_draw_eraser: false,
};
var lastActiveTool;
on(dom.byId("div-tools-draw"), "click", function (evt) {
function disableActiveCSS() {
for (var property in toolState) {
$("img#" + property + ".k-button.single").removeClass("buttonSelected");
$("img#" + property + ".k-button.single").removeClass("buttonHoverState");
}
}
function enableCSS() {
$("img#" + evt.target.id + ".k-button.single").addClass("buttonSelected");
$("img#" + evt.target.id + ".k-button.single").addClass("buttonHoverState");
}
toolState[evt.target.id] = !toolState[evt.target.id];
if (toolState[evt.target.id] == toolState[lastActiveTool]) {
toolState[lastActiveTool] = false;
}
disableActiveCSS();
enableCSS();
if (evt.target.id == lastActiveTool) {
disableActiveCSS();
}
}
Any help would be greatly appreciated.
I see that your code contains the '$' notation so I used jQuery in my response. It also assumes that we only care if another tool is currently "ON". So the 3 options in my response are:
Turn on the selected tool if no tool is on.
Turn off the current tool and turn on the selected tool.
Turn off the current tool if it is currently on.
var lastActiveTool = false;
$.click("#div-tools-draw", function(evt) {
// Disable Active CSS
$("img.k-button.single").removeClass("buttonSelected").removeClass("buttonHoverState");
if (!lastActiveTool) {
activateTool(evt.target.id);
} else if (evt.target.id == lastActiveTool) {
sameToolToggle(evt.target.id);
} else {
otherToolToggle(evt.target.id);
}
};
var activateTool = function (id) {
$("img#" + id + ".k-button.single").addClass("buttonSelected")addClass("buttonHoverState");
lastActiveTool = evt.target.id;
};
var otherToolToggle = function(id) {
$("img#" + id + ".k-button.single").addClass("buttonSelected")addClass("buttonHoverState");
lastActiveTool = evt.target.id;
// Whatever else you need to do to toggle between tools
}
// Only gets called when the same tool is currently toggled ON
var sameToolToggle = function(id) {
lastActiveTool = false;
}

My Javascript functions aren't working when adding more than 9 elements

I have this page developed for listing people out. When you click on their names I have another section built out to hold the content of that individual. It has been working fine, but now I have a need to add more than 9 people to the list.
When Adding the 10th element you can no longer click the name on the left and load the correct persons information. It is selected and jumps to the #1 element. I have provided the code below and a link to the page on https://github.com/supasmo/Management-Testing.
I need help with correcting this problem so it can take on as many people as I need to add to the list. Thanks in Advance for any suggestions.
JS
management = {
debug: true,
defaultItem: 1,
currentItem: 0,
bios: ".bios .bio",
bio: "#bio",
manager: ".managers div.bio",
managerLinks: ".managers a",
topLinks: ".bio a.top",
paging: ".bio .paging",
bioNames: ".bio h1",
yellowArrowSrc: "public/assets/common/arrow-link.png",
blueArrowSrc: "public/assets/common/arrow-link-blue.png",
init: function() {
this.log("management.init()");
// count bios
this.bioCount = $(this.bios).length;
this.log("Found " + this.bioCount + " bios.");
// hide bios, names and "top" links, show paging links
$(this.bios).hide();
$(this.topLinks).hide();
$(this.bioNames).hide();
$(this.paging).show();
// show default item
this.showItem(this.defaultItem);
// adjust bio links
$(this.managerLinks).click(function(e) {
e.preventDefault();
management.linkClick($(this).parent());
});
// enable next and prev clicks
$(this.paging + " .next").css("cursor", "pointer").click(function() {
management.nextClick();
});
$(this.paging + " .prev").css("cursor", "pointer").click(function() {
management.prevClick();
});
},
prevClick: function() {
this.log("prevClick()");
newItem = this.currentItem - 1;
if (newItem < 1) {
newItem = this.bioCount;
}
this.showItem(newItem);
},
nextClick: function() {
this.log("nextClick()");
newItem = this.currentItem + 1;
if (newItem > this.bioCount) {
newItem = 1;
}
this.showItem(newItem);
},
linkClick: function(which) {
this.showItem(which.attr("class").substr(3, 1));
},
showItem: function(which) {
this.log("showItem(" + which + ")");
if (which == this.currentItem) {
this.log("--> aborted: item is already showing");
} else {
$(this.bio + this.currentItem).hide();
$(this.bio + which).show();
$(this.manager).removeClass("current");
$(this.manager + which).addClass("current");
$(this.manager + " img.arrow").attr("src", this.yellowArrowSrc);
$(this.manager + which + " img.arrow").attr("src", this.blueArrowSrc);
this.currentItem = which;
}
},
log: function(message) {
if (this.debug) {
console.log(message);
}
},
// ===== End of Object =====
endOfObject: 1
}
$(document).ready(function() {
management.init();
});
this.showItem(which.attr("class").substr(3, 1));
This part doesn’t work for more than one digit, and is just not the right way to do that in general, since the order of classes in class is not supposed to matter. At the very least, you should use a data attribute:
<div class="bio" data-bio="10">
this.showItem(which.data("bio"));
If you want to be substringy, though, you’ve got a perfectly good link:
// adjust bio links
$(this.managerLinks).click(function(e) {
management.linkClick(this);
e.preventDefault();
});
linkClick: function(which) {
this.showItem(which.getAttribute("href").match(/\d+/)[0]);
},

Categories