Search code examples
javascriptractivejs

Referring to Child Properties in Ractive.js


New to Ractive.js and after reading over the docs I'm still hung up on some of the syntax. My example below creates a canvas tag for each element in my array and then instantiates a distinct object of a custom class for each to handle the animation. As noted in comments, I'm throwing three errors.

In the event handler I want to refer to the height property (defined in my array), but neither this.get(repeater.height) nor this.repeater.height work. I also want to call a method of the object associated with that event, but using this.myCustomClass.setFrame() isn't working either.

I'm also getting at least one error in the code for my animation class when I try to refer to methods of the canvas: this.canvas.getContext() where "canvas" is the id I passed in the object constructor.

So clearly I'm misunderstanding something about how to refer to child properties of Ractive instances. Any help here?

var repeater = [
    {id: "stalkerOne", width: 225, height: 432, left: 8, spriteSheetURL: "spriteSheets/stalkerone.jpg", rows: 5, columns: 5, totalFrames:  24},
    {id: "stalkerTwo", width: 175, height: 432, left: 230, spriteSheetURL: "spriteSheets/stalkertwo.jpg", rows: 6, columns: 5, totalFrames:  26},
    {id: "stalkerThree", width: 251, height: 432, left: 404, spriteSheetURL: "spriteSheets/stalkerthree.jpg", rows: 6, columns: 5, totalFrames:  28}
]

var CanvasAnimation = Ractive.extend( {
    oninit: function() {
        this.get('repeaters').forEach(function(repeater){
            var myCanvasSprite = new CanvasSprite(repeater.id, repeater.width, repeater.height, repeater.spriteSheetURL, repeater.rows, repeater.columns, repeater.totalFrames);
            myCanvasSprite.setFrame(0);
            //this.canvas.getContext() throwing error in class code
        });

        this.on('setFrame', function (event) {
            var offsetY = event.original.clientY - event.node.getBoundingClientRect().top;
            var relY = offsetY/this.get(repeater.height); //why doesn't this.get() work here?
            this.myCanvasSprite.setFrame(relY); //custom class method not working either...
        });
    }
});       

var canvasAnimation = new CanvasAnimation({
    el: '#container',
    template: '#template',
    data: { repeaters: repeater }
});

Solution

  • I think what you're missing is adding the array of CanvasSprite objects to the Ractive data, and then using the event context to get the current sprite (see https://jsfiddle.net/martypdx/srbvekc4/2/ for full example):

    var CanvasAnimation = Ractive.extend( {
        template: `
            {{#each sprites}}
                <li on-click='setFrame'>{{.id}}</li>
            {{/each}}`,
        oninit() {
            var sprites = this.get('repeaters').map( function(repeater){    
                return new CanvasSprite(repeater.id, repeater.width, repeater.height, repeater.spriteSheetURL, repeater.rows, repeater.columns, repeater.totalFrames);
            });
    
            // add the class instances to the data
            this.set( 'sprites', sprites );
    
            this.on('setFrame', function (event) {
                // here's how we get the sprite from the event
                var sprite = event.context;
                var offsetY = event.original.clientY - event.node.getBoundingClientRect().top;
                var relY = offsetY/sprite.height;
                sprite.setFrame(relY);
            });
        },
        onrender() {
            // wait till render, otherwise nothing there!
            this.get( 'sprites' ).forEach( sprite => sprite.setFrame(0) );
        }
    });  
    

    EDIT: You can also use components to better encapsulate each sprite (see https://jsfiddle.net/martypdx/srbvekc4/1/):

    var CanvasAnimation = Ractive.extend( {
        template: `
            {{#each repeater}}
                <Sprite options='{{this}}'></Sprite>
            {{/each}}`
    });    
    
    var Sprite = Ractive.extend({
        template: `<li on-click='setFrame'>{{sprite.id}}</li>`,
        oninit() {
            const sprite = new CanvasSprite( repeater.id, repeater.width, repeater.height, repeater.spriteSheetURL, repeater.rows, repeater.columns, repeater.totalFrames );
    
            // since example template is using properties from sprite,
            // we add it to the data
            this.set( 'sprite',  sprite );
    
            this.on('setFrame', function (event) {
                var offsetY = event.original.clientY - event.node.getBoundingClientRect().top;
                var relY = offsetY/sprite.height;
                sprite.setFrame(relY);
            });
        },
        onrender() {
            this.sprite.setFrame(0);
        }
    });
    
    var canvasAnimation = new CanvasAnimation({
        el: document.body,
        template: '#template',
        data: { repeaters: repeater },
        components: { Sprite }
    });
    

    UPDATED: Use multi-parameter constructor and also realized better encapsulation if component handles instantiation of CanvasSprite.