lundi 31 juillet 2023

Accessing Ember.js services from browser extension in versions ≥4.0

In Ember 4.0 and up, access to the Ember global object is deprecated. We have a browser plugin for internal debugging/support purposes that would gather some variables from an ember service using this global object and generate a text report that first-line support personel could use when investigating an issue.

Below is a part of the report generator script for Ember 3.28. This will normally be injected by the extension using chrome.scripting.executeScript with world 'MAIN', but pasting in the console will have the same effect for reproduction purposes. In Ember 4.0 and up, this will throw a TypeError since window.Ember is undefined.

var namespace = window.Ember.Namespace.NAMESPACES.find(ns => ns.name === 'acmecorp');
var sessionService = namespace.__container__.lookup('service:session');
var applicationAdapter = namespace.__container__.lookup('adapter:application');
var user = sessionService.get('user');
var userId = sessionService.get('user.id');
var userType = sessionService.get('user.type');
var userTypePath = applicationAdapter.pathForType(userType ?? 'user');

Following our upgrade to Ember 4.0 and up, is there any way to access this information from a browser extension?




mardi 25 juillet 2023

Ember 5.1 with ember-cli-stencil and third party components not working

I'm stuck. Perhaps someone is able to point me in the right direction to solve my problem.

I am trying to use third party stencil components in my ember app. Build is ok, but when i visit the app, this error is thrown:

Uncaught TypeError: (0 , _loader.applyPolyfills) is not a function
    at Module.callback (auto-import-stencil-collections.js:10:1)

I created a new and empty Ember-App, so nothing else is in it. I am using:

My package.json:

{
  "name": "desi-test-app",
  "version": "0.0.0",
  "private": true,
  "description": "Small description for desi-test-app goes here",
  "repository": "",
  "license": "MIT",
  "author": "",
  "directories": {
    "doc": "doc",
    "test": "tests"
  },
  "scripts": {
    "build": "ember build --environment=production",
    "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
    "lint:css": "stylelint \"**/*.css\"",
    "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
    "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
    "lint:hbs": "ember-template-lint .",
    "lint:hbs:fix": "ember-template-lint . --fix",
    "lint:js": "eslint . --cache",
    "lint:js:fix": "eslint . --fix",
    "start": "ember serve",
    "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
    "test:ember": "ember test"
  },
  "devDependencies": {
    "@babel/eslint-parser": "^7.22.5",
    "@babel/plugin-proposal-decorators": "^7.22.5",
    "@ember/optional-features": "^2.0.0",
    "@ember/string": "^3.1.1",
    "@ember/test-helpers": "^3.1.0",
    "@glimmer/component": "^1.1.2",
    "@glimmer/tracking": "^1.1.2",
    "@my-secret-thirdparty-components": "^2.7.0",
    "broccoli-asset-rev": "^3.0.0",
    "concurrently": "^8.2.0",
    "ember-auto-import": "^2.6.3",
    "ember-cli": "^5.1.0",
    "ember-cli-app-version": "^6.0.1",
    "ember-cli-babel": "^7.26.11",
    "ember-cli-clean-css": "^2.0.0",
    "ember-cli-dependency-checker": "^3.3.2",
    "ember-cli-htmlbars": "^6.2.0",
    "ember-cli-inject-live-reload": "^2.1.0",
    "ember-cli-sri": "^2.1.1",
    "ember-cli-stencil": "^1.0.0",
    "ember-cli-terser": "^4.0.2",
    "ember-data": "~5.1.0",
    "ember-fetch": "^8.1.2",
    "ember-load-initializers": "^2.1.2",
    "ember-modifier": "^4.1.0",
    "ember-page-title": "^7.0.0",
    "ember-qunit": "^7.0.0",
    "ember-resolver": "^10.1.1",
    "ember-source": "~5.1.1",
    "ember-template-lint": "^5.11.0",
    "ember-welcome-page": "^7.0.2",
    "eslint": "^8.43.0",
    "eslint-config-prettier": "^8.8.0",
    "eslint-plugin-ember": "^11.9.0",
    "eslint-plugin-n": "^16.0.1",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-qunit": "^8.0.0",
    "loader.js": "^4.7.0",
    "prettier": "^2.8.8",
    "qunit": "^2.19.4",
    "qunit-dom": "^2.0.0",
    "stylelint": "^15.9.0",
    "stylelint-config-standard": "^33.0.0",
    "stylelint-prettier": "^3.0.0",
    "tracked-built-ins": "^3.1.1",
    "webpack": "^5.88.2"
  },
  "engines": {
    "node": "16.* || >= 18"
  },
  "ember": {
    "edition": "octane"
  }
}

