Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View hooks for native and non-jQuery libraries #3003

Merged
merged 92 commits into from
Mar 16, 2014

Conversation

akre54
Copy link
Collaborator

@akre54 akre54 commented Feb 18, 2014

This is a continuation of the discussion in #2959, and focuses on making View easier to extend by users wishing to use non-jQuery libraries. It's similar in plugability to Backbone.ajax or other overridable parts of Backbone.

Native Views and non-jQuery DOM libraries would be able to create a native View implementation by overriding $, make, remove, setElement, delegate, and undelegateEvents (see the NativeView reference implementation). This preserves the majority of the public contract of View (with the notable exception of View#$ and view.$el), and the public interface of the constructor (including passing options), delegateEvents/undelegateEvents, render, remove, and setElement would be virtually unchanged. The PR also re-adds View#make.

The next step would be to remove Backbone.$() calls from History and perhaps extend View#setElement and View#make to use a common exposed createElement utility.


Update for folks tuning in late: The final list of overridable methods is available on the wiki with instructions for how to use it in your own project.

@jashkenas
Copy link
Owner

Do the other interested parties agree that these hooks are the minimal surface area required to implement a NativeView in a fashion that's optimally performant?

@jdalton
Copy link
Contributor

jdalton commented Feb 18, 2014

👍
You could cleanup the addEventListener by removing useCapture and providing false for its arg.
Also you might be able to cleanup the iframe creation with make.

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

@akre54 I like how you just copied 99% of my PR and stamped your own name on the copyright there :)

The event listener shims are slower because of the extra function calls and branching. useCapture also useless. Best keep the highly optimized event listener shims implemented in #2959. Not sure about using make for iframe creation because that brings back the whole Backbone.utils thing.

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

Also, there needs to be a View#_removeElement or something, or you'll forget to call undelegateEvents or stopListening like @akre54 reference implementation.

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

And if you make setElement and _ensureElement make use of make, you only have to worry about make.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

Yeah its just boilerplate from one of my other plugins. I credited you in
the readme and in the changeset since I couldn't chery-pick your commit.

The more important thing is if this PR (and the native view plugin) works
for you. It seems it should. Whose name appears on it I could really care
less.

The shims are only used in History, which shouldnt be a hot code path. In
my eyes the delegate method is the right solution here.
On Feb 19, 2014 12:15 AM, "Jimmy Yuen Ho Wong" notifications@github.com
wrote:

@akre54 https://github.com/akre54 I like how you just copied 99% my PR
and stamped your own name on the copyright there :)

The event listener shims are slow because of the extra function calls and
branching. useCapture also useless. Best keep the highly optimized event
listener shims implemented in #2959#2959.
Not sure about using make for iframe creation because that brings back
the whole Backbone.utils thing.

Reply to this email directly or view it on GitHubhttps://github.com//pull/3003#issuecomment-35466977
.

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

That's true if you've decided to not bank on the shims to pull out a native BaseView.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

you might be able to cleanup the iframe creation with make.

I thought about this, but being able to set html in a clean, cross-browser way in make is important enough to use jQuery there, and for History we're trying to avoid jQuery altogether. I think it is even a little cleaner than adding another helper method, too.

