Docs Help

Terms, Icons, and Labels

Many classes have shortcut names used when creating (instantiating) a class with a configuration object. The shortcut name is referred to as an alias (or xtype if the class extends Ext.Component). The alias/xtype is listed next to the class name of applicable classes for quick reference.

Access Levels

Framework classes or their members may be specified as private or protected. Else, the class / member is public. Public, protected, and private are access descriptors used to convey how and when the class or class member should be used.

Member Types

Member Syntax

Below is an example class member that we can disect to show the syntax of a class member (the lookupComponent method as viewed from the Ext.button.Button class in this case).

lookupComponent ( item ) : Ext.Component
protected

Called when a raw config object is added to this container either during initialization of the items config, or when new items are added), or {@link #insert inserted.

This method converts the passed object into an instanced child component.

This may be overridden in subclasses when special processing needs to be applied to child creation.

Parameters

item :  Object

The config object being added.

Returns
Ext.Component

The component to be added.

Let's look at each part of the member row:

Member Flags

The API documentation uses a number of flags to further commnicate the class member's function and intent. The label may be represented by a text label, an abbreviation, or an icon.

Class Icons

- Indicates a framework class

- A singleton framework class. *See the singleton flag for more information

- A component-type framework class (any class within the Ext JS framework that extends Ext.Component)

- Indicates that the class, member, or guide is new in the currently viewed version

Member Icons

- Indicates a class member of type config

- Indicates a class member of type property

- Indicates a class member of type method

- Indicates a class member of type event

- Indicates a class member of type theme variable

- Indicates a class member of type theme mixin

- Indicates that the class, member, or guide is new in the currently viewed version

Class Member Quick-Nav Menu

Just below the class name on an API doc page is a row of buttons corresponding to the types of members owned by the current class. Each button shows a count of members by type (this count is updated as filters are applied). Clicking the button will navigate you to that member section. Hovering over the member-type button will reveal a popup menu of all members of that type for quick navigation.

Getter and Setter Methods

Getting and setter methods that correlate to a class config option will show up in the methods section as well as in the configs section of both the API doc and the member-type menus just beneath the config they work with. The getter and setter method documentation will be found in the config row for easy reference.

History Bar

Your page history is kept in localstorage and displayed (using the available real estate) just below the top title bar. By default, the only search results shown are the pages matching the product / version you're currently viewing. You can expand what is displayed by clicking on the button on the right-hand side of the history bar and choosing the "All" radio option. This will show all recent pages in the history bar for all products / versions.

Within the history config menu you will also see a listing of your recent page visits. The results are filtered by the "Current Product / Version" and "All" radio options. Clicking on the button will clear the history bar as well as the history kept in local storage.

If "All" is selected in the history config menu the checkbox option for "Show product details in the history bar" will be enabled. When checked, the product/version for each historic page will show alongside the page name in the history bar. Hovering the cursor over the page names in the history bar will also show the product/version as a tooltip.

Search and Filters

Both API docs and guides can be searched for using the search field at the top of the page.

On API doc pages there is also a filter input field that filters the member rows using the filter string. In addition to filtering by string you can filter the class members by access level, inheritance, and read only. This is done using the checkboxes at the top of the page.

The checkbox at the bottom of the API class navigation tree filters the class list to include or exclude private classes.

Clicking on an empty search field will show your last 10 searches for quick navigation.

API Doc Class Metadata

Each API doc page (with the exception of Javascript primitives pages) has a menu view of metadata relating to that class. This metadata view will have one or more of the following:

Expanding and Collapsing Examples and Class Members

Runnable examples (Fiddles) are expanded on a page by default. You can collapse and expand example code blocks individually using the arrow on the top-left of the code block. You can also toggle the collapse state of all examples using the toggle button on the top-right of the page. The toggle-all state will be remembered between page loads.

Class members are collapsed on a page by default. You can expand and collapse members using the arrow icon on the left of the member row or globally using the expand / collapse all toggle button top-right.

Desktop -vs- Mobile View

Viewing the docs on narrower screens or browsers will result in a view optimized for a smaller form factor. The primary differences between the desktop and "mobile" view are:

Viewing the Class Source

The class source can be viewed by clicking on the class name at the top of an API doc page. The source for class members can be viewed by clicking on the "view source" link on the right-hand side of the member row.

Ext JS 6.2.0


top

View Models & Data Binding

Data binding and the ViewModel that powers it are powerful additions to Ext JS.
Together they enable you to do more with less code and write in a much more declarative style while helping you maintain a clean separation of concerns.

A ViewModel is a class that manages a data object. It then allows those interested in this data to bind to it and be notified when it changes. The ViewModel, like ViewController, is owned by the view that references it. Because ViewModels are associated with a view, they are also able to link to a parent ViewModel owned by ancestor components in the component hierarchy. This allows child views to simply "inherit" the data of their parent ViewModel.

Components have a bind config that allows them to associate many of their configs to data from the ViewModel. Using bind, you can be sure that the appropriate component config will have its setter method called whenever the bound value changes - no custom event handlers needed.

In this guide, we will walk through some examples that show the power of ViewModels and Data Binding.

Component Binding

Probably the best way to understand binding and ViewModels is to look at the various ways you can use bindings on components. This is because components are the primary consumers of data binding and components are something familiar to Ext JS developers. In order for binding to work, however, we do need a ViewModel so we will reference one for now and define it later.

Binding and Configs

Binding for components is the process of connecting data from a Ext.app.ViewModel to a component's config properties. Any configuration a component possesses may be bound to, so long as it has a setter method. For instance, since there's a setTitle() method on Ext.panel.Panel, you can bind to the title configuration.

In this example, we will set the width of a panel based on the results of our ViewModel's data. We can bind our data to width since setWidth() is a method that Ext.panel.Panel may use.

Ext.create('Ext.panel.Panel', {
    title: 'Simple Form',

    viewModel: {
        type: 'test'  // we will define the "test" ViewModel soon
    },

    bind: {
        html: '<p>Hello {name}</p>',
        width: '{someWidth}'
    }
});

The syntax used for bind values is very similar to Ext.Template. You can put text around tokens inside braces. You can use formatters as well, as with Ext.Template. Unlike Ext.Template, however, when a template is a single token (like '{someWidth}') then its value is passed unmodified. That is, it is not converted to a string.

We will see later how the data for name and someWidth are defined. The above example simply shows how the data is consumed by a component.

Binding Boolean Configs

Many configs you will want to bind are boolean values, such as visible (or hidden), disabled, checked, and pressed. Bind templates support boolean negation "inline" in the template. Other forms of algebra are relegated to formulas (see below), but boolean inversion is common enough there is special provision for it. For example:

Ext.create('Ext.panel.Panel', {
    title: 'Simple Form',

    viewModel: {
        type: 'test'
    },

    items: [{
        xtype: 'button',
        bind: {
            hidden: '{!name}'  // negated
        }
    }]
});

This also highlights how values in single token templates are not converted to strings. In the above, while "name" is a string value, it is negated using "!" and becomes a boolean value and that is passed to the setHidden method on the button.

Binding and Priority

Bound config properties will always overwrite configurations set statically on the component as soon as the bound result is available. In other words, bound data will always take priority over static configuration values but may be delayed in order to fetch that data.

Ext.create('Ext.panel.Panel', {
    title: 'Simple Form',

    viewModel: {
        type: 'test'
    },

    bind: {
        title: 'Hello {name}'
    }
});

Once the binding for "name" is delivered, the "Simple Form" title will be replaced.

Binding and Child Components

One of the most helpful parts of binding is that all of the children of the component with a viewModel also have access to their container's data.

In this example, you can see the children items of a form may be bound to their container's viewModel.

Ext.create('Ext.panel.Panel', {
    title: 'Simple Form',

    viewModel: {
        type: 'test'
    },

    layout: 'form',
    defaultType: 'textfield',

    items: [{
        fieldLabel: 'First Name',
        bind: '{firstName}' // uses "test" ViewModel from parent
    },{
        fieldLabel: 'Last Name',
        bind: '{lastName}'
    }]
});

Two Way Binding

The bind config also allows for two-way data binding, which translates to live synchronization of data between the view and the model. Any data change made in the view is automatically written back to the model. This automatically updates any other components that may be bound to that same data. Note: Not all configs will publish their value to the ViewModel when changed.
Configs defined within the publish and twoWayBindable array will publish changes back up to the ViewModel. Values may also be published in Component / application logic using the publishState method.

In the above example, because the "firstName" and "lastName" properties were bound to text fields, changes in the input would be written back to the ViewModel. To see how all this connects, it is time to complete the example and define the ViewModel.

Ext.define('MyApp.view.TestViewModel', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.test', // connects to viewModel/type below

    data: {
        firstName: 'John',
        lastName: 'Doe'
    },

    formulas: {
        // We'll explain formulas in more detail soon.
        name: function (get) {
            var fn = get('firstName'), ln = get('lastName');
            return (fn && ln) ? (fn + ' ' + ln) : (fn || ln || '');
        }
    }
});

