This component is designed for editing string values and can be used to create input and editing forms in CRUD-based systems.
The KoiFormFieldString component, like all input fields, allows users to modify a specific part of a data set. During initialization, the component takes a snapshot of the specified data segment, stores it internally, and passes this internal data to the socket as the initial value. Users see this initial value and can issue commands to change it. Upon receiving a command, the component updates its internal data and emits it as part of an event. The component intercepting the event can then replace the corresponding part of the original data set with the updated value.
The command issued by the user depends on the control tool provided. These tools can range from native input fields to components from the KoiCom library. Examples include toggle switches, single or multi-selection lists, sliders, and more. For the KoiFormFieldString component, the control tool is a native input field, and the command is a request to "update the value of a data element," containing the user-provided input.
Users may enter invalid values or fail to provide a required value for the data element. To help users identify such issues, the KoiFormFieldString component displays notifications regarding the completeness and validity of the entered data.
For example, try entering a value in the input field below, and then delete it. The component will respond to the attempt to submit an empty string with an error notification.
This example demonstrates how the KoiFormFieldString component receives a user command, updates its internal data based on the command, and displays an error notification if the resulting data is invalid.
Additionally, this example shows how the component transmits its internal data through events. When a user modifies a value, the component triggers the koi-form-field-change event, which has the bubbles property set to true. As a result, the event propagates up the DOM tree. This feature enables input fields to be embedded within a composite component — commonly referred to as a form — and allows the composite component to automatically process incoming data as a controller.
To intercept the koi-form-field-change event, the composite component must implement the KoiFormFieldChangesInterceptable behavior.
In the example above, the panel containing the input field component listens for the koi-form-field-change event and adjusts a label (via the KoiLabel component). However, more commonly, a form's role is to aggregate data from multiple fields and submit it to a server.
For the form to correctly aggregate data, input fields must include not only the values entered by the user in the event but also the identifiers of the corresponding data elements.
The following example simulates how a form works with two KoiFormFieldString components and a data submission button.
In this example, the form creates input fields without initial values, processes user commands, and simulates submitting the data to a server.
Class
The initialization process of the KoiFormFieldString component is as follows:
-
_onConstructed() KoiDataCapable
- _constructState() KoiStateCapable
- _constructData() KoiFormFieldDataCapable
- _constructSocket() KoiFormFieldStringNativeInputSocketConnectable
- _constructInnerNativeComponentData() KoiNativeInputDataCapable
-
_onBeforeConnected() KoiDataCapable
- _constructFormFieldChangedEvent() KoiFormFieldChangedEventDispatchable
- _prepareDefaultDataValuesFromAttributes() KoiDataCapable
- _prepareSocket() KoiSocketConnectable
-
- socket.getTemplate() KoiFormFieldBaseSocket
-
- socket._getTemplateForInput() KoiFormFieldStringNativeInputSocket
- socket._getLabelTemplate() KoiFormFieldBaseSocket
- socket._getErrorHolderTemplate() KoiFormFieldBaseSocket
-
_onConnected() KoiElementStencil
- _updateSomethingWhenConnected() KoiElementStencil
-
- _updateOwnDataWhenConnected() KoiElementStencil
- _updateStateCodeWhenConnected() KoiStateCapable
-
_onAfterConnected() KoiElementStencil
- isSomethingChanged() KoiDataCapable
- _handleSomethingChangedWhenAfterConnected() KoiBaseControl
-
- _updateAppearance() KoiBaseControl
- _setNothingChanged()KoiDataCapable
- _subscribeToEvents() KoiOperationsInterceptable
-
- _subscribeToOperateEvent() KoiOperationsInterceptable
The KoiFormFieldString component inherits from KoiFormFieldStencil and implements the following behaviors: KoiFormFieldDataCapable, KoiNativeInputDataCapable, and KoiFormFieldStringNativeInputSocketConnectable.
The base class, KoiFormFieldStencil, defines the behavior for all input fields. When a user interacts with an internal component, the input field intercepts the event triggered by the internal component, retrieves the command from the event data, executes this command (modifying its internal data), and then triggers its own koi-form-field-change event, passing along its updated internal data.
It is important to note that the user does not directly modify the value. Instead, the user issues a command to modify the value. The input field processes this command, resulting in an update to its internal data. For instance, if the user enters a value, the entered value does not necessarily match the value stored in the internal data.
The internal data of the KoiFormFieldString component is of type KoiFormFieldData, and the value within this data is of type KoiDataElementString. This is established by the KoiFormFieldDataCapable behavior.
Using a data element of type KoiDataElementString influences how the component provides an initial value for the user to modify and how it processes the user’s modified value.
To provide the user with an initial value for modification, the socket retrieves this value from the internal data. In the case of KoiFormFieldString, supplying an initial value to the user requires only the value itself. In more complex cases, additional information is needed, such as input constraints, a set of options for user selection, and so on. This collection of information constitutes the socket’s initial settings. The process of forming these initial settings is implemented in the _convertDataToInitialSocketSettings method.
Since the internal data contains a value of type KoiDataElementString, and the user command derived from the internal component’s data contains a string value, updating the component’s internal data involves a straightforward replacement of the current value with the value provided in the command. This is implemented in the _applyOperationToOwnData method.
To enable the component to execute user commands, internal components must be capable of generating and passing commands in events. However, native input fields do not include commands in their events. To transform a native event into a command, an additional data object of type KoiOperationData is required. The KoiNativeInputDataCapable behavior creates such a data object in the _constructInnerNativeComponentData method. The KoiFormFieldString component uses this object by implementing the _getOperationDataFromEvent method. In this method, the component pretends to extract a command from the event but, in reality, retrieves the value directly from the socket. It uses this event to modify the additional data object and then returns this object as if it were a command obtained from the event.
Finally, the KoiFormFieldStringNativeInputSocketConnectable behavior defines the native component used by the KoiFormFieldString component, specifically input[type="text"]. Thus, the KoiFormFieldString component acts as a wrapper around this native component.
However, a simple wrapper around a native component is insufficient for handling user input effectively. The KoiFormFieldStringNativeInputSocket enhances the component's template by adding a placeholder and an error holder, providing additional visual states for the component.
As you can see, the main class for input fields is KoiFormFieldStencil, which establishes the process for intercepting user input and triggering the koi-form-field-change event. Let’s break down this process further.
The KoiFormFieldStencil class implements the KoiOperationsInterceptable behavior, enabling the component to intercept events from control components. The _attemptApplyOperated handler is used to respond to these events.
- _isOwnOperateEvent()KoiOperationsInterceptable
- event.stopPropagation()KoiOperationsInterceptable
- _updateSomethingWhenOperated()KoiOperationsInterceptable
-
- _updateOwnDataWhenOperated() KoiOperationsInterceptable
-
- _getOperationDataFromEvent() KoiFormFieldString
- _applyOperationToOwnData() KoiFormFieldString
- _updateStateCodeWhenOperated() KoiOperationsInterceptable
-
- _determineStateCode() KoiDataCapable
- _setStateCode() KoiStateCapable
- _onAfterOperated() KoiOperationsInterceptable
-
- isSomethingChanged() KoiDataCapable
- _handleSomethingChangedWhenOperated() KoiOperationsInterceptable
-
- _dispatchEventsWhenChangedAfterOperated() KoiFormFieldStencil
- _updateAppearance() KoiBaseControl
- _setNothingChanged() KoiDataCapable
- _handleOperated() KoiOperationsInterceptable
Here we see that the process of handling an internal event structurally mirrors other processing flows in the KoiCom library. For example, the event handler for pressing the KoiIdButton has a similar structure.
However, there are some differences. The KoiFormFieldString component is based on a native component, which does not pass commands in its events. Therefore, when processing an event from the native component, KoiFormFieldString calls an auxiliary data object in the _getOperationDataFromEvent method, sets the command parameters within it, and returns it as a command. The subsequent _applyOperationToOwnData method uses this command to modify the component's own data. These updated data are then passed along in an event triggered by the _dispatchEventsWhenChangedAfterOperated method. If needed, the component's appearance is updated in the _updateAppearance method.
The _updateAppearance method modifies the component's appearance based on its current data. Let’s look at the sequence of its execution.
- isStateAbnormal() KoiStateCapable
-
- _displayAbnormalState() KoiFormFieldBaseSocketConnectable
- _beforeDisplayNormalState() KoiSocketConnectable
-
- _displaySocket() KoiFormFieldStencil
-
- _getInitialData() KoiFormFieldDataCapable
- _convertDataToInitialSocketSettings() KoiFormFieldString
- _displayInitialSocketState() KoiFormFieldBaseSocketConnectable
- _displayNormalState() KoiSocketConnectable
-
- _updateSocket() KoiFormFieldStencil
-
- _removeErrorMessage() KoiFormFieldBaseSocketConnectable
From this sequence, it is evident that the KoiFormFieldString component, depending on the state and validity of its own data, may invoke the _displayAbnormalState method. Using this method, the input field can display an input error, as demonstrated in the example above.
If the data are valid, the input field does not adopt its initial value at the getTemplate stage, as components like KoiLabel do. Instead, this occurs during the _displaySocket stage. This distinction arises because the input field's initial value becomes available not during socket preparation but only when the data are ready for display.
The _displaySocket method is unique. It is executed only once, thanks to the _callOnceBeforeDisplayNormalState method, which is defined in the base class for all controls, KoiBaseControl. Through this mechanism, the input field retrieves its initial value in the _getInitialData method when the data are ready for display.
During subsequent updates to the component's appearance in the _displayNormalState method, the component does not modify the input field’s value. Once the component has displayed the initial value, control of the input field’s value passes to the user, and the component should no longer alter it autonomously.
This might raise a question: Suppose there is a complex data entry form, and based on the data entered in one field, the value in another field needs to change automatically. Often, methods like direct setters are used for this. I consider this approach incorrect. If changing the value in one field should trigger an automatic change in another, this should be treated as a reinitialization of part of the form for further user interaction. Instead of introducing a direct field modification method, a reinitialization method should be implemented at the form level, not the field level. The reinitialization method should notify the user of the change in part of the form — for example, by displaying a loading spinner and temporarily disabling input fields.
Data
The KoiFormFieldString component’s own data are of type KoiFormFieldData and are defined by the following schema:
The component retrieves initial data values from the tag’s attributes using the standard _prepareDefaultDataValuesFromAttributes method.
Direct manipulation of the KoiFormFieldString component’s data is not recommended. The component's purpose is to receive commands from the user, modify its own data based on those commands, and pass the updated data in events. Therefore, the data values are set during the component's initialization and subsequently modified by the user, not programmatically.
For this reason, the KoiFormFieldDataCapable behavior does not provide public methods for retrieving or setting values. However, such methods can be implemented in subclasses using the methods of the KoiFormFieldData class.
The only additional operation the component performs on user-entered values is validation, checking for correctness and completeness. This validation is carried out using methods of the KoiDataElementString class. You can specify the data requirements in the data schema by passing arguments to the constructor, such as setting allow_empty to false.
Connector
Setting initial values through attributes has a significant drawback: attribute values are specified as strings enclosed in quotation marks. As a result, special characters in the value might be incorrectly processed by the browser. To avoid this, you can escape the characters in the attribute value.
Another way to set initial values for the component's data is by connecting to a data provider via a connector. In this case, you can either retrieve the initial values and use them during initialization, or you can link the component to the connector so that any future changes in the connector's data automatically update the component's own data.
In the first case, you can use the KoiSingleConnectorInitializable behavior, and in the second case, the KoiSingleConnectorInteractable behavior.
In both cases, a subclass of the component is created with an additional provider_id attribute, where the identifier of the data provider should be specified.
In the following example, the input field is connected to a label acting as a data provider. The buttons on the left control the state and value of the label, and the input field reacts to these changes.
This connection uses the KoiSingleConnectorInteractable behavior. Such a connection for string and numeric values is rarely used because changes in the provider's data may cause the input field's value to change unexpectedly during user interaction. More commonly, the provider is a form that creates input fields and passes only initial values to them, assuming that subsequent changes will be made exclusively by the user.
That said, a connector-based connection can be useful. With a connector, the component can receive both the initial value and additional settings derived from the provider's data, such as restrictions on the length of the input, and so on.
It is assumed that these settings may not match the exact settings of the provider's data element. Additionally, the value entered by the user may not necessarily be used by the provider and might have a different data type or purpose altogether. For example, the provider might provide the component with the air temperature in Celsius, the component might display an initial value in Fahrenheit based on this information, allow the user to modify it, and the modified value could then be used to populate an external table.
Given this purpose, the component only retrieves the initial data value from the provider. Subsequent changes to the component's value do not affect the provider’s data, and vice versa: changes to the provider’s data do not impact the component's data. Moreover, when validating its own data, the component does not rely on the provider’s data settings.
This behavior can be easily modified if desired. For instance, you could make the component reinitialize whenever the provider’s data changes. In the example above, you can see a reinitialization scenario. When a new value is assigned to the data provider (in this case, KoiLabel), the KoiFormFieldString component resets its current value, regenerates its own data based on the provider’s data, and re-executes the _displaySocket method.
Note that during reinitialization, the component’s value changes, but the koi-form-field-change event is not triggered. This is because the event is only triggered when the user interacts with the component. If another component needs to know that the value has changed due to a change in the provider’s data, it should receive this information directly from the provider, not from the input field.
Additionally, while the component's value after initialization no longer depends on the provider, the component's state does. If the provider transitions to an error or loading state, the component will respond by transitioning to the corresponding state as well.
Attributes
id | The component identifier. |
---|---|
field_name | The name of the data element being modified by the user. |
field_value | The initial value displayed. |
placeholder | A hint for the user. |
The field_name attribute can be used to specify which editable data element the component works with. For example, one instance of KoiFormFieldString could be designated for editing a client's name, while another could be used for editing a client's phone number.
The value of the field_name attribute is passed along with the new value in the koi-form-field-change event. This allows the component handling the event to determine which record field the user intended to modify. When processing the event, the value of field_name can be retrieved using the getFieldName method of the KoiFormFieldData class.
In some cases, the user may not need to enter new data but rather modify existing data. For the KoiFormFieldString component, default data values are specified through the component's internal data object. This is facilitated by the field_value attribute, which sets the initial value for the input field.
Care should be taken with the field_value attribute. Since the value of an HTML attribute is enclosed in double quotes, the presence of special characters — such as quotation marks themselves — can lead to incorrect interpretation by the browser.
The placeholder attribute is used to display a hint next to the input field. This can be an instruction or an explanation to guide the user on how the field should be filled out.
Component State
The KoiFormFieldString component inherits from KoiBaseControl, and thus provides two primary display states, controlled by the show and hide methods.
Additionally, the KoiFormFieldString component implements the behavior of KoiSocketInputEnablementToggleableSocketConnectable, allowing a set of methods to manage user access to modifying the component's value. These methods include setReadonly, removeReadonly, enable, and disable.
Error display is managed internally by the component, based on its own state and the validity of its internal data. Therefore, if a controller component wants the KoiFormFieldString component to display an error derived from business logic, corresponding methods should be implemented in KoiFormFieldString. These methods can leverage _displayErrorMessage and _removeErrorMessage.
However, despite the ability to implement methods for error display control, I recommend a different approach: delegate part of the business logic responsible for data validation to the component's data object. This encapsulates validation within the component itself, simplifying the remaining business logic code significantly.
As mentioned earlier, the value of the KoiFormFieldString component is controlled exclusively by the user during input. Consequently, the component does not include methods for direct control of the displayed value or error messages.
Nevertheless, methods for managing the KoiFormFieldString component's value can be implemented. This allows a controller component, which encapsulates business logic and contains the KoiFormFieldString, to initialize or reinitialize the component based on the data and format it requires from the user.
An example of such a controller component could be a form that allows resetting user-entered values to their initial state.
To enable reinitialization of the component, it is necessary to implement a method that clears the data and processes the changes correctly afterward. Importantly, clearing the data refers to resetting the value of the data element but does not reset the default value. Additionally, it is crucial to remember that the component has already been initialized, meaning its _displaySocket method has been invoked once. The _displaySocket method is designed to block subsequent invocations, so this block must be removed. In the example above, the block is removed in the _makeBeforeDisplayNormalStateCallable method.
Event
The KoiFormFieldString component has its own data but does not implement the KoiChangedEventDispatchable behavior, and therefore does not trigger the koi-changed event. Instead, the KoiFormFieldString component implements the KoiFormFieldChangedEventDispatchable behavior, which allows it to trigger the koi-form-field-change event.
The koi-form-field-change event is triggered after the user changes the input field's value, when the field loses focus, or when the user presses the Enter key in the field.
The koi-form-field-change event has the bubbles property set to true, meaning it propagates through the DOM tree. This allows the creation of composite components, such as input forms with multiple fields and internal logic for data validation that is handled through events.
To intercept the koi-form-field-change event, a component must implement the KoiFormFieldChangesInterceptable behavior. In the handler method _attemptApplySocketChange, the component can use methods from the KoiFormFieldData object, such as getFieldName and getFieldValue. These methods allow the retrieval of the name of the data field associated with the input field and the value of the input field.
It is important to note that for the states "the user has not entered a value" and "the user has entered an empty value," the _defined flag in the field_value data element is used. This flag allows the user interface to give different feedback to the user. In one case, a prompt like "fill in the field" may be displayed, while in another, an error message such as "this field is required" may be shown.
Application
As a rule, the KoiFormFieldString component is not used independently. It is most often used as part of a form for adding or modifying data. The form acts as the controller and decides how to respond to changes in the input fields.
- The form can accumulate the values received from the input fields. When the user is ready to do something with the data they entered, they click a button (e.g., the submit button), and the form submits the accumulated data.
- The form can take action upon receiving each new batch of data. For this, neither the button nor the data object for the form is required.
- The form can also take action when data is changed, not only in batches but also as the user enters it. In this case, the input fields must trigger change events, not only after a change, but also during input, for example, responding to keystrokes.
As you can see, the use case for input fields depends on the form type. The only time it is necessary to extend the behavior of the input fields is when it is required to track key presses.
The fact that changing the value in an input field triggers an event instead of calling a handler allows the logic to be moved to the form level, making it the controller.
As the controller, the form can also perform additional validation.
Validation occurs in two stages. In the first stage, validation is performed by the KoiFormFieldString component, which validates its own data independently of the form and other components on the form. Typically, this is validation for completeness, format compliance, and some boundary conditions.
In the second stage, data is validated by the form itself, considering all the data it has accumulated. For example, at this stage, the form might decide whether to disable or enable the "submit" button based on whether all required fields are filled out. Or it might display an error notification next to a specific input field, indicating how the entered value matches the values obtained from other fields.
Finally, I will highlight a couple of important points once again.
The first important point is that, as a rule, each input field on the form is bound to a data element one-to-one. Therefore, the form determines which specific KoiFormFieldString component triggered the change event using the getFieldName method. It is not necessary to pass a reference to the component in the event. This is done to further emphasize the form's independence from the input fields. The form should manage the input fields, if necessary, only through the socket methods.
An equally important point is related to the form's behavior when one or more data elements must be filled in. When rendering the form in the DOM, the input field corresponding to such an element has an empty value, but no error is displayed because the user has not yet made an error by leaving the field empty. However, when the user clears the field value or attempts to submit the form without filling out the field, this should trigger an error display. This feature is implemented in the KoiDataElement class, and the form should not be responsible for this behavior.
To conclude, it is important to note that the form's own data and the data of its components do not necessarily correspond to each other. In other words, the form may send to the server data that the user did not enter, but data derived from the user's input. For this reason, I recommend using a data object on the form to accumulate and transform the values received by the form.