📜 ⬆️ ⬇️

Making a beautiful list with GroupingStore / View and ExtJS

Today we will talk how to make ExtJS a beautiful (and functional) list of any data, for example, a list of users or groups. I use such a list in one of the current projects (although it’s not so beautiful and convenient there). This widget can be used to display any data that is characterized not only by the test line, but also by extended data, and you also need to compare some actions to each recruitment. You can dynamically update data (via the Store), as well as sorting and grouping - in general, all the possibilities provided by the Grid component of ExtJS. Immediately I will say that I will use the version of ExtJS 3.0, but in the previous release, the 2.3.x example should also be workable. The given component is an example and is not ready for use by the code, but only a demonstration of the possibilities; you can change and modify as you like in your projects. For the same reason, there is intentionally no source code for the article.

The main point that I took into account in this component is to shorten the distance between actions as much as possible, that is, looking at the list, the user should immediately get the maximum of the information he needs (in the context of the list, of course), and if that cannot be shown that information about the action itself ( opportunities) should also be immediately visible. That is why I abandoned the context menu, or rather, duplicated actions with icons right on the list. Thus, I eliminate the need for additional action (opening the menu only to find out the answer to the question “what else can I do from here”), immediately showing possible actions. Proceeding from the same principle, only reference information describing one or another item is given to tooltips — it is used only if the user cannot initially understand the purpose of an element and explicitly requests a hint by pointing the cursor.

Okay, let's start developing. As the data source for the list, we will use the data array (reading it through the ArrayReader ), and Ext.data.GroupingStore as the data storage . This stor allows you to use grouping data by one of the fields, although I was faced with an interesting situation with the rendering of groups, but more on that later. And so, we transfer our data to the store, and it sorts and groups them according to the specified criterion, which we use as one of the fields. Array as a repository of information was chosen based on the fact that I did not need to directly update the data from the server, we have a separate mechanism involved in it, so for simplicity we believe that we already have all the data locally.

The source data has the following structure:

')
var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  1. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  2. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  3. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  4. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  5. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  6. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  7. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  8. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  9. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  10. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  11. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
  12. var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .
var _user_groups = [ [0, 'user_home.png' , ' ' ,2, 'system' , ' ' ], [10, 'user_home.png' , ' ' ,10, 'profy' , '' ], [20, 'user_home.png' , ' iPhone/iTouch' ,22, 'profy' , '' ], [30, 'user_home.png' , ' FreeBSD' ,11, 'profy' , '' ], [40, 'user_home.png' , ' ' ,20, 'filials' , ' ' ], [50, 'user_home.png' , ' ' ,120, 'publicusers' , '' ], [60, 'user_home.png' , ' ' ,99, 'publicusers' , ' , ' ] ]; * This source code was highlighted with Source Code Highlighter .


And this is what the Store preparation code looks like. We set the group code as a field for grouping, then by it we will get the rest of the data from the service array. Unfortunately, there are some difficulties in working with the grouping field - when rendering each line, the script will rewrite the group header and therefore we need to rewrite it each time. Although there is a template header templating mechanism, but despite the examples, it didn’t work out for me, so I redefined the header rendering function manually. Consider also that grouping is possible only by the same field that we sort.

  1. var _usergroup_store = new Ext.data.GroupingStore ({
  2. reader: new Ext.data.ArrayReader ({},
  3. [{
  4. name: 'id' ,
  5. type: 'int'
  6. }, {
  7. name: 'group_icon' ,
  8. type: 'string'
  9. }, {
  10. name: 'group_name' ,
  11. type: 'string'
  12. }, {
  13. name: 'count_users' ,
  14. type: 'string'
  15. }, {
  16. id: 'group_type' ,
  17. name: 'group_type' ,
  18. type: 'string'
  19. }, {
  20. name: 'group_desc' ,
  21. type: 'string'
  22. }])
  23. data: _user_groups,
  24. sortInfo: {
  25. field: 'group_type' ,
  26. direction: "ASC"
  27. },
  28. groupField: 'group_type' ,
  29. groupOnSort: true
  30. });
* This source code was highlighted with Source Code Highlighter .


