Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Need some best-practice input re. filtering dropdown based on list grid data.

    Hi guys! I am hoping someone can chime in with some thoughts about solving a use case I've never had before.

    Let's say I have a "schedule" for a user. the schedule datasource has a foreign key to user like so:
    Code:
    <field name="reporterId" type="integer" title="Användare" foreignKey="user.id" required="true" combo="true"/>
    So, in my schedule tab, I have a list grid to the left, that lists all existing schedules. You can add, edit remove a schedule item for a user. A user might have a schedule, but if the user does, there is only ONE row. (i.e. unique constraint on userId column)

    The "schedule" form is regular, i.e. a bunch of fields, and a ComboboxItem for the users list.

    Now, that list will contains all users returned by the datasource, as per normal as well.

    However, what I'm trying to do, is to filter that list based on already existing schedules. I.e. when a new schedule is to be created, the dropdown should only contain the items that are NOT already in the grid.

    I could of course use operationId, and have a custom server-side fetch to populate the dropdown. However, that would mean that I'd have to re-fetch it from the server every time a schedule is added or removed.

    ---

    I was thinking that I could do this client-side, by using a ondataarrivedhandler on the grid, store them away in an array. Then I could filter the items in the select item based on the data in the list grid every Time the user presses "add".

    So, basically, my question is, am I looking at this wrong way? is there a better approach to handle this, perhaps by using some sort of filtering mechanism I've missed?

    Thankful for pointers.

    #2
    Hi mathias, are you saying that the form dropdown must show only users which haven't a schedule yet?

    Comment


      #3
      yes exactly. when I click on the select item, It automatically uses the "user" datasource to fetch all user records automatically, as per usual. So, i am thinking about how to best filter out those fetched users that already have a schedule item (with userId) in the grid.

      Comment


        #4
        ok, so I think you must already fetch them with a criteria which excludes those who already have a schedule.

        Then, if you don't want to refresh the the picklist, I think that you could try an updateCaches with operationType: "remove" of the user used, but I think this will remove the record from all resultSets bounded to that dataSource, irrespective of the operationId used for the fetch.
        So I think you'll need a dedicated dataSource. Or a directly created ResultSet.

        The only other option that comes to mind, is something on the lines of https://www-demos.smartclient.com/sm...&skin=Twilight but with dragDataAction: "move", but this will require a different UI, at least, and a server side logic to make the "remove" operation issued on the employees dataSource a no-op.

        Comment


          #5
          Hi mathias,

          I have a similar problem (chat members, of course you can only add a person once, so this person should not be available anymore when adding more people).
          I solve this serverside as well, as I don't see an easy clientside solution either.

          But on the clientside the issue is even bigger, as the clientside caches the result from the 1st fetch with "1 user more". So you need to somehow bypass this cache and I do this by clientside criteria: "PKField notEqual randomNegativeValue". This clearly seems like a hack, but I don't see another solution. See here for an example. A different solution (using JS) from Isomorphic is shown here.
          To be honest, I'd still like simple setting (no showPicker-JS, no getPickListFilterCriteria) to enforce a fresh fetch on every pickList open.

          Best regards
          Blama

          Comment


            #6
            Hi mathias,

            with this very old enhancement idea one could even solve this clientside without any server code.
            Example for my case could be something along the lines of:
            Code:
            pickListCriteria: {fieldname:"userId", operatorId: "notInSet",
                 subRequest: {outputs: "userId", dataSource: "chatUser", criteria: {fieldname:"chatId", operatorId: "equals", value: "current_chat_id" }
            }
            Best regards
            Blama

            Comment


              #7
              You just need to implement getPickListFilterCriteria() and form a "notInSet" criteria from the PKs of users shown in the grid. As long as you don't need paging on that grid, this works, as the client has all the necessary data to do the filter.

              If the grid needs to be paged, then you should use an operationId, and basically do the same thing on the server: use a separate DSRequest to look up all the users that don't have a schedule item, and form a "notInSet" criteria from that, which should be combined with the inbound criteria from the original request.

              This latter approach will eventually be possible purely from the client.

              Comment


                #8
                Hi Isomorphic,

                could you elaborate on your last sentence? Are you thinking of something like #6 / this thread?

                mathias: If you do need a separate operationId, this might actually work without any server code, just a .ds.xml <whereClause>. See here.

                Best regards
                Blama

                Comment


                  #9
                  Hi guys, huge thanks for feedback. I'll try to respond individually... :)

                  Blama
                  Yeah exactly, I can define "setOptionOperationId()" on the ComboBoxItem and do a custom fetch server-side but that's only when page loads. How do I keep the list filtered when the user creates new schedules? I'll look at bit more at your links.

                  Isomorphic
                  OK that sounds interesting, but the same thing here, how do I time this? I add a ondataarrivedhandler on the grid, and in onArrived() create the picklistfilter and apply it to the comboboxitem? And again, how would I keep it up-to-date when the list grid has new items added or removed by the user?

                  cheers

                  Comment


                    #10
                    Hi mathias,

                    it definitely starts with ComboBoxItem.setPickListFilterCriteriaFunction(). Then two options:
                    • Little data in Schedule-ListGrid: dataFetchMode:"basic" ListGrid, and inside your ComboBoxItem-PickListFilterCriteriaFunction you collect the data for the notInSet-Criteria from the ListGrid-ResultSet (what Isomorphic said in #7)
                    • Lots of data (=Paging possible) in Schedule-ListGrid: PickListFilterCriteriaFunction with "PKField notEqual randomNegativeValue" (to enforce a new fetch on every PickList open) and then either
                      • operationBinding and serverside code
                      • operationBinding and .ds.xml whereClause with NOT IN (), like here

                    No on*-Handlers needed, the dynamic aspect is covered by the PickListFilterCriteriaFunction.

                    Best regards
                    Blama
                    Last edited by Blama; 15 Mar 2024, 07:33.

                    Comment


                      #11
                      Hey again, I threw a custom ComboBoxItem together, I think I got it working. See class below, if anyone needs parent classes let me know.

                      At first, I just used the setpicklistcriteria-function to create the criterion, but that thing gets called every time the user presses a key! We have clients with hundreds of users.

                      So, I only create it once, and then I have a reset-method that I call whenever the user presses "add", so that the criterion gets re-calculated once per "form-create-event" if you see what I mean.

                      Optimally I would only re-calculate the Criteria whenever the data in the list grid is updated but not sure how I would do that, this is fine enough I think.

                      If anyone see any issues, or have suggestions for improvements, let me know :)


                      Code:
                      import com.smartgwt.client.data.Criteria;
                      import com.smartgwt.client.data.Criterion;
                      import com.smartgwt.client.data.DataSource;
                      import com.smartgwt.client.data.DataSourceField;
                      import com.smartgwt.client.types.OperatorId;
                      import com.smartgwt.client.widgets.grid.ListGrid;
                      import com.smartgwt.client.widgets.grid.ListGridRecord;
                      
                      /**
                       * A FKComboBoxItem that filters the picklist based on the content of a ListGrid. It takes the attribute
                       * to use as filter from the grid and the attribute to filter on in the select item.
                       */
                      public class FKComboBoxWithGridFilterItem extends FKComboBoxItem {
                      
                          ListGrid filterGrid;
                          String gridFilterAttribute;
                          String selectItemFilterAttribute;
                      
                          private Criterion currentFilter;
                      
                          public FKComboBoxWithGridFilterItem(DataSource owningDataSource, String dataSourceField, ListGrid filterGrid,
                                                              String gridFilterAttribute, String selectItemFilterAttribute) {
                              this(owningDataSource, owningDataSource.getField(dataSourceField), filterGrid, gridFilterAttribute, selectItemFilterAttribute);
                          }
                      
                      
                          public FKComboBoxWithGridFilterItem(DataSource owningDataSource, DataSourceField dataSourceField, ListGrid filterGrid,
                                                              String gridFilterAttribute, String selectItemFilterAttribute) {
                              super(owningDataSource, dataSourceField, null);
                              this.filterGrid = filterGrid;
                              this.gridFilterAttribute = gridFilterAttribute;
                              this.selectItemFilterAttribute = selectItemFilterAttribute;
                          }
                      
                          /**
                           * Iterates through the records in the filterGrid and picks out the filter attributes, then creates
                           * a criterion that can be used as data for a filter in the selectitem.
                           *
                           * @return the Criteria to be used as a filter in the picklist.
                           */
                          @Override
                          public Criteria getPickListFilterCriteria() {
                              if(currentFilter == null){
                                  currentFilter = refreshFilter();
                              }
                              return currentFilter;
                          }
                      
                          private Criterion refreshFilter(){
                              ListGridRecord[] records = filterGrid.getRecords();
                              Long[] ids = new Long[records.length];
                              for (int i = 0; i < records.length ; i++) {
                                  ids[i] = records[i].getAttributeAsLong(gridFilterAttribute);
                              }
                              return new Criterion(selectItemFilterAttribute, OperatorId.NOT_IN_SET, ids);
                          }
                      
                          public void clearFilter(){
                              currentFilter = null;
                          }
                      }
                      Last edited by mathias; 16 Mar 2024, 00:34.

                      Comment


                        #12
                        Hi mathias,

                        At first, I just used the setpicklistcriteria-function to create the criterion, but that thing gets called every time the user presses a key!
                        This is needed, as you still need to create criteria for the entered user name. So if I enter "Alice", I don't want any "Bob"s to show up.
                        So in total you'll want an iContains or similar for a String value = ((ComboBoxItem) itemContext.getFormItem()).getEnteredValue()-criterion and the notInSet together in an AND-AdvancedCriteria, that you return from the getCriteria(FormItemFunctionContext itemContext)-override in your FormItemCriteriaFunction.

                        Best regards
                        Blama

                        Comment


                          #13
                          No, I know, just that it's not great to re-create the array for every key press. :) I do think the class works as intended, I tried with live char search and it seems to work.

                          Comment


                            #14
                            Ah, OK. Then it must be something automatic in the background that adds the entered chars as criteria.

                            Yes, building the array every time is not needed, but I assume will take very little time.
                            But if the optimization works, great.

                            Best regards
                            Blama

                            Comment


                              #15
                              Yeah, I think it's the live filtering of the records based on the key presses making up the narrowing of the filtering, and every press also calls my function for any "additional" filtering.
                              As you say, not sure how much processing time recreating an array of say 500 records on every key press matters, but better safe than sorry, that's what my grandmother used to say.

                              Comment

                              Working...
                              X