Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Sheetworker class prototype functions unable to resolve object properties

I have a class and a prototype function for that class.  However, "this.Name" comes back as undefined. Any ideas why? class AbilityScore { /**@Constructor @param {string} [name]: The name of the ability score you wish to load. @param {function} [callback]: A callback to call after all ability scores are ready. @param {ModifierCollection} [modifierCollection]: A readied ModifierCollection. */ constructor(name,callback,modifierCollection) { let score = Repository.AbilityScores[name]; this.__ready = callback; this.Name = name; this.Abbreviation = score.Abbreviation; getAttrs([ `AbilityScore_${name}_Base`, `AbilityScore_${name}_Roll`, `AbilityScore_${name}_Type` ], (attributes) => { this.Roll = attributes[`AbilityScore_${name}_Roll`] || 10; this.Base = attributes[`AbilityScore_${name}_Base`] || this.Roll; this.Type = attributes[`AbilityScore_${name}_Type`] || 'Normal'; if (modifierCollection) { this.ApplyModifiers(modifierCollection); } this.__ready(this.Name, this); }); } } /**@ApplyModifiers @param {ModifierCollection} [modifierCollection]: A readied ModifierCollection. */ AbilityScore.prototype.ApplyModifiers = (modifierCollection) => { debug.message('AbilityScore.p.AM.Name => ' + this.Name); let score = Repository.AbilityScores[this.Name]; this.Total = modifierCollection.ApplyModifiers([this.Name, "Ability Scores"], [], this.Base); this.BoundTotal = Math.max(Math.min(this.Total, score.Values.length), 1) let type = null; if (this.Type !== 'Normal' && score.Types) { type = score.Types[this.Type]; } _.each(score.DependentValues, (dv) => { this[dv.Key] = score.Values[this.BoundTotal - 1][dv.Key]; if (type && type[dv.Key]) { this[dv.Key] = modifierCollection.ApplyModifiers( [`${this.Name} Dependent Values`, `${dv.Value}`], [], evaluator.eval( type[dv.Key] , { b: this.BoundTotal, u: this.Total, x: this[dv.Key] } ) ); } }); }
1532790171

Edited 1532790548
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
This is probably because getattrs (along with most sheet workers functions) is asynchronous, so when you create the class and then later try to access the applymodifiers prototype method, the class isn't "complete" for lack of a better word (or at least it is highly unlikely to be so). Since classes are just unhoisted functions in JS, as I understand them, I haven't looked at using them for much. Maybe try it with the getattrs outside the class, and simply passing the results of getattrs into it? Also, unless those ability scores and modifiers aren't going to need anything else to calculate their effect on other attributes, I would highly recommend changing how you are structuring your script so that you make a single getAttrs for all the attributes that may be needed for a given type of change and then use logic (ifs, switches, objects, etc) to decide what to do with them based on the event type, source attribute, and new/previous value.
**grumble**
Thanks.  I'm a .NET developer by trade, so I was really hoping to keep to a strong object-oriented pattern.  It's going to take some thinking to figure out how to make that work. I tried doing simple event-driven logic, but it just results in unmaintainable spaghetti code as things about the sheet change.
1532796876

