[ 5 posts ]

Steven Olmsted

YUI Contributor

  • Username: solmsted
  • Joined: Wed Apr 14, 2010 1:47 pm
  • Posts: 82
  • Location: Richmond, VA
  • GitHub: solmsted
  • Gists: solmsted
  • IRC: solmsted
  • Offline
  • Profile

YUI 3.5 App Framework: An ongoing discussion

Post Posted: Sat Feb 04, 2012 11:46 pm
+0-
I did a lot of work last year with YUI 3.3. The App Framework didn’t exist yet so I made up my own infrastructure for managing my apps and their pages. Now with YUI 3.5 approaching, I’m transitioning to the App Framework. As I’m at the beginning of some reasonably large projects, I’ve been thoroughly researching the App Framework. At first I thought it would be difficult to get the same functionality from the App Framework as I did from my custom solution, but it turns out that the App Framework easily handles almost all of what I was looking for. I’m going to use this forum thread as a running log of things I learn and ideas and questions I have. I hope others will find this information useful. Feel free to join in and start a conversation.

It’s probably pretty common for the initial concepts or design documents for a new web application to describe functionality in terms of different pages of the application. Today’s web applications with ajax and pjax generally don’t make use of multiple actual page loads, yet from both the points of view of an application’s user and an application’s developer, the mental concept of a page persists. The application will have some base functionality which is common from page to page, but each page will generally have its own very different set of requirements and different functionality which needs to be implemented.

Without getting into the details, my old architecture basically had an app object and page objects. The app object was instantiated immediately on load, it managed things on the app level and was responsible for loading pages. The page objects handled everything that a particular page needed to do. Now Y.App provides a great place to manage initialization and app level functionality. It also conveniently handles navigation and routing. Y.Model, Y.ModelList, and Y.View all exist to separate different concerns for the app. Models and ModelLists manage data specific logic while Views handle displaying data and receiving user input. The App Framework does not provide an object that specifically represents a page. This is a good thing, because it encourages a developer to separate the page’s concerns into Models, ModelLists, and Views. This might be a bad thing, because there’s no object in the App Framework that specifically matches a developer’s or a user’s mental concept of a page.

The first thing I had to learn about the App Framework, some of the logic of a page is in Models some is in Views and some is outside of either of those. I understood Models right away. I have done a lot of work with relational databases and I was pleasantly surprised by how nice and easy the sync interface is to work with. At first, once I had my Models all set, I tried cramming all of the other logic into View. That seemed good at the time, then showView was a convenient method for switching between pages. It turned out that this was very wrong. I realized this was wrong when I had a view trying to fetch data before it could render. The showView method doesn’t expect a View’s initializer or render method to do anything asynchronous. I wanted to have the App display a loading message during the data request; the puzzle pieces just didn’t fit together that way. Views shouldn’t be talking to App directly or making data requests; Models or ModelLists should be passed to Views. Therefore, some page logic must exist outside of Model, ModelList or View.

Now I finally understand why Y.Router used to be called Y.Controller. I never knew why a utility for managing HTML 5 history and routing was lumped together with Model, ModelList, and View. The bits of logic that belong to a page but do not belong to Model, ModelList, or View should go in a route callback function on App. This lets you do whatever you need to do to set up the page, call showView to display a temporary loading message, call Y.use to lazily load page requirements, make sync requests to fetch data, eventually call showView again when the page’s view is ready. The puzzle pieces fit together much better this way.

Should there actually be some sort of Y.Controller object, or maybe some sort of page object? Putting a page’s non-model and non-view logic in a route callback function works really well if it acts mostly like an initializer. I can imagine a complex page implementation needing a number of extra object instances and functions. All of this could currently be stored on App, but when an application has many complex pages, I fear App will grow into an unwieldy mess. Also, if all this functionality is on App or in route callback functions it can’t be lazily loaded. That’s potentially a lot of upfront loading, especially if a user might not visit every page in the app. Even if YUI doesn’t provide this as an object, YUI provides Base, and there’s nothing stopping application developers from making something that suits their specific needs.

After working with the App Framework, so far I’ve only found two somewhat minor features that I would like to see, but there doesn’t seem to be a simple way to implement them.

My old framework has a feature where once a page is loaded, when the user clicks a link to load a new page, the current page fires an unload event and can cancel that event to prevent the new page from loading. The unload event is useful sort of like a destructor for the page. The ability to cancel the unload is pretty nice too. This allows the page to display a modal panel asking the user, “Would you like to save your work before you leave this page? [Yes] [No] [Cancel]”. The App Framework does not really have this functionality built in. The navigate event inherited from Y.Pjax is preventable and is the only place I know of to implement something like this but it’s not so easy. Route callback functions can do whatever you want them to do, they don’t have to load a new page. That means, a navigate event doesn’t necessarily mean a new page will load. Therefore, if a page wants to listen on navigate and potentially cancel another page from loading, it needs a way to find out what routes the navigate will dispatch to, and if any of those routes will cause a page load. I don’t think that can be easily implemented on top of Y.App as it exists currently. I have some ideas for a class extension for Router that might help, but I feel like it’ll be complex and difficult to maintain alongside future development on Router. Anyone have any other solutions?

A page can potentially take a while to load, making data requests, loading new modules, etc. If an application has a menu containing links to different pages, a user could potentially click on many links very quickly. This causes lots of unnecessary work for the application as it’s initializing the pages that will be displayed for less than a second. (The app-transitions module already has an issue with this. http://yuilibrary.com/projects/yui3/ticket/2531760) I’ve been wondering how this situation could possibly be handled better. My ideas, all involve a couple changes to Router. Under the covers, Router’s save and replace implementations will queue up those requests and deal with them one by one, but as soon as the save or replace occurs, dispatch happens immediately and calls the matching route callback function. So one solution could be to also queue up the dispatches. Then when a user clicks a link, it will start loading a page, if they click several links before the first page finishes loading, instead of dispatching each of those clicks, they will queue up. When the first page is finished loading, it could skip all of the intermediate dispatches and just dispatch the most recent one. This stops the application from performing a lot of needless work and will get the proper page loaded for the user more quickly. Implementing this would require knowing when dispatch begins and ends, so route callback functions would be required to call a callback function if they do anything asynchronous. Could skipping a dispatch during a series of quick navigates potentially break something? Instead of queueing and skipping could their be a way such that dispatches could happen normally, but if another dispatch happens while a previous one is still doing work, the previous one could be safely aborted? Any solution I can think of involves making changes to Router and forcing route callback functions to do something special.

The App Framework in general is really great. Giving application developers a common way to logically organize an application’s functionality should make it easier for the community to help each other make great applications. What have you learned? What questions do you have? What features would you like to see?

Eric Ferraiuolo

YUI Developer

  • Username: ericf
  • Joined: Mon Jan 12, 2009 8:26 pm
  • Posts: 380
  • Location: Boston, MA
  • Twitter: ericf
  • GitHub: ericf
  • Gists: ericf
  • IRC: eric_f
  • YUI Developer
  • Offline
  • Profile

Re: YUI 3.5 App Framework: An ongoing discussion

Post Posted: Tue Feb 07, 2012 11:08 am
+0-
Steven,

Thanks for all the feedback, it is really helpful to have people like yourself flexing these new areas of the App Framework!

I thought it would be easier to reply to your specific points inline:

solmsted wrote:
The App Framework does not provide an object that specifically represents a page. This is a good thing, because it encourages a developer to separate the page’s concerns into Models, ModelLists, and Views. This might be a bad thing, because there’s no object in the App Framework that specifically matches a developer’s or a user’s mental concept of a page.


Y.App does use page-level Views to represent pages from a visual/DOM level, and the Y.App instance serves as the top-level component which remains persistent throughout page changes.

solmsted wrote:
At first, once I had my Models all set, I tried cramming all of the other logic into View. That seemed good at the time, then showView was a convenient method for switching between pages. It turned out that this was very wrong. I realized this was wrong when I had a view trying to fetch data before it could render. The showView method doesn’t expect a View’s initializer or render method to do anything asynchronous. I wanted to have the App display a loading message during the data request; the puzzle pieces just didn’t fit together that way. Views shouldn’t be talking to App directly or making data requests; Models or ModelLists should be passed to Views. Therefore, some page logic must exist outside of Model, ModelList or View.


This is still possible to do, but the View has to be written in such a way that it can handle rendering itself without a loaded Model/ModelList. That said, I would not recommend this approach, as you found.

solmsted wrote:
Should there actually be some sort of Y.Controller object, or maybe some sort of page object? Putting a page’s non-model and non-view logic in a route callback function works really well if it acts mostly like an initializer. I can imagine a complex page implementation needing a number of extra object instances and functions. All of this could currently be stored on App, but when an application has many complex pages, I fear App will grow into an unwieldy mess. Also, if all this functionality is on App or in route callback functions it can’t be lazily loaded. That’s potentially a lot of upfront loading, especially if a user might not visit every page in the app. Even if YUI doesn’t provide this as an object, YUI provides Base, and there’s nothing stopping application developers from making something that suits their specific needs.


Instead of creating an explicit "Page" object, there is work we can do to make setting up a hierarchy of Views easier.

I would like to write up some tutorials on how to incorporate module loading with routing to show some approaching for organizing code of a large application. In sort, calling `Y.use()` in a router handler can be useful.

solmsted wrote:
My old framework has a feature where once a page is loaded, when the user clicks a link to load a new page, the current page fires an unload event and can cancel that event to prevent the new page from loading. The unload event is useful sort of like a destructor for the page. The ability to cancel the unload is pretty nice too. This allows the page to display a modal panel asking the user, “Would you like to save your work before you leave this page? [Yes] [No] [Cancel]”. The App Framework does not really have this functionality built in. The navigate event inherited from Y.Pjax is preventable and is the only place I know of to implement something like this but it’s not so easy. Route callback functions can do whatever you want them to do, they don’t have to load a new page. That means, a navigate event doesn’t necessarily mean a new page will load. Therefore, if a page wants to listen on navigate and potentially cancel another page from loading, it needs a way to find out what routes the navigate will dispatch to, and if any of those routes will cause a page load. I don’t think that can be easily implemented on top of Y.App as it exists currently. I have some ideas for a class extension for Router that might help, but I feel like it’ll be complex and difficult to maintain alongside future development on Router. Anyone have any other solutions?


There is also the `activeViewChange` event, which itself is preventable; this event is fired when `showView()` is called. We are also planning on adding a `dispatch` event (to Router), which gives three interesting moments:

  1. `navigate`: User clicks a link or `navigate()` is called and there is one or more matching route-handlers.
  2. `dispatch`: Routing is happening, caused from navigating or back/forward buttons.
  3. `activeViewChange`: `showView()` was called.

solmsted wrote:
A page can potentially take a while to load, making data requests, loading new modules, etc. If an application has a menu containing links to different pages, a user could potentially click on many links very quickly. This causes lots of unnecessary work for the application as it’s initializing the pages that will be displayed for less than a second. (The app-transitions module already has an issue with this. http://yuilibrary.com/projects/yui3/ticket/2531760) I’ve been wondering how this situation could possibly be handled better. My ideas, all involve a couple changes to Router. Under the covers, Router’s save and replace implementations will queue up those requests and deal with them one by one, but as soon as the save or replace occurs, dispatch happens immediately and calls the matching route callback function. So one solution could be to also queue up the dispatches. Then when a user clicks a link, it will start loading a page, if they click several links before the first page finishes loading, instead of dispatching each of those clicks, they will queue up. When the first page is finished loading, it could skip all of the intermediate dispatches and just dispatch the most recent one. This stops the application from performing a lot of needless work and will get the proper page loaded for the user more quickly. Implementing this would require knowing when dispatch begins and ends, so route callback functions would be required to call a callback function if they do anything asynchronous. Could skipping a dispatch during a series of quick navigates potentially break something? Instead of queueing and skipping could their be a way such that dispatches could happen normally, but if another dispatch happens while a previous one is still doing work, the previous one could be safely aborted? Any solution I can think of involves making changes to Router and forcing route callback functions to do something special.


This will be easier to solve when we've figured out how the `dispatch` event should behave.

Steven Olmsted

YUI Contributor

  • Username: solmsted
  • Joined: Wed Apr 14, 2010 1:47 pm
  • Posts: 82
  • Location: Richmond, VA
  • GitHub: solmsted
  • Gists: solmsted
  • IRC: solmsted
  • Offline
  • Profile

Re: YUI 3.5 App Framework: An ongoing discussion

Post Posted: Tue Feb 07, 2012 6:59 pm
+0-
Eric: Thanks for your comments. There are still a few things that I’m having trouble putting together. Maybe I’m missing something? Maybe you have other ideas that just aren’t implemented yet? All of these little issues are interconnected and all together the App Framework is a huge topic. I’ll continue searching to see what we can learn.

What is a view?

I struggled with this at first. I might still be struggling. Currently I see view as having two jobs to do. Its first job is to render its data to the DOM. Its second job is to handle user interaction for the DOM it rendered. Is there anything else it should be responsible for? As stated previously, it’s possible for a view to do more, but it’s not recommended.

Rendering to the DOM seems mostly self explanatory. Use Handlebars, or Y.substitute templates, or build nodes directly, whatever works for you and your view. Handling user interaction is a fuzzier topic. How much is too much for a view to handle? Rendering a tooltip when a node gets moused over? Sure a view can do that. Handling drag and drop within the view? Sure. Handling drag and drop between this view and another view? Hmm, I think something outside of both views should set that up. Handling form validation and submit? I don’t really know. Some bits of validation can happen right on model. Should a view ever edit its own data or should the view re fire the submit event for something higher up to deal with it?

I don’t really like the Todo List example that shows a view loading its own data. In this case the sync is just reading from local storage and it’s synchronous, so everything should work out, but I feel like most applications will sync with a remote server. Everything just gets ugly if a view’s initializer or render methods do anything asynchronously. The Todo List’s views also modify their own data directly.

I really like the GitHub Contributors example. It demonstrates asynchronous data sync, views are given their data, and views never modify their data. The views re fire user interaction events and they bubble up to the App and are handled there. It is a very clean and manageable implementation.

So answering my own question by looking at the GitHub Contributors example, views shouldn’t handle user interaction events that involve modifying data. In that case the view should re fire the event and let it bubble up. (If this concept of a view is inaccurate, than my remaining conclusions about views are also likely to be inaccurate.)

I don’t see how a hierarchy of views can replace a page functionality object. With this definition of a view, even page-level views do not contain all of the functionality of a page. A page-level view should still bubble events up to something higher to handle the functionality. Currently, the only thing higher than a page-level view is the app instance. If the app instance has to be the bubble target for a page’s events, there’s no easy way to lazy load the page’s functionality. There needs to be another object between the app instance and the views.

It’s possible that a page-level view could be ‘special’. If we allow a page-level view to load data, manipulate data, and handle events which have bubbled up from deeper views, then the page-level views break all the rules we just said that views should adhere to. In this case, is a page-level view really still a view? The requirement for the initializer and render methods to remain synchronous is a huge limitation for using page-level views this way.

Steven Olmsted

YUI Contributor

  • Username: solmsted
  • Joined: Wed Apr 14, 2010 1:47 pm
  • Posts: 82
  • Location: Richmond, VA
  • GitHub: solmsted
  • Gists: solmsted
  • IRC: solmsted
  • Offline
  • Profile

Re: YUI 3.5 App Framework: An ongoing discussion

Post Posted: Tue Feb 07, 2012 7:06 pm
+0-
I’ve been specifically researching lazy loading in my free time for the last several days.

Larger or more complicated applications can benefit greatly by lazily loading modules. The modules that run different pages of an application are the most obvious place to support lazy loading. It seems very likely that a user could visit the ‘Order Free Dessert’ page of your application without ever visiting other pages like ‘Upload Cat Pictures’ or ‘View Complicated Charts and Tables’. It seems like a waste to load all the functionality up front when it may not even get used.

ericf wrote:
I would like to write up some tutorials on how to incorporate module loading with routing to show some approaching for organizing code of a large application. In sort, calling `Y.use()` in a router handler can be useful.


I would like to see your example implementation. I found some annoying limitations with Y.use and I create my own workaround.

YUI’s module system and Loader are really nice tools that can accomplish a lot. There are other guides out there that explain Loader in greater detail. Assuming you already know and love Loader, I’m going to focus on a couple of intricacies that are unique to lazy loading.

YUI’s module system and the Y instance are some of the best parts of YUI. Unfortunately, I’ve noticed there is a source of confusion here for some people when they are first introduced to it. Once you load the module ‘io’ you can use Y.io in your code. Once you load the module ‘calendar’ you can use Y.Calendar in your code. Once you load the module ‘json’ you can use Y.JSON in your code. Once you load the module ‘node’ you can use Y.one in your code. There’s no enforced correlation between a module name and what it does with Y. This makes the module system really nice and flexible, but it makes lazy loading more difficult to do in a generic way. Any bit of code that wants to use a module, whether up front or lazily, needs to know both the module’s name and what that module does to Y. There’s no getting around this.

To lazy load a module you can simply call Y.use(‘module-name’, callbackFunction); Note that the callback function will get executed synchronously if the module or modules are already loaded. So far, this is quick and easy, but it’s really incomplete. I’ve found two issues with Y.use.

There’s always a chance that loader can fail. Network connectivity issues, incorrect module meta data, and misspelled module names can all contribute to loader being unsuccessful. A really good blog post was recently written about handling loader errors: viewtopic.php?f=18&t=9376. This all involves defining error handling methods and passing them in the config object when the Y instance is instantiated. That works really well for catching initial page load errors. For lazy loading, it’s really awkward trying to handle loader errors within a function that was defined way before anything else and which may get called at different times for different reasons across the entire application. It would be much easier to be able to handle errors locally, within the same scope that the load was initiated in.

Y.use is helpful because it doesn’t care if the module is already loaded or not, it checks for you, then runs your callback. Occasionally this isn’t good enough. Imagine that you want to lazily load a view class, then make it the app’s active view. After loading it, but before calling showView, the view should be registered with the app’s views property. This registration only needs to happen once, the first time the view is loaded. Y.use doesn’t tell you if this is the first time or not. (In this example there are other ways to accomplish this, and the view doesn’t technically need to get registered, but this was only an example.)

To overcome these two issues with Y.use, I made this: http://yuilibrary.com/gallery/show/lazy-load. Y.lazyLoad works the same way as Y.use, except it circumvents the normal error handling methods. The callback function receives an array of errors if any were generated by this particular load. The callback function also receives an object which lists the modules that were attached to the Y instance by this particular load. This allows you to do error handling locally, and check if this is the first time a particular module loaded.

I’m wondering why these issues weren’t addressed by someone sooner. Are people generally not lazy loading modules with Y.use? Do people not care about error handling? Both?

Steven Olmsted

YUI Contributor

  • Username: solmsted
  • Joined: Wed Apr 14, 2010 1:47 pm
  • Posts: 82
  • Location: Richmond, VA
  • GitHub: solmsted
  • Gists: solmsted
  • IRC: solmsted
  • Offline
  • Profile

Re: YUI 3.5 App Framework: An ongoing discussion

Post Posted: Tue Feb 07, 2012 7:19 pm
+0-
solmsted wrote:
My old framework has a feature where once a page is loaded, when the user clicks a link to load a new page, the current page fires an unload event and can cancel that event to prevent the new page from loading. The unload event is useful sort of like a destructor for the page. The ability to cancel the unload is pretty nice too. This allows the page to display a modal panel asking the user, “Would you like to save your work before you leave this page? [Yes] [No] [Cancel]”. The App Framework does not really have this functionality built in. The navigate event inherited from Y.Pjax is preventable and is the only place I know of to implement something like this but it’s not so easy. Route callback functions can do whatever you want them to do, they don’t have to load a new page. That means, a navigate event doesn’t necessarily mean a new page will load. Therefore, if a page wants to listen on navigate and potentially cancel another page from loading, it needs a way to find out what routes the navigate will dispatch to, and if any of those routes will cause a page load. I don’t think that can be easily implemented on top of Y.App as it exists currently. I have some ideas for a class extension for Router that might help, but I feel like it’ll be complex and difficult to maintain alongside future development on Router. Anyone have any other solutions?

ericf wrote:
There is also the `activeViewChange` event, which itself is preventable; this event is fired when `showView()` is called. We are also planning on adding a `dispatch` event (to Router), which gives three interesting moments:

`navigate`: User clicks a link or `navigate()` is called and there is one or more matching route-handlers.
`dispatch`: Routing is happening, caused from navigating or back/forward buttons.
`activeViewChange`: `showView()` was called.


In terms of a page unload event, and a page being able to cancel the unload, I think navigate is the only event that can deal with that. Anything after navigate and the url has already changed. It seems like it would put the whole application into a weird inconsistent state if the url changed and then dispatch or activeViewChange was prevented. Those events definitely have good uses, but not for this.

There is still the difficulty that none of these events define a page transition. A navigate, dispatch, and activeViewChange could all potentially happen within one page of an application.

Relating this back to my earlier discussion about views, here's another good reason for having a page-level object between app and view. Views shouldn't ever be telling the app what to do. Some sort of page-level functionality has to listen on navigate and decide to prevent it. That feels like too much authority to hand over to a view.
  [ 5 posts ]
Display posts from previous:  Sort by  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum