Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

View layer Analysis of Ember.js

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains the "Ember.js View layer Analysis", the content of the article is simple and clear, easy to learn and understand, the following please follow the editor's ideas slowly in depth, together to study and learn "Ember.js View layer Analysis" bar!

Ember.js has a complex set of systems for creating, managing, and rendering hierarchical views connected to the browser DOM. The view is responsible for responding to user events such as clicking, dragging, and scrolling, as well as updating the contents of the DOM when the underlying data of the view changes.

The view level is usually created by evaluating a Handlebars template. When the template is evaluated, child views are added. When those child views are evaluated, their child views are added, recursively, until the entire hierarchy is created.

Even if you do not explicitly create a child view in the Handlebars template, the view system is used internally to update the bound values within Ember.js. For example, each Handlebars expression {{value}} creates a view behind the scenes that knows how to update the bound value when the value changes.

You can also use the Ember.ContainerView class to make changes to the view level while the application is running. A container view exposes an array of child view instances that can be modified manually, rather than template-driven.

Working in tandem with views and templates provides a robust system for creating any user interface you dream of. In the end, users should be separated from complex things such as timing events when rendering and event propagation. Application developers should be able to describe their UI as a Handlebars tag string at once and then continue to complete their application without worrying about making sure it is always up-to-date.

What problem does it solve?

Child view

In a typical client application, the view represents nested elements both in itself and in the DOM. In a naive solution to this problem, separate view objects represent a single DOM element, and specific references address different types of views to keep track of views nested within them in the concept.

Here is a simple example of an application main view with a collection nested view and independent elements nested within the collection.

The system looks exactly the same at first glance, but imagine we're going to open Joe's lamprey hut at 8 a. M. instead of 9 a. M. In this case, we will want to re-render the application view. There are several problems with this re-rendering process because developers need to build references to subviews on a special basis.

In order to re-render the application view, the application view must also manually re-render the child views and re-insert them into the elements of the application view. If implemented perfectly, the process will work, but it depends on a perfect, dedicated view-level implementation. If any view does not implement it exactly, the entire re-rendering process will fail.

To avoid these problems, the view level of Ember is conceptually branded with child views.

When the graph is re-rendered when applied, the Ember, rather than the application code, is responsible for re-rendering and inserting the child view. This also means that Ember can perform any memory management for you, such as cleaning up observers and bindings.

This not only eliminates the boilerplate code to some extent, but also removes the possibility of late failure caused by a flawed view-level implementation.

Event delegation

In the past, web developers have used to add event listeners to individual elements to know when users interact with them. For example, you will have an element that registers a function that is triggered when the user clicks it.

However, this approach does not scale in dealing with a large number of interactive elements. For example, imagine a project with 100 items followed by a delete button. Since all of these project behaviors are uniform, it is undoubtedly inefficient to create a total of 100 event listeners for each delete button.

To solve this problem, developers have discovered a technique called "event delegation". Instead of creating a listener for each item in the question, you can register a listener on the container element and use event.target to identify which element is clicked by the user.

Implementing this is a bit subtle because some events, such as focus, blur, and change, do not bubble. Fortunately, jQuery has solved this problem completely; all native browser events can be handled reliably with jQuery's on method.

Other JavaScript frameworks deal with this problem in one of two ways. The first is that they require you to implement the native solution yourself, creating a separate view for each project. When you create a view, it sets a listener on the elements of the view. If you have a list of 500 items, you will create 500 views and each view will set a listener on its own element.

The second approach is that the framework has built-in event delegation in the view layer. When creating a view, you can provide a list of events to delegate a method to call when an event occurs. All that's left is the click context that identifies the method that accepts the event (for example, which item in the list).

You are now faced with two disturbing choices: create a new view for each project, which loses the advantage of event delegation, or create a single view for all projects, which must store the information of the underlying JavaScript in the DOM.

To solve this problem, Ember uses jQuery to delegate all events to the root element of the application (usually the body of the document). When an event occurs, Ember recognizes the nearest event view and invokes its event handler. This means that you can create a view to hold a JavaScript context, but still benefit from event delegation.