Edited 1532796963
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
so, I typically do a massive getAttrs in response to the change, then I can code as if it was synchronous code and then do a single setAttrs at the end. If you did that, you should be able to use your classes and prototypes, although keep in mind that they aren't "actually" classes, they're more like a type of function (similar to say let/const vs var). EDIT: Note that a single getAttrs of 1000 attributes and a getAttrs of a single attribute will take roughly the same amount of time (certainly not 1000 fold difference between them), whereas doing 1000 getAttrs will take forever as the asynchronous processes have to be resolved individually.
I wrote this badboy to help do just that... I ripped some of the code from the wiki. Basically I just add a bunch of properties and repeating sections to it, then kick it off.  I haven't had the chance to test it yet.  The main issue I have is with getting the ordered ids from repeating sections.  It requires me to fire off 2 getAttrs requests per repeating section.  I just woke up, so I haven't really looked into improving it. class Getter { constructor() { this.Properties = []; this.RepeatingSections = {}; } } /**@AddProperty @param {string} [property]: The name of the property you'd like to add to the getter array. */ Getter.prototype.AddProperty = (property) => { this.Properties.push(property); } /**@AddRepeatingSection @param {string} [repeatingSectionName]: The name of the repeating section. @param {Array<string>} [properties]: A string array of properties within the repeating section. */ Getter.prototype.AddRepeatingSection = (repeatingSectionName, properties) => { let section = this.Repeatingsections[repeatingSectionName]; if (section) { section.Properties = _.union(section.Properties, properties); } else { this.RepeatingSections[repeatingsectionName] = new RepeatingSectionRequest(repeatingSectionName, properties); } } /**@InitiateGetAttrs @param {function} [callback]: The complete ability score objects.. */ Getter.prototype.BeginGetAttrs = (callback) => { this.__endGetAttrsCallback = callback; this.__getSectionIDsOrdered(this.__repeatingSectionCallback); } Getter.prototype.__repeatingSectionCallback = () => { if (_.every(this.RepeatingSections, (section) => { return section.Complete; })) { let g = this.__makeMasterGetArray(); getAttrs(g, (sheet) => { let output = {}; _.each(this.Properties, (property) => { output[property] = sheet[property]; }); _.each() }); } } Getter.prototype.__foldAttributesObject = (propertyArray, attributes) => { let output = {}; _.each(this.Properties, (property) => { output[property] = attributes[property]; }); _.each(Object.keys(this.RepeatingSections), (sectionName) => { let section = this.RepeatingSections[sectionName]; output[sectionName] = {}; _.each(section.Ids, (rowId) => { output[sectionName][rowId] = {}; _.each(section.Properties, (property) => { output[sectionName][rowId][property] = attributes[`repeating_${sectionName}_${rowId}_${property}`]; }); }); }); this.__endGetAttrsCallback(output); } Getter.prototype.__makeMasterGetArray = () => { let g = _.union([],this.Properties); _.each(Object.keys(this.RepeatingSections), (sectionName) => { let section = this.RepeatingSections[sectionName]; _.each(section.Ids, (rowId) => { _.each(section.Properties, (property) => { g.push(`repeating_${sectionName}_${rowId}_${property}`); }); }); }); return g; } Getter.prototype.__getSectionIDsOrdered = () => { _.each(Object.keys(this.RepeatingSections), (repeatingSection) => { Getter.getSectionIDsOrdered(repeatingSection, (idArray) => { this.RepeatingSections[repeatingSection].Ids = idArray; this.RepeatingSections[repeatingSection].Complete = true; this.__repeatingSectionCallback(); }); }); } class RepeatingSectionRequest { constructor(sectionName, properties) { this.SectionName = sectionName; this.Properties = properties; } } Getter.getSectionIDsOrdered = function (sectionName, callback) { 'use strict'; getAttrs([`_reporder_${sectionName}`], function (v) { getSectionIDs(sectionName, function (idArray) { let reporderArray = v[`_reporder_${sectionName}`] ? v[`_reporder_${sectionName}`].toLowerCase().split(',') : [], ids = [...new Set(reporderArray.filter(x => idArray.includes(x)).concat(idArray))]; callback(ids); }); }); };
The problem persists: class Getter { constructor() { this.Properties = []; this.RepeatingSections = {}; } } /**@AddProperty @param {string} [property]: The name of the property you'd like to add to the getter array. */ Getter.prototype.AddProperty = (property) => { this.Properties.push(property); //Throws Error this.Properties is undefined } AbilityScore.RetrieveGetAttrProperties = (name,getter) => { getter.AddProperty(`AbilitySscore_${name}`); getter.AddProperty(`AbilitySscore_${name}_Base`); getter.AddProperty(`AbilitySscore_${name}_Roll`); getter.AddProperty(`AbilitySscore_${name}_Type`); } on('change:abilityscore_strength_roll change:abilityscore_strength_base', (eventInfo) => { logger.Log(`${eventInfo.sourceAttribute} Changed - Old: ${eventInfo.previousValue}, New: ${eventInfo.newValue}`); let getter = new Getter(); AbilityScore.RetrieveGetAttrProperties('Strength', getter); }
I discovered the source of the problem.  I was mixing ES5 and ES6.  Class.prototype.function is ES5, in ES6 the prototype functions are simply declared inside the class declaration.
1532871371
GiGs
Pro
Sheet Author
API Scripter
Forgive me if I'm wrong because I'm not familiar with your code, but it seems like you are trying to load every attribute into a data object, and then extract attributes you need for that? Is that right? If so, it's a really  inefficient way to do things in roll20, and I cant imagine the use case where it would be needed. What is the problem you are trying to solve with this approach?
For object-oriented developers, typical JavaScript looks like spaghetti code. Utilizing ES6 classes, which, under the hood are just syntactical sugar, helps structure code in a way that makes it more manageable.  By dividing things into classes, especially these Getter & Setter classes, I create an abstraction layer between the Character Sheet and the rest of my Sheet Worker. As far as efficiency, I don't notice any performance loss.  Round-trip for changing something that triggers a BUNCH of changes, like an ability score, takes about 25ms.