Ext.define('MyApp.view.TestView', {
    extend: 'Ext.panel.Panel',
    layout: 'form',

    // Always use this form when defining a view class. This
    // allows the creator of the component to pass data without
    // erasing the ViewModel type that we want.
    viewModel: {
        type: 'test'  // references alias "viewmodel.test"
    },

    bind: {
        title: 'Hello {name}'
    },

    defaultType: 'textfield',
    items: [{
        fieldLabel: 'First Name',
        bind: '{firstName}'
    },{
        fieldLabel: 'Last Name',
        bind: '{lastName}'
    },{
        xtype: 'button',
        text: 'Submit',
        bind: {
            hidden: '{!name}'
        }
    }]
});

Ext.onReady(function () {
    Ext.create('MyApp.view.TestView', {
        renderTo: Ext.getBody(),
        width: 400
    });
});

When the above panel is displayed we can see that changes in the text fields are reflected in the panel title as well as the hidden state of the "Submit" button.

Binding and Component State

Sometimes a component's state, e.g. the checked state of a checkbox or the selected record of a grid, is interesting to other components. When a component is assigned a reference to identify it, that component will publish some of its key properties in the ViewModel.

In this example, we have the "Admin Key" textfield's disabled config bound to the the checked state of the checkbox. This results in the textfield being disabled until the checkbox is checked. This sort of behavior is well suited for dynamic forms like this:

