#SWB Traits in Actor sheet

1 messages ยท Page 1 of 1 (latest)

inland schooner
#

Just to reduce the amount of messages in module-development channel.

bleak anvil
#

okay so, lemme get everything that relates to traits, as I have it

#
_onItemControl(event, variable="item") {
    event.preventDefault();

    // Obtain event data
    const button = event.currentTarget;
    const li = button.closest(".item");
    const item = this.actor.items.get(li?.dataset.itemId);

    // Handle different actions
    switch ( button.dataset.action ) {
      case "create":
        const cls = getDocumentClass("Item");
        return cls.create({name: game.i18n.localize("SIMPLE.ItemNew"), type: variable}, {parent: this.actor});
      case "edit":
        return item.sheet.render(true);
      case "delete":
        return item.delete();
    }
  }

this should be the script that create/edit/deletes it

inland schooner
#

So the first thing is to change your HTML to do #each over data.items

bleak anvil
#

so like

#
<div class="tab traits" data-group="primary" data-tab="traits">
            <ol class="trait-list">
                {{#each data.items as |trait id|}}
                <li class="trait flexrow" data-trait-id="{{trait._id}}">
                    <img src="{{trait.img}}" title="{{trait.name}}" width="24" height="24" />
                    <h4 class="trait-name">{{trait.name}}</h4>
                    {{!-- Iterate through all attributes on the trait and output buttons for any that are formula. --}}
                    <div class="trait-buttons">
                        {{#each trait.system.attributes as |traitAttr key|}}
                        {{#if traitAttr.dtype}}
                            {{#if traitAttr.isFormula}}
inland schooner
#

Yes - you should see ALL the items in your actor sheet.

bleak anvil
#

i'll take a look

inland schooner
#

The second step is to add an #if to only process items that have a type of "trait" (if that is how you are marking things as traits instead of other types of items).

bleak anvil
#

currently not showing

#

wait, forgot to save

#

yeah currently not showing

#

I wonder if i should just copy/paste the item script exactly

#

then modify it

#

hmm

inland schooner
#

That might be best.

#

You can keep the "|item id|" in your copied section too.

#

Once you see a list of items in your new tab, you can add an "if" statement to show all the traits in your Traits tab, and NOT show them in the Items tab.

bleak anvil
#

okay

#

its currently showing all items, as per the items tab

#

and we can make new items in either tab

#

so i should add a filter somewhere

inland schooner
#

So second step is to add an #if into the template.

#

After {{#each data.items as |item id|}} you need to add a new line.
{{#if (eq item.type "trait") }}
and just before the matching {{/each}} you need to add {{/if}}

bleak anvil
#

done

inland schooner
#

That should reduce the list of items to only those items with a type of "trait"

bleak anvil
#

now it should show nothing i think

#

okay yep

#

its showing nothing but clicking "'create" will add anothing object to Items

#

so i just need to make the Create Item button on traits make a new object with the "trait" type

inland schooner
#

Yes.

#

You will need to add a similar #if to the Items tab to ignore items with a type of trait.

bleak anvil
#

whats not eq

#

is it just not eq

#
{{#if (not eq item.type "trait")}}
inland schooner
#

I'm just looking ๐Ÿ™‚

bleak anvil
#

nws

inland schooner
#

Almost, you need to wrap not inside more parentheses.
{{#if (not (eq item.type "trait")) }}

#

(Edited, to put ( in front of not

bleak anvil
#

okay and i think if i want to make a new item with type trait

#

then

#
html.find(".traits .item-control").click(this._onItemControl.bind(this, "trait"));
#

should work for this function

#
_onItemControl(event, variable="item") {
    event.preventDefault();

    // Obtain event data
    const button = event.currentTarget;
    const li = button.closest(".item");
    const item = this.actor.items.get(li?.dataset.itemId);

    // Handle different actions
    switch ( button.dataset.action ) {
      case "create":
        const cls = getDocumentClass("Item");
        return cls.create({name: game.i18n.localize("SIMPLE.ItemNew"), type: variable}, {parent: this.actor});
      case "edit":
        return item.sheet.render(true);
      case "delete":
        return item.delete();
    }
  }
#

uh oh seem to have screwed something in the html, sheet won't open at all

inland schooner
#

Have you changed the classes in your copied HTML to use "traits" and "trait" instead of "items" and "item"?

bleak anvil
#

not yet

inland schooner
#

Only the class names need to change.
Your html.find is looking for an element with a class called "traits"

inland schooner
#

For your html.find, only the top <div class="tab items"... needs to be changed to have "tab traits" instead

bleak anvil
#
<div class="tab traits" data-group="primary" data-tab="traits">
inland schooner
#

Yes, that will also get the "sheet tab navigation" working.

bleak anvil
#

hmm

#
<ol class="item-list">
                {{#each data.items as |item id|}}
                {{#if (eq item.type "trait") }}
                <li class="item flexrow" data-item-id="{{item._id}}">

...

<div class="item-controls">
                        <a class="item-control" title="{{ localize "SIMPLE.ItemEdit" }}" data-action="edit"><i class="fas fa-edit"></i></a>
                        <a class="item-control" title="{{ localize "SIMPLE.ItemDelete" }}" data-action="delete"><i class="fas fa-trash"></i></a>
                    </div>
                </li>
                {{/if}}
                {{/each}}
            </ol>
#

this works fine

#
<ol class="item-list">
                {{#each data.items as |item id|}}
                {{#if (not(eq item.type "trait")) }}
                <li class="item flexrow" data-item-id="{{item._id}}">

....

<div class="item-controls">
                        <a class="item-control" title="{{ localize "SIMPLE.ItemEdit" }}" data-action="edit"><i class="fas fa-edit"></i></a>
                        <a class="item-control" title="{{ localize "SIMPLE.ItemDelete" }}" data-action="delete"><i class="fas fa-trash"></i></a>
                    </div>
                </li>
                {{/if}}
                {{/each}}
            </ol>
#

this causes an error

inland schooner
#

You might need a space after not, so #if (not (eq

bleak anvil
#

ah yes that does it

bleak anvil
#

with just the navigation change and this

html.find(".traits .item-control").click(this._onItemControl.bind(this, "trait"));

it doesn't work yet

inland schooner
#

If you change the <div> I mentioned above, then clicking your buttons should trigger your bind function.

bleak anvil
#

this is what i currently have in the topmost div

inland schooner
#

Ok.

bleak anvil
#

maybe the bind function isn't firing

inland schooner
#

Does your _onItemContrll trigger?

bleak anvil
#

or maybe because the other onItemControl exists

#

in the basic SWB

#

it fires that instead

#

as SWB has

html.find(".item-control").click(this._onItemControl.bind(this));
inland schooner
#

Ah, I see SWB is hooking it onto all those buttons.

bleak anvil
#

lemme log it

#

to see whats getting output

#

we should get "trait" as variable, it might be giving "item" though

inland schooner
#

I suppose changing the 'item-control' to 'trait-control' will prevent SWB from finding those buttons.

bleak anvil
#

yeah

#

yep it just logs item

#

so that's not firing

#

so i'd need to change item-control here

<div class="item-controls">
                        <a class="item-control" title="{{ localize "SIMPLE.ItemEdit" }}" data-action="edit"><i class="fas fa-edit"></i></a>
                        <a class="item-control" title="{{ localize "SIMPLE.ItemDelete" }}" data-action="delete"><i class="fas fa-trash"></i></a>
                    </div>
                </li>
                {{/if}}
                {{/each}}
            </ol>
            <p>
                <a class="item-control" title="{{ localize "SIMPLE.ItemCreate" }}" data-action="create"><i class="fas fa-plus"></i> {{ localize "SIMPLE.ItemCreate" }}</a>
            </p>
        </div>
inland schooner
#

So change both your HTML and your html.find to use "trait-control" as the class name.

bleak anvil
#

to trait-control

inland schooner
#

yes.

#

in those 3 places

bleak anvil
#

hmm

#

nope

#

lemme log again

inland schooner
#

did you also change it in your html.find call?

bleak anvil
#

yes

#
html.find(".trait-control").click(this._onItemControl.bind(this, "trait"));
#

hmm

inland schooner
#

Oh, I would also change the name of the routine to _onTraitControl, so that your class isn't overriding the base function of the underlying SWB actor sheet class.

bleak anvil
#

its not calling the function at all

#

oh that's a good idea

inland schooner
#

Is your html.find in the activateListeners(html) function of your actor sheet class?

bleak anvil
#

yep

#

okay

#

now it is calling the function

#

but not making an item with the type "trait"

#

its still adding it to the "items" tab but not the traits tab

#

wait

#

i know what the problem is

inland schooner
#

It looks like the "Item" definition in template.json doesn't add a field called "type".

bleak anvil
#

yeah

#

i might need to redo that

inland schooner
#

Can you add it to the attributes{} of the item?

bleak anvil
#

probably

inland schooner
#

so you would check item.attributes.type in the {{#if statements too.

#

or maybe the "Groups" field?

bleak anvil
#

where would i add it to attributes{} in here

#
_onTraitControl(event) {
    console.log("here")
    event.preventDefault();
    
    // Obtain event data
    const button = event.currentTarget;
    const li = button.closest(".item");
    const item = this.actor.items.get(li?.dataset.itemId);

    // Handle different actions
    switch ( button.dataset.action ) {
      case "create":
        const cls = getDocumentClass("Item");
        return cls.create({name: game.i18n.localize("SIMPLE.ItemNew"), type: "item"}, {parent: this.actor});
      case "edit":
        return item.sheet.render(true);
      case "delete":
        return item.delete();
    }
  }
inland schooner
#

in the cls.create call.

return cls.create({
 name: game.i18n.localize("SIMPLE.ItemNew"),
 {attributes: { type: "item"}},
 {parent: this.actor});
bleak anvil
#

this still needs type: "item" i think

#

the basic SWB one has it

inland schooner
#

Ah true.

#
return cls.create({
 name: game.i18n.localize("SIMPLE.ItemNew"),
 type: "Item".
 {attributes: { type: "trait"}},
 {parent: this.actor});
bleak anvil
#

in this case i should overwrite the normal one

#

because i'll need to add {attributes: { type: "item"}}, to the other one right?

#

otherwise {{#if (not (eq item.attributes.type "trait")) }} will cause a crash?

inland schooner
#

No need to add it to the other one.
If the attribute is missing, then it isn't a Trait ๐Ÿ™‚

bleak anvil
#

huh okay i thought it would just crash because it can't perform the operation

inland schooner
#

I think the value "item.attributes.type" will be null or undefined, rather than crashing.

bleak anvil
#

lets give

return cls.create({name: game.i18n.localize("SIMPLE.ItemNew"), type: "item"}, {attributes: { type: "trait"}}, {parent: this.actor});

a try

#

hmm

inland schooner
bleak anvil
#

it makes an item but it doesn't show up in either Traits or Items

#

seems to be a difference between firing it in Traits and firing it in Items

inland schooner
#

You have } in wrong place:

return cls.create({name: game.i18n.localize("SIMPLE.ItemNew"), type: "item", attributes: { type: "trait"}}, {parent: this.actor});
bleak anvil
#

oh

inland schooner
#

attributes wasn't being set.

bleak anvil
#

okay

#

now its showing in Items but not in Traits still

inland schooner
#

I think the attributes part of the actor might be more complicated than just a string.

bleak anvil
#

probably

#

i can't see it in here

inland schooner
#

Ok. maybe best to set a FLAG on the item.

#

In your getData, loop over all the items, and set a new flag on each item called "isTrait" - and set value to item.getFlag('yourmodulename'. 'isTrait')

#

Then the HTML can simply check {{#if item.isTrait}} for traits, and
{{#if (not (item.isTrait)) }} for your items list.

bleak anvil
# inland schooner In your getData, loop over all the items, and set a new flag on each item called...
async getData(options) {
    const context = await super.getData(options);
    EntitySheetHelper.getAttributeData(context.data);
    context.shorthand = !!game.settings.get("worldbuilding", "macroShorthand");
    context.systemData = context.data.system;
    context.dtypes = ATTRIBUTE_TYPES;
    context.biographyHTML = await TextEditor.enrichHTML(context.systemData.biography, {
      secrets: this.document.isOwner,
      async: true
    });
    return context;
  }
#

context.items = ?

inland schooner
#

So before return context you can do:

for (const item of context.data.
items) {
  item.isTrait = item.getFlag('yourmodulename', 'isTrait') || false
}
#

It is either context.items or context.data.items
Probably context.data.items

bleak anvil
#

then when i'm doing cls.create i do

#

isTrait: {True} ?

inland schooner
#

In your create routine, you can set the flag during creation:

return cls.create({
name: game.i18n.localize("SIMPLE.ItemNew"), 
type: "item", 
flags: { ['yourmodulename'] : { isTrait: true }} }, 
{parent: this.actor});
#

It needs to be stored in the right place for getFlag to find it ๐Ÿ™‚

bleak anvil
#

okay lemme see

#

maybe it is context.items

#

also not that

inland schooner
#

No, I think the "Item" object has been cleansed of functions.
You could try accessing the flag field directly:
item.isTrait : item.flags?.['yourmodulename']?.isTrait

bleak anvil
#

i think its failing here

#
async getData(options) {
    const context = await super.getData(options);
    for (const item of context.items) {
        item.isTrait = item.getFlag('icon-145-sheet', 'isTrait') || false
    }
    return context;
  }
#

hmm

#

maybe its better to edit the item object

#

to allow an attribute?

inland schooner
#

Replace the getFlag call with the direct access to item.flags?.['yourmodulename']?.isTrait

bleak anvil
#
async getData(options) {
    const context = await super.getData(options);
    for (const item of context.items) {
        item.isTrait : item.flags?.['icon-145-sheet']?.isTrait || false
    }
    return context;
  }
#

?

inland schooner
#

Sorry, using = not :

#
async getData(options) {
    const context = await super.getData(options);
    for (const item of context.items) {
        item.isTrait = item.flags?.['icon-145-sheet']?.isTrait || false
    }
    return context;
  }
bleak anvil
#

okay

inland schooner
#

Or should it be item of context.data.items ?

bleak anvil
#

the sheet will open

#

but

#

if i try to make an item

#

i don't think SWB is set up to allow this

#

so will probably need to make a new item.js

#

that inherits the old + adds a bit for attributes or flags

inland schooner
#

That error indicates that the HTML template is not quite right.

#

With the change to getData, the HTML #if statements can be simplified

{{#if item.isTrait}} for your trait list
{{#if (not (item.isTrait)) }} for your item list

bleak anvil
#

yes i have these

#
<ol class="item-list">
                {{#each data.items as |item id|}}
                {{#if (not (item.isTrait)) }}
                <li class="item flexrow" data-item-id="{{item._id}}">
#
<ol class="item-list">
                {{#each data.items as |item id|}}
                {{#if item.isTrait }}
                <li class="item flexrow" data-item-id="{{item._id}}">
#

unless the second one is space sensitive

#

no the space is supposed to be there

inland schooner
#

In the getData() function, can you use a breakpoint to check that the context.data.items has an isTrait field on every item?

bleak anvil
#

err

#

how do i do that

inland schooner
#

In your browser/app, open the console with F12.

bleak anvil
#

ye

inland schooner
#

Select the Sources tab, and on the left you see a directory tree of all the files.

#

There should be one that says "modules" or "modules/yourmodulename"

#

If you open that, you should find your .js source file there.

#

Click on that file, and the right pane changes to show your source file.

bleak anvil
#

yea

#

left click to set breakpoint?

inland schooner
#

Find the correct line in the source file, then click on the line number of that line.

bleak anvil
#

yep

inland schooner
#

good.

#

Now try opening your actor sheet.

#

Oh wait.

bleak anvil
inland schooner
#

Put the breakpoint on the return statement, not the line you showed above.

bleak anvil
#

i think this is what you're looking for

inland schooner
#

The controls are on the left at the top of your picture - the right-arrow causes the debugger to resume control.

#

Ok, in the bottom right panel, open up the "items" array, and see if each entry has a flags

bleak anvil
#

looks like yes

#

so does appear to work

inland schooner
#

As well as the flags, does each item have an "isTrait" property

bleak anvil
#

yes

#

all are set to true

#

a bit weird because i thought this script default set to false

#

oh well

#

its only the one that i added items to before that is broken hmm

#

new actor works fine

#

but once i add an item

#

close

#

and try to reopen

#

it breaks

#

yeah okay

#

i made one in the items tab (shows) has isTrait: false

#

made one in the traits tab (doesn't show), in the console it says its there with isTrait: true

#

closed the actor

#

try to open the actor, error

#

interesting

#

i can add items to Items and close without it erroring when i reopen

#

but if i try to add items to Traits, not only will it not show, but it causes an error when i reopen

inland schooner
#

Does the breakpoint trigger immediately on opening the actor sheet?

bleak anvil
#

yes

#

well

#

opening one of these broken actor sheets at least

#

with a fresh one it also triggers immediately

inland schooner
#

Ah, maybe one of it {{#if is wrong:
It probably should be
{{#if (not item.isTrait) }}
(without the extra parantheses after not

bleak anvil
#

that one isn't the broken one though

#

i can add any number of non-traits and it won't break

#

but if i add a trait it will

#

hmm

#

i'll try

#

oh

#

that was it

#

there's a couple issues like the css being off for some random reason

#

but otherwise appears to work

inland schooner
#

The simple.css has a section:

.worldbuilding .item-list .item-controls {
  flex: 0 0 36px;
}

which you will have to duplicate to set the same for .trait-list

bleak anvil
#

ah right

inland schooner
#

You won't be able to create Items as Traits in the world without some more code to set the Traits flag on a created item.

bleak anvil
#

should probably get that done

inland schooner
#

Maybe a macro is the simplest way, just set that flag on the currently open Item.

#

But that is a separate task - the first task is getting your Actor sheet working ๐Ÿ˜‰

bleak anvil
#

yeah i fixed the css

#

all my other things broke but i can fix those later

#

wait

#

no

#

they didn't break

inland schooner
#

lol

bleak anvil
#

yeah just forgot how i set up my own things lmao

#

oki thanks very much

#

I don't really need to make new traits outside the sheet anyway

#

(at least at this point in time)

#

(I'll probably come back to regret this later)

inland schooner
#

Write a basic macro now that sets the flag, just for future reference ๐Ÿ™‚

bleak anvil
#

uhh

#

okay

#

hmm

#

i have no idea how to affect the currently open thing

inland schooner
#

Neither do I. but just use a macro to remember the setFlag call that you need to make.

#

Just as a Note, so you can come back later and it will remind you what you need to do.

bleak anvil
#

ok, thanks a lot Farling!