Home > Back-end >  I have an issue with handlebars and a counter function in JavaScript
I have an issue with handlebars and a counter function in JavaScript

Time:02-05

i'm trying create a counter function on object compilated with handlebars.js. I'm trying to make a project site for a moving company, and I'm trying to create a cost estimation system in it, depending on what things and how many they have to move in their new house

handlebars compilated divs

enter image description here

problem

enter image description here

So I created objects in js which contain things to move such as fridge, bed, table etc... their are compilated right into HTML with handlebars template.

I want to be able to increase and decrease the numbers of things separately, so I was able to create one function that does this, but the problem is that it works only on the first compilated object and all other objects are not affected with this code, i don't even know if it is possible to do such thing with handlebars template and vanilla js, I know I can do this for each objects individually but there will be way too much duplicated codes in HTML and JS files..

here is the handlebars template on HTML file:

<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
    <script id="templateHB" type="text/x-handlebars-template">

        {{#each lesMeublesSalon}}
        <div ><img src="{{this.image}}">
            <p>{{this.element}}</p>
            <div >
                <button id="increase-{{this.index}}"> </button>
                <p>{{this.quantity}}</p>
                <button id="decrease-{{this.index}}">-</button>
            </div>
        </div>
        {{/each}}

here is the js file code:

const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);

const contextSalon = {
    lesMeublesSalon: [
        {
            image: 'images/canape.png',
            element: 'Canapé',
            quantity: 0,
            index: 0
        },
        {
            image: 'images/canape.png',
            element: 'Lit',
            quantity: 0,
            index: 0
        }
    ]
};

let compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;

// here start the function

let quantity = contextSalon.lesMeublesSalon[0].quantity;
let addOneBtn = document.getElementById("increase-0");
let removeOneBtn = document.getElementById("decrease-0");

function updateQuantity(quantity) {
  contextSalon.lesMeublesSalon[0].quantity = quantity;
  compiledHtmlSalon = template(contextSalon);
  injectionObjetSalon.innerHTML = compiledHtmlSalon;

  addOneBtn = document.getElementById("increase-0");
  removeOneBtn = document.getElementById("decrease-0");
  
  addOneBtn.addEventListener("click", function() {
    updateQuantity(quantity   1);
  });

  removeOneBtn.addEventListener("click", function() {
    updateQuantity(quantity - 1);
  });
}

updateQuantity(0);

if the thing i'm trying to do is impossible with js and handlebars.js, what other tech can you suggest me? any js framework such as node.js and express.js?

I just created function to increase and decrease number based on ID of the template inside html file with handlebars, I was expecting it to work with all others compilated objects to work the same way.

CodePudding user response:

It is possible that a rendering framework like React or Vue could be used for this, but they come with learning curves. The conditions as you have outlined them are fairly simple and so I think you can get by with Handlebars.

But we will need to make some modifications to your code.

The first issue I see is that you have index: 0 set on all objects in your lesMeublesSalon array. You could correct those to be sequential (0, 1...), but I think a better option would be to use Handlebars' built-in @index variable to set the index value for each item.

The next problem I would address is that you are trying to re-render the Handlebars template with each click of the increment/decrement button. The problem I see with this is that it is that you will have to re-attach your event listeners each time you re-render the HTML because the DOM will have new button elements that need to be listened to.

I think a better approach would be to render the initial HTML with Handlebars, but then to use JavaScript to directly update the DOM when your counts change.

Here is how I would implement this:

{{#each lesMeublesSalon}}
    <div ><img src="{{this.image}}">
      <p>{{this.element}}</p>
      <div >
        <button data-increase="{{@index}}"> </button>
        <p data-quantity="{{@index}}">{{this.quantity}}</p>
        <button data-decrease="{{@index}}">-</button>
    </div>
  </div>
{{/each}}
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);

const contextSalon = {
    lesMeublesSalon: [
        {
            image: 'images/canape.png',
            element: 'Canapé',
            quantity: 0,
            index: 0
        },
        {
            image: 'images/canape.png',
            element: 'Lit',
            quantity: 0,
            index: 0
        }
    ]
};

// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;

// Note the use of data-* attributes in the template.
// This allows us to query for ALL increment/decrement buttons
// and to attach a listener to each.
// Getting them by ID allowed us to get only one increment
// and one decrement button.
const addOneBtns = document.querySelectorAll("[data-increase]");
const removeOneBtns = document.querySelectorAll("[data-decrease]");
const quantityDisplays = document.querySelectorAll("[data-quantity]");

// This is the function that will directly manipulate the displayed
// quantity value for each item.
// It relies on the indexes of the elements to match the indexes in
// our lesMeublesSalon so that the correct quantity will be set.
function renderQuantities () {
  quantityDisplays.forEach((quantityDisplay, index) => {
    const quantity = contextSalon.lesMeublesSalon[index].quantity;
    quantityDisplay.textContent = String(quantity);
  }) 
};

// We loop through EACH increment button and attach a click listener.
addOneBtns.forEach(addOneBtn => {
  addOneBtn.addEventListener('click', function (event) {
    // We get the index from the `[data-increase]` attribute.
    const index = Number(event.target.dataset.increase);
    // We use that index to increment the quantity on the
    // corresponding item in our array.
    contextSalon.lesMeublesSalon[index].quantity  = 1;
    // We re-render the quantities because we know there is a change.
    renderQuantities();
  });
});

// This is basically the same as above, except for decrementing.
removeOneBtns.forEach(removeOneBtn => {
  removeOneBtn.addEventListener('click', function (event) {
    const index = Number(event.target.dataset.decrease);
    // We use Math.max() so the quantity can't become less than zero.
    contextSalon.lesMeublesSalon[index].quantity = Math.max(contextSalon.lesMeublesSalon[index].quantity - 1, 0);
    renderQuantities();
  });
});

I have created a Codepen for reference.

CodePudding user response:

I've tesed your code and it works well , thanks you! however I forgot to mention that I have 4 categories of objects , as you can see on the picture , I got (Salon, Chambre, Cuisine, Bain) , how can we make so that it works on all of these categories ? So it works on the "Salon" categories but not in the others, here you have all others object :

const contextSalon = {
    lesMeublesSalon: [
        {
            image: 'images/canape.png',
            element: 'Canapé',
            quantity: 0,
            index: 0
        },
        {
            image: 'images/canape.png',
            element: 'Lit',
            quantity: 0,
            index: 0
        }
    ]
};

// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;

const contextChambre = {
    lesMeublesChambre: [
        {
            image: 'images/bed.svg.png',
            element: 'Lit double',
            quantity: 0,
            index: 0
        }
    ]
}

const compiledHtmlChambre = template(contextChambre);
const injectionObjetChambre = document.getElementById('meuble-chambre');
injectionObjetChambre.innerHTML = compiledHtmlChambre;

const contextCuisine = {
    lesMeublesCuisine: [
        {
            image: 'images/frigo.svg',
            element: 'Frigo',
            quantity: 0,
            index: 0
        }
    ]
}

const compiledHtmlCuisine = template(contextCuisine);
const injectionObjetCuisine = document.getElementById('meuble-cuisine');
injectionObjetCuisine.innerHTML = compiledHtmlCuisine;

const contextBain = {
    lesMeublesBain: [
        {
            image: 'images/machine-a-laver.svg',
            element: 'Machine à laver',
            quantity: 0,
            index: 0
        }
    ]
}

const compiledHtmlBain = template(contextBain);
const injectionObjetBain = document.getElementById('meuble-bain');
injectionObjetBain.innerHTML = compiledHtmlBain;
{{#each lesMeublesSalon}}
        <div ><img src="{{this.image}}">
          <p>{{this.element}}</p>
            <div >
                <button data-increase="{{@index}}"> </button>
                <p data-quantity="{{@index}}">{{this.quantity}}</p>
                <button data-decrease="{{@index}}">-</button>
            </div>
        </div>
        {{/each}}

        {{#each lesMeublesChambre}}
        <div ><img src="{{this.image}}">
            <p>{{this.element}}</p>
            <div >
                <button> </button>
                <p>{{this.quantity}}</p>
                <button>-</button>
            </div>
        </div>
        {{/each}}
        
        {{#each lesMeublesCuisine}}
        <div ><img src="{{this.image}}">
            <p>{{this.element}}</p>
            <div >
                <button> </button>
                <p>{{this.quantity}}</p>
                <button>-</button>
            </div>
        </div>
        {{/each}}

        {{#each lesMeublesBain}}
        <div ><img src="{{this.image}}">
            <p>{{this.element}}</p>
            <div >
                <button> </button>
                <p>{{this.quantity}}</p>
                <button>-</button>
            </div>
        </div>
        {{/each}}

  • Related