diff --git a/waspc/data/Generator/templates/react-app/src/components/_Button.js b/waspc/data/Generator/templates/react-app/src/components/_Button.js
new file mode 100644
index 000000000..d28430e19
--- /dev/null
+++ b/waspc/data/Generator/templates/react-app/src/components/_Button.js
@@ -0,0 +1,39 @@
+{{={= =}=}}
+import React from 'react'
+import { connect } from 'react-redux'
+import Button from '@material-ui/core/Button'
+
+{=# onClickAction =}
+import { {= exportedIdentifier =} } from '{= importPath =}'
+{=/ onClickAction =}
+
+export class {= button.name =} extends React.Component {
+ // TODO: Add propTypes.
+
+ {=# onClickAction =}
+ onClick = () => {
+ this.props.{= exportedIdentifier =}()
+ }
+ {=/ onClickAction =}
+
+ render() {
+ return (
+
+ )
+ }
+}
+
+export default connect(state => ({
+ // Selectors
+}), {
+ // Actions
+ {=# onClickAction =}
+ {= exportedIdentifier =}
+ {=/ onClickAction =}
+})({= button.name =})
diff --git a/waspc/data/Generator/templates/react-app/src/entities/_entity/_Entity.js b/waspc/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
index ee06dd3d9..852dce035 100644
--- a/waspc/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
+++ b/waspc/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
@@ -1,7 +1,7 @@
{{={= =}=}}
import uuidv4 from 'uuid/v4'
-export default class {= entity.name =} {
+export default class {= entityClassName =} {
_data = {}
constructor (data = {}) {
diff --git a/waspc/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js b/waspc/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
index 0a0ed0afb..917ac07f4 100644
--- a/waspc/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
+++ b/waspc/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
@@ -1,4 +1,5 @@
{{={= =}=}}
export const ADD = 'entities/{= entityLowerName =}/ADD'
+export const SET = 'entities/{= entityLowerName =}/SET'
export const UPDATE = 'entities/{= entityLowerName =}/UPDATE'
export const REMOVE = 'entities/{= entityLowerName =}/REMOVE'
diff --git a/waspc/data/Generator/templates/react-app/src/entities/_entity/actions.js b/waspc/data/Generator/templates/react-app/src/entities/_entity/actions.js
index 3e53095e5..5e0d7b7d7 100644
--- a/waspc/data/Generator/templates/react-app/src/entities/_entity/actions.js
+++ b/waspc/data/Generator/templates/react-app/src/entities/_entity/actions.js
@@ -1,13 +1,23 @@
{{={= =}=}}
import * as types from './actionTypes'
+import {=entityClassName=} from './{=entityClassName=}'
+import { selectors } from './state'
/**
* @param {{= entity.name =}} {= entityLowerName =}
*/
-export const add = ({= entityLowerName =}) => ({
+export const add = ({=_entity=}) => ({
type: types.ADD,
- data: {= entityLowerName =}.toData()
+ data: {=_entity=}.toData()
+})
+
+/**
+ * @param {{=entityClassName=}[]} {=_entities=}
+ */
+export const set = ({=_entities=}) => ({
+ type: types.SET,
+ {=_entities=}: {=_entities=}.map({=_e=} => {=_e=}.toData())
})
/**
@@ -27,3 +37,13 @@ export const remove = (id) => ({
type: types.REMOVE,
id
})
+
+{=# entityActions =}
+export const {=name=} = () => (dispatch, getState) => {
+ const {=_entities=} = selectors.all(getState())
+ const updateFn = {=&updateFn=}
+ const new{=_Entities=} = updateFn({=_entities=}.map({=_e=} => {=_e=}.toData())).map({=_e=} => new {=entityClassName=}({=_e=}))
+ dispatch(set(new{=_Entities=}))
+}
+
+{=/ entityActions =}
diff --git a/waspc/data/Generator/templates/react-app/src/entities/_entity/state.js b/waspc/data/Generator/templates/react-app/src/entities/_entity/state.js
index fee090cf8..ccab55547 100644
--- a/waspc/data/Generator/templates/react-app/src/entities/_entity/state.js
+++ b/waspc/data/Generator/templates/react-app/src/entities/_entity/state.js
@@ -1,13 +1,13 @@
{{={= =}=}}
import { createSelector } from 'reselect'
-import {= entity.name =} from './{= entity.name =}'
+import {=entityClassName=} from './{=entityClassName=}'
import * as types from './actionTypes'
// We assume that root reducer of the app will put this reducer under
// key ROOT_REDUCER_KEY.
-const ROOT_REDUCER_KEY = 'entities/{= entity.name =}'
+const ROOT_REDUCER_KEY = 'entities/{=entity.name=}'
const initialState = {
all: []
@@ -21,14 +21,20 @@ const reducer = (state = initialState, action) => {
all: [ ...state.all, action.data ]
}
+ case types.SET:
+ return {
+ ...state,
+ all: action.{=_entities=}
+ }
+
case types.UPDATE:
return {
...state,
all: state.all.map(
- {= entityLowerName =} =>
- {= entityLowerName =}.id === action.id
- ? { ...{= entityLowerName =}, ...action.data }
- : {= entityLowerName =}
+ {=_entity=} =>
+ {=_entity=}.id === action.id
+ ? { ...{=_entity=}, ...action.data }
+ : {=_entity=}
)
}
@@ -36,7 +42,7 @@ const reducer = (state = initialState, action) => {
return {
...state,
all: state.all.filter(
- {= entityLowerName =} => {= entityLowerName =}.id !== action.id
+ {=_entity=} => {=_entity=}.id !== action.id
)
}
@@ -53,7 +59,7 @@ selectors.root = (state) => state[ROOT_REDUCER_KEY]
* @returns {{= entity.name =}[]}
*/
selectors.all = createSelector(selectors.root, (state) => {
- return state.all.map(data => new {= entity.name =}(data))
+ return state.all.map(data => new {=entityClassName=}(data))
})
diff --git a/waspc/examples/todoApp/ext/Main.css b/waspc/examples/todoApp/ext/Main.css
index 7ce3eeef3..74c5275e6 100644
--- a/waspc/examples/todoApp/ext/Main.css
+++ b/waspc/examples/todoApp/ext/Main.css
@@ -38,7 +38,7 @@ button.selected {
background-color: white;
border: none;
display: inline-block;
- font-size: 24px;
+ font-size: 24px !important;
cursor: pointer;
}
diff --git a/waspc/examples/todoApp/ext/Todo.js b/waspc/examples/todoApp/ext/Todo.js
index 36da7263b..1529d0d54 100644
--- a/waspc/examples/todoApp/ext/Todo.js
+++ b/waspc/examples/todoApp/ext/Todo.js
@@ -7,24 +7,16 @@ import { connect } from 'react-redux'
// These will have well defined and documented APIs and paths.
// Note that Task, NewTaskForm and TaskList are generated based on the declarations
// we made in todoApp.wasp file.
-import Task from '@wasp/entities/task/Task'
import NewTaskForm from '@wasp/entities/task/components/NewTaskForm'
import TaskList from '@wasp/entities/task/components/TaskList'
import * as taskState from '@wasp/entities/task/state.js'
import * as taskActions from '@wasp/entities/task/actions.js'
+import ToggleIsDoneButton from '@wasp/components/ToggleIsDoneButton'
+import DeleteDoneButton from '@wasp/components/DeleteDoneButton'
class Todo extends React.Component {
// TODO: prop types.
- toggleIsDoneForAllTasks = () => {
- const areAllDone = this.props.taskList.every(t => t.isDone)
- this.props.taskList.map(t => this.props.updateTask(t.id, { isDone: !areAllDone }))
- }
-
- deleteCompletedTasks = () => {
- this.props.taskList.map((t) => { if (t.isDone) this.props.removeTask(t.id) })
- }
-
isAnyTaskCompleted = () => this.props.taskList.some(t => t.isDone)
isThereAnyTask = () => this.props.taskList.length > 0
@@ -36,12 +28,10 @@ class Todo extends React.Component {
Todos
-
+ />
{ this.isThereAnyTask() && (<>
-
+
@@ -61,11 +49,9 @@ class Todo extends React.Component {
-
+
>)}
@@ -78,7 +64,5 @@ class Todo extends React.Component {
export default connect(state => ({
taskList: taskState.selectors.all(state)
}), {
- addTask: taskActions.add,
- updateTask: taskActions.update,
- removeTask: taskActions.remove
+ addTask: taskActions.add
})(Todo)
diff --git a/waspc/examples/todoApp/todoApp.wasp b/waspc/examples/todoApp/todoApp.wasp
index 874d964be..551f836cb 100644
--- a/waspc/examples/todoApp/todoApp.wasp
+++ b/waspc/examples/todoApp/todoApp.wasp
@@ -52,3 +52,24 @@ entity-list TaskList {
active: {=js task => !task.isDone js=}
}
}
+
+button ToggleIsDoneButton {
+ label: "✓",
+ onClick: toggleIsDoneAction
+}
+
+button DeleteDoneButton {
+ label: "Delete completed",
+ onClick: deleteDoneAction
+}
+
+action toggleIsDoneAction {=js
+ tasks => {
+ const areAllDone = tasks.every(t => t.isDone)
+ return tasks.map(t => ({ ...t, isDone: !areAllDone }))
+ }
+js=}
+
+action deleteDoneAction {=js
+ tasks => tasks.filter(t => !t.isDone)
+js=}
diff --git a/waspc/src/Generator/Button.hs b/waspc/src/Generator/Button.hs
new file mode 100644
index 000000000..6f27248cd
--- /dev/null
+++ b/waspc/src/Generator/Button.hs
@@ -0,0 +1,58 @@
+module Generator.Button
+ ( generateButtons
+ ) where
+
+import Data.Maybe (fromJust)
+import Data.Aeson ((.=), object)
+import qualified Data.Aeson as Aeson
+import qualified System.FilePath as FP
+import Path ((>), relfile, reldir)
+import qualified Path
+import qualified Path.Aliases as Path
+import qualified Path.Extra as Path
+
+import Wasp (Wasp)
+import qualified Wasp
+import qualified Wasp.Button as WButton
+import Generator.FileDraft (FileDraft, createTemplateFileDraft)
+import qualified Generator.Entity
+import qualified Generator.Common as Common
+
+generateButtons :: Wasp -> [FileDraft]
+generateButtons wasp = concatMap (generateButton wasp) (Wasp.getButtons wasp)
+
+generateButton :: Wasp -> WButton.Button -> [FileDraft]
+generateButton wasp button =
+ [ generateButtonComponent wasp button
+ ]
+
+generateButtonComponent :: Wasp -> WButton.Button -> FileDraft
+generateButtonComponent wasp button = createTemplateFileDraft dstPath srcPath (Just templateData)
+ where
+ srcPath = [reldir|src|] > [reldir|components|] > [relfile|_Button.js|]
+ dstPath = Common.srcDirPath > buttonDirPathInSrc > (fromJust $ Path.parseRelFile $ (WButton._name button) ++ ".js")
+
+ onClickActionData :: Maybe Aeson.Value
+ onClickActionData = do
+ actionName <- WButton._onClickActionName button
+ action <- Wasp.getActionByName wasp actionName
+ let (pathInSrc, exportedIdentifier) = Generator.Entity.getImportInfoForAction wasp action
+ return $ object [ "importPath" .= buildImportPathFromPathInSrc pathInSrc
+ , "exportedIdentifier" .= exportedIdentifier
+ ]
+
+ templateData = object $
+ [ "wasp" .= wasp
+ , "button" .= button
+ ]
+ ++ maybe [] (\d -> ["onClickAction" .= d]) onClickActionData
+
+buttonDirPathInSrc :: Path.RelDir
+buttonDirPathInSrc = [reldir|components|]
+
+-- | Takes path relative to the src path of generated project and turns it into relative path that can be
+-- used as "from" part of the import in the button component source file.
+-- NOTE: Here we return FilePath instead of Path because we need stuff like "./" or "../" in the path,
+-- which Path would normalize away.
+buildImportPathFromPathInSrc :: Path.Path Path.Rel a -> FilePath
+buildImportPathFromPathInSrc pathInSrc = (Path.reversePath buttonDirPathInSrc) FP.> (Path.toFilePath pathInSrc)
diff --git a/waspc/src/Generator/Entity.hs b/waspc/src/Generator/Entity.hs
index f5712ba08..9ad4cb29d 100644
--- a/waspc/src/Generator/Entity.hs
+++ b/waspc/src/Generator/Entity.hs
@@ -6,6 +6,8 @@ module Generator.Entity
, entityStatePathInSrc
, entityActionsPathInSrc
+ , getImportInfoForAction
+
-- EXPORTED FOR TESTING:
, generateEntityClass
, generateEntityState
@@ -16,12 +18,15 @@ module Generator.Entity
, entityTemplatesDirPath
) where
+import qualified Data.Aeson as Aeson
import Data.Maybe (fromJust)
import Path ((>), relfile)
import qualified Path
import qualified Path.Aliases as Path
+import Util (jsonSet)
import Wasp
+import qualified Wasp.Action
import Generator.FileDraft
import qualified Generator.Common as Common
import Generator.Entity.EntityForm (generateEntityCreateForm)
@@ -59,7 +64,23 @@ generateEntityActionTypes wasp entity
generateEntityActions :: Wasp -> Entity -> FileDraft
generateEntityActions wasp entity
- = createSimpleEntityFileDraft wasp entity (entityActionsPathInSrc entity) [relfile|actions.js|]
+ = createEntityFileDraft (entityActionsPathInSrc entity) [relfile|actions.js|] (Just templateData)
+ where
+ entityActions = getActionsForEntity wasp entity
+ templateData = jsonSet "entityActions" (Aeson.toJSON entityActions) (entityTemplateData wasp entity)
+
+-- | Provides information on how to import and use given action.
+-- Returns: (path (in src dir) to import action from, identifier under which it is exported).
+-- NOTE: This function is in this module because this is where logic for generating action is,
+-- but ideally that would move to more-standalone action generator and so would this function.
+getImportInfoForAction :: Wasp -> Wasp.Action.Action -> (Path.RelFile, String)
+getImportInfoForAction wasp action = (pathInSrc, exportedIdentifier)
+ where
+ -- NOTE: For now here we bravely assume that entity with such name exists.
+ Just entity = Wasp.getEntityByName wasp $ Wasp.Action._entityName action
+ pathInSrc = entityActionsPathInSrc entity
+ exportedIdentifier = Wasp.Action._name action
+
generateEntityComponents :: Wasp -> Entity -> [FileDraft]
generateEntityComponents wasp entity = concat
@@ -82,11 +103,16 @@ generateEntityLists wasp entity = map (generateEntityList wasp) entityLists
-- | Helper function that captures common logic for generating entity file draft.
createSimpleEntityFileDraft :: Wasp -> Entity -> Path.RelFile -> Path.RelFile -> FileDraft
createSimpleEntityFileDraft wasp entity dstPathInSrc srcPathInEntityTemplatesDir
- = createTemplateFileDraft dstPath srcPath (Just templateData)
+ = createEntityFileDraft dstPathInSrc srcPathInEntityTemplatesDir (Just templateData)
+ where
+ templateData = entityTemplateData wasp entity
+
+createEntityFileDraft :: Path.RelFile -> Path.RelFile -> Maybe Aeson.Value -> FileDraft
+createEntityFileDraft dstPathInSrc srcPathInEntityTemplatesDir maybeTemplateData =
+ createTemplateFileDraft dstPath srcPath maybeTemplateData
where
srcPath = entityTemplatesDirPath > srcPathInEntityTemplatesDir
dstPath = Common.srcDirPath > dstPathInSrc
- templateData = entityTemplateData wasp entity
-- * Paths of generated code (relative to src/ directory)
diff --git a/waspc/src/Generator/Entity/Common.hs b/waspc/src/Generator/Entity/Common.hs
index 966b9e7e1..47f01129d 100644
--- a/waspc/src/Generator/Entity/Common.hs
+++ b/waspc/src/Generator/Entity/Common.hs
@@ -39,16 +39,28 @@ entityTemplateData wasp entity = object
[ "wasp" .= wasp
, "entity" .= entity
, "entityLowerName" .= getEntityLowerName entity
+ , "entityUpperName" .= getEntityUpperName entity
-- TODO: use it also when creating Class file itself and in other files.
, "entityClassName" .= getEntityClassName entity
, "entityTypedFields" .= map entityFieldToJsonWithTypeAsKey (entityFields entity)
+ -- Below are shorthands, so that templates are more readable.
+ -- Each one has comment example for Task entity.
+ , "_entity" .= getEntityLowerName entity -- task
+ , "_entities" .= ((getEntityLowerName entity) ++ "s") -- tasks
+ , "_Entity" .= getEntityUpperName entity -- Task
+ , "_Entities" .= ((getEntityUpperName entity) ++ "s") -- Tasks
+ , "_e" .= [head $ getEntityLowerName entity] -- t
+ , "_es" .= ((head $ getEntityLowerName entity) : "s") -- ts
]
getEntityLowerName :: Entity -> String
getEntityLowerName = Util.toLowerFirst . entityName
+getEntityUpperName :: Entity -> String
+getEntityUpperName = Util.toUpperFirst . entityName
+
getEntityClassName :: Entity -> String
-getEntityClassName = Util.toUpperFirst . entityName
+getEntityClassName = getEntityUpperName
{- | Converts entity field to a JSON where field type is a key set to true, along with
all other field properties.
diff --git a/waspc/src/Generator/Generators.hs b/waspc/src/Generator/Generators.hs
index 8d95bf906..f9b43a8d9 100644
--- a/waspc/src/Generator/Generators.hs
+++ b/waspc/src/Generator/Generators.hs
@@ -14,6 +14,7 @@ import Generator.FileDraft
import qualified Generator.Entity as EntityGenerator
import qualified Generator.PageGenerator as PageGenerator
import qualified Generator.ExternalCode as ExternalCodeGenerator
+import qualified Generator.Button
import qualified Generator.Common as Common
@@ -63,6 +64,7 @@ generateSrcDir wasp
]
++ PageGenerator.generatePages wasp
++ EntityGenerator.generateEntities wasp
+ ++ Generator.Button.generateButtons wasp
++ [generateReducersJs wasp]
generateReducersJs :: Wasp -> FileDraft
diff --git a/waspc/src/Lexer.hs b/waspc/src/Lexer.hs
index 6e43b996f..ea8a1733b 100644
--- a/waspc/src/Lexer.hs
+++ b/waspc/src/Lexer.hs
@@ -19,6 +19,12 @@ reservedNameApp = "app"
reservedNamePage :: String
reservedNamePage = "page"
+reservedNameButton :: String
+reservedNameButton = "button"
+
+reservedNameAction :: String
+reservedNameAction = "action"
+
reservedNameEntity :: String
reservedNameEntity = "entity"
@@ -51,6 +57,8 @@ reservedNames =
, reservedNamePage
, reservedNameEntity
, reservedNameEntityForm
+ , reservedNameButton
+ , reservedNameAction
-- * Data types
, reservedNameString
, reservedNameBoolean
diff --git a/waspc/src/Parser.hs b/waspc/src/Parser.hs
index 8f4e1785d..b97cd18ee 100644
--- a/waspc/src/Parser.hs
+++ b/waspc/src/Parser.hs
@@ -15,6 +15,8 @@ import Parser.Entity (entity)
import Parser.Entity.EntityForm (entityForm)
import Parser.Entity.EntityList (entityList)
import Parser.JsImport (jsImport)
+import Parser.Button (button)
+import Parser.Action (action)
import Parser.Common (runWaspParser)
@@ -25,10 +27,18 @@ waspElement
<|> waspElementEntity
<|> waspElementEntityForm
<|> waspElementEntityList
+ <|> waspElementButton
+ <|> waspElementAction
waspElementApp :: Parser Wasp.WaspElement
waspElementApp = Wasp.WaspElementApp <$> app
+waspElementButton :: Parser Wasp.WaspElement
+waspElementButton = Wasp.WaspElementButton <$> button
+
+waspElementAction :: Parser Wasp.WaspElement
+waspElementAction = Wasp.WaspElementAction <$> action
+
waspElementPage :: Parser Wasp.WaspElement
waspElementPage = Wasp.WaspElementPage <$> page
diff --git a/waspc/src/Parser/Action.hs b/waspc/src/Parser/Action.hs
new file mode 100644
index 000000000..0d8bfbcfe
--- /dev/null
+++ b/waspc/src/Parser/Action.hs
@@ -0,0 +1,21 @@
+module Parser.Action
+ ( action
+ ) where
+
+import Text.Parsec.String (Parser)
+
+import qualified Wasp.Action as Action
+
+import qualified Parser.Common as Common
+import qualified Parser.JsCode as JsCode
+import qualified Lexer as L
+
+
+action :: Parser Action.Action
+action = do
+ (entityName, actionName, updateFn) <- Common.waspElementLinkedToEntity L.reservedNameAction JsCode.jsCode
+ return Action.Action
+ { Action._name = actionName
+ , Action._entityName = entityName
+ , Action._updateFn = updateFn
+ }
diff --git a/waspc/src/Parser/Button.hs b/waspc/src/Parser/Button.hs
new file mode 100644
index 000000000..4bc84c6a4
--- /dev/null
+++ b/waspc/src/Parser/Button.hs
@@ -0,0 +1,44 @@
+module Parser.Button
+ ( button
+ ) where
+
+import Text.Parsec
+import Text.Parsec.String (Parser)
+
+import qualified Lexer as L
+import qualified Wasp.Button as Button
+import Parser.Common
+
+data Property
+ = Label !String
+ | OnClick !String -- ^ Name of action to execute on click.
+ deriving (Show, Eq)
+
+-- | Parses Button properties, separated by a comma.
+properties :: Parser [Property]
+properties = L.commaSep1 $
+ propLabel
+ <|> propOnClickActionName
+
+propLabel :: Parser Property
+propLabel = Label <$> waspPropertyStringLiteral "label"
+
+propOnClickActionName :: Parser Property
+propOnClickActionName = OnClick <$> waspProperty "onClick" L.identifier
+
+getLabel :: [Property] -> String
+getLabel ps = head $ [c | Label c <- ps]
+
+getOnClickActionName :: [Property] -> Maybe String
+getOnClickActionName ps = let actions = [a | OnClick a <- ps]
+ in if null actions then Nothing else Just (head actions)
+
+button :: Parser Button.Button
+button = do
+ (buttonName, buttonProps) <- waspElementNameAndClosure L.reservedNameButton properties
+
+ return Button.Button
+ { Button._name = buttonName
+ , Button._label = getLabel buttonProps
+ , Button._onClickActionName = getOnClickActionName buttonProps
+ }
diff --git a/waspc/src/Parser/Common.hs b/waspc/src/Parser/Common.hs
index 6f772e508..7874af138 100644
--- a/waspc/src/Parser/Common.hs
+++ b/waspc/src/Parser/Common.hs
@@ -50,18 +50,17 @@ waspElementNameAndClosure elementType closure =
return (elementName, closureContent)
-- | Parses declaration of a wasp element linked to an entity.
--- E.g. "entity-form {...}" or "entity-list {...}"
+-- E.g. "entity-form ..." or "action ..."
waspElementLinkedToEntity
:: String -- ^ Type of the linked wasp element (e.g. "entity-form").
- -> Parser a -- ^ Parser to be used for parsing closure content of the wasp element.
- -> Parser (String, String, a) -- ^ Name of the linked entity, element name and closure content.
-waspElementLinkedToEntity elementType closure = do
+ -> Parser a -- ^ Parser to be used for parsing body of the wasp element.
+ -> Parser (String, String, a) -- ^ Name of the linked entity, element name and body.
+waspElementLinkedToEntity elementType bodyParser = do
L.reserved elementType
linkedEntityName <- L.angles L.identifier
elementName <- L.identifier
- closureContent <- waspClosure closure
-
- return (linkedEntityName, elementName, closureContent)
+ body <- bodyParser
+ return (linkedEntityName, elementName, body)
-- | Parses wasp property along with the key, "key: value".
waspProperty :: String -> Parser a -> Parser a
diff --git a/waspc/src/Parser/Entity/EntityForm.hs b/waspc/src/Parser/Entity/EntityForm.hs
index 644893ea0..03cbbe4b6 100644
--- a/waspc/src/Parser/Entity/EntityForm.hs
+++ b/waspc/src/Parser/Entity/EntityForm.hs
@@ -26,7 +26,7 @@ import qualified Lexer as L
entityForm :: Parser EntityForm
entityForm = do
(entityName, formName, options) <-
- P.waspElementLinkedToEntity L.reservedNameEntityForm entityFormOptions
+ P.waspElementLinkedToEntity L.reservedNameEntityForm (P.waspClosure entityFormOptions)
return WEF.EntityForm
{ WEF._name = formName
diff --git a/waspc/src/Parser/Entity/EntityList.hs b/waspc/src/Parser/Entity/EntityList.hs
index e844167cb..7c37d70bc 100644
--- a/waspc/src/Parser/Entity/EntityList.hs
+++ b/waspc/src/Parser/Entity/EntityList.hs
@@ -22,7 +22,7 @@ import qualified Lexer as L
entityList :: Parser EntityList
entityList = do
(entityName, listName, options) <-
- P.waspElementLinkedToEntity L.reservedNameEntityList entityListOptions
+ P.waspElementLinkedToEntity L.reservedNameEntityList (P.waspClosure entityListOptions)
return WEL.EntityList
{ WEL._name = listName
diff --git a/waspc/src/Wasp.hs b/waspc/src/Wasp.hs
index 85ba20e02..bc47ee1cd 100644
--- a/waspc/src/Wasp.hs
+++ b/waspc/src/Wasp.hs
@@ -20,6 +20,14 @@ module Wasp
, getEntityFormsForEntity
, getEntityListsForEntity
+ , getButtons
+ , addButton
+
+ , getActions
+ , addAction
+ , getActionsForEntity
+ , getActionByName
+
, module Wasp.Page
, getPages
, addPage
@@ -37,6 +45,9 @@ import qualified Wasp.EntityForm as EF
import qualified Wasp.EntityList as EL
import Wasp.JsImport
import Wasp.Page
+import Wasp.Button
+import Wasp.Action (Action)
+import qualified Wasp.Action
import qualified Util as U
@@ -54,6 +65,8 @@ data WaspElement
| WaspElementEntity !Entity
| WaspElementEntityForm !EF.EntityForm
| WaspElementEntityList !EL.EntityList
+ | WaspElementButton !Button
+ | WaspElementAction !Action
deriving (Show, Eq)
fromWaspElems :: [WaspElement] -> Wasp
@@ -108,6 +121,33 @@ getPages wasp = [page | (WaspElementPage page) <- waspElements wasp]
addPage :: Wasp -> Page -> Wasp
addPage wasp page = wasp { waspElements = (WaspElementPage page):(waspElements wasp) }
+-- * Button
+
+getButtons :: Wasp -> [Button]
+getButtons wasp = [button | (WaspElementButton button) <- waspElements wasp]
+
+addButton :: Wasp -> Button -> Wasp
+addButton wasp button = wasp { waspElements = (WaspElementButton button):(waspElements wasp) }
+
+-- * Action
+
+getActions :: Wasp -> [Action]
+getActions wasp = [action | (WaspElementAction action) <- waspElements wasp]
+
+addAction :: Wasp -> Action -> Wasp
+addAction wasp action = wasp { waspElements = (WaspElementAction action):(waspElements wasp) }
+
+-- | Gets action with a specified name from wasp, if such an action exists.
+-- We assume here that there are no two actions with same name.
+getActionByName :: Wasp -> String -> Maybe Action
+getActionByName wasp name = U.headSafe $ filter (\a -> Wasp.Action._name a == name) (getActions wasp)
+
+-- | Retrieves all actions that are performed on specific entity.
+getActionsForEntity :: Wasp -> Entity -> [Action]
+getActionsForEntity wasp entity = filter isActionOfGivenEntity (getActions wasp)
+ where
+ isActionOfGivenEntity action = entityName entity == Wasp.Action._entityName action
+
-- * Entities
getEntities :: Wasp -> [Entity]
diff --git a/waspc/src/Wasp/Action.hs b/waspc/src/Wasp/Action.hs
new file mode 100644
index 000000000..c0cd3aaab
--- /dev/null
+++ b/waspc/src/Wasp/Action.hs
@@ -0,0 +1,26 @@
+module Wasp.Action
+ ( Action(..)
+ ) where
+
+import Data.Aeson ((.=), object, ToJSON(..))
+import Wasp.JsCode (JsCode)
+
+-- Although name is general (Action), we are actually making many implicit assumptions here:
+-- That Action is an update action that updates the whole collection of certain entity at once.
+-- We are doing this because it is general enough for our purposes right now, but in the future we
+-- will want to have much more developed system of actions, and Action will probably only be the
+-- umbrella name/type for all of the action types.
+
+data Action = Action
+ { _name :: !String
+ , _entityName :: !String
+ -- | Js is expected to be function that takes list of entities and returns new list of entities.
+ , _updateFn :: !JsCode
+ } deriving (Show, Eq)
+
+instance ToJSON Action where
+ toJSON action = object
+ [ "name" .= _name action
+ , "entityName" .= _entityName action
+ , "updateFn" .= _updateFn action
+ ]
diff --git a/waspc/src/Wasp/Button.hs b/waspc/src/Wasp/Button.hs
new file mode 100644
index 000000000..ff09d96d0
--- /dev/null
+++ b/waspc/src/Wasp/Button.hs
@@ -0,0 +1,18 @@
+module Wasp.Button
+ ( Button(..)
+ ) where
+
+import Data.Aeson ((.=), object, ToJSON(..))
+
+data Button = Button
+ { _name :: !String
+ , _label :: !String
+ , _onClickActionName :: !(Maybe String)
+ } deriving (Show, Eq)
+
+instance ToJSON Button where
+ toJSON button = object
+ [ "name" .= _name button
+ , "label" .= _label button
+ , "onClickActionName" .= _onClickActionName button
+ ]
diff --git a/waspc/test/Parser/CommonTest.hs b/waspc/test/Parser/CommonTest.hs
index 899b83ab5..a2937e4c6 100644
--- a/waspc/test/Parser/CommonTest.hs
+++ b/waspc/test/Parser/CommonTest.hs
@@ -13,7 +13,7 @@ spec_parseWaspCommon :: Spec
spec_parseWaspCommon = do
describe "Parsing wasp element linked to an entity" $ do
it "When given a valid declaration, parses it correctly." $ do
- runWaspParser (waspElementLinkedToEntity "entity-form" whiteSpace) "entity-form TaskForm { }"
+ runWaspParser (waspElementLinkedToEntity "entity-form" (waspClosure whiteSpace)) "entity-form TaskForm { }"
`shouldBe` Right ("Task", "TaskForm", ())
describe "Parsing wasp element name and properties" $ do