My ember-cli-build:

'use strict';

const EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function (defaults) {
  const app = new EmberApp(defaults, {
    // Add options here
    autoImport: {
      alias: {
        '@my-secret-thirdparty-components/loader': '@my-secret-thirdparty-components/dist/cjs/index.cjs',
      },
    }
  });


  return app.toTree();
};



dimanche 23 juillet 2023

persisting data in local storage using ember.js

I have some feature flags listed on the config file,

module.exports = function(environment) {
  let ENV = {
      featureFlags: {
        featureA: true, 
        featureB: false, 
        featureC: false,
        featureD: true,
      }
    }
  };

  return ENV;
};

and all the feature flags are shown as a card in a page.

    <UI::Card as |card| >
    <card.header @heading="Feature Flags" />
    <card.body as |body|>
        <div class="justify-content-between card-section">
            <ul class="list-group list-group-flush">
                
                    <li class="list-group-item justify-content-between align-items-center">
                      <div class="card-row flexbox">
                        <div class="card-left">
                          
                        </div>
                        <div class="card-right">
                            <UI::Switch
                            @value=
                            class="flex-grow-1"
                            @onClick=
                            />
                        </div>
                      </div>
                    </li>   
                
            </ul>
        </div>
    </card.body>
</UI::Card>

I am working with ember-tracked-local-storage library to connect the flags to the local storage.

@trackedInLocalStorage({ defaultValue: config.featureFlags }) featureFlags;

In the card whenever a feature flag is toggled I want to save that particular feature flag as an object to the local storage. If another flag is toggles it will get added on that object. And those particular feature flags should be shown as the saved value in local storage, and the other feature flags should be shown from the config file. There are less documentation on ember-tracked-local-storage. So, I am kinda lost on how I should go forward with this implementation.




mercredi 19 juillet 2023

Ember.js computed property not waiting for asynchronous RSVP promise

I have an Ember.js component where I'm trying to use a computed property to determine its visibility based on the result of an asynchronous RSVP promise. However, it seems like the computed property is not waiting for the promise to resolve, resulting in the count object being undefined.

Here's an excerpt from my component code:

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import RSVP from 'rsvp';

export default Component.extend({
    classNames: ['count'],
    countService: service('count'),

    getCount: computed(function () {
        debugger;
        RSVP.all([
            this.get('countService').getCount()
        ]).then(([count]) => {
            return Number(count);
        });
    }),

    isVisible: computed('getCount', function () {
        debugger;
        let count = this.get('getCount');
        return count !== undefined && count > 0;
    }),
});

As you can see, the getCount computed property is calling a method getCount() on the countService injected service. This method returns a promise that resolves with the count value.

In the isVisible computed property, I'm trying to access the count value returned by the getCount computed property. However, when I log the value of count during debugging, it shows up as undefined, even though the promise should have resolved by that point.

I'm not sure why the computed property is not waiting for the promise to resolve before trying to access the value. Am I missing something in my implementation? Is there a better way to handle asynchronous dependencies in Ember.js computed properties?

Any help or insights would be greatly appreciated!




lundi 17 juillet 2023

Not able to trigger event of parent application on click of Child application plugged-in using IFrame

I have One application created using Ember JS. Inside that I have plugined another application using IFrame created using react JS. I wanted to hide dropdown rendered from parent application by clicking inside IFrame where Child Application is rendered.

