forked from janek/compareware
Implemented multi record handling
This commit is contained in:
parent
182f567304
commit
bc358f8835
|
@ -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
|
|
@ -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|
|
||||||
|
@ -13,11 +14,4 @@ instance View EditView where
|
||||||
breadcrumb = renderBreadcrumb
|
breadcrumb = renderBreadcrumb
|
||||||
[ breadcrumbLink "Tasks" TasksAction
|
[ breadcrumbLink "Tasks" TasksAction
|
||||||
, breadcrumbText "Edit Task"
|
, breadcrumbText "Edit Task"
|
||||||
]
|
]
|
||||||
|
|
||||||
renderForm :: Task -> Html
|
|
||||||
renderForm task = formFor task [hsx|
|
|
||||||
{(textField #description)}
|
|
||||||
{submitButton}
|
|
||||||
|
|
||||||
|]
|
|
|
@ -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 ", "
|
|
@ -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" } }
|
||||||
|]
|
|]
|
Loading…
Reference in New Issue