Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Manasi S 2 posts 72 karma points
    Jun 20, 2017 @ 17:32
    Manasi S
    0

    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 - enter image description here

    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?

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Jun 20, 2017 @ 18:03
    Nicholas Westby
    0

    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:

    Education Field

    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:

    /// <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.

  • Manasi S 2 posts 72 karma points
    Jul 10, 2017 @ 09:29
    Manasi S
    0

    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- enter image description here

    Request your help in implementing this functionality.

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Jul 10, 2017 @ 20:29
    Nicholas Westby
    0

    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):

    JSON Position

    Or, if this gets implemented as an actual validation, it'd go in the "Validations" block (along with some other data for the validation):

    JSON Validation Position

    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

Please Sign in or register to post replies

Write your reply to:

Draft