Create personalized components in your view using Aurelia's dynamic generation feature

I am currently in the process of developing a web application for a board game and have opted to utilize the Aurelia JavaScript framework for the front-end. While I am new to Aurelia, I encountered an obstacle when attempting to create a new instance of a custom component with just the click of a button. To provide further clarity on my issue, let me illustrate what I aim to achieve using an example. The configuration for the game project I want to execute is as shown:

Game Configuration Image

To shed more light on the problem I am facing, I have prepared a basic example. This challenge mainly roots from my unfamiliarity with Aurelia rather than any other factor. In this simplistic demonstration, I have the main app.js viewmodel and app.html view that Aurelia recognizes as the primary viewmodel and view respectively. They are structured as follows:

app.js

import { Card } from './card';

export class App {
    cards = [];

    newCard() {
        this.cards.push(new Card());
    }
}

app.html

<template>
    <require from="./card"></require>

    <button click.delegate="newCard()">New Card</button>

    <div>
        <li repeat.for="card of cards">
            <compose view-model="card"></compose>
        </li>
    </div>
</template>

Moreover, there exists a card component that simply represents a playing card. Below are its viewmodel and view:

card.js

export class Card {
    cardValues = ['2','3','4','5','6','7','8','9','10',
        'J','Q','K','A'];
    cardSuits = ['Diamonds', 'Clubs', 'Hearts', 'Spades'];

    value;
    suit;

    activate() {
        this.value = this.pickRandomItem(this.cardValues);
        this.suit = this.pickRandomItem(this.cardSuits);
    }

    pickRandomItem(data) {
        let index = Math.floor(Math.random() * (data.length -1));
        return data[index];
    }
}

card.html

<template>
    <div style="border: 2px solid black;
                display: inline-block;
                margin-top: 10px;">
        <h3>Value: ${value}</h3>
        <h4>Suit: ${suit}</h4>
    </div>
</template>

At present, I can generate new cards dynamically by clicking the button in the app view, triggering the instantiation of a new Card object in the app viewmodel's button click event handler. However, my concern lies in manually instantiating the Card objects from the app viewmodel. It seems like there should be a way to instruct Aurelia to create a new Card object, but I have not grasped how to achieve that yet. Therefore, my query is: Is there a more efficient method to dynamically produce custom components without the need to manually instantiate them as I am currently doing?

I find this approach incorrect because with the current structure, the constructor for a Card object is called twice instead of once. Additionally, if the Card class necessitates injected dependencies, I would have to pass those into the new Card objects manually, which feels cumbersome.

Thank you tremendously for your assistance!

You can access the minimal functional repository through this link on GitHub

Answer №1

The solution may involve utilizing the model property within the compose element. By using the model property, you can pass an object of values to be utilized in your view model. These values are accessible in the activate method as the first argument.

To implement this, follow these steps:

<li repeat.for="cardObj of cards">
    <compose view-model="card" model.bind="cardObj"></compose>
</li>

By following this approach, the card viewmodel remains intact while moving the generation logic out and passing the card object from the cards array. This results in a cleaner and simpler process, avoiding the instantiation of heavy objects repeatedly.

In your app.js file, creating a new object triggers the constructor each time due to the new keyword. Consequently, when the compose element calls it, the object is instantiated again, resulting in double instantiation.

Rather than using new Card(), you should push a simple object into your cards array. Move the card generation logic to a class that isn't instantiated multiple times.


Edit

After receiving additional feedback, here's an example of how everything could fall into place. The card creation logic has been moved into a function. This function generates an object with two properties: suit and value. This object is then passed to the compose element as data and utilized accordingly.

app.js

export class App {
    cards = [];

    newCard() {
        this.cards.push(generateCard());
    }
}

function generateCard() {
    let cardValues = ['2','3','4','5','6','7','8','9','10',
        'J','Q','K','A'];

    let cardSuits = ['Diamonds', 'Clubs', 'Hearts', 'Spades'];

    function pickRandomItem(arr) {
        let index = Math.floor(Math.random() * (arr.length -1));
        return arr[index];
    }

    return {
        suit: pickRandomItem(cardSuits),
        value: pickRandomItem(cardValues)
    };
}

app.html

<template>
    <button click.delegate="newCard()">New Card</button>

    <div>
        <li repeat.for="cardObj of cards">
            <!-- cardObj will be our object {suit: 'suit', value: 'value'} -->
            <compose view-model="card" model.bind="cardObj"></compose>
        </li>
    </div>
</template>

card.js

export class Card {
    suit;
    value;

    activate(model) {
        if (model) {
            this.suit = model.suit;
            this.value = model.value;
        }
    }
}

Your card.html file will remain unaffected.

Answer №2

One alternative method is to designate the Card class as transient and implement the Lazy resolver. Please forgive my use of ES6.

MainApp.js

import { Card } from './card';
import {Lazy,inject} from 'aurelia-framework';

@inject(Lazy.of(Card))
export class MainApp {
    constructor(cardCreator){
        this.cardCreator = cardCreator;
    }
    allCards = [];

    createNewCard() {
        this.allCards.push(this.cardCreator());
   }
}

Card.js

