Browse Source

框架结构调整/导航菜单调整

zhuth 6 years ago
parent
commit
22ba97923a

+ 0 - 3
app.js

@@ -12,7 +12,4 @@ Ext.application({
         // so that application classes do not need to require each other.
         'uas.*'
     ],
-
-    // The name of the initial view to create.
-    mainView: 'uas.view.main.Main'
 });

+ 3 - 1
app.json

@@ -31,7 +31,9 @@
      * application resides (the same folder in which this file is located).
      */
     "classpath": [
-        "app"
+        "${app.dir}/app",
+        "${app.dir}/ux",
+        "${app.dir}/samples"
     ],
 
     /**

+ 7 - 5
app/Application.js

@@ -15,11 +15,6 @@ Ext.define('uas.Application', {
     defaultToken: '',
 
     init: function() {
-
-        Ext.create('uas.store.Navigation', {
-            storeId: 'navigation'
-        });
-
         Ext.tip.QuickTipManager.init(null, {
             showOnTap: true
         });
@@ -29,6 +24,13 @@ Ext.define('uas.Application', {
         }
     },
 
+    launch: function () {
+        var view = 'uas.view.main.Main';
+        this.setMainView({
+            xclass: view
+        });
+    },
+
     onAppUpdate: function () {
         Ext.Msg.confirm('Application Update', 'This application has an update, reload?',
             function (choice) {

+ 8 - 6
app/controller/Global.js

@@ -2,6 +2,11 @@ Ext.define('uas.controller.Global', {
     extend: 'Ext.app.Controller',
     namespace: 'uas',
 
+    stores: [
+        'Navigation',
+        'Companies'
+    ],
+
     config: {
         control: {
             'navigation-tree': {
@@ -22,12 +27,10 @@ Ext.define('uas.controller.Global', {
 
     beforeHandleRoute: function(target, action) {
         let me = this,
-            store = Ext.StoreMgr.get('navigation'),
-            idx = store.find('target', target),
             className = Ext.ClassManager.getNameByAlias('widget.' + target),
             ViewClass = Ext.ClassManager.get(className);
 
-        if(idx > -1 && !!ViewClass) {
+        if(!!ViewClass) {
             //resume action
             action.resume();
         }else {
@@ -46,9 +49,8 @@ Ext.define('uas.controller.Global', {
 
     handleRoute: function(target) {
         let me = this,
-            store = Ext.StoreMgr.get('navigation'),
-            idx = store.find('target', target),
-            node = store.getAt(idx),
+            store = Ext.StoreMgr.get('Navigation'),
+            node = store.findNode('target', target),
             title = node.get('text'),
             contentPanel = me.getContentPanel();
         

+ 1054 - 0
app/data/Company.js

@@ -0,0 +1,1054 @@
+Ext.define('uas.data.Company', {
+    requires: [
+        'uas.data.Init'
+    ]
+}, function() {
+    var companies = [{
+        "id": 1,
+        "name": "Roodel",
+        "phone": "602-736-2835",
+        "price": 59.47,
+        "priceChange": 1.23,
+        "priceLastChange": "10/8",
+        "industry": "Manufacturing",
+        "desc": "In hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.\n\nNulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.\n\nCras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.",
+        "priceChangePct": 2.11
+    }, {
+        "id": 2,
+        "name": "Voomm",
+        "phone": "662-254-4213",
+        "price": 41.31,
+        "priceChange": 2.64,
+        "priceLastChange": "10/18",
+        "industry": "Services",
+        "desc": "Curabitur at ipsum ac tellus semper interdum. Mauris ullamcorper purus sit amet nulla. Quisque arcu libero, rutrum ac, lobortis vel, dapibus at, diam.",
+        "priceChangePct": 6.83
+    }, {
+        "id": 3,
+        "name": "Dabvine",
+        "phone": "745-225-8364",
+        "price": 29.94,
+        "priceChange": 3.55,
+        "priceLastChange": "10/11",
+        "industry": "Finance",
+        "desc": "Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh.",
+        "priceChangePct": 13.45
+    }, {
+        "id": 4,
+        "name": "Twitterbeat",
+        "phone": "862-540-4332",
+        "price": 89.96,
+        "priceChange": -3.82,
+        "priceLastChange": "10/2",
+        "industry": "Computer",
+        "desc": "Sed ante. Vivamus tortor. Duis mattis egestas metus.",
+        "priceChangePct": -4.07
+    }, {
+        "id": 5,
+        "name": "Lajo",
+        "phone": "351-170-1070",
+        "price": 65.51,
+        "priceChange": 1.48,
+        "priceLastChange": "10/14",
+        "industry": "Manufacturing",
+        "desc": "Vestibulum ac est lacinia nisi venenatis tristique. Fusce congue, diam id ornare imperdiet, sapien urna pretium nisl, ut volutpat sapien arcu sed augue. Aliquam erat volutpat.",
+        "priceChangePct": 2.31
+    }, {
+        "id": 6,
+        "name": "Livetube",
+        "phone": "745-259-7013",
+        "price": 52.34,
+        "priceChange": 0.91,
+        "priceLastChange": "10/3",
+        "industry": "Automotive",
+        "desc": "In hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.",
+        "priceChangePct": 1.77
+    }, {
+        "id": 7,
+        "name": "Flipstorm",
+        "phone": "255-457-6789",
+        "price": 41.81,
+        "priceChange": -1.58,
+        "priceLastChange": "10/9",
+        "industry": "Retail",
+        "desc": "Integer tincidunt ante vel ipsum. Praesent blandit lacinia erat. Vestibulum sed magna at nunc commodo placerat.\n\nPraesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.\n\nMorbi porttitor lorem id ligula. Suspendisse ornare consequat lectus. In est risus, auctor sed, tristique in, tempus sit amet, sem.",
+        "priceChangePct": -3.64
+    }, {
+        "id": 8,
+        "name": "Oloo",
+        "phone": "862-723-7988",
+        "price": 53.27,
+        "priceChange": 2.06,
+        "priceLastChange": "10/14",
+        "industry": "Finance",
+        "desc": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.",
+        "priceChangePct": 4.02
+    }, {
+        "id": 9,
+        "name": "Roombo",
+        "phone": "622-156-8067",
+        "price": 21.53,
+        "priceChange": -4.04,
+        "priceLastChange": "10/13",
+        "industry": "Services",
+        "desc": "Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui.",
+        "priceChangePct": -15.8
+    }, {
+        "id": 10,
+        "name": "Ntags",
+        "phone": "482-558-5069",
+        "price": 34.31,
+        "priceChange": 2.94,
+        "priceLastChange": "10/14",
+        "industry": "Food",
+        "desc": "Sed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.\n\nPellentesque at nulla. Suspendisse potenti. Cras in purus eu magna vulputate luctus.",
+        "priceChangePct": 9.37
+    }, {
+        "id": 11,
+        "name": "Shuffletag",
+        "phone": "145-574-5042",
+        "price": 25.92,
+        "priceChange": 0.77,
+        "priceLastChange": "10/2",
+        "industry": "Food",
+        "desc": "Aenean lectus. Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum.\n\nCurabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.",
+        "priceChangePct": 3.06
+    }, {
+        "id": 12,
+        "name": "Skivee",
+        "phone": "812-555-0295",
+        "price": 50.61,
+        "priceChange": -3.11,
+        "priceLastChange": "10/4",
+        "industry": "Manufacturing",
+        "desc": "Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.",
+        "priceChangePct": -5.79
+    }, {
+        "id": 13,
+        "name": "Tanoodle",
+        "phone": "221-841-0818",
+        "price": 64.26,
+        "priceChange": -2.91,
+        "priceLastChange": "10/1",
+        "industry": "Finance",
+        "desc": "Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.\n\nMauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci. Mauris lacinia sapien quis libero.",
+        "priceChangePct": -4.33
+    }, {
+        "id": 14,
+        "name": "Buzzster",
+        "phone": "542-221-3452",
+        "price": 37.16,
+        "priceChange": -1.09,
+        "priceLastChange": "10/14",
+        "industry": "Computer",
+        "desc": "Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.\n\nCras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.\n\nQuisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.",
+        "priceChangePct": -2.85
+    }, {
+        "id": 15,
+        "name": "Topicblab",
+        "phone": "632-732-0112",
+        "price": 80.68,
+        "priceChange": -3.68,
+        "priceLastChange": "10/12",
+        "industry": "Food",
+        "desc": "Quisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.\n\nPhasellus in felis. Donec semper sapien a libero. Nam dui.\n\nProin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.",
+        "priceChangePct": -4.36
+    }, {
+        "id": 16,
+        "name": "Thoughtworks",
+        "phone": "622-654-8350",
+        "price": 64.59,
+        "priceChange": -2.68,
+        "priceLastChange": "10/16",
+        "industry": "Manufacturing",
+        "desc": "Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui.",
+        "priceChangePct": -3.98
+    }, {
+        "id": 17,
+        "name": "Feedfire",
+        "phone": "622-744-0512",
+        "price": 21.51,
+        "priceChange": -3.72,
+        "priceLastChange": "10/12",
+        "industry": "Food",
+        "desc": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin risus. Praesent lectus.",
+        "priceChangePct": -14.74
+    }, {
+        "id": 18,
+        "name": "Thoughtstorm",
+        "phone": "622-479-3734",
+        "price": 80.48,
+        "priceChange": -2.77,
+        "priceLastChange": "10/18",
+        "industry": "Automotive",
+        "desc": "Duis aliquam convallis nunc. Proin at turpis a pede posuere nonummy. Integer non velit.\n\nDonec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.\n\nDuis bibendum. Morbi non quam nec dui luctus rutrum. Nulla tellus.",
+        "priceChangePct": -3.33
+    }, {
+        "id": 19,
+        "name": "Agivu",
+        "phone": "358-757-5355",
+        "price": 74.05,
+        "priceChange": 0.14,
+        "priceLastChange": "10/4",
+        "industry": "Manufacturing",
+        "desc": "Praesent id massa id nisl venenatis lacinia. Aenean sit amet justo. Morbi ut odio.",
+        "priceChangePct": 0.19
+    }, {
+        "id": 20,
+        "name": "Babbleblab",
+        "phone": "504-149-8727",
+        "price": 37.24,
+        "priceChange": -0.43,
+        "priceLastChange": "10/18",
+        "industry": "Manufacturing",
+        "desc": "Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.",
+        "priceChangePct": -1.14
+    }, {
+        "id": 21,
+        "name": "Thoughtstorm",
+        "phone": "632-278-4707",
+        "price": 71.75,
+        "priceChange": -0.83,
+        "priceLastChange": "10/2",
+        "industry": "Computer",
+        "desc": "Duis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.\n\nIn hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo.",
+        "priceChangePct": -1.14
+    }, {
+        "id": 22,
+        "name": "Skalith",
+        "phone": "145-310-2923",
+        "price": 52.57,
+        "priceChange": 3.79,
+        "priceLastChange": "10/17",
+        "industry": "Food",
+        "desc": "Duis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.\n\nIn hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo.\n\nAliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis.",
+        "priceChangePct": 7.77
+    }, {
+        "id": 23,
+        "name": "Vipe",
+        "phone": "622-869-7830",
+        "price": 67.77,
+        "priceChange": 1.18,
+        "priceLastChange": "10/1",
+        "industry": "Manufacturing",
+        "desc": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.\n\nPellentesque at nulla. Suspendisse potenti. Cras in purus eu magna vulputate luctus.",
+        "priceChangePct": 1.77
+    }, {
+        "id": 24,
+        "name": "Bubblemix",
+        "phone": "522-374-1131",
+        "price": 61.24,
+        "priceChange": -3.11,
+        "priceLastChange": "10/15",
+        "industry": "Automotive",
+        "desc": "Cras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
+        "priceChangePct": -4.83
+    }, {
+        "id": 25,
+        "name": "Kamba",
+        "phone": "351-332-9983",
+        "price": 37.2,
+        "priceChange": -2.96,
+        "priceLastChange": "10/10",
+        "industry": "Manufacturing",
+        "desc": "In sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus.\n\nSuspendisse potenti. In eleifend quam a odio. In hac habitasse platea dictumst.",
+        "priceChangePct": -7.37
+    }, {
+        "id": 26,
+        "name": "Zoombox",
+        "phone": "622-496-8296",
+        "price": 21.13,
+        "priceChange": -3.47,
+        "priceLastChange": "10/1",
+        "industry": "Finance",
+        "desc": "Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.\n\nQuisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.",
+        "priceChangePct": -14.11
+    }, {
+        "id": 27,
+        "name": "Roomm",
+        "phone": "145-321-7713",
+        "price": 25.09,
+        "priceChange": -2.25,
+        "priceLastChange": "10/18",
+        "industry": "Services",
+        "desc": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.",
+        "priceChangePct": -8.23
+    }, {
+        "id": 28,
+        "name": "Yacero",
+        "phone": "970-809-4952",
+        "price": 38.35,
+        "priceChange": 4.5,
+        "priceLastChange": "10/12",
+        "industry": "Medical",
+        "desc": "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.\n\nEtiam vel augue. Vestibulum rutrum rutrum neque. Aenean auctor gravida sem.",
+        "priceChangePct": 13.29
+    }, {
+        "id": 29,
+        "name": "Oyoloo",
+        "phone": "862-906-7336",
+        "price": 64.89,
+        "priceChange": -1.73,
+        "priceLastChange": "10/18",
+        "industry": "Manufacturing",
+        "desc": "Praesent id massa id nisl venenatis lacinia. Aenean sit amet justo. Morbi ut odio.\n\nCras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
+        "priceChangePct": -2.6
+    }, {
+        "id": 30,
+        "name": "Blogpad",
+        "phone": "622-375-1023",
+        "price": 64.2,
+        "priceChange": 0.14,
+        "priceLastChange": "10/1",
+        "industry": "Medical",
+        "desc": "Maecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat.",
+        "priceChangePct": 0.22
+    }, {
+        "id": 31,
+        "name": "Lajo",
+        "phone": "392-365-1092",
+        "price": 84.82,
+        "priceChange": -2.05,
+        "priceLastChange": "10/16",
+        "industry": "Retail",
+        "desc": "Quisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.\n\nPhasellus in felis. Donec semper sapien a libero. Nam dui.\n\nProin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.",
+        "priceChangePct": -2.36
+    }, {
+        "id": 32,
+        "name": "Zoombox",
+        "phone": "599-642-7887",
+        "price": 51.51,
+        "priceChange": 4.44,
+        "priceLastChange": "10/12",
+        "industry": "Automotive",
+        "desc": "Aliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis.\n\nSed ante. Vivamus tortor. Duis mattis egestas metus.\n\nAenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.",
+        "priceChangePct": 9.43
+    }, {
+        "id": 33,
+        "name": "Voolith",
+        "phone": "622-474-4785",
+        "price": 62.93,
+        "priceChange": 0.59,
+        "priceLastChange": "10/1",
+        "industry": "Food",
+        "desc": "In hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo.\n\nAliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis.\n\nSed ante. Vivamus tortor. Duis mattis egestas metus.",
+        "priceChangePct": 0.95
+    }, {
+        "id": 34,
+        "name": "Kwinu",
+        "phone": "357-354-0150",
+        "price": 48.11,
+        "priceChange": -2.66,
+        "priceLastChange": "10/7",
+        "industry": "Retail",
+        "desc": "Morbi porttitor lorem id ligula. Suspendisse ornare consequat lectus. In est risus, auctor sed, tristique in, tempus sit amet, sem.\n\nFusce consequat. Nulla nisl. Nunc nisl.\n\nDuis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.",
+        "priceChangePct": -5.24
+    }, {
+        "id": 35,
+        "name": "Livefish",
+        "phone": "862-232-8537",
+        "price": 21.23,
+        "priceChange": -0.72,
+        "priceLastChange": "10/11",
+        "industry": "Services",
+        "desc": "Aenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.\n\nQuisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.",
+        "priceChangePct": -3.28
+    }, {
+        "id": 36,
+        "name": "Kwinu",
+        "phone": "745-275-6224",
+        "price": 68.76,
+        "priceChange": 3.56,
+        "priceLastChange": "10/16",
+        "industry": "Services",
+        "desc": "Cras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\n\nProin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.",
+        "priceChangePct": 5.46
+    }, {
+        "id": 37,
+        "name": "Miboo",
+        "phone": "982-619-7532",
+        "price": 46.6,
+        "priceChange": 3.45,
+        "priceLastChange": "10/18",
+        "industry": "Automotive",
+        "desc": "In hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.\n\nNulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.\n\nCras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.",
+        "priceChangePct": 8
+    }, {
+        "id": 38,
+        "name": "Kwilith",
+        "phone": "351-595-8792",
+        "price": 58.14,
+        "priceChange": 0.14,
+        "priceLastChange": "10/7",
+        "industry": "Retail",
+        "desc": "Morbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.",
+        "priceChangePct": 0.24
+    }, {
+        "id": 39,
+        "name": "Photolist",
+        "phone": "622-519-3547",
+        "price": 56.49,
+        "priceChange": -4.73,
+        "priceLastChange": "10/3",
+        "industry": "Finance",
+        "desc": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.",
+        "priceChangePct": -7.73
+    }, {
+        "id": 40,
+        "name": "Miboo",
+        "phone": "380-372-8082",
+        "price": 77.71,
+        "priceChange": -3.93,
+        "priceLastChange": "10/9",
+        "industry": "Medical",
+        "desc": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.",
+        "priceChangePct": -4.81
+    }, {
+        "id": 41,
+        "name": "Browsedrive",
+        "phone": "462-687-7028",
+        "price": 49.9,
+        "priceChange": -1.72,
+        "priceLastChange": "10/2",
+        "industry": "Computer",
+        "desc": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.",
+        "priceChangePct": -3.33
+    }, {
+        "id": 42,
+        "name": "Riffpedia",
+        "phone": "356-106-1367",
+        "price": 45.9,
+        "priceChange": 0.11,
+        "priceLastChange": "10/12",
+        "industry": "Services",
+        "desc": "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.\n\nEtiam vel augue. Vestibulum rutrum rutrum neque. Aenean auctor gravida sem.\n\nPraesent id massa id nisl venenatis lacinia. Aenean sit amet justo. Morbi ut odio.",
+        "priceChangePct": 0.24
+    }, {
+        "id": 43,
+        "name": "Oozz",
+        "phone": "862-353-0334",
+        "price": 87.35,
+        "priceChange": 4.48,
+        "priceLastChange": "10/6",
+        "industry": "Computer",
+        "desc": "Phasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.",
+        "priceChangePct": 5.41
+    }, {
+        "id": 44,
+        "name": "Shuffledrive",
+        "phone": "862-563-6500",
+        "price": 88.31,
+        "priceChange": 2.06,
+        "priceLastChange": "10/11",
+        "industry": "Automotive",
+        "desc": "Proin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.",
+        "priceChangePct": 2.39
+    }, {
+        "id": 45,
+        "name": "Yakitri",
+        "phone": "552-429-1428",
+        "price": 69.33,
+        "priceChange": 2.72,
+        "priceLastChange": "10/6",
+        "industry": "Computer",
+        "desc": "Curabitur at ipsum ac tellus semper interdum. Mauris ullamcorper purus sit amet nulla. Quisque arcu libero, rutrum ac, lobortis vel, dapibus at, diam.",
+        "priceChangePct": 4.08
+    }, {
+        "id": 46,
+        "name": "Linkbuzz",
+        "phone": "462-377-7472",
+        "price": 70.51,
+        "priceChange": 0.07,
+        "priceLastChange": "10/18",
+        "industry": "Computer",
+        "desc": "Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.\n\nPhasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.\n\nProin eu mi. Nulla ac enim. In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem.",
+        "priceChangePct": 0.1
+    }, {
+        "id": 47,
+        "name": "Wordpedia",
+        "phone": "267-704-2054",
+        "price": 26.92,
+        "priceChange": -4.43,
+        "priceLastChange": "10/13",
+        "industry": "Medical",
+        "desc": "Maecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat.\n\nCurabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.",
+        "priceChangePct": -14.13
+    }, {
+        "id": 48,
+        "name": "Yabox",
+        "phone": "745-780-8768",
+        "price": 76.81,
+        "priceChange": 2.59,
+        "priceLastChange": "10/10",
+        "industry": "Automotive",
+        "desc": "Suspendisse potenti. In eleifend quam a odio. In hac habitasse platea dictumst.\n\nMaecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat.",
+        "priceChangePct": 3.49
+    }, {
+        "id": 49,
+        "name": "Dynabox",
+        "phone": "862-898-8042",
+        "price": 64.65,
+        "priceChange": -2.11,
+        "priceLastChange": "10/6",
+        "industry": "Manufacturing",
+        "desc": "Morbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.\n\nFusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.",
+        "priceChangePct": -3.16
+    }, {
+        "id": 50,
+        "name": "Topicstorm",
+        "phone": "482-108-7665",
+        "price": 87.72,
+        "priceChange": 4.28,
+        "priceLastChange": "10/4",
+        "industry": "Retail",
+        "desc": "Cras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
+        "priceChangePct": 5.13
+    }, {
+        "id": 51,
+        "name": "Realpoint",
+        "phone": "842-806-2602",
+        "price": 82.67,
+        "priceChange": 2.54,
+        "priceLastChange": "10/10",
+        "industry": "Services",
+        "desc": "Duis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.\n\nIn hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo.\n\nAliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis.",
+        "priceChangePct": 3.17
+    }, {
+        "id": 52,
+        "name": "Vimbo",
+        "phone": "745-182-0490",
+        "price": 56.51,
+        "priceChange": -0.43,
+        "priceLastChange": "10/16",
+        "industry": "Computer",
+        "desc": "Nullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris.\n\nMorbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.\n\nFusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.",
+        "priceChangePct": -0.76
+    }, {
+        "id": 53,
+        "name": "Babbleset",
+        "phone": "632-908-4430",
+        "price": 24.72,
+        "priceChange": -1.85,
+        "priceLastChange": "10/11",
+        "industry": "Computer",
+        "desc": "In congue. Etiam justo. Etiam pretium iaculis justo.\n\nIn hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.",
+        "priceChangePct": -6.96
+    }, {
+        "id": 54,
+        "name": "Myworks",
+        "phone": "862-462-8001",
+        "price": 59.48,
+        "priceChange": -1.99,
+        "priceLastChange": "10/9",
+        "industry": "Manufacturing",
+        "desc": "Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.\n\nMauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci. Mauris lacinia sapien quis libero.\n\nNullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh.",
+        "priceChangePct": -3.24
+    }, {
+        "id": 55,
+        "name": "Kazio",
+        "phone": "380-980-3093",
+        "price": 75.84,
+        "priceChange": 4.58,
+        "priceLastChange": "10/6",
+        "industry": "Services",
+        "desc": "Duis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.",
+        "priceChangePct": 6.43
+    }, {
+        "id": 56,
+        "name": "Linkbridge",
+        "phone": "512-129-3871",
+        "price": 60.95,
+        "priceChange": 2.28,
+        "priceLastChange": "10/15",
+        "industry": "Services",
+        "desc": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.\n\nAenean lectus. Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum.\n\nCurabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.",
+        "priceChangePct": 3.89
+    }, {
+        "id": 57,
+        "name": "Quinu",
+        "phone": "503-662-9741",
+        "price": 55,
+        "priceChange": 2.7,
+        "priceLastChange": "10/12",
+        "industry": "Computer",
+        "desc": "In hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo.\n\nAliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis.\n\nSed ante. Vivamus tortor. Duis mattis egestas metus.",
+        "priceChangePct": 5.16
+    }, {
+        "id": 58,
+        "name": "Wikivu",
+        "phone": "462-209-9969",
+        "price": 57.09,
+        "priceChange": -4.92,
+        "priceLastChange": "10/18",
+        "industry": "Food",
+        "desc": "Maecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti.\n\nNullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris.",
+        "priceChangePct": -7.93
+    }, {
+        "id": 59,
+        "name": "Yata",
+        "phone": "622-394-7257",
+        "price": 29.53,
+        "priceChange": -2.92,
+        "priceLastChange": "10/17",
+        "industry": "Manufacturing",
+        "desc": "Sed ante. Vivamus tortor. Duis mattis egestas metus.\n\nAenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.\n\nQuisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.",
+        "priceChangePct": -9
+    }, {
+        "id": 60,
+        "name": "Feedfish",
+        "phone": "745-750-2429",
+        "price": 62.17,
+        "priceChange": 0.94,
+        "priceLastChange": "10/3",
+        "industry": "Retail",
+        "desc": "Vestibulum ac est lacinia nisi venenatis tristique. Fusce congue, diam id ornare imperdiet, sapien urna pretium nisl, ut volutpat sapien arcu sed augue. Aliquam erat volutpat.",
+        "priceChangePct": 1.54
+    }, {
+        "id": 61,
+        "name": "Trudoo",
+        "phone": "357-282-4066",
+        "price": 56.56,
+        "priceChange": 0.4,
+        "priceLastChange": "10/15",
+        "industry": "Finance",
+        "desc": "Sed ante. Vivamus tortor. Duis mattis egestas metus.\n\nAenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.\n\nQuisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.",
+        "priceChangePct": 0.71
+    }, {
+        "id": 62,
+        "name": "Kazio",
+        "phone": "552-561-3265",
+        "price": 22.92,
+        "priceChange": 0.17,
+        "priceLastChange": "10/6",
+        "industry": "Medical",
+        "desc": "Morbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.\n\nFusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.",
+        "priceChangePct": 0.75
+    }, {
+        "id": 63,
+        "name": "Quamba",
+        "phone": "862-243-2456",
+        "price": 26.54,
+        "priceChange": 2.38,
+        "priceLastChange": "10/14",
+        "industry": "Retail",
+        "desc": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.\n\nPellentesque at nulla. Suspendisse potenti. Cras in purus eu magna vulputate luctus.",
+        "priceChangePct": 9.85
+    }, {
+        "id": 64,
+        "name": "Eadel",
+        "phone": "353-940-5410",
+        "price": 80.18,
+        "priceChange": 2.63,
+        "priceLastChange": "10/16",
+        "industry": "Medical",
+        "desc": "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
+        "priceChangePct": 3.39
+    }, {
+        "id": 65,
+        "name": "Wikibox",
+        "phone": "992-708-2594",
+        "price": 84.65,
+        "priceChange": 4.18,
+        "priceLastChange": "10/5",
+        "industry": "Retail",
+        "desc": "Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.\n\nPhasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.\n\nProin eu mi. Nulla ac enim. In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem.",
+        "priceChangePct": 5.19
+    }, {
+        "id": 66,
+        "name": "Youopia",
+        "phone": "462-373-9588",
+        "price": 64.06,
+        "priceChange": 4.28,
+        "priceLastChange": "10/5",
+        "industry": "Food",
+        "desc": "Vestibulum quam sapien, varius ut, blandit non, interdum in, ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio. Curabitur convallis.\n\nDuis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.",
+        "priceChangePct": 7.16
+    }, {
+        "id": 67,
+        "name": "Edgeblab",
+        "phone": "502-372-3812",
+        "price": 30.6,
+        "priceChange": -4.12,
+        "priceLastChange": "10/10",
+        "industry": "Automotive",
+        "desc": "Fusce consequat. Nulla nisl. Nunc nisl.",
+        "priceChangePct": -11.87
+    }, {
+        "id": 68,
+        "name": "JumpXS",
+        "phone": "145-573-3692",
+        "price": 27.65,
+        "priceChange": -4.44,
+        "priceLastChange": "10/14",
+        "industry": "Manufacturing",
+        "desc": "Praesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.\n\nMorbi porttitor lorem id ligula. Suspendisse ornare consequat lectus. In est risus, auctor sed, tristique in, tempus sit amet, sem.",
+        "priceChangePct": -13.84
+    }, {
+        "id": 69,
+        "name": "Skyvu",
+        "phone": "502-411-8686",
+        "price": 57.77,
+        "priceChange": -2.65,
+        "priceLastChange": "10/4",
+        "industry": "Food",
+        "desc": "Proin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.\n\nInteger ac leo. Pellentesque ultrices mattis odio. Donec vitae nisi.",
+        "priceChangePct": -4.39
+    }, {
+        "id": 70,
+        "name": "Flipbug",
+        "phone": "522-768-1133",
+        "price": 80.04,
+        "priceChange": 0.31,
+        "priceLastChange": "10/13",
+        "industry": "Services",
+        "desc": "Proin eu mi. Nulla ac enim. In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem.\n\nDuis aliquam convallis nunc. Proin at turpis a pede posuere nonummy. Integer non velit.\n\nDonec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.",
+        "priceChangePct": 0.39
+    }, {
+        "id": 71,
+        "name": "Wordtune",
+        "phone": "342-989-5892",
+        "price": 53.64,
+        "priceChange": -0.2,
+        "priceLastChange": "10/2",
+        "industry": "Services",
+        "desc": "Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.\n\nPhasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.\n\nProin eu mi. Nulla ac enim. In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem.",
+        "priceChangePct": -0.37
+    }, {
+        "id": 72,
+        "name": "Kamba",
+        "phone": "842-977-2740",
+        "price": 84.58,
+        "priceChange": 2.47,
+        "priceLastChange": "10/17",
+        "industry": "Food",
+        "desc": "Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.",
+        "priceChangePct": 3.01
+    }, {
+        "id": 73,
+        "name": "Skyble",
+        "phone": "502-710-5986",
+        "price": 37.61,
+        "priceChange": 0.3,
+        "priceLastChange": "10/2",
+        "industry": "Food",
+        "desc": "Phasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.",
+        "priceChangePct": 0.8
+    }, {
+        "id": 74,
+        "name": "Lajo",
+        "phone": "622-522-5934",
+        "price": 89.56,
+        "priceChange": 4.06,
+        "priceLastChange": "10/11",
+        "industry": "Manufacturing",
+        "desc": "Fusce consequat. Nulla nisl. Nunc nisl.\n\nDuis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.",
+        "priceChangePct": 4.75
+    }, {
+        "id": 75,
+        "name": "Mynte",
+        "phone": "342-657-8165",
+        "price": 71.19,
+        "priceChange": -4.55,
+        "priceLastChange": "10/18",
+        "industry": "Finance",
+        "desc": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.",
+        "priceChangePct": -6.01
+    }, {
+        "id": 76,
+        "name": "Devbug",
+        "phone": "351-802-1189",
+        "price": 31.95,
+        "priceChange": 4.6,
+        "priceLastChange": "10/18",
+        "industry": "Food",
+        "desc": "Proin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.",
+        "priceChangePct": 16.82
+    }, {
+        "id": 77,
+        "name": "Trudeo",
+        "phone": "355-931-4788",
+        "price": 57,
+        "priceChange": -3.41,
+        "priceLastChange": "10/12",
+        "industry": "Manufacturing",
+        "desc": "Maecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti.\n\nNullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris.",
+        "priceChangePct": -5.64
+    }, {
+        "id": 78,
+        "name": "Twimm",
+        "phone": "145-344-5265",
+        "price": 35.03,
+        "priceChange": -4.22,
+        "priceLastChange": "10/6",
+        "industry": "Food",
+        "desc": "Fusce consequat. Nulla nisl. Nunc nisl.\n\nDuis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.",
+        "priceChangePct": -10.75
+    }, {
+        "id": 79,
+        "name": "Edgeblab",
+        "phone": "632-603-3459",
+        "price": 30.44,
+        "priceChange": -0.87,
+        "priceLastChange": "10/17",
+        "industry": "Services",
+        "desc": "Sed ante. Vivamus tortor. Duis mattis egestas metus.",
+        "priceChangePct": -2.78
+    }, {
+        "id": 80,
+        "name": "Yakidoo",
+        "phone": "145-691-9042",
+        "price": 43.55,
+        "priceChange": -2.11,
+        "priceLastChange": "10/11",
+        "industry": "Automotive",
+        "desc": "Proin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.",
+        "priceChangePct": -4.62
+    }, {
+        "id": 81,
+        "name": "Jaxspan",
+        "phone": "862-322-9633",
+        "price": 42.44,
+        "priceChange": -4.68,
+        "priceLastChange": "10/10",
+        "industry": "Automotive",
+        "desc": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.\n\nAenean lectus. Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum.\n\nCurabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.",
+        "priceChangePct": -9.93
+    }, {
+        "id": 82,
+        "name": "Realpoint",
+        "phone": "502-180-8057",
+        "price": 44.58,
+        "priceChange": -1.62,
+        "priceLastChange": "10/14",
+        "industry": "Services",
+        "desc": "Nullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris.\n\nMorbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.\n\nFusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.",
+        "priceChangePct": -3.51
+    }, {
+        "id": 83,
+        "name": "Voonyx",
+        "phone": "351-412-4147",
+        "price": 71.83,
+        "priceChange": 0.21,
+        "priceLastChange": "10/5",
+        "industry": "Manufacturing",
+        "desc": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.\n\nAenean lectus. Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum.",
+        "priceChangePct": 0.29
+    }, {
+        "id": 84,
+        "name": "Gabtune",
+        "phone": "482-607-1635",
+        "price": 39.98,
+        "priceChange": 3.11,
+        "priceLastChange": "10/15",
+        "industry": "Food",
+        "desc": "Sed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.\n\nPellentesque at nulla. Suspendisse potenti. Cras in purus eu magna vulputate luctus.",
+        "priceChangePct": 8.44
+    }, {
+        "id": 85,
+        "name": "Topicblab",
+        "phone": "622-599-1742",
+        "price": 79.32,
+        "priceChange": 0.16,
+        "priceLastChange": "10/3",
+        "industry": "Retail",
+        "desc": "In sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus.\n\nSuspendisse potenti. In eleifend quam a odio. In hac habitasse platea dictumst.\n\nMaecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat.",
+        "priceChangePct": 0.2
+    }, {
+        "id": 86,
+        "name": "Realbridge",
+        "phone": "662-336-2221",
+        "price": 69.83,
+        "priceChange": 1.82,
+        "priceLastChange": "10/14",
+        "industry": "Manufacturing",
+        "desc": "Integer ac leo. Pellentesque ultrices mattis odio. Donec vitae nisi.",
+        "priceChangePct": 2.68
+    }, {
+        "id": 87,
+        "name": "Oyoba",
+        "phone": "862-525-0830",
+        "price": 47.04,
+        "priceChange": 1.63,
+        "priceLastChange": "10/14",
+        "industry": "Retail",
+        "desc": "Duis bibendum, felis sed interdum venenatis, turpis enim blandit mi, in porttitor pede justo eu massa. Donec dapibus. Duis at velit eu est congue elementum.",
+        "priceChangePct": 3.59
+    }, {
+        "id": 88,
+        "name": "Tambee",
+        "phone": "745-993-1655",
+        "price": 29.26,
+        "priceChange": 0.14,
+        "priceLastChange": "10/7",
+        "industry": "Retail",
+        "desc": "Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.\n\nQuisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.",
+        "priceChangePct": 0.48
+    }, {
+        "id": 89,
+        "name": "Gabtune",
+        "phone": "542-938-0543",
+        "price": 82.52,
+        "priceChange": 2.56,
+        "priceLastChange": "10/13",
+        "industry": "Automotive",
+        "desc": "Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.\n\nPhasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.",
+        "priceChangePct": 3.2
+    }, {
+        "id": 90,
+        "name": "Skiptube",
+        "phone": "552-499-2316",
+        "price": 20.15,
+        "priceChange": 1.45,
+        "priceLastChange": "10/17",
+        "industry": "Retail",
+        "desc": "Morbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.\n\nFusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.\n\nSed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.",
+        "priceChangePct": 7.75
+    }, {
+        "id": 91,
+        "name": "Skimia",
+        "phone": "591-947-1885",
+        "price": 56.5,
+        "priceChange": -1.5,
+        "priceLastChange": "10/8",
+        "industry": "Medical",
+        "desc": "Aliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis.",
+        "priceChangePct": -2.59
+    }, {
+        "id": 92,
+        "name": "Jaxworks",
+        "phone": "562-250-5384",
+        "price": 87.72,
+        "priceChange": -0.9,
+        "priceLastChange": "10/18",
+        "industry": "Services",
+        "desc": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.",
+        "priceChangePct": -1.02
+    }, {
+        "id": 93,
+        "name": "Quatz",
+        "phone": "385-589-2985",
+        "price": 29.71,
+        "priceChange": -0.48,
+        "priceLastChange": "10/2",
+        "industry": "Computer",
+        "desc": "Quisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.\n\nPhasellus in felis. Donec semper sapien a libero. Nam dui.\n\nProin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.",
+        "priceChangePct": -1.59
+    }, {
+        "id": 94,
+        "name": "Gigashots",
+        "phone": "145-321-1934",
+        "price": 58.79,
+        "priceChange": 1.67,
+        "priceLastChange": "10/6",
+        "industry": "Retail",
+        "desc": "In quis justo. Maecenas rhoncus aliquam lacus. Morbi quis tortor id nulla ultrices aliquet.",
+        "priceChangePct": 2.92
+    }, {
+        "id": 95,
+        "name": "Edgeblab",
+        "phone": "862-721-4334",
+        "price": 33.14,
+        "priceChange": -0.74,
+        "priceLastChange": "10/8",
+        "industry": "Manufacturing",
+        "desc": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin risus. Praesent lectus.",
+        "priceChangePct": -2.18
+    }, {
+        "id": 96,
+        "name": "Vipe",
+        "phone": "351-720-0324",
+        "price": 60.94,
+        "priceChange": -2.67,
+        "priceLastChange": "10/18",
+        "industry": "Medical",
+        "desc": "In quis justo. Maecenas rhoncus aliquam lacus. Morbi quis tortor id nulla ultrices aliquet.\n\nMaecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui.\n\nMaecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti.",
+        "priceChangePct": -4.2
+    }, {
+        "id": 97,
+        "name": "Zoonder",
+        "phone": "632-839-5313",
+        "price": 36.2,
+        "priceChange": 1.86,
+        "priceLastChange": "10/6",
+        "industry": "Food",
+        "desc": "Curabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.\n\nInteger tincidunt ante vel ipsum. Praesent blandit lacinia erat. Vestibulum sed magna at nunc commodo placerat.\n\nPraesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.",
+        "priceChangePct": 5.42
+    }, {
+        "id": 98,
+        "name": "Zoovu",
+        "phone": "862-687-7673",
+        "price": 52.25,
+        "priceChange": 1.07,
+        "priceLastChange": "10/14",
+        "industry": "Food",
+        "desc": "Praesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.",
+        "priceChangePct": 2.09
+    }, {
+        "id": 99,
+        "name": "Kamba",
+        "phone": "351-618-0859",
+        "price": 49.84,
+        "priceChange": 2.32,
+        "priceLastChange": "10/16",
+        "industry": "Computer",
+        "desc": "Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.",
+        "priceChangePct": 4.88
+    }, {
+        "id": 100,
+        "name": "Twimm",
+        "phone": "862-401-6472",
+        "price": 56.27,
+        "priceChange": -4.32,
+        "priceLastChange": "10/1",
+        "industry": "Finance",
+        "desc": "Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.",
+        "priceChangePct": -7.13
+    }];
+
+    //from https://git.daplie.com/Daplie/knuth-shuffle/
+    function shuffle (array) {
+        array = Ext.Array.clone(array);
+
+        var currentIndex = array.length,
+            temporaryValue, randomIndex;
+
+        // While there remain elements to shuffle...
+        while (0 !== currentIndex) {
+            // Pick a remaining element...
+            randomIndex = Math.floor(Math.random() * currentIndex);
+
+            currentIndex -= 1;
+
+            // And swap it with the current element.
+            temporaryValue = array[currentIndex];
+
+            array[currentIndex] = array[randomIndex];
+            array[randomIndex]  = temporaryValue;
+        }
+
+        return array;
+    }
+
+    Ext.ux.ajax.SimManager.register({
+        type: 'json',
+        delay: 300,
+        url: /\/uas\/Company(\/\d+)?/,
+
+        data: function(ctx) {
+            var idPart = ctx.url.match(this.url)[1],
+                id;
+
+            if (idPart) {
+                id = parseInt(idPart.substring(1), 10);
+
+                return Ext.Array.findBy(companies, function(company) {
+                    return company.id === id;
+                });
+            } else if (ctx.params.shuffle) {
+                return shuffle(companies);
+            }
+
+            return companies;
+        }
+    });
+});

+ 127 - 0
app/model/Company.js

@@ -0,0 +1,127 @@
+Ext.define('uas.model.Company', {
+    extend: 'uas.model.Base',
+    requires: [
+        'uas.model.field.PhoneNumber',
+        'Ext.data.proxy.Rest'
+    ],
+    fields: [
+        {name: 'name'},
+        {name: 'phone', type: 'phonenumber' },
+        {name: 'price', type: 'float'},
+        { name: 'priceChange', type: 'float' },
+        { name: 'priceChangePct', type: 'float' },
+        { name: 'priceLastChange', type: 'date', dateReadFormat: 'n/j' },
+
+        // Calculated field. Depends on price value. Adds it to price history.
+        // Trend begins with the current price. Changes get pushed onto the end
+        {
+            name: 'trend',
+            calculate: function(data) {
+                // Avoid circular dependency by hiding the read of trend value
+                var trend = data['trend'] || (data['trend'] = []);
+
+                trend.push(data.price);
+
+                if (trend.length === 1) {
+                    //let's start the trend off with a change
+                    trend.push(data.price + data.priceChange);
+                }
+
+                if (trend.length > 10) {
+                    trend.shift();
+                }
+
+                return trend;
+            },
+            
+            // It's the same array. But we need Model#set to see it as modified so it
+            // is flushed to the UI
+            isEqual: function() {
+                return false;
+            }
+        },
+        
+        // Calculated field. Depends on price history being populated.
+        {
+            name: 'change',
+            type: 'float',
+            calculate: function(data) {
+                var trend = data.trend,
+                    len = trend.length;
+
+                return len > 1 ? trend[len - 1] - trend[len - 2] : 0;
+            }
+        },
+        
+        // Calculated field. Depends on price history and last change being populated.
+        {
+            name: 'pctChange',
+            type: 'float',
+            calculate: function(data) {
+                var trend = data.trend,
+                    len = trend.length;
+
+                return len > 1 ? (data.change / trend[len - 2]) * 100 : 0;
+            }
+        },
+        
+        // Calculated field, recalculated when price changes
+        {
+            name: 'lastChange',
+            type: 'date',
+            depends: ['price'],
+
+            // The calculator is run whenever price changes.
+            // This field is a purely calculated value and can not be edited.
+            calculate: function() {
+                return new Date();
+            }
+
+        },
+        {name: 'industry'},
+        {name: 'desc'},
+        // Rating dependent upon last price change performance 0 = best, 2 = worst
+        {
+            name: 'rating',
+            type: 'int',
+
+            // Use a converter to only derive the value onces on record creation
+            convert: function(value, record) {
+                var data = record.data,
+                    pct = data.pctChange;
+
+                // Only calculate it first time.
+                if (!data.hasOwnProperty('rating')) {
+                    return (pct < -5) ? 2 : ((pct < 5) ? 1 : 0);
+                }
+
+                return value;
+            }
+        }
+    ],
+
+    proxy: {
+        type: 'ajax',
+        reader: {
+            type: 'json'
+        },
+        url: '/uas/Company'
+    },
+
+    validators: {
+        name: 'presence'
+    },
+
+    addPriceTick: function () {
+        // Set data, but pass "clean" flag.
+        this.set('price', this.generateNewPrice(), {
+            dirty: false
+        });
+    },
+
+    generateNewPrice: function () {
+        var newPrice = Math.abs(this.data.price + Ext.Number.randomInt(-2345, 2345) / 100);
+
+        return Math.round(newPrice * 100) / 100;
+    }
+});

+ 13 - 0
app/model/field/PhoneNumber.js

@@ -0,0 +1,13 @@
+Ext.define('uas.model.field.PhoneNumber', {
+    extend: 'Ext.data.field.String',
+
+    alias: 'data.field.phonenumber',
+
+    validators: [
+        { 
+            type: 'format', 
+            matcher: /^\d{3}-?\d{3}-?\d{4}$/,
+            message: 'Must be in the format xxx-xxx-xxxx'
+        }
+    ]
+});

+ 22 - 0
app/store/Companies.js

@@ -0,0 +1,22 @@
+Ext.define('uas.store.Companies', {
+    extend: 'Ext.data.Store',
+    alias: 'store.companies',
+    model: 'uas.model.Company',
+    
+    autoLoad: true,
+    pageSize: null,
+    
+    proxy: {
+        type: 'ajax',
+        url: '/uas/Company',
+
+        reader: {
+            type: 'json',
+            rootProperty: 'data',
+
+            // Do not attempt to load orders inline.
+            // They are loaded through the proxy
+            implicitIncludes: false
+        }
+    }
+});

+ 59 - 0
app/view/grid/basic/Panel.js

@@ -0,0 +1,59 @@
+Ext.define('uas.view.grid.basic.Panel', {
+    extend: 'Ext.grid.Panel',
+    xtype: 'basic-grid',
+    controller: 'basicgrid',
+
+
+    store: 'Companies',
+    stateful: true,
+    multiSelect: true,
+    stateId: 'stateGrid',
+    headerBorders: false,
+
+    viewConfig: {
+        enableTextSelection: true
+    },
+
+    columns: [{
+        text: 'Company',
+        flex: 1,
+        dataIndex: 'name'
+    }, {
+        text: 'Price',
+        width: 95,
+        formatter: 'usMoney',
+        dataIndex: 'price'
+    }, {
+        text: 'Change',
+        width: 80,
+        renderer: 'renderChange',
+        dataIndex: 'priceChange'
+    }, {
+        text: '% Change',
+        width: 100,
+        renderer: 'renderPercent',
+        dataIndex: 'priceChangePct'
+    }, {
+        text: 'Last Updated',
+        width: 115,
+        formatter: 'date("m/d/Y")',
+        dataIndex: 'priceLastChange'
+    }, {
+        xtype: 'actioncolumn',
+        width: 50,
+        menuDisabled: true,
+        sortable: false,
+
+        items: [{
+            iconCls: 'x-fa fa-check green',
+            handler: 'onApprove'
+        }, {
+            iconCls: 'x-fa fa-ban red',
+            handler: 'onDecline'
+        }]
+    }],
+
+    signTpl: '<span style="' +
+            'color:{value:sign(\'"#cf4c35"\',\'"#73b51e"\')}"' +
+        '>{text}</span>'
+});

+ 49 - 0
app/view/grid/basic/PanelController.js

@@ -0,0 +1,49 @@
+/**
+ * Controller for several grid examples (such as BasicGrid).
+ *
+ * Provides column renderers and handlers for the ActionColumn and buttons.
+ */
+Ext.define('uas.view.grid.basic.PanelController', {
+    extend: 'Ext.app.ViewController',
+    alias: 'controller.basicgrid',
+
+    onApprove: function (grid, rowIndex, colIndex) {
+        var rec = grid.getStore().getAt(rowIndex);
+
+        Ext.Msg.alert('Approve', rec.get('name'));
+    },
+
+    onDecline: function(grid, rowIndex, colIndex) {
+        var rec = grid.getStore().getAt(rowIndex);
+
+        Ext.Msg.alert('Decline', rec.get('name'));
+    },
+
+    renderChange: function (value) {
+        return this.renderSign(value, '0.00');
+    },
+
+    renderPercent: function (value) {
+        return this.renderSign(value, '0.00%');
+    },
+
+    renderSign: function (value, format) {
+        var text = Ext.util.Format.number(value, format),
+            tpl = this.signTpl,
+            data = this.data;
+
+        if (Math.abs(value) > 0.1) {
+            if (!tpl) {
+                this.signTpl = tpl = this.getView().lookupTpl('signTpl');
+                this.data = data = {};
+            }
+
+            data.value = value;
+            data.text = text;
+
+            text = tpl.apply(data);
+        }
+
+        return text;
+    }
+});

+ 0 - 4
app/view/grid/dataList/DataListPanel.js

@@ -13,10 +13,6 @@ Ext.define('uas.view.grid.dataList.DataListPanel', {
     ],
 
     title: '筛选头列表',
-    collapsible: true,
-    frame: true,
-    height: 600,
-    resizable: true,
 
     plugins: {
         gridHeaderFilter: true

+ 13 - 0
app/view/grid/summary/Panel.js

@@ -0,0 +1,13 @@
+Ext.define('uas.view.grid.summary.Panel', {
+    extend: 'Ext.tab.Panel',
+    xtype: 'grid-summary-panel',
+
+    items: [{
+        title: '滚动',
+        xtype: 'summary1',
+    }, {
+        title: '固定',
+        html: 'nothing...'
+    }]
+
+});

+ 52 - 0
app/view/grid/summary/Summary1.js

@@ -0,0 +1,52 @@
+Ext.define('uas.view.grid.summary.Summary1', {
+    extend: 'uas.view.grid.basic.Panel',
+    xtype: 'summary1',
+
+    features: [{
+        ftype: 'summary'
+    }],
+
+    columns: [{
+        text: 'Company',
+        flex: 1,
+        dataIndex: 'name',
+        summaryType: 'count',
+        summaryRenderer: function(value, summaryData, dataIndex) {
+            return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
+        }
+    }, {
+        text: 'Price',
+        width: 95,
+        formatter: 'usMoney',
+        dataIndex: 'price',
+        summaryType: 'average'
+    }, {
+        text: 'Change',
+        width: 80,
+        renderer: 'renderChange',
+        dataIndex: 'priceChange'
+    }, {
+        text: '% Change',
+        width: 100,
+        renderer: 'renderPercent',
+        dataIndex: 'priceChangePct'
+    }, {
+        text: 'Last Updated',
+        width: 115,
+        formatter: 'date("m/d/Y")',
+        dataIndex: 'priceLastChange'
+    }, {
+        xtype: 'actioncolumn',
+        width: 50,
+        menuDisabled: true,
+        sortable: false,
+
+        items: [{
+            iconCls: 'x-fa fa-check green',
+            handler: 'onApprove'
+        }, {
+            iconCls: 'x-fa fa-ban red',
+            handler: 'onDecline'
+        }]
+    }]
+});

+ 1 - 1
app/view/main/NavigationTree.js

@@ -4,6 +4,6 @@ Ext.define('uas.view.main.NavigationTree', {
 
     id: 'navigation-tree',
 
-    store: 'navigation',
+    store: 'Navigation',
 
 });

+ 43 - 2
resources/json/navigation.json

@@ -10,6 +10,12 @@
         "expanded": false,
         "iconCls": "x-fa fa-book",
         "children": [
+            {
+                "text": "基本列表",
+                "target": "basic-grid",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
             {
                 "text": "筛选头",
                 "target": "dataListPanel",
@@ -18,20 +24,55 @@
             },
             {
                 "text": "合计栏",
-                "target": "grid-summary",
+                "target": "grid-summary-panel",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
+            {
+                "text": "分组列",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
+            {
+                "text": "展开列",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
+            {
+                "text": "列头合并",
                 "leaf": true,
                 "iconCls": "x-fa fa-smile-o"
             },
             {
-                "text": "合并列",
+                "text": "合并",
                 "target": "grid-merge-column",
                 "leaf": true,
                 "iconCls": "x-fa fa-smile-o"
             },
+            {
+                "text": "复合排序",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
+            {
+                "text": "锁定列",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
             {
                 "text": "复制/粘贴",
                 "leaf": true,
                 "iconCls": "x-fa fa-smile-o"
+            },
+            {
+                "text": "导出",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
+            },
+            {
+                "text": "分页",
+                "leaf": true,
+                "iconCls": "x-fa fa-smile-o"
             }
         ]
     },

+ 14 - 0
samples/data/Init.js

@@ -0,0 +1,14 @@
+Ext.define('uas.data.Init', {
+    requires: [
+        'Ext.ux.ajax.JsonSimlet',
+        'Ext.ux.ajax.SimManager'
+    ],
+
+    singleton: true,
+
+    constructor: function() {
+        Ext.ux.ajax.SimManager.init({
+            defaultSimlet: null
+        });
+    }
+});

+ 222 - 0
ux/ajax/DataSimlet.js

@@ -0,0 +1,222 @@
+/**
+ * This base class is used to handle data preparation (e.g., sorting, filtering and
+ * group summary).
+ */
+Ext.define('Ext.ux.ajax.DataSimlet', function () {
+ 
+    function makeSortFn (def, cmp) {
+        var order = def.direction,
+            sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;
+ 
+        return function (leftRec, rightRec) {
+            var lhs = leftRec[def.property],
+                rhs = rightRec[def.property],
+                c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);
+ 
+            if (c || !cmp) {
+                return c * sign;
+            }
+ 
+            return cmp(leftRec, rightRec);
+        };
+    }
+ 
+    function makeSortFns (defs, cmp) {
+        for (var sortFn = cmp, i = defs && defs.length; i; ) {
+            sortFn = makeSortFn(defs[--i], sortFn);
+        }
+        return sortFn;
+    }
+ 
+    return {
+        extend: 'Ext.ux.ajax.Simlet',
+ 
+        buildNodes: function (node, path) {
+            var me = this,
+                nodeData = {
+                    data: []
+                },
+                len = node.length,
+                children, i, child, name;
+ 
+            me.nodes[path] = nodeData;
+ 
+            for (i = 0; i < len; ++i) {
+                nodeData.data.push(child = node[i]);
+                name = child.text || child.title;
+ 
+                child.id = path ? path + '/' + name : name;
+                children = child.children;
+ 
+                if (!(child.leaf = !children)) {
+                    delete child.children;
+ 
+                    me.buildNodes(children, child.id);
+                }
+            }
+        },
+ 
+        deleteRecord : function(pos) {
+            if(this.data && typeof this.data !== 'function') {
+                Ext.Array.removeAt(this.data,pos); 
+            }
+        },
+ 
+        fixTree: function (ctx, tree) {
+            var me = this,
+                node = ctx.params.node,
+                nodes;
+ 
+            if (!(nodes = me.nodes)) {
+                me.nodes = nodes = {};
+                me.buildNodes(tree, '');
+            }
+ 
+            node = nodes[node];
+            if (node) {
+                if (me.node) {
+                    me.node.sortedData = me.sortedData;
+                    me.node.currentOrder = me.currentOrder;
+                }
+ 
+                me.node = node;
+                me.data = node.data;
+                me.sortedData = node.sortedData;
+                me.currentOrder = node.currentOrder;
+            } else {
+                me.data = null;
+            }
+        },
+ 
+        getData: function (ctx) {
+            var me = this,
+                params = ctx.params,
+                order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),
+                tree = me.tree,
+                dynamicData,
+                data, fields, sortFn;
+ 
+            if (tree) {
+                me.fixTree(ctx, tree);
+            }
+ 
+            data = me.data;
+            if (typeof data === 'function') {
+                dynamicData = true;
+                data = data.call(this, ctx);
+            }
+ 
+            // If order is '--' then it means we had no order passed, due to the string concat above 
+            if (!data || order === '--') {
+                return data || [];
+            }
+ 
+            if (!dynamicData && order == me.currentOrder) {
+                return me.sortedData;
+            }
+ 
+            ctx.filterSpec = params.filter && Ext.decode(params.filter);
+            ctx.groupSpec = params.group && Ext.decode(params.group);
+ 
+            fields = params.sort;
+            if (params.dir) {
+                fields = [{ direction: params.dir, property: fields }];
+            } else {
+                fields = Ext.decode(params.sort);
+            }
+ 
+            if (ctx.filterSpec) {
+                var filters = new Ext.util.FilterCollection();
+                filters.add(this.processFilters(ctx.filterSpec));
+                data = Ext.Array.filter(data, filters.getFilterFn());
+            }
+ 
+            sortFn = makeSortFns((ctx.sortSpec = fields));
+            if (ctx.groupSpec) {
+                sortFn = makeSortFns([ctx.groupSpec], sortFn);
+            }
+ 
+            // If a straight Ajax request, data may not be an array. 
+            // If an Array, preserve 'physical' order of raw data... 
+            data = Ext.isArray(data) ? data.slice(0) : data;
+            if (sortFn) {
+                Ext.Array.sort(data, sortFn);
+            }
+ 
+            me.sortedData = data;
+            me.currentOrder = order;
+ 
+            return data;
+        },
+        
+        processFilters: Ext.identityFn,
+ 
+        getPage: function (ctx, data) {
+            var ret = data,
+                length = data.length,
+                start = ctx.params.start || 0,
+                end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;
+ 
+            if (start || end < length) {
+                ret = ret.slice(start, end);
+            }
+ 
+            return ret;
+        },
+ 
+        getGroupSummary: function (groupField, rows, ctx) {
+            return rows[0];
+        },
+ 
+        getSummary: function (ctx, data, page) {
+            var me = this,
+                groupField = ctx.groupSpec.property,
+                accum,
+                todo = {},
+                summary = [],
+                fieldValue,
+                lastFieldValue;
+ 
+            Ext.each(page, function (rec) {
+                fieldValue = rec[groupField];
+                todo[fieldValue] = true;
+            });
+ 
+            function flush () {
+                if (accum) {
+                    summary.push(me.getGroupSummary(groupField, accum, ctx));
+                    accum = null;
+                }
+            }
+ 
+            // data is ordered primarily by the groupField, so one pass can pick up all 
+            // the summaries one at a time. 
+            Ext.each(data, function (rec) {
+                fieldValue = rec[groupField];
+ 
+                if (lastFieldValue !== fieldValue) {
+                    flush();
+                    lastFieldValue = fieldValue;
+                }
+ 
+                if (!todo[fieldValue]) {
+                    // if we have even 1 summary, we have summarized all that we need 
+                    // (again because data and page are ordered by groupField) 
+                    return !summary.length;
+                }
+ 
+                if (accum) {
+                    accum.push(rec);
+                } else {
+                    accum = [rec];
+                }
+ 
+                return true;
+            });
+ 
+            flush(); // make sure that last pesky summary goes... 
+ 
+            return summary;
+        }
+    };
+}());

+ 35 - 0
ux/ajax/JsonSimlet.js

@@ -0,0 +1,35 @@
+/**
+ * JSON Simlet.
+ */
+Ext.define('Ext.ux.ajax.JsonSimlet', {
+    extend: 'Ext.ux.ajax.DataSimlet',
+    alias: 'simlet.json',
+ 
+    doGet: function (ctx) {
+        var me = this,
+            data = me.getData(ctx),
+            page = me.getPage(ctx, data),
+            reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),
+            root = reader && reader.getRootProperty(),
+            ret = me.callParent(arguments), // pick up status/statusText 
+            response = {};
+ 
+        if (root && Ext.isArray(page)) {
+            response[root] = page;
+            response[reader.getTotalProperty()] = data.length;
+        } else {
+            response = page;
+        }
+ 
+        if (ctx.groupSpec) {
+            response.summaryData = me.getSummary(ctx, data, page);
+        }
+ 
+        ret.responseText = Ext.encode(response);
+        return ret;
+    },
+ 
+    doPost : function(ctx) {
+        return this.doGet(ctx);
+    }
+});

+ 234 - 0
ux/ajax/SimManager.js

@@ -0,0 +1,234 @@
+/**
+ * This singleton manages simulated Ajax responses. This allows application logic to be
+ * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
+ * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
+ * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
+ *
+ * The requires hooks are inserted when either the {@link #init} method is called or the
+ * first {@link Ext.ux.ajax.Simlet} is registered. For example:
+ *
+ *      Ext.onReady(function () {
+ *          initAjaxSim();
+ *
+ *          // normal stuff
+ *      });
+ *
+ *      function initAjaxSim () {
+ *          Ext.ux.ajax.SimManager.init({
+ *              delay: 300
+ *          }).register({
+ *              '/app/data/url': {
+ *                  type: 'json',  // use JsonSimlet (type is like xtype for components)
+ *                  data: [
+ *                      { foo: 42, bar: 'abc' },
+ *                      ...
+ *                  ]
+ *              }
+ *          });
+ *      }
+ *
+ * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}. To make
+ * non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
+ * to the Ajax options:
+ *
+ *      Ext.Ajax.request({
+ *          url: 'page.php',
+ *          nosim: true, // ignored by normal Ajax request
+ *          params: {
+ *              id: 1
+ *          },
+ *          success: function(response){
+ *              var text = response.responseText;
+ *              // process server response here
+ *          }
+ *      });
+ */
+Ext.define('Ext.ux.ajax.SimManager', {
+    singleton: true,
+ 
+    requires: [
+        'Ext.data.Connection',
+        'Ext.ux.ajax.SimXhr',
+        'Ext.ux.ajax.Simlet',
+        'Ext.ux.ajax.JsonSimlet'
+    ],
+ 
+    /**
+     * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
+     * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
+     * return 404. Set this to null to use real Ajax calls for non-matching URL's.
+     */
+ 
+    /**
+     * @cfg {String} defaultType 
+     * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
+     * default is 'basic'.
+     */
+    defaultType: 'basic',
+ 
+    /**
+     * @cfg {Number} delay 
+     * The number of milliseconds to delay before delivering a response to an async request.
+     */
+    delay: 150,
+ 
+    /**
+     * @property {Boolean} ready 
+     * True once this singleton has initialized and applied its Ajax hooks.
+     * @private
+     */
+    ready: false,
+ 
+    constructor: function () {
+        this.simlets = [];
+    },
+ 
+    getSimlet: function (url) {
+        // Strip down to base URL (no query parameters or hash): 
+        var me = this,
+            index = url.indexOf('?'),
+            simlets = me.simlets,
+            len = simlets.length,
+            i, simlet, simUrl, match;
+ 
+        if (index < 0) {
+            index = url.indexOf('#');
+        }
+        if (index > 0) {
+            url = url.substring(0, index);
+        }
+        
+        for (i = 0; i < len; ++i) {
+            simlet = simlets[i];
+            simUrl = simlet.url;
+            if (simUrl instanceof RegExp) {
+                match = simUrl.test(url);
+            } else {
+                match = simUrl === url;
+            }
+            if (match) {
+                return simlet;
+            }
+        }
+ 
+        return me.defaultSimlet;
+    },
+ 
+    getXhr: function (method, url, options, async) {
+        var simlet = this.getSimlet(url);
+ 
+        if (simlet) {
+            return simlet.openRequest(method, url, options, async);
+        }
+ 
+        return null;
+    },
+ 
+    /**
+     * Initializes this singleton and applies configuration options.
+     * @param {Object} config An optional object with configuration properties to apply.
+     * @return {Ext.ux.ajax.SimManager} this
+     */
+    init: function (config) {
+        var me = this;
+ 
+        Ext.apply(me, config);
+ 
+        if (!me.ready) {
+            me.ready = true;
+ 
+            if (!('defaultSimlet' in me)) {
+                me.defaultSimlet = new Ext.ux.ajax.Simlet({
+                    status: 404,
+                    statusText: 'Not Found'
+                });
+            }
+ 
+            me._openRequest = Ext.data.Connection.prototype.openRequest;
+ 
+            Ext.data.request.Ajax.override({
+                openRequest: function (options, requestOptions, async) {
+                    var xhr = !options.nosim &&
+                              me.getXhr(requestOptions.method, requestOptions.url, options, async);
+                    if (!xhr) {
+                        xhr = this.callParent(arguments);
+                    }
+                    return xhr;
+                }
+            });
+ 
+            if (Ext.data.JsonP) {
+                Ext.data.JsonP.self.override({
+                    createScript: function (url, params, options) {
+                        var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),
+                            script = !options.nosim &&
+                                     me.getXhr('GET', fullUrl, options, true);
+ 
+                        if (!script) {
+                            script = this.callParent(arguments);
+                        }
+ 
+                        return script;
+                    },
+ 
+                    loadScript: function (request) {
+                        var script = request.script;
+                        if (script.simlet) {
+                            script.jsonpCallback = request.params[request.callbackKey];
+                            script.send(null);
+ 
+                            // Ext.data.JsonP will attempt dom removal of a script tag, so emulate its presence 
+                            request.script = document.createElement('script');
+                        } else {
+                            this.callParent(arguments);
+                        }
+                    }
+                });
+            }
+        }
+ 
+        return me;
+    },
+ 
+    openRequest: function (method, url, async) {
+        var opt = {
+            method: method,
+            url: url
+        };
+        return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);
+    },
+ 
+    /**
+     * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
+     * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
+     * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
+     * instances or configs.
+     */
+    register: function (simlet) {
+        var me = this;
+ 
+        me.init();
+ 
+        function reg (one) {
+            var simlet = one;
+            if (!simlet.isSimlet) {
+                simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);
+            }
+            me.simlets.push(simlet);
+            simlet.manager = me;
+        }
+ 
+        if (Ext.isArray(simlet)) {
+            Ext.each(simlet, reg);
+        } else if (simlet.isSimlet || simlet.url) {
+            reg(simlet);
+        } else {
+            Ext.Object.each(simlet, function (url, s) {
+                s.url = url;
+                reg(s);
+            });
+        }
+ 
+        return me;
+    }
+});

+ 120 - 0
ux/ajax/SimXhr.js

@@ -0,0 +1,120 @@
+/**
+ * Simulates an XMLHttpRequest object's methods and properties but is backed by a
+ * {@link Ext.ux.ajax.Simlet} instance that provides the data.
+ */
+Ext.define('Ext.ux.ajax.SimXhr', {
+    readyState: 0,
+ 
+    mgr: null,
+    simlet: null,
+ 
+    constructor: function (config) {
+        var me = this;
+ 
+        Ext.apply(me, config);
+        me.requestHeaders = {};
+    },
+ 
+    abort: function () {
+        var me = this;
+ 
+        if (me.timer) {
+            Ext.undefer(me.timer);
+            me.timer = null;
+        }
+        me.aborted = true;
+    },
+ 
+    getAllResponseHeaders: function () {
+        var headers = [];
+        if (Ext.isObject(this.responseHeaders)) {
+            Ext.Object.each(this.responseHeaders, function (name, value) {
+                headers.push(name + ': ' + value);
+            });
+        }
+        return headers.join('\x0d\x0a');
+    },
+ 
+    getResponseHeader: function (header) {
+        var headers = this.responseHeaders;
+        return (headers && headers[header]) || null;
+    },
+ 
+    open: function (method, url, async, user, password) {
+        var me = this;
+        me.method = method;
+        me.url = url;
+        me.async = async !== false;
+        me.user = user;
+        me.password = password;
+ 
+        me.setReadyState(1);
+    },
+ 
+    overrideMimeType: function (mimeType) {
+        this.mimeType = mimeType;
+    },
+ 
+    schedule: function () {
+        var me = this,
+            delay = me.simlet.delay || me.mgr.delay;
+            
+        if (delay) {
+            me.timer = Ext.defer(function () {
+                me.onTick();
+            }, delay);
+        } else {
+            me.onTick();
+        }
+    },
+ 
+    send: function (body) {
+        var me = this;
+ 
+        me.body = body;
+ 
+        if (me.async) {
+            me.schedule();
+        } else {
+            me.onComplete();
+        }
+    },
+ 
+    setReadyState: function (state) {
+        var me = this;
+        if (me.readyState != state) {
+            me.readyState = state;
+            me.onreadystatechange();
+        }
+    },
+ 
+    setRequestHeader: function (header, value) {
+        this.requestHeaders[header] = value;
+    },
+ 
+    // handlers 
+ 
+    onreadystatechange: Ext.emptyFn,
+ 
+    onComplete: function () {
+        var me = this,
+            callback;
+ 
+        me.readyState = 4;
+        Ext.apply(me, me.simlet.exec(me));
+ 
+        callback = me.jsonpCallback;
+        if (callback) {
+            var text = callback + '(' + me.responseText + ')';
+            eval(text);
+        }
+    },
+ 
+    onTick: function () {
+        var me = this;
+ 
+        me.timer = null;
+        me.onComplete();
+        me.onreadystatechange && me.onreadystatechange();
+    }
+});

+ 218 - 0
ux/ajax/Simlet.js

@@ -0,0 +1,218 @@
+/**
+ * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
+ * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
+ */
+Ext.define('Ext.ux.ajax.Simlet', function () {
+    var urlRegex = /([^?#]*)(#.*)?$/,
+        dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,
+        intRegex = /^[+-]?\d+$/,
+        floatRegex = /^[+-]?\d+\.\d+$/;
+ 
+    function parseParamValue (value) {
+        var m;
+ 
+        if (Ext.isDefined(value)) {
+            value = decodeURIComponent(value);
+ 
+            if (intRegex.test(value)) {
+                value = parseInt(value, 10);
+            } else if (floatRegex.test(value)) {
+                value = parseFloat(value);
+            } else if (!!(m = dateRegex.exec(value))) {
+                value = new Date(Date.UTC(+m[1], +m[2]-1, +m[3], +m[4], +m[5], +m[6]));
+            }
+        }
+ 
+        return value;
+    }
+ 
+    return {
+        alias: 'simlet.basic',
+ 
+        isSimlet: true,
+ 
+        responseProps: ['responseText', 'responseXML', 'status', 'statusText', 'responseHeaders'],
+ 
+        /**
+         * @cfg {String/Function} responseText
+         */
+ 
+        /**
+         * @cfg {String/Function} responseXML
+         */
+ 
+        /**
+         * @cfg {Object/Function} responseHeaders
+         */
+ 
+        /**
+         * @cfg {Number/Function} status
+         */
+        status: 200,
+ 
+        /**
+         * @cfg {String/Function} statusText
+         */
+        statusText: 'OK',
+ 
+        constructor: function (config) {
+            Ext.apply(this, config);
+        },
+ 
+        doGet: function (ctx) {
+            return this.handleRequest(ctx);
+        },
+        
+        doPost: function (ctx) {
+            return this.handleRequest(ctx);
+        },
+ 
+        doRedirect: function (ctx) {
+            return false;
+        },
+ 
+        doDelete: function (ctx) {
+            var me = this,
+                xhr = ctx.xhr,
+                records = xhr.options.records;
+            me.removeFromData(ctx,records);
+        },
+ 
+        /**
+         * Performs the action requested by the given XHR and returns an object to be applied
+         * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
+         * this is delegated to `doMethod` methods on this class, such as `doGet`.
+         *
+         * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
+         * @return {Object} The response properties to add to the XMLHttpRequest.
+         */
+        exec: function (xhr) {
+            var me = this,
+                ret = {},
+                method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()), // doGet 
+                fn = me[method];
+ 
+            if (fn) {
+                ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));
+            } else {
+                ret = { status: 405, statusText: 'Method Not Allowed' };
+            }
+ 
+            return ret;
+        },
+ 
+        getCtx: function (method, url, xhr) {
+            return {
+                method: method,
+                params: this.parseQueryString(url),
+                url: url,
+                xhr: xhr
+            };
+        },
+ 
+        handleRequest: function(ctx) {
+            var me = this,
+                ret = {},
+                val;
+ 
+            Ext.Array.forEach(me.responseProps, function (prop) {
+                if (prop in me) {
+                    val = me[prop];
+                    if (Ext.isFunction(val)) {
+                        val = val.call(me, ctx);
+                    }
+                    ret[prop] = val;
+                }
+            });
+ 
+            return ret;
+        },
+ 
+        openRequest: function (method, url, options, async) {
+            var ctx = this.getCtx(method, url),
+                redirect = this.doRedirect(ctx),
+                xhr;
+            if (options.action === 'destroy'){
+                method = 'delete';
+            }
+            if (redirect) {
+                xhr = redirect;
+            } else {
+                xhr = new Ext.ux.ajax.SimXhr({
+                    mgr: this.manager,
+                    simlet: this,
+                    options: options
+                });
+                xhr.open(method, url, async);
+            }
+            
+            return xhr;
+        },
+ 
+        parseQueryString : function (str) {
+            var m = urlRegex.exec(str),
+                ret = {},
+                key,
+                value,
+                i, n;
+ 
+            if (m && m[1]) {
+                var pair, parts = m[1].split('&');
+ 
+                for (i = 0, n = parts.length; i < n; ++i) {
+                    if ((pair = parts[i].split('='))[0]) {
+                        key = decodeURIComponent(pair.shift());
+                        value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);
+ 
+                        if (!(key in ret)) {
+                            ret[key] = value;
+                        } else if (Ext.isArray(ret[key])) {
+                            ret[key].push(value);
+                        } else {
+                            ret[key] = [ret[key], value];
+                        }
+                    }
+                }
+            }
+ 
+            return ret;
+        },
+ 
+        redirect: function (method, url, params) {
+            switch (arguments.length) {
+                case 2:
+                    if (typeof url == 'string') {
+                        break;
+                    }
+                    params = url;
+                    // fall... 
+                case 1:
+                    url = method;
+                    method = 'GET';
+                    break;
+            }
+ 
+            if (params) {
+                url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
+            }
+            return this.manager.openRequest(method, url);
+        },
+ 
+        removeFromData: function(ctx, records) {
+            var me = this,
+                data = me.getData(ctx),
+                model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},
+                idProperty = model.idProperty || 'id';
+ 
+            Ext.each(records, function(record) {
+                var id = record.get(idProperty);
+                for (var i = data.length; i-- > 0;) {
+                    if (data[i][idProperty] === id) {
+                        me.deleteRecord(i);
+                        break;
+                    }
+                }
+            });
+        }
+    };
+}());