Excel 2016 (Office 365) 32 bits, 16.0.6965.2115, Visual Studio 14.0.25425.01 Update 3
I'm quite sure the statement below used to work, but now it doesn't work anymore:
var range = ctx.workbook.names.getItem("Countries").getRange();
I get an error stating that there is no support for getRange method, but it should be supported as documented here.
What am I'm doing wrong?
--- EDIT: this is the code I'm using ---
function paintRange() {
Excel.run(function (ctx) {
var range = ctx.workbook.names.getItem("Countries").getRange();
range.format.fill = "green";
return ctx.sync();
}).catch(function (error) {
app.showNotification("Error", error);
})
}
paintRange is attached to a button. There is a global scope defined name called Countries.
I don't have any more details of the error besides the one I mentioned, I also tried opening the quick watch window to get more clues.
UPDATE: The issue is fixed with an update to the CDN. You should be able to use namedItem.getRange() now. Thanks for reporting the issue, and allowing us to do a quick turn-around on it.
================
Felipe, looks like you're absolutely right. This is definitely a bug. Let me talk to the right folks to get this regression fixed as soon as we can. I'll see if we can put in some processes to avoid this in the future, as well.
From an immediate-workaround perspective, two options:
Use the BETA CDN (esp if it's for an in-development add-in, rather than a production one). That URL is: https://appsforoffice.microsoft.com/lib/beta/hosted/office.js
Do a temporarily filling in of the inadvertently-removed getRange functionality. Inside of Office.initialize, include the following code:
if (!Excel.NamedItem.prototype.getRange) {
Excel.NamedItem.prototype.getRange=function () {
return new Excel.Range(this.context,
OfficeExtension.ObjectPathFactory.createMethodObjectPath(
this.context, this, "GetRange",
OfficeExtension.OperationType.Read, [], false, true, null
)
);
};
}
The workaround in #2 should not cause harm even after the functionality is restored, but I would none-the-less recommend making a mental note to remove this after we've fixed the issue. I'll update this thread once we have fixed the underlying bug, hopefully within a weeks' time (as a very rough estimate, pending any complications that might delay it).
Thanks for bringing it to our attention -- both the individual bug, and the underlying process that let the regression to this one API go unnoticed.
Related
I can't for the life of me figure out how to get console.log() to appear with my express server. It's a middle-tier API for our front-end. You'll have to forgive me if I speak about it a little awkwardly, I'm relatively inexperienced with these tools but I'll do my best to explain the issue despite my inexperience. I'm trying to use console.log to get a better idea of a rather complex projects behavior and what might be causing some issues with it in its current state. Unfortunately console.log only seems to work within plainjane examples like so:
export const routerExample = express.Router();
routerExample.use((req, res, next) => {
console.log('Time: ', Date.now()); // I show up in console just fine
next();
});
When I try to lookup the problem I'm experiencing all solutions seem to be regarding getting routing examples like the one above to appear in console, I can see such examples just fine. The problem comes from getting anything to show up in examples like:
// routing.ts
import { homeController } from '../controllers/homeController';
const homeEx: HomeExample = new HomeExample();
routerExample.get('/home', homeEx.getHome);
// homeController.ts
export class HomeExample {
public getHome (req: Request, res: Response) : void {
console.log("something is happening");
// do stuff
}
}
Any uses of console.log like above never appear anywhere in node's console (or elsewhere as far as I can tell).
What am I missing that is needed to make these log messages appear? This has to be incredibly simple but I've been through numerous similar sounding issues on stackoverflow and elsewhere and everything single of one of them seems to be describing slightly different issues (or misunderstandings) that don't solve my own issue. I've tried numerous versions of node/npm and have added the DEBUG:* flag as well. None of this seems to solve it. If I'm missing any code that'd help give context to the issue let me know. I've obviously cut down parts and renamed some objects as I can't exactly dump work-related code here. What am I missing? Any help would be greatly appreciated :)
Edit 1: since many similar posts to this seem to get this mixed up, no I'm not looking at my front-end's console or something for the output. I'm looking in the terminal window where I start the server from, where the router example does appear.
Edit 2: for reference, my file structure is something like:
app/
controllers/
homeController.ts (HomeExample stuff is here)
routes
routing.ts (routerExample stuff is here)
app.ts
...
Edit 3: the code works overall to be clear. the problem is explicitly that that log.console() isn't appearing, all the code I've wrapped into "// do stuff" is working as expected.
Checkout Express Middlewares
routerExample.get('/home', homeExample);
function homeExample (req: Request, res: Response, next:NextFunction) : void {
console.log("something is happening");
// do stuff
}
}
You are also calling a member of a non static or instantiated class see this:
TypeScript - Static
What you are missing is to create a new instance of the homeExample class. What I recommend is to export the new instance on the route file like this:
/routes/home.route.js
class HomeRoute {
/* your methods */
}
export default new HomeRoute();
then you can use it:
import homeRoutes from './routes/home.route';
router.get('/home', homeRoutes.getHome);
See the example:
https://replit.com/#abranhe/expressjs-console-log#index.js
After a fresh nights sleep I've figured it out. It, of course, was the most obvious problem that managed to slip by me in the overall complexity of the codebase. The /home call was deprecated and replaced with a different call in the front-end without mention in the middle-tier code that I had posted. I didn't even consider checking what was being called any deeper since I was experiencing the same issue with multiple other calls that I didn't include in the original post for brevity. Basically all the tools I'm working with here are completely new to me so my inexperience got the best of me. Thank you to #jfriend00 who made me double-take how /home was being called (it wasn't).
Since I was getting the data I needed without issue on the front-end I assumed these functions were being run, seeing as the data they produced was the same kind of data that was successfully being shown by the front-end, just without the console.log() output I added appearing.
Moral of the story: if every other question related to an issue on Stack Overflow concludes with "I just made a dumb mistake," take absolutely every precaution possible to observe what's happening, even if you feel like you already ruled out certain possibilities. Unfortunately I got a bit caught up with all the weird solutions I saw to the point where I got ahead of myself in debugging the problem.
I'm still a bit confused since the /home call specifically should still be "active" even if not called by the front-end, but console.log() is clearly working on other similar functions I've tested since figuring this out. There's either something hidden deep in the codebase that's breaking/overwriting /home and other old calls, or it's simply not being called right when I'm testing it outside of the front-end.
TLDR: I'm an idiot, every single API call I thought I was testing was not actually being called. Double-check your assumptions before asking for a specific solution.
I haven't used my Discord recently but the last time I ran it, it still worked perfectly. However, I keep getting this error Cannot set property 'avatar' of undefined at Member.set [as avatar] these days.
Can someone help me?
I was struggling with this issue and eventually figured it out. It's because the 'this' keyword is getting hijacked somehow in the code within the Object.defineProperty call inside the Member constructor(line 2606 in my version of index.js, but I've made a few other fixes already so yours is probably different). I was able to fix it by caching off the 'this' reference into a private member and referencing that instead. It feels hacky but it works. Like so:
function Member(client, server, data) {
copyKeys(data, this, ['user', 'joined_at',]);
this.id = data.user.id;
this.joined_at = Date.parse(data.joined_at);
this.color = colorFromRole(server, this);
var tempThis = this;
['username', 'discriminator', 'bot', 'avatar', 'game'].forEach(function(k) {
if (k in Member.prototype) return;
Object.defineProperty(Member.prototype, k, {
get: function() { return client.users[tempThis.id][k]; },
set: function(v) { client.users[tempThis.id][k] = v; },
enumerable: true,
});
});
}
I just experienced the same error with my own bot completely out of the blue. After some investigation, I checked the code at DiscordClient.handleWSMessage (on my error it was showing at index.js:1871:31 as opposed to index.js:1891:31, however I'm not sure if it's a matter of having different versions of discord.io installed) - in any case, the error seemed to be stemming from the Event switch statement responding to a GUILD_CREATE event - this may be different for you:
case "GUILD_CREATE":
/*The lib will attempt to create the server using the response from the
REST API, if the user using the lib creates the server. There are missing keys, however.
So we still need this GUILD_CREATE event to fill in the blanks.
If It's not our created server, then there will be no server with that ID in the cache,
So go ahead and create one.*/
client.servers[_data.id] = new Server(client, _data);
return emit(client, message, client.servers[_data.id]);
I don't understand why GUILD_CREATE events are being received, my bot has never been programmed to handle these, however commenting out the executable lines in the switch case above and replacing them with an empty return statement seemed to stop the error occurring, and my bot stayed connected (so far, I've only been testing for a few minutes).
Might not work for everyone, but on my computer this issue was resolved by saving and only running after I saved. Slightly annoying but it worked!
I'm still a novice web developer, so please bear with me if I miss something fundamental !
I'm creating a backoffice for a Strapi backend, using react-admin.
React-admin library uses a 'data provider' to link itself with an API. Luckily someone already wrote a data provider for Strapi. I had no problem with step 1 and 2 of this README, and I can authenticate to Strapi within my React app.
I now want to fetch and display my Strapi data, starting with Users. In order to do that, quoting Step 3 of this readme : 'In controllers I need to set the Content-Range header with the total number of results to build the pagination'.
So far I tried to do this in my User controller, with no success.
What I try to achieve:
First, I'd like it to simply work with the ctx.set('Content-Range', ...) hard-coded in the controller like aforementioned Step 3.
Second, I've thought it would be very dirty to c/p this logic in every controller (not to mention in any future controllers), instead of having some callback function dynamically appending the Content-Range header to any fetchAll request. Ultimately that's what I aim for, because with ~40 Strapi objects to administrate already and plenty more to come, it has to scale.
Technical infos
node -v: 11.13.0
npm -v: 6.7.0
strapi version: 3.0.0-alpha.25.2
uname -r output: Linux 4.14.106-97.85.amzn2.x86_64
DB: mySQL v2.16
So far I've tried accessing the count() method of User model like aforementioned step3, but my controller doesn't look like the example as I'm working with users-permissions plugin.
This is the action I've tried to edit (located in project/plugins/users-permissions/controllers/User.js)
find: async (ctx) => {
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
// Send 200 `ok`
ctx.send(data);
},
From what I've gathered on Strapi documentation (here and also here), context is a sort of wrapper object. I only worked with Express-generated APIs before, so I understood this snippet as 'use fetchAll method of the User model object, with ctx.query as an argument', but I had no luck logging this ctx.query. And as I can't log stuff, I'm kinda blocked.
In my exploration, I naively tried to log the full ctx object and work from there:
// Send 200 `ok`
ctx.send(data);
strapi.log.info(ctx.query, ' were query');
strapi.log.info(ctx.request, 'were request');
strapi.log.info(ctx.response, 'were response');
strapi.log.info(ctx.res, 'were res');
strapi.log.info(ctx.req, 'were req');
strapi.log.info(ctx, 'is full context')
},
Unfortunately, I fear I miss something obvious, as it gives me no input at all. Making a fetchAll request from my React app with these console.logs print this in my terminal:
[2019-09-19T12:43:03.409Z] info were query
[2019-09-19T12:43:03.410Z] info were request
[2019-09-19T12:43:03.418Z] info were response
[2019-09-19T12:43:03.419Z] info were res
[2019-09-19T12:43:03.419Z] info were req
[2019-09-19T12:43:03.419Z] info is full context
[2019-09-19T12:43:03.435Z] debug GET /users?_sort=id:DESC&_start=0&_limit=10& (74 ms)
While in my frontend I get the good ol' The Content-Range header is missing in the HTTP Response message I'm trying to solve.
After writing this wall of text I realize the logging issue is separated from my original problem, but if I was able to at least log ctx properly, maybe I'd be able to find the solution myself.
Trying to summarize:
Actual problem is, how do I set my Content-Range properly in my strapi controller ? (partially answered cf. edit 3)
Collateral problem n°1: Can't even log ctx object (cf. edit 2)
Collateral problem n°2: Once I figure out the actual problem, is it feasible to address it dynamically (basically some callback function for index/fetchAll routes, in which the model is a variable, on which I'd call the appropriate count() method, and finally append the result to my response header)? I'm not asking for the code here, just if you think it's feasible and/or know a more elegant way.
Thank you for reading through and excuse me if it was confuse; I wasn't sure which infos would be relevant, so I thought the more the better.
/edit1: forgot to mention, in my controller I also tried to log strapi.plugins['users-permissions'].services.user object to see if it actually has a count() method but got no luck with that either. Also tried the original snippet (Step 3 of aforementioned README), but failed as expected as afaik I don't see the User model being imported anywhere (the only import in User.js being lodash)
/edit2: About the logs, my bad, I just misunderstood the documentation. I now do:
ctx.send(data);
strapi.log.info('ctx should be : ', {ctx});
strapi.log.info('ctx.req = ', {...ctx.req});
strapi.log.info('ctx.res = ', {...ctx.res});
strapi.log.info('ctx.request = ', {...ctx.request});
ctrapi.log.info('ctx.response = ', {...ctx.response});
Ctx logs this way; also it seems that it needs the spread operator to display nested objects ({ctx.req} crash the server, {...ctx.req} is okay). Cool, because it narrows the question to what's interesting.
/edit3: As expected, having logs helps big time. I've managed to display my users (although in the dirty way). Couldn't find any count() method, but watching the data object that is passed to ctx.send(), it's equivalent to your typical 'res.data' i.e a pure JSON with my user list. So a simple .length did the trick:
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
ctx.set('Content-Range', data.length) // <-- it did the trick
// Send 200 `ok`
ctx.send(data);
Now starting to work on the hard part: the dynamic callback function that will do that for any index/fetchAll call. Will update once I figure it out
I'm using React Admin and Strapi together and installed ra-strapi-provider.
A little boring to paste Content-Range header into all of my controllers, so I searched for a better solution. Then I've found middleware concept and created one that fits my needs. It's probably not the best solution, but do its job well:
const _ = require("lodash");
module.exports = strapi => {
return {
// can also be async
initialize() {
strapi.app.use(async (ctx, next) => {
await next();
if (_.isArray(ctx.response.body))
ctx.set("Content-Range", ctx.response.body.length);
});
}
};
};
I hope it helps
For people still landing on this page:
Strapi has been updated from #alpha to #beta. Care, as some of the code in my OP is no longer valid; also some of their documentation is not up to date.
I failed to find a "clever" way to solve this problem; in the end I copy/pasted the ctx.set('Content-Range', data.length) bit in all relevant controllers and it just worked.
If somebody comes with a clever solution for that problem I'll happily accept his answer. With the current Strapi version I don't think it's doable with policies or lifecycle callbacks.
The "quick & easy fix" is still to customize each relevant Strapi controller.
With strapi#beta you don't have direct access to controller's code: you'll first need to "rewrite" one with the help of this doc. Then add the ctx.set('Content-Range', data.length) bit. Test it properly with RA, so for the other controllers, you'll just have to create the folder, name the file, copy/paste your code + "Search & Replace" on model name.
The "longer & cleaner fix" would be to dive into the react-admin source code and refactorize so the lack of "Content-Range" header doesn't break pagination.
You'll now have to maintain your own react-admin fork, so make sure you're already committed into this library and have A LOT of tables to manage through it (so much that customizing every Strapi controller will be too tedious).
Before forking RA, please remember all the stuff you can do with the Strapi backoffice alone (including embedding your custom React app into it) and ensure it will be worth the trouble.
We've been developing using Excel JavaScript API for quite a few months now. We have been coming across context related issues which got resolved for unknown reasons. We weren't able to replicate these issues and wondered how they got resolved. Recently similar issues have started popping up again.
Error we consistently get:
property 'address' is not available. Before reading the property's
value, call the load method on the containing object and call
"context.sync()" on the associated request context.
We thought as we have multiple functions defined to modularise code in project, may be context differs somewhere among these functions which has gone unnoticed. So we came up with single context solution implemented via JavaScript Module pattern.
var ContextManager = (function () {
var xlContext;//single context for entire project/application.
function loadContext() {
xlContext = new Excel.RequestContext();
}
function sync(object) {
return (object === undefined) ? xlContext.sync() : xlContext.sync(object);
}
function getWorksheetByName(name) {
return xlContext.workbook.worksheets.getItem(name.toString());
}
//public
return {
loadContext: loadContext,
sync: sync,
getWorksheetByName: getWorksheetByName
};
})();
NOTE: above code shortened. There are other methods added to ensure that single context gets used throughout application.
While implementing single context, this time round, we have been able to replicate the issue though.
Office.initialize = function (reason) {
$(document).ready(function () {
ContextManager.loadContext();
function loadRangeAddress(rng, index) {
rng.load("address");
ContextManager.sync().then(function () {
console.log("Address: " + rng.address);
}).catch(function (e) {
console.log("Failed address for index: " + index);
});
}
for (var i = 1; i <= 1000; i++) {
var sheet = ContextManager.getWorksheetByName("Sheet1");
loadRangeAddress(sheet.getRange("A" + i), i);//I expect to see a1 to a1000 addresses in console. Order doesn't matter.
}
});
}
In above case, only "A1" gets printed as range address to console. I can't see any of the other addresses (A2 to A1000)being printed. Only catch block executes. Can anyone explain why this happens?
Although I've written for loop above, that isn't my use case. In real use case, such situations occur where one range object in function a needs to load range address. In the mean while another function b also wants to load range address. Both function a and function b work asynchronously on separate tasks such as one creates table object (table needs address) and other pastes data to sheet (there's debug statement to see where data was pasted).
This is something our team hasn't been able to figure out or find a solution for.
There is a lot packed into this code, but the issue you have is that you're calling sync a whole bunch of times without awaiting the previous sync.
There are several problems with this:
If you were using different contexts, you would actually see that there is a limit of ~50 simultaneous requests, after which you'll get errors.
In your case, you're running into a different (and almost opposite) problem. Given the async nature of the APIs, and the fact that you're not awaiting on the sync-s, your first sync request (which you'd think is for just A1) will actually contain all the load requests from the execution of the entire for loop. Now, once this first sync is dispatched, the action queue will be cleared. Which means that your second, third, etc. sync will see that there is no pending work, and will no-op, executing before the first sync ever came back with the values!
[This might be considered a bug, and I'll discuss with the team about fixing it. But it's still a very dangerous thing to not await the completion of a sync before moving on to the next batch of instructions that use the same context.]
The fix is to await the sync. This is far and away the simplest to do in TypeScript 2.1 and its async/await feature, otherwise you need to do the async version of the for loop, which you can look up, but it's rather unintuitive (requires creating an uber-promise that keeps chaining a bunch of .then-s)
So, your modified TypeScript-ified code would be
ContextManager.loadContext();
async function loadRangeAddress(rng, index) {
rng.load("address");
await ContextManager.sync().then(function () {
console.log("Address: " + rng.address);
}).catch(function (e) {
OfficeHelpers.Utilities.log(e);
});
}
for (var i = 1; i <= 1000; i++) {
var sheet = ContextManager.getWorksheetByName("Sheet1");
await loadRangeAddress(sheet.getRange("A" + i), i);//I expect to see a1 to a1000 addresses in console. Order doesn't matter.
}
Note the async in front of the loadRangeAddress function, and the two await-s in front of ContextManager.sync() and loadRangeAddress.
Note that this code will also run quite slowly, as you're making an async roundtrip for each cell. Which means you're not using batching, which is at the very core of the object-model for the new APIs.
For completeness sake, I should also note that creating a "raw" RequestContext instead of using Excel.run has some disadvantages. Excel.run does a number of useful things, the most important of which is automatic object tracking and un-tracking (not relevant here, since you're only reading back data; but would be relevant if you were loading and then wanting to write back into the object).
Finally, if I may recommend (full disclosure: I am the author of the book), you will probably find a good bit of useful info about Office.js in the e-book "Building Office Add-ins using Office.js", available at https://leanpub.com/buildingofficeaddins. In particular, it has a very detailed (10-page) section on the internal workings of the object model ("Section 5.5: Implementation details, for those who want to know how it really works"). It also offers advice on using TypeScript, has a general Promise/async-await primer, describes what .run does, and has a bunch more info about the OM. Also, though not available yet, it will soon offer information on how to resume using the same context (using a newer technique than what was originally described in How can a range be used across different Word.run contexts?). The book is a lean-published "evergreen" book, son once I write the topic in the coming weeks, an update will be available to all existing readers.
Hope this helps!
I've written a Firefox addon for the first time and it was reviewed and accepted a few month ago. This add-on calls frequently a third-party API. Meanwhile it was reviewed again and now the way it calls setInterval is criticized:
setInterval called in potentially dangerous manner. In order to prevent vulnerabilities, the setTimeout and setInterval functions should be called only with function expressions as their first argument. Variables referencing function names are acceptable but deprecated as they are not amenable to static source validation.
Here's some background about the »architecture« of my addon. It uses a global Object which is not much more than a namespace:
if ( 'undefined' == typeof myPlugin ) {
var myPlugin = {
//settings
settings : {},
intervalID : null,
//called once on window.addEventlistener( 'load' )
init : function() {
//load settings
//load remote data from cache (file)
},
//get the data from the API
getRemoteData : function() {
// XMLHttpRequest to the API
// retreve data (application/json)
// write it to a cache file
}
}
//start
window.addEventListener(
'load',
function load( event ) {
window.removeEventListener( 'load', load, false ); needed
myPlugin.init();
},
false
);
}
So this may be not the best practice, but I keep on learning. The interval itself is called inside the init() method like so:
myPlugin.intervalID = window.setInterval(
myPlugin.getRemoteData,
myPlugin.settings.updateMinInterval * 1000 //milliseconds!
);
There's another point setting the interval: an observer to the settings (preferences) clears the current interval and set it exactly the same way like mentioned above when a change to the updateMinInterval setting occures.
As I get this right, a solution using »function expressions« should look like:
myPlugin.intervalID = window.setInterval(
function() {
myPlugin.getRemoteData();
},
myPlugin.settings.updateMinInterval * 1000 //milliseconds!
);
Am I right?
What is a possible scenario of »attacking« this code, I've overlooked so far?
Should setInterval and setTimeout basically used in another way in Firefox addons then in »normal« frontend javascripts? Because the documentation of setInterval exactly shows the way using declared functions in some examples.
Am I right?
Yes, although I imagine by now you've tried it and found it works.
As for why you are asked to change the code, it's because of the part of the warning message saying "Variables referencing function names are acceptable but deprecated as they are not amenable to static source validation".
This means that unless you follow the recommended pattern for the first parameter it is impossible to automatically calculate the outcome of executing the setInterval call.
Since setInterval is susceptible to the same kind of security risks as eval() it is important to check that the call is safe, even more so in privileged code such as an add-on so this warning serves as a red flag to the add-on reviewer to ensure that they carefully evaluate the safety of this line of code.
Your initial code should be accepted and cause no security issues but the add-on reviewer will appreciate having one less red flag to consider.
Given that the ability to automatically determine the outcome of executing JavaScript is useful for performance optimisation as well as automatic security checks I would wager that a function expression is also going to execute more quickly.