Giter VIP home page Giter VIP logo

ember-ref-bucket's Introduction

ember-ref-bucket

This addon was created as a rethinking of ember-ref-modifier, with a more simplified API and without some of the downsides of the previous implementation.

The addon allows users to get access to DOM nodes inside components, including accessing wrapping/destroying logic.

A simple use case:

  • applying ref modifier with passed name to an element.
<div {{create-ref "FavouriteNode"}}>hello</div>
  • gain access to it inside the component class as a decorated property
import Component from '@glimmer/component';
import { ref } from 'ember-ref-bucket';

export default class MyComponent extends Component {
  @ref("FavouriteNode") node; 
  // this.node === "<div>hello</div>"
}

API differences, comparing to ember-ref-modifier:

In ember-ref-modifier ref modifier accept 2 positional arguments {{ref this "property"}}:

  1. context to set path (this)
  2. path to set on context ("property")

In ember-ref-bucket ref modifier accept 1 positional argument {{create-ref "field"}}:

  1. reference name ("field")

reference name should be passed as an argument to the @ref("field") decorator, to allow it to find the reference by name.

Compatibility

  • Ember.js v3.24 or above
  • Ember CLI v3.24 or above
  • Node.js v14 or above

Installation

ember install ember-ref-bucket

Usage

Examples

Simple player

<audio {{create-ref "player"}} src="music.mp3"></audio>
<button {{on "click" this.onPlay}}>Play</button>
import Component from '@glimmer/component';
import { ref } from 'ember-ref-bucket';
import { action } from '@ember/object';

export class Player extends Component {
  @ref('player') audioNode;
  @action onPlay() {
    this.audioNode.play()
  }
}

Link div to node property.

<div {{create-ref "field"}} ></div>
import Component from '@glimmer/component';
import { ref } from 'ember-ref-bucket';

export default class MyComponent extends Component {
  @ref("field") node = null;
}

Dynamically show div content updates

<div {{create-tracked-ref "field"}}>hello</div>

{{get (tracked-ref-to "field") "textContent"}}

Use div as component argument

<div {{create-ref "field"}}>hello</div>

<SecondComponent @helloNode={{ref-to "field"}} />

Use registerNodeDestructor

This method is very useful if you want to wrap the node and control its lifecycle.

<div {{create-ref "field"}}>
import Component from '@glimmer/component';
import { ref, registerNodeDestructor } from 'ember-ref-bucket';

class NodeWrapper {
  constructor(node) {
    this.node = node;
  }
  destroy() {
    this.node = null;
  }
  value() {
    return this.node.textContent;
  }
}

export default class WrappedNodeComponent extends Component {
  @ref('field', (node) => {
    const instance = new NodeWrapper(node);
    registerNodeDestructor(node, () => instance.destroy());
    return instance;
  }) node = null;
  get value() {
    return this.node?.value();
  }
}

Available decorators:

import { ref, globalRef, trackedRef, trackedGlobalRef } from 'ember-ref-bucket';

/*
  ref - usage: @ref('foo', nodeWrapFn?), ref to bucket with current component context
  globalRef - usage: @globalRef('foo', nodeWrapFn?), ref to global context (app)
  trackedRef - usage: @trackedRef('foo', nodeWrapFn?), tracked ref to local context
  trackedGlobalRef - usage: @trackedGlobalRef('foo', nodeWrapFn?), tracked ref to global context (app)

*/

Available methods:

import { registerNodeDestructor, unregisterNodeDestructor } from 'ember-ref-bucket';

/*
  registerNodeDestructor(node, fn) - to assign any ref-node destructor
  unregisterNodeDestructor(node, fn) - to remove assigned ref-node destructor 

  usage will be like:

  @ref('field', (node) => {
    const item = new InputMask(node);
    registerNodeDestructor(node, () => item.destroy());
    return item;
  });
*/
/* 
  nodeFor - functional low-level primitive to get node access
*/

import { nodeFor } from 'ember-ref-bucket';

const domNode = nodeFor(this, 'field');

Definition of @trackedRef decorators

  • If you use dom node in @tracked chain calculations, you should use trackedRef.

  • If you don't need to rerun the tracked chain (for example, you use ref only for some event-based dom access), you should not use trackedRef.

Definition of {{create-tracked-ref}} modifiers

  • If you need to watch for node changes (resize, content, attributes), you can use the create-tracked-ref modifier. It can add observe resizing and mutations for the associated element and will mark it as "dirty" for any mutation.

