1
0
Fork 0

Implemented multi record handling

This commit is contained in:
Marc Scholten 2023-04-16 15:54:20 +02:00
parent 182f567304
commit bc358f8835
4 changed files with 115 additions and 24 deletions

View File

@ -9,10 +9,13 @@ import Web.View.Tasks.Show
instance Controller TasksController where instance Controller TasksController where
action TasksAction = do action TasksAction = do
tasks <- query @Task |> fetch tasks <- query @Task |> fetch
>>= collectionFetchRelated #tags
render IndexView { .. } render IndexView { .. }
action NewTaskAction = do action NewTaskAction = do
let task = newRecord let tags = [newRecord @Tag, newRecord @Tag]
let task = newRecord @Task
|> updateField @"tags" tags
render NewView { .. } render NewView { .. }
action ShowTaskAction { taskId } = do action ShowTaskAction { taskId } = do
@ -21,28 +24,62 @@ instance Controller TasksController where
action EditTaskAction { taskId } = do action EditTaskAction { taskId } = do
task <- fetch taskId task <- fetch taskId
>>= fetchRelated #tags
render EditView { .. } render EditView { .. }
action UpdateTaskAction { taskId } = do action UpdateTaskAction { taskId } = do
task <- fetch taskId task <- fetch taskId
let tagIds :: [Id Tag] = paramList "tags_id"
let tagNames :: [Text] = paramList "tags_name"
originalTags <- fetch tagIds
let tags = zip tagIds tagNames
|> map (\(id, name) -> originalTags
|> find (\tag -> tag.id == id)
|> fromMaybe (newRecord |> set #taskId task.id)
|> \tag -> buildTag tag name
)
task task
|> buildTask |> buildTask
|> updateField @"tags" tags
|> bubbleValidationResult #tags
|> ifValid \case |> ifValid \case
Left task -> render EditView { .. } Left task -> do
render EditView { task }
Right task -> do Right task -> do
task <- task |> updateRecord (task, tags) <- withTransaction do
task <- task
|> clearTags
|> updateRecord
tags <- mapM updateOrCreateRecord tags
pure (task, tags)
setSuccessMessage "Task updated" setSuccessMessage "Task updated"
redirectTo EditTaskAction { .. } redirectTo EditTaskAction { .. }
action CreateTaskAction = do action CreateTaskAction = do
let task = newRecord @Task let task = newRecord @Task
let names :: [Text] = paramList "tags_name"
let tags = names |> map (buildTag newRecord)
task task
|> buildTask |> buildTask
|> updateField @"tags" tags
|> bubbleValidationResult #tags
|> ifValid \case |> ifValid \case
Left task -> render NewView { .. } Left task -> render NewView { task }
Right task -> do Right taskAndTags -> do
task <- task |> createRecord (task, tags) <- withTransaction do
setSuccessMessage "Task created" task <- taskAndTags |> clearTags |> createRecord
tags <- taskAndTags.tags
|> map (set #taskId task.id)
|> createMany
pure (task, tags)
setSuccessMessage "Task and Tags created"
redirectTo TasksAction redirectTo TasksAction
action DeleteTaskAction { taskId } = do action DeleteTaskAction { taskId } = do
@ -53,3 +90,34 @@ instance Controller TasksController where
buildTask task = task buildTask task = task
|> fill @'["description"] |> fill @'["description"]
|> validateField #description nonEmpty
buildTag :: Tag -> Text -> Tag
buildTag tag name = tag
|> set #name name
|> validateField #name nonEmpty
-- | Adds a validation error to the record when any of the child records is invalid
bubbleValidationResult :: forall fieldName record childRecord.
( HasField "meta" record MetaBag
, SetField "meta" record MetaBag
, KnownSymbol fieldName
, HasField fieldName record [childRecord]
, HasField "meta" childRecord MetaBag
, SetField "meta" childRecord MetaBag
) => Proxy fieldName -> record -> record
bubbleValidationResult field record =
if isEmpty childAnnotations
then record
else record |> attachFailure field "Invalid records"
where
childAnnotations :: [(Text, Violation)]
childAnnotations = get field record
|> map (\record -> record.meta.annotations)
|> concat
updateOrCreateRecord record | isNew record = createRecord record
updateOrCreateRecord record = updateRecord record
clearTags :: Include "tags" Task -> Task
clearTags task = updateField @"tags" (newRecord @Task).tags task

View File

@ -1,7 +1,8 @@
module Web.View.Tasks.Edit where module Web.View.Tasks.Edit where
import Web.View.Prelude import Web.View.Prelude
import Web.View.Tasks.New (renderForm)
data EditView = EditView { task :: Task } data EditView = EditView { task :: Include "tags" Task }
instance View EditView where instance View EditView where
html EditView { .. } = [hsx| html EditView { .. } = [hsx|
@ -14,10 +15,3 @@ instance View EditView where
[ breadcrumbLink "Tasks" TasksAction [ breadcrumbLink "Tasks" TasksAction
, breadcrumbText "Edit Task" , breadcrumbText "Edit Task"
] ]
renderForm :: Task -> Html
renderForm task = formFor task [hsx|
{(textField #description)}
{submitButton}
|]

View File

@ -1,7 +1,7 @@
module Web.View.Tasks.Index where module Web.View.Tasks.Index where
import Web.View.Prelude import Web.View.Prelude
data IndexView = IndexView { tasks :: [Task] } data IndexView = IndexView { tasks :: [Include "tags" Task] }
instance View IndexView where instance View IndexView where
html IndexView { .. } = [hsx| html IndexView { .. } = [hsx|
@ -26,12 +26,19 @@ instance View IndexView where
[ breadcrumbLink "Tasks" TasksAction [ breadcrumbLink "Tasks" TasksAction
] ]
renderTask :: Task -> Html renderTask :: Include "tags" Task -> Html
renderTask task = [hsx| renderTask task = [hsx|
<tr> <tr>
<td>{task.description}</td> <td>{task.description}</td>
<td>{renderTags task.tags}</td>
<td><a href={ShowTaskAction task.id}>Show</a></td> <td><a href={ShowTaskAction task.id}>Show</a></td>
<td><a href={EditTaskAction task.id} class="text-muted">Edit</a></td> <td><a href={EditTaskAction task.id} class="text-muted">Edit</a></td>
<td><a href={DeleteTaskAction task.id} class="js-delete text-muted">Delete</a></td> <td><a href={DeleteTaskAction task.id} class="js-delete text-muted">Delete</a></td>
</tr> </tr>
|] |]
renderTags :: [Tag] -> Text
renderTags tags =
tags
|> map (.name)
|> intercalate ", "

View File

@ -1,12 +1,14 @@
module Web.View.Tasks.New where module Web.View.Tasks.New where
import Web.View.Prelude import Web.View.Prelude
import Text.Blaze.Html.Renderer.Text
data NewView = NewView { task :: Task } data NewView = NewView { task :: Include "tags" Task }
instance View NewView where instance View NewView where
html NewView { .. } = [hsx| html NewView { .. } = [hsx|
{breadcrumb} {breadcrumb}
<h1>New Task</h1> <h1>New Task</h1>
{renderForm task} {renderForm task}
|] |]
where where
@ -14,10 +16,30 @@ instance View NewView where
[ breadcrumbLink "Tasks" TasksAction [ breadcrumbLink "Tasks" TasksAction
, breadcrumbText "New Task" , breadcrumbText "New Task"
] ]
renderForm :: Include "tags" Task -> Html
renderForm :: Task -> Html
renderForm task = formFor task [hsx| renderForm task = formFor task [hsx|
{(textField #description)} {textField #description}
{submitButton}
<fieldset>
<legend>Tags</legend>
{nestedFormFor #tags renderTagForm}
</fieldset>
<button type="button" class="btn btn-light" data-prototype={prototypeFor #tags (newRecord @Tag)} onclick="this.insertAdjacentHTML('beforebegin', this.dataset.prototype)">Add Tag</button>
{submitButton}
|]
prototypeFor :: _ => _ -> _ -> Text
prototypeFor field record =
cs $ renderHtml prototype
where
parentFormContext = ?formContext
prototype :: Html
prototype = let ?formContext = parentFormContext { model = parentFormContext.model |> set field [newRecord] } in nestedFormFor field renderTagForm
renderTagForm :: (?formContext :: FormContext Tag) => Html
renderTagForm = [hsx|
{(textField #name) { disableLabel = True, placeholder = "Tag name" } }
|] |]