Ext.create('Ext.panel.Panel', {
    title: 'Sign Up Form',

    viewModel: {
        type: 'test'
    },

    items: [{
        xtype: 'checkbox',
        boxLabel: 'Is Admin',
        reference: 'isAdmin'
    },{
        xtype: 'textfield',
        fieldLabel: 'Admin Key',
        bind: {
            disabled: '{!isAdmin.checked}'
        }
    }]
});

Bind Descriptors

So far we've seen three basic forms of bind descriptors:

  • {firstName} - A "direct bind" to some value in the ViewModel. This value is passed through unmodified and so may arrive as any type of data.

  • Hello {name} - A "bind template" always produces a string by inserting the textual value of the various bind expressions. Bind templates can also use formatters as with a normal Ext.Template, for example: 'Hello {name:capitalize}'.

  • {!isAdmin.checked} - The negated form of a direct bind, useful for binding to boolean config properties.

Beyond these basic forms, there are a few specialized forms of bind descriptors that you can use.

Multi-Bind

If an object or array is given as a bind descriptor, the ViewModel will produce an object or array of the same shape but with the various properties replaced by their bind result. For example:

Ext.create('Ext.Component', {
    bind: {
        data: {
            fname: '{firstName}',
            lname: '{lastName}'
        }
    }
});

This sets the "data" config for the component to be an object with two properties whose values are set from the ViewModel.

Record Bind

When a particular record is desired, say "User" with id of 42, the bind descriptor is an object but with a "reference" property. For example:

Ext.create('Ext.Component', {
    bind: {
        data: {
            reference: 'User',
            id: 42
        }
    }
});

In this case, the component's tpl will receive the User record once it is loaded. This currently requires the use of an Ext.data.Session.

Association Bind

Similar to a record bind, one can also bind to an association, say the User's Address record:

Ext.create('Ext.Component', {
    bind: {
        data: {
            reference: 'User',
            id: 42,
            association: 'address'
        }
    }
});

In this case, the component's tpl will receive the User's "address" record once it is loaded. This also currently requires the use of an Ext.data.Session.

Bind Options

The final form of bind descriptor is used when you need to describe binding options. The following example shows how to receive only one value for a binding and then disconnect automatically.

Ext.create('Ext.Component', {
    bind: {
        data: {
            bindTo: '{name}',
            single: true
        }
    }
});

The bindTo property is the second reserved name in a bind descriptor object (the first being reference). When present, it signifies that the value of bindTo is the actual bind descriptor and the other properties are configuration options for the binding.

The other bind option currently supported is deep. This option is used when binding to an object so that the binding will be notified when any property of that object changes, not just the reference itself. This would be most useful when binding to the data config of a component since these often receive objects.

Ext.create('Ext.Component', {
    bind: {
        data: {
            bindTo: '{someObject}',
            deep: true
        }
    }
});

Creating ViewModels

Now that we've gotten a taste for how components use ViewModels and gotten a glimpse of what ViewModels look like, it is time to learn more about ViewModels and what they offer.

As stated previously, a ViewModel is a manager for an underlying data object. It is the content of that object that is being consumed by bind statements. The inheritance of data from a parent ViewModel to its child ViewModels leverages the JavaScript prototype chain. This is covered in more detail in the View Model Internals guide, but in a nutshell a child ViewModel's data object has the data object of its parent ViewModel as its prototype.

Formulas

In addition to holding data and providing binding, ViewModels also offer a convenient way to calculate data from other data called formulas. Formulas allow you to encapsulate data dependencies in the ViewModel and keep your views free to focus on declaring their structure.

