-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
mixins? or provide a semantically meaningful API to execute behavior methods within a view? #1491
Comments
Thanks for bringing this up @derickbailey. Without knowing too much about the problem you're trying solve, I'd suggest a loadable behavior that responds to events like We've been able to use this event decoupling to build out nice UIs like forms where a behavior listens to form ui events like With that said, there are cases where a shared layout is a nice pattern or another type of delegation like composition makes sense. Can you provide more details on your example use case? I'd like to have a better idea of what you're trying to build. One more thought, when I look at your behavior Correct me if I'm putting words in your mouth, but I see you trying to encapsulate the idea of showing a loader, when with a behavior the semantics would be closer to handling events around loading. The distinction is subtle, but with show loading you're sharing a common implementation for showing and hiding loaders so it makes sense to mixin |
yes, this. what i really want is a mixin... but i don't know a way to do this. so i'm trying to abuse behaviors, and it's breaking all the important semantics. "it works" - but not in a way that I'm happy with. have any other suggestions for making this work, not using behaviors? |
This might be a bust, but one suggestion would be to "import" a single function or module as a utility. Here's an example w/ require, if you're not using a module system this could feel ugly. require([], function(Marionette, showLoader) {
return Marionette.View.extend({
ShowLoader: {
message: "loading ..."
}
showLoader: showLoader
}); require([], function(Marionette, loaderUtil) {
return Marionette.View.extend({
ShowLoader: {
message: "loading ..."
}
showLoader: loaderUtil.showLoader,
hideLoader: loaderUtil.hideLoader
}); This is a simple "mixin" strategy for just importing functionality. It's nice because it's verbose. The drawback in my mind is that we don't have a separation of state. I could see supporting a pattern that encapsulates some of the benefits of composition, which would look like this if everything were written out, but could be dried up. Marionette.View.extend({
ShowLoader: {
message: "loading ..."
},
initialize: function() {
this.loader = new Loader(this);
}
showLoader: function() {return this.loader.showLoader()},
hideLoader: function() {return this.loader.hideLoader()},
}); |
Why not use Backbone.Advice? |
@STAH, I think functional mixins are a very appealing solution. I was not aware of Backbone.Advice. Very cool! I've been meaning to give Flight's mixin library a go for it recently: lib/advice.js api. @samccone and I were talking to Ben Vinegar of Disqus at jsConf a couple weeks ago about how they use Flight's mixins for a similar use case. I'd like to see a couple examples of Functional Mixins in the wild to inform my opinion. Maybe a good starting off point would be to add a couple of examples in the cookbook under an expirmental mixin folder. There are a couple patterns in there for overlays and hotkeys, which could be used as a jumping off point. |
I'll provide some examples from my production code :) |
I'm right smack in the middle of this need, so I'll try out a few of these soon and report back. |
If you're looking for something lightweight, I just put this together for my company's applications today: |
Great! |
Just one example:
|
Hey, @STAH - thanks for sharing those two patterns. They help highlight some of the distinctions for me between behaviors event forwarding, mixin object extending, and functional mixin wrapping. I just put together an opinioneted refactor of the patterns you shared, with what seems to me to be a cleaner version. Notice that I'm just doing three things:
These changes, might well be bad. It's my first take at wrapping my mind around how this might look similar to other things that I'm used to looking at :) ValidableViewMixin = {
setupMixin: {
'after': 'initialize',
'after': 'onClose'
},
initialize: function () {
Backbone.Validation.bind(this);
},
onClose: function () {
Backbone.Validation.unbind(this);
}
});
SaveCancelViewMixin = {
setupMixin: {
'addToObj': 'events',
'setDefaults': 'onSave',
'setDefaults': 'onCancel'
},
events: {
'click [data-action=cancel]': 'onCancel',
'click [data-action=save]': 'onSave'
},
onSave: function (e) {
e.preventDefault();
e.stopPropagation();
this.model.validate();
if (this.model.isValid()) {
app.main.execute(mixOptions.action, this.model);
}
},
onCancel: function (e) {
e.preventDefault();
e.stopPropagation();
app.main.back();
}
});
MyView = Marionette.ItemView.extend({
mixins: {
ValidableViewMixin: {},
SaveCancelViewMixin: {
action: 'myModel:save'
}
}
}); |
It's sooo.... perfect ;) |
But to make things simple, "mixins" block should be placed outside of MyView declaration (like in my example). |
@STAH right. I kinda like it - especially now that I have a better idea of the advice api :) re: moving the mixins block outside of the declaration. View.extend({
// configuration
template: {},
events: {},
behaviors: {},
// callbacks
initialize: function(){},
onX: function() {},
// private methods
_foo: function() {}, Mixins could be an exceptions to the rule here. One reason is that this will probably have to happen at runtime to modify the instance the way we setup behaviors as opposed to modifying the prototype. Curious what you think? Just added two examples to the cookbook. Both examples port the Dropdown Behavior, the first is the standard Advice api, the second is the sugared version with a small hack to make it work. Feel free to git clone the cookbook, |
@jasonLaster so, possible interface: MyView = Marionette.ItemView.extend({
mixins: [
ValidableViewMixin,
{
mixinClass: SaveCancelViewMixin,
options: {
action: 'myModel:save'
}
}
]
}); |
At this point it sounds like we're no longer describing a "mixin". Typically, a mixin is simply a way of defining multiple-inheritance or including methods from another class. The easiest way to do this in Backbone is simply: _.extend(FooClass.prototype, BarMixin); Which is something frequently seen in Backbone applications, eg. If we're talking about "mixins", then there should be no options passed to it, and it shouldn't be implementing anything from Advice.js ( MyView = Marionette.ItemView.extend({
mixins: [
ValidableViewMixin,
SaveCancelViewMixin
]
});
// or to avoid modifying extend:
MyView = Marionette.ItemView.extend({
// ...
});
MyView.mixin(ValidateViewMixin);
MyView.mixin(SaveCancelViewMixin); If you are okay using the second (I prefer it), then you can use it today (along with If we're talking about something else that exists in Advice.js or a similar library, I think that lies outside the scope of Marionette and you should just include one of those libraries. |
@thejameskyle well written. I also prefer the second option. |
@stephanebachelier Thanks I think the important thing to note here (since this is why it came up) is: Behaviors Are Not Mixins™ Marionette.ItemView.extend({
behaviors: { foo: { behaviorClass: Foo } }
}); Behaviors are an isolated set of interactions added to a class that are interfaced via Events. The parent class cannot and shouldn't be able to directly interface with its Behaviors. Doing so would break the paradigm. Why: To encapsulate logic in an evented system _.extend(MyView.prototype, {
// mixing in ...
}); Mixins are a set of methods that can be arbitrarily added to any Object. A mixin can directly interface with its host object, and a host object can directly interface with a mixin. In fact, most mixins will overwrite the objects methods. Mixins can also sometimes contain state. Why: To expose methods/state on any object var MyView = Backbone.View.extend({
// subclassing ...
}); Subclasses are classes that are extended via prototypal inheritance. They setup a prototype chain and allow you to reference parent classes via Why: To abstract functionality into a hierarchy |
@cmaher - I can see how order could bite you, but I wonder if we could encourage patterns where functional mixins were built to be order agnostic. The big advantage I see here, is that it would be one less thing the end user has to think about when adding her mixins to the class. @thejameskyle yup - "Behaviors are Not Mixins". To build on what you're saying, we're really talking about delegation strategies:
It's not clear how much Marionette core will support these types of delegation patterns. At the very least, it'd be nice to document them in the cookbook and guides with some best practices and tips for when each is appropriate. We might also be able to support some small Marionette plugins like what you started working on the other day at work and shared with the jsfiddle. @derickbailey - how is your thinking evolving? |
some of our prod code:
then usage at the bottom of a view:
containing:
|
makes sense. I can see how the namespaces work well |
|
I don't see us adding a formalized Mixin or I think we could close this and add better content around delegation such as guide, cookbook recipe, or blog post. |
👍 I vote for a recipe using Backbone.Advice |
For anyone still in need. To answer the initial question: behaviors:
theBehavior:
class: this in your methods:
mymethod: ->
initialize: ->
_.extend @options.class, @methods I also was in need of something more like a concern (like used in rubyonrails). I think it is up to the developer how it is used (e.g. for behavior only) |
I have a need for what I typically think of as a mixin. In this case, it's a simple "loading..." message with a spinner gif. typical stuff, nothing special. but i want a re-usable component for this - one that i can display within any other view that needs it, when it needs it.
I thought, "behaviors!" - but it doesn't work the way I hoped.
If I define a method on my behavior, I have no way of invoking that behavior directly from within my view.
I was hoping to be able to call the method directly. But as @samccone pointed out to me, this isn't possible. I understand that behaviors are sandboxed and that rocks. I like it. But now I want a semantically meaningful way of executing the showLoader function.
In a chat with Sam, he gave me some options - and I don't like any of them. They all break semantics, and/or tightly couple the developer's brain to the knowledge of how behaviors are implemented in order to use events / triggerMethod to make it work.
I'm picky about semantics. They are important. I want a semantically meaningful way to do this.
What I really want is a mixin - by name and by semantics. But I don't know of an easy way to make that happen in Marionette, right now, and behaviors were the closes thing. So I'm trying to abuse behaviors.
What's a better way to do this? Or is there a semantically meaningful API that can be added to behaviors, to allow a method to call behavior methods?
FWIW, here's the IRC log from my chat w/ Sam
The text was updated successfully, but these errors were encountered: