ml-archive/submissions

Proposal: SelectTag

Closed this issue · 1 comments

I have created a tag renderer to create select boxes. The syntax is:

#form:select(fieldName (String), availableOptions ([String]), extraAttributes (String))

If the user includes multiple in extraAttributes, they can even select multiple values.

I'm happy to file a PR to add this to the project, but wanted to inquire first whether this functionality is actually desired here.

Here's the full tag renderer:

import Leaf
import TemplateKit

public final class SelectTag: TagRenderer {
	public func render(tag: TagContext) throws -> Future<TemplateData> {
		try tag.requireNoBody()
		
		let data = try tag.submissionsData()
		
		guard let availableOptions = (tag.parameters[safe: 1]?.array?.compactMap { $0.string }),
			!availableOptions.isEmpty
			else { throw tag.error(reason: "no values provided") }
		let extraAttributes = tag.parameters[safe: 2]?.string ?? ""
		
		var selectedOptions = Set<String>()
		if let fieldValue = data.value {
			// Multiple selected options are encoded to string in the form of `["optionA", "optionB"]`.
			// To extract the original values, we try decoding the string as a JSON string array first.
			// (The ideal solution would be to change `Field.value`'s type to `TemplateData`, but that would require
			// more effort.
			if let decodedArray = try? JSONDecoder().decode([String].self, from: fieldValue.convertToData()) {
				selectedOptions = Set(decodedArray)
			} else {
				selectedOptions.insert(fieldValue)
			}
		}
		
		let optionTags = availableOptions.map {
			"<option value=\"\($0)\"\(selectedOptions.contains($0) ? " selected" : "")>\($0)</option>"
		}
		
		var labelTag = ""
		if let label = data.label {
			labelTag = "<label class=\"control-label\" for=\"\(data.key)\">\(label)</label>"
		}
		
		return tag.future(.string("""
			<div class="form-group">
			\(labelTag)
			<select class="form-control" id="\(data.key)" name="\(data.key)" \(extraAttributes)>
			\(optionTags.joined(separator: "\n"))
			</select>
			</div>
			"""))
	}
}

@MrMage Thanks for proposing this. I took parts of your proposal and created a PR here #46 - please have a look and feel free to provide feedback