Implemented List component for entity. (#32)

This commit is contained in:
Matija Sosic 2019-06-09 13:04:07 +02:00 committed by GitHub
parent c9d52d6c20
commit 46ed578d36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 132 additions and 5 deletions

View File

@ -7,6 +7,7 @@ import * as {= entityLowerName =}State from '{= entityStatePath =}'
import * as {= entityLowerName =}Actions from '{= entityActionsPath =}' import * as {= entityLowerName =}Actions from '{= entityActionsPath =}'
import {= entity.name =} from '{= entityClassPath =}' import {= entity.name =} from '{= entityClassPath =}'
import {= entity.name =}CreateForm from '{= entityCreateFormPath =}' import {= entity.name =}CreateForm from '{= entityCreateFormPath =}'
import {= entity.name =}List from '{= entityListPath =}'
{=/ entities =} {=/ entities =}

View File

@ -0,0 +1,68 @@
{{={= =}=}}
import _ from 'lodash'
import React from 'react'
import { connect } from 'react-redux'
import Paper from '@material-ui/core/Paper'
import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
import TableCell from '@material-ui/core/TableCell'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'
import * as {= entityLowerName =}State from '../state'
import {= entityClassName =} from '../{= entityClassName =}'
export class List extends React.Component {
render() {
return (
<div style={ { margin: '20px' } }>
<Paper>
<Table>
<TableHead>
<TableRow>
{=# entityTypedFields =}
{=# boolean =}
<TableCell>{= name =} (bool)</TableCell>
{=/ boolean =}
{=# string =}
<TableCell>{= name =} (string)</TableCell>
{=/ string =}
{=/ entityTypedFields =}
</TableRow>
</TableHead>
<TableBody>
{this.props.{= entityLowerName =}List.map({= entityLowerName =} => (
<TableRow>
{=# entityTypedFields =}
{=# boolean =}
<TableCell>
{{= entityLowerName =}.{= name =} || 'no bool value'}
</TableCell>
{=/ boolean =}
{=# string =}
<TableCell>
{{= entityLowerName =}.{= name =} || 'no string value'}
</TableCell>
{=/ string =}
{=/ entityTypedFields =}
</TableRow>
))}
</TableBody>
</Table>
</Paper>
</div>
)
}
}
export default connect(state => ({
// Selectors
{= entityLowerName =}List: {= entityLowerName =}State.selectors.all(state)
}), {
// Actions
})(List)

View File

@ -6,6 +6,7 @@ module Generator.EntityGenerator
, entityStatePathInSrc , entityStatePathInSrc
, entityActionsPathInSrc , entityActionsPathInSrc
, entityCreateFormPathInSrc , entityCreateFormPathInSrc
, entityListPathInSrc
-- EXPORTED FOR TESTING: -- EXPORTED FOR TESTING:
, generateEntityClass , generateEntityClass
@ -17,6 +18,7 @@ module Generator.EntityGenerator
import Data.Aeson ((.=), object) import Data.Aeson ((.=), object)
import qualified Data.Aeson as Aeson import qualified Data.Aeson as Aeson
import qualified Data.Text as Text
import System.FilePath (FilePath, (</>), (<.>)) import System.FilePath (FilePath, (</>), (<.>))
import qualified Util import qualified Util
@ -56,6 +58,7 @@ generateEntityActions wasp entity
generateEntityComponents :: Wasp -> Entity -> [FileDraft] generateEntityComponents :: Wasp -> Entity -> [FileDraft]
generateEntityComponents wasp entity = generateEntityComponents wasp entity =
[ generateEntityCreateForm wasp entity [ generateEntityCreateForm wasp entity
, generateEntityList wasp entity
] ]
-- TODO: add tests / update tests. -- TODO: add tests / update tests.
@ -76,6 +79,13 @@ generateEntityCreateForm wasp entity
= createSimpleEntityFileDraft wasp entity (entityCreateFormPathInSrc entity) = createSimpleEntityFileDraft wasp entity (entityCreateFormPathInSrc entity)
("components" </> "CreateForm.js") ("components" </> "CreateForm.js")
-- TODO(matija): do I need wasp at all?
-- | Generates list component for the specified entity, so user can see all the
-- entity instances.
generateEntityList :: Wasp -> Entity -> FileDraft
generateEntityList wasp entity
= createSimpleEntityFileDraft wasp entity (entityListPathInSrc entity)
("components" </> "List.js")
-- | Helper function that captures common logic for generating entity file draft. -- | Helper function that captures common logic for generating entity file draft.
createSimpleEntityFileDraft :: Wasp -> Entity -> FilePath -> FilePath -> FileDraft createSimpleEntityFileDraft :: Wasp -> Entity -> FilePath -> FilePath -> FileDraft
@ -86,6 +96,22 @@ createSimpleEntityFileDraft wasp entity dstPathInSrc srcPathInEntityTemplatesDir
dstPath = "src" </> dstPathInSrc dstPath = "src" </> dstPathInSrc
templateData = entityTemplateData wasp entity templateData = entityTemplateData wasp entity
{- | Converts entity field to a JSON where field type is a key to the object holding
all the other properties. E.g. a field of type boolean could look this as JSON:
{ boolean: { name: "description", type: "boolean" }
This method is needed to achieve conditional rendering with Mustache.
-}
entityFieldToJsonWithTypeAsKey :: EntityField -> Aeson.Value
entityFieldToJsonWithTypeAsKey entityField = object
-- TODO(matija): maybe it would be cleaner to have a flat structure, like
-- { boolean: true, type: "boolean", name: "description" }
[ (toText $ entityFieldType entityField) .= entityField
]
where
toText = Text.pack . show
-- | Default generic data for entity templates. -- | Default generic data for entity templates.
entityTemplateData :: Wasp -> Entity -> Aeson.Value entityTemplateData :: Wasp -> Entity -> Aeson.Value
entityTemplateData wasp entity = object entityTemplateData wasp entity = object
@ -95,6 +121,7 @@ entityTemplateData wasp entity = object
-- TODO: this entityClassName is used only in CreateForm, use it also when creating -- TODO: this entityClassName is used only in CreateForm, use it also when creating
-- Class file itself and in other files. -- Class file itself and in other files.
, "entityClassName" .= (Util.toUpperFirst $ entityName entity) , "entityClassName" .= (Util.toUpperFirst $ entityName entity)
, "entityTypedFields" .= map entityFieldToJsonWithTypeAsKey (entityFields entity)
] ]
-- | Location in templates where entity related templates reside. -- | Location in templates where entity related templates reside.
@ -119,8 +146,13 @@ entityActionTypesPathInSrc entity = (entityDirPathInSrc entity) </> "actionTypes
entityClassPathInSrc :: Entity -> FilePath entityClassPathInSrc :: Entity -> FilePath
entityClassPathInSrc entity = (entityDirPathInSrc entity) </> (entityName entity) <.> "js" entityClassPathInSrc entity = (entityDirPathInSrc entity) </> (entityName entity) <.> "js"
-- * Components
entityComponentsDirPathInSrc :: Entity -> FilePath entityComponentsDirPathInSrc :: Entity -> FilePath
entityComponentsDirPathInSrc entity = (entityDirPathInSrc entity) </> "components" entityComponentsDirPathInSrc entity = (entityDirPathInSrc entity) </> "components"
entityCreateFormPathInSrc :: Entity -> FilePath entityCreateFormPathInSrc :: Entity -> FilePath
entityCreateFormPathInSrc entity = (entityComponentsDirPathInSrc entity) </> "CreateForm.js" entityCreateFormPathInSrc entity = (entityComponentsDirPathInSrc entity) </> "CreateForm.js"
entityListPathInSrc :: Entity -> FilePath
entityListPathInSrc entity = (entityComponentsDirPathInSrc entity) </> "List.js"

View File

@ -35,4 +35,5 @@ generatePage wasp page = createTemplateFileDraft dstPath srcPath templateData
, "entityActionsPath" .= ("./" ++ (EntityGenerator.entityActionsPathInSrc entity)) , "entityActionsPath" .= ("./" ++ (EntityGenerator.entityActionsPathInSrc entity))
, "entityClassPath" .= ("./" ++ (EntityGenerator.entityClassPathInSrc entity)) , "entityClassPath" .= ("./" ++ (EntityGenerator.entityClassPathInSrc entity))
, "entityCreateFormPath" .= ("./" ++ (EntityGenerator.entityCreateFormPathInSrc entity)) , "entityCreateFormPath" .= ("./" ++ (EntityGenerator.entityCreateFormPathInSrc entity))
, "entityListPath" .= ("./" ++ (EntityGenerator.entityListPathInSrc entity))
] ]

View File

@ -5,6 +5,7 @@ module Generator.Templates
) where ) where
import qualified Text.Mustache as Mustache import qualified Text.Mustache as Mustache
import Text.Mustache.Render (SubstitutionError(..))
import qualified Data.Aeson as Aeson import qualified Data.Aeson as Aeson
import System.FilePath ((</>)) import System.FilePath ((</>))
import Data.Text (Text) import Data.Text (Text)
@ -50,11 +51,23 @@ compileMustacheTemplate templateRelPath = do
raiseCompileError err = error $ -- TODO: Handle these errors better? raiseCompileError err = error $ -- TODO: Handle these errors better?
printf "Compilation of template %s failed. %s" templateRelPath (show err) printf "Compilation of template %s failed. %s" templateRelPath (show err)
areAllErrorsSectionDataNotFound :: [SubstitutionError] -> Bool
areAllErrorsSectionDataNotFound subsErrors = all isSectionDataNotFoundError subsErrors
where
isSectionDataNotFoundError e = case e of
SectionTargetNotFound _ -> True
_ -> False
renderMustacheTemplate :: Mustache.Template -> Aeson.Value -> IO Text renderMustacheTemplate :: Mustache.Template -> Aeson.Value -> IO Text
renderMustacheTemplate mustacheTemplate templateData = do renderMustacheTemplate mustacheTemplate templateData = do
let mustacheTemplateData = Mustache.toMustache templateData let mustacheTemplateData = Mustache.toMustache templateData
let (errors, fileText) = let (errors, fileText) =
Mustache.checkedSubstituteValue mustacheTemplate mustacheTemplateData Mustache.checkedSubstituteValue mustacheTemplate mustacheTemplateData
if (null errors) -- TODO: Handle these errors better.
-- NOTE(matija): Mustache reports errors when object does
-- not have a property specified in the template, which we use to implement
-- conditionals. This is why we ignore these errors.
if (null errors) || (areAllErrorsSectionDataNotFound errors)
then (return fileText) then (return fileText)
else (error $ "Errors occured while rendering template: " ++ (show errors)) else (error $ "Unexpected errors occured while rendering template: "
++ (show errors))

View File

@ -20,6 +20,8 @@ module Wasp
) where ) where
import Data.Aeson ((.=), object, ToJSON(..)) import Data.Aeson ((.=), object, ToJSON(..))
import qualified Data.Aeson as Aeson
import qualified Data.Text as T
-- * Wasp -- * Wasp
@ -88,7 +90,11 @@ data EntityField = EntityField
, entityFieldType :: !EntityFieldType , entityFieldType :: !EntityFieldType
} deriving (Show, Eq) } deriving (Show, Eq)
data EntityFieldType = EftString | EftBoolean deriving (Show, Eq) data EntityFieldType = EftString | EftBoolean deriving (Eq)
instance Show EntityFieldType where
show EftString = "string"
show EftBoolean = "boolean"
getEntities :: Wasp -> [Entity] getEntities :: Wasp -> [Entity]
getEntities (Wasp elems) = [entity | (WaspElementEntity entity) <- elems] getEntities (Wasp elems) = [entity | (WaspElementEntity entity) <- elems]
@ -96,6 +102,7 @@ getEntities (Wasp elems) = [entity | (WaspElementEntity entity) <- elems]
addEntity :: Wasp -> Entity -> Wasp addEntity :: Wasp -> Entity -> Wasp
addEntity (Wasp elems) entity = Wasp $ (WaspElementEntity entity):elems addEntity (Wasp elems) entity = Wasp $ (WaspElementEntity entity):elems
-- * ToJSON instances. -- * ToJSON instances.
-- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use -- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use
@ -128,8 +135,7 @@ instance ToJSON EntityField where
] ]
instance ToJSON EntityFieldType where instance ToJSON EntityFieldType where
toJSON EftString = "string" toJSON = Aeson.String . T.pack . show
toJSON EftBoolean = "boolean"
instance ToJSON Wasp where instance ToJSON Wasp where
toJSON wasp = object toJSON wasp = object

View File

@ -37,6 +37,9 @@ spec_parseWasp =
\ submitButtonLabel={'Create new task'}\n\ \ submitButtonLabel={'Create new task'}\n\
\ />\n\ \ />\n\
\ </div>\n\ \ </div>\n\
\\n\
\ My tasks\n\
\ <TaskList />\n\
\ </div>" \ </div>"
} }
, WaspElementPage $ Page , WaspElementPage $ Page

View File

@ -19,6 +19,9 @@ page Landing {
submitButtonLabel={'Create new task'} submitButtonLabel={'Create new task'}
/> />
</div> </div>
My tasks
<TaskList />
</div> </div>
jsx=} jsx=}
} }