I tried one way in which I added 'click' event on the Document of the child element and using CSS I am able to hide the dropdown but not able to switch the flag state of the dropdown component of the parent application. I have added code snippet of the useEffect which i have added inside the child appliaction where I am handling click event.

As in this way I am not able to maintain consistency for Flag states of parent application through which Dropdown component is conditionally rendered.

Can anyone provide a feasible solution to achieve this scenario so that I an manage ClickOutside event applied on the dropdown component of the parent application and can able to manage show and hide feature even on the click of Child application rendered inside Iframe.

useEffect(() => { document.body.addEventListener('click', (e) => { const popup = window.parent.document.querySelector('.popover.bs-popover-top.show');

        console.log(window.parent);

        if (popup) {
            popup.style.display = 'none';
        }
    });
});

}

Added this useEffect inside child Application so that to handle click event for Child application.




samedi 15 juillet 2023

How to add a multi-line text field to a Discourse Community signup page?

I would like to add a multi-line text field to my Discourse community’s signup page, but I am not sure how. Am I able to edit the source code to do this, add a file to the source code, or use a plugin that enables this? Either way, I am unsure how to accomplish this task.

I tried using the default settings first and was only presented with single-line text field, confirmation, drop-down, and multi-select field options.




jeudi 13 juillet 2023

Triggering live-reloading when developing an Ember addon

I'm developing a private Ember addon for my team that will contain a few branded UI components that we will reuse in a bunch of different apps, such as a <BrandHeader /> and <BrandFooter />.

Is there an standard for creating a development environment that allows you to get live-reloading previews of your components as you build them? If there's not a standard, does anyone have a workflow that you've created that works well for you?

I tried using the strategy of creating an addon with the ember-cli ember addon <name> and then npm linking to an Ember app where I call the components, but with that method I need to rebuild the ember app every time I make a change in to the addon.




vendredi 7 juillet 2023

How to implement dynamic filtering in Ember.js dropdowns?

I'm working on an Ember.js application and I need to implement two dropdowns where the options in the second dropdown are filtered based on the selected value in the first dropdown. I have the following requirements:

The second dropdown should fetch its options from an API endpoint. Whenever a value is selected in the first dropdown, the options in the second dropdown should be filtered accordingly. The filtering should happen dynamically without a page refresh. I have tried implementing this functionality, but I'm facing some issues. The second dropdown does not update its options when a new value is selected in the first dropdown. How can I achieve this dynamic filtering behavior?

Here's a simplified version of the code I currently have: I have a parent and then 2 dropdown components inside it. I send the slected value of first dropdown from parent to the second dropdown. But the issue is that the new data is not filtered based on the value of first dropdown (namely: this.SelectedBU (injected from parent)). The component is very complex, thus I am only posting the index.js and index.hbs for the second dropsown.

second_dropdown.hbs



<h3></h3>
<div data-test-product-select>
  
    
      <Inputs::BadgeDropdownList
        @items=
        @listIsOrdered=true
        @onItemClick=
        @selected=
        @placement=
        @isSaving=
        @renderOut=
        @icon=
        class="w-80 product-select-dropdown-list"
        ...attributes
      >
        <:item as |dd|>
          <dd.Action data-test-product-select-badge-dropdown-item>
            <Inputs::TeamSelect::Item
              @product=
              @selected=
            />
          </dd.Action>
        </:item>
      </Inputs::BadgeDropdownList>
    
      <X::DropdownList
        @items=
        @listIsOrdered=true
        @onItemClick=
        @selected=
        @placement=
        @isSaving=
        @renderOut=
        class="w-[300px] product-select-dropdown-list"
        ...attributes
      >
        <:anchor as |dd|>
          <dd.ToggleAction
            class="x-dropdown-list-toggle-select product-select-default-toggle hds-button hds-button--color-secondary hds-button--size-medium"
          >
            <FlightIcon @name= />

            <span
              class="product-select-selected-value
                "
            >
              
            </span>

            
              <span class="product-select-toggle-abbreviation">
                
              </span>
            

            <FlightIcon @name="caret" class="product-select-toggle-caret" />
          </dd.ToggleAction>
        </:anchor>
        <:item as |dd|>
          <dd.Action class="pr-5">
            <Inputs::TeamSelect::Item
              @product=
              @selected=
              @abbreviation=
            />
          </dd.Action>
        </:item>
      </X::DropdownList>
    
  
    <FlightIcon data-test-product-select-spinner @name="loading" />
  
    <div
      class="absolute top-0 left-0"
      
    ></div>
  
