Giter VIP home page Giter VIP logo

ember-could-get-used-to-this's Introduction

ember-could-get-used-to-this

I Could Get Used To This

Ember Could Get Used To This is an opinionated take on the future direction of non-component template constructs in Ember. See this blog post for more details!

Compatibility

  • Ember.js 3.23 or above
  • Ember CLI v2.13 or above
  • Node.js v8 or above

Installation

ember install ember-could-get-used-to-this

Usage

Functions

Good news! As of Ember 4.5, this feature is now built into Ember, and includes both positional and named arguments. For more details, read the Plain Old Functions as Helper blog post.

You can export plain functions from inside app/helpers and use them as helpers. This only support positional arguments:

// app/helpers/add-numbers.js
export default function addNumbers(number1, number2) {
  return number1 + number2;
}
{{! Usage in template: outputs 13 }}
{{add-numbers 10 3}}

Modifiers

You can define your own modifiers. You can do so using either a class-based or a functional style.

Modifiers can be used like this:

<button {{on "click" this.onClick}}>My button</button>

Functional modifiers

import { modifier } from 'ember-could-get-used-to-this';

export default modifier(function on(element, [eventName, handler]) => {
  element.addEventListener(eventName, handler);

  return () => {
    element.removeEventListener(eventName, handler);
  }
});

Class-based modifiers

// app/modifiers/on.js
import { Modifier } from 'ember-could-get-used-to-this';

export default class On extends Modifier {
  event = null;
  handler = null;

  setup() {
    let [event, handler] = this.args.positional;

    this.event = event;
    this.handler = handler;

    this.element.addEventListener(event, handler);
  }

  teardown() {
    let { event, handler } = this;

    this.element.removeEventListener(event, handler);
  }
}

Resources

Resources are, as of now, also defined in the app/helpers directory. They can be either used directly in your templates, or by a JavaScript class.

// app/helpers/counter.js
import { tracked } from '@glimmer/tracking';
import { Resource } from 'ember-could-get-used-to-this';

class Counter extends Resource {
  @tracked count = 0;

  intervalId = null;

  get value() {
    return this.count;
  }

  setup() {
    this.intervalId = setInterval(() => this.count++, this.args.positional[0]);
  }

  update() {
    clearInterval(this.intervalId);
    this.intervalId = setInterval(() => this.count++, this.args.positional[0]);
  }

  teardown() {
    clearInterval(this.intervalId);
  }
}

This example resource can be used from a template like this:

