Behavior implementation question

12 views
Skip to first unread message

Rolf Langenhuijzen

unread,
Oct 9, 2011, 7:18:50 AM10/9/11
to MooTools Users
(Hi Aaron,)

Apologies for this non-focused subject, but I couldn't think of a
shorter to the point one..

Here's my case slightly modified/simplyfied:
1. I have a simple list with items. These items are just text, like
names of people. I also add a data-image="my-image.jpg" to the list
element.
2. With a class I turn this into a grid view and it fires an
onComplete. In the onComplete I trigger a pagination class that turns
the grid in X pages in a smaller viewport and some controls.
3. I use the onComplete from the paginate class to trigger a final
class (preview) that enriches the grid with images using the data-
attribute.

Let's cut out 2 for now (the pagination part).

When I use Behavior to create the grid. How should I use the complete
event to trigger the 'preview' class for the elements in the grid?
The preview class is dependent on the grid.. if there's no grid, there
are no images.

Rolf

Eric Patrick

unread,
Oct 9, 2011, 5:23:00 PM10/9/11
to MooTools Users
I'm not entirely sure this addresses your question, but it sounds
similar to an issue Aaron assisted me with.

I will assume that you have:

- a Class 'GridView'
- a Class 'GridPreview'
- a Behavior 'Grid'
- a Behavior 'Preview'

The theory is to use the Behavior API as an event arbitrator between
your GridView and GridPreview classes, like this:

Behavior.addGlobalFilter('Grid', function(el, api) {
// create your GridView class
var myGridView = ...
myGridView.addEvent('complete', function() {
api.fireEvent('gridRendered', {}); // I'm passing empty args, but
you may need to pass something substantial
})
});

Behavior.addGlobalFilter('Preview', function(el, api) {
// create your GridPreview class
var myGridPreview = ...
api.addEvent('gridRendered', function(args) {
myGridPreview.preview(args)
});
});

The pattern above has worked well for me working with classes that
deal with fetching server-side data and a pagination class.

Regards,

Eric

Aaron Newton

unread,
Oct 9, 2011, 9:13:32 PM10/9/11
to mootool...@googlegroups.com
Eric has the general idea correct here. The important rule to never violate is to ensure that no filter depends on another. If you DO have a dependency - where one filter makes no sense without another either create a new filter (that creates instances of both your classes) or a filter plugin.

When to choose one over the other? The event arbitration method is cleaner in that other filters that you or someone else writes later can implement it (instead of writing a plugin for every single new thing). But sometimes you have something truly esoteric that makes zero sense to use arbitration.

Plugins are mostly intended for creating specialized implementations for your environment. For example, let's say you want to use the Form.Validator plugin but you want to add some additional logic in your app. So you write a plugin for your app's implementation of that filter.

Anyway, back to your actual use case. Here's what I'd write:

1) I'd not put the image information as a filter property but rather actually put the images inline. Part of Behavior's impetus is to have regular HTML documents that work without the JavaScript. So I'd have my list with images and then style them in CSS. Hide the text and put them in a grid or whatever. I'd put that grid in an overflown element so that the user can scroll through them.

2) I'd write a class that turns overflown elements into a paginated set. You could configure it with explicit dimensions or have it read the sizes of the elements and guess how to paginate. It then wraps up your content and puts the page controls in place.

This way it degrades well and the HTML isn't overly complicated. It's a list with some images. If you turn off the pagination, it still looks great.

Rolf Langenhuijzen

unread,
Oct 10, 2011, 6:16:39 AM10/10/11
to MooTools Users
Thanks for the responses! Didn't know it was as easy as demonstated
with the event arbitration method. I'll try and see how it works.

Some specific response to Aaron's thoughts:

1) I don't have images inline because in this case it wouldn't work,
plus it could be too many images to load (or create dynamically). I
simplified the case description. The actual grid is created with
options that can be set per element (eg. a colspan:2 rowspan:2
setting).

2) The pagination class takes a default "max-rows-to-show" option into
consideration and hides other rows. But, depending if an element spans
more rows that what is visible by default it will have to expand the
viewport, which in turn affects the page controls :) - I'm working on
that with some prototype hack 'n slash code.