</div>

and

second_dropdown.ts

import { assert } from "@ember/debug";
import {action, computed} from "@ember/object";
import { inject as service } from "@ember/service";
import { Placement } from "@floating-ui/dom";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { task } from "ember-concurrency";
import FetchService from "hermes/services/fetch";
import getProductId from "hermes/utils/get-product-id";

interface InputsTeamSelectSignature {
  Element: HTMLDivElement;
  Args: {
    selectedBU: string | null;
    selected?: string;
    onChange: (value: string, attributes?: TeamArea) => void;
    formatIsBadge?: boolean;
    placement?: Placement;
    isSaving?: boolean;
    renderOut?: boolean;
  };
}

type TeamAreas = {
  [key: string]: TeamArea;
};

export type TeamArea = {
  abbreviation: string;
  perDocDataType: unknown;
  BU: string
};

export default class InputsTeamSelectComponent extends Component<InputsTeamSelectSignature> {
  @service("fetch") declare fetchSvc: FetchService;

  @tracked selected = this.args.selected;

  @tracked teams: TeamAreas | undefined = undefined;
  @tracked selectedBU: string | null = null;

  @computed('args.selectedBU')
  get ReFetchTeams() {
    this.selectedBU = this.args.selectedBU;
    return this.fetchteams.perform();
  }

  get icon(): string {
    let icon = "folder";
    if (this.selected && getProductId(this.selected)) {
      icon = getProductId(this.selected) as string;
    }
    return icon;
  }

  get selectedProductAbbreviation(): string | null {
    if (!this.selected) {
      return null;
    }
    const selectedProduct = this.teams?.[this.selected];
    assert("selected Team must exist", selectedProduct);
    return selectedProduct.abbreviation;
  }

  @action onChange(newValue: any, attributes?: TeamArea) {
    this.selected = newValue;
    this.args.onChange(newValue, attributes);
  }

  // @action onBUChange(){
  //   this.selectedBU = this.args.selectedBU;
  //   this.fetchteams.perform();
  // }

  // protected fetchProducts = task(async () => {
  //   try {
  //     let products = await this.fetchSvc
  //       .fetch("/api/v1/products")
  //       .then((resp) => resp?.json());
  //     this.products = products;
  //   } catch (err) {
  //     console.error(err);
  //     throw err;
  //   }
  // });
    protected fetchteams = task(async () => {
      try {
        // Filter the teams based on the selected business unit
        console.log("parent injected value is: ",this.args.selectedBU)
        let teams = await this.fetchSvc
            .fetch("/api/v1/teams")
            .then((resp) => resp?.json());

        // Filter the teams based on the selected business unit
        const filteredTeams: TeamAreas = {};

        for (const team in teams) {
          if (Object.prototype.hasOwnProperty.call(teams, team)) {
            const teamData: TeamArea | undefined = teams[team];
            if (teamData && teamData.BU  === this.args.selectedBU) {
              filteredTeams[team] = teamData;
            }
          }
        }
        console.log("the filtered teams are: ",filteredTeams);
        this.teams = filteredTeams;
          console.log(this.teams);
        } catch (err) {
          console.error(err);
          throw err;
        }
      });

}

declare module "@glint/environment-ember-loose/registry" {
  export default interface Registry {
    "Inputs::TeamSelect": typeof InputsTeamSelectComponent;
  }
}

