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();