{{#let (counter @interval) as |count|}}
  {{count}}
{{/let}}

Or in a JS class:

// app/components/counter-wrapper.js
import Component from '@glimmer/component';
import { use } from 'ember-could-get-used-to-this';
import Counter from 'my-app/helpers/counter';

export default class CounterWrapper extends Component {
  @use count = new Counter(() => [this.args.interval]);
}
{{! app/components/counter-wrapper.hbs }}
{{this.count}}

If you provide an update function in your resource, this will be called every time an argument changes. Else, the resource will be torn down and re-created each time an argument changes.

You can also provide named arguments to a resource, which are available via this.args.named.

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

ember-could-get-used-to-this's People

Contributors

chriskrycho avatar ember-tomster avatar jenweber avatar mydea avatar nullvoxpopuli avatar pzuraq avatar sandstrom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ember-could-get-used-to-this's Issues

Usage with standalone glimmer apps

Hi @pzuraq,

I've been following this project with great interest with an especial interest in effects implementation; I think it will quite handily solve a number of pain points integrating third party libraries that we're experiencing.

Out of curiosity: would there be a way to use this in a standalone glimmerjs app?

Thanks for hard work on this and underlying architecture!

TypeScript

Would you prefer a PR adding .d.ts files or "properly" converting to .ts?

Arguments only used in `update` hook are not tracked

It took me some time to figure it out, but it seems that arguments only used in the update hook (no matter if named or positional) are not properly tracked.

I made a failing test case here:

mydea@4b051ca

E.g.:

 class extends Resource {
        @tracked value;

        setup() {
          this.value = this.args.positional[0];
        }

        update() {
          if (this.args.named.shouldReload) {
            this.value = `${this.args.positional[0]} RELOADED`;
          }
        }
      }

Changing shouldReload will not trigger the update.

Uncaught Error: Could not find module `@ember/helper` imported from `ember-could-get-used-to-this/-private/functions`

Hi,

After installing via

ember install ember-could-get-used-to-this

the site builds but I’m getting this error in the console.

loader.js:247 Uncaught Error: Could not find module `@ember/helper` imported from `ember-could-get-used-to-this/-private/functions`
    at missingModule (loader.js:247)
    at findModule (loader.js:258)
    at Module.findDeps (loader.js:168)
    at findModule (loader.js:262)
    at Module.findDeps (loader.js:168)
    at findModule (loader.js:262)
    at Module.findDeps (loader.js:168)
    at findModule (loader.js:262)
    at requireModule (loader.js:24)
    at r (loader.js:176)

This is before adding any new code.

Ember CLI : 3.20
Ember : 3.20.3

Removing the addon and everything is working as expected again.

Am I missing something? Thanks.

Document how to provide named arguments

In the README it is mentioned that one can provide named arguments to a resource, however, it doesn't say how.
I've tried returning an object instead of an array from the initializer function, like so:

class MyResource extends Resource{
  setup() {
    console.log(this.args.named); //empty
    }
}

class MyComponent extends Component {
   @use data = new MyResource(() => ({foo: this.args.foo, bar: this.args.bar}));
   
  }

But no dice. How can we do this? and if we can't, it would be good to add to the README

Ember._helperManagerCapabilities is not a function

Trying to experiment with this in an add-on and in the dummy app I get the following in console:

Uncaught TypeError: Ember._helperManagerCapabilities is not a function at new FunctionalHelperManager (functions.js:8)

Really looking forward to fiddling with this as it seems to solve a number of issues that we've solved badly in a few different ways!

initial-implementation - effect cleanup not called when DEBUG is true

Hello,

First off, this library is awesome, and I do understand that it is very much a work in progress. With that said, there appears to be an issue on the initial-implementation branch in which the provided effect function, when given a function that returns a cleanup function, does not invoke the cleanup function correctly when the component is destroyed. This only happens while running in the local development mode

in the following example, when DEBUG is true and when the Test component is destroyed, the second log will never execute:

import Component from '@glimmer/component'
import { use, effect } from 'ember-usable'

class Test extends Component {
  constructor(owner, args) {
    super(owner, args)
    use(this, effect(() => {
      console.log('effect is run')
      return () => console.log('cleanup is run')
    })
  }
}

I hope this is helpful, and please let me know if more description is needed. Meanwhile, I will do some digging and see if I can come up with a PR. Thanks!

EDIT:

I've published a PR here #5 that fixes the issue I was seeing

Even if `value` does not change, children are recomputed

If you have a resource with an update hook that sometimes updates the value, the value will still be invalidated every time.

Example:

update() {
  if (this.args.named.shouldReload) {
    this.value = this.loadData();
  }
}

In such a scenario, if shouldReload would be switched from true to false, it would still invalidate value even though it did not change. This is noticeable if you have downstream helpers which will be re-triggered, e.g.:

{{#let (my-resource shouldReload=this.shouldReload) as |resource|}}
  {{run-on-update this.notifyUpdate resource.value}}
{{/let}}

Here, run-on-update would be triggered again even though value would be exactly the same as before. When coupled with complex data types, e.g. an array, it will even be the same instance, but still be triggered again.

To deal with this I need to add guard checks everywhere to check if the values have actually changed, which kind of messes with the nice reactivity model at the core of this, I'd say.

I made a failing test case here:

mydea@371fdf2

Resource hooks are constantly re-run

I have just tried write a resource "helper" like this:

export default class LoadDataResource extends Resource {
  @service store;

  @tracked data = null;
  @tracked isLoading = true;
  @tracked isError = false;

  get value() {
    return {
      isLoading: this.isLoading,
      isError: this.isError,
      data: this.data,
    };
  }

  setup() {
    let options = this.args.positional[0];
     taskFor(this.loadDataTask).perform(options);
  }

  teardown() {
    taskFor(this.loadDataTask).cancelAll();
  }

  @task
  *loadDataTask(options) {
    this.isLoading = true;
    this.isError = false;

    try {
      let data = yield this.store.loadData(options);

      this.isError = false;
      this.isLoading = false;
      this.data = data;
    } catch (error) {
      this.isError = true;
      this.isLoading = false;
      this.data = error;
    }
  }
}

Used like this:

{{# let (load-data (hash name='xx' age=30)) as |resource|}}
  {{log resource}}
{{/let}}

However, that goes into an infinite loop of setup/teardown of the resource.

Conditional use resource

I'm playing with this lib (the initial-implementation branch) and I'm wondering how to get a @use with a conditional.

I have a set of logic deciding when to fetch the data, but using the decorator alone doesn't seem to be possible to decide if I should execute my resource. Looking at the RFC, the useResource might be a good option. However, I still need to get the state in "tracked matter". Maybe a combination of effect and useResource?

Any help here would be awesome, as I experiment with use & resources.

Here is a high-level example:

// Works!

class MyComponent extends Componnet {
  @use accounts = watchQuery(this.args.myArgs);
}

// Well, just something that will not work...

class MyComponent extends Componnet {
  @tracked accounts; // ???

  constructor(owner, args) {
    super(owner, args);

    this.accounts = use(
      this,
      watchQuery(() => [
         ...this.args.myArgs
      ])
    ).state;

    console.log(this.accounts);
  }
}

PS, this watchQuery resource is an Apollo GraphQL thing that I'm working on.. Here is a sketch of that resource: https://gist.github.com/josemarluedke/2e60b6d7303d4eb4fdfbba157e11dc34

Plain functions as helpers fail with hash params in ember > 4.5, due to test in ember-could-get-used-to-this

  1. Clone this repo: https://github.com/johanrd/plain-functions-as-helpers-with-hash-params

See that the repo fails with the following error after installing ember-mobile-menu:

Error: Assertion Failed: Functional helpers cannot receive hash parameters. `substring` received start, end

This error comes from the substring function in the official ember guides on functions with hash params, where the message itself stems from ember-could-get-used-to-this:

assert.equal(e.message, 'Assertion Failed: Functional helpers cannot receive hash parameters. `add` received first,second');

Is there any way to bypass this test in ember-could-get-used-to-this? According to the readme:

As of Ember 4.5, this feature is now built into Ember, and includes both positional and named arguments.

I've also posted this in nickschot/ember-mobile-menu#771, as I'm not sure whether this could be fixed by a config there, or needs to be fixed here.

Thanks,

Update when argument changes

This isn't necessarily an issue with the code, could be a misunderstanding on my part as well.

I have a resource that doesn't seem to update then the argument route changes.

I've currently got it working using {{did-update …}} render modifiers, but that's a workaround.

Trying to understand if I've misunderstood how Resources are supposed to work?

But if the did-update modifier can pick up that the route has changed, I was thinking that the update hook would fire as well.

// helpers/geographic-map-resource.js

import { Resource } from 'ember-could-get-used-to-this';

export default class GeographicMapResource extends Resource {
  _instance = null;

  get value() {
    return this._instance;
  }

  setup() {
    let element = this.args.positional[0];
    let route = this.args.positional[1];

    // instantiate only if the map isn't already setup
    if (!this._instance) {
      this._renderMap(element);
    }

    // render map route
    this._updateRoute(route);
  }

  update() {
    let element = this.args.positional[0];
    let route = this.args.positional[1];

    // instantiate only if the map isn't already setup
    if (!this._instance) {
      this._renderMap(element);
    }

    // render map route
    this._updateRoute(route);
  }

  _renderMap(element) {
    this._instance = this._createMapFromThirdPartyLib(element);

    // …
  }
  
  _updateRoute(route) {
    // …
  }
}
// components/geographic-map.js
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { use } from 'ember-could-get-used-to-this';
import GeographicMapResource from 'client/helpers/geographic-map-resource';

export default class GeographicMapComponent extends Component {
  mapContainer = null;

  @use map = new GeographicMapResource(() => [this.mapContainer, this.args.route]);

  @action
  loadMap(element) {
    this.mapContainer = element;
    this.map; // instantiate
  }

  @action
  updateMap(_element) {
    // Resources are supposed to auto-update if any of their args changes, but that doesn't seem to be the case,
    // so we're using did-update to call this method, to trigger an update if route/markers change.
    if (this.args.route) {
      this.map; // trigger update
    }
  }
}
<!-- components/geographic-map.hbs -->
<div class='geographic-map-component'>
  <div {{did-insert this.loadMap}} {{did-update this.updateMap @route}}></div>
</div>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.