Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.javascript > #41453 > unrolled thread
| Started by | luserdroog <luser.droog@gmail.com> |
|---|---|
| First post | 2023-10-08 18:15 -0700 |
| Last post | 2023-10-20 10:39 -0700 |
| Articles | 9 — 3 participants |
Back to article view | Back to comp.lang.javascript
Code review: personnel scheduling app luserdroog <luser.droog@gmail.com> - 2023-10-08 18:15 -0700
Re: Code review: personnel scheduling app Julio Di Egidio <julio@diegidio.name> - 2023-10-08 19:53 -0700
Re: Code review: personnel scheduling app luserdroog <luser.droog@gmail.com> - 2023-10-10 06:00 -0700
Re: Code review: personnel scheduling app Julio Di Egidio <julio@diegidio.name> - 2023-10-10 06:55 -0700
Re: Code review: personnel scheduling app "Michael Haufe (TNO)" <tno@thenewobjective.com> - 2023-10-14 11:17 -0700
Re: Code review: personnel scheduling app luserdroog <luser.droog@gmail.com> - 2023-10-18 07:47 -0700
Re: Code review: personnel scheduling app "Michael Haufe (TNO)" <tno@thenewobjective.com> - 2023-10-18 10:22 -0700
Re: Code review: personnel scheduling app "Michael Haufe (TNO)" <tno@thenewobjective.com> - 2023-10-18 10:32 -0700
Re: Code review: personnel scheduling app luserdroog <luser.droog@gmail.com> - 2023-10-20 10:39 -0700
| From | luserdroog <luser.droog@gmail.com> |
|---|---|
| Date | 2023-10-08 18:15 -0700 |
| Subject | Code review: personnel scheduling app |
| Message-ID | <963227be-2594-4ae3-8d4c-4b65b3d3e142n@googlegroups.com> |
This a single-page web app for viewing and manipulating the daily schedule of people for a business like a restaurant or a multi-restaurant theme park. It evolved from a very stupid simple bash script but ultimately I thought it would be easier to spend weeks and months to rewrite it in html/javascript rather than trying to teach my coworkers how to use termux and emacs on a phone. They're not computer people, just regular people. So after 3 re-writes, it seems to be mostly in a workable form but with bugs and stupids a-plenty I'm sure. The UI is organized as an area with expandable controls, kinda like menus, and the whole control area can be collapsed down to the date to make a more printable page. In the "file" controls there is a text box where you can paste text of the daily schedule copied from paycor.com (previous versions worked with data from whentowork.com) -- but note that the parser uses a hard-coded key based on our company name which is present in the text so the program will not be usable as-is for this function without modification for the specific paycor client. Once it's loaded with data, the app should provide intuitive editing, sorting, filtering and writing little text notes attached to persons or locations. There is also an experimental section allowing customization of extra fields in the personnel data beyond the required 5 fields which are: location, position, name, start time, end time. You can use this link to see the program in action upon a fictious set of people and locations: https://luser-dr00g.github.io/cm-pc.html?q=data+for+Friday+July+1%2C+2023%0Acustom+fields%3A+%5B%7B%22here%22%3A%22clickbox%22%7D%2C%7B%22notes%22%3A%22text%22%7D%5D%0Astation+notes%3A+%7B%7D%0ADesk+Concierge%0A%09M.+Myster%0910%3Aam-5%3A30pm%09%09%0AFood+Service+Corkscrew+Bar%0A%09Screwball+Sam+McKenzie%0910am-6pm%09%09%0AFood+Service+Even+Sillier+Place+Job%0A%09Firstname+Lastname%099%3A30am-4pm%09%09%0AFood+Service+Pie+in+the+Sky+FOH%0A%09Daisy+Dukes%099%3A30am-4pm%09%09%0A%09Kandoo+Candide%093pm-6pm%09%09%0AFood+Service+Pie+in+the+Sky+BOH%0A%09Piper+Prawne%099%3A30am-4pm%09%09%0A%09Someother+Name%093pm-6pm%09%09%0AOperations+Floor+walker%0A%09Pied+Piermont%0911am-4pm%09%09%0ARetail+Gift+Shop%0A%09Matron+Marms%099am-6pm%09%09%0AServices+Groundskeeping%0A%09That+old+dude%095pm-5%3A15pm%09%09 The code is viewable from github. The html/css/javascript is in one file: https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/cm-pc.html and the separate components: https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/click.js https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/more.js https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/tabs.js zip (whole project, more than these files): https://github.com/luser-dr00g/luser-dr00g.github.io/archive/ebe90e12ce13df2ffd96385552f744dc9430d7c7.zip The code is organized (loosely) around the MVVC design. A few global variables hold the "Model" data. The output() function renders this data into HTML structures populated with event handlers. Any event handler which wants to alter the model has to call one of the Model's interface functions which first creates an Undo/Redo record for the change and then does a Redo (albeit for the first time this time). So, it's my attempt to do an MVC design but with all Procedural Programming. There's barely any OO except for using a class to make a web component like you have to do. I have a sense that maybe I've been avoiding OO too much and judicious sprinkling of it might help. Any critique or other comments welcome, even from Julio. Earlier versions of the web components have been discussed in previous threads started by me. -- Additional sentence that is very curt and declarative.
[toc] | [next] | [standalone]
| From | Julio Di Egidio <julio@diegidio.name> |
|---|---|
| Date | 2023-10-08 19:53 -0700 |
| Message-ID | <9ec432b3-bbf4-4e17-9b89-6745042d5c46n@googlegroups.com> |
| In reply to | #41453 |
On Monday, 9 October 2023 at 03:15:41 UTC+2, luserdroog wrote: <snip> > <https://github.com/luser-dr00g/luser-dr00g.github.io> > > The code is organized (loosely) around the MVVC design. > A few global variables hold the "Model" data. The output() > function renders this data into HTML structures populated > with event handlers. Any event handler which wants to alter the > model has to call one of the Model's interface functions > which first creates an Undo/Redo record for the change > and then does a Redo (albeit for the first time this time). Which suggests you are rather lumping model an controller. But the code is too unstructured to look at (unless you pay me for a couple of days of re-engineering): 2K+ lines of lumped HTML+CSS+JS is simply not good enough. I'd advise you to start by separating things... and while you are at it, review all naming and code structure. > So, it's my attempt to do an MVC design It isn't: indeed nobody can understand MVC who has not actually tried to build a project with explicit model, view and controller components. > Any critique or other comments welcome, even from Julio. You must be kidding. HTH, Julio
[toc] | [prev] | [next] | [standalone]
| From | luserdroog <luser.droog@gmail.com> |
|---|---|
| Date | 2023-10-10 06:00 -0700 |
| Message-ID | <eaeb8578-aca2-41bb-b1cc-cb3f77182543n@googlegroups.com> |
| In reply to | #41454 |
On Sunday, October 8, 2023 at 9:53:58 PM UTC-5, Julio Di Egidio wrote: > On Monday, 9 October 2023 at 03:15:41 UTC+2, luserdroog wrote: > <snip> > > <https://github.com/luser-dr00g/luser-dr00g.github.io> > > > > The code is organized (loosely) around the MVVC design. > > A few global variables hold the "Model" data. The output() > > function renders this data into HTML structures populated > > with event handlers. Any event handler which wants to alter the > > model has to call one of the Model's interface functions > > which first creates an Undo/Redo record for the change > > and then does a Redo (albeit for the first time this time). > Which suggests you are rather lumping model an controller. > But the code is too unstructured to look at (unless you pay > me for a couple of days of re-engineering): 2K+ lines of > lumped HTML+CSS+JS is simply not good enough. I'd > advise you to start by separating things... and while you > are at it, review all naming and code structure. > > So, it's my attempt to do an MVC design > It isn't: indeed nobody can understand MVC who has not > actually tried to build a project with explicit model, view > and controller components. Sigh. I suppose you're right. It kind of has these components "spiritually" but not explicitly. All of my design work is done on paper. So there are several sketches with the Model, View and Controller parts as separate boxes with arrows connecting them. But then I just wrote functions for all the arrows and the larger structure vanished. Plus, several of my functions are really, really long with deep nesting (taking full advantage of my 2 space indents) and still have to do crazy hand formatting to squeeze everything in. ach. oop.
[toc] | [prev] | [next] | [standalone]
| From | Julio Di Egidio <julio@diegidio.name> |
|---|---|
| Date | 2023-10-10 06:55 -0700 |
| Message-ID | <54fd401e-59ac-44a3-86e6-d3eab0424858n@googlegroups.com> |
| In reply to | #41457 |
On Tuesday, 10 October 2023 at 15:00:07 UTC+2, luserdroog wrote: > On Sunday, October 8, 2023 at 9:53:58 PM UTC-5, Julio Di Egidio wrote: > Sigh. I suppose you're right. It kind of has these components > "spiritually" but not explicitly. No, it doesn't, and I have said how: and that you can't see it, not even parse my reply, until you try and do it. And it won't take just one attempt, rather years... Your code is somewhat cleaner than what I remember from few years ago, though far from perfect, but for the rest, now as then, you seem committed to remain the same crank. EOD. Julio
[toc] | [prev] | [next] | [standalone]
| From | "Michael Haufe (TNO)" <tno@thenewobjective.com> |
|---|---|
| Date | 2023-10-14 11:17 -0700 |
| Message-ID | <32090dcf-befa-48d0-b98a-0618b236d934n@googlegroups.com> |
| In reply to | #41453 |
On Sunday, October 8, 2023 at 8:15:41 PM UTC-5, luserdroog wrote:
>
> [...]
>
> The code is viewable from github.
> The html/css/javascript is in one file:
> https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/cm-pc.html
> and the separate components:
> https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/click.js
> https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/more.js
> https://github.com/luser-dr00g/luser-dr00g.github.io/blob/ebe90e12ce13df2ffd96385552f744dc9430d7c7/tabs.js
>
> zip (whole project, more than these files):
> https://github.com/luser-dr00g/luser-dr00g.github.io/archive/ebe90e12ce13df2ffd96385552f744dc9430d7c7.zip
>
>
> The code is organized (loosely) around the MVVC design.
> A few global variables hold the "Model" data. The output()
> function renders this data into HTML structures populated
> with event handlers. Any event handler which wants to alter the
> model has to call one of the Model's interface functions
> which first creates an Undo/Redo record for the change
> and then does a Redo (albeit for the first time this time).
>
> So, it's my attempt to do an MVC design but with all Procedural
> Programming. There's barely any OO except for using a class
> to make a web component like you have to do. I have a sense
> that maybe I've been avoiding OO too much and judicious
> sprinkling of it might help.
>
> Any critique or other comments welcome, even from Julio.
> Earlier versions of the web components have been
> discussed in previous threads started by me.
To quote Kent Beck: "Make it work, make it right, make it fast"
You've finished step 1, so iteration and improvement should be straightforward.
Avoiding OO/modules can work but makes things progressively harder as the codebase size increases as everything is in the same namespace (informally speaking). Some functions are more related to each other than others so defining an explicit grouping and naming them gives you power over them and an organizing principle. An object/class/module accomplish this.
I'd suggest improving the separation of concerns as the intent of the code is non-obvious unless you run the application.
1. separate out your domain.
What are the abstract, platonic concepts of the application divorced from the interface and how you're storing them?
From what I can see you have: "Station" as the the lone top level entity and a number of supporting entities:
// TypeScript
class Station {
location: Location
position: Position
name: string
timeSpan: TimeSpan
presence: boolean
notes: string
}
enum Location { ... }
enum Position { ... }
2. Separate out your data access
The responsibility of loading/saving data should be separated from other concerns. Define and use the Repository pattern for each primary entity:
abstract class Repository<T> {
getById(id: string): Promise<T>
getAll(): Promise<T[]>
update(item: T): Promise<void>
save(item: T): Promise<void>
delete(id: string): Promise<void>
}
class LocationRepository extends Repository<Location> {
/* manipulate localstorage and such. You can also perform the mappings to/from JSON here */
}
3. Explicitly define your use cases.
The application you're writing should be ignorant of how data is stored and what the UI looks like. By defining explicit use cases you can obtain
"Screaming Architecture". In other words by simply looking at the project we know exactly what it's doing by only glancing at the code and not running it.
A Use Case is either a Command or a Query and can be build via the Command Pattern:
abstract class UseCase<T,U> {
abstract exec(t: T): U
}
abstract class Command<T> extends UseCase<T, void> {}
abstract class Query<U> extends UseCase<void, U> {}
This enforces the CQS principle <https://en.wikipedia.org/wiki/Command%E2%80%93query_separation>
An example usage:
class ShowStationsUseCase extends Query<Station[]> {
constructor(
private repo: Repository<Station>
) { super() }
exec(): Station[] {
const stations = this.repo.getAll()
// sort or whatever data massaging you want
return stations
}
}
4. The presentation layer utilizes use cases instead of mixing business logic and such as it is now:
class MyStationListingComponent {
repository = new StationRepository()
useCase = new ShowStationsUseCase(this.repository)
...
constructor() {
super()
// in constructor, or maybe in a callback method:
const stations = this.useCase.exec()
this.present(stations)
}
}
This is a very impressionistic set of suggestions and is a variant of what is referred to as "Clean Architecture" which unifies the "Hexagonal" and "Onion" architectures:
<https://www.plainionist.net/Implementing-Clean-Architecture/>
[toc] | [prev] | [next] | [standalone]
| From | luserdroog <luser.droog@gmail.com> |
|---|---|
| Date | 2023-10-18 07:47 -0700 |
| Message-ID | <125fd145-4617-479a-a40b-7ce1aa3c74f4n@googlegroups.com> |
| In reply to | #41464 |
On Saturday, October 14, 2023 at 1:17:33 PM UTC-5, Michael Haufe (TNO) wrote:
> On Sunday, October 8, 2023 at 8:15:41 PM UTC-5, luserdroog wrote:
[snip]
>
> This is a very impressionistic set of suggestions and is a variant of what is referred to as "Clean Architecture" which unifies the "Hexagonal" and "Onion" architectures:
>
> <https://www.plainionist.net/Implementing-Clean-Architecture/>
Thanks a bunch. I think this is exactly what I needed. I feel like we're
all on the same page, although to me it's still a palimpsest.
As a start, and because my bias is typically from the low-level view,
I've tried to carve off a tiny component just to deal with saving and
loading user preferences tied to a control like a checkbox.
For the larger task, I'm finding it very difficult to imagine the thing
from a top-down Object Oriented view ignoring concerns about
the representation. But I expect this will become easier after more
reading about these patterns (searching like "repository vanilla js",
"usecase presentation vanilla js", etc). Again, thank you. I feel like
I've caught the scent.
html:
<!DOCTYPE html>
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
<html>
<body>
<input id="box" type="checkbox">
</body>
<script src="app.js"></script>
</html>
app.js:
const prefs = persistent( localStorage, "prefs", {} );
init();
function init(){
bindCheckboxToPrefs( "#box", "checked" );
}
function bindCheckboxToPrefs( selector, propName ){
let input = document.querySelector( selector );
if( prefs.hasOwnProperty( propName ) )
input.checked = prefs[ propName ];
input.addEventListener( "click",
(event) => prefs[ propName ] = event.target.checked );
}
function persistent( storage, key, initial ){
let data = storage.getItem( key ) ?
JSON.parse( storage.getItem( key ) ) :
initial;
let proxy = new Proxy( data, {
set( target, prop, receiver ){
const ret = Reflect.set( ...arguments );
storage.setItem( key, JSON.stringify( data ) );
return ret;
}
} );
return proxy;
}
[toc] | [prev] | [next] | [standalone]
| From | "Michael Haufe (TNO)" <tno@thenewobjective.com> |
|---|---|
| Date | 2023-10-18 10:22 -0700 |
| Message-ID | <492a2bf7-180d-47ff-903c-f580855c1125n@googlegroups.com> |
| In reply to | #41468 |
On Wednesday, October 18, 2023 at 9:47:49 AM UTC-5, luserdroog wrote: > As a start, and because my bias is typically from the low-level view, > I've tried to carve off a tiny component just to deal with saving and > loading user preferences tied to a control like a checkbox. > > For the larger task, I'm finding it very difficult to imagine the thing > from a top-down Object Oriented view ignoring concerns about > the representation. But I expect this will become easier after more > reading about these patterns (searching like "repository vanilla js", > "usecase presentation vanilla js", etc). Again, thank you. I feel like > I've caught the scent. "Everything should be built top-down, except the first time." - Alan Perlis The reason top-down (Use Case driven) is preferred generally is so you stay on target and constrain yourself to the goal. This can avoid creeping featurism. Also it enables you to create an MVP you can then iterate upon. The comment on "except the first time" I interpret as referring to the need for a well-defined domain (Domain-Driven Design wasn't a thing when Perlis wrote that). Example, I can define some use-cases for a graphics drawing program (create layer, flood, etc), but how far will I get if I don't have a concept of platonic shapes to work with? Each use-case might end up doing it their own way instead of utilizing a common language (Domain of Discourse). I have a couple **draft** blog posts you might find helpful as an analogy: https://github.com/thenewobjective/thenewobjective.github.io/blob/ec8bc7aee3151d8ff300b87e4a0920f542ec94e8/_drafts/tile-graphics/2023-07-15-1-from-domain-to-display.md https://github.com/thenewobjective/thenewobjective.github.io/blob/ec8bc7aee3151d8ff300b87e4a0920f542ec94e8/_drafts/tile-graphics/2023-07-15-1-from-domain-to-display.md What I'm building there isn't important, more-so the shape of the the approach. Each feature is implemented as a vertical slice through the onion: https://github.com/thenewobjective/thenewobjective.github.io/blob/ec8bc7aee3151d8ff300b87e4a0920f542ec94e8/media-library/software-systems-engineering/clean-architecture.png There are other ways to approach this (a hierarchy of "onions" in a PAC/HMVC style), but that's another rabbit hole we'd have to tackle in another thread.
[toc] | [prev] | [next] | [standalone]
| From | "Michael Haufe (TNO)" <tno@thenewobjective.com> |
|---|---|
| Date | 2023-10-18 10:32 -0700 |
| Message-ID | <22921a66-7986-44dd-bfc0-899c9d90460cn@googlegroups.com> |
| In reply to | #41468 |
On Wednesday, October 18, 2023 at 9:47:49 AM UTC-5, luserdroog wrote: > As a start, and because my bias is typically from the low-level view, > I've tried to carve off a tiny component just to deal with saving and > loading user preferences tied to a control like a checkbox. Try to think in terms of "what" you're doing instead of "how". What you've presented are implementation details of storage and presentation, but what's the reason for them? // usecases LoadFilters ApplyFilters ChangeTheme LoadTheme etc.
[toc] | [prev] | [next] | [standalone]
| From | luserdroog <luser.droog@gmail.com> |
|---|---|
| Date | 2023-10-20 10:39 -0700 |
| Message-ID | <87f7da2b-7c41-4f46-b738-79ac0188039fn@googlegroups.com> |
| In reply to | #41470 |
On Wednesday, October 18, 2023 at 12:32:05 PM UTC-5, Michael Haufe (TNO) wrote: > On Wednesday, October 18, 2023 at 9:47:49 AM UTC-5, luserdroog wrote: > > As a start, and because my bias is typically from the low-level view, > > I've tried to carve off a tiny component just to deal with saving and > > loading user preferences tied to a control like a checkbox. > Try to think in terms of "what" you're doing instead of "how". What you've presented are implementation details of storage and presentation, but what's the reason for them? > > // usecases > > LoadFilters > ApplyFilters > ChangeTheme > LoadTheme > etc. Ok. I'm starting to grasp something. This is similar, but not exactly the same, as the many lists in my notebook of "little features" which then translated into snippets of code inserted into the various paths (bloating up various "hub" functions into long fiddly "storybook" code). So instead, all of these fabulous little tweaks and fancies need to be reified, things in their own right, with a name and a ... shudder ... *class*, perhaps. And yes, the Preferences gizmo just sidesteps the whole exercise. Because of course I was going to use it as a grab bag for unrelated things like the filter settings, sorting settings, style settings. Rather each of these: viz. filter, sorting, styles can, and I suppose ought to, be Repositories. And the storage options for it can be more varied than just shipping JSON over to localStorage. Maybe I can make a ComposedRepository to actually wrap these things in a single PreferencesRepository but at a higher level of composition. But, all of these gives me what I want: nice abstractions. Goodly sized/scoped boxes to encapsulate the behavior I want from these conjured entities. I think I'm getting it. So I'm listing out lots of little useCases in english sentences. Then I can refine the list, rewrite it a few times, and appropriate groupings should become apparent as the steps progress. Good thing pens are cheap... And the tempting part is ... with a little more organization, then my fancy gizmos can be even more fancy. Instead of regular expressions matched against predefined fields, I could make a filter builder something like the Scratch language. You create an expression "<field> <operator> <pattern|value>" and compose them together with "and" and "or" and "not", give it a name, manage the list of saved ones.... sigh. baby steps.
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.javascript
csiph-web