We also need an additional source of data about groups. I implemented it as an object, where by the group code you can get its description, icon and other data. This, of course, could all be included in the original dataset, but the same data will be used in other places, so they are taken out separately.

  1. var _user_groups_cats = {
  2. system: {
  3. icon: 'user_home.png' ,
  4. title: 'System Groups' ,
  5. desc: 'A service group for Wheemplay Ltd. employees only' ,
  6. isClosed: true
  7. isPrivate: false
  8. },
  9. profy: {
  10. icon: 'user_star.png' ,
  11. title: 'Professional groups' ,
  12. desc: 'Interest Group' ,
  13. isClosed: false ,
  14. isPrivate: false
  15. },
  16. filials: {
  17. icon: 'user_earth.png' ,
  18. title: 'Affiliate Groups' ,
  19. desc: 'Private Branch Groups' ,
  20. isClosed: false ,
  21. isPrivate: true
  22. },
  23. publicusers: {
  24. icon: 'group.png' ,
  25. title: 'General Custom' ,
  26. desc: 'Public Groups, where anyone can enter' ,
  27. isClosed: false ,
  28. isPrivate: false
  29. },
  30. my: {
  31. icon: 'user_comment.png' ,
  32. title: '<strong> My Groups </ strong>' ,
  33. desc: 'My groups (of which you are a member)' ,
  34. isClosed: true
  35. isPrivate: true
  36. }
  37. }
* This source code was highlighted with Source Code Highlighter .


In addition to the usual parameters of groups, I have two additional - whether the group is closed and private. You can have any other arbitrary parameters. They are used to display the necessary icons and actions for each group based on the parameters. That is, if the group is closed, then there is no need to display an icon with the action “join the group”, etc.

Additionally, I store in the array the codes of the groups to which the current user belongs:
  var _i_in_group = [0, 30, 60]; 

Now we have all the data to render our list. To do this, we use the Ext.grid.GridPanel component, although its functionality is, of course, a bit redundant for such an example. It would be better to use something like ListView, perhaps in the next article I will try to remake everything for it, but for now let's try through the Grid. Otherwise, it would be necessary to invent an independent implementation of the group, and the future expansion would be questionable. So you will have to consider your case personally - if you don’t need all the features of the grid, try using the lightweight and fast ListView , however there will be a lot more manual work.

And so, we create the Ext.grid.GridPanel component, as the data source we use the Ext.data.GroupingStore (_usergroup_store) that we previously created. The main functionality is concentrated in the description of the columns and their renderings, which we now discuss in more detail. First, I will show you the description of columns without renders (render or renderer is a special method that returns arbitrary text or html code, which is displayed as a value in a cell).

  1. columns: [{
  2. id: 'group_name' ,
  3. header: "<strong> Groups </ strong>" ,
  4. sortable: false ,
  5. width: 150,
  6. dataIndex: 'group_name'
  7. }, {
  8. header: "Members" ,
  9. width: 35,
  10. hidden: false
  11. sortable: true
  12. dataIndex: 'count_users'
  13. }, {
  14. header: "actions" ,
  15. width: 60,
  16. hidden: false
  17. sortable: false ,
  18. dataIndex: 'count_users'
  19. }, {
  20. id: 'group_type' ,
  21. dataIndex: 'group_type' ,
  22. hidden: true
  23. groupable: true
  24. }]
* This source code was highlighted with Source Code Highlighter .


As you can see, the grouping occurs by the last field - the group_type column, which uses data from the same column in the stack (usually I use the same column names, because the column id is the same as dataIndex). But we do not need to display this column, it is purely auxiliary for grouping, so we set the parameter hidden: true, and also indicate that we want to group by this field - groupable: true.

There are two types of renders for each column - renderer and groupRenderer. The first is responsible for displaying data in cells, the second is used to display the group header. Here lies one significant complexity (although, apparently, this is just a strange architectural solution of developers). The heading of the group is used as the style name of this element, and if we redefine the render of the group, for example, by adding the html-code, all this text will go to the group style identifier. Below is a screenshot from Firebug to illustrate this point. I also fully admit that I did not fully understand the grouping mechanism, so if you can supplement or correct me, I will be grateful.



Let us proceed to the description of the output of each column. Before the name of the group, we can have one or two icons. The green dot indicates whether the group is open - if it is, then the group is public, or it is private or closed, another icon is displayed. You can use your own group attributes and display other information. Each icon has its own tooltip added through markup (it is often more convenient).

  1. renderer: function (obj, x, y)
  2. {
  3. var src = '<span style = "cursor: pointer;">' ;
  4. // to simplify
  5. var tmp = _user_groups_cats [y.data.group_type];
  6. if (tmp.isClosed == true )
  7. {
  8. src = src +
  9. '<img src = "/ images / icons / bullet_error.png" alt = "" align = "absmiddle" />' ;
  10. }
  11. if (tmp.isPrivate == true )
  12. {
  13. src = src +
  14. '<img src = "/ images / icons / bullet_key.png" alt = "" align = "absmiddle" />' ;
  15. }
  16. if ((tmp.isClosed == false ) && (tmp.isPrivate == false ))
  17. {
  18. src = src +
  19. '<img src = "/ images / icons / bullet_green.png" alt = "" align = "absmiddle" />' ;
  20. }
  21. // be sure to return the result
  22. return src + '' + obj + '</ span>' ;
  23. }
