gdub
gdub

Reputation: 852

Extending Ember LinkView

Ember.LinkView, the the view class behind the handlebars {{linkTo}} helper is now public in Ember 1.0 RC2. I want to extend it so I can create a custom view without having an extra nested tag for linkTo.

For example:

App.MyLinkView = Ember.LinkView.extend({
    namedRoute: 'another'
});

then

{{#view App.MyLinkView}}LinkView to another route{{/view}}

Looked through the source a bit without much luck, as it constantly throws an error. Here's a jsfiddle with the standard {{linkTo}} working, and the LinkView attempt commented out so it doesn't throw an error.

http://jsfiddle.net/HgmEy/1/


Edit:

Here is a more realistic example of why you would want to do this: http://jsfiddle.net/HgmEy/3/

The desired functionality is working here using a regular view, however using a LinkView would be preferred to avoid the extra dom element.

Upvotes: 1

Views: 2029

Answers (2)

darvelo
darvelo

Reputation: 325

I needed to do this to override Ember.LinkView's call to transitionTo in order to come up with a solution for jQuery animations between transitions. It seems to me that there are a couple of viable ways to override LinkView. The second one I succeeded with is Trek's last option, and is simpler. This is method #2:

Method #2

{{#linkTo 'items' this eventName="myEvent"}} Link to {{title}} {{/linkTo}}

Now rewrite the app-wide LinkView:

Ember.LinkView.reopen({
  // this handler is still called on click, but
  // if we specify eventName in our template,
  // we can call that handler only when we need to,
  // or not at all
  click: function (e) {
    var evtName = this.get('eventName');

    // transitionTo was already invoked by
    // this._invoke() if evtName was `click`
    if (evtName === 'click') return;

    e.preventDefault();

    // do some stuff here

    var args = [].slice.call(arguments);
    this.trigger.apply(this, [evtName].concat(args));
  }
});

Method #1

The first method I came up with was to extend Ember.LinkView and create a custom Handlebars helper. The Ember source was really handy here for reading, but I had to override a private method, so I don't think this is really ideal. Here's the implementation. Keep in mind I was trying to control when the View triggered a transitionTo:

{{#appLinkTo 'items' this}} Link to {{title}} {{/appLinkTo}}

Now code it up!

App.LinkView = Ember.LinkView.extend({
  // always called after this.invoke(),
  // which calls transitionTo
  click: function (e) {
    e.preventDefault();
  },

  // already bound to the click event by this.init().
  // our click handler above always gets called after this one
  _invoke: function (event) {
    // we need to simulate the old _invoke if we
    // want to override its call to transitionTo
    // 
    // https://github.com/emberjs/ember.js/blob/v1.0.0/packages/ember-routing/lib/helpers/link_to.js#L297
    var isSimpleClick = Ember.ViewUtils.isSimpleClick;
    if (!isSimpleClick(event)) { return true; }
    event.preventDefault();
    if (this.bubbles === false) { event.stopPropagation(); }
    if (this.get('_isDisabled')) { return false; }

    if (this.get('loading')) {
      Ember.Logger.warn("This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.");
      return false;
    }

    // now we can start messing around
    var routeArgs = this.get('routeArgs');
    // routeArgs seems to have format ['routeName', models for dynamic segments]
    this.set('routeArgs', ['group', routeArgs[1]]);

    // if we use:
    this.get('controller').send('someAction', routeArgs);

    // the controller can do in its `someAction` handler:
    // `this.transitionToRoute.apply(this, routeArgs);`
  }
});

// besides the naming, this is verbatim from the end of:
// https://github.com/emberjs/ember.js/blob/v1.0.0/packages/ember-routing/lib/helpers/link_to.js
Ember.Handlebars.registerHelper('app-link-to', function(name) {
    var options = [].slice.call(arguments, -1)[0],
        params = [].slice.call(arguments, 0, -1),
        hash = options.hash;

    hash.disabledBinding = hash.disabledWhen;
    hash.parameters = {
      context: this,
      options: options,
      params: params
    };

    return Ember.Handlebars.helpers.view.call(this, App.LinkView, options);
});

Ember.Handlebars.registerHelper('appLinkTo', Ember.Handlebars.helpers['app-link-to']);

Method #3

If you want the best of both, you could combine both methods and extend Ember.LinkView, create a custom Handlebars helper, and use custom event names to signify which actions you want to take. That way, overriding Ember.LinkView, and overwriting _invoke aren't necessary.

Good luck!

Upvotes: 2

Trek Glowacki
Trek Glowacki

Reputation: 3621

LinkView is intended to be created via a helper, which passes (and provides default values for) some options.

Your error occurs when trying to determine whether your custom class is active or not. You'll need to do one of the following

  • pass or supply the expected default options when using your App.MyLinkView
  • override the active function and implement what you need
  • just pass options to {{linkTo}} for the behavior you want
  • reopen Ember.LinkView to provide the app-wide behavior you'd want

Upvotes: 2

Related Questions