Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

baby steps

62 views
Skip to first unread message

luserdroog

unread,
Dec 17, 2021, 12:18:18 AM12/17/21
to
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 );

Michael Haufe (TNO)

unread,
Dec 18, 2021, 3:14:27 PM12/18/21
to
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.

Julio Di Egidio

unread,
Dec 18, 2021, 4:56:49 PM12/18/21
to
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

Michael Haufe (TNO)

unread,
Dec 18, 2021, 6:09:39 PM12/18/21
to
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.

Julio Di Egidio

unread,
Dec 19, 2021, 5:40:24 AM12/19/21
to
As much as you fraudulent spammers are free to get the fuck out of here.

*Plonk*

Julio

luserdroog

unread,
Dec 19, 2021, 1:05:38 PM12/19/21
to
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
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();
0 new messages