Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.javascript > #41453 > unrolled thread

Code review: personnel scheduling app

Started byluserdroog <luser.droog@gmail.com>
First post2023-10-08 18:15 -0700
Last post2023-10-20 10:39 -0700
Articles 9 — 3 participants

Back to article view | Back to comp.lang.javascript


Contents

  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

#41453 — Code review: personnel scheduling app

Fromluserdroog <luser.droog@gmail.com>
Date2023-10-08 18:15 -0700
SubjectCode 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]


#41454

FromJulio Di Egidio <julio@diegidio.name>
Date2023-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]


#41457

Fromluserdroog <luser.droog@gmail.com>
Date2023-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]


#41458

FromJulio Di Egidio <julio@diegidio.name>
Date2023-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]


#41464

From"Michael Haufe (TNO)" <tno@thenewobjective.com>
Date2023-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]


#41468

Fromluserdroog <luser.droog@gmail.com>
Date2023-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]


#41469

From"Michael Haufe (TNO)" <tno@thenewobjective.com>
Date2023-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]


#41470

From"Michael Haufe (TNO)" <tno@thenewobjective.com>
Date2023-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]


#41472

Fromluserdroog <luser.droog@gmail.com>
Date2023-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