Hello, gophers!
On one of the projects, I had to create some dynamic inputs which are grouped into an array. Here is my Form example:
<form action="" method="POST">
<div class="form-row row mt-3">
<div class="col">
<label><strong>Storage configuration</strong></label>
<button type="button" class="btn btn-inverse-success storage-add"><i class="mdi mdi-plus"></i> Add</button>
</div>
</div>
<div id="clone-keeper">
<div class="form-row row mt-3 clone-container">
<div class="col-2">
<label class="control-label" for="storage_size_0"><strong>Size</strong></label>
<input class="form-control" name="storage[0][size]" id="storage_size_0" placeholder="Enter Disk Size" required />
</div>
<div class="col-3">
<label class="control-label" for="storage_profile_0"><strong>Profile</strong></label>
<select class="form-control storage-profile" name="storage[0][profile]" id="storage_profile" required />
<option value="">Select Storage Profile</option>
<option value="SSD">SSD</option>
<option value="HDD">HDD</option>
</select>
</div>
<div class="col">
<label class="control-label" for="storage_mount_0"><strong>Mount</strong></label>
<input class="form-control" name="storage[0][mount]" id="storage_mount_0" placeholder="Enter Mount point on target instance" required />
</div>
<div class="col-1">
<label class="control-label d-block" for="storage_remove_0"><strong> </strong></label>
<button type="button" class="w-100 storage-remove btn btn-inverse-danger" name="storage_remove" id="storage_remove_0" title="Delete this block">
<i class="mdi mdi-delete-forever"></i>
</button>
</div>
</div>
</div>
</form>
The script I’m using for cloning you can find here: github.com/rajneeshgautam/jquery-dynamicform . I had to modify and cleanup this script to meet my needs, but main logic is the same.
Form items cloner initialization code:
$('button.storage-add').cloneData({
mainContainerId: 'clone-keeper', // Main container Should be ID
cloneContainer: 'clone-container', // Which you want to clone
removeButtonClass: 'storage-remove', // Remove button for remove cloned HTML
removeConfirm: true, // default true confirm before delete clone item
removeConfirmMessage: 'Are you sure want to delete?', // confirm delete message
minLimit: 1, // Default 1 set minimum clone HTML required
maxLimit: 5, // Default unlimited or set maximum limit of clone HTML
defaultRender: 1, // Default 1 render clone HTML
});
Now, golang’s ParseForm method do not parse such fields into array, it returns such names as a string.
So, I wrote parsing methods for such inputs:
// ParseFormCollection is a helper method to parse an array of Form items.
// Eg, if Form field name like `inputfield[0][name1]`...
func ParseFormCollection(r *http.Request, typeName string) (result []map[string]string) {
r.ParseForm()
for key, values := range r.Form {
re := regexp.MustCompile(typeName + "\\[([0-9]+)\\]\\[([a-zA-Z]+)\\]")
matches := re.FindStringSubmatch(key)
if len(matches) >= 3 {
index, _ := strconv.Atoi(matches[1])
for index >= len(result) {
result = append(result, map[string]string{})
}
result[index][matches[2]] = values[0]
}
}
return
}
Usage is simple:
storages := ParseFormCollection(req, "storage")
With 3 cloned fields blocks we’ll get following collection of maps:
[map[mount:/ profile:SSD size:32] map[mount:/var profile:SSD size:100] map[mount:/usr/lib profile:HDD size:200]]
This was dumped with fmt.Printf("%+v", storages)
.
That is it.
As a Bonus, here is a code snippet to parse input maps with names like host[prefix]
, host[domain]
, etc.
// ParseFormCollection is a helper method to parse Form "groupped" items with square bracketing.
// Eg, if Form field name like `inputfield[name1]`...
func ParseFormMap(r *http.Request, typeName string) (result map[string]string) {
result = make(map[string]string)
r.ParseForm()
for key, values := range r.Form {
re := regexp.MustCompile(typeName + "\\[([a-zA-Z]+)\\]")
matches := re.FindStringSubmatch(key)
fmt.Printf("%#v\n", matches)
if len(matches) >= 2 {
result[matches[1]] = values[0]
}
}
return
}
Happy golang
ing!