karimayachi / karimayachi.github.io

MIT License
2 stars 0 forks source link

What does it mean to have a MVVM style library? #1

Open karimayachi opened 4 years ago

karimayachi commented 4 years ago

Taken from Knockout:

Not taken from Knockout:

avickers commented 4 years ago

I agree with all of those things, and implemented them in Knockdown.

Where I deviated was shifting some of the what buiilds on top of that to the VM.

For instance, KO uses compound bindings in certain cases

<div data-bind="style: { margin: myObs }"></div>

The way that it is currently implemented in KD is a bit different:

<div data-bind="style: myObs"></div>
const vm = {
   myObs: ko.observable('1em').target('margin')
}

It's an attempt at a more fluent API. I expect pushback from the KO community on this, so I'll be interested to hear what you think.

And, you likely noticed in my Todo List example that ko.observableArray() could chain both a template() and an emptyTemplate() method, which accept either WC constructors or strings that function a lot like KO templates (they understand $data for instance).

But the logic here again is inside the VM in a fluent API rather than the view, where you'd need to perform some sort of case binding to determine whether the OA was empty and, therefore, decide what to render.

I can see an argument for going either way, so I chose the one that was easier to implement and would provide superior performance. If you are using different files for your components, the way that KO did it was kind of nice because you could basically read the HTML and figure out exactly what was going on, even if it was a little fuzzy on the SOC. OTOH, the more modern approach is for single file components, where this benefit isn't entirely lost by pushing the logic into the VM because they will typically be immediately adjacent to one another in the same file. But I won't be terribly surprised if no one else shares this PoV. :)

karimayachi commented 4 years ago

I like fluent API a lot. I'm a bit reluctant in this case though. From an MVVM / SoC stand the HTML should be a readable as possible and the ViewModel should not be concerned with View-specifics as margins, etc.

Ideally the VM only concerns itself with functionality and the View only with how it's presented. I generally use natural language / understandble English to 'communicate' between the two. Something like:

<div data-bind="style: { margin: isSuperImportant ? '2em' : '0px' }"></div>
const vm = {
   isSuperImportant: ko.observable(true);
}

So the view remains easy readable without (much) programming-knowledge: 'If this item is super important, than create some margin' and the VM knows nothing about layout or margins. (Even more SoC-y to create a CSS class .superimportant and do the margins and rest of the styling there ofcouse).

From a Composition stand though I can see why a fluent API is a nice way of doing things. But maybe it can be made more generic and less specific to the style-binding by using more generic terms:

const vm = {
   myObs: ko.observable().key('margin').value('2em');
}

or

const vm = {
   myObs: ko.observable(new ObservableDictionary('margin', '2em'));
}

or (if we do want to make it specific to the style binding) maybe we can use the DOM specification:

let styleObject = document.createElement('STYLE');
styleObject.margin = '2em';

const vm = {
   myObs: ko.observable(styleObject);
}

And make the style-object's properties observable somehow.

karimayachi commented 4 years ago

The more I think about it, the less I think my idea about the ObservableDictionary makes any sense. It does nothing towards fluent API and it also has no real advantage over just an object with observable properties.

However, I kinda like the idea of passing the style-binding a DOM style object. Just to get really close in syntax and semantics to native DOM programming.

avickers commented 4 years ago

How would you go about implementing that?

let styleObject = {
   marginTop: '2em',
   marginBottom: '0',
   color: 'red'
}

That would be easy enough to merge into the bound element's own style property.

This leads to an orthagonal question: Should Knockdown behave like ko.mapping() by default?

That is to say, if you supply an observable with an object, should its sub-properties behave as observables by default?

karimayachi commented 4 years ago

This leads to an orthagonal question: Should Knockdown behave like ko.mapping() by default?

Not by default I think. But for specific cases (opinionated?) such a decision could be made. In this case the style-binding: I can see how -if the specification were to be that it accepts a DOM style-object- it would automatically observe its properties.

How would you go about implementing that?

Probably wrapping and proxying :-) Subclassing the style-object would be another, but less transparent option.

Anyway, none of these would have to be part of a MVP, imo.

Maybe create a new issue for discussing style-binding specifics?

avickers commented 4 years ago

Yes, perhaps the best approach to the style and attribute bindings should be its own topic.

As for MVVM, we have focused a lot on the boundaries between the View and View Model. What are the views towards the boundary between the VM and Model?

This is an area that I have put less focus on to date. I have implemented something called Buffer, which is a terrible pun and continuation of the boxing theme--Michael Buffer was the announcer that coined the saying, "Let's get ready to rumble!"--that Steve started. (CS purist might hated and/or be confused by the use of the term Buffer. I'm open to changing it.)

Currently, it really just gives you the ability to create an Obs or ObsArr in a global scope that can either be shared by any component or that should be persisted in memory across app navigation.

The idea is also that any code that touches a persistence layer or backend should live in a separate file(s), be attached to the Buffer as actions/methods, and called via it, but this aspect hasn't really been implemented yet.

It also has a rudimentary implementation of a graph, the idea being at some point to add developer tools that would visualize the graph and also make sure it was a DAG and warn of any potential circular dataflows.

Flux-based libraries and Vuex are a lot more opionated. I'm not sure what, if any, of these newer trends should make their way into MVVM, since Knockout itself really only cared about the V/VM boundaries and left handling the Model entirely up to the user.