MscmpSystForms behaviour (mscmp_syst_forms v0.1.0)
The MscmpSystForms
module provides a standard methodology for authoring
application user interface forms in support of business systems development.
use MscmpSystForms
When you
use MscmpSystForms
, the MscmpSystForms module will set@behaviour MscmpSystForms
and the following functions will be created for the using module:
update_button_state/3
start_processing_override/2
finish_processing_override/2
update_display_data/3
get_component_info/1
Documentation for these functions is available in the module and they are simply delegates to the same functions (give or take some arity) in
MscmpSystForms
.
Foundational Ideas
At the heart of our methodology and support are several key ideas presented here.
On "Forms"
One thing to be mindful of as you read this section and the documentation generally is our use of the word "Form". In this documentation we will more often use the word "Form" in the more generic sense of a user interface to a particular subject or feature than to refer to a specific Form Component or something like an HTML form; we will use the word in that manner as well and hopefully context should make clear which sense we mean.
Definitions vs. Rendering
A "Form" conforming to the MscmpSystForms
standard will be defined in two
distinct parts:
Definitional Concerns
Our forms may be complex and include a variety of both informational and input fields (elements) to which different users will have different permission based entitlements. Additionally, some element properties, such as labels, may appear not just along side the element in the user interface but also in tool tips, quick help pages and the like. Some properties, such as permissions or even some runtime display properties can easily be made inheritable from parent elements to child elements; naturally, the idea of virtual elements can be useful in building such hierarchies as well.
Having a place to define both these static elements and the inheritance hierarchies between elements without necessarily being coupled to the layout has advantages. We can create a cleaner definition of these properties while reducing the redundancies that defining these attributes in the context of page layout concerns could force on us as well as reducing the noise that display related attributes would necessarily force on us.
Rendering Concerns
All forms must ultimately be laid out for rendering as the user interface and this area of concern deals with the issues of laying out our form elements.
The truth is that the "Rendering Concerns" are really the typical user interface development activities (web or otherwise) that most front end developers will think about and it is the "Definitional Concerns" which we are adding to that process.
Form Data
It is not uncommon to closely couple database Ecto schema definitions with the presentation and management forms, using the Ecto database schema to directly drive the user interface forms. The problem is that it can force unnatural compromises to either the database structure (assuming that the Ecto Schemas are representative of the underlying table structures) or the user interface forms or both.
In our model we view both the database structure and the user interface design as first class concerns and as such our standard is to not directly use Ecto Schemas which define database data to also back forms. We create independent Ecto "Embedded Schemas" to back the forms specifically. It absolutely happens that there can be form schemas and database schemas with high degrees of similarity, but by setting the expectation that we will always have an Embedded Schema defined to back the form ensures that the development of the form avoids the aforementioned conflicts and compromises.
Any given form will also have three senses of its data, each of which may differ from each other enough that we track the data for all three purposes:
Original Data
This is the data which a form initializes with. When creating new records, this will either be empty or populated with the default values defined as the starting place for any new records. For viewing or editing existing data records, this value will be the data as loaded from the database. As the user interacts with the form, these original data values will not change, always reflecting the starting point with which the form was initialized.
While not always useful we can use the original data for fallback/reset purposes, to display changes (e.g. percentage changed) relative to the starting data, and similar such purposes.
In the form's assigns we represent this using the form backing
Ecto
embedded schema struct which can be passed as the "data" expected byEcto.Changeset
validation functions. This data is kept by the view including all values regardless of user permissions to see or alter those values.Current Data
The current state of the data, including any changes made by the user or system as they interact with the form, but prior to those changes being committed to the database. The starting point of this data when the form initializes is typically the same as the Original Data.
As the user interacts with the form, the current data is updated to reflect changes. We usually store this data in the form's assigns as a map as we can pass this directly to the data validation functions (
Ecto.Changeset
based) as needed.The current data includes all form data regardless of the user's permissions to view or edit that data.
Display Data
A representation of the data for the purposes of display to the user in the form. The data values in Display Data are the same as those in Current Data except they are filtered by the user's permissions for data visibility.
Display Data is stored in the view's assigns as a
Phoenix.HTML.Form.t/0
struct so that it can be accessed directly by the LiveView for rendering.
Working with Phoenix
The most common use case for MscmpSystForms
is for facilitating Phoenix based
web and external API user interface development. To understand how
MscmpSystForms
fits into the Phoenix development model it's important to
understand certain assumptions we make about the role of Phoenix in our broader
application development paradigm.
Important
Our thinking and approach to working with Phoenix is not the conventional or generally accepted approach in many ways. Use caution and understand the trade-offs when evaluating our work in this regard.
Our development model takes the stance that Phoenix is not the application, but the user interface layer. That layer brings together a lot of dependencies to deliver services, but those dependencies are independently developed as separate Elixir/OTP applications and could be used (in theory) in other contexts.
As such our Phoenix application only deals with the presentation of the user interface and wiring that user interface to the actual business/domain logic written elsewhere. In the process some elements which by convention has certain roles within typical Phoenix application development are repurposed to serve our needs better.
Developing Forms
With our Foundational Ideas having been discussed, we now move on to looking how this translates into form development.
Source Organization & File Roles
Starting with a standard Phoenix application, to which MscmpSystForms
has been
added as a dependency, we will have two basic directories (or applications in
the case where the Phoenix project is initialized as an umbrella project):
my_app
, my_app_web
. In typical Phoenix applications the web based user
interface including views and controllers are built in the my_app_web
directory and the business/domain logic (i.e. "contexts") resides in the
my_app
directory. We adopt the directories created by mix phx.new
, but we
change their purpose:
my_app_web
Hosts view related code. Controller like code is discouraged here.
my_app:
Hosts controller like logic. Business/Domain Logic is discouraged here and should be developed in external Elixir Projects and included as dependencies in the Phoenix application.
Using these directories, and assuming our application has one form (my_form
),
we will create the following basic file structure for our form's source code:
my_app_web
└── lib
└── my_app_web
└── live
├── my_form_live.ex
└── my_form_live.html.heex
my_app
└── lib
├── api
│ └── msform
│ └── my_form.ex
└── impl
└── msform
└── my_form
├── actions.ex
├── data.ex
└── definitions.ex
Note
The example directory structure above has been simplified to focus on those entries important to understanding the
MscmpSystForms
development model. Other files and directories which are standard for Phoenix development and which would be present but unaltered under our model have been excluded from the example listings.
my_app_web
In the
my_app_web
example listing the pathmy_app_web/lib/my_app_web/live
is the standard Phoenix pathing forPhoenix.LiveView
pages. While we do make some assumption changes to Phoenix standard practices in regard to what gets done in this directory, those changes are minor and really a matter of convention.Files
my_form_live.html.heex
this file is a typical Phoenix LiveView
Heex
file. The only difference from standard Phoenix Heex development is thatMscmpSystForms.WebComponents
will be used as a source of components rather than theCoreComponents
module which comes with Phoenix (though it is available).my_form_live.ex
this file is the typical LiveView controller file which backs the LiveView which is home to the LiveView's
mount/3
,handle_event/3
, andhandle_info/2
functions. The difference under theMscmpSystForms
model is that we typically code much less logic directly in this file allowing that logic to exist in themy_app
directory hierarchy.Therefore this file is limited to two roles: 1) mapping LiveView events to controller level logic in the
my_app
code; and 2) values which directly become display issues in the web interface; for example text strings for flash messages related to validation failures might be coded here since we're effectively still in the "view" layer per our definitions.
my_app
Under our methodology we make the greatest departures from typical Phoenix development in this directory. In typical Phoenix development practice this directory is the home of the business/domain logic, the "model". For us, however, this is our controller layer, but even more importantly it is where we deal with the "Definitional Concerns" discussed earlier and which is unique to
MscmpSystForms
based forms.Sub-directories & Files
api/msform
This directory holds the source files which define the API to the form which is used by the form logic defined in
my_app_web
.For each form, we define a single Elixir source file named after the name of the form it exists to support.
api/msform/my_form.ex
This Elixir source file defines a single module in the
Msform
namespace which implements theMscmpSystForms
behavior (typically withuse MscmpSystForms
). This module also defines a struct of the form's backing data usingEcto.Schema.embedded_schema/1
so that we can use the full data mapping and validation capability of both Ecto and Phoenix forms.Finally this module also defines an API for the form which includes the
defdelegate
calls for theMscmpSystForms
Behaviour callbacks and functions to expose other controller-like logic as appropriate to the forms specific needs. Note that the API is typically delegating to specific source files and internal modules written in theimpl/msform/my_form/*.ex
files.Public facing types for the form are also defined in this file. Usually this will contain the definition of
t()
for the embedded schema and the a typeparameters
for use in typespecs associated with Changeset processing. Since functions likeEcto.Changeset.cast/4
require their 'params' argument to be represented as a map and since we know the possible valid structures of the map we can define a typespec to help documenting that structure.impl/msform
This directory holds the implementations of form related business logic. The source files defining the business logic for each form is kept in a sub-directory named after the form's name. This logic is broken out into topically oriented source files (
actions.ex
,data.ex
, anddefinitions.ex
) as described below.impl/msform/my_form/actions.ex
This source file typically defines a single module for the form implementing the controller-like actions which are initiated from the user interface or other sources the form should respond to (e.g. PubSub messages).
impl/msform/my_form/data.ex
Typically defines a single module containing functions which implement form data validation via Changeset processing.
impl/msform/my_form/definitions.ex
This file contains a single module directed at resolving a form's "Definitional Concerns". In this module we find the implementation of the
MscmpSystForms.get_form_config/0
andMscmpSystForms.get_form_modes/0
functions.
Summary
Form Generation
Retrieves the textual information (label
, label_link
, and info
)
field values from the Form Configuration for the identified component.
Builds current Component Configurations based on the Form Configuration and Form Modes definitions in combination with runtime values such as the current Form State and the current user's permission grants.
Initializes the MscmpSystForms
standard assign attributes and readies the
form for rendering.
Rebuilds component configuration assigns updating the configurations driving user interface rendering.
Form Data Management
Converts a form data Changeset into a Phoenix.HTML.Form.t/0
struct after
having applied the current user's data visibility permissions.
Updates the display form data with new values.
State Management
Removes a processing override from the active overrides list.
A convenience function which sets the Form State State value on the appropriate Standard Assign Attribute.
A convenience function which sets the Form State Mode and State values on the appropriate Standard Assign Attributes.
A convenience function which sets the Form State Feature, Mode, and State values on the appropriate Standard Assign Attributes.
Adds a processing override to the active overrides list.
Sets the state of MscmpSystForms.WebComponents.msvalidated_button/1
components.
Callbacks
Returns a list of MscmpSystForms.Types.FormConfig.t/0
structs which
represent the abstract configuration of the form.
Returns a map of the recognized form states and the display modes each component should take when a given form state is specified.
An initialization sequence run during the connected, second call to
Phoenix.LiveView.mount/3
.
An initialization sequence run during first, static mount process.
An Ecto.Changeset.t/0
generating function which validates that a
given set of data is sufficient for posting as a fully fledged business
transaction.
An Ecto.Changeset.t/0
generating function which validates that a
given set of data is sufficient for simply saving to the database.
Form Generation
get_component_info(module, component_id)
@spec get_component_info( module(), %{form_id: MscmpSystForms.Types.form_id()} | %{binding_id: MscmpSystForms.Types.binding_id()} ) :: MscmpSystForms.Types.ComponentInfo.t() | nil
Retrieves the textual information (label
, label_link
, and info
)
field values from the Form Configuration for the identified component.
This is a convenience function which accepts either a form_id
value or
a binding_id
value and returns the textual information for the
component if found by the passed identifier.
Parameters
module
- the name of the form module implementing theMscmpSystForms.get_form_config/0
callback with which form configuration data will be retrieved.component_id
- this value is either theform_id
orbinding_id
that is associated with the component for which textual information is being retrieved.
get_render_configs(module, feature, mode, state, perms)
@spec get_render_configs( module(), MscmpSystForms.Types.form_state_feature_name(), MscmpSystForms.Types.form_state_mode_name(), MscmpSystForms.Types.form_state_state_name(), MscmpSystPerms.Types.perm_grants() ) :: MscmpSystForms.Types.render_configs()
Builds current Component Configurations based on the Form Configuration and Form Modes definitions in combination with runtime values such as the current Form State and the current user's permission grants.
Render configurations, also called Component Configurations in this documentation provide each component in the user interface instructions on how to render and what interactivity to accept from the user.
The Component Configurations generated by this function are returned as a map
of key/value pairs where the keys are the MscmpSystForms.Types.form_id/0
values of the user interface components and the values are
MscmpSystForms.Types.ComponentConfig.t/0
structs defining the current
rendering requirements of the components. Typically this map is merged into
the assigns of the form so that components can retrieve the configurations at
render time.
Parameters
module
- the name of theMscmpSystForms
implementing module which backs the form.feature
- the Form State Feature to reference when building Component Configurations.mode
- the Form State Mode to reference when building Component Configurations.state
- the Form State State to reference when building Component Configurations.perms
- the current user's applicable permission grants for the form.
init_assigns(socket_or_assigns, session_name, module, feature, mode, state, user_perms, opts \\ [])
@spec init_assigns( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.session_name(), module(), MscmpSystForms.Types.form_state_feature_name(), MscmpSystForms.Types.form_state_mode_name(), MscmpSystForms.Types.form_state_state_name(), MscmpSystPerms.Types.perm_grants(), Keyword.t() | [] ) :: MscmpSystForms.Types.socket_or_assigns()
Initializes the MscmpSystForms
standard assign attributes and readies the
form for rendering.
Typically this function is called in the implementation of the
MscmpSystForms.preconnect_init/6
of the form once the user, the user
permissions, and any starting data for the form has been resolved.
This function must be called prior to any attempted rendering of the form as many user interface components expect values in the Standard Assign Attributes to be available.
In addition to adding the Standard Assign Attributes, this function will
process the Form Configurations (MscmpSystForms.get_form_config/0
) using
the Form State parameters (feature
, mode
, and state
) and the current
user's permissions (user_perms
)
Standard Assign Attributes:
msrd_instance_id
- the identifier for any single instance of a running form. This supports the use case where a user logged into a single session may have multiple instances of the same form open accessing the same data, but desiring to also edit that data. In this case we only allow a single instance of the form to be in an editable mode, blocking all other instances, including other instances in the same session, from editing the data.msrd_session_name
- the identifier for the user's authenticated session. This session name is a reference to the session record in the database which contains session oriented data and session management statistics such as expiration date/time. It is by having a valid, authenticated session identified by this value that the system knows that the user is authenticated.msrd_form_module
- theMscmpSystForms
behaviour implementing module which backs the form.msrd_original_data
- the data backing the form at the time the form was initialized. This data does not change as user interacts with the form, including changing the form's data, allowing for comparisons, validations, and resets of changed data with the starting data. The original data is a struct as defined by theMscmpSystForms
backing module where the keys are the binding ID's of the form data fields. Note that this data includes all form backing data without regard to the current user's data visibility permissions.msrd_current_data
- the data backing the form including any changes made by the user or by the system in response to various interactions with the form. As the name suggests this is value represents the current state of the data and is the data that must pass any validation attempts and the data that will ultimately be saved to the database. This value is represented as a simple map of key/value pairs where the keys are the binding ID's of the form data fields. Note that this data includes all form backing data without regard to the current user's data visibility permissions.msrd_display_data
- the data used to fill user interface form elements. This data is the same as themsrd_current_data
except that values which are disallowed by the user data visibility permissions are excluded and the data is represented as aPhoenix.HTML.Form.t/0
value.msrd_feature
- the currently prevailing form state feature. The "feature" is the highest level determinant of form state, which determines how some form user interface components present themselves or allow for interactivity. Typical examples of how themsrd_feature
of a form might be used includes a single form supporting both sales quoting and sales ordering: while very similar there are differences in the functionality and elements required by these two activities and themsrd_feature
would tell the form which mode was currently in use. The value is an atom. SeeMscmpSystForms.get_form_modes/0
for more informationmsrd_mode
- the current mode of the form state. This is typically used to distinguish between "view only" form modes and "maintenance" form modes which allow a user to change data. The actual modes implemented by a form are not restricted to these purposes and may be arbitrarily defined as the form needs dictate. The currentmsrd_mode
value is considered a subdivision of the currentmsrd_feature
value; this means that the samemsrd_mode
value may appear to behave differently depending on the current value of themsrd_feature
attribute. The value is an atom. SeeMscmpSystForms.get_form_modes/0
for more informationmsrd_state
- the current form state within themsrd_mode
. "Form states" are the most granular level at which form user interface behaviors are determined. This value supports functionality which allows for "progressive entry" style forms: forms which only allow certain user inputs to be made prior to allowing others which depend on the earlier values, for example.msrd_state
values are subordinate to the prevailingmsrd_mode
value and as such the samemsrd_state
value may exhibit different behaviors for differing values ofmsrd_mode
. The value is an atom. SeeMscmpSystForms.get_form_modes/0
for more informationmsrd_overrides
- a list of the currently active processing overrides. As a user interacts with a form, there may be certain actions which result in longer running processes during which certain user interface interactions or data displays should become inactive or indicate some form of "please wait" message; the list of active processing overrides indicate that such processing exists so that user interface elements may respond as necessary. This list is maintained using theMscmpSystForms.start_processing_override/2
andMscmpSystForms.finish_processing_override/2
functions.msrd_user_perms
- The relevant permission grants of the current user. This is a map conforming to theMscmpSystPerms.Types.perm_grants/0
type. The permission grants are used as a filter for determining what the user may see in terms of data and do in terms of form functionality.
Parameters
socket_or_assigns
- the socket or assigns for the current view.session_name
- the name of the session. This value is usually set in prior to the view being called and is part of the parameters passed to the view. Ultimately this is the link between the users client and our richer sense of session available to the server side view logic.module
- the name of the form module backing the form and implementing theMscmpSystForm
behaviour.feature
- the currently prevailing form state feature. See themsrd_feature
Standard Assign Attribute description above for more.mode
- the currently prevailing form state mode. See themsrd_mode
Standard Assign Attribute description above for more.state
- the currently prevailing form state. See themsrd_state
Standard Assign Attribute description above for more.user_perms
- the current user permission grants which apply to the form. See themsrd_user_perms
Standard Assign Attributes discussion above for more.opts
- this function accepts a number of optional parameters. The available options are:original_data
- the starting form data, often times as drawn from the database. The data retained here does not reflect changes in the data by the user interacting with the form. All data backing the form is kept in this value without regard to the current user's data visibility permissions. The expected value for this option is the starting data as a struct defined by the form backingMscmpSystForms
behaviour implementing module. The default value is an empty struct of the expected type.current_data
- the current form data reflecting changes made due to user interactions which have not yet been committed to the database. The data here is the complete current data, without regard to the user's data visibility permissions. This data given for this parameter is expected to be a simple map based on the form backingMscmpSystForms
implementing module. The default value is a map copied from theoriginal_data
option value.display_data
- this option will ultimately set themsrd_display_data
Standard Assign Attribute described above. For the purposes ofinit_assigns/8
, this value should be a reference to aMscmpSystForms.Types.data_validation_types/0
value which will process the data provided by theoriginal_data
andcurrent_data
options into the correct display data for the form, after having applied the user's data visibility permissions.overrides
- this option allows the processing overrides list stored inmsrd_overrides
to be populated on initialization. This could be helpful if certain initialization processes themselves are expected to be long running. By default this option is set to an empty list.
rebuild_component_assigns(socket_or_assigns)
@spec rebuild_component_assigns(MscmpSystForms.Types.socket_or_assigns()) :: MscmpSystForms.Types.socket_or_assigns()
Rebuilds component configuration assigns updating the configurations driving user interface rendering.
This function needs to be called after settings which should alter the renderable Component Configurations. For example, changing the form's current Form State (feature, mode, or state) will change how the user interface components are rendered; after the Form State has been changed this function must be called to rebuild the component configuration assigns that actually drive the rendering of those components.
Activities requiring the Component Configurations assigns to be rebuilt include:
Changing the
msrd_feature
,msrd_mode
, ormsrd_state
Form State values.Updating the
msrd_user_perms
value.
Most other operations, such as processing form data changes don't require Component Configuration rebuilding as they work within the existing Component Configurations.
Rebuilding the Component Configuration assigns is a somewhat expensive process
and to avoid over-processing these rebuilds its recommended that all changes
which require a Component Configuration rebuild be performed prior to calling
rebuild_component_assigns/1
.
Form Data Management
to_form(changeset, perms, opts \\ [])
@spec to_form(Ecto.Changeset.t(), MscmpSystPerms.Types.perm_grants(), Keyword.t()) :: Phoenix.HTML.Form.t()
Converts a form data Changeset into a Phoenix.HTML.Form.t/0
struct after
having applied the current user's data visibility permissions.
Once the current form's data has been validated it must be turned into a form
that can be rendered. This function basically wraps the Phoenix.Ecto
implementation of the Phoenix.HTML.FormData.to_form/2
function so that we
can apply user data visibility permission to the data prior to the conversion
of that data into a renderable Phoenix.HTML.Form.t/0
struct.
Parameters
changeset
- anEcto.Changeset.t/0
struct representing the validated data with which to build the form. Typically this Changeset will be generated either byvalidate_save/2
orvalidate_post/2
.perms
- the current user's permission grants as recorded in the Standard Assign Attributemsrd_user_perms
. Seeinit_assigns/8
for more.opts
- while not typically used, there are optional parameters which are passed to thePhoenix.HTML.FormData.to_form/2
function. The available options are documented atPhoenix.HTML.Form.form_for/4
. The options here have slightly different names to avoid naming collisions.component_id
- the same as theid
option ofPhoenix.HTML.Form.form_for/4
.component_method
- the same as themethod
option ofPhoenix.HTML.Form.form_for/4
.component_multipart
- the same as themultipart
option ofPhoenix.HTML.Form.form_for/4
.component_csrf_token
- the same as thecsrf_token
option ofPhoenix.HTML.Form.form_for/4
.component_errors
- the same as theerrors
option ofPhoenix.HTML.Form.form_for/4
.
update_display_data(socket_or_assigns, display_data, opts \\ [])
@spec update_display_data( MscmpSystForms.Types.socket_or_assigns(), Ecto.Changeset.t() | Phoenix.HTML.Form.t() | MscmpSystForms.Types.data_validation_types(), Keyword.t() ) :: MscmpSystForms.Types.socket_or_assigns()
Updates the display form data with new values.
The display data of the form, which represents the form's backing data
after the application of effective user permissions to purge values that
the user is not entitled to see, is set using this function. The data is
stored in the view's assigns as a Phoenix.HTML.Form.t/0
value which is
then passed to the view for rendering.
Parameters
socket_or_assigns
- the socket or assigns for the current view.display_data
- this option contains either the new display data to set and with which to update the form or indicates the kind of data validation to perform on the assigns stored data (msrd_original_data
&msrd_current_data
; seeMscmpSystForms.init_assigns/8
for more).One method for setting the display data is to pass this option to the function using actual data. This data can take the form of either a
Ecto.Changeset.t/0
value or aPhoenix.HTML.Form.t/0
value. If a Changeset is passed, the function will automatically process it into aPhoenix.HTML.Form.t/0
struct, applying the permissions currently set in themsrd_user_perms
Standard Assigns Attribute to filter the data. If the value to be passed in this option is aPhoenix.HTML.Form.t/0
value, the struct should have been generated usingMscmpSystForms.to_form/3
so that the user data visibility permissions will have been allied.The second method is to pass
display_data
as a value referencing a display data validation type (MscmpSystForms.Types.data_validation_types/0
). When this method is used, the values of themsrd_current_data
andmsrd_original_data
are validated using the standard validation functions (MscmpSystForms.validate_save/2
andMscmpSystForms.validate_post/2
) and then processed into aPhoenix.HTML.Form.t/0
value to save as the newmsrd_display_data
value. Either of the validation types will result in the application of user data visibility permissions per themsrd_user_perms
Standard Assigns Attribute.opts
- this function defines some optionally required parameters which are dependent on thedisplay_data
parameter. When thedisplay_data
value is passed as aMscmpSystForms.Types.data_validation_types/0
allowed value the following are required:original_data
- a struct of values representing the starting data initialized on initial form loading and absent any changes the user may have made and not yet committed to the database. This value should be available in the standard assigns forMscmpSystForms
based forms.current_data
- a map of values representing the current data backing the form. This data is complete (unfiltered by user data related permissions) and includes any edits made by the user and not yet committed to the database. This value is available in the standard assigns forMscmpSystForms
based forms.module
- the name of the form module implementing theMscmpSystForm
behaviour and the functions to validate the Changeset.
State Management
finish_processing_override(socket_or_assigns, override)
@spec finish_processing_override( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.processing_override_name() ) :: MscmpSystForms.Types.socket_or_assigns()
Removes a processing override from the active overrides list.
Once an active operation previously added to the process overrides list has completed its processing, this function is used to remove it from the list so that any user interface components that are watching for the operation to be active can resume their normal behavior.
Parameters
socket_or_assigns
- the socket or assigns for the current view.override
- the name of the processing override to remove from the active process overrides list.
set_form_state(socket_or_assigns, state)
@spec set_form_state( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.form_state_state_name() ) :: MscmpSystForms.Types.socket_or_assigns()
A convenience function which sets the Form State State value on the appropriate Standard Assign Attribute.
This allows only updating the Form State State value of the form, leaving the Form State Feature and Mode values unchanged.
See set_form_state/4
for more information and caveats.
Parameters
socket_or_assigns
- the socket or assigns for the current view.state
- the new Form State State to set for the form.
set_form_state(socket_or_assigns, mode, state)
@spec set_form_state( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.form_state_mode_name(), MscmpSystForms.Types.form_state_state_name() ) :: MscmpSystForms.Types.socket_or_assigns()
A convenience function which sets the Form State Mode and State values on the appropriate Standard Assign Attributes.
This allows only updating the Form State Mode and State values of the form, leaving the Form State Feature value unchanged.
See set_form_state/4
for more information and caveats.
Parameters
socket_or_assigns
- the socket or assigns for the current view.mode
- the new Form State Mode to set for the form.state
- the new Form State State to set for the form.
set_form_state(socket_or_assigns, feature, mode, state)
@spec set_form_state( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.form_state_feature_name(), MscmpSystForms.Types.form_state_mode_name(), MscmpSystForms.Types.form_state_state_name() ) :: MscmpSystForms.Types.socket_or_assigns()
A convenience function which sets the Form State Feature, Mode, and State values on the appropriate Standard Assign Attributes.
Note
Using this function does not result in the regeneration of Component Configurations. This is not done to avoid regenerating Component Configurations multiple times unnecessarily.
You must explicitly regenerate the Component Configuration for changes made by this function to be rendered correctly. This is typically done by calling
rebuild_component_assigns/1
some time after this function has been called.
For more see MscmpSystForms.init_assigns/8
.
Parameters
socket_or_assigns
- the socket or assigns for the current view.feature
- the new Form State Feature to set for the form.mode
- the new Form State Mode to set for the form.state
- the new Form State State to set for the form.
start_processing_override(socket_or_assigns, override)
@spec start_processing_override( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.processing_override_name() ) :: MscmpSystForms.Types.socket_or_assigns()
Adds a processing override to the active overrides list.
Some user interface components are configured to change their presentation and
interactivity when certain, possibly long running, processes are underway.
This function adds the value of the override
parameter to the active
processes list allowing components interest in that processing state to
respond accordingly.
Parameters
socket_or_assigns
- the socket or assigns for the current view.override
- the name of the processing override to activate.
update_button_state(socket_or_assigns, form_id, button_state)
@spec update_button_state( MscmpSystForms.Types.socket_or_assigns(), MscmpSystForms.Types.form_id(), MscmpSystForms.Types.msvalidated_button_states() ) :: MscmpSystForms.Types.socket_or_assigns()
Sets the state of MscmpSystForms.WebComponents.msvalidated_button/1
components.
Validated buttons exist in one of three states defined by
MscmpSystForms.Types.msvalidated_button_states/0
. This function will
set the state of the validated button identified by the form_id
parameters to the state identified by the button_state
parameters.
Parameters
socket_or_assigns
- the socket or assigns for the current view.form_id
- the identifier of the component to update. SeeMscmpSystForms.get_form_config/0
for more about form configuration attributes.button_state
- the state to which the validated button component should be set. Any value defined by theMscmpSystForms.Types.msvalidated_button_states/0
type is valid for this purpose.
Callbacks
get_form_config()
@callback get_form_config() :: [MscmpSystForms.Types.FormConfig.t()]
Returns a list of MscmpSystForms.Types.FormConfig.t/0
structs which
represent the abstract configuration of the form.
This function is usually simply returns a hard-coded list of
MscmpSystForms.Types.FormConfig.t/0
values as defined
by the form developer. This function is called by various form rendering
related functions to get the starting point for each component from which
MscmpSystForms.Types.ComponentConfig.t/0
values will be created by
MscmpSystForms.get_render_configs/5
.
Note that not all elements in a user interface require representation in the form configuration returned by this function. However, any element not included in the form configuration here will be excluded from responding to any changes in form state, processing overrides, or user permissions.
The configurations returned by this function, and the parent/child
relationships between individual FormConfig
values, should be structured to
facilitate the appropriate inheritance of properties from FormConfig
parent
to child; the goal being to reduce redundant establishment of those properties
in the configuration. This will often times result in the data returned by
this function being hierarchical and resembling the layout structure of the
rendered form even though there is no requirement that the structure of these
configurations are related to the rendered layout in any way.
Examples
Consider the following example configuration:
def get_form_config do
[
%FormConfig{
permission: :form_access_permission,
label: "Virtual FormConfig",
children: [
%FormConfig{
form_id: :concrete_config_input,
binding_id: :data_field_input,
label: "Example Input Field",
},
%FormConfig{
form_id: :concrete_config_submit_button,
label: "Submit",
button_state: :message
}
]
}
]
end
At the top level we have a virtual or abstract FormConfig
value; we know
it's virtual because it defines no form_id
value. This top level value
exists so that its children can inherit its permission value and its defined
display modes (see MscmpSystForms.get_form_modes/0
).
This doesn't mean that the returned configuration represents all elements in
the rendered form and the parent in this case, being virtual, doesn't
correspond to any rendered form element at all (that would require a form_id
value being defined). The overall structure and the virtual element exist to
purely support the inheritance of configuration and state related values.
While with the structure in the example above, we can take advantage of inherited values, we can selectively override those values as needed. Consider this revision of the first example:
def get_form_config do
[
%FormConfig{
permission: :form_access_permission,
label: "Virtual FormConfig",
children: [
%FormConfig{
form_id: :concrete_config_input,
binding_id: :data_field_input,
label: "Example Input Field",
},
%FormConfig{
form_id: :concrete_config_submit_button,
permission: :form_submit_permission,
label: "Submit",
button_state: :message
}
]
}
]
end
In the revised example, we don't inherit the the :form_access_permission
value of the parent in :concrete_config_submit_button
any longer, but now
check the :form_submit_permission
permission instead. The
:concrete_config_submit_button
will continue to inherit other values from
the parent that it has not explicitly overridden.
get_form_modes()
@callback get_form_modes() :: map()
Returns a map of the recognized form states and the display modes each component should take when a given form state is specified.
The map returned by this function has a basic hierarchical structure where the
Form State feature
is at the top level, the Form State mode
taking the
next level, and the Form State State
being nested under mode
. In this
way each defined feature
can have one or more mode
entries and each mode
entry can have one or more state
entries.
%{
<feature>: %{
default: %{
<component_form_id>:
%MscmpSystForms.Types.ComponentDisplayModes{<default component modes>}
},
<mode>: %{
<state>: %{
<component_form_id>:
%MscmpSystForms.Types.ComponentDisplayModes{<component modes>}
}
},
processing_overrides: %{
<component_form_id>: [<processing override name>]
}
}
}
Mode Structure Rules & Considerations
Feature Level Map
The Feature level of the map will accept one or more Feature entries where the
key for each entry is the name
(MscmpSystForms.Types.form_state_feature_name/0
) by which the Feature is
to be referenced elsewhere in the application code. Most forms are likely to
only support a single Feature; in this case simply name the Feature
:default
as this is the default feature name used when one isn't otherwise
provided.
The contents of each Feature's map consist of:
a single
:default
key with values establishing the default component modes to use when the other Form State modes fail to define a component mode for a given component.a
:processing_overrides
key which define the processing overrides the various form components should respond to.one or more "modes" where the key is the Mode name (
MscmpSystForms.Types.form_state_mode_name/0
) and the value is a map of Form State States which define the various states supported by that Mode.
Mode Level Map
The Mode level is expressed as a key/value map where the keys are Form State
"State" names (MscmpSystForms.Types.form_state_state_name/0
) and the
values of those keys are the State level maps. There are no additional
entries or default values.
State Level Map
The State level is a simple key/value map where keys are Form State State
Names (MscmpSystForms.Types.form_state_state_name/0
) and the values are
maps of component Form IDs (MscmpSystForms.Types.form_id/0
) as keys along
with their configured component modes
(MscmpSystForms.Types.ComponentDisplayModes.t/0
) for that given
Feature/Mode/State combination.
Examples
Different form elements may take on different display properties as the state
of the form evolves over time with changes in data or in response to user
interactions. This function returns a nested map structure which, based on
the examples from get_form_config/0
, might look like:
%{
default: %{
default: %{
concrete_config_input:
%MscmpSystForms.Types.ComponentDisplayModes{component_mode: :visible},
concrete_config_submit_button:
%MscmpSystForms.Types.ComponentDisplayModes{component_mode: :visible}
},
entry: %{
basic_form_state: %{
concrete_config_input:
%MscmpSystForms.Types.ComponentDisplayModes{component_mode: :entry},
concrete_config_submit_button:
%MscmpSystForms.Types.ComponentDisplayModes{component_mode: :entry}
}
},
view: %{
basic_form_state: %{
concrete_config_input:
%MscmpSystForms.Types.ComponentDisplayModes{component_mode: :visible},
concrete_config_submit_button:
%MscmpSystForms.Types.ComponentDisplayModes{component_mode: :visible}
}
},
processing_overrides: %{
concrete_config_input: [:process_underway]
concrete_config_submit_button: [:process_underway]
}
}
}
postconnect_init(socket_or_assigns)
@callback postconnect_init( socket_or_assigns :: Phoenix.LiveView.Socket.t() | Phoenix.LiveView.Socket.assigns() ) :: Phoenix.LiveView.Socket.t() | Phoenix.LiveView.Socket.assigns()
An initialization sequence run during the connected, second call to
Phoenix.LiveView.mount/3
.
This function currently doesn't have any specific or well defined "typical use
cases" as does preconnect_init/6
.
It is assumed that any required data or configurations were initially set
using preconnect_init/6
and are available in the socket assigns passed to
Phoenix.LiveView.mount/3
.
Parameters
socket_or_assigns
- the socket or assigns for the current view.
preconnect_init(socket_or_assigns, session_name, feature, mode, state, opts)
@callback preconnect_init( socket_or_assigns :: Phoenix.LiveView.Socket.t() | Phoenix.LiveView.Socket.assigns(), session_name :: binary(), feature :: atom(), mode :: atom(), state :: atom(), opts :: Keyword.t() | [] | nil ) :: Phoenix.LiveView.Socket.t() | Phoenix.LiveView.Socket.assigns()
An initialization sequence run during first, static mount process.
This callback is intended to define a function called in the first, static
Phoenix.LiveView.mount/3
. While preconnect_init/6
can encapsulate any
logic necessary during this phase of initialization, we would typically see
the following activities handled:
The capture of socket values passed to
Phoenix.LiveView.mount/3
, such assession_name
in preparation for callinginit_assigns/8
.The resolution of user permissions required by the form.
The loading of backing data from the database for the view or maintenance of existing data, or defaulted data in the case of new record creation.
Initialization of the Standard Assign Attributes using
init_assigns/8
.
Naturally, while these would be typical activities to perform in
preconnect_init/6
, they are not required not is this callback limited to
running these functions. Truly, whatever makes sense for the form to process
during this stage of the form life-cycle is acceptable.
Parameters
socket_or_assigns
- the socket or assigns for the current view.session_name
- the name of the session. This value is usually set in prior to the view being called and is part of the parameters passed to the view. Ultimately this is the link between the users client and our richer sense of session available to the server side view logic.feature
- the currently prevailing form state feature. Typically this value is passed toinit_assigns/8
for further processing.mode
- the currently prevailing form state mode. Typically this value is passed toinit_assigns/8
for further processing.state
- the currently prevailing form state. Typically this value is passed toinit_assigns/8
for further processing.opts
- the options which might be used here will depended on the how this callback is implemented for the form. Since we often callinit_assigns/8
frompreconnect_init/6
, the options will simply be the same as those and just passed toinit_assigns/8
.
validate_post(original_data, current_data)
@callback validate_post(original_data :: term(), current_data :: map()) :: Ecto.Changeset.t()
An Ecto.Changeset.t/0
generating function which validates that a
given set of data is sufficient for posting as a fully fledged business
transaction.
This function serves to validate that a form's data fully meets the
requirements of a complete business transaction. In cases where
less-than-ready works in progress should be saveable, the validate_save/2
function should be called instead.
Parameters
original_data
- a representation of the starting data using theMscmpSystForms
implementing struct backing the form. Setting this value correctly allows the Changeset to correctly identify changes to the data.current_data
- a map based representation of the current data which is to be validated during the creation of the Changeset.
validate_save(original_data, current_data)
@callback validate_save(original_data :: term(), current_data :: map()) :: Ecto.Changeset.t()
An Ecto.Changeset.t/0
generating function which validates that a
given set of data is sufficient for simply saving to the database.
In many cases in business applications, it is possible to save "work in
progress" to the database without needing to post a fully committed business
transaction. For example, a purchase order may be authored over the course of
a few days before it is final and ready to send to the vendor; in this case
we could validate just enough data to make the in-progress purchase order
distinguishable from other similar orders while not requiring a fully
executable order and thus allow the author to save their work to the database.
Later, a transaction ready to be committed in the business sense ("posting")
can call validate_post/2
to validate that the transaction may be posted as
a valid business transaction.
Often times there is no distinction between the "savable" and "postable"
state. In these cases, it's best to write the main validate logic in the
validate_post/2
function and simply delegate this function to that one;
raising an exception in this function is also an option to force the correct
call.
Parameters
original_data
- a representation of the starting data using theMscmpSystForms
implementing struct backing the form. Setting this value correctly allows the Changeset to correctly identify changes to the data.current_data
- a map based representation of the current data which is to be validated during the creation of the Changeset.