Dependant drop down list


#1

Hey guys !

I am working on app that manages an office.
i want to create a from with autoform, that contains 2 drop down lists : names of clients and references of debts.
When the user selects a client the second list should only shows debts of the client selected.

can anyone help me with this ?
thank you.


#2

Try this, of course you’ll need to tweak here and there :smile:

'client': {
  label: 'Client',
  type: String,
  /* If client is transitional and you don't want to save it, then keep the
     optional and autovalue attributes, but if you want to also save the client
     information, then remove optional and autoValue from the schema
  */
  optional:true,
  autoValue: function() {
    this.unset();
  },
  autoform: {
    firstOption: 'Select a client',
    options: function() {
      return Clients.find({},{sort: {name: 1}).map(function(client){return {label: client.name, value: client._id};});
    }
  }
},
'debt': {
  label: 'Debt',
  type: String,
  allowedValues: function() {
    return Debts.find({}).map(function(debt){return debt._id;});
  },
  autoform: {
    firstOption: 'Select debt option',
    options: function() {
      var client = AutoForm.getFieldValue('client');
      return Debts.find({clientId: client},{sort: {name: 1}}).map(function(debt){return {label: debt.name, value: debt._id};});
    }
  }
}

#3

thank you so much, that helps me a lot.
I just have one more question, can i add another drop down list, i mean can i have 3 dependant drop down lists. And how does that work ?


#4

No problem, glad I could help.

You can use as many dropdowns as you want, you just follow the same pattern. You use the options property and AutoForm.getFieldValue('fieldYouWantToDependOn') which is reactive so an extended example would be:

'client': {
  label: 'Client',
  type: String,
  autoform: {
    firstOption: 'Select a client',
    options: function() {
      return Clients.find({},{sort: {name: 1}).map(function(client){return {label: client.name, value: client._id};});
    }
  }
},
'debt': {
  label: 'Debt',
  type: String,
  autoform: {
    firstOption: 'Select debt option',
    options: function() {
      var client = AutoForm.getFieldValue('client');
      return Debts.find({clientId: client},{sort: {name: 1}}).map(function(debt){return {label: debt.name, value: debt._id};});
    }
  }
},
'debtDetail': {
  label: 'Debt Detail',
  type: String,
  autoform: {
    firstOption: 'Select debt detail option',
    options: function() {
      var debt = AutoForm.getFieldValue('debt');
      return DebtDetails.find({debtId: debt},{sort: {name: 1}}).map(function(debtDetail){return {label: debtDetail.name, value: debtDetail._id};});
    }
  }
}

#5

ah okay
thank you :smile:


#6

Maybe a silly issue, or a problem with some other part of my app, but I seem to be running into problems trying to use this patter within another collection’s Schema. Was this ever the intention?

I am attempting to create an autoform for making new “Payouts” with several dependent drop down lists. Here is a dead-simple example trying to mimic your ‘Select a client’ options:

Payouts.attachSchema(new SimpleSchema({
...
service: {
    type: String,
    label: " ",
    autoform: {
      type: "select",
      firstOption: 'Choose a provider',
      options: function () {
        console.log(PayoutServices.find({}));
        return PayoutServices.find({}).map(function(service){return {label: service.name, value: service.name};});
      }
    }
  }
...
}});

Yet nothing appears in the form.


#7

What does the log do? does it print anything on the console?


#8

The log is just a sanity check while I am coding. Despite testing with autopublish on the Server it gives us the expected object, but not the Client, therefore not populating the list.


#9

I think I sorted some things out. For contextual sake, I am extending the materialize boilerplate app provided by differential.com.

After messing with the publish/subscribe setup I now know that the dropdown loads… eventually. I think the wait-on needs tweaking because I depends on 2 collections in this form.

  1. including a wait-on and data function in my router controller
PayoutsController = AppController.extend({
  waitOn: function() {
    return [this.subscribe('payoutservices'), this.subscribe('payoutoptions')];
  },
  data: function() {
    return {
      payoutservices: PayoutServices.find({}),
      payoutoptions: PayoutOptions.find({})
    }
  },
  onAfterAction: function () {
    Meta.setTitle('NewPayout');
  }
});
  1. Create a proper publish method for ‘payoutservices’
Meteor.publishComposite("payoutservices", function() {
  return {
    find: function() {
      return PayoutServices.find({});
    }
    ,
    children: [
      {
        find: function(item) {
          return PayoutServices.find({ _id : item._id });
        }
      }
    ]
  }
});

#10

Yep, that was why I asked if the log prints anything :slight_smile: Now you know that your problem was not having the data ready on the client.

By the way, if you are not too far ahead, switch from iron router to flow router and move your subscriptions to template level subs.


#11

Good call, looking at router things lead me to the final piece of the puzzle!!
In my route, I had never overridden the controller itself!
adding:

Router.route('/payouts/new', {
  ...
  controller: 'PayoutsNewController'
  ...
});

(herp derp :unamused:)

Thanks for the help, I really like how you’ve handled dependent drop downs. Now that it works, I am off to the races!


#12

Hi @serkandurusoy,

I am about to post this same topic but I happened to stumble upon to this thread and this is exactly what i am looking for :slight_smile:

Thanks for the pattern! :+1:


#13

Quick question:

this works:

'father': {
  label: 'Father',
  type: String,
  autoform: {
    firstOption: 'Select a Father',
    options: function() {
      return Family.find({},{sort: {name: 1}).map(function(father){return {label: father.name, value: father._id};});
    }
  }
},

but when i queried other fields with the same collection like this based on the father’s selection, there’s no option:

'children': {
  label: 'Children',
  type: String,
  autoform: {
    firstOption: 'Select Children',
    options: function() {
      var pap = AutoForm.getFieldValue('father');
      return Family.find({children: pap},{sort: {name: 1}}).map(function(children){return {label: children.name, value: children._id};});
      // notice it's from the same collection
    }
  }
},

There’s no children option.

Note: Children is not an array.


#14

There is an unresolved problem with autoform when it comes to reactive field values. Try this instead and see if it works:

'children': {
  label: 'Children',
  type: String,
  autoform: {
    firstOption: 'Select Children',
    options: function() {
      var formId = AutoForm.getFormId();
      var pap = AutoForm.getFieldValue('father', formId);
      return Family.find({children: pap},{sort: {name: 1}}).map(function(children){return {label: children.name, value: children._id};});
      // notice it's from the same collection
    }
  }
},

if it still does not work, make sure your query does actually return a resultset given the selected pap.


#15

Thanks @serkandurusoy,

I’ve created a change event whenever there’s a selection on the father and i can see the Id of the selected father on the console but no option on the children dropdown.

Note: Since this is not an array and I realized that I’m not making any sense if I put a drop down list if the data has only one option.

Here’s my schema on the children part:

'child1': {
  label: 'Child 1',
  type: String
},
'child2': {
  label: 'Child 2',
  type: String
},
'child3': {
  label: 'Child 3',
  type: String
},
max of 10 children
.....

I might change the context of the query, like map all the children in family collection instead of one child on drop down list.

You probably might be wondering why I separated all the children field, because I have an import CSV functionality and it’s a pain to import a file if you have a nested fields using simple schema, I have to flatten my schema in order to work the import.

To better understand my use case, the context is for insurance claims( claims collection), so when the father claims his insurance, he has to choose his dependent children which he enrolled in enrollments collection.

How do I map all the children on the above context?


#16

@ajaxsoap I’m sorry I really cannot put all this to context and having separate fields instead of an array also feels counterintuitive to me.

What I can do, though, is if you can provide a minimal reproduction of your case either on meteorpad or as a github repo, I can take a look.


#17

Fair enough.

Please, don’t apologize, the code that you share is more than enough.

I think I can handle the situation, playing around the pattern that you share. :smile:

Thanks again @serkandurusoy :+1:


#18

Look great.
But now I would like to change any addon options depend on other field value.
I to use datepicker addon on simple schema and autoform

calculateType: {
        type: String,
        autoform: {
            type: "select-radio-inline",
            options: function () {
               return [
                   {label: "2013", value: 2013},
                   {label: "2014", value: 2014},
              ];
            }
        }
},
fromDate: {
        type: Date,
        defaultValue: moment().toDate(),
        autoform: {
            afFieldInput: {
                type: 'bootstrap-datetimepicker',
                dateTimePickerOptions: {
                    format: 'DD/MM/YYYY'
                }
            }
        }
}

If calculateType change to 2013, date format to DD/MM/YYYY
If calculateType change to 2014, date format to DD/MM/YYYY HH:mm:ss
Please help me.


#19

I think you can use a helper like this:

if (!calculateType === '2014') {
  return { fromDate: moment().format(DD/MM/YYYY) }; 
} else 
  return { fromDate: moment().format(DD/MM/YYYY HH:mm:ss) };

this code is not tested but you can play around with it.

I’m not sure the correct syntax for moment format but you have the idea.


#20

Could I call Meteor Method instead of Collection.find()......?

var tmpInfo = new ReactiveVar();
-----------------------
'debt': {
  label: 'Debt',
  type: String,
  autoform: {
    firstOption: 'Select debt option',
    options: function() {
      var client = AutoForm.getFieldValue('client');
      Meteor.call('myMethod', {clientId: client}, function(err, res){
          if(!err){
               tmpInfo = res;
          }
      });
      
      return tmpInfo;
    }
  }
},

It alway runing (don’t stop).
Please help me.