Filter by ID

I have a custom filter that I would expect to be able to use with an array of ids to open a selection-list drawer with only the accounts in the array.

<?php
  $viewdefs['Accounts']['base']['filter']['basic']['filters'][] = array(
  'id' => 'FilterAccountsIdTemplate',
  'name' => 'LBL_FILTER_ACCOUNTS_BY_ID_TEMPLATE',
  'filter_definition' => array(
    array(
      'id' => array(
        '$in'=>'',
      ),
    ),
  ),
  'editable' => true,
  'is_template' => true,
);
?>

The ids are returned by an API call which gets the Account IDs related to a Contact (we have am M-N relationship between Accounts and Contacts).
The intent is, if there is only one account related to the contact, populate the Account relate field on this module, else, present the user with a selection-list drawer with only Accounts related to the selected Contact

({  extendsFrom: 'CreateActionsView',
  initialize: function(options){
    this._super('initialize', [options]);
    this.model.on('change:case_primary_contact_c', this.updateAccount, this);
  },
  updateAccount: function() {   
    console.log('updateAccount');
    if(!_.isEmpty(this.model.get('contact_id_c'))){
      var contact_id = this.model.get('contact_id_c'),
          url = app.api.buildURL('Contacts/'+contact_id+'/link/accounts_contacts/');
      app.api.call('GET', url, null, {
        success: _.bind( function (data){
          console.log(data);
          if(data.records.length == 1){
            this.model.set('account_id', data.records[0].id);
            this.model.set('account_name', data.records[0].name);
          }else if(data.records.length >1){
           console.log('more than 1');
            var account_ids = [];
            _.each(data.records, function (record){
              account_ids.push(record.id); //generate array of Account ids
            });
            console.log(account_ids);
            var filterOptions = new app.utils.FilterOptions()
              .config({
                'initial_filter': 'FilterAccountsByIdTemplate',
                'initial_filter_label': 'LBL_FILTER_ACCOUNTS_BY_ID_TEMPLATE',
                'filter_populate': {
                 'id':account_ids,
                }
              }).format();
            console.log(filterOptions);
            app.drawer.open({
              layout: 'selection-list',
              context: {
                module: 'Accounts',
                filterOptions: filterOptions,
              }
            });
          }
        }, this),
        error:_.bind(function(o){
          console.log("Error retrieving Account related to Contact" + o);
        }, this),
      });
    }
  },

But, although the filter shows in the selection-list, the criteria is not applied (it shows all accounts as if unfiltered)

I suspect the problem is filtering by ID, which is not normally a filter field.

Any suggestions on how to get around this?
FrancescaS
Parents
  • Hi FrancescaS,

    Yes I too have faced this problem.Then I used  'name' instead of id and it started working for me.Also I believe the filter definition should be like this.
    'filter_definition' => array(
        array(
          'name' => array(
            '$in'=> array(),
          ),
        ),
      ),

    You can use $in => array() instead of an empty string as we are going to pass an array of account names.

    Hope it helps.
    Thanks!
  • If we are passing id which field is being selected as the filter in the UI?
  • Hey one question, accounts_contacts standard relationship is one to many from accounts to contacts no?
    I got confused when you said one contact can have many accounts! Did you change the standard relationship?
  • Hi FrancescaS,

    It is currently not possible to filter by ID. 
    ID is not a field type that is supported by the client: see the list of field types and their supported operators: clients/base/filters/operators/operators.php 

    You can add support yourself by doing some work: 
    - You add the “id” field type, and the operators you’d like -> easy
    - You add the "id" field to the list of filterable fields for the Accounts module (modules/Accounts/clients/base/filters/basic/basic.php)  -> easy
    - You extend clients/base/views/filter-rows/filter-rows.js #handleOperatorSelected to add logic specific to your field type. I believe you should pay attention to the “relate” field type that works somewhat similar (“relate" filters by id but also fetches the names and instantiates a relate field to display nice in the UI), even though as of the current release it only supports filtering by a single record… :( Note that this part will break on upgrades) -> painful

    Another strategy would be to open a selection-list drawer that fetches only the contact's related accounts without applying a filter. 
    You can get that with: 
    app.drawer.open({ layout: 'selection-list', context: { module: 'Contacts', parentModelId: ‘<the_contact_id>', link: 'accounts' } });
    Notes: 
    - I think he selection-list does not support a related context very well. You will have to extend it a little in order to get the Accounts metadata instead of the Contacts metadata (for the columns to be displayed in the list view). 
    - You will be restricted to selecting an account from that list.  

    I think I would go with option 2. 

    Hope this helps,
    Julien.
Reply
  • Hi FrancescaS,

    It is currently not possible to filter by ID. 
    ID is not a field type that is supported by the client: see the list of field types and their supported operators: clients/base/filters/operators/operators.php 

    You can add support yourself by doing some work: 
    - You add the “id” field type, and the operators you’d like -> easy
    - You add the "id" field to the list of filterable fields for the Accounts module (modules/Accounts/clients/base/filters/basic/basic.php)  -> easy
    - You extend clients/base/views/filter-rows/filter-rows.js #handleOperatorSelected to add logic specific to your field type. I believe you should pay attention to the “relate” field type that works somewhat similar (“relate" filters by id but also fetches the names and instantiates a relate field to display nice in the UI), even though as of the current release it only supports filtering by a single record… :( Note that this part will break on upgrades) -> painful

    Another strategy would be to open a selection-list drawer that fetches only the contact's related accounts without applying a filter. 
    You can get that with: 
    app.drawer.open({ layout: 'selection-list', context: { module: 'Contacts', parentModelId: ‘<the_contact_id>', link: 'accounts' } });
    Notes: 
    - I think he selection-list does not support a related context very well. You will have to extend it a little in order to get the Accounts metadata instead of the Contacts metadata (for the columns to be displayed in the list view). 
    - You will be restricted to selecting an account from that list.  

    I think I would go with option 2. 

    Hope this helps,
    Julien.
Children
  • Merci Julien,
    I think the bigger difficulty is that the relationship Contacts<->Accounts was changed to N-M and right now I'm getting an error in the log:

    Warning: Multiple links found for relationship accounts_contacts within module Contacts

    I will try option 2.
  • Hey, just check the file data/relationships/M2MRelationship.php.In line number 59 you can see this particular warning.Sugar has the method getMostAppropriateLinkedDefinition() to get the appropriate link entry in the case of multiple links for the same relationship.

    I couldnt find the reason why this has happened.How did you modify the standard relationship.Which all files you have modified?
  • I would rather go with a new custom relationship to avoid this kind of mess...:)
  • Julien,

    I couldn't get option 2 working but the idea of opening the select-list without filtering got me thinking:
       since I have the Accounts I want to display I could create a Collection of Accounts from the results of the API and pass it to the selection-list as part of the context.

    But it turns out it's just taunting me, it loads the correct Accounts in the drawer (the ones related to the Contact) and then just runs the default filter and refreshes the list with the results of that filter, giving me just a glimpse of what I'm wanting to achieve...

    How can I stop the filter from running?

    So in Cases record.js

    <...the usual extends/initialize...>

         this.model.on('change:case_primary_contact_c', this.updateAccount, this);  },
      updateAccount: function() {
        console.log('updateAccount');
        if(!_.isEmpty(this.model.get('contact_id_c'))){
          var contact_id = this.model.get('contact_id_c'),
              url = app.api.buildURL('Contacts/'+contact_id+'/link/accounts_contacts/');
          app.api.call('GET', url, null, {
            success: _.bind( function (data){
              if(data.records.length == 1){
                //only one account found, use it.
                this.model.set('account_id', data.records[0].id);
                this.model.set('account_name', data.records[0].name);
              }else if(data.records.length > 1){
                //multiple accounts found, open a select-list with those accounts
                accounts = app.data.createBeanCollection("Accounts",data.records);
                app.drawer.open({
                  layout: 'selection-list',
                  context:
                    {
                              module: 'Accounts',
                              parentModel: 'Cases',
                              parentModelID: this.model.get('id'),
                              collection: accounts,
                    }
                });
              }
            }, this),
            error:_.bind(function(o){
              console.log("Error retrieving Account related to Contact" + o);
            }, this),
          });
        }
      },


    I suspect I need to find a way to reset the currentFilterId: "all_records"
    ...this will need more work on a fresh mind...
     
    thanks,
    FrancescaS


  • I don't know if you find a way to do your filter but maybe you can add on your view a validationTask to check if the account is linked to the contact and block the save process if not. Of course it's not a nice capability like a filtered drawer.
  • Hi Francesca,

    This approach of passing the filtered collection to the context key of the drawer object I have tried before sugar has come up with the Initial filter in 7.x.
    I was facing the same problem which you are facing currently.
    When the selection list open sugar calls a defualt api and once the system receive the api response , those records will get passed to the context.

    I was restricting this by overwriding the render method. Along with passing the collection in context I passed one more parmeter like 'filteredList'  as true, and then I extended the selection-list view and overwridden the render method something like this.

    render:function(){
     if(this.context.get(filteredList)){
              //don't call api
     }else{
              this._super('render');
     }
    }

    This is not a correct solution as it will prevernt the loading of complete UI also (the top header link and create buttons of the selection-list view will not load).

    I would be happy if you can figurout something from this!

    Thanks!

  • UPDATE 2017 NOTE: This no longer works in 7.7.x

    Looking for alternative solution.

    FrancescaS

    ------------------------------------------------

    So it's not perfect yet but this works.

    NB. If you are just coming across this I have a custom N-M Account-Contact relationship defined in my instance of SugarCRM.

    The clue to opening the select-list with a preset collection was in: jssource/src_files/clients/base/layouts/filter/filter.js, specifically the applyFilter function:

    applyFilter: function(query, dynamicFilterDef) {
           var filterOptions = this.context.get('filterOptions') || {};        if (filterOptions.auto_apply === false) {
                return;
            }
    <etc....>


    By setting the filterOptions with auto_apply:false I stop the load of the default filter when opening the selection-list.

    I still have a minor problem, the "Loading..." message message at the bottom of the list never goes away, even after the collection has been loaded.


    So in custom/modules/Cases/clients/base/views/record/record.js

    ({  extendsFrom: 'RecordView',
      initialize: function(options){
        this._super('initialize', [options]);
        <other events here>
        this.model.on('change:case_primary_contact_c', this.updateAccount, this); 
      },
     updateAccount: function() {          if(!_.isEmpty(this.model.get('contact_id_c')) && !_.isEqual(this.model.get('contact_id_c'),this.model.previous('contact_id_c'))){
          var contact_id = this.model.get('contact_id_c'),
              url = app.api.buildURL('Contacts/'+contact_id+'/link/accounts_contacts/'),
              self = this;
          app.api.call('GET', url, null, {
            success: _.bind( function (data){
              if(data.records.length == 1){
                //there is only one account related to the selected contact
                //set the account name and id in the Account Relate field.
                self.model.set('account_id', data.records[0].id);
                self.model.set('account_name', data.records[0].name);
              }else if(data.records.length > 1){
               //there are multiple Accounts related to this Contact
               //create a Collection with these accounts and open a selection list with this
               //collection to choose from
                var parentModel = self.model,
                    linkModule = 'Accounts',
                    accounts = app.data.createBeanCollection( "Accounts", data.records);
                app.drawer.open({
                  layout: 'selection-list',
                  context: { module: linkModule,
                              parentModel: parentModel,
                              collection:accounts,
                              filterOptions:{
                                auto_apply:false,
                              },
                  }
                }, function(selectedModel) {
                     if (!_.isEmpty(selectedModel)) {
                       self.model.set('account_id', selectedModel.id);
                       self.model.set('account_name', selectedModel.name);
                     }
                });
              }
            }, this),
            error:_.bind(function(o){
              console.log("Error retrieving Account related to Contact" + o);
            }, this),
          });
        }
      },
    <snip>



    Any critique is welcome.

    FrancescaS



  • Great !!
    But After loading the collection, if the user want to search for a particular record from this filtered collection?
    When I checked your example, if we are passing auto_apply as false, the system is not filtering if do manually set filter in the selection-list.
     
    In case if there are 30 plus account associated with the contact.!
  • Good catch, not a likely scenario for us, but, more work needed :)
  • Hi FrancescaS

    I've modified the view list-bottom to remove the message "Loading..." when you set to the context filterOptions.auto_apply=false.

    https://gist.github.com/betobaz/b8d1c36338691eecfa00#file-custom__clients__base__views__list-bottom__list-bottom-js-L101-L104

    Hope this helps,

    Betobaz