Any, sort of guidance will be highly appreciated! Thanks!

I have tried using @traked, @did-update etc, but nothing seems to work, even if I remove the "did-insert" the dropdown disappears completely.




mercredi 5 juillet 2023

How to set a a unique id to the elements of a DOM in ember.js

To add more context, I would like to incorporate Product Fruits to our ember app. The service that we will use is the Tours of Product Fruits. When creating a tour, product fruits target the element of client app with using id of an element. Ember generates ID for every element of the DOM. For example, if you have a button, the ID can be id="priority-navbar-item-ember110". However, if you refresh the page, the ID changes.

Question: Besides using data-test selectors, how can I have a unique id and will not change after refreshing the page or revisiting the app?




mardi 4 juillet 2023

How to properly restore a session on reload using ember simple auth custom authenticator's restore() function?

I have followed this YouTube tutorial on getting started with ember simple auth: https://www.youtube.com/watch?v=bSWN4_EbTPI&t=908s

I have everything working properly except for when refreshing the page. The custom authenticator I am using is supposed to use its restore() method on a refresh and grab the user token from the cookieStore, however when my code does this it gets the passed parameter, data which is an empty object upon console logging it. It's like it doesn't know to look in the cookieStore to fill out data with. I have tried messing with some settings in config/environment.js but can't find anywhere online with solid documentation on how to configure simple-auth. I am using v5.0.0

I can see the cookie being created when the app is loaded and filled out when I log in. For reference, here is what it looks like after logging in:

ember-simple-auth-session=%7B%22authenticated%22%3A%7B%22authenticator%22%3A%22authenticator%3Atoken%22%7D%2C%22token%22%3A%22secret%20token%22%7D

Here it is decoded:

{"authenticated":{"authenticator":"authenticator:token"},"token":"secret token"}

Here is my custom authenticator - app/authenticators/token.js

import Base from 'ember-simple-auth/authenticators/base';
import { inject as service } from '@ember/service';

export default class CustomAuthenticator extends Base {
  @service router;
  @service session;

  restore(data) {
    console.log('Restore');
    console.log(data);
    const { token } = data;

    if(token) {
      return data;
    }
    else {
      throw 'no valid session data';
    }
  }

  async authenticate(userData) {
    let response = await fetch('http://localhost:5000/users/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData),
    });

    if(response.ok) {
      response.json().then((data) => {
        const { token } = data;
        this.session.set('data.token',token);

        console.log(this.session);
        this.router.transitionTo('user', data.id);
      });
    } 
    else {
      let error = await response.text();
      throw new Error(error);
    }
  }

  invalidate() {
    return new Promise((resolve, reject) => {
      resolve();
    });
  }
}

And this is all I have in session-stores/application.js

import CookieStore from 'ember-simple-auth/session-stores/cookie';

export default class ApplicationSessionStore extends CookieStore {}

I'm trying to just extract the token field from the cookie which by my understanding should be getting passed into restore(data) but when I do console.log(data) I get {} which is just an empty object. If I try console.log(data.token) I get undefined. I've been stuck on this for a really long time now and I am sure it's something silly on my end. Any help is appreciated.




set variable in hbs template using ember.js

I have an array of data and I need to iterate over it in my hbs template:


  <th>
    
  </th>



  <th>
    
  </th>

How do I declare a variable and increase it to replace the hardcoded value?

I tried to wrap with but that did not worked:


  
    <th>
      
    </th>
  

I'm using ember 2.13.




how to update controller after RSVP.allSettled

After all rsvp promises are done in ember controller, how can I save all error status in controller and update DOM? I got undefined 'this', controller no longer accessible.

export default class someController extends Controller {
  @tracked errorlist = [];

  @action someAction {
    RSVP.allSettled(prommises).then(function(retarray){
      retarray.forEach((retstate) => {
        if (retstate.state == 'rejected') {
          this.errorlist.push(stateRet.reason);   
          //'this' undefined, how to update errlist?
        }
      });
    })
  }
}