Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to custom array field #202

Closed
pajooh opened this issue Jun 5, 2014 · 20 comments
Closed

how to custom array field #202

pajooh opened this issue Jun 5, 2014 · 20 comments

Comments

@pajooh
Copy link

pajooh commented Jun 5, 2014

i want to render an array of objects field, which it's properties need to be customized.
so i can't use afQuickField.
but i like afQuickField auto generated plus/minus buttons for arrays.
how can i do that?

@pajooh
Copy link
Author

pajooh commented Jun 6, 2014

found out that it's doable with custom templates :)

@pajooh pajooh closed this as completed Jun 6, 2014
@pajooh pajooh reopened this Jun 6, 2014
@pajooh
Copy link
Author

pajooh commented Jun 6, 2014

i know that i have to modify afArrayField template, but it uses afQuickField inside, and afQuickField has many helpers, uses other templates, etc.
seem's it's not as easy as i think
having an example of this should solve the issue !

@aldeed
Copy link
Collaborator

aldeed commented Jun 6, 2014

It can get tricky. Can you explain more which parts need customizing? I can provide an example if I know more specifics.

@pajooh
Copy link
Author

pajooh commented Jun 6, 2014

part of my schema is :

  series: {
    type: [Object]
  },
  "series.$.type": {
    type: String
  },
  "series.$.reference": {
    type: Number
  },

for the reference, i want to have options in form, i.e. i want to use

{{> afQuickField name='series' template="seriesTemplate"}}

and have series.$.reference to render with options (a select control)

@aldeed
Copy link
Collaborator

aldeed commented Jun 6, 2014

OK, if it's just adding options, you could actually do that by customizing the afQuickField template.

First copy and customize the afQuickField_bootstrap3 template that's included in the package.

<template name="afQuickField_addOptions">
  <div class="form-group {{#if afFieldIsInvalid name=this.atts.name autoform=this.atts.autoform}}has-error{{/if}}">
    {{#unless this.skipLabel}}
    {{> afFieldLabel this.afFieldLabelAtts}}
    {{/unless}}
    {{> afFieldInput attsPlusOptions}}
    <span class="help-block">{{{afFieldMessage name=this.atts.name autoform=this.atts.autoform}}}</span>
  </div>
</template>

I made only two small changes:

  1. Changed the template name to afQuickField_addOptions, where "addOptions" is whatever you want your custom template to be called.
  2. Changed {{> afFieldInput this.afFieldInputAtts}} to {{> afFieldInput attsPlusOptions}}

So now we can create an attsPlusOptions template helper to customize the attributes being passed to the input:

  Template["afQuickField_addOptions"].attsPlusOptions = function attsPlusOptions() {
    var self = this;
    var name = self.atts.name;
    var additionalAtts = {};

    // For certain field names, add options
    if (name === "series.$.reference") {
      additionalAtts.options = [
        {label: "One", value: 1}
      ];
    }

    return _.extend(additionalAtts, self.afFieldInputAtts);
  };

And finally we tell autoform to always use our custom template for afQuickFields instead of the built-in template:

AutoForm.setDefaultTemplateForType('afQuickField', 'addOptions');

@djhi
Copy link

djhi commented Jun 7, 2014

Is there a way to achieve this without overriding the default template for afQuickField ?

@aldeed
Copy link
Collaborator

aldeed commented Jun 7, 2014

Not currently, but something is in the works.

@djhi
Copy link

djhi commented Jun 7, 2014

Ok, nice to know :)

I tried to provide my own template for afArrayField, copying the one from the bootstrap template. But if I add template="myCustomeQuickFieldTemplate" on the {{>quickField}}, I got some errors about the name attribute being not valid.

Thx for your work, guys, this lib is awesome :)

@djhi
Copy link

djhi commented Jun 9, 2014

Here's my usecase:

I have a form for an order which uses the bootstrap3 template. An order has an array of order items and I'd like to use the afArrayfield but with a template which will not output labels and will put every inputs on a single line (like a table).

Can you advise me a way to achieve this ?
Thx !

@djhi
Copy link

djhi commented Jun 9, 2014

Finally found the way to do it !

I made my own afArrayField, afObjectField and afQuickField. This way, I was able to pass the template attribute correctly.

My afArrayField is a copy of the bootstrap3 template, removing all bs3 panels, but I call afObjectField instead of afQuickField, specify my template and the name attribute (name=this.name).

My afObjectField also removes bs panels and simply call afQuickField name=this.name template="myOwnTemplate"in the #each.

Finally I can fully customize the afQuickField

@pajooh
Copy link
Author

pajooh commented Jun 11, 2014

thank you @aldeed
name === "series.$.reference" is never picked, but name === "series.0.reference" works
it seems that $ is not interpreted here as array index!

@aldeed
Copy link
Collaborator

aldeed commented Jun 11, 2014

Oh, sorry, try doing this instead: var name = SimpleSchema._makeGeneric(self.atts.name);

@pajooh
Copy link
Author

pajooh commented Jun 11, 2014

it worked
thank you
keep up the good work

@aldeed aldeed closed this as completed Jun 13, 2014
@pajooh
Copy link
Author

