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

[Sheet Author] replace/rename existing attribute

May 13 (4 years ago)
vÍnce
Pro
Sheet Author

Couple of use cases I'm looking at;

  1. Let's say "someone" created an attribute on a sheet, but later(sheet's been live for quite some time) realized it was misspelled and would like to correct said attribute with a properly spelled attribute.
  2. Sheet has numerous mixed case attribute names, but I would like to change them to all lowercase for consistency.  Wondering if attributes could simply be renamed to lowercase without issue...?
  3. Sheet is using the same attribute name in two locations. One is located within a repeating section and represents spell @{level} and the other is used outside the repeating section to represent class @{Level}.  I know that you're supposed to use unique attribute names. Note there is a difference in case and one of the attribute is used within a repeating section which "might" make it unique even with the same name...?  

I'm thinking I need to replace the new attribute name in the html, then get the value of the old attribute(getattrs) and set the new attribute equal to the old attribute using a sheetworker triggered by a sheet version check when opening the sheet?  Sound about right?  Thoughts? TIA 

May 13 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

For #2, you can go ahead and change them without issue - roll20 doesnt care about case.  the names Attribute, attribute, ATTRIBUTE, and aTtRiBuTe are all the same attribute.

The only place where case matters is in the fieldset class of a repeating section, and that should always be entirely lower case.

For #3, this primarily matters with sheet workers. If you have an attribute called level, and repeating section called repeating_spells which also has level, any sheet worker checking for the change of level will trigger on both of them. It might not do anything, or it might silently crash, but it will still trigger.

This is why although its clunky, it's always a good idea to give names inside a repeating section a unique suffix.

So I'd recommend changing the name inside the repeating section, and if you're doing that, you may as well correct the misspelling at the same time.

I'm thinking I need to replace the new attribute name in the html, then get the value of the old attribute(getattrs) and set the new attribute equal to the old attribute using a sheetworker triggered by a sheet version check when opening the sheet?  Sound about right?  Thoughts? TIA 

Yes, thats right. You want to create a new attribute, call it something like codeversion, set its value to 0.

Then create a sheet:opened worker, which checks for version value, and if its below, say 1, you launch a function.

In that function, you have the getAttrs for the old attributes, transfer them to the new attributes, and also set the codeversion attribute to 1.

It's not important for this function, but if you plan on having multiple version changes, you want to include in the setAttrs callback a call back to the version checking function, so that multiple updates can be done in sequence, without overlapping.


May 13 (4 years ago)

Edited May 13 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

Here's some code that should do what you need, with some name tweaks. I;ll post an explanation of each part, then the completed thing at the bottom.

So first you need the sheet opened:

on('sheet:opened', () => {
    // log statement that you are checking version
    getAttrs(['codeversion'], v => {
        versionator(parseInt(v.codeversion) || 0);
    });
    //log statement that version check is complete
});
This checks the attribute you have named for the version checking. It can be the same as your normal version attribute.
This calls a separate function to do the actual version checking. This is important because sometimes your version checking will call it again, so it needs to be separated from the sheet: opened event.

So, then the version checking function. There's more than one way to build this. It could look like this:
const versionator = (codeversion) => {
    if(codeversion < 1) {
        version_0_to_1();
    } else if(codeversion < 2) {
        version_1_to_2();
    } else if(codeversion < 3) {
        version_2_to_3();
    }
};
or this:
const versionator = (codeversion) => {
    switch(true) {
        case (codeversion <1):
            version_0_to_1();
            break;
        case (codeversion <2):
            version_1_to_2();
            break;
        case (codeversion <3):
            version_2_to_3();
            break;
        default:
            // log statement that no change is needed?
    }
};
Or the order could be reversed, going from highest version to low (and reversing the equality comparison).

All these versions of the function do exactly the same thing. They accept a version parameter, and then go through steps to see if the version is less than a threshold, and if so run a version upgrade function.
Think about what happens if you r sheets code is up to version 3, and someone has never opened their sheet since version 0.
The versionator will run, find they are below version 1, and so run the code that upgrades it to version 1, and then stop. (I'll explain why it is important that it stops after one and only upgrade, below.)

So in the function that upgrades to version 1, we have to run versionator again.
So we would have a function that looks at base like this:
const version_0_to_1 = () => {
    getAttrs(['some', 'attributes', 'to', 'change'] = (v) => {
        // create an object to hold the new attributes
        const update = {};
        // transfer to old attributes into the new attributes
        update.new_some = v.some;
        update.new_attributes = v.attributes
        update.new_to = v.to;
        update.new_change  = v.change;
        // add the version attribute
        const codeversion = 1;
        update.codeversion = version
        setAttrs(update, // send the new attributes to the sheet
            {silent:true}, // you probably want it not to trigger sheet workers
            versionator(codeversion)); // and run the versionator function again
    });
};
So some things to note here: you add the new version number to the updated attributes.
and you include a new call to the versionator function inside the setAttrs function. This is really important. It means that it only runs after the sheet attributes have been updated.
    And you include the version number in the paremeters.
So now, the sheet has been upgraded to version 1, and goes back to versionator. The first check - is it below 1? - is run again, and it passes that. It reaches the second check - is it below 2? Since it isnt, that function now runs. And when it ends, it send back to the versionator, and so on.

So, this seems a little convoluted. It would be easier to run the versionator without stopping after each upgrade, just let it run and complete all 3 upgrades (in this example). But setAttrs is asynchronous - it starts to work but doesnt finish immediately. The rest of the code continues simultaneously, without waiting for the setAttrs process to finish. So you could easily have the version 3 upgrade running at the same time as the version 1 upgrade - and the version 3 upgrade might complete before  the version 1 upgrade does. Sometimes, the order things happen can lead to different results. When doing sheet updates it's especially important that upgrades complete properly.

With the code being constructed this way, you can update a sheet multiple times, and be confident that each update is fully complete before the next one starts. 


So, with that explanation out of the way, here's code that will serve your purpose:
on('sheet:opened', () => {
    // log statement that you are checking version
    getAttrs(['codeversion'], v => {
        versionator(parseInt(v.codeversion) ||0);
    });
    //log statement that version check is complete
});

const versionator = (codeversion) => {
    if(codeversion < 1) {
        version_0_to_1();
    }
};

const version_0_to_1 = () => {
    // log statement that you are updating to version 1
    getSectionIDs('repeating_spells', idarray => {
        const fieldnames = [];
        idarray.forEach(id => fieldnames.push(`repeating_spells_${id}_level`));
        getAttrs(['NAMEWITHTYPO', ...fieldnames], v => {
            const update = {};             const codeversion = 1;
            update.codeversion = codeversion;
            update['NAMEWITHOUTTYPO'] = v['NAMEWITHTYPO'];
            idarray.forEach(id => {
                update[`repeating_spells_${id}_spell_level`] = v[`repeating_spells_${id}_level`];
            });
            // log statement that you have completed updating to version 1
            setAttrs(update,{silent:true}, versionator(codeversion));
        });
    });
}

Change each instance of NAMEWITHTYPO and NAMEWITHOUTTYPO to what they should be. Likewise I've used repeating_spells for your section name, and spell_level for your new spell level name. Change them to what you need them to be.

May 14 (4 years ago)
vÍnce
Pro
Sheet Author

Thanks GiGs.  I have a version checking function on the sheet(ad&d1e and forbidden lands) already which includes some previous code updates based on the sheet's version. I'll see if I can figure out out how to use your code to work with the existing versioning code.  Although, your versionator model looks very tempting.

May 14 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

I looked at the forbidden lands sheet. Does the version checker do anything other than set announcements?

I also looked at the AD&D version code and recoiled in horror. Picture me rocking in the corner whispering "no no no no"... If Scott saw this code, I fear for his sanity.

More seriously, it looks like you have combined two functions which should be separate: 

  • Actual version checking, where there is a change to the sheets code that if a sheet worker isnt run to fix it, will destroy user data
  • minor sheet updates, where the sheet's function hasnt changed in a way that needs sheet worker code to fix

Incidentally this is why i suggested using a code_version attribute, so you can have it separate from sheet_version, which manages whether to show announcements and normal sheet updates. The code_version never needs displaying to users, btw. It's only for internal use.


If you want help to streamline and optimise this code, we can take it to PM.



May 14 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

Okay I figured out what each step of the AD&D version checker was doing, and I've rewritten it to match the versionator I provided on the forum, though i have rewritten it a touch. You can find it here

https://gist.github.com/G-G-G/52b96cff0b827c42f9d04ab411fb42e8

I recommend starting a new campaign, and commenting out parts of the versionator before opening any sheets, and test it with values of 0.1, 1.2, and 1.5 in sequence to make sure each of the old version functions are converted properly.

So, the versionator looks like this

const versionator = (old_version, new_version) => {
    // set the sheet version you are upgrading sheet to, and set show_announcement to 1 if you want to force an announcement to be shown. 
    // dont set a code_version; we use the switch below for that.

    if(old_version < 0.1) {
        dmgSwap(0.1, new_version);
    } else if (old_version < 1.2) {
        maxSwap(1.2, new_version);
    } else if (old_version < 1.5) {
        nwpMacroColorUpdate(1.5, new_version);
    } else if (old_version < 1.6) {
        spelllNameFix(1.6, new_version);
    } else if(old_version < new_version) {
        setAttrs({
            sheet_version: new_version
        });
    }
};


On your first run comment out as follows:

const versionator = (old_version, new_version) => {
    // set the sheet version you are upgrading sheet to, and set show_announcement to 1 if you want to force an announcement to be shown. 
    // dont set a code_version; we use the switch below for that.

    if(old_version < 0.1) {
        dmgSwap(0.1, new_version); /* ===========================================
    } else if (old_version < 1.2) {
        maxSwap(1.2, new_version);
    } else if (old_version < 1.5) {
        nwpMacroColorUpdate(1.5, new_version);
    } else if (old_version < 1.6) {
        spelllNameFix(1.6, new_version); ============================================= */
    } else if(old_version < new_version) {
        setAttrs({
            sheet_version: new_version
        });
    }
};

So it just does the first one.

Then try on a different character with

const versionator = (old_version, new_version) => {
    // set the sheet version you are upgrading sheet to, and set show_announcement to 1 if you want to force an announcement to be shown. 
    // dont set a code_version; we use the switch below for that.


    if(old_version < 0.1) {
        dmgSwap(0.1, new_version);
    } else if (old_version < 1.2) {
        maxSwap(1.2, new_version);
/* ===========================================     } else if (old_version < 1.5) {
        nwpMacroColorUpdate(1.5, new_version);
    } else if (old_version < 1.6) {
        spelllNameFix(1.6, new_version);
============================================= */
    } else if(old_version < new_version) {
        setAttrs({
            sheet_version: new_version
        });
    }
};

and so on since you've tested each section. Let me know if any aren't working properly.


I added your new version change at version 1.6, so you can raise it to there to see it in action. But it has some edits you need to do first: the NAMEWITHTYPO and NAMEWITHOUTTYPO attributes, and the repeating section name "spell_level", if you want it to be something else, you'll need to change it.


I also included a blank template for future upgrades which includes all the bits that are essential for the versionator to work. It looks like this:

const blankUpdateTemplate = function (old_version, new_version) {
// have this line near the start
const update = {}; // do your stuff = geSectionIDs, getAttrs, all the various calculations // then end the worker
update.sheet_version = old_version;
setAttrs(update, {
silent: true
}, versionator(old_version, new_version));
// dont forget any close brackets for getyttrs, etc.
}

I do get carried away sometimes...

May 15 (4 years ago)

Edited May 15 (4 years ago)
vÍnce
Pro
Sheet Author
Picture me rocking in the corner whispering "no no no no"... 

Lol.  GiGs, I'm always willing to incorporate your suggestions.  Nearly all of the sheetworker code on the sheet was done by me (non-coder) years ago.  I know a little more now, thanks to your tutelage and forum posts. ;-)  I really appreciate any help that you are willing to offer.  I'll change over to your updated versioning function as well as use it to implement a couple attribute updates I should make to the sheet.  Thank you.

May 15 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

hehe, great.

May 15 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

I added a link to here on the https://wiki.roll20.net/Sheet_Author_Tips page, so there is now a section for "sheet versioning" to look up or reference in the future.