Options:

  • resize - default: false, if truthy observes the resizing of the DOM element.
  • attributes - default: false, if truthy observes the changing of any attribute on the DOM element.
  • character - default: false, if truthy observes the change of the innerText of the DOM element. Note that setting innerText can change the children or the character depending on the current content of the element.
  • children - default: false, if truthy observes changes to the list of direct children of the DOM element.
  • subtree - default: false, if truthy observes the above options on the entire DOM subtree, not just the element decorated by the modifier.

Definition of {{tracked-ref-to}} helpers

  • If you need to recalculate helper if some dom node changes (size, children, attributes), you need to use tracked-ref-to helper.
  • If you don't need it (you need to just have ref to dom node), you should choose ref-to helper.

Template-only components

  • create-ref modifier and ref-to helpers will not work in template-only components (because of no context). You should use create-global-ref and global-ref-to instead. You can also provide a bucket param to the create-ref modifier / helper.

The addon provide only 1 modifier (create-ref) and 1 helper (ref-to). Other derivatives will be transformed, and are described below:

Modifiers will be transformed according to this table:

Invocation Will be transformed to
{{create-ref "foo"}} {{create-ref "foo" bucket=this}}
{{create-tracked-ref "foo"}} {{create-ref "foo" bucket=this tracked=true}}
{{create-global-ref "foo"}} {{create-ref "foo" bucket=undefined}}
{{create-tracked-global-ref "foo"}} {{create-ref "foo" bucket=undefined tracked=true}}

Helpers will be transformed according to this table:

Invocation Will be transformed to
{{ref-to "foo"}} {{ref-to "foo" bucket=this}}
{{tracked-ref-to "foo"}} {{ref-to "foo" bucket=this tracked=true}}
{{global-ref-to "foo"}} {{ref-to "foo" bucket=undefined}}
{{tracked-global-ref-to "foo"}} {{ref-to "foo" bucket=undefined tracked=true}}

Contributing

See the Contributing guide for details.

Version matrix:

Ember-Modifier 4 - v5; Ember 3.28 - v4; Ember 3.24 - v3

License

This project is licensed under the MIT License.

ember-ref-bucket's People

Contributors

acorncom avatar backspace avatar bryancrotaz avatar ctjhoa avatar cyril-sf avatar ember-tomster avatar jasonbekolay avatar jelhan avatar lifeart avatar robbiethewagner avatar shishouille avatar tniezurawski avatar tzellman avatar wozny1989 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

Watchers

 avatar  avatar  avatar  avatar

ember-ref-bucket's Issues

bubble mutationObserver options up to modifier

It's very easy to accidentally create an infinite loop that crashes the browser because by default you're observing the entire tree.

<div {{create-ref "outer" bucket=this tracked=true}}>
  <div style={{this.style}}/>
</div>
@trackedRef("outer") _outer;

get style() {
  return htmlSafe(`font-height:${this._outer.clientHeight}px`);
}

Default should be to only monitor the element that has the modifier. Options should be provided:

  • subtree: false - this is the expensive one
  • children: false
  • attributes: true
  • resize: true

Ember modifier v3 depreciation

Here is the migration guide for next ember modifier v4 : https://github.com/ember-modifier/ember-modifier/blob/v3/MIGRATIONS.md

