I have a custom date field that needs to be repeated multiple times.
The idea is to associate an image button to this field that will enable the repetition of the field. Once the field is repeated, the image button should change to enable deletion. Something like this -
How do I get the image button next to the text box? Also, how can I limit the number of times this field can be repeated?
I would recommend implementing this as a custom field which allows for you to do pretty much whatever you want. We have done similarly to allow users to add their education history:
Here you can see I've added two schools, and each has a button so it can be removed. There is also a button to add more schools.
You'll want to store the data as JSON, which would look funky in an email. To format it for an email, ensure your field does a bit of formatting:
/// <summary>
/// Formats the specified education list values for better presentation (e.g., in emails).
/// </summary>
/// <param name="values">
/// The submission values.
/// </param>
/// <param name="format">
/// The presentation format to use (e.g., for email).
/// </param>
/// <returns>
/// The formatted value.
/// </returns>
public string FormatValue(IEnumerable<string> values, FieldPresentationFormats format)
{
var formatted = values.Select(x => FormatSingleValue(x));
return string.Join(Environment.NewLine, formatted);
}
/// <summary>
/// Formats a single value in a submission.
/// </summary>
/// <param name="value">
/// The value to format.
/// </param>
/// <returns>
/// The formatted value.
/// </returns>
private string FormatSingleValue(string value)
{
// Validate input.
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
// Variables.
var twoLines = Environment.NewLine + Environment.NewLine;
var deserialized = JsonConvert.DeserializeObject(value) as dynamic;
var gradeType = deserialized?.highestGradeType?.Value as string;
var highestGrade = deserialized?.highestGrade?.Value as string;
var schools = (deserialized["schools"] as JArray)
.MakeSafe().Select(x => x as dynamic);
var formattedSchools = schools.Select(x => FormatSingleSchool(x));
var combinedSchools = string.Join(twoLines, formattedSchools).Trim();
// Choose header based on if there are any schools.
var schoolsHeader = string.IsNullOrWhiteSpace(combinedSchools)
? "No Schools Listed"
:
@"
Schools Attended
================".Trim().RemoveLeadingSpaces() + twoLines;
// Format and return education information.
var formatted =
$@"
Highest Grade Completed: {highestGrade} ({gradeType})
{schoolsHeader}{combinedSchools}
".Trim().RemoveLeadingSpaces() + twoLines;
return formatted;
}
Here is the Angular for the frontend of that education list field type:
'use strict';
var ngTmpls = require('ngTmpls');
var angular = require('angular');
var lodash = require('lodash');
var util = require('../util');
var MANDATORY_VALIDATION = util.MANDATORY_VALIDATION;
var app = angular.module('app');
function formatRadioOption(value) {
return {
label: value,
value: value
};
}
var RADIO_OPTIONS = {
schoolTypes: [
formatRadioOption('High School / G.E.D'),
formatRadioOption('Vocational / College'),
formatRadioOption('N/A')
],
yesNo: [
formatRadioOption('Yes'),
formatRadioOption('No')
],
grades: {
'High School / G.E.D': ['7', '8', '9', '10', '11', '12', 'G.E.D.'].map(formatRadioOption),
'Vocational / College': ['13', '14', '15', '16', '17+'].map(formatRadioOption)
}
};
var FIELD_VALIDATORS = {
highestGradeType: [MANDATORY_VALIDATION],
highestGrade: [MANDATORY_VALIDATION],
name: [MANDATORY_VALIDATION],
location: [MANDATORY_VALIDATION],
graduated: [MANDATORY_VALIDATION]
};
function EducationListFieldController($scope) {
var fieldId = $scope.fieldCtrl.id;
var fieldModels = $scope.ctrl.fieldModels;
// Init model
if (!fieldModels.hasOwnProperty(fieldId)) {
fieldModels[fieldId] = {
schools: [{id: util.idGenerator()}]
};
}
this.educationModel = fieldModels[fieldId];
// Constants
this.options = RADIO_OPTIONS;
this.validation = FIELD_VALIDATORS;
this.addSchool = function () {
// Reset Errors
$scope.formCtrl.$setPristine();
// Insert new school
this.educationModel.schools.push({id: util.idGenerator()});
};
this.removeSchool = function (school) {
// Reset Errors
$scope.formCtrl.$setPristine();
lodash.pull(this.educationModel.schools, school);
};
this.getGrades = function () {
var type = this.educationModel.highestGradeType;
if (RADIO_OPTIONS.grades.hasOwnProperty(type)) {
return RADIO_OPTIONS.grades[type];
}
};
this.unsetGrade = function () {
delete this.educationModel.highestGrade;
};
}
function educationListField() {
return {
restrict: 'E',
replace: true,
template: ngTmpls['education-list-field/education-list-field'],
controller: EducationListFieldController,
controllerAs: 'educationCtrl',
bindToController: true
};
}
app.directive('educationListField', educationListField);
Some of that code is specific to my project, but it should give you some general ideas for how you can accomplish your repeated date field type.
How do I get the image button next to the text box? Also, how can I limit the number of times this field can be repeated?
These are basically just CSS/Angular questions. There is nothing particular to Formulate that controls this.
Hi Nick, we have created repeatable custom fields that need to have validation for minimum and maximum occurrences. Do we have any standard validation for these in Formulate? If not, can we create user defined attributes and access the same in Angularjs? Something like this-
Request your help in implementing this functionality.
Hello again. First off, I want to clarify that the "minOccurs" and "maxOccurs" would not go where you have specified. They would go in the "FieldConfiguration" (a few lines above in your screenshot):
Or, if this gets implemented as an actual validation, it'd go in the "Validations" block (along with some other data for the validation):
It looks like it may be a bit of a pain to extend Formulate's built-in validations at the moment (we'll work on that to make it extensible), but after speaking with my coworker, Josef, he recommended an alternative.
Rather than validating, you can initialize your list of repeatable fields with the minimum number of items (and disable the delete buttons). Once an item is added, enable the delete buttons. Once the max has been reached, disable the add button. That would have the same effect as a min/max validation.
You'd also have to decorate your input fields with validations appropriate to them (e.g., the date field might have a "required" validation). You can read about that here: https://www.w3schools.com/angular/angular_validation.asp
Custom Repeatable Date Field
Hi Nick,
I have a custom date field that needs to be repeated multiple times. The idea is to associate an image button to this field that will enable the repetition of the field. Once the field is repeated, the image button should change to enable deletion. Something like this -
How do I get the image button next to the text box? Also, how can I limit the number of times this field can be repeated?
I would recommend implementing this as a custom field which allows for you to do pretty much whatever you want. We have done similarly to allow users to add their education history:
Here you can see I've added two schools, and each has a button so it can be removed. There is also a button to add more schools.
I would recommend starting here to build your custom field: http://www.formulate.rocks/articles/custom-field-types
You'll want to store the data as JSON, which would look funky in an email. To format it for an email, ensure your field does a bit of formatting:
Here is the Angular for the frontend of that education list field type:
Some of that code is specific to my project, but it should give you some general ideas for how you can accomplish your repeated date field type.
These are basically just CSS/Angular questions. There is nothing particular to Formulate that controls this.
Hi Nick, we have created repeatable custom fields that need to have validation for minimum and maximum occurrences. Do we have any standard validation for these in Formulate? If not, can we create user defined attributes and access the same in Angularjs? Something like this-
Request your help in implementing this functionality.
Hello again. First off, I want to clarify that the "minOccurs" and "maxOccurs" would not go where you have specified. They would go in the "FieldConfiguration" (a few lines above in your screenshot):
Or, if this gets implemented as an actual validation, it'd go in the "Validations" block (along with some other data for the validation):
It looks like it may be a bit of a pain to extend Formulate's built-in validations at the moment (we'll work on that to make it extensible), but after speaking with my coworker, Josef, he recommended an alternative.
Rather than validating, you can initialize your list of repeatable fields with the minimum number of items (and disable the delete buttons). Once an item is added, enable the delete buttons. Once the max has been reached, disable the add button. That would have the same effect as a min/max validation.
You'd also have to decorate your input fields with validations appropriate to them (e.g., the date field might have a "required" validation). You can read about that here: https://www.w3schools.com/angular/angular_validation.asp
If you want to modify the core of Formulate temporarily until we fix up the validations, here is where you'd add a new validation type in the frontend AngularJS code: https://github.com/rhythmagency/formulate/blob/f5a8e360aeb08b772c9dc20d8e717a3962b6e01e/src/formulate.app/JavaScript/FormTemplates/responsive.bootstrap.angular/directives/form/validation.js#L33
is working on a reply...