|
Bitburger-New View API Notes
Page history
last edited
by Evin Grano 15 years, 8 months ago
Use Cases
The new view API is designed around the following use cases:
- The program needs to display a dialog with a number of controls. We need to get the final HTML for the dialog generated and on the screen as fast as possible. This generally means trying to create this HTML, including having all the values filled in and event handlers setup, with as little real DOM manipulation as possible.
- A CollectionView needs to display a list of 100,000 contacts. We must support incremental rendering - i.e. rendering only the HTML that is on the screen. When the user scrolls slowly, we should add/remove just the HTML needed to fill the newly visible area. When the user jerks the scrollbar quickly down, we must be able to quickly redraw the entire visible area.
- A CollectionView needs to display a list comments static layout. In this case, we need to display a stacked list of comments which may have different unknown heights. We need to be able to simply render the HTML for these comments and allow the browser to do the layout. The comments should still support having buttons and other controls internally. It is OK to sacrifice incremental rendering in this case.
- A TableView needs to display 10 columns. A table view is a lot like a list view with multiple columns. We need to be able to rapidly render all 10 columns without have to create (n x m) views for the display. Reusable views would be very useful here.
Current Status
The basic code for SC.View has been hacked in. SC.LabelView has also been updated to use the new render API. SC.RenderContext has been created and it pretty well unit tested. We still need to:
- Add unit testing for all methods on SC.View
- Update SC.Pane to use the new approach -- specifically to lazily create the layer DOM for its child views when it goes on screen.
- Update SC.ContainerView, SC.SplitView, and SC.ButtonView just to get some sense of whether this new API will work as we expect or not.
- If it does, then we need to convert the rest of the views over.
The code for the new API is currently on the new-view-api branch in the staging repository.
Current Design Notes
These notes just document the current concepts in source. They may change at any time.
Layers
- Each view owns a single DOM element and its children. This DOM element is called the view's layer.
- A view's layer is not created until the view actually becomes visible in the window. This means that (a) the pane the view lives in has actually been added to the DOM and (b) the view itself is visible.
Creating a Layer
- When a layer is created, the top level view as handed a RenderContext and asked to render its layer. The RenderContext is essentially an array that you can add strings to. When the rendering is complete, the strings will be joined and converted to DOM using innerHTML. During render, the top level view will ask its child views to render their own HTML into the same RenderContext. Thus, the HTML for the entire view hierarchy is created with just one innerHTML.
- Note that since the layer is not generated until the very last second, the view's themselves should already have be created and have their bindings setup. We should make sure this is the case through unit testing. As such, then the view's do their initial rendering, they can render in all of their relevant state; they should rarely need to go do any post touchup on the DOM once it is created, except maybe to add event listeners.
- When a top-level view generates its layer, the top level view's layer property will then point to this new DOM element. Child views, however, will not have their layer properties updated. However, the layer property is a computed property. If the child views later ask for their layer, a method will be invoked to find the layer in the DOM of the top level view. This way we avoid searching the DOM for elements unless a view actually needs it.
Updating a Layer
- Whenever a view's state changes, you can simply call the view's displayDidChange() method. This will set the view's layerNeedsUpdate property to YES and schedule a call to updateLayerIfNeeded() later on.
- When a layer is updated, the view creates another RenderContext, this time it is targeting the view's layer DOM element. Like when you create a layer, we invoke the view's render() method which can set class names, styles, and add innerHTML strings to the context. However, this time when the render is complete, we will apply these changes to the layer DOM element instead of creating a new DOM element. This way you can reuse the same render code.
- The view's render() method takes a firstTime param. This param is YES when the render() is being called to create a new layer, and NO when the render() is being called on an update. You may choose to do some things only on create and others only on update. For example, a tab view may need to update itself when its visible container is changed. On a createLayer, it will simply render the HTML for the new child view. ON updateLayer() it may simply swap the DOM in for the new child view and avoid generating new HTML.
FrameSupport & Layout
- You can still set the layout of a view by altering its 'layout' property. However, now instead of scheduling a method to run on the view itself to apply that layout to the view's DOM, the view will ask the view's layoutView (by default its parentView), to schedule the layout instead. This hook allows the parent view to decide how and when to layout its child views. For example, a GridView could choose to precisely control the layout of its children and ignore the layout property of its child views altogether. A SplitView would definitely do this; altering its child layout properties to fit its own determination.
- By default, Views not longer have a frame or clippingFrame or several related methods or properties. To get these features, you must include the SC.FrameSupport mixin on your SC.View subclasses. The reason for this split is that these methods are the primary features that require absolute positioning to function properly. Many views, especially controls like buttons and text fields, do not need that properties on a regular basis. Therefore we can omit them entirely, allowing the views to be statically positioned if needed and improving perf.
- If a view does want to do incremental rendering or other things with the frames, then it will need to include SC.FrameSupport. What's more, all of its parent Views must ALSO include FrameSupport. Most container views in the framework will probably include FrameSupport by default, though their child views may not.
SC.RenderContext
- SC.RenderContext is the new engine used to render DOM elements for views. This is not a substitute for CoreQuery, but given the new API, views will often only need to work with SC.RenderContext and not CoreQuery. In fact, CoreQuery may be able to be relegated to an external framework or left out entirely.
- Fundamentally, a RenderContext instance represents a single root element, called the "outer element". You can push() strings into the RenderContext and those will be joined to form the innerHTML of the root element.
- You can also use helpers to edit the outer element such as:
- tagName() - set and get the tag name
- id() - set and get the id.
- classNames(), addClass(), removeClass(), setClass(), css() - edit the 'class' attribute
- styles(), addStyle(), removeStyle() - edit the 'style' attribute
- attr() - add/remove additional attributes
- These helper methods are intentionally designed to model jQuery/CoreQuery. However, unlike jQuery/CoreQuery, these methods do NOT immediately work on the DOM element. Instead they queue up all of these changes on the context. At the end of the context session, they are applied all at once.
- RenderContexts can be nested. You can call begin(tagName) on an existing context and it will return a new context. When you call end() on that context, the HTML you setup will be added to the parent context.
- RenderContexts can be created either to generate new HTML altogether or you can pass an existing DOM element, which will become the "outer element" for the context. At the end of your render session, call element() to create a new element from the render context or update() to apply the context to the element you passed when you first created the context.
- When you update() an existing element, if you never push() any strings into the RenderContext, the innerHTML of the element will not be replaced. This way you can use a RenderContext just to update classnames, ids and attributes on the outer element without destroying the innerHTML.
Testing Requirements
- Currently the RenderContext is pretty well unit tested.
- The view layer needs extensive unit tests added. We need to write tests for each method, but we also need to write some functional test that go over various creation scenarios to ensure that layers are not created until absolutely needed.
- One tricky method that still needs testing is isVisibleInWindow. This property is key to determining when a layer can actually be created. It needs to become true only when: the view and all of its parentViews have isVisible = YES & the view's pane is visible on screen.
Example Code
Here is how you would write a basic HelloWorld view:
MyApp.HelloWorldView = SC.View.extend({
value: "Hello World",
displayProperties: 'value', // automatically update layer if value changes
render: function(context, firstName) {
context.text(this.get('value')); // text will escape HTML
}
});
Here is how you might blend SC.View with YUI (derived from YUI examples):
MyApp.AutocompleteView = SC.View.extend({
// a YUI 'DataSource' object
dataSource: null,
// add required initial content
render: function(context) {
context.push("<input id='myInput' /><div id='myContainer' ></div>");
},
// setup YUI JS when layer is created
didCreateLayer: function() {
this.yui = new YAHOO.widget.AutoComplete("myInput", "myContainer",
this.get('dataSource'));
}
});
Bitburger-New View API Notes
|
Tip: To turn text into a link, highlight the text, then click on a page or file from the list above.
|
|
|
|
|
Comments (0)
You don't have permission to comment on this page.