Further, because Ember registers only one event for the entire Ember application, creating a new view never requires an event listener, which makes re-rendering efficient and error-free. When a view has a child view, this also means that you do not need to manually undelegate the view that is replaced during re-rendering.

Rendering Pip

Most web applications use special template language tags to specify their user interfaces. For Ember.js, we have finished writing templates in the Handlebars template language that automatically updates the template when the value is changed.

While the process of displaying templates is automatic for developers, it obscures the necessary steps to convert the original template into a final template and generate a user-visible DOM representation.

This is the approximate life cycle of the Ember view:

1. Template compilation

The applied template is loaded through the network or in the form of a string as the load of the application. When the application loads, it sends a template string to Handlebars to compile into a function. Once compiled, template functions are saved and can be reused by multiple views, each time they need to be recompiled.

This step will be issued where the server precompiles the template in the application. In those cases, the template is not transmitted as the original human-readable template, but as compiled code.

Because Ember is responsible for template compilation, you don't need to do any extra work to ensure that the compiled template can be reused.

two。 Concatenation of strings

When an application calls append or appendTo on a view, a view rendering process is started. The append or appendChild call schedules the view rendering and inserts it later. This allows delay logic in the application, such as binding synchronization, to be performed before rendering the element.

To start the rendering process, Ember creates a RenderBuffer and presents it to the view to attach the contents of the view to it. During this process, the view can create and render child views. When it does so, the parent view creates and assigns a RenderBuffer to the child view and connects it to the parent view's RenderBuffer.

Ember refreshes the binding synchronization queue before rendering each view. In this way, Ember guarantees that expired data that needs to be replaced immediately will not be rendered.

Once the main view has finished rendering, the rendering process creates a view tree (that is, the view level), which is connected to the buffer tree. By walking down the buffer tree and converting them to strings, we have a string that can be inserted into the DOM.

Here is a simple example:

In addition to child nodes (strings and other RenderBuffer), RenderBuffer also encapsulates element signature, id, class, style, and other attributes. This makes it possible for the rendering process to modify these attributes, such as styles, even after the substring has been rendered. Because many of these properties can be controlled by binding (for example, with bindAttr), this makes the rendering process robust and transparent.

3. Creation and insertion of elements

At the end of the rendering process, the root view requests its elements from RenderBuffer. RenderBuffer takes its full string and converts it into an element using jQuery. The view assigns that element to its element attribute and places it in the correct location in the DOM (the location specified by appendTo, which is the root element of the application if the application uses append).

Although the parent view assigns its elements directly, each child view is lazy to find its elements. It does this by looking for elements where id matches its elementId attribute. Unless explicitly provided, the rendering process generates an elementId attribute that assigns its value to the view's RenderBuffer more than you do, and RenderBuffer allows the view to find its elements as needed.

4. Re-render

After the view inserts itself into the DOM, both the Ember and the application will re-render the view. They can call the rerender method on the view to start a re-render.

Re-rendering repeats steps 2 and 3 above, with two exceptions:

Instead of inserting the element into an explicitly defined location, rerender replaces the existing element with the new one.

In addition to rendering the new element, it also deletes the old element and destroys its child elements. This allows Ember to automatically handle the undo of the appropriate bindings and observers when the view is re-rendered. This makes the observer on the path feasible, because registering and unregistering all nested observers are automatic.

The most common cause of view re-rendering is when binding to the Handlebars expression ({{foo}}) changes. Ember internally creates a simple view for each expression and registers an observer on the path. When the path changes, Ember updates the DOM for that area with the new value.

