Proposal: SelectTag
Closed this issue · 1 comments
MrMage commented
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>
"""))
}
}
steffendsommer commented