Have you ever been annoyed by the fact that getAttrs and setAttrs are asynchronous, but you really don't want to deal with that? Maybe you tried Promises, but they don't work with sheet workers? Well, I can't solve those problems, but I was annoyed by the API of getAttrs and setAttrs, so I made a new one. Since it does not support getSectionIds (yet?), it's not a complete replacement, but maybe someone finds it useful. You can paste this at the start of your sheet workers. class AttributeSetter {
constructor(attrs) {
this._sourceAttrs = attrs;
this._targetAttrs = {};
}
setAttr(name, value) {
this._sourceAttrs[name] = value;
this._targetAttrs[name] = value;
}
setAttrs(values) {
for (let key in values) {
this.setAttr(key, values[key]);
}
}
getAttr(name) {
return this._sourceAttrs[name];
}
finalize(callback) {
getAttrs(Object.keys(this._targetAttrs), (values) => {
const finalAttrs = {};
for (let key in this._targetAttrs) {
if (String(this._targetAttrs[key]) !== values[key])
finalAttrs[key] = String(this._targetAttrs[key]);
}
setAttrs(finalAttrs, { silent: true }, callback);
});
}
}
const attributeHandler = {
get: function (target, name) {
return target.getAttr(name);
},
set: function (target, name, value) {
target.setAttr(name, value);
return true;
}
};
function getSetAttrs(attrs, callback, finalCallback) {
getAttrs(attrs, (values) => {
const setter = new AttributeSetter(values);
callback(new Proxy(setter, attributeHandler), setter);
setter.finalize(finalCallback);
});
}
From then on, you can use the magic getSetAttrs function and a single callback to treat attributes like they are object properties (again, as long as you don't need repeating sections). How does it work? Your callback function (which must be synchronous, so don't use any asynchronous API functions!) will be called with one or two arguments, let's call them attrs and setter . The first of these, attrs , allows you to set and get attributes like they are object properties. E.g., you can use the following: getSetAttrs(["foo", "bar"], function(attrs) {
attrs["baz"] = attrs["foo"] + attrs["bar"];
}); And that's it! The attribute will be set correctly, and if you use it in a follow-up calculation, it will work as expected. The second argument (optional) setter allows you to set attributes in bulk by calling setter.setAttrs() : getSetAttrs(["foo", "bar"], function(attrs, setter) {
setter.setAttrs({
"baz": attrs["foo"] + attrs["bar"],
"another_attributes": 23 * (parseInt(attrs["foo"]) || 0)
});
}); There is some other magic in place behind the scenes: All the attributes will be set in one operation, which is both faster and less confusing than asynchronous calls to setAttrs spread throughout the code. All attribute values are coerced to String before being set. There are some strange edge cases avoided by doing that (believe me). I personally find it more pleasant to set attributes using object notation. Attributes are not set if they are equal to the values already on the sheet. This prevents you from needlessly creating attributes that clutter the Attributes & Abilities tab. Attributes are always set using 'silent' mode, which prevents further registered callbacks from triggering apart from the explicit finalCallback argument to getSetAttrs (though you could change that). There is no longer any need to gather everything in one object for a final setAttrs call (depending if you like that, this may be a plus or a minus :D). Any (synchronous) modifications in the callback will be collected and applied at the end, so any way of modifying it (one big object, multiple property assignments, passing setter or attrs on to subfunctions) is maximally efficient. Maybe someone can use this, I was getting annoyed of multiple levels callbacks, so I wrote this.