• Code review: personnel scheduling app

    From luserdroog@21:1/5 to All on Sun Oct 8 18:15:36 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julio Di Egidio@21:1/5 to luserdroog on Sun Oct 8 19:53:50 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luserdroog@21:1/5 to Julio Di Egidio on Tue Oct 10 06:00:00 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julio Di Egidio@21:1/5 to luserdroog on Tue Oct 10 06:55:37 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to luserdroog on Sat Oct 14 11:17:28 2023
    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/>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luserdroog@21:1/5 to All on Wed Oct 18 07:47:44 2023
    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;
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to luserdroog on Wed Oct 18 10:32:00 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to luserdroog on Wed Oct 18 10:22:26 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luserdroog@21:1/5 to All on Fri Oct 20 10:39:00 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)