Exploring the world of Ember through engaging click events

Exploring EmberJS and in the process of transitioning an existing website to this framework, I encountered a challenge with a Bootstrap-based dropdown. While troubleshooting this issue, I found that it deepened my understanding of Ember's concepts, although I am left with some lingering questions.

To implement the dropdown (alongside other elements), I utilized the ember-bootstrap module. Below is the code snippet for the dropdown functionality:

{{#bs-dropdown as |dd|}}
  {{#dd.button}}
    Sort by
  {{/dd.button}}

  {{#dd.menu as |ddm|}}
    {{#ddm.item}}{{#ddm.link-to "index"}}Price low to high{{/ddm.link-to}}{{/ddm.item}}
    {{#ddm.item}}{{#ddm.link-to "index"}}Price high to low{{/ddm.link-to}}{{/ddm.item}}
  {{/dd.menu}}
{{/bs-dropdown}}

I now wish to trigger specific JavaScript code when a user clicks on one of the dropdown items. Referring to the module's documentation, I located and modified the code within the menu item component as shown below:

export default Component.extend({
  layout,
  classNameBindings: ['containerClass'],

  /* ... */

  actions: {
    // My addition
    sortByPrice(param){
      alert("sorting");
    },
    // End of the addition

    toggleDropdown() {
      if (this.get('isOpen')) {
        this.send('closeDropdown');
      } else {
        this.send('openDropdown');
      }
    },
  },
});

Subsequently, I made updates to the hbs file as follows:

{{#dd.menu as |ddm|}}
   {{#ddm.item action "sortByPrice" low_to_high}}

    {{#ddm.link-to "index"  action "sortByPrice" low_to_high}}
      Prix croissant
    {{/ddm.link-to}}

  {{/ddm.item}}
{{/dd.menu}}

However, despite these changes, the desired outcome was not achieved. This led me to include the *action* attribute to the *link-to* element and define the corresponding action in its component file as well.

import LinkComponent from '@ember/routing/link-component';

export default LinkComponent.extend({
  actions: {
    sortByPrice(param){
        alert("sorting");
      console.log("sorting");
      },
  },
});

Upon further investigation, I realized that the *link-to* component extending the LinkComponent does not inherently handle click events, as elaborated in this thread.

Feeling somewhat frustrated, I resorted to a less elegant workaround that served the purpose:

{{#bs-dropdown id="sort" as |dd|}}
  {{#dd.button}}
    Sort by
  {{/dd.button}}

  {{#dd.menu as |ddm|}}
    {{#ddm.item action "sortByPrice" low_to_high}}
      <a
        class="dropdown-item"
        onclick="sortByPrice('low_to_high'); return false;"
        href="#"
      >
        Price low to high
      </a>
    {{/ddm.item}}
  {{/dd.menu}}
{{/bs-dropdown}}

Here are my inquiries:

  1. Why did defining actions in both the Component file and the hbs file not yield the expected result?
  2. Why doesn't the LinkComponent intrinsically manage click events? While links traditionally redirect users to a new page, DOM events are still triggered. Does Ember purposely overlook this behavior to prevent developers from handling it? Understanding the rationale behind this decision would be beneficial.
  3. Is there a more optimal solution than the one I've implemented?

Thank you.

Answer №1

Well done on delving into EmberJS and posing an insightful question!

Mistakes to Correct

  1. Avoid altering code inside the node_modules/ and bower_components/ directories. If necessary, use an initializer for any patches. However, your situation does not call for patching.

  2. You tried defining an action in the menu item component, but then applied it in a parent template. The action should be defined in the parent's template component/controller instead.

  3. The way you invoked this is incorrect:

    {{#ddm.link-to "index"  action "sortByPrice" low_to_high}}
    

    Issues with this include:

    1. The ddm.link-to component is meant to generate a link to another route and does not appear to support passing an action into it.

    2. You're providing positional params to the component without proper syntax. If the component could accept an action, the correct syntax would be:

      {{#ddm.link-to "index" argName=(action "sortByPrice" low_to_high)}}
      

      In this context, "index" is a position param while argName is a named param.

    3. low_to_high without quotes refers to a property within the current scope. You likely intended it as a string: "low_to_high".

  4. Avoid using JavaScript directly in templates. In Ember, this approach should be avoided:

    <a onclick="sortByPrice('low_to_high'); return false;">
    

    Instead, pass an action (defined locally in a component or controller):

    <a onclick={{action 'sortByPrice' 'low_to_high'}}>
    

    The property name of onclick is optional. An action defined without a property implies onclick. Provide the property name only if attaching the action to a different event like so:

    <a {{action 'sortByPrice' 'low_to_high'}}>
    

    To ensure proper styling in browsers, include a href attribute for links. No need to pass a value like '#', as Ember prevents URL overwriting, allowing an empty href.

    Correct usage example:

    <a href {{action 'sortByPrice' 'low_to_high'}}>
    

Answers to Your Queries

  1. Why didn't defining actions in both the Component file and the HBS file alter the result?

This occurred because you defined them in different scopes.

If an action is defined in app/components/foo-bar.js, apply it in

app/templates/components/foo-bar.hbs
.

Similarly, an action from app/controllers/index.js should be used in app/templates/index.hbs.

  1. Why doesn't the LinkComponent natively handle click events? Is there a rationale behind letting the routing system manage transitions instead of developers?

In PWAs, actual page redirects are avoided to prevent app reloads.

The LinkComponent instructs Ember's routing system to initiate a transition instead of conventional clicks. Proper routes must be set up for this mechanism to function effectively.

If your goal is adjusting a variable rather than transitioning, the LinkComponent may not suit your needs. Unless coupling the sort order property to a query parameter allows you to modify the ordering through a new query param transition.

  1. Are there better approaches than my solution?

Explore a simpler method utilizing ember-bootstrap's dropdown feature outlined below.


Functional Demo

Controller:

export default Ember.Controller.extend({
  isSortAccending: true,

  actions: {
    changeSortDirection (isSortingAscending) {
      this.set('isSortAccending', isSortingAscending);
    }
  }
});

Template:

<p>
  Current sort order:
  {{if isSortAccending "ascending" "descending"}}
</p>

{{#bs-dropdown as |dd|}}
  {{#dd.button}}
    Sort by
  {{/dd.button}}

  {{#dd.menu as |ddm|}}
    {{#ddm.item}}
      <a href {{action "changeSortDirection" true}}>
        Price high to low
      </a>
    {{/ddm.item}}

    {{#ddm.item}}
      <a href {{action "changeSortDirection" false}}>
        Price high to low
      </a>
    {{/ddm.item}}
  {{/dd.menu}}
{{/bs-dropdown}}

View the live demo.

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

Deletion of input is not permitted

Currently, I have a telephone input field on my form that only allows numbers. I have implemented a simple JavaScript code to enforce this validation. However, the issue I am facing now is that the input box cannot be deleted. <form id="aylikal" action ...

The first Ajax call was successful, but the second Ajax call was unexpectedly sent twice

After making an AJAX call to post the value of a textarea to the database, I have noticed a strange issue. The first post always goes through correctly. However, when attempting to post another entry, the AJAX call seems to be executed twice. Subsequently, ...

How can you hide all siblings following a button-wrapper and then bring them back into view by clicking on that same button?

On my webpage, I have implemented two buttons - "read more" and "read less", using a Page Builder in WordPress. The goal is to hide all elements on the page after the "Read More" button upon loading. When the button is clicked, the "Read More" button shoul ...

What is the best way to retrieve search queries from suggestqueries.google.com through a fetch request, or is there another method to obtain Google suggestions?

https://i.sstatic.net/1rTiC.png I have recently created a Vue.JS new tab page and I'm looking to integrate Google suggestions into the search bar. After some research, I stumbled upon an API that seems like it could help me achieve this. However, whe ...

Automated Recommendation Selector

I'm searching for a JavaScript library that can provide the following functionalities: An auto-suggest dropdown list that uses Ajax to retrieve results from a database. Restricting the user to only select values provided by the Ajax call. Abilit ...

Error: You forgot to close the parenthesis after the argument list / there are duplicate items

I have already tried to review a similar question asked before by checking out this post, but unfortunately, I still couldn't find a solution for my problem. Even after attempting to add a backslash (\) before the quotation mark ("), specificall ...

Organize the table data based on time

My website specializes in offering cell phone rental services. Users can visit the site to view the available devices that we have. I designed the display of these devices using a table format and components from "@mui/material". One of the columns in thi ...

Sending dynamic internationalization resources from Node.js to Jade templates

Looking for a way to show a custom error page to the user in case of an error, but it needs to be internationalized (i18n-ed). Solution: My idea is to validate in node -> if not accepted -> res.render('error', {message: errorMessageNameToo ...

Unexpected behavior is being observed after incorporating the 'node' environment into my .eslintrc file

My project is currently using eslint v1.8.0 to analyze the test.js file: require('fs'); var a = 1; Initially, my .eslintrc file is empty: { } After running eslint test.js, I get the following errors: 1:1 error "require" is not defined ...

Avoiding remote submission in Grails forms: Best practices

<g:formRemote name="form1" update="homeBody" url="[controller: 'xxx', action:'aaa']"> <Button type="submit">Save</Button> </g:formRemote> I am facing a scenario where I need to place a text field o ...

Obtain the correct form ID format that is dynamically loaded following the execution of an AJAX function

When adding dynamic HTML elements, it is recommended to use delegation binding. However, I am encountering an issue with getting the proper "id" of the form element. $(document).on("submit", "form#add_education", function(e){ e.preventDefault(); ...

Using MEAN.JS to Define Query Parameters for Mongo from the Client Controller

I am currently developing an application using the MEAN stack - MongoDB, Angular, Express, and Node.js. To kickstart my project, I utilized the MEAN.JS generator to set up the initial structure of my application. The articles module within my application ...

Utilizing AngularJS: Executing directives manually

As a newcomer to AngularJS, I am facing a challenge that requires creating a 3-step workflow: The initial step involves calling a web service that provides a list of strings like ["apple", "banana", "orange"]. Upon receiving this response, I must encap ...

What is the best way to style the header of a table when scrolling in CSS?

Currently, I am facing an issue with applying the top CSS property to the thead element of a table while scrolling. I have attempted various methods but have been unsuccessful in achieving the desired outcome. Initially, I used the scroll event, however, ...

Tips for displaying HTML elements using JSON data in React?

My goal is to dynamically render HTML elements based on JSON data using a React class that takes objects and generates a list of divs. The values inside the divs correspond to the first value in each object within the JSON. Here's an example of the J ...

Utilize Google Maps API to showcase draggable marker Latitude and Longitude in distinct TextFields

I am currently utilizing the Google Maps example for Draggable markers. My objective is to showcase the latitude and longitude values within separate TextFields, where the values dynamically change as I drag the marker. Once the user stops dragging the mar ...

Is there a way to trigger this pop-up after reaching a certain percentage of the page while scrolling?

I've been working on a WordPress site that features an "article box" which suggests another article to users after they scroll to a certain point on the page. However, the issue is that this point is not relative but absolute. This means that the box ...

Looking for the optimal method to display numerous lines of text in HTML, one by one, at intervals of 60 seconds?

I'm working on a display page for my website. The main text in the center needs to change every 60 seconds. I have over 150 individual lines of text that I want to cycle through on the page. What would be the most efficient way to load all these te ...

What is the most effective way to toggle the visibility of div elements using jQuery?

I am currently working on a small project centered around interviewing people using short GIF animations. I want the viewers to have 10 seconds to watch the GIF, but I've noticed that the timer is not accurate in my code. After some research, I came ...

Unable to post form attribute value after submission

I have created a form that looks like this: <form method="POST" action="create.php" data-id="0" class="postForm"> <input type="hidden" id="#formId" value="1"> <textarea class="formBodyText"></textarea> <button typ ...