Key Design Decision: Only Allow FormStorageObserver to Support localStorage
This implementation of FormStorageObserver is a divergence from what I originally had in mind for the class.
Originally, my hope was for the FormStorageObserver class to support all kinds of form data storage. By default, the class would use localStorage to store form data; but the user would be able to supply any custom interface that had the setItem, getItem, and removeItem methods -- just like localStorage. This way, they'd be able to store form data any way they wanted (for instance, in an in-memory object) -- not just with localStorage. Unfortunately, after trying to work with the idea, I concluded that it wasn't practical or worthwhile, and I abandoned the idea entirely.
When it comes to storing or retrieving form data, we need to know the kind of data being stored because this influences how the data is transformed during data storage/retrieval. That is, we need to know "Is this data for a checkbox?" ("Should I store this as a stringified boolean?"), "Is it data for a multi-select?" ("Should I store this as a stringified array?"), "Is it data for a regular text input?" ("Should I store this as a stringified string [sic]?"), etc. This information is necessary to make sure that the form fields are correctly updated whenever data is loaded from the "storage" into the actual fields. For instance, a checkbox will need its checked property/attribute updated with a valid boolean during loading, whereas a regular text input will need its value property/attribute udpated with a valid string.
Additionally, the setItem, getItem, and removeItem methods of the localStorage interface expect to be working with stringified data. But perhaps not everyone wants to work with stringified data. For instance, maybe they want the setItem method to accept a generic data type instead of just a string. (In other words, perhaps they want to store and retrieve data as a literal boolean instead of as a stringified boolean.)
This situation brings up a lot of questions if we want users to be able to store data in different ways. Do we want users to be able to supply "transformers" for data storage/retrieval? How do we enforce that the end users write "transformers" that take into account the types of data being stored (checkbox data, mutli-select data, text field data, etc.)? Do we want users to only be able to store stringified data (as in the case of localStorage), or do we want to support other types of data? Does the end user even want to be bothered with these concerns? How will our FormStorageObserver class handle these things in a clear and performant way without sacrificing the code's maintainability?
The questions go on and on. And although valid solutions exist, trying to handle these cases would ultimately make FormStorageObserver slower and more difficult to maintain. Moreoever, it's highly unlikely that a significant number of people are going to be desperate for a storage option besides localStorage. Most folks just want an easy way to throw form data into localStorage. That's it. It's sufficient to support that instead of diving into impractical use cases.
A big benefit of making a FormStorageObserver that only supports localStorage is that in some cases we can leverage local functions or static class methods that are only defined once. (This should save on memory per each instance of FormStorageObserver.) Another big benefit is that the code remains very simple since we get to define how the data transformation operates. (And from this information, we can make decisions that optimize performance when interacting with localStorage.) So ... it seems wiser for now to only support localStorage.
If users truly needed something more, we could add it. However, it would probably make more sense for the end user to create their own solution. More than likely, the end user will have a single preferred way of storing form data across their application. (Consistency is incredibly valuable.) Thus, if for any reason something other than localStorage was needed (again, highly unlikely), then the user could extend FormObserver to create something similar to FormStorageObserver -- but for their specific use case. The public code in the FormStorageObserver file should make it clear how to go about that. (This is another reason to keep FormStorageObserverclear and maintainable).
For now, we shouldn't overcomplicate things if localStorage is what will be used by most people for form data storage.
Additional Thoughts
Why Aren't You Supporting sessionStorage?
Because sessionStorage and localStorage behave the exact same way when it comes to their setItem, getItem, and removeItem methods, it would be possible for FormStorageObserver to allow the end developer to choose which kind of storage they used. However, I consider this impractical for 3 reasons:
First: The user experience is better with localStorage. If a user is working through a long/complex form and they are forced to close the browser for any reason (or perhaps they accidentally close their tab/browser), they will lose all of their progress if sessionStorage is used. Not so with localStorage. Thus, why bother with sessionStorage if localStorage provides a better user experience?
Second: We lose the benefit of static methods/functions if we support multiple storage options. When we choose to exclusively use localStorage, we can simplify things by creating local functions and static class methods that are only defined once. But if the end developer starts dictating what kind of storage is used, that information will need to be passed in as an argument to the constructor. This ultimately means that our methods will need to refer to something like this.#storage instead of window.localStorage, which in turn means that what could have been a static method will need to be changed into an instance method, which in turn means that we'll be recreating functions that effectively do the exact same thing for each instance of FormStorageObserver. Seems wasteful, doesn't it?
Third: If the life of the form data is a concern, the developer will need to take responsibility for it anyway. Although it would be a little odd, someone could argue that using sessionStorage makes life easier because the form data would automatically be cleared when the browser tab is closed. However, it doesn't make sense to leave a user's storage cluttered with obsolete data for any period of time. As soon as the form data becomes irrelevant, it should be removed. This benefits the users of our web apps, and it likely shields us developers from potential sources of unnecessary confusion. Whether localStorage or sessionStorage is used, this cleanup will have to be done; so we may as well keep things simple by keeping to one approach over the other (in light of the earlier points).
Key Design Decision: Only Allow
FormStorageObserver
to SupportlocalStorage
This implementation of
FormStorageObserver
is a divergence from what I originally had in mind for the class.Originally, my hope was for the
FormStorageObserver
class to support all kinds ofform
data storage. By default, the class would uselocalStorage
to storeform
data; but the user would be able to supply any custom interface that had thesetItem
,getItem
, andremoveItem
methods -- just likelocalStorage
. This way, they'd be able to storeform
data any way they wanted (for instance, in an in-memory object) -- not just withlocalStorage
. Unfortunately, after trying to work with the idea, I concluded that it wasn't practical or worthwhile, and I abandoned the idea entirely.When it comes to storing or retrieving
form
data, we need to know the kind of data being stored because this influences how the data is transformed during data storage/retrieval. That is, we need to know "Is this data for a checkbox?" ("Should I store this as a stringifiedboolean
?"), "Is it data for a multi-select?" ("Should I store this as a stringifiedarray
?"), "Is it data for a regular text input?" ("Should I store this as a stringifiedstring
[sic]?"), etc. This information is necessary to make sure that the form fields are correctly updated whenever data is loaded from the "storage" into the actual fields. For instance, a checkbox will need itschecked
property/attribute updated with a validboolean
during loading, whereas a regular text input will need itsvalue
property/attribute udpated with a validstring
.Additionally, the
setItem
,getItem
, andremoveItem
methods of thelocalStorage
interface expect to be working with stringified data. But perhaps not everyone wants to work with stringified data. For instance, maybe they want thesetItem
method to accept a generic data type instead of just astring
. (In other words, perhaps they want to store and retrieve data as a literalboolean
instead of as a stringifiedboolean
.)This situation brings up a lot of questions if we want users to be able to store data in different ways. Do we want users to be able to supply "transformers" for data storage/retrieval? How do we enforce that the end users write "transformers" that take into account the types of data being stored (checkbox data, mutli-select data, text field data, etc.)? Do we want users to only be able to store stringified data (as in the case of
localStorage
), or do we want to support other types of data? Does the end user even want to be bothered with these concerns? How will ourFormStorageObserver
class handle these things in a clear and performant way without sacrificing the code's maintainability?The questions go on and on. And although valid solutions exist, trying to handle these cases would ultimately make
FormStorageObserver
slower and more difficult to maintain. Moreoever, it's highly unlikely that a significant number of people are going to be desperate for a storage option besideslocalStorage
. Most folks just want an easy way to throwform
data intolocalStorage
. That's it. It's sufficient to support that instead of diving into impractical use cases.A big benefit of making a
FormStorageObserver
that only supportslocalStorage
is that in some cases we can leverage local functions orstatic
class methods that are only defined once. (This should save on memory per each instance ofFormStorageObserver
.) Another big benefit is that the code remains very simple since we get to define how the data transformation operates. (And from this information, we can make decisions that optimize performance when interacting withlocalStorage
.) So ... it seems wiser for now to only supportlocalStorage
.If users truly needed something more, we could add it. However, it would probably make more sense for the end user to create their own solution. More than likely, the end user will have a single preferred way of storing
form
data across their application. (Consistency is incredibly valuable.) Thus, if for any reason something other thanlocalStorage
was needed (again, highly unlikely), then the user could extendFormObserver
to create something similar toFormStorageObserver
-- but for their specific use case. The public code in theFormStorageObserver
file should make it clear how to go about that. (This is another reason to keepFormStorageObserver
clear and maintainable).For now, we shouldn't overcomplicate things if
localStorage
is what will be used by most people forform
data storage.Additional Thoughts
Why Aren't You Supporting
sessionStorage
?Because
sessionStorage
andlocalStorage
behave the exact same way when it comes to theirsetItem
,getItem
, andremoveItem
methods, it would be possible forFormStorageObserver
to allow the end developer to choose which kind of storage they used. However, I consider this impractical for 3 reasons:First: The user experience is better with
localStorage
. If a user is working through a long/complex form and they are forced to close the browser for any reason (or perhaps they accidentally close their tab/browser), they will lose all of their progress ifsessionStorage
is used. Not so withlocalStorage
. Thus, why bother withsessionStorage
iflocalStorage
provides a better user experience?Second: We lose the benefit of static methods/functions if we support multiple storage options. When we choose to exclusively use
localStorage
, we can simplify things by creating local functions and static class methods that are only defined once. But if the end developer starts dictating what kind of storage is used, that information will need to be passed in as an argument to the constructor. This ultimately means that our methods will need to refer to something likethis.#storage
instead ofwindow.localStorage
, which in turn means that what could have been a static method will need to be changed into an instance method, which in turn means that we'll be recreating functions that effectively do the exact same thing for each instance ofFormStorageObserver
. Seems wasteful, doesn't it?Third: If the life of the
form
data is a concern, the developer will need to take responsibility for it anyway. Although it would be a little odd, someone could argue that usingsessionStorage
makes life easier because theform
data would automatically be cleared when the browser tab is closed. However, it doesn't make sense to leave a user's storage cluttered with obsolete data for any period of time. As soon as theform
data becomes irrelevant, it should be removed. This benefits the users of our web apps, and it likely shields us developers from potential sources of unnecessary confusion. WhetherlocalStorage
orsessionStorage
is used, this cleanup will have to be done; so we may as well keep things simple by keeping to one approach over the other (in light of the earlier points).