June 27, 2011 Jay Garcia
Brand new to Ext JS 4.0 is a class Loader system that makes use of the new dependency system. These two powerful new features allow you to create extensive applications that allow the browser to download digest code as necessary.
Today, we’ll be looking at creating a small application that makes use of this new class Loader system, exercising the dependency management system. Along the way, we’ll discuss various configuration options for the Ext Loader system.
Learn the latest about Ext JS and HTML5/JavaScript for three intensive days with 60+ sessions, 3+ parties and more at SenchaCon 2013. Register today!
Before we begin, we will fast forward into the future and peek at the results. Doing so will allow us to identify classes that we’ll need to extend.
Our simple app will contain two-way binding to a grid Panel and form Panels, respectively called UserGridPanel and UserFormPanel. The UserGridPanel will require the creation of a Model and a Store to support its operations. The UserGridPanel and UserFormPanel will be rendered in and managed by an extension to the Ext JS Window class known as UserEditorWindow. All of these classes will be under a namespace called MyApp.
Before we can begin with code, we’ll have to layout the directory structure. Here’s what the folder, organized by namespace, looks like.
As you can see with the illustration above, we have MyApp split according to namespace groupings or “packages.” In the end, our entire application will have an inter-dependency model that will function like the following illustration.
Even though we’re organizing our code to mimic the MVC pattern from Ext JS 4, we will not be using it for this example.
We’ll begin by filling out the contents of the index.html file, which sits at the root of our application and includes all that we’ll need to bootstrap our application.
Ext 4 Loader
The index.html file contains the necessary link tag for the CSS file for Ext JS 4. This you’ve seen many times, but the inclusion of the ext-debug.js javascript file is something that might raise an eyebrow, as you’re probably used to including ext-all-debug.js for development and ext-all.js for production.
There are a few choices that Ext JS gives you out of the box, each with their own distinct advantages or disadvantages.
Here is a rundown of each of the files.
ext-all-debug-w-comments.js
- Debug version of the entire framework, with comments. This is the largest file of the bunch and requires the most processing to display in browsers.
ext-all-debug.js
- Same as above, but without the comments. Still very large, but very good for debugging in most cases.
ext-all.js
- The entire framework concatenated and minified. Debugging is impossible with this file, so it should be used for production systems only.
ext-debug.js
- This file contains the Ext JS foundation and whitespace. With this file, you can remote load all of Ext JS as needed, and it provides the best debugging experience. The tradeoff is that it’s the slowest.
ext.js
- The minified version of
ext-debug.js
.
Our index.html file made use of ext-debug.js, which is the bare minimum to get dynamic loading working. Later on, we’ll show you how you can use ext-all versions of the framework, allowing you to get the best of both worlds..
Since the UserGridPanel class requires a Model and data Store, we’ll have to develop those supporting classes first. We’ll begin by creating the Model and Store.
Ext.define('MyApp.models.UserModel', { extend : 'Ext.data.Model', fields : [ 'firstName', 'lastName', 'dob', 'userName' ] });
The above example contains code to extend Ext.data.Model, creating our UserModel class. By virtue of extending the data.Model class, Ext JS will dynamically load it for us and then create the UserModel class after it and the dependencies for data.Model have loaded.
Next, we’ll create the UserStore class, an extension to Ext.data.Store.
Ext.define('MyApp.stores.UserStore', { extend : 'Ext.data.Store', singleton : true, requires : ['MyApp.models.UserModel'], model : 'MyApp.models.UserModel', constructor : function() { this.callParent(arguments); this.loadData([ { firstName : 'Louis', lastName : 'Dobbs', dob : '12/21/34', userName : 'ldobbs' }, { firstName : 'Sam', lastName : 'Hart', dob : '03/23/54', userName : 'shart' }, { firstName : 'Nancy', lastName : 'Garcia', dob : '01/18/24', userName : 'ngarcia' } ]); } });
When creating our singleton UserStore class, we add a new requires key to the UserStore prototype. The requires key is a list that instructs Ext JS to fetch any required classes before our class is instantiated. In this case, we list our UserModel class as a required class.
By virtue of our UserModel class being defined in our store’s model property, Ext JS will load it automatically. It is my opinion that listing the class in the requires key allows your code to be self-documenting, reminding you that the UserModel class is required.
OK. We have our foundation classes created for the views.UserGridPanel, which means we can move on and create the UserGridPanel itself.
Ext.define('MyApp.views.UsersGridPanel', { extend : 'Ext.grid.Panel', alias : 'widget.UsersGridPanel', requires : ['MyApp.stores.UserStore'], initComponent : function() { this.store = MyApp.stores.UserStore; this.columns = this.buildColumns(); this.callParent(); }, buildColumns : function() { return [ { header : 'First Name', dataIndex : 'firstName', width : 70 }, { header : 'Last Name', dataIndex : 'lastName', width : 70 }, { header : 'DOB', dataIndex : 'dob', width : 70 }, { header : 'Login', dataIndex : 'userName', width : 70 } ]; } });
When looking at the above code, keep a keen eye on the requires key. Notice how we added the UserStore as a required class. We just configured a direct dependency relationship between our grid Panel extension and our own data Store extension.
Next, we’ll create the form Panel extension.
Ext.define('MyApp.views.UserFormPanel', { extend : 'Ext.form.Panel', alias : 'widget.UserFormPanel', bodyStyle : 'padding: 10px; background-color: #DCE5F0;' + ' border-left: none;', defaultType : 'textfield', defaults : { anchor : '-10', labelWidth : 70 }, initComponent : function() { this.items = this.buildItems(); this.callParent(); }, buildItems : function() { return [ { fieldLabel : 'First Name', name : 'firstName' }, { fieldLabel : 'Last Name', name : 'lastName' }, { fieldLabel : 'DOB', name : 'dob' }, { fieldLabel : 'User Name', name : 'userName' } ]; } });
Because our UserForm class does not technically require any of the classes we’ve developed thus far, we don’t need to add a requires directive.
We’re almost done. We need to create the UserEditorWindow class and then apps.js, which will launch our application. The code for the UserEditorWindow class is below. The class is pretty lengthy because it contains the code for the grid to form panel binding, so please bear with me on this.
Ext.define('MyApp.views.UserEditorWindow', { extend : 'Ext.Window', requires : ['MyApp.views.UsersGridPanel','MyApp.views.UserFormPanel'], height : 200, width : 550, border : false, layout : { type : 'hbox', align : 'stretch' }, initComponent : function() { this.items = this.buildItems(); this.buttons = this.buildButtons(); this.callParent(); this.on('afterrender', this.onAfterRenderLoadForm, this); }, buildItems : function() { return [ { xtype : 'UsersGridPanel', width : 280, itemId : 'userGrid', listeners : { scope : this, itemclick : this.onGridItemClick } }, { xtype : 'UserFormPanel', itemId : 'userForm', flex : 1 } ]; }, buildButtons : function() { return [ { text : 'Save', scope : this, handler : this.onSaveBtn }, { text : 'New', scope : this, handler : this.onNewBtn } ]; }, onGridItemClick : function(view, record) { var formPanel = this.getComponent('userForm'); formPanel.loadRecord(record) }, onSaveBtn : function() { var gridPanel = this.getComponent('userGrid'), gridStore = gridPanel.getStore(), formPanel = this.getComponent('userForm'), basicForm = formPanel.getForm(), currentRec = basicForm.getRecord(), formData = basicForm.getValues(), storeIndex = gridStore.indexOf(currentRec), key; //loop through the record and set values currentRec.beginEdit(); for (key in formData) { currentRec.set(key, formData[key]); } currentRec.endEdit(); currentRec.commit(); // Add and select if (storeIndex == -1) { gridStore.add(currentRec); gridPanel.getSelectionModel().select(currentRec) } }, onNewBtn : function() { var gridPanel = this.getComponent('userGrid'), formPanel = this.getComponent('userForm'), newModel = Ext.ModelManager.create({}, 'MyApp.models.UserModel'); gridPanel.getSelectionModel().clearSelections(); formPanel.getForm().loadRecord(newModel) }, onAfterRenderLoadForm : function() { this.onNewBtn(); } });
The code for our UserEditorWindow contains a lot of stuff to manage the full binding lifecycle of the UserGridPanel and UserFormPanel classes. In order to instruct Ext JS to load the two classes we created earlier, we have to list them in the requires list.
We’re now ready to tie this all together in the app.js file. To maximize our learning, we’ll be making three passes at this. We’ll begin with the simplest configuration first, and add more as we progress.
Ext.Loader.setPath('MyApp', 'js/MyApp'); Ext.onReady(function() { Ext.create('MyApp.views.UserEditorWindow').show(); });
Initially, our app.js file instructs Ext JS to add a path for the MyApp namespace. We do this via the Ext.loader.setPath call, passing in the namespace as the first argument, and the relative path to the page being loaded.
Next, we call Ext.onReady, passing in an anonymous function, which contains a call to Ext.create. Ext.create is responsible for lazy instantiating classes with Ext JS 4.0. We pass in the String representation of our UserEditorWindow class to be instantiated. Since we don’t need a reference to the instance of our class and want to show it immediately, we chain a show method call.
If we view this in our page (http://moduscreate.com/senchaarticles/01/pass1.html), we see that the UI renders, but it’s pretty slow and we get a warning message inside of FireBug from Ext JS!
Ext JS barks at us because we’re not using the Loader system in the most optimized way. We’ll revisit this issue in a second. However, this is a great learning opportunity that we should capitalize on!
I’ve configured my FireBug instance to display XHR requests inside of the console, which allows me to see all such requests without having to switch to the Net tab. This gives us chance to filter though all of the Ext JS classes loaded, to watch the class dependency system work, as we’ve instructed it to do!
Simply type in “User” in the FireBug console filter and you’ll see how it all comes together.
As we can see, the UserEditorWindow class is first loaded, which requires the UserGridPanel. The UserGridPanel requires the UserStore and UserModel classes. Lastly, the UserFormPanel class is loaded.
I mentioned earlier that Ext JS barked because we’re not using the Loader system in the most optimized way. This is because dependencies identified after Ext.onReady has fired are loaded via synchronous XHR, which is not the most efficient way and is not easy to debug at all.
To remedy this issue, we’ll have to modify app.js to instruct Ext JS to load our classes in a way that is both performant and easier to debug
Ext.Loader.setPath('MyApp', 'js/MyApp'); Ext.require('MyApp.views.UserEditorWindow'); Ext.onReady(function() { Ext.create('MyApp.views.UserEditorWindow').show(); });
To enable faster loading of our classes and afford a better debugging experience, we simply have to add a call to Ext.require above our Ext.onReady, instructing Ext JS to require our UserEditorWindow class. Doing this allows Ext JS to inject script tags to the HEAD of the document, allowing resources to load before Ext.onReady fires.
Visit http://moduscreate.com/senchaarticles/01/pass2.html to see this working. After loading the page, you’ll notice that Ext JS will no longer fire warning messages to the console.
What we’ve done is enable lazy loading of Ext JS framework and our application classes. While this is the best for debugging, the page render times can be very painful for rapid debugging sessions. Why – you ask?
The simple answer is that the number of resources loaded is immense. In our example, Ext JS makes 193 total requests to the web server for JavaScript resources, most of which are cached.
We created six JavaScript files (five classes and app.js). That means that to load the required Ext JS files, the browser had to make 187 requests. This solution is viable but not ideal for many and works best when you’re developing in a local dev environment.
To remedy this issue, we can create a hybrid situation, where we load the entire framework via ext-all-debug and then load our classes dynamically. To do this, we’ll need to modify two files.
First, we’ll need to modify our index.html to include ext-all-debug.js instead of ext-debug.js
<script type="text/javascript" src="js/ext-4.0.1/ext-all-debug.js"></script>
Next, we’ll need to modify app.js to enable Ext Loader.
(function() { Ext.Loader.setConfig({ enabled : true, paths : { MyApp : 'js/MyApp' } }); Ext.require('MyApp.views.UserEditorWindow'); Ext.onReady(function() { Ext.create('MyApp.views.UserEditorWindow').show(); }); })();
We enable Ext Loader by calling Loader.setConfig, passing in an anonymous object, which has its enabled property set to true and our namespace set to path mapping.
By modifying our app.js file, we allowed our application to load and render in just over a second in a local dev environment
And there you have it! We created a simple application allowing us to use the new Ext JS class dependency system as well as Loader. You may download these lab files viahttp://moduscreate.com/senchaarticles/01/files.zip.
댓글 없음:
댓글 쓰기