In other words, the data is not changed in the ViewModel's data, but can be displayed differently by transforming it using formulas. This is similar to how the convert configuration works for fields of a traditional data model.

We saw a simple "name" formula in the previous example. In that case, the "name" formula was simply a function that combined two other values from the ViewModel: "firstName" and "lastName".

Formulas can also use the results of other formulas as if the result were just another data property. For example:

Ext.define('MyApp.view.TestViewModel2', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.test2',

    formulas: {
        x2y: function (get) {
            return get('x2') * get('y');
        },

        x2: function (get) {
            return get('x') * 2;
        }
    }
});

The "x2" formulas uses an "x" property to define "x2" as "x * 2". The "x2y" formulas uses both "x2" and "y". This definition means that if "x" changes, "x2" is recalculated and then "x2y". But if "y" changes, only "x2y" needs to be recalculated.

Formulas With Explicit Binding

In the above examples, the dependencies for the formula were found via inspecting the function, however this isn't always the best solution. An explicit bind statement can be used, which will return a simple object when all the values in the bind have presented.

Ext.define('MyApp.view.TestViewModel2', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.test2',

    formulas: {
        something: {
            bind: {
                x: '{foo.bar.x}',
                y: '{bar.foo.thing.zip.y}'
            },

            get: function (data) {
                return data.x + data.y;
            }
        }
    }
});

Two-Way Formulas

When a formula is invertible we can also define a set method to be called when the value is set (say via two-way binding). Since the "this" pointer is the ViewModel, the set method can call this.set() to set the appropriate properties in the ViewModel.

A revised version of the TestViewModel below shows how "name" can be defined as a two-way formula.

Ext.define('MyApp.view.TestViewModel', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.test',

    formulas: {
        name: {
            get: function (get) {
                var fn = get('firstName'), ln = get('lastName');
                return (fn && ln) ? (fn + ' ' + ln) : (fn || ln || '');
            },

            set: function (value) {
                var space = value.indexOf(' '),
                    split = (space < 0) ? value.length : space;

                this.set({
                    firstName: value.substring(0, split),
                    lastName: value.substring(split + 1)
                });
            }
        }
    }
});

Recommendations

With all the power of ViewModels, formulas, and data binding, it can be easy to overuse or abuse these mechanisms and create an application that is hard to understand or debug, or is slow to update or leaks memory. To help avoid these issues and still get the most out of ViewModels here are some recommended techniques:

  • Always use the following form when configuring your viewModel. This is important because of the way the Config System merges config values. With this form, the "type" property is preserved during the merge.

      Ext.define('MyApp.view.TestView', {
          //...
          viewModel: {
              type: 'test'
          },
      });
    
  • Make names obvious, especially in high level ViewModels. In JavaScript we rely on textual searching so pick names that make finding usages possible. The more code that might use a property the more important it is to pick meaningful or even unique names.

  • Don't nest data in objects more deeply than necessary. Multiple top-level objects stored in the ViewModel will require less bookkeeping compared to one object with lots of nested sub-objects. Further, this will help make dependencies on this information more obvious than if many components depend on some large containing object. There are reasons to share objects, but remember the ViewModel is just a managed object so you can use its properties too.

  • Use child ViewModels to allow data to be cleaned up with the components that needed it. If you put all data in high-level ViewModels, that data will likely never be removed even when the child views that needed it have been destroyed. Instead, create ViewModels for the child view and pass data into its ViewModel.

  • Don't create child ViewModels unless they are actually needed. Each ViewModel instance takes time to create and memory to manage. If a child view does not need data unique to itself, it can simply use the ViewModel it inherits from its container. See the previous recommendation though, because it is better to create child ViewModels when they are needed than pollute a parent ViewModel and effectively leak memory.

  • Use formulas instead of repeating binds. If you followed how formulas are ways to combine the results of several bound values you can probably see how using a formula would help reduce the number of dependencies compared to directly using those same values in many places. For example, one formula with 3 dependencies and 4 users make 3 + 4 = 7 dependencies to track in the ViewModel. Compared to 4 users with those 3 dependencies on themselves we would have 3 * 4 = 12 dependencies. Fewer dependencies to track means less memory used and time to process them.

  • Don't chain formulas too deeply. This is not so much a run-time cost issue as a code clarity issue. Chained formulas can mask the connections between data and components making it hard to understand what is happening.

  • Two-way formulas must be stable. Say formula "foo" is a calculation of value "bar". When "foo" is set it will set "bar" by inverting the formula from its get method. The result is stable if the get method will produce the exact same value for "foo" that is now being set. If it does not, the process will cycle until it reaches a stable point or it will continue indefinitely. Neither result is likely desirable.

Further Reading

For more information about the viewModels, please take a few minutes to check out our ViewModel Internals guide.

Ext JS 6.2.0