import {transient} from 'aurelia-framework';
@transient()
export class Card {
    //perform actions
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Use the JavaScript executor to combine a dynamic string

I have a String variable that retrieves IDs from an Excel sheet. String id = formatter.formatCellValue(sheet.getRow(i).getCell(2)); I am trying to dynamically update the ID using JavaScript executor, but it seems that the concatenation is not working cor ...

Dynamic element substitution

Can you provide guidance on how to create a smooth transition in the height of a container based on the child element's height? Currently, my code does not include any animation effects. setTimeout(() => { document.getElementById("page1").st ...

Error message: "Issue encountered with locating Node import module while operating within a docker

I've created a React app along with a Node.js server that includes the following imports: import express from 'express' import compression from 'compression' import cookieParser from 'cookie-parser' import bodyParser from ...

Executing multiple XMLHTTP requests within a Bootstrap Modal

My website features a Bootstrap5 Modal that loads data once the animation is complete. The issue arises when I close the modal by clicking the X in the top-right corner, the close button, or outside the modal. When I reopen the modal for another dataset (p ...

Activate Keyboard and Background in the Bootstrap Modal

I have set up my modal to disable the escape key and backdrop by default. $(modal).modal({ backdrop: "static", keyboard: false }); However, at a later time, I want to enable them again. $(modal).modal({ backdrop: true, keyboard: true }); The is ...

React Native has encountered an issue with an undefined object, resulting in an error

I have a JSON file with the following structure: { "main": { "first": { "id": "123", "name": "abc" }, "second": { "id": "321", "name": "bca" } } } Before making an AP ...

Modifying worldwide variables within an ajax request

After spending considerable time attempting to achieve the desired outcome, I am faced with a challenge. My goal is to append the object from the initial ajax call after the second ajax call. However, it appears that the for loop is altering the value to ...

Erase the Japanese content from the input

Having trouble removing Japanese characters from a text input box using jQuery. Here is the code I am trying: $('#email').keyup(function () { this.value = $(this).val().replace(/[^a-zA-Z0-9!.@#$%^&*()_-]/g,''); }); // try &l ...

Tips on incorporating toggle css classes on an element with a click event?

When working with Angular typescript instead of $scope, I am having trouble finding examples that don't involve $scope or JQuery. My goal is to create a clickable ellipsis that, when clicked, removes the overflow and text-overflow properties of a spec ...

Tips for altering the currently active tab in a separate window using a browser extension?

I'm currently working on developing a Firefox Extension and I'm facing a challenge. I'm trying to navigate to a specific browser tab in a different window. After reading through the Firefox Browser Extensions API documentation, I learned tha ...

Is it best to remove trailing/leading whitespace from user input before insertion into the database or during the input process?

There's something I've been pondering that pertains to MVC platforms, but could also be relevant to any web-based platform that deals with user input forms. When is the best time and method to eliminate leading/trailing whitespace from user inpu ...

I aim to showcase div elements based on the specific Props value

My goal is to showcase the 'selected' option when the values consist of { query: string; isSelect: boolean } and the isSelect property is set to true. Error: The 'isSelect' property is not recognized in the type '{ query: string; ...

Modifying various items depending on the variable's value

I'm attempting to adjust various variables depending on which button the user clicks. For instance, there are three buttons: <button id="button1" onclick="isClicked(this.id)">B1</button> <button id="button2" onclick="isClicked(this.id) ...

Guidelines for Optimizing NPM Packages: Maximizing Efficiency and Providing Multiple Import Routes

I have recently developed an NPM package that utilizes Webpack and Babel for transpiling and bundling. Within my package.json, I have specified the main file as "main": "build/index.js". Additionally, in my Webpack configuration, the entry point is set to ...

The message appearing on my screen reads: "Attempting to read properties of an undefined value, specifically 'map'."

I am attempting to create a map of my various people, but I keep encountering an error with the title. I'm having trouble understanding where the issue lies, here is my code snippet: export const Testt = ({ childs}) => { console.log(childs) ...

Obtain navigation instructions from current location to destination when marker is selected

I have successfully generated a map with markers loaded from an XML file and determined my current location. However, I am facing trouble in getting directions from my location to a marker when clicked. The code I have seems to be working fine for everyt ...

Tips for updating the text of an HTML element without deleting its children using JavaScript

Currently, I am implementing a code snippet to translate elements that contain the data-i18next attribute: const elementsToTranslate = document.querySelectorAll('[data-i18next]'); for (let i = 0; i < elementsToTranslate.length; i++) { ele ...

What is the best way to use AJAX to load a PHP file as a part

I'm exploring different methods for making an AJAX call with an included file. Let's create a simple working example. Initially, I have my main index.php file which contains the following content. In this file, I aim to access all the data retur ...

Eliminate items/attributes that include a certain term

Is there a way in Node.js to remove all fields or objects from a JSON file that have a specific word (e.g. "test") as their name, and then return the modified JSON file? Take a look at an example of the JSON file: { "name": "name1", "version": "0 ...

Change the data-theme using jQuery Mobile once the page has finished loading

I am facing an issue with my buttons in a control group that represent on/off functionality. Every time I click on one of the buttons to switch their themes, the theme reverts back once I move the mouse away from the button. How can I make sure that the ...