* This source code was highlighted with Source Code Highlighter .


Read more about the renderer and how to set it up in the official documentation . Various data is transferred to the method, we are only interested in the initial value (the first parameter), the mat-data on the html object, where the value is rendered, as well as the current record object (the third parameter).

The second column is the number of participants in each group. But if the group is private, you need to hide this data, so we in the render check the type of the group and display the required value.

  1. renderer: function (obj, x, y)
  2. {
  3. if (_user_groups_cats [y.data.group_type] .isPrivate == false )
  4. {
  5. return obj + 'members.' ;
  6. }
  7. else
  8. return '<em> hidden </ em>' ;
  9. }
* This source code was highlighted with Source Code Highlighter .


The most interesting will be the third and last column, where we will display the button icons for user actions, and their set will depend on the group parameters. True, there are user extensions, RowActions and CellActions , but today we will do the same thing manually (honestly, I just did it first and then found these extensions, so I didn’t redo the finished code).

  1. renderer: function (obj, x, y)
  2. {
  3. var src = '' ;
  4. var tmp = _user_groups_cats [y.data.group_type];
  5. src = src + '<img style = "cursor: pointer;" ' +
  6. 'src = "/ images / icons / vcard.png" alt = "" align = "absmiddle" />' ;
  7. // if the group is not private and the user is not a member
  8. if ((tmp.isPrivate == false ) && (_i_in_group.indexOf (y.data.id) == -1))
  9. {
  10. src = src + '<img style = "cursor: pointer;" ' +
  11. 'src = "/ images / icons / user_add.png" alt = "" align = "absmiddle" />' ;
  12. }
  13. if ((tmp.isPrivate == false ) && (tmp.isClosed == false ))
  14. {
  15. src = src + '<img style = "cursor: pointer;" ' +
  16. 'src = "/ images / icons / group.png" alt = "" align = "absmiddle" />' ;
  17. }
  18. // check if the user is in the group
  19. if (_i_in_group.indexOf (y.data.id)! = -1)
  20. {
  21. src = src + '<img style = "cursor: pointer;" ' +
  22. 'src = "/ images / icons / user_delete.png" alt = "" align = "absmiddle" />' +
  23. '<img style = "cursor: pointer;" src = "/ images / icons / comments.png" alt = "" align = "absmiddle" /> ' ;
  24. }
  25. return src;
  26. }
* This source code was highlighted with Source Code Highlighter .


Next we have a hidden column, which is grouped. For her, we will redefine the render group to display a nice title. Render gets one value, the field that is declared as a condition for grouping, in our case, this is the group code, by which we will receive all the other information.

  1. groupRenderer: function (group)
  2. {
  3. return '<span> <img src = "/ images / icons /' + _user_groups_cats [group] .icon +
  4. '"alt =" "align =" absmiddle "/>' + _user_groups_cats [group] .title +
  5. '</ span>' ;
  6. }
* This source code was highlighted with Source Code Highlighter .


There is a final touch, it is necessary for our table to specify the required View, in our case - GroupingView .

  1. view: new Ext.grid.GroupingView ({
  2. forceFit: true
  3. enableNoGroups: false ,
  4. autoFill: true
  5. scrollOffset: 0,
  6. showGroupName: false ,
  7. groupTextTpl: '{text}'
  8. })
* This source code was highlighted with Source Code Highlighter .


We indicated that there could be no lines without groups, and also banned the display of the group name, since we redefined the render group. groupTextTpl - here you can see the template for displaying the name of the group, but there are difficulties with it, since it is rather difficult to describe the conditions and the processing of global values ​​inside the template. Therefore, we simply deduce what our render function returns and that’s all, without any processing.

That's it, as a result we get a nice grouped list. To implement custom actions, you need to hang handlers on the icons, but with this, I think, you can handle it yourself. I also note that in the example the table header is intentionally hidden, but if it is enabled (via the config of the grid), it will be possible to sort the data (according to those columns that are declared as sortable) by clicking on the header. In this case, the grouping will be saved, that is, the data within each group will be sorted individually.

In conclusion, I will show another screenshot, the second list, in which all users and additional data like the name of the company and a link to its website are displayed. It is obtained by a small refinement of the example described above, so that you can experiment on your own.


Source: https://habr.com/ru/post/66099/


All Articles