3) Depending on what is visible I would trigger the preview class and
it would replace the content of grid elements with images (and in my
case it should load some webfonts). Using Class.Occlude this is done
only once per element.

All classes could be used on their own (though atm the paginate class
is a bit bound to how the grid is created, but that's because i'm
prototyping)

If there's no javascript you still have a list with all items/links -
no problemo.

Aaron Newton

unread,
Oct 10, 2011, 11:07:42 AM10/10/11
to mootool...@googlegroups.com
1) I don't have images inline because in this case it wouldn't work,
plus it could be too many images to load (or create dynamically). I
simplified the case description. The actual grid is created with
options that can be set per element (eg. a colspan:2 rowspan:2
setting).

That's cool. My point mainly is that I often find myself wanting to make things work with JavaScript that browsers are actually really good at. Browsers are pretty awesome at dealing with a lot of images, for example. Whenever you can solve the problem with HTML, do it. Whenever you can solve it with HTML + CSS, do it. When you can't, there's JavaScript.

One thing you might also consider is spending a little time with Benchmark.js It'll tell you what your application really costs. If you are using Behavior, you get benchmarks and unit tests almost for free. Look at the tests in More-Behaviors or in Bootstrap. To run them, check out https://github.com/anutron/mootools-development/tree/clientcide (quite specifically the clientcide configuration).
 
2) The pagination class takes a default "max-rows-to-show" option into
consideration and hides other rows. But, depending if an element spans
more rows that what is visible by default it will have to expand the
viewport, which in turn affects the page controls :) - I'm working on
that with some prototype hack 'n slash code.

Optimization suggestion: if you have a lot of rows, hide them all and have your code show the first batch rather than the other way around.
 
3) Depending on what is visible I would trigger the preview class and
it would replace the content of grid elements with images (and in my
case it should load some webfonts). Using Class.Occlude this is done
only once per element.

Behavior does this for you. When you do myBehavior.apply(element) it applies to that element and its children, but if you did it again, it wouldn't invoke the filters again. It would do the DOM search for data-behavior elements again, loop through them, and see that they've already been set up. If you did myBehavior.cleanup(element) and THEN called apply again, it would invoke the filters again, but otherwise not.

Also, you can greatly increase your startup time for filters that only have actions when you mouseover them or click them or whatever by using the delayUntil option. Tips, for example, need not be instantiated if the user never mouses over them.
 
All classes could be used on their own (though atm the paginate class
is a bit bound to how the grid is created, but that's because i'm
prototyping)

If there's no javascript you still have a list with all items/links -
no problemo.

Nice. 

Rolf Langenhuijzen

unread,
Oct 10, 2011, 7:48:58 PM10/10/11
to MooTools Users
I follow your thoughts.. totally agree. Especially with css +
transitions + pseudo elements a lot can be done right.
Have to check benchmark.js!

With regard to the Behavior event arbitration method...I'm not getting
it.. or at least, not for my current case:

Behavior.addGlobalFilter('Grid', function(element, api){
var grid = new Grid(element, ...);
grid.addEvent('complete', function(){ api.fireEvent('gridRendered',
element); });
grid.make();
return grid;
});

Behavior.addGlobalFilter('Preview', function(element, api){
var preview = new Preview(element, ...);
api.addEvent('gridRendered', function(container){
console.log('gridRendered', container);
container.getElements('some-selector').preview();
});
return preview;
});

1)
The "gridRendered" event is not picked up (in Preview filter) when
there is no element in the dom with the data-behavior="Preview"
attribute. (when the filter hasn't run)
In assumed the filter Preview was registered and globally
"listening" (to catch the fired event) even if the Preview filter has
not been actively used yet (by some already declared element in the
dom).
So, to test it I added a dummy div to the dom (not associated to the
list that is turned into the grid) with the data-behavior="Preview"
attribute, which is silly of course...
What's wrong with my thinking process?

2)
The Preview class (and thus the Behavior filter) is setup for one 1
element basically. If I add data-behavior="Preview" to an element the
filter is called and the class is instantiated.
But in the code above I abuse the "gridRendered" event to call a
preview() method on an array of elements I select with a css selector
on the grid container (basically: all childs except empty cells).
(the preview method call does: return new Preview(this, options) -
pretty straightforward).
This is of course wrong: I should not call the Element method
preview() on multiple elements in a filter created to manage 1
element.
How should I "get this right"?

Should I just create a filter that is called PreviewMany which runs my
Element method preview() on a collection of elements? and
And then I should add this behavior to my list element also: <ul data-
behavior="Grid PreviewMany" ...>... </ul> else it's not listening to
my event firing........

It's starting to get confusing, no? ;)

anyway- tia... 1.48am here now.. time to zzz
> Also, you can *greatly* increase your startup time for filters that only

Aaron Newton

unread,
Oct 10, 2011, 8:00:29 PM10/10/11
to mootool...@googlegroups.com
1)
The "gridRendered" event is not picked up (in Preview filter) when
there is no element in the dom with the data-behavior="Preview"
attribute. (when the filter hasn't run)
In assumed the filter Preview was registered and globally
"listening" (to catch the fired event) even if the Preview filter has
not been actively used yet (by some already declared element in the
dom).
So, to test it I added a dummy div to the dom (not associated to the
list that is turned into the grid) with the data-behavior="Preview"
attribute, which is silly of course...
What's wrong with my thinking process?

Behavior filters are rendered in DOM order (so containers with filters have their filters applied before their children, and siblings with filters have theirs applied first child, then second, and so on). Filters are also applied in the ordered they are listed, so if you have data-behavior="A B" it will render A and then B. The biggest reason that the rule is that filters can't know about each other is that the user of the filter shouldn't have to know that A comes before B (or whatever) and it gets even more complicated the more filters you add.

When you give an element a filter property, that filter's setup method is invoked (the function you define; there's also a long-form version of a filter that's an object with that function defined as the "setup" property). Anyway, that function is never invoked if there is no element with that filter. So if you don't have an element with data-behavior="Preview" your function which adds the event listener will never be invoked. Make sense?
 

2)
The Preview class (and thus the Behavior filter) is setup for one 1
element basically. If I add data-behavior="Preview" to an element the
filter is called and the class is instantiated.
But in the code above I abuse the "gridRendered" event to call a
preview() method on an array of elements I select with a css selector
on the grid container (basically: all childs except empty cells).
(the preview method call does: return new Preview(this, options) -
pretty straightforward).
This is of course wrong: I should not call the Element method
preview() on multiple elements in a filter created to manage 1
element.
How should I "get this right"?

It seems to me that the "Preview" class is not a behavior filter. It's not something in the DOM that gets decorated. Rather, it's something that gets instantiated by your Grid class. Because you don't want your grid to have knowledge of your preview class (instead opting to connect the two together with events, which I think is appropriate), I see three options for you:
  1. Create a super class that knows about Grid and Preview (GridPreview). Create a filter for this.
  2. Create a behavior that has the event hooks into the Grid that creates previews (a behavior filter called GridPreview that has onComplete: function(){ new Preview...} in the invocation of the grid class).
  3. Have only one behavior filter (Grid) but an option that turns on previews for the grid that, when true, adds these event hooks.
For filters to work right, they each have to do a thing on their own. Use even arbitration when they need to coordinate their behavior. Thus a Preview class that does it's own thing and works even if the Grid isn't there and vice versa. Your DOM is decorated with BOTH of these things and the event arbitration exists only to deal with the event that they are both in use on the same DOM tree.

-a

Rolf Langenhuijzen

unread,
Oct 11, 2011, 11:44:47 AM10/11/11
to MooTools Users
I follow you 100%. Going to think about it some more in a few hours
(driving home in a about 10 mins) .. and decide my next move.
>    1. Create a super class that knows about Grid and Preview (GridPreview).
>    Create a filter for this.
>    2. Create a behavior that has the event hooks into the Grid that creates
>    previews (a behavior filter called GridPreview that has onComplete:
>    function(){ new Preview...} in the invocation of the grid class).
>    3. Have only one behavior filter (Grid) but an option that turns on
Reply all
Reply to author
Forward
0 new messages