testem.js:967 DEPRECATION: ember-modifier (in RefModifier at Error
    at new ClassBasedModifier (http://localhost:7357/assets/vendor.js:91247:228)
    at new RefModifier (http://localhost:7357/assets/vendor.js:92514:7)
    at ClassBasedModifierManager.createModifier (http://localhost:7357/assets/vendor.js:91062:24)
    at CustomModifierManager.create (http://localhost:7357/assets/vendor.js:37782:31)
    at Object.evaluate (http://localhost:7357/assets/vendor.js:45257:25)
    at AppendOpcodes.evaluate (http://localhost:7357/assets/vendor.js:43797:19)
    at LowLevelVM.evaluateSyscall (http://localhost:7357/assets/vendor.js:47663:22)
    at LowLevelVM.evaluateInner (http://localhost:7357/assets/vendor.js:47619:14)
    at LowLevelVM.evaluateOuter (http://localhost:7357/assets/vendor.js:47611:14)
    at VM.next (http://localhost:7357/assets/vendor.js:48622:24)): `willDestroy`, `isDestroyed`, and `isDestroyed` are deprecated. Use the corresponding API from '@ember/destroyable' instead. [deprecation id: ember-modifier.use-destroyables]
        at logDeprecationStackTrace (http://localhost:7357/assets/test-support.js:582:21)
        at HANDLERS.<computed> (http://localhost:7357/assets/test-support.js:689:9)
        at raiseOnDeprecation (http://localhost:7357/assets/test-support.js:611:9)
        at HANDLERS.<computed> (http://localhost:7357/assets/test-support.js:689:9)
        at http://localhost:7357/assets/test-support.js:6448:10
        at HANDLERS.<computed> (http://localhost:7357/assets/test-support.js:689:9)
        at invoke (http://localhost:7357/assets/test-support.js:701:9)
        at deprecate (http://localhost:7357/assets/test-support.js:657:28)
        at new ClassBasedModifier (http://localhost:7357/assets/vendor.js:91247:160)
testem.js:967 DEPRECATION: ember-modifier (in RefModifier at Error
    at RefModifier.get [as args] (http://localhost:7357/assets/vendor.js:91437:155)
    at RefModifier.get name [as name] (http://localhost:7357/assets/vendor.js:92653:19)
    at new RefModifier (http://localhost:7357/assets/vendor.js:92516:42)
    at ClassBasedModifierManager.createModifier (http://localhost:7357/assets/vendor.js:91062:24)
    at CustomModifierManager.create (http://localhost:7357/assets/vendor.js:37782:31)
    at Object.evaluate (http://localhost:7357/assets/vendor.js:45257:25)
    at AppendOpcodes.evaluate (http://localhost:7357/assets/vendor.js:43797:19)
    at LowLevelVM.evaluateSyscall (http://localhost:7357/assets/vendor.js:47663:22)
    at LowLevelVM.evaluateInner (http://localhost:7357/assets/vendor.js:47619:14)
    at LowLevelVM.evaluateOuter (http://localhost:7357/assets/vendor.js:47611:14)): using `this.args` is deprecated. Access positional and named arguments directly in the `modify` hook instead. [deprecation id: ember-modifier.no-args-property]
        at logDeprecationStackTrace (http://localhost:7357/assets/test-support.js:582:21)
        at HANDLERS.<computed> (http://localhost:7357/assets/test-support.js:689:9)
        at raiseOnDeprecation (http://localhost:7357/assets/test-support.js:611:9)
        at HANDLERS.<computed> (http://localhost:7357/assets/test-support.js:689:9)
        at http://localhost:7357/assets/test-support.js:6448:10
        at HANDLERS.<computed> (http://localhost:7357/assets/test-support.js:689:9)
        at invoke (http://localhost:7357/assets/test-support.js:701:9)
        at deprecate (http://localhost:7357/assets/test-support.js:657:28)
        at RefModifier.get [as args] (http://localhost:7357/assets/vendor.js:91437:87)

modifier transforms don't appear to work

Ember 3.22
{{create-tracked-ref "my-element"}}
Error: Compile Error: Unexpected Modifier create-tracked-ref @ 0..0

{{create-ref "my-element" bucket=this tracked=true}} works

`create-ref` doesn't seem to clean up after element is destroyed

<div>
  <button class="p-2 ml-2" {{on "click" this.addItems}}>Add 100 items</button>
  <button class="p-2 ml-2" {{on "click" this.clear}}>Clear</button>
</div>

{{#each this.items as |item|}}
  <div {{create-ref (concat "item-" item)}}>
    {{item}}
  </div>
{{/each}}
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class Test extends Component {
  @tracked items: number[] = [];

  @action addItems() {
    this.items = [
      ...this.items,
      ...Array(100)
        .fill(0)
        .map((_, i) => i),
    ];
  }

  @action clear() {
    this.items = [];
  }
}

I have some code like above. When I add 100 items, then clear them, I'm expecting that those elements won't show up in memory, but I'm still seeing them as detached elements. Here's a screenshot from Edge:

image

Memory leak when using {{create-ref}}

Something is leaking when using {{create-ref}}. When running a memory snapshot after running tests I see an application hanging around. I was able to pinpoint it to this addon and {{create-ref}} usage.

image

The way I use it (source):

<span {{create-ref "FavouriteNode"}}>Lorem ipsum</span>

Steps to reproduce:

  1. I created a minimal repo for reproduction. Please clone it -> https://github.com/tniezurawski/ember-ref-bucket-memory-leak

The repo is bare-bone. All I did was:

  • creating a new Ember app
  • adding ember-ref-bucket
  • creating a test component (you can check there's no leak at this point)
  • adding {{create-ref}} to that component
  1. Run tests with this command: ember test -s -f "Integration | Component | tomek-test". No more tests there but you'll be sure that you are running only the one.
  2. Open Chrome devtools > Open Memory > click "Take snapshot"
  3. Search for "Container" as this indicates an application instance hanging around
  4. Select "Container" as on the screenshot and look at "Retainers". You'll find references to this addon

I didn't check yet why this is happening as I don't have knowledge about internals but I hope we can get it fixed together :)

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.