Giter Club home page Giter Club logo

ember-link's Introduction

ember-link

CI npm version Download Total Ember Observer Score

Introduces a new Link primitive to pass around self-contained references to routes, like URLs, but with state (isActive, ...) and methods (open, ...). Also brings along an accompanying template helper for easy usage in templates.

ember-link does to routing what ember-concurrency did to asynchrony!

/r/whatjawsdid

Installation

Install ember-link with:

ember install ember-link

Usage

You can use ember-link in a declarative form with a (link) helper or imperatively with the LinkManager Service.

(link) Helper Example

Use the (link) helper to create a link primitive and attach it to an element.

{{#let (link "about") as |l|}}
  <a href={{l.url}} {{on "click" l.open}}>
    About us
  </a>
{{/let}}

LinkManager Service Example

Use the LinkManager.createLink() method to create a link programmatically.

import Contoller from '@ember/controller';
import { service } from '@ember/service';
import type { LinkManagerService } from 'ember-link';

export default class PageHeader extends Controller {
  @service declare linkManager: LinkManagerService;

  aboutUsLink = this.linkManager.createLink('about');
}

Working with Primitives

The idea of ember-link is to be able to create link primitives, that you can pass around. Create links at route level and then pass them into components.

A more in-depth guide is available at using primitives.

Testing

ember-link has testing support on board, preparing the environment with setupLink() and linkFor() to create a link to a route on the fly:

import { click, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';

import { linkFor, setupLink } from 'ember-link/test-support';

import type { TestContext as BaseTestContext } from '@ember/test-helpers';
import type { TestLink } from 'ember-link/test-support';

interface TestContext extends BaseTestContext {
  link: TestLink;
}

module('`setupLink` example', function (hooks) {
  setupRenderingTest(hooks);
  setupLink(hooks);

  test('using link in render tests', async function (this: TestContext, assert) {
    // arrange
    this.link = linkFor('some.route');
    this.link.onTransitionTo = () => assert.step('link clicked');

    await render(hbs`
      {{#let this.link as |l|}}
        <a href={{l.url}} {{on "click" l.open}}>Click me</a>
      {{/let}}
    `);

    // act
    await click('a');

    // assert
    assert.verifySteps(['link clicked']);
  });
});

Related RFCs / Projects

ember-link's People

Contributors

balinterdi avatar bertdeblock avatar buschtoens avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar ember-tomster avatar gossi avatar renovate-bot avatar renovate[bot] avatar tstormk avatar turbo87 avatar villander 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  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ember-link's Issues

Test Helpers

Something like this maybe?

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { find, render, settled } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { setupLink, linkFor } from 'ember-link/test-support';

module('setupLink with rendering test', function(hooks) {
  setupRenderingTest(hooks);
  setupLink(hooks);

  test('it is a shortcut for accessing translations', async function(assert) {
    assert.expect(2);

    await render(hbs`
      <Link @route="foo" as |l|>
        <a
          data-test-link
          href={{l.url}}
          class={{if l.isActive "is-active"}}
          {{on "click" l.transitionTo}}
        >
          Link
        </a>
      </Link>
    `);

    assert.dom('[data-test-link]').hasAttribute(linkFor('foo').url);

    linkFor('foo').onTransitionTo(() => assert.step('transitionTo'));
    linkFor('foo').onReplaceWith(() => assert.step('replaceWith'));

    await click('[data-test-link]');

	assert.verifySteps(['transitionTo']);
  });
});
function linkFor(
  route: string,
  ...models: RouteModel[],
  { ['queryParams' | 'query']: QueryParams }?
): TestLink;

function linkFor({
  route: string,
  model?: RouteModel,
  models?: RouteModel[],
  query?: QueryParams
}): TestLink;

interface TestLink {
  // all these properties can be overridden and sync to all matching `Link` instances
  isActive: boolean;
  isActiveWithoutQueryParams: boolean;
  isActiveWithoutModels: boolean;
  isEntering: boolean;
  isExiting: boolean;
  url: string; // initialized with a guid value

  readonly routeName: string;
  readonly qualifiedRouteName: string; // alias for `routeName`
  readonly models: RouteModel[];
  readonly queryParams?: QueryParams;

  readonly onTransitionTo(listener: (event?: Event) => void): void;
  readonly onReplaceWith(listener: (event?: Event) => void): void;
}

Module import error from Octane app

Trying to use ember-link (version 1.0.1) in an Octane app and I get the following error:

Could not find module ember-link/components/link/template imported from sac-sac-mate/components/link/template

I don't think it matters but that's how I try to use it from the template:

<Link @route="players.player" @models={{array player}} as |link|>
  <a
   class="no-underline text-blue hover:text-blue-darker w-350px"
   href={{link.href}}>
   <PlayerCard @player={{player}} />
  </a>
</Link>

It seems that the problem comes from the addon's merged app tree but I'm not sure how to fix it (or whether that's indeed the problem).

Handle Cmd / Ctrl click to open in a new tab

When a link is clicked with Cmd / Ctrl it should open in a new tab.

The reason it does not work right now is that we always call event.preventDefault(), if @preventDefault is true / unset.

I think there's different solutions to this.

For one, we can add another boolean: @allowNewTab
If true (default) it will check whether event.currentTarget is an <a> element and if Cmd / Ctrl are pressed, in which case it will not call event.preventDefault(), even if @preventDefault is true.

In addition to this, we can also add another action openInNewTab, that opens href in a new tab.

Big thanks to @tchak13, who reported this via Twitter:
https://twitter.com/tchak13/status/1106149903317852160

Ember 3.24 support

Assertion Failed: ember-link.setupLink: Test helpers can only be used in integration tests

After upgrading to Ember 3.24 we've started seeing the above error message in a few of our tests. This is most likely related to emberjs/ember.js#19080, which now automatically sets up the router also in integration tests.

Currying Links ?

So, let's say this setup:

  • We have a component that represents a page/screen
  • The component is injected into two different hosts (let's say app and engine).
  • The component will link to a particular page
  • The component knows about that target route and what kind of parameters to pass in
  • Yet the component doesn't know under which name the route is available in the current host

As routes are the connectivity layer - they have knowledge about route names but at route level the information about what parameters to be put in are not directly present.

FWIW is to curry links. Ie. the route passes in the link/name of the route and the component curries it with the parameters. I assume something like this code:

{{! route.hbs }}
<MyPage @detailLink={{link 'go-to-details'}} />
{{! component }}

<Button @link={{link @detailLink this.model.id}}>...</Button>

Now, this example looks like as if it is enough to pass in the route name, but also on route level you shall be able to pass generic parameters in as well, so within the component it is only to "finalize" the link.

thoughts?

Clicking a link build with the `{{link}}` helper, the transition is not executed in Edge <= 18

Hi there 👋,

Thanks for this addon, that we use. :)
I think I found a bug. AFAICT the issue would be in ember-source. But I'm opening the issue here for tracking purpose.

A bug

Clicking a link build with the {{link}} helper, the transition is not executed in Edge <= 18.

Instead, I get the following error in the console:
Unable to get property 'router' of undefined or null reference
on the following line:

return this._linkManager.router.transitionTo(...this._routeArgs);

A possible cause

The bug only happens in Edge <= 18. I wonder if the code:

ember-link/addon/link.ts

Lines 276 to 283 in 8f0fab3

@action
transitionTo(event?: Event | unknown): Transition {
// Intentionally putting this *before* the assertion to prevent navigating
// away in case of a failed assertion.
this.preventDefault(event);
return super.transitionTo();
}
would not be a reproduction for this issue in the framework: emberjs/ember.js#18955

Keep (Parent Route) Query Parameters

The default behavior of Ember's <LinkTo> is to keep the query parameters of parent routes.

<Link> does not auto-append any query parameters to the generated URL. They need to be passed in explicitly via @query. This is über annoying, when you want to use <Link> as a substitute for <LinkTo> in a scenario where a child route links back to a parent route.

<Link> and {{link}} should offer a @keepQuery option. It can take these values:

  • none / falsy value (default, current behavior): No QPs of the current location are kept.
    • Only QPs provided via @query are appended.
  • known: Only QPs of the current location at the time of invocation that are defined as known QPs in the current route hierarchy are kept.
    • QPs provided via @query override these QPs individually.
    • A null / undefiend value in the @query object will remove a kept QP.
  • all: All QPs of the current location at the time of invocation are kept.
    • QPs provided via @query override these QPs individually.
    • A null / undefiend value in the @query object will remove a kept QP.
  • tracked-known: Only QPs that are defined as known QPs in the current route hierarchy are kept.
    • When the the current route / QPs change, the url will be updated live.
    • QPs provided via @query override these QPs individually.
    • A null / undefiend value in the @query object will remove a kept QP.
  • tracked-all: All QPs of the current location are kept.
    • When the the current route / QPs change, the url will be updated live.
    • QPs provided via @query override these QPs individually.
    • A null / undefiend value in the @query object will remove a kept QP.

Unresolved Questions

none is the current behavior. Changing this would be a breaking change IMO, so I'd make it the default, too. Do we want to keep it this way though or switch to a different default?

Assuming we want to switch, we could raise a deprecation warning, prompting the user to explicitly chose none or a different behavior.
Should the default be configurable or should we allow users to opt-in to the new default mode prior to upgrading to the next major version?

Do the names used here make sense? @keepQuery, none, known, all, tracked-*, untracked-*?

Do we need the tracked-* variants? Or do we not need the untracked variant? Should the untracked variants also be prefixed with untracked-*? Or should the tracked-* variants instead have no prefix?

Update Release Management

With release-it and lerna-changelog this reads in the information from the root package.json (such as version and repository).

When the next version is released only the version number from ember-link/package.json is incremented but root package.json stays as is. This will be problematic for further releases.

Also publishing through github actions has some trouble with npm auth.

Find a better solution for this.

External Links

This makes no sense when used directly in the template, like:

<Link @external="https://example.com" as |l|>
  <a href={{l.url}} {{on "click" l.transitionTo}}>
    Example
  </a>
</Link>

However, it becomes very useful when passing Links around, e.g. to build a navigation menu, because you can then easily use the same primitive in a common code path:

<NavigationMenu
  @navItems={{array
    (hash label="Top Sellers" link=(link "top"))
    (hash label="Categories" link=(link "categories"))
    (hash label="Shopping Cart" link=(link "cart"))
    (hash label="Legal" link=(external-link "/legal"))
  }}
/>
{{#each @navItems as |item|}}
  <a href={{item.link.url}} {{on "click" l.transitionTo}}>
    {{item.label}}
  </a>
{{/each}}

I'm not sure about the exact API yet.

Another thing: Usually you'll want external links to open in a new tab. This should not need to be defined at the "call site" where the Link is used on an <a> element, but rather where the Link is defined.

Something like this maybe? #289

<NavigationMenu
  @navItems={{array
    (hash label="Top Sellers" link=(link "top"))
    (hash label="Categories" link=(link "categories"))
    (hash label="Shopping Cart" link=(link "cart"))
    (hash label="Legal" link=(external-link "/legal" newTab=true))
  }}
/>

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Warning

These dependencies are deprecated:

Datasource Name Replacement PR?
npm @babel/plugin-proposal-class-properties Unavailable

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • chore(deps): pin dependencies (@gossi/config-eslint, @gossi/config-prettier, @gossi/config-template-lint, @typescript-eslint/eslint-plugin, @typescript-eslint/parser, ember-template-lint, eslint, eslint-plugin-ember, eslint-plugin-qunit, prettier)
  • chore(deps): pin dependencies (@types/qunit, @types/rsvp, @types/sinon)
  • chore(deps): pin dependencies (concurrently, ember-qunit, ember-source-channel-url, ember-try, qunit, qunit-dom)
  • chore(deps): update babel monorepo (@babel/core, @babel/eslint-parser, @babel/runtime)
  • chore(deps): update dependency typedoc to ^0.26.0
  • chore(deps): update node.js to v20.16.0
  • chore(deps): update actions/configure-pages action to v5
  • chore(deps): update dependency @ember/string to v4
  • chore(deps): update dependency @release-it-plugins/lerna-changelog to v7
  • chore(deps): update dependency ember-try to v3
  • chore(deps): update dependency sinon to v18
  • chore(deps): update framework dependencies (major) (@embroider/addon-dev, @embroider/test-setup, ember-resolver)
  • chore(deps): update lint dependencies (major) (@typescript-eslint/eslint-plugin, @typescript-eslint/parser, ember-template-lint, eslint)
  • 🔐 Create all rate-limited PRs at once 🔐

Edited/Blocked

These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • wyvox/action v1
  • wyvox/action v1
  • wyvox/action v1
  • ubuntu 20.04
  • ubuntu 20.04
  • ubuntu 20.04
.github/workflows/docs.yml
  • wyvox/action v1
  • actions/configure-pages v3
  • actions/upload-pages-artifact v1
  • actions/deploy-pages v2
.github/workflows/release.yml
  • wyvox/action v1
npm
ember-link/package.json
  • @embroider/addon-shim ^1.8.7
  • @embroider/macros ^1.13.4
  • @glimmer/env ^0.1.7
  • @babel/core ^7.23.7
  • @babel/plugin-proposal-class-properties ^7.18.6
  • @babel/plugin-proposal-decorators ^7.23.7
  • @babel/preset-typescript ^7.23.3
  • @babel/runtime ^7.23.8
  • @ember/test-helpers ^3.2.1
  • @embroider/addon-dev ^4.1.3
  • @glint/template ^1.3.0
  • @gossi/config-eslint ^0.6.0
  • @gossi/config-prettier ^0.6.0
  • @release-it-plugins/lerna-changelog ^6.1.0
  • @tsconfig/ember ^3.0.3
  • @types/qunit ^2.19.10
  • @typescript-eslint/eslint-plugin ^5.60.0
  • @typescript-eslint/parser ^5.60.0
  • concurrently ^8.2.2
  • ember-source ^5.6.0
  • eslint ^8.56.0
  • eslint-plugin-ember ^12.0.0
  • prettier ^3.2.4
  • release-it ^17.0.3
  • rollup 4.9.6
  • rollup-plugin-copy ^3.5.0
  • rollup-plugin-ts ^3.4.5
  • typedoc-plugin-markdown ^4.0.0-next.50
  • typedoc-vitepress-theme ^1.0.0-next.8
  • typedoc ^0.25.7
  • typescript ^5.3.3
  • @ember/test-helpers ^2.9.3 || ^3.0.0
  • @glimmer/tracking ^1.1.2
  • node >= 16.*
package.json
  • concurrently ^8.2.2
  • vitepress ^1.0.0-rc.41
  • node 20.11.0
test-app/package.json
  • @babel/core ^7.23.7
  • @babel/eslint-parser ^7.23.3
  • @ember/optional-features ^2.0.0
  • @ember/string ^3.1.1
  • @ember/test-helpers ^3.2.1
  • @embroider/test-setup ^3.0.3
  • @glimmer/env ^0.1.7
  • @glimmer/tracking ^1.1.2
  • @gossi/config-eslint ^0.6.0
  • @gossi/config-prettier ^0.6.0
  • @gossi/config-template-lint ^0.6.0
  • @tsconfig/ember ^3.0.3
  • @types/qunit ^2.19.10
  • @types/rsvp ^4.0.9
  • @types/sinon ^17.0.3
  • @typescript-eslint/eslint-plugin ^5.59.9
  • @typescript-eslint/parser ^5.59.9
  • broccoli-asset-rev ^3.0.0
  • ember-auto-import ^2.7.2
  • ember-cli ^5.6.0
  • ember-cli-babel ^8.2.0
  • ember-cli-dependency-checker ^3.3.2
  • ember-cli-htmlbars ^6.3.0
  • ember-cli-inject-live-reload ^2.1.0
  • ember-cli-sri ^2.1.1
  • ember-cli-terser ^4.0.2
  • ember-disable-prototype-extensions ^1.1.3
  • ember-load-initializers ^2.1.2
  • ember-qunit ^8.0.2
  • ember-resolver ^11.0.1
  • ember-sinon-qunit ^7.4.0
  • ember-source ^5.6.0
  • ember-source-channel-url ^3.0.0
  • ember-template-lint ^5.13.0
  • ember-try ^2.0.0
  • eslint ^8.56.0
  • eslint-plugin-ember ^12.0.0
  • eslint-plugin-qunit ^8.0.1
  • loader.js ^4.7.0
  • p-defer ~3.0.0
  • qunit ^2.20.0
  • qunit-dom ^3.0.0
  • sinon ^17.0.1
  • typescript ^5.3.3
  • webpack ^5.90.0
  • node >= 16.*

  • Check this box to trigger a request for Renovate to run again on this repository

Unfound exports `LinkParams` and `UILinkParams` under Embroider

I assume this is because these exports are actually TS interfaces and should be exported using export type?

WARNING in ./node_modules/ember-link/index.js 1:0-75
export 'LinkParams' (reexported as 'LinkParams') was not found in './link' (possible exports: UILink, default, isQueryParams)
 @ ./components/dropdown/actions/link/index.js 13:0-34 16:117-121
 @ ./tests/integration/components/dropdown/actions/link-test.js 5:0-74 7:9-11
 @ ./assets/test.js 32:13-85

WARNING in ./node_modules/ember-link/index.js 1:0-75
export 'UILinkParams' (reexported as 'UILinkParams') was not found in './link' (possible exports: UILink, default, isQueryParams)
 @ ./components/dropdown/actions/link/index.js 13:0-34 16:117-121
 @ ./tests/integration/components/dropdown/actions/link-test.js 5:0-74 7:9-11
 @ ./assets/test.js 32:13-85

Doesn't work in a project with e-decorators v6

We're getting the following console error when using the pre-octane version in an Ember 3.9 + Decorators v6 project:

Error: Assertion Failed: computed decorators must return an instance of an Ember ComputedProperty descriptor, received [object Function]

I searched the discord and it seems that this is related to the usage of e-decorators v5 in this addon.

Are there any plans for updating the pre-octane version? :) If not, I totally understand. We can always use the ember-router-helpers as an (more verbose) alternative.

Add `toString()` method alias for `url`

The code shown in #271 would be nicer, if Link had a toString() method, that returns the url.

Then you could do:

<Ui::Button
-  @link={{link "signup" query=(hash callback=(get (link "checkout") "url"))}}
+  @link={{link "signup" query=(hash callback=(link "checkout"))}}
>
  Please sign up first to continue
</Ui::Button>

[Bug] 3.24.2 - up to - 3.25.3: `href` empty from links in rendering tests

🐞 Describe the Bug

In a rendering test the link helper always return an emptyhref.

🔬 Minimal Reproduction

  test('href test', async function (assert) {
    assert.expect(1);
    await render(hbs`
      {{#let (link route="login") as |l|}}
        <a href={{l.url}}>Login</a>
      {{/let}}
    `);
    assert.dom('a').hasAttribute('href', `/login`);
  });

Screen Shot 2021-03-08 at 9 29 56 AM

😕 Actual Behavior

Element a has an empty attribute "href".

🤔 Expected Behavior

Element a has attribute "href" with value "/login"

🌍 Environment

  • Ember: 3.25.3

➕ Additional Context

emberjs/ember.js#19408

`getLinkParamsFromRouteInfo` does not collect models for the entire `RouteInfo` tree

At the moment, only paramNames on the leaf RouteInfo instance is used here:
https://github.com/buschtoens/ember-link/blob/main/ember-link/src/services/link-manager.ts#L100

But I think we need to traverse up the entire tree (using the parent property) to collect all models?

Otherwise, Ember's router throws an error when creating links using the fromURL option, and providing a URL with e.g. multiple dynamic segments.

Support URLs as input parameter

It would be great, if the {{link}} helper could support URLs that are recognizable by the router.

This would make {{link}} a more usable primitive in scenarios where a "serialized" URL has to be passed, e.g. as a callback query parameter.

{{! origin route }}
<Ui::Button
  @link={{link "signup" query=(hash callback=(get (link "checkout") "url"))}}
>
  Please sign up first to continue
</Ui::Button>
{{! "signup" target route }}
<Ui::Button
  @link={{link fromURL=callback}}
>
  Finish registration
</Ui::Button>

`setupLink()` is broken for engines

As we figured, we weren't able to use setupLink() from within engines. @buschtoens did a run-down of this (credits to him, I'm just writing it down):

We are hit by this: emberjs/ember.js#11173 (comment)

assert(
'ember-link.setupLink: You have already called `setupLink` once',
!this.owner.hasRegistration('service:link-manager') ||
!(
this.owner.lookup('service:link-manager') instanceof
TestInstrumentedLinkManagerService
)
);

places the normal LinkManagerService in the container.cache and container.factoryManagerCache.
Then the following owner.unregister(…) apparently only removes the registration, but not the instance in the container.cache.

There fix was added to ApplicationInstance, but the owner in our test is not a fully-fledged ApplicationInstance: https://github.com/emberjs/ember.js/pull/12680/files

This seems to be an upstream bug in @ember/test-helpers, here is the trace:

We don’t have an application in render tests, but a resolver, so we hit L57 in
https://github.com/emberjs/ember-test-helpers/blob/a4f710420e8dfba1a4245981483d7cbb5dcc865e/addon-test-support/%40ember/test-helpers/build-owner.ts#L41-L60

Which then builds a registry and owner on its own terms.
https://github.com/emberjs/ember-test-helpers/blob/a4f710420e8dfba1a4245981483d7cbb5dcc865e/addon-test-support/@ember/test-helpers/-internal/build-registry.ts

This of course lacks the unregister patch:
https://github.com/emberjs/ember.js/blob/0da7fedd3767668e7fb01b50757f1a636782fb54/packages/%40ember/engine/instance.ts#L162-L167

Cannot be used in integration tests (`setupRenderingTest`)

The <LinkTo> component is backed by the private -routing service, which has explicit short-circuit logic to detect when it is being run in an integration / render test.

When you use <Link> in a render test, you currently get this error:

Uncaught (in promise) TypeError: Cannot read property 'generate' of undefined
    at Class.generate (router.js:479)
    at RouterService.urlFor (router.js:200)
    at LinkComponent.get href [as href] (component.js:78)
    at getPossibleMandatoryProxyValue (metal.js:2532)
    at get (metal.js:2612)
    at glimmer.js:507
    at track (metal.js:2498)
    at RootPropertyReference.compute (glimmer.js:506)
    at RootPropertyReference.value (glimmer.js:374)
    at ComponentElementOperations.flush (runtime.js:1584)

This is because RouterService#urlFor delegates to the system's Router#generate method, which expects _routerMicrolib to be initialized, which it is only in application / acceptance tests.

/cc @Charizard

Including a loading substate seems to break isActive

Hi, thank you for writing this addon.

It seems great and exactly what I need to be able to do conditional classnames like this example which is really handy for functional CSS, like tailwind.

I've encountered what seems like a bug to me, though, when I include a loading substate.

Assuming I have a simple nested route like this...

// routes
stations.js
stations
│   station.js

// templates
stations.hbs
stations
 │   station.hbs

And in my stations.hbs template I set an active link like this:

<Link @route="stations.station" @model={{station.id}} as |l|>
  <a
    href={{l.href}}
    class="{{if l.isActive "active-class"}}"
    {{on "click" l.transitionTo}}
  >
    {{station.name}}
  </a>
</Link>

Then the active-class is displayed as expected.

But, if I introduce a loading substate like this...

// routes
stations.js
stations
│   station.js

// templates
stations.hbs
stations
 │   loading.hbs
 │   station.hbs

Then the active-class is not displayed once the station route has resolved.

Worth noting that this is, I think, due to Ember Data Storefront as I've noticed that if I use findAll and findRecord methods in the models of the routes, instead of Ember Data Storefront's loadRecords and loadRecord, then it does still work.

But on the other hand using the <LinkTo> component still works as expected whichever approach for loading the model data is used.

<LinkTo @route="stations.station" @model={{station.id}} class="">
  {{station.name}}
</LinkTo>

For the moment I'm going to stick with and some non-functional CSS but if this is something you consider a bug then I'd be happy to try to help resolve it.

Thanks.

Encode Behavior in Link Primitive + Building Blocks

The Problem

ember-link provides a primitive to pass around. It's a bit of fuzzy what it is exactly. It can be seen as just a DTO and on the other hand provides operations how to deal with the link itself (such as transitionTo() and replaceWith() methods).

Using them together is fine, when the owner of the primitive is also in control of how a link get's attached to the UI. This no longer works when the two split (ie. when the primitive is passed around).

A Solution

In order to make it work, this needs handling on two sides of the problem:

  1. The owner needs to express his wished behavior on the primitive itself
  2. On the attaching sides, it needs one interface, the implementor will use - and behind that interface the expressed behavior from (1) is executed.

A sample for (1) - making up some possibly imaginery code (as I don't have the full and correct API in my head):

this.linkManager.createUILink({ route: 'target-route', behavior: { replaceWith: true });
this.linkManager.createUILink({ url: 'https://example.com', behavior: { openInNewWindow: true });

Onto the second part of the solution (2). Let's say, this we received our link in a component as @link argument:

<a href={{@link.url}} {{on "click" @link.open}} {{link-attributes @link}}>Click me</a>

There are two additions here:

  • @link.open - which would be used to either call transitionTo() or replaceWith() on the link
  • {{link-attributes}} - which would put all attributes onto the element as needed (e.g. target="_blank" to open in a new window)

This is partially the idea from #333 - yet a little bit more exhaustive. First, it is good to have these multiple pieces as building blocks (see below on thiird party implementors). There maybe can also be just one modifier that handles all of the above @link related markup in one place.
As implementor, using that interface would give me the chill, that I attached the link properly to my markup.

For third party implementors, the exhaustive building blocks might be more suitable. See here: https://github.com/gossi/ember-command/blob/9101687488c9c20e4c7e9510632f44c38cd88a1f/addon/components/commander/index.hbs

Enforce new tab (`target="_blank"`) via `UILinkParams`

Relates to #6 (support for Cmd / Ctrl click).

Ideas:

  • (link "foo" newTab=true)
  • (link "foo" target="_blank"), support for generic targets?
  • It would be nice, if target could be applied to the <a>.
    • A modifier for jointly applying all attributes and click listener?
    • target property on Link / UILink?
    • Should the click handler respect the target attribute of the <a> tag?
  • Should we allow the "call site" to opt-out?
    • We could use transitionTo / replaceWith as an explicit opt-out.
    • Add a more generic navigate.
    • Relation to #290 (external links)?

Types are wrong - break with TS v4

When running a project with typescript v4 I'm getting these errors:

... is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.
Click here
../../node_modules/ember-link/test-support/test-link.d.ts:4:5 - error TS2610: 'isActive' is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.

4     isActive: boolean;
      ~~~~~~~~

../../node_modules/ember-link/test-support/test-link.d.ts:5:5 - error TS2610: 'isActiveWithoutQueryParams' is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.

5     isActiveWithoutQueryParams: boolean;
      ~~~~~~~~~~~~~~~~~~~~~~~~~~

../../node_modules/ember-link/test-support/test-link.d.ts:6:5 - error TS2610: 'isActiveWithoutModels' is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.

6     isActiveWithoutModels: boolean;
      ~~~~~~~~~~~~~~~~~~~~~

../../node_modules/ember-link/test-support/test-link.d.ts:7:5 - error TS2610: 'isEntering' is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.

7     isEntering: boolean;
      ~~~~~~~~~~

../../node_modules/ember-link/test-support/test-link.d.ts:8:5 - error TS2610: 'isExiting' is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.

8     isExiting: boolean;
      ~~~~~~~~~

../../node_modules/ember-link/test-support/test-link.d.ts:9:5 - error TS2610: 'url' is defined as an accessor in class 'Link', but is overridden here in 'TestLink' as an instance property.

9     url: string;
      ~~~

Checking https://www.npmjs.com/package/ember-link?activeTab=explore the generated typescript definition file is not compatible with TS v4: test-support/test-support/test-link.d.ts

It should actually be like this:

import Transition from '@ember/routing/-private/transition';
import { Link } from 'ember-link';

export default class TestLink extends Link {
    get isActive(): boolean;
    get isActiveWithoutQueryParams(): boolean;
    get isActiveWithoutModels(): boolean;
    get isEntering(): boolean;
    get isExiting(): boolean;
    get url(): string;
    onTransitionTo?(): void;
    onReplaceWith?(): void;
    get qualifiedRouteName(): string;
    transitionTo(event?: Event): Transition;
    replaceWith(event?: Event): Transition;
    private _preventTransitionOut;
    private _createDummyTransition;
}

Can we pls either patch the test-link.d.ts and publish new version or upgrade typescript to v4?

Roadmap to v3

ember-link is becoming more and more problematic in terms of types (it affects the builds of packages using this one) and also writing tests in v2 addons is becoming problematic. I see a weird instanceof Link problem, which turns false, even though both are instances of links (I don't know more).

Here is an idea for the roadmap:

Version 2: Deprecations

  • Deprecate <Link> component in favor of (link) helper
  • Deprecate UILink interface

Version 3

  • Migrate into v2 addon
  • Glint support with template-registry and signatures (should fix all typing problems) #772
  • Add Behavior to Link in order to remove UILink #770
  • Remove UILink in favor of only Link (and TestLink) #771
  • Export all relevant things from index.ts
  • Remove <Link> component #747
  • Documentation

Link modifier?

I was wondering if a modifier for situations like this would be a good idea:

<MyComponentWhereIDontKnowTheElementFor {{link ...}}/>

the {{link}} modifier would turn the element into an <a> tag and apply its values. Passing around the link helper as primitive would still work as the modifier would take the helper as valid input although it really looks stupid: {{link (link ...)}} 😂

The idea: With only the helper each component need to be made aware of a link passed in as helper and then handle the logic of applying it or pass it down. The modifier eliminates this component-specific-handling and applies it to where it is attached, some examples:

<Button>foo</Button> 
-> <button class="_button1234">foo</button>

<Button {{link ...}}>foo</Button> 
-> <a href="..." class="_button1234">foo</a>

<Card>bar</Card> 
-> <div class="_card1744">bar</div>

<Card {{link ...}}>bar</Card> 
-> <a href="..." class="_card1744">bar</a>

WDYT?

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.