mercredi 26 janvier 2022

Ember Data - Lazy loading child data of a model without re-creating previously created objects again

I'm new to ember-data. I'm trying to load comment list from a API using multiple API calls. The comment list feature works like below,

  • A comment object can have a parent comment or children comments (replies)
  • All comments (children & parent) from different comment threads are list down in a single comment list using a 1st API call.
  • If user click on specific comment from above list it will prompt respective comment thread. Respective parent or children comments loading using 2nd API call

The comment model is implemented as below,

export default CommentModel.extend( {
  parent: computed(function() {
    return get(this, 'store').queryRecord('comment', {
      _overrideURL: `comments/${get(this, 'id')}/parent`,
    });
  }),

  children: computed(function() {
    return get(this, 'store').query('comment', {
      _overrideURL: `comments/${get(this, 'id')}/children`,
    });
  }),
...

As this implementation, if user click on child comment (reply) from the comment list, the 2nd API call with load the respective parent comment and parent comment will load its children comments again. That behaviour cause reload the comment list component in UI.

Is there any other way in ember-data to lazy load relationship without creating already existing objects?




Ember embedded model with belongsTo

I have a data model that looks like this on the server:

recipes : {
  <recipe ID> : {
    ingredients : [
      {
        count: 2,
        ingredient: <ingredient ID>
      },
      {
        count: 4,
        ingredient: <ingredient ID>
      }
    ]
  }
},

ingredients : {
  <Ingredient ID> : {...},
  <Ingredient ID> : {...}
}

I want to be able to use the ingredient ID in the recipe as a full Ingredient model object, but I can't figure out the best way of doing that. My server doesn't support Includes. Do I intercept this in a custom serializer and then use store.findRecord? Or is there a more efficient way of setting up my model to support this? I haven't been able to find a way to have the ingredients list as a model because there's no associated ID with an ingredients list, so I've been treating it as a read-only object using @attr on the recipe model.




mardi 25 janvier 2022

Calling the becomeDirty action no longer seems to set hasDirtyAttributes

I have updated to the latest 3.x version of Ember and have got ember-data version 3.28.7. In older versions of Ember (3.20) calling the becomeDirty action on a model set hasDirtyAttributes to true on that model, but in 3.28.7 of ember-data observers that monitor hasDirtyAttributes on a list of models are triggered, but the actual value of hasDirtyAttributes in the model seems to be false.

Has anyone else seen this? I cannot find any links to people having this same issue?

I should say I am using classic Ember objects: Model.extend({})

I get the following results with the following commands:

model.hasDirtyAttributes // false
model._internalModel.currentState.isDirty // true

Looking in @ember-data, it looks like when getting the @tagged currentState it returns this.___recordState instead of the this._internalModel.currentState because CUSTOM_MODEL_CLASS is defined? (Whatever that means.) Anyway the result of the state object thing seems to be false:

model.___recordState.isDirty // false

Why isn't that set to true? Investigating more...

It looks like a lot of things called canary-features have been enabled. In the old version of @ember-data a configuration file looked like:

export default {
  SAMPLE_FEATURE_FLAG: null,
  RECORD_DATA_ERRORS: null,
  RECORD_DATA_STATE: null,
  IDENTIFIERS: true,
  REQUEST_SERVICE: null,
  CUSTOM_MODEL_CLASS: null,
  FULL_LINKS_ON_RELATIONSHIPS: true,
};

but now it looks like:

export default {
  SAMPLE_FEATURE_FLAG: null,
  RECORD_DATA_ERRORS: true,
  RECORD_DATA_STATE: true,
  IDENTIFIERS: true,
  REQUEST_SERVICE: true,
  CUSTOM_MODEL_CLASS: true,
  FULL_LINKS_ON_RELATIONSHIPS: true,
  RECORD_ARRAY_MANAGER_IDENTIFIERS: true,
  REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT: true,
};

And if I hardcode CUSTOM_MODEL_CLASS to be false things seem to be working again. So now I need to figure out the best way of turning this stuff off without editing node_modules files.

Anyone know if just setting CUSTOM_MODEL_CLASS to false is OK or should I turn off other things?




lundi 24 janvier 2022

Configure Ember CLI Mirage to support liverload for mirage configuration with Embroider

I have an Ember.js application, which uses Embroider and Ember CLI Mirage. Sadly live-reload is not working for Mirage configuration changes. A change to Mirage configuration does not trigger a rebuild. It seems as if mirage/ folder is not watched.

I reproduced this issue in a newly created Ember application using Ember CLI v4.1 with the --embroider flag. So it is not related to any special configuration of my application.

Do I need to configure Embroider somehow to watch mirage/ folder?




dimanche 23 janvier 2022

Ember tracked list not reflected in Each

I'm using the Each helper to loop over a tracked list of items. No matter what I do, though, the component prints out the list as empty and won't re-compute. I tried wrapping my list in an Ember Array to see if that'd help with the tracking, but it didn't. When I use the logger helper in my .hbs, the items property prints out as a fulfilled promise of the correct number of array elements. If I switch getItems to use .then() instead of await, it prints as undefined.

I know sometimes you need to "reset" arrays so that they notify subscribers properly... is that necessary here even though the sub-objects are tracked? How can I get this array to be truly tracked?

**item-table.js
import { A } from '@ember/array';
export default class ItemTableComponent extends Component
{
  @tracked items = this.getItems();

  async getItems()
  {
    let allItems = await this.store.findAll('item');
    const ingredients = this.inventory.currentInventory?.ingredients ?? {}; // Inventory service
    let tableItems = <array of new ItemCount objects>
    return A(tableItems);
  }
}

class ItemCount
{
    @tracked item;
    @tracked count;

    constructor(item, count)
    {
        this.item = item;
        this.count = count;
    }
}

**item-table.hbs





mardi 18 janvier 2022

How to locally filter a model and automatically respond to changes to model data?

I have a model that's fetched with store.find. It's a list of "tasks" and the user can filter these tasks by status and other properties. How do we keep the UI/component updated when the filter is applied and when properties are changed? Eg. Task list is filtered only to show completed tasks and the user changes the status of one the tasks from completed to in-progress. The task has to be automatically removed from the UI/component.




lundi 17 janvier 2022

Ember CLI (4.1.0) github output

I am new emberjs and trying to track my project via github. I currently have a directory tracking my changes /workspace, which should contain a subdirectory /workspace/currentproj. The top level directory is being tracked by my github however the lower level changes are not. When I check the status on currentproj the files are being track but not by my remote repo. In the ember-cli-update.json file I see a reference to a "outputRepo": "https://github.com/ember-cli/ember-new-output". I am wondering if that is where I need to add my repo information. If so its not working. How do track changes using my current repo?

I am using a mac air m1 with atom editor.




Ember how to change Ember boostrap Tooltip background color

Currently I'm using Ember Bootstrap Tooltip component, I read the doc, I didn't found anything about custom Background-color. It will be helpful if someone show me how to do it.




vendredi 14 janvier 2022

What resolve is doing in this situation?

New to ember, I am having trouble making sense of this code:

On the front-end, is a simple form, that is used to submit user text input as feedback

      <Form @onSubmit=>

          <TextField
            @multiline=3
            @placeholder="Additional comments"
            @autoFocus=
            @value=
            @disabled=
            @onChange=
          />

          <ButtonGroup>
            <Button
              @text="Send feedback"
              @loading=
              @onClick=
            />

            <Button
              @text="Cancel"
              @plain=true
              @onClick=
            />
          </ButtonGroup>
      </Form>

Backing it up, on the .js Component I have:

export default class FeedbackFormComponent extends Component {
  /**
   * Callback when the feedback form is submitted
   * Provides `feedbackText` and `wasContentUseful`
   * as arguments.
   *
   * @type {Function}
   * @public
   */
  onSubmitFeedback = resolve;

  @dropTask
  submitFeedback = function* submitFeedback() {
    let { feedbackText, wasContentUseful, onSubmitFeedback } = this;
    yield onSubmitFeedback(wasContentUseful, feedbackText);
  };

}

How can I track, what this submission is actually doing? What is resolve doing in a situation like this? onSubmitFeedback = resolve;




samedi 8 janvier 2022

Is it a good way to call API calls from component classes in emberjs?

I am learning emberjs right now and i was wondering if it is a common way to make fetch request inside a function (for example for button clicks) in component classes and store the data in the class component. For example:

export default class ExampleComponent extends Component {
  @tracked items;
  @action async toggleCall() {
    await fetch(...)
    .then(...)
  }
}

Or do you do this in a different way in emberjs?




vendredi 7 janvier 2022

How to work with REST API using Ember data?

I'm trying to use ember data with REST API from server. The project is quiet simple and works fine with Fetch API, but i would like to implement the same result using Ember data and understand it's principles. I could not use ember twiddle with this configuration. Below will be the main parts of the project. It was made by Ember CLI version 4.0.1.

The JSON responce from url http://localhost:3000/api/v1/items looks like:

[
  {
    "id": 1,
    "name": "Item 1",
    "description": "12345",
    "created_at": "2022-01-07T11:43:21.755Z",
    "updated_at": "2022-01-07T11:43:21.755Z"
  },
  {
    "id": 2,
    "name": "Item 2",
    "description": "22222",
    "created_at": "2022-01-07T11:43:29.787Z",
    "updated_at": "2022-01-07T11:43:29.787Z"
  },
  {
    "id": 3,
    "name": "Item 3",
    "description": "33333",
    "created_at": "2022-01-07T11:43:37.885Z",
    "updated_at": "2022-01-07T11:43:37.885Z"
  }
]

The responce from a single item request such as http://localhost:3000/api/v1/items/1:

{
    "id": 1,
    "name": "Item 1",
    "description": "12345",
    "created_at": "2022-01-07T11:43:21.755Z",
    "updated_at": "2022-01-07T11:43:21.755Z"
}

app/models/item.js:

import Model, { attr } from '@ember-data/model';

export default class ItemModel extends Model {
  @attr name;
  @attr description;
}

app/routes/items.js:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class ItemsRoute extends Route {
  @service store;
  async model() {
    return this.store.findAll('item');
  }
}

app/routes/item.js:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class ItemRoute extends Route {
  @service store;
  async model(params) {
    return this.store.findRecord('item', params.id);
  }
}

app/templates/items.hbs:


  <LinkTo @route='item' @model=></LinkTo>

app/adapters/application.js:

import RESTAdapter from '@ember-data/adapter/rest';

export default class ItemAdapter extends RESTAdapter {
  host = 'http://localhost:3000';
  namespace = 'api/v1';

  buildURL(...args) {
    return `${super.buildURL(...args)}.json`;
  }
}

app/serializers/application.js:

import RESTSerializer from '@ember-data/serializer/rest';

export default class ItemSerializer extends RESTSerializer {}

app/router.js:

import EmberRouter from '@ember/routing/router';
import config from 'dist/config/environment';

export default class Router extends EmberRouter {
  location = config.locationType;
  rootURL = config.rootURL;
}

Router.map(function () {
  this.route('items');
  this.route('item', { path: '/:id' });
});

When I move to route /items on the ember client, the following error appears in the console:

WARNING: Encountered "0" in payload, but no model was found for model name "0" (resolved model name using <dist@serializer:item::constructor>.modelNameFromPayloadKey("0"))

And the same errors for "1" and "2". The template doesn't render links in the each loop.

Probably, there are some features in Ember data with REST API, and I would really like to figure it out. Thanks for attention!




mardi 4 janvier 2022

Ember component is rendering the number of time the tracked property from component is changed

I have been trying to implement this project in ember. I am aware of the DDAU design paradigm. But in this particular project I wanted to have the AudioPlayer manage the pause and play state. Hence I used a component-class PlayerComponent to manage this state. I further attached a modifier to the audio tag and added an EventListener for canplay. I passed the isPlaying property from the component-class and I'm logging it to the console.
Instead of logging the the value of isPlaying once when the new media is loaded when I hit the next button (I am loading the media locally) it prints value of isPlaying the number of times its value was changed. I am not able to decipher why this is happening.

Here is the video showing the problem

Here is the Code:

1. index.hbs (templates/index.hbs)

<h1>Music Player</h1>
<Player
  @song=
  @next=
  @prev=
/>

2. index.js (controller/index.js)
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class IndexController extends Controller {
  songList = ['ukulele', 'summer', 'hey'];
  @tracked current = 0;
  
  @action
  next() {
    if (this.current == this.songList.length - 1) this.current = 0;
    else this.current++;
  }

  @action
  prev() {
    if (this.current == 0) this.current = this.songList.length - 1;
    else this.current--;
  }

  get currentSong() {
    return this.songList[this.current];
  }
}

3. player.hbs (components/player.hbs)
<div class='music-container ' id='music-container'>
  <div class='music-info'>
    <h4 id='title'></h4>
    <div class='progress-container' id='progress-container'>
      <div class='progress' id='progress'></div>
    </div>
  </div>

  <audio
    src='/music/.mp3'
    id='audio'
    
  ></audio>

  <div class='img-container'>
    <img src='images/.jpeg' alt='music-cover' id='cover' />
  </div>
  <div class='navigation'>
    <button id='prev' class='action-btn' type='button'>
      <i class='fas fa-backward' ></i>
    </button>
    <button id='play' class='action-btn action-btn-big' type='button'>
      <i
        class='fas '
        
      ></i>
    </button>
    <button id='next' class='action-btn' type='button'>
      <i class='fas fa-forward' ></i>
    </button>
  </div>
</div>

4. player.js (components/player.js)
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class PlayerComponent extends Component {
  @tracked isPlaying = false;
  @action
  playPause() {
    if (this.isPlaying) this.isPlaying = false;
    else this.isPlaying = true;
    this.playSong(this.isPlaying);
  }
  playSong(cond) {
    let audioElement = document.querySelector('#audio');
    if (cond) audioElement.play();
  }
}

4. song-change.js (modifiers/song-change.js)
import { modifier } from 'ember-modifier';

export default modifier(function songChange(element, [isplaying]) {
  // element.addEventListener('canplay', onChange);
  element.addEventListener('canplay', loaded);

  function loaded() {
    console.log(isplaying);
  }
});



dimanche 2 janvier 2022

ember json api camelcase/model attributes undefined

I am using the older Ember 'JSONSerializer' because of the json format being returned by the API;

[
  {
    "userId": 1,
    "id": 1,
    "title": "quidem molestiae enim"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "sunt qui excepturi placeat culpa"
  },
.....
]

The model requires attributes with dashes so I have created an Application Serializer

import JSONSerializer from '@ember-data/serializer/json';
import { dasherize } from '@ember/string';

export default class ApplicationSerializer extends JSONSerializer {
   keyForAttribute(attr) {
     return dasherize(attr)
  }
}

Album Model

import Model, { attr } from '@ember-data/model';

export default class AlbumModel extends Model {
  @attr userId;
  @attr title;
}

But the model attributes are still undefined

Album Template

<h1>album</h1>
  
  <li>
  
  
  
</li>
  

ember-cli: 4.0.1 node: 12.10.0

enter image description here