pajooh commented Jun 24, 2014

@aldeed, updating to 0.13.0 made your code not to work !
i see the changelog, but i don't get where of template should be updated

@aldeed
Copy link
Collaborator

aldeed commented Jun 24, 2014

@pajooh, if your options are static, you can put them in the schema now if you want:

series: {
  type: [Object]
},
"series.$.type": {
  type: String
},
"series.$.reference": {
  type: Number,
  autoform: {
    options: [
      {label: "One", value: 1},
      // etc...
    ]
  }
},

If you do that, no custom template is needed.

I'm surprised that the custom template would have stopped working, though. I don't think any changes should be necessary. I'll have to try it out.

@aldeed aldeed reopened this Jun 24, 2014
@aldeed
Copy link
Collaborator

aldeed commented Jun 24, 2014

OK, extending options in a custom template does not and cannot work anymore. You'll have to put them in the schema or write out more of the form. In the latest release, you can also set autoform.options to a function that returns the options array. It's not reactive like a helper, though.

@aldeed aldeed closed this as completed Jun 24, 2014
@pajooh
Copy link
Author

pajooh commented Jun 25, 2014

may be, i have to open a new issue for this:
my case is something more complicated: options on some field (e.g. series.$.reference) relate on the value of other field (e.g. series.$.type).
can autoform help on this? as autoform.options (as a function) has not access to other fields of the schema (via this context or something), how can i do this?

@aldeed
Copy link
Collaborator

aldeed commented Jun 25, 2014

@pajooh, the AutoForm.getFieldValue method is reactive, so this would be technically possible, but you would have to use a reactive options helper (a function in the schema is not currently reactive) and you would need to pass the current index to the options helper, too, because you would need to specifically look up series.0.type for example instead of series.$.type.

Given the complexity, I think your best bet is to use a custom template for that particular object field.

The custom template:

<template name="afObjectField_series">
  <div class="panel panel-default">
    <div class="panel-heading">
      <h3 class="panel-title">{{afFieldLabelText name=this.atts.name}}</h3>
    </div>
    <div class="panel-body">
      {{#if afFieldIsInvalid name=this.atts.name autoform=this.autoform}}
      <span class="help-block">{{{afFieldMessage name=this.atts.name autoform=this.autoform}}}</span>
      {{/if}}
      {{#each afFieldNames name=this.atts.name fields=this.atts.fields omitFields=this.atts.omitFields}}
        {{#with getOptions ../atts.name this}}
        {{> afQuickField name=.. options=this}}
        {{/with}}
      {{/each}}
    </div>
  </div>
</template>

This is a copy of the built-in bootstrap template. I only changed the template name to "afObjectField_series" and changed the afQuickField to pass in options obtained from a getOptions helper.

Then you need to define the getOptions helper:

Template["afObjectField_series"].getOptions = function (itemFieldName, fieldName) {
  if (fieldName === 'reference') {
    var type = AutoForm.getFieldValue(formId, itemFieldName + '.type');
    return References.find({type: type}).map(function(doc){
      return {label: doc.name, value: doc.id};
    });
  }
};

The one change you will have to make is to replace formId with the actual string id for the autoform that's using this custom template.

Finally, in your schema, tell autoform to use your custom template for that object field:

series: {
  type: Array
},
"series.$": {
  type: Object,
  autoform: {
    template: "series"
  }
},
"series.$.type": {
  type: String
},
"series.$.reference": {
  type: Number
}

I did not test any of this, but hopefully it's close enough to help you figure it out.

@pajooh
Copy link
Author

pajooh commented Jun 28, 2014

thank you @aldeed , with some minor modifications it works:

use _makeGeneric for fieldName:

Template["afObjectField_series"].getOptions = function (itemFieldName, fieldName) {
  var name = SimpleSchema._makeGeneric(fieldName);
  if (name === 'series.$.reference') {
    var type = AutoForm.getFieldValue(formId, itemFieldName + '.type');
    return References.find({type: type}).map(function(doc){
      return {label: doc.name, value: doc.id};
    });
  }
};

add an else block for non-option fields:

<template name="afObjectField_series">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">{{afFieldLabelText name=this.atts.name}}</h3>
        </div>
        <div class="panel-body">
            {{#if afFieldIsInvalid name=this.atts.name autoform=this.autoform}}
            <span class="help-block">{{{afFieldMessage name=this.atts.name autoform=this.autoform}}}</span>
            {{/if}}
            {{#each afFieldNames name=this.atts.name fields=this.atts.fields omitFields=this.atts.omitFields}}
            {{#with getOptions ../atts.name this}}
            {{> afQuickField name=.. options=this}}
            {{else}}
            {{> afQuickField name=this options="auto" fields=../atts.fields omitFields=../atts.omitFields}}
            {{/with}}
            {{/each}}
        </div>
    </div>
</template>

as a general thought, having object and array components available also as a block helper, will provide the user with proper template (minus/plus buttons, etc) while the user has ability to modify array/object fields more elegantly

@leizard
Copy link

leizard commented Jul 8, 2016

I think customizing on array field is a important feature. Love to had it officially documented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants