partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

StoreMenu.js (12168B)


      1 /**
      2  * Ext.ux.menu.StoreMenu  Addon
      3  *
      4  * Inspired from the Ext.ux.menu.StoreMenu for ExtJs 3 by Marco Wienkoop
      5  *
      6  * This version is a complete rewrite and enhanced for ExtJs 4 with some of the old features removed.
      7  * @author Joe Kuan
      8  * @docauthor Joe Kuan
      9  * Joe Kuan <kuan.joe@gmail.com>
     10  *
     11  * #Demo and Download
     12  * Here are the links for the online [demo](http://joekuan.org/demos/StoreMenu_ExtJs_4/) and [github](http://github.com/JoeKuan/StoreMenu_ExtJs_4)
     13  * download. For usage, see [license](http://github.com/JoeKuan/StoreMenu_ExtJs_4/blob/master/License).
     14  *
     15  * #Creating &amp; Applying StoreMenu
     16  *
     17  * Suppose we define a JSON Store for a list of menus as follows:
     18  *
     19  *      @example
     20  *      Ext.define('Menu', {
     21  *          extend: 'Ext.data.Model',
     22  *          fields: [ 'id', 'text', 'iconCls' ]
     23  *      });
     24  *
     25  *     var store = Ext.create('Ext.data.Store', {
     26  *         model: 'Menu',
     27  *         proxy: {
     28  *             type: 'ajax',
     29  *             url: 'demo/menu.php',
     30  *             reader: {
     31  *                type: 'json',
     32  *                root: 'root'
     33  *             }
     34  *         }
     35  *     });
     36  *
     37  *  The *id*, *text* and *iconCls* fields are mapped to menu item config.
     38  *
     39  *  Assume the file menu.php returns the following in JSON:
     40  *     @example
     41  *     { "success": true,
     42  *       "root": [{
     43  *                  "id": 1, "text": "Menu 1"
     44  *                 },{
     45  *                  "id": 2, "text": "Menu 2", "iconCls": "calendar"
     46  *                 },{
     47  *                  "id": 3, "text": "Menu 3"
     48  *                 }
     49  *        ]
     50  *     }
     51  *
     52  *  To produce a simple Window with store menu inside a toolbar, here is the code
     53  *     @example
     54  *     Ext.create('Ext.window.Window', {
     55  *         height: 380,
     56  *         width: 450,
     57  *         title: 'Menu Store for ExtJs 4',
     58  *         tbar: [{
     59  *            menu: Ext.create('Ext.ux.menu.StoreMenu', {
     60  *                      store: store,
     61  *                      itemsHandler: function(item, evt) {
     62  *                           Ext.example.msg("Store Menu",
     63  *                                           "You click on item with id " + item.id);
     64  *                      }
     65  *                  }),
     66  *            // Need this for empty menu - no items option
     67  *            showEmptyMenu: true,
     68  *            text: 'Menu Demo 1'
     69  *         }]
     70  *     }).show();
     71  *
     72  * Clicking any of the menus dynamically generated will call itemsHandler. To differentiate
     73  * between the menus, each menu item is created with the id option which is taken from the
     74  * id field of the store
     75  *
     76  * #StoreMenu without *items* option
     77  * The menu rendering policy has been changed since ExtJs 4.2.1 so that menu is not rendered
     78  * if the {@link Ext.menu.Menu#cfg-items} option is empty. Subsequently, this will stop the
     79  * store from loading, hence no dynamic menus will be displayed. In order to force the
     80  * StoreMenu to render, an option, {@link Ext.button.Button#cfg-showEmptyMenu},
     81  * is needed to pass to the owner button in this scenario.
     82  *
     83  * #Creating Specific Menus
     84  * For more specific menus, StoreMenu supports object specifier through single field name, *config*.
     85  * Store record with *config* field is expected to contain required options for creating menu objects, such as xtype.
     86  * Menus like: menucheckitem, separator can be specified through this scheme.
     87  * Moreover, this can be mixed with normal menu item creation which the data model definition
     88  * includes both field name schemes. The following shows an example for creating specific menus along with default
     89  * menu item through the store.
     90  *     @example
     91  *     Ext.define('Menu', {
     92  *         extend: 'Ext.data.Model',
     93  *         fields: [ 'id', 'text', 'iconCls', 'config' ]
     94  *     });
     95  *
     96  * The server side is configured to return the following menus in JSON:
     97  *     @example
     98  *     { "root": [{ "config": { "id": "2A", "text": "Menu 2A", "xtype": "menucheckitem"} },
     99  *                { "config": { "xtype": "menuseparator"} },
    100  *                { "id": "2C", "text": "Menu 2C" }
    101  *               ]
    102  *     }
    103  *
    104  * #Creating Submenus
    105  * The StoreMenu also supports submenu entries (single level only). The server will need to
    106  * return additional fields for including submenu entries (see menuField) and id string for
    107  * their handlers (see smHandlers).
    108  * The following is what the server side should return for submenus
    109  *     @example
    110  *     { "root": [{ "id": "3A", "text": "Menu 3A",
    111  *                  "menu": [{  "id": "3A-1", "text": "Submenu 3A-1", "smHandler": "submenu3A1" },
    112  *                           {  "id": "3A-2", "text": "Submenu 3A-2", "smHandler": "submenu3A2" }
    113  *               ]
    114  *     }
    115  * To bind with the submenu handlers, we can create the StoreMenu as follows:
    116  *     @example
    117  *     var menu3 = Ext.create('Ext.ux.menu.StoreMenu', {
    118  *         store: store,
    119  *         smHandlers: {
    120  *             submenu3A1: function(item) {
    121  *                 Ext.example.msg("Third Menu Store", "This is submenu handler specific for menu 3A-1");
    122  *             },
    123  *             submenu3A2: function(item) {
    124  *                 Ext.example.msg("Third Menu Store", "This is submenu handler specific for menu 3A-2");
    125  *             }
    126  *         }
    127  *     });
    128  *
    129  */
    130 Ext.define("Ext.ux.menu.StoreMenu", {
    131     extend: 'Ext.menu.Menu',
    132     alias: 'widget.storemenu',
    133 
    134     config: {
    135         /***
    136          * message shown next to the store menu when it is loading
    137          */
    138         loadingText: 'Loading...',
    139         /**
    140          *  offset is to use with static menus, i.e. the offset position for
    141          *  the dynamic menus to start. E.g. if two static menus are included inside the items
    142          *  option, by setting *offset* to 2 the dynamic menus start below the static
    143          *  menus. Also a separator is added between static and dynamic menus
    144          */
    145         offset: 0,
    146         /**
    147          *  reload the store everytime the top menu is expanded
    148          */
    149         autoReload: true,
    150         /**
    151          *  nameField is to map the field for the menu title returning from the
    152          *  store.
    153          */
    154         nameField: 'text',
    155 
    156         /**
    157          *
    158          * optional Field of the store
    159          */
    160         url: 'url',
    161 
    162         /**
    163          *  idField is to map the menu id entry from the store
    164          */
    165         idField: 'id',
    166         /**
    167          *  iconField is to map the menu icon (iconCls)
    168          */
    169         iconField: 'iconCls',
    170         /**
    171          *  itemsHandler is the general menu handler for the __first level__ menus.
    172          *  For submenu handler, see subMenuHandler
    173          */
    174         itemsHandler: Ext.emptyFn,
    175         /**
    176          *  configField is used for specific menu types other than
    177          *  menu item (default). The config field returned from the server
    178          *  side is expected to hold all the required options to create the
    179          *  specific menu
    180          */
    181         configField: 'config',
    182         /**
    183          *  the field name containing the list of submenu entries in the store
    184          */
    185         menuField: 'menu'
    186     },
    187 
    188     /***
    189      * @property {Object} store
    190      * Data store for the menus &amp; submenus entries
    191      */
    192     store: null,
    193 
    194     // Keep track of what menu items have been added
    195     storeMenus: [],
    196 
    197     loaded: false,
    198     loadMask: null,
    199 
    200     onMenuLoad: function ()
    201     {
    202         if (!this.loaded || this.autoReload)
    203         {
    204             this.store.load();
    205         }
    206     },
    207 
    208     updateMenuItems: function (loadedState, records)
    209     {
    210 
    211         for (var i = 0; i < this.storeMenus.length; i++)
    212         {
    213             this.remove(this.storeMenus[i]);
    214         }
    215         this.storeMenus = [];
    216 
    217         if (loadedState)
    218         {
    219 
    220             // If offset is specified, it means we have to put the
    221             // dynamic menus after the static menus. We put a separator
    222             // to separate both
    223             var count = 0;
    224             if (this.offset)
    225             {
    226                 this.storeMenus.push(this.insert(this.offset, {xtype: 'menuseparator'}));
    227                 count = 1;
    228             }
    229 
    230             Ext.each(records, function (record)
    231             {
    232 
    233                 var menuSettings = {};
    234                 var patterns = {};
    235                 patterns.protocol = '^(http(s)?(:\/\/)){1}(www[.])?';
    236                 patterns.domain = '([a-zA-Z0-9-_\.])+';
    237                 patterns.params = '([.][a-zA-Z0-9(-|\/|=|?)?]+)';
    238                 var regex = new RegExp(patterns.protocol + patterns.domain + patterns.params + "$");
    239 
    240 
    241                 if (record.data[this.configField])
    242                 {
    243                     Ext.apply(menuSettings, record.data[this.configField]);
    244                 } else
    245                 {
    246                     menuSettings = {
    247                         id: record.data[this.idField],
    248                         text: record.data[this.nameField],
    249                         iconCls: record.data[this.iconField],
    250                         handler: this.itemsHandler
    251                     };
    252                     regex.test(record.data[this.url]) ? menuSettings.url = record.data[this.url] : "";
    253                 }
    254 
    255                 if (record.data[this.menuField])
    256                 {
    257                     menuSettings.menu = {items: []};
    258                     Ext.each(record.data[this.menuField], function (menuitem)
    259                     {
    260                         // Make sure the handler name is defined
    261                         if (menuitem.smHandler && this.smHandlers[menuitem.smHandler])
    262                         {
    263                             menuSettings.menu.items.push({
    264                                 id: menuitem[this.idField],
    265                                 text: menuitem[this.nameField],
    266                                 iconCls: menuitem[this.iconField],
    267                                 url: menuItem[this.url],
    268                                 handler: this.smHandlers[menuitem.smHandler]
    269                             });
    270                         }
    271                     }, this);
    272                 }
    273 
    274                 this.storeMenus.push(this.insert(this.offset + count, menuSettings));
    275                 count++;
    276             }, this)
    277 
    278         } else
    279         {
    280             this.storeMenus.push(this.insert(this.offset,
    281                 '<span class="loading-indicator">' +
    282                 this.loadingText + '</span>'));
    283         }
    284 
    285         this.loaded = loadedState;
    286     },
    287 
    288     onBeforeLoad: function ()
    289     {
    290         this.updateMenuItems(false);
    291     },
    292 
    293     onLoad: function (store, records)
    294     {
    295         this.updateMenuItems(true, records);
    296     },
    297 
    298     /***
    299      * Set a submenu handler.
    300      * @param handlerType {String} the id value for the handler function
    301      * @param handler {Function} the handler implementation - See menuitem handler for function parameters
    302      */
    303     setSubMenuHandler: function (handlerType, handler)
    304     {
    305         this.smHandlers[handlerType] = handler;
    306     },
    307 
    308     /**
    309      *  A utility method for changing parameters of the underlying
    310      *  JSON store. Values inside params object will overwrite the store's
    311      *  existing parameters with the same name
    312      *  @param {Object} params an object of parameters to be set in the store
    313      */
    314     setParams: function (params)
    315     {
    316         Ext.apply(this.store.getProxy().extraParams, params);
    317     },
    318 
    319     setStore: function (store)
    320     {
    321         this.store = store;
    322     },
    323 
    324     /**
    325      * @cfg smHandlers {Object} is an object holding all the submenu handler implementations.
    326      * Inside the object, option name is the identifier for the handler function which is the option value.
    327      * (See the [submenu section] (#submenu) for example)
    328      */
    329     smHandlers: {},
    330 
    331     initComponent: function (config)
    332     {
    333         this.callParent(arguments);
    334 
    335         if (!this.store)
    336         {
    337             //at least url/proxy or data need to be given in config when initiating this
    338             // component
    339             this.store = Ext.create('Ext.data.Store', {
    340                 model: this.model,
    341                 remoteFilter: true
    342             });
    343         }
    344 
    345         this.on("beforeshow", this.onMenuLoad, this);
    346         this.store.on('beforeload', this.onBeforeLoad, this);
    347         this.store.on('load', this.onLoad, this);
    348     }
    349 
    350 });