• baby steps

    From luserdroog@21:1/5 to All on Thu Dec 16 21:18:12 2021
    I've read through Michael Haufe's example a few more times, and
    re-read the old article by Peter Michaux. And through all of it, I like
    the idea of structuring stuff. The major divisions of responsibility
    make sense. And to a certain extent, the lines of communication
    make sense. But in all of these implementations there feels (to me)
    like a whole lotta stuff I don't need. Or don't need yet. Or don't
    understand. Or something.

    So I've tried to build everything up as simply as I could manage.
    I don't need to dynamically change the list of observers beyond
    adding at least one: so it's just one one-liner function; etc. I did run
    in to one weird corner in trying to make a Button out of a View.
    It doesn't really need a model. But I have to give it some kind of
    model because the constructor needs to add itself as an observer
    to *something*.

    But this feels like progress on the major front. It's a little sloppy
    and free-for-all inside the objects, but everything is *inside* the
    objects! This code doesn't actually draw anything except the
    buttons. And the buttons don't visibly do anything. But importantly
    they do all of this nothing *without errors*.



    class Model {
    constructor(){ this._observers = []; }
    observedBy( observer ){ this._observers.push( observer ); return this; }
    notify( event ){ this._observers.forEach(o=>o.update(event)); }
    }

    class View {
    constructor( model, container ){
    this._model = model.observedBy( this );
    this._container = container;
    this._root = this.initRoot();
    }
    initRoot(){ return document.createElement("div"); }
    render(){ this._container.appendChild( this._root ); }
    update( event ){}
    }

    class Controller {
    constructor(){ }
    }


    class Notes extends Model {
    constructor(){
    super();
    this.data = [];
    }
    sync(){ this.notify({data:this.data}); }
    clear(){
    this.data = [];
    this.sync();
    }
    add( notes ){
    this.data.push(notes);
    this.sync();
    }
    toggle( y, x ){
    if( this.data[ y ].includes( x ) ){
    this.data[ y ] = this.data[ y ].filter( n=> n!=x );
    } else {
    this.data[ y ].push( x );
    }
    this.sync();
    }
    }

    class PianoRollView extends View {
    constructor( model, container ){
    super( model, container );
    }
    initRoot(){ return document.createElement("table"); }
    update( event ){ this.draw( event.data ); }
    draw( notes ){ }
    }

    class PianoPlayer extends View {
    constructor( model, container ){
    super( model, container );
    }
    play( notes ){ }
    }

    class Button extends View {
    constructor( model, label, action, container ){
    super( model, container );
    this._root.textContent = label;
    this._root.onclick = action;
    this.render();
    }
    initRoot(){ return document.createElement("button"); }
    }

    class PianoRoll extends Controller {
    constructor( container ){
    super();
    this.model = new Notes();
    this.view = new PianoRollView( this.model, container );
    this.player = new PianoPlayer( this.model, container );

    var nullModel = new Model();
    var model = this.model;
    var player = this.player;
    this.add = new Button( nullModel,
    'add row',
    ()=>model.add([]),
    container );
    this.play = new Button( nullModel,
    'play',
    ()=>player.play(model.data),
    container );
    this.clear = new Button( nullModel,
    'clear',
    ()=>model.clear(),
    container );
    }
    }

    const piano = new PianoRoll( document.body );

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to luser...@gmail.com on Sat Dec 18 12:14:21 2021
    On Thursday, December 16, 2021 at 11:18:18 PM UTC-6, luser...@gmail.com wrote:
    I've read through Michael Haufe's example a few more times, and
    re-read the old article by Peter Michaux. And through all of it, I like
    the idea of structuring stuff. The major divisions of responsibility
    make sense. And to a certain extent, the lines of communication
    make sense. But in all of these implementations there feels (to me)
    like a whole lotta stuff I don't need. Or don't need yet. Or don't understand. Or something.

    Most likely "don't need yet". MVC is overkill for simplistic pages or things don't really rise to the level of an application.

    Recall that Peter Michaux and I both presented an MVC library in ~100 lines. With such examples they should be treated as impressionistic and incomplete.

    So I've tried to build everything up as simply as I could manage.
    I don't need to dynamically change the list of observers beyond
    adding at least one: so it's just one one-liner function; etc. I did run
    in to one weird corner in trying to make a Button out of a View.
    It doesn't really need a model. But I have to give it some kind of
    model because the constructor needs to add itself as an observer
    to *something*.

    You don't need a model. It could be just the Controller and View.

    But this feels like progress on the major front. It's a little sloppy
    and free-for-all inside the objects, but everything is *inside* the
    objects! This code doesn't actually draw anything except the
    buttons. And the buttons don't visibly do anything. But importantly
    they do all of this nothing *without errors*.

    I'm glad you've made progress with my back-of-napkin example.
    There are plenty of places that could be improved with naming
    conventions, refactoring, utilities, etc.

    One thing I'd consider is to align the obervers with the DOM.
    Since the DOM uses `addEventListener` is might be more
    clear to rename `observedBy` to something similar.

    `container` might be more intuitive being renamed to `parent`

    Should the Notes be responsible for toggling? Would that make more sense in the owning controller?

    Again on the event management side. Note that the DOM's 'addEventListener' can accept an object as its second parameter.
    This could be a good opportunity to simplify all of the event management in general

    An example:

    class Observable {
    // ...
    handleEvent(e) {
    let name = e.type;
    if(this[name]) this[name](e);
    }
    }

    class View extends Observable {
    // ...
    }

    class MyView extends View {
    constructor() {
    super()
    // ...
    this._rootElement.addEventListener('click', this)
    }

    onclick(e) {
    // do something with the click event
    }
    }

    Playing around with Model a little should enable a common approach for both. One challenge being that the DOM elements have a private list of observers
    so some duplication of this list would exist.

    You can see more of this `handleEvent` usage in another example I made for a previous discussion in the group:

    (note: NOT an MVC approach. Just look at the handleEvent usage) <https://codepen.io/mlhaufe/pen/RwKqzvr>

    Thinking about this as I write it: Maybe a weird hack could be
    to have a dummy HTML element in the model just to take advantage
    of its event code. So instead of notifyObservers, you raise the CustomEvent against that dummy element so the same `handleEvent` method is called.

    If I have time this weekend, I'll play with the idea.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julio Di Egidio@21:1/5 to All on Sat Dec 18 13:56:45 2021
    On Saturday, 18 December 2021 at 21:14:27 UTC+1, Michael Haufe (TNO) wrote:

    You don't need a model. It could be just the Controller and View.

    Just the opposite is the case: if you don't need anything, you need a model, otherwise it's a view-model, otherwise it's a model-view-view-mdel, otherwise it's full fledged MVC.

    One thing I'd consider is to align the obervers with the DOM.

    Again, on the contrary: observables are more fundamental and more ubiquitous than any DOM.

    One challenge being that the DOM elements have a private list of observers

    Once challenge is to avoid any such coupling, and that's where one wants a view.

    Thinking about this as I write it: Maybe a weird hack could be
    to have a dummy HTML element in the model just to take advantage
    of its event code.

    From bad to worse: that would very ugly, not just unnecessary and in fact upside down.

    If I have time this weekend, I'll play with the idea.

    Of all the ideas...

    HTH,

    Julio

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to ju...@diegidio.name on Sat Dec 18 15:09:34 2021
    On Saturday, December 18, 2021 at 3:56:49 PM UTC-6, ju...@diegidio.name wrote:
    On Saturday, 18 December 2021 at 21:14:27 UTC+1, Michael Haufe (TNO) wrote:

    You don't need a model. It could be just the Controller and View.
    Just the opposite is the case: if you don't need anything, you need a model, otherwise it's a view-model, otherwise it's a model-view-view-mdel, otherwise it's full fledged MVC.

    We're referring to a button. A model gives you nothing there.

    One thing I'd consider is to align the obervers with the DOM.
    Again, on the contrary: observables are more fundamental and more ubiquitous than any DOM.

    It's not contrary, it's orthogonal. Right now he has to manage observers himself in the model, but the DOM
    is managing them in the view. There are two different approaches to managing these right now. These should
    be normalized. Since you can't avoid using the DOM here one might as well bring the other approach into alignment.

    One challenge being that the DOM elements have a private list of observers
    Once challenge is to avoid any such coupling, and that's where one wants a view.
    Thinking about this as I write it: Maybe a weird hack could be
    to have a dummy HTML element in the model just to take advantage
    of its event code.
    From bad to worse: that would very ugly, not just unnecessary and in fact upside down.
    If I have time this weekend, I'll play with the idea.
    Of all the ideas...

    You're free to present something better.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julio Di Egidio@21:1/5 to All on Sun Dec 19 02:40:19 2021
    On Sunday, 19 December 2021 at 00:09:39 UTC+1, Michael Haufe (TNO) wrote:
    On Saturday, December 18, 2021 at 3:56:49 PM UTC-6, ju...@diegidio.name wrote:
    On Saturday, 18 December 2021 at 21:14:27 UTC+1, Michael Haufe (TNO) wrote:

    You don't need a model. It could be just the Controller and View.
    Just the opposite is the case: if you don't need anything, you need a model, otherwise it's a view-model, otherwise it's a model-view-view-mdel, otherwise it's full fledged MVC.
    We're referring to a button. A model gives you nothing there.
    One thing I'd consider is to align the obervers with the DOM.
    Again, on the contrary: observables are more fundamental and more ubiquitous than any DOM.
    It's not contrary, it's orthogonal. Right now he has to manage observers himself in the model, but the DOM
    is managing them in the view. There are two different approaches to managing these right now. These should
    be normalized. Since you can't avoid using the DOM here one might as well bring the other approach into alignment.
    One challenge being that the DOM elements have a private list of observers
    Once challenge is to avoid any such coupling, and that's where one wants a view.
    Thinking about this as I write it: Maybe a weird hack could be
    to have a dummy HTML element in the model just to take advantage
    of its event code.
    From bad to worse: that would very ugly, not just unnecessary and in fact upside down.
    If I have time this weekend, I'll play with the idea.
    Of all the ideas...
    You're free to present something better.

    As much as you fraudulent spammers are free to get the fuck out of here.

    *Plonk*

    Julio

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luserdroog@21:1/5 to All on Sun Dec 19 10:05:33 2021
    On Saturday, December 18, 2021 at 2:14:27 PM UTC-6, Michael Haufe (TNO) wrote:
    On Thursday, December 16, 2021 at 11:18:18 PM UTC-6, luser...@gmail.com wrote:
    I've read through Michael Haufe's example a few more times, and
    re-read the old article by Peter Michaux. And through all of it, I like
    the idea of structuring stuff. The major divisions of responsibility
    make sense. And to a certain extent, the lines of communication
    make sense. But in all of these implementations there feels (to me)
    like a whole lotta stuff I don't need. Or don't need yet. Or don't understand. Or something.
    Most likely "don't need yet". MVC is overkill for simplistic pages or things don't really rise to the level of an application.

    Recall that Peter Michaux and I both presented an MVC library in ~100 lines. With such examples they should be treated as impressionistic and incomplete.
    So I've tried to build everything up as simply as I could manage.
    I don't need to dynamically change the list of observers beyond
    adding at least one: so it's just one one-liner function; etc. I did run
    in to one weird corner in trying to make a Button out of a View.
    It doesn't really need a model. But I have to give it some kind of
    model because the constructor needs to add itself as an observer
    to *something*.
    You don't need a model. It could be just the Controller and View.

    Hmm. I don't quite follow that. Conceptually yes, but the way I've
    implemented it, the constructor for the View superclass expects
    a Model (something that implements observedBy()). But I tucked it
    away inside the Button constructor now.

    But this feels like progress on the major front. It's a little sloppy
    and free-for-all inside the objects, but everything is *inside* the objects! This code doesn't actually draw anything except the
    buttons. And the buttons don't visibly do anything. But importantly
    they do all of this nothing *without errors*.
    I'm glad you've made progress with my back-of-napkin example.
    There are plenty of places that could be improved with naming
    conventions, refactoring, utilities, etc.

    One thing I'd consider is to align the obervers with the DOM.
    Since the DOM uses `addEventListener` is might be more
    clear to rename `observedBy` to something similar.

    Worth considering. I like the "adjective" name as being more functional
    than a "verb" name, but maybe that's not the most important factor here.

    `container` might be more intuitive being renamed to `parent`

    Yeah, that's fewer letters too.

    Should the Notes be responsible for toggling? Would that make more sense in the owning controller?

    Hmm. Maybe. The click event that initiates it should probably route
    through the controller. But I strongly see it as an operation on the model which should be part of the model's interface.

    Again on the event management side. Note that the DOM's 'addEventListener' can accept an object as its second parameter.
    This could be a good opportunity to simplify all of the event management in general

    An example:
    [snip interesting event handling example]

    I finished fleshing out the code to the same level of functionality as the previous
    It relies on the scales and running_sum() functions from my other thread in a file called fim.js. Plus an additional scale
    const pentatonic = [ 2, 3, 2, 2, 3 ]; // c#..d#..f#..g#..a#..(c#)
    used for painting the black keys on the keyboard.

    The way I have it set up, the PianorollView builds all the functions
    that call model.toggle(). And the View doesn't have knowledge of the Controller, but it does have a pointer to the Model. The Controller
    includes the others by composition rather than inheritance.


    <html><!-- fim.html -->
    <head>
    <meta charset="utf-8" />
    <style>
    .pianoroll table { table-layout: fixed;
    border-collapse: collapse;
    border: 1px solid;
    cursor: pointer;
    }
    .pianoroll td { border: 1px solid;
    padding: 0; }
    .pianoroll tr.heavy td.black:hover { color: red; background-color: red; }
    .pianoroll td.black:hover { color: blue; background-color: blue; }
    .pianoroll td:hover { color: red; background-color: red; }
    .heavy { border: 2.5px solid; }
    .black { background-color: black; }
    </style>
    </head>
    <body>
    </body>
    <script src="fim.js"></script> <!--scales and chords-->
    <script src="mvc.js"></script> <!--mvc piano roll-->
    </html>


    // mvc.js

    class Model {
    constructor(){ this._observers = []; }
    observedBy( observer ){ this._observers.push( observer ); return this; }
    notify( event ){ this._observers.forEach(o=>o.update(event)); }
    }

    class View {
    constructor( model, container ){
    this._model = model.observedBy( this );
    this._container = container;
    this._root = this.initRoot();
    }
    initRoot(){ return document.createElement("div"); }
    render(){ this._container.appendChild( this._root ); }
    update( event ){}
    }

    class Controller {
    constructor(){ }
    }


    class Notes extends Model {
    constructor(){
    super();
    this.data = [];
    }
    sync(){ this.notify({data:this.data}); }
    clear(){
    this.data = [];
    this.sync();
    }
    add( notes ){
    this.data.push(notes);
    this.sync();
    }
    toggle( y, x ){
    if( this.data[ y ].includes( x ) ){
    this.data[ y ] = this.data[ y ].filter( n=> n!=x );
    } else {
    this.data[ y ].push( x );
    }
    this.sync();
    }
    }

    class PianoRollView extends View {
    constructor( model, container ){
    super( model, container );
    }
    initRoot(){ return document.createElement("table"); }
    update( event ){ this.draw( event.data ); }
    draw( notes ){
    var table = document.createElement("table");
    var row = this.row;
    var cell = this.cell;
    var black = this.black;
    var toggle = this.toggle;
    var model = this._model;
    notes.forEach( function( n, idx ){
    table.appendChild( row( n, idx, toggle, model, cell, black ) );
    });
    table.appendChild( this.keyboard() );
    this._container.removeChild( this._root );
    this._root = table;
    this.render();
    }
    toggle( model, i, j ){
    return ()=> model.toggle( i, j );
    }
    add( model, i, j ){
    return ()=> model.add( [j] );
    }
    keyboard(){
    return this.heavy( this.row( running_sum( repeat( octaves, pentatonic ) )
    .map( x=> x+1 ),
    0, this.add, this._model, this.cell, this.black ) );
    }
    row( pitches, idx, action, model, cell, black ){
    pitches.sort( (x,y)=> x<y?-1 :x==y?0 :1 );
    var tr = document.createElement("tr");
    var i = 0;
    pitches.forEach( p=>{
    for( ; i < p && i < octaves*12; i++ )
    tr.appendChild( cell( action, model, idx, i ) );
    if( i >= octaves*12 ) return;
    tr.appendChild( black( cell( action, model, idx, i++ ) ) );
    });
    for( ; i < octaves*12; i++ )
    tr.appendChild( cell( action, model, idx, i ) );
    return tr;
    }
    black( thing ){ thing.className = 'black'; return thing; }
    heavy( thing ){ thing.className = 'heavy'; return thing; }
    cell( action, model, i, j ){
    var td = document.createElement("td");
    var a = document.createElement("a");
    td.appendChild( a );
    var content = document.createTextNode("\u00A0"); //nbsp
    a.appendChild( content );
    a.onclick = action( model, i, j );
    return td;
    }
    }

    class PianoPlayer extends View {
    constructor( model, container ){
    super( model, container );
    }
    play( notes ){ }
    }

    class Button extends View {
    constructor( label, action, container ){
    super( new Model(), container );
    this._root.textContent = label;
    this._root.onclick = action;
    this.render();
    }
    initRoot(){ return document.createElement("button"); }
    }

    class PianoRoll extends Controller {
    constructor( container = document.body ){
    super();
    var a = document.createElement("div");
    a.className = 'pianoroll';
    container.appendChild(a);
    this.model = new Notes();
    this.view = new PianoRollView( this.model, a );
    this.view.render();
    this.model.sync();
    this.player = new PianoPlayer( this.model, a );

    var b = document.createElement("div");
    container.appendChild(b);
    var model = this.model;
    var player = this.player;
    this.add = new Button( 'add row', ()=>model.add([]), b );
    this.play = new Button( 'play', ()=>player.play(model.data), b );
    this.clear = new Button( 'clear', ()=>model.clear(), b );
    }
    }

    const octaves = 4;
    const piano = new PianoRoll();

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