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.
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.
Public classes and class members are available for use by any other class or application code and may be relied upon as a stable and persistent within major product versions. Public classes and members may safely be extended via a subclass.
Protected class members are stable public
members intended to be used by the
owning class or its subclasses. Protected members may safely be extended via a subclass.
Private classes and class members are used internally by the framework and are not intended to be used by application developers. Private classes and members may change or be omitted from the framework at any time without notice and should not be relied upon in application logic.
static
label next to the
method name. *See Static below.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).
Let's look at each part of the member row:
lookupComponent
in this example)( item )
in this example)Ext.Component
in this case). This may be omitted for methods that do not
return anything other than undefined
or may display as multiple possible values
separated by a forward slash /
signifying that what is returned may depend on the
results of the method call (i.e. a method may return a Component if a get method calls is
successful or false
if unsuccessful which would be displayed as
Ext.Component/Boolean
).PROTECTED
in
this example - see the Flags section below)Ext.container.Container
in this example). The source
class will be displayed as a blue link if the member originates from the current class
and gray if it is inherited from an ancestor or mixed-in class.view source
in the example)item : Object
in the example).undefined
a "Returns" section
will note the type of class or object returned and a description (Ext.Component
in the
example)Available since 3.4.0
- not pictured in
the example) just after the member descriptionDefaults to: false
)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.
classInstance.method1().method2().etc();
false
is returned from
an event handler- 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
- 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
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.
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.
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.
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.
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:
Ext.button.Button
class has an alternate class name of Ext.Button
). Alternate class
names are commonly maintained for backward compatibility.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.
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:
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.
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.
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 for components is the process of connecting data from an
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.
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.
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.
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}'
}]
});
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.
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}'
}
}]
});
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.
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.
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
.
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
.
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
}
}
});
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.
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.
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;
}
}
}
});
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)
});
}
}
}
});
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.
For more information about the viewModels, please take a few minutes to check out our ViewModel Internals guide.