@@ -1448,7 +1472,11 @@
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function() {
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
if (this._hasPushState) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a reason I appended && window.onpopstate here. This broke IE<=7

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

Mmmm, $el strikes again. Doesn't look like there's a way to use make inside setElement and not break things huh? If not then make is kinda pointless.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

Eh? setElement is one of the methods that inheriting views should override. make's contract is to return an Element, not a jquery-wrapped element.

As I said in #2959, plugins should rely on el unless they absolutely must look to $el. This would be no different with a BaseView approach.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

@jdalton whoah, didn't know you could pass html directly into createElement. Awesome.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

We'd still need the IE sniff for the off hashChange, right?

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

In chrome at least I'm getting an error: invalid characters

@jdalton
Copy link
Contributor

jdalton commented Feb 19, 2014

@akre54

We'd still need the IE sniff for the off hashChange, right?

Ya, I removed the UA sniff not the this._oldIE prop.

In chrome at least I'm getting an error: invalid characters

That's why it's wrapped in a try-catch. That syntax is only supported in IE < 9.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

Yup, got it. Neat little old feature.


// Cross-platform `addEventListener`.
var addEventListener = function(obj, eventName, listener) {
if (obj.addEventListener) return obj.addEventListener(eventName, listener, false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for return use in addEventListener and removeEventListener helpers or use them and drop the else

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

@akre54 @jdalton Can you guys just clean up History and leave View alone for now? think we can all agree on removing jQuery from History.

I'm gonna remove all the shims and changes in History in #2959 and experiment a little with BaseView to iron out a minimal set of hooks to expose that doesn't break things.

@wyuenho
Copy link
Contributor

wyuenho commented Feb 19, 2014

Also, @akre54 If you don't use make internally? Why introduce/advertise it as a hook?

if (!this.options.silent) return this.loadUrl();
},

// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function() {
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
// Cross-platform `removeEventListener`.
var removeEventListener = function(obj, eventName, listener) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as @jdalton said, but here's an even better optimization:

var removeEventListener = window.removeEventListener ||
  function (eventName, listener) {
    return detachEvent('on' + eventName, listener);
  };

removeEventListener('popstate', this.checkUrl, false);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll still want to pass the false for the third argument to addEventListener and removeEventListener as it was not optional Firefox < 6.

No need for .call(window); you can also change this.detachEvent to be detachEvent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snippet fixed.

@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

@akre54 @jdalton Can you guys just clean up History and leave View alone for now? I can think we can all agree on removing jQuery from History.

I can open another pull, but I think these are part and parcel to the same concept. Might as well merge 'em together or leave them both out.

Also, @akre54 If you don't use make internally? Why introduce/advertise it as a hook?

We do, in _ensureElement. The reason for breaking out make is to allow _ensureElement to remain untouched in a Native View, and only have to override make. I was thinking of doing something similar with setElement, but I think that's integrated enough that just overriding that line makes more sense here.

#2959 takes the opposite tack with View as this one. Are there any speed problems with this strategy in your eyes?

@akre54 akre54 mentioned this pull request Feb 19, 2014
@akre54
Copy link
Collaborator Author

akre54 commented Feb 19, 2014

@wyuenho moving History hooks to a new PR, and turning this one into just View.

@braddunbar
Copy link
Collaborator

Some minor nits addressed in akre54#19.

braddunbar and others added 4 commits March 14, 2014 11:56
* Rename #_remove as #_removeElement.
* Add #_setAttributes instead of using #setElement.
* Slim down tests a bit.
* #delegate returns this.
Add #_setAttributes and clean up #setElement.
@braddunbar
Copy link
Collaborator

Alright, this is looking good. The final consensus is view subclasses that do not use an API conforming to Backbone.$ will have to override six methods which cover all DOM operations in Backbone.View.

  • _removeElement
  • delegate
  • undelegate
  • undelegateEvents
  • _setElement
  • _setAttributes

@jashkenas Note the addition of _setAttributes. I think a specific hook for setting attributes is much safer (for future API additions/changes) than passing the argument through two unrelated methods with a mix of public and internal arguments.

undelegate is also new. It mirrors the delegate API nicely and maps directly to jQuery#off.

Many thanks to @akre54, @wyuenho, and others for their patience with me, I know we covered a lot of ground here. 😃 👍

@nhunzaker
Copy link
Contributor

Huge 👍 for this work. It will be really exciting to see how this influences Backbone frameworks.

@eastridge
Copy link
Contributor

@braddunbar @akre54 @wyuenho

Thanks for all of your work on this. Already have immediate plans for it as soon as it is ready:

https://github.com/FormidableLabs/handlebones

jashkenas added a commit that referenced this pull request Mar 16, 2014
View hooks for native and non-jQuery libraries
@jashkenas jashkenas merged commit 3994c1c into jashkenas:master Mar 16, 2014
@jashkenas
Copy link
Owner

Anyone feel like adding a page to the wiki for library developers explaining how to take advantage of the full implications of this patch?

@wyuenho
Copy link
Contributor

wyuenho commented Mar 17, 2014

Heads off to @paulmillr for the initial PR too. Thanks everyone for helping out with this!

@jashkenas Since we'll have to document the new APIs anyway, shouldn't we add a section to the FAQ instead?

@stavarotti
Copy link

This has been a fantastic pr to follow. Thanks to all participants striving to make this project better.

@akre54
Copy link
Collaborator Author

akre54 commented Mar 17, 2014

Yeah I can write something up for the wiki or docs. Nice job everybody. This was one crazy long PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.