Another common situation is a {{# if}} or {{# with}} block. When you render a template, Ember creates a virtual view of these block helper tags. These virtual views do not appear at the publicly accessible view level (when parentView and childViews are obtained from the view), but their existence enables consistent re-rendering.

When a path change is passed to {{# if}} or {{# with}}, Ember automatically re-renders the virtual view to replace its contents and, importantly, destroys all child views to free memory.

In addition to these scenarios, applications sometimes have to explicitly re-render the view (usually a ContainerView, see below). In this case, the application can call rerender directly, and Ember will queue a re-rendering work with the same semantic elements.

The process goes like this:

View level

Father and son

When Ember renders a templated view, it generates a view level. Let's assume that we already have a template form.

Original text link:

{{view App.Search placeholder= "Search"}} {{# view Ember.Button}} Go! {{/ view}}

Then we insert it into the DOM like this:

Var view = Ember.View.create ({templateName: 'form'}) .append ()

This creates a small view level like this:

You can use the parentView and childViews properties to navigate through the view level.

Var children = view.get ('childViews') / / [,] children.objectAt (0). Get (' parentView') / / View

A common way to use parentView is in an instance of a child view.

App.Search = Ember.View.extend ({didInsertElement: function () {/ / this.get ('parentView') points to `view`}})

Life cycle hook

To easily perform behavior at different points in the view's life cycle, there are several hooks that you can implement.

WillInsertElement: this hook is called before being inserted into the DOM after the view is rendered. It does not provide access to the element of the view.

DidInsertElement: this hook is called immediately after the view is inserted into the DOM. It provides access to the element of the view and is useful for integration into external libraries. Any explicit DOM setting code should be limited to this hook.

WillDestroyElement: this hook is called immediately before the element is removed from the DOM. This provides an opportunity to destroy any external state associated with the DOM node. Like didInsertElement, it is useful for integrating external libraries.

WillRerender: this hook is called immediately before the view is re-rendered. This is useful if you want to perform some destruction before the view is re-rendered.

BecameVisible: this hook is called when the isVisible of the view or the isVisible of one of its ancestors becomes true and the associated element becomes visible. Note that this hook is only reliable when all visibility is controlled by the isVisible property.

BecameHidden: this hook is called after the isVisible of the view or the isVisible of one of its ancestors becomes false and the associated element becomes hidden. Note that this hook is only reliable when all visibility is controlled by the isVisible property.

The application can implement the hook by defining a method with the same name as the hook on the view. Alternatively, it is possible to register a listener for the hook on the view.

View.on ('willRerender', function () {/ / do something with view})

Virtual view

As mentioned above, Handlebars creates views at the view level to represent bound values. Every time you use a Handlebars expression, whether it's a simple value or a block expression such as {{# with}} or {{# if}}, Handlebars creates a new view.

Because Ember only uses these views for internal bookkeeping, they are hidden from the public parentView and childViews API of the view. The common view level reflects only views created with {{view}} auxiliary tags or through ContainerView (see below).

For example, consider the following Handlebars template:

Joe's Lamprey Shack {{controller.restaurantHours}} {{# view App.FDAContactForm}} if you are not feeling well after eating in Joe's lamprey cabin, please use the form below to submit a complaint to FDA. {{# if controller.allowComplaints}} {{view Ember.TextArea valueBinding= "controller.complaint"}} submit {{/ if}} {{/ view}}

Rendering this template creates a hierarchy like this:

Behind the scenes, Ember tracks the additional virtual views created for Handlebars expressions:

In TextArea, parentView will point to FDAContactForm, and the childViews of FDAContactForm will be an array containing only TextArea.

You can view the internal view level through _ parentView and _ childViews, which includes virtual views:

Var _ childViews = view.get ('_ childViews'); console.log (_ childViews.objectAt (0). ToString ()); / / >

Warning! You should not rely on these internal API in your application code. They will be changed at any time and there are no public contracts. The return value cannot be observed or bound. It may not be an Ember object. If you feel there is a need to use them, please contact us so that we can expose a better public API for your use needs.

Bottom line: this API is like XML. If you think you need to use it, then you probably don't understand the problem enough. Think twice!

Event bubbling

One of the tasks of the view is to respond to the original user events and translate them into events that are semantic to your application.

For example, a delete button translates the original click event into an application-specific "remove this element from the array".

To respond to user events, create a subclass of the view to implement the event as a method:

App.DeleteButton = Ember.View.create ({click: function (event) {var stateManager = this.getPath ('controller.stateManager'); var item = this.get (' content'); stateManager.send ('deleteItem', item);}})

When you create a new Ember.Application instance, it uses jQuery's event delegate API to register an event handler for each native browser event. When a user triggers an event, the application event allocator finds the view closest to the event and implements that event.

A view implements the event by defining a method with the same name as the event. When the event name consists of multiple words (such as mouseup), the method name uses the Camel naming method to use the event name as the method name (mousUp).

The event bubbles at the view level until the event reaches the root view. An event handler can stop event propagation using the same techniques as regular jQuery event handlers:

Return false in the view

Event.stopPropagation

For example, suppose you have defined the following view class:

App.GrandparentView = Ember.View.extend ({click: function () {console.log ('grandparentures');}}); App.ParentView = Ember.View.extend ({click: function () {console.log ('parentures'); return false;}}); App.ChildView = Ember.View.extend ({click: function () {console.log ('childish');}})

This is the Handlebars template that uses them.

{{# view App.GrandparentView}} {{# view App.ParentView}} {{# view App.ChildView}} Click here! {{/ view}} {{/ view}} {{/ view}}

If you click, you will see the following output in the browser console:

Child! Parent!

You can see that Ember invokes the handler on the deepest view of accepting events. The event continues to float to ParentView, but does not reach GrandparentView because ParentView returns false from its event handler.

You can use regular event bubbling techniques to implement common patterns. For example, you can implement a FormView with the submit method. Because the browser triggers the submit event when the user enters a carriage return into the text field, defining a submit method on the form view will "just get the job done".

App.FormView = Ember.View.extend ({

TagName: "form"

Submit: function (event) {

/ / the browser will be triggered by any user

/ / `submit` method is called

}

});

{{# view App.FormView}} {{view Ember.TextFieldView valueBinding= "controller.firstName"}} {{view Ember.TextFieldView valueBinding= "controller.lastName"}} OK {{/ view}}

Add a new event

Ember has built-in support for the following native browser events:

Event name

Method name

TouchstarttouchStarttouchmovetouchMovetouchendtouchEndtouchcanceltouchCancelkeydownkeyDownkeyupkeyUpkeypresskeyPressmousedownmouseDownmouseupmouseUpcontextmenucontextMenuclickclickdblclickdoubleClickmousemovemouseMove

Event name

Method name

FocusinfocusInfocusoutfocusOutmouseentermouseEntermouseleavemouseLeavesubmitsubmitchangechangedragstartdragStartdragdragdragenterdragEnterdragleavedragLeavedragoverdragOverdropdropdragenddragEnd

When you create a new application, you can add additional events to the event allocator:

App = Ember.Application.create ({customEvents: {/ / add loadedmetadata Media player event 'loadedmetadata': "loadedMetadata"}})

For this to work for custom events, the HTML5 specification must define the event as "bubbling", otherwise jQuery must provide an event delegation compromise for this event.

Templated view

As you have seen so far in this guide, most of the views you will use in your application rely on templates. When using a template, you do not need to write your view level, because the template will create it for you.

When rendering, the view template can attach the view to its array of child views. The appendChild method of the view is called inside the {{view}} auxiliary tag of the template.

Calling appendChild does two things:

Add the view to the childViews array.

Immediately render the child view and add it to the render buffer of the parent view.

You should not call appendChild after the view leaves the rendered state. The template renders "mixed content" (including views and plain text), so when the rendering process is complete, the parent view does not know exactly where to insert the new child view.

In the example above, imagine trying to insert a new view into the childViews array of the parent view. Should it be placed immediately after the closed label of App.MyView? Or after the closed label of the entire view? The answer is not always correct.

Because of this ambiguity, the only way to create a view level is to use the template's {{view}} auxiliary tag, which always inserts the view in the right place relative to any plain text.

Although this mechanism works for most scenarios, occasionally you want the program to directly control a child view of a view. In this case, you can use Ember.ContainerView, which explicitly exposes the API for this purpose.

Container view

The container view does not contain plain text. They consist entirely of child views (which may depend on templates).

ContainerView exposes two public API for modifying its own content:

A writable childViews array into which you can insert Ember.View instances.

A currentView property that inserts the new value into the child view array when set. If an earlier currentView value exists, it is removed from the childViews array.

Here is an example of creating a new view with childViews API, starting with the hypothetical DescriptionView, and you can add a new button with the addButton method at any time:

App.ToolbarView = Ember.ContainerView.create ({init: function () {var childViews = this.get ('childViews'); var descriptionView = App.DescriptionView.create (); childViews.pushObject (descriptionView); this.addButton (); return this._super ();}, addButton: function () {var childViews = this.get (' childViews'); var button = Ember.ButtonView.create (); childViews.pushObject (button);}}))

As you can see in the example above, we initialize the ContainerView with two views and can add additional views at run time. There is a convenient shortcut to setting the view without overriding the init method:

App.ToolbarView = Ember.ContainerView.create ({childViews: ['descriptionView',' buttonView'], descriptionView: App.DescriptionView, buttonView: Ember.ButtonView, addButton: function () {var childViews = this.get ('childViews'); var button = Ember.ButtonView.create (); childViews.pushObject (button);}})

As above, when using this shorthand method, you specify childViews as an array of strings. During initialization, each string is used as a keyword when looking for a view instance or class. That view is automatically instantiated and, if necessary, added to the childViews array.

{{# if controller.isAuthenticated}} Welcome {{controller.name}} {{/ if}} {{# with controller.user}}

You have {{notificationCount}} notifications.

{{/ with}}

In the above template, when the isAuthenticated attribute changes from false to true, Ember renders the block again, using the original external scope as its context.

The {{# with}} auxiliary tag modifies the context of its block to the user attribute of the current controller. When the user property is modified. Ember re-renders the block and uses the new value of controller.user as its upper and lower parts.

View scope

In addition to the Handlebars context, templates in Ember also have the concept of the current view. Regardless of the current context, the view property is always referenced to the nearest view.

Note that the view attribute does not refer to internal views created by block expressions such as {{# if}}. This allows you to distinguish between Handlebars contexts, which work in the same way in Handlebars as at the view level.

Because view points to an instance of Ember.View, you can use expressions such as view.propertyName to access any property on the view. You can use view.parentView to access the parent view of the view.

For example, imagine that you have a view with the following properties:

App.MenuItemView = Ember.View.create ({templateName: 'menu_item_view', bulletText:' *'})

…… And the following template:

{{# with controller}} {{view.bulletText}} {{name}} {{/ with}}

Although the Handlebars context has become the current controller, you can still use view.bulletText to access the bulletText of the view.

Template variable

So far, we have encountered the controller attribute in the Handlebars template. Where did it come from?

Handlebars contexts in Ember can inherit variables in their parent context. Before Ember looks for variables in the current context, it first checks its template variables. When a view creates a new Handlebars scope, they automatically inherit the variables of their parent scope.

Ember defines these view and controller variables, so when an expression uses view or controller variable names, they are always found first.

As mentioned above, Ember sets the view variable in the Handlebars context, whenever the {{# view}} auxiliary tag is used in the template. At first, Ember set the view variable to the view that is rendering the template.

Ember sets the controller variable in the Handlebars context, regardless of whether the rendered view is stored in the controller property. If the view does not have a controller property, it inherits the controller variable from the view that owns the property closest in time.

Other variables

The Handlebars auxiliary tag in Ember also specifies variables. For example, the {{# with controller.person as tom}} form specifies a tom variable whose descendant scope is accessible. Even if a subcontext has a tom attribute, the tom variable abolishes it.

The biggest advantage of this form is that it allows you to simplify long paths without losing access to the parent scope.

In the {{# each}} auxiliary tags, it is particularly important to provide the form {{# each person in people}}. In this form, the descendant context can access the person variable, but leave the same scope where the template calls each.

{{# with controller.preferences}} Title {{# each person in controller.people}} {{! Prefix here is controller.preferences.prefix}} {{prefix}}: {{person.fullName}} {{/ each}} {{/ with}}

Note that these variables inherit those in ContainerView, even if they are not part of the Handlebars context hierarchy.

Access template variables from the view

In most cases, you will need to access these template variables from the template. In some unusual scenarios, you may want to access scoped variables in the JavaScript code of the view.

You can do this by accessing the templateVariables property of the view, which returns a JavaScript object that contains the variables that exist in its action when the view is rendered. ContainerView can also access this property, which points to the template variable of the view on which the most recent template depends in time.

Currently, you cannot observe or bind a path that contains templateVariables.

Thank you for your reading, the above is the content of "Ember.js View layer Analysis", after the study of this article, I believe you have a deeper understanding of the problem of Ember.js View layer Analysis, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report