Closed gregpabian closed 7 years ago
So I'll drop few lines after quick lookup.
For now I really like how models work - I don't know how other MV* frameworks does it (besides Backbone) but not having verbose setters and getter is very nice.
On the other hand templates look weird to me. Maybe because I'm used to string templates and this is something different? I don't feel how they should work and how to build something more complex, ie multiple nested divs with some content (ie image with caption and some overlay span with data).
Anyway the the model-view cooperation is working great plus great to have two-way binding.
bob
stands for?MVC.Model
, MVC.Space
, MVC.PluginManagerMixin
, MVC.Application
specified in a single file doesn't sound like any fun to me.change
event supposed to occur after the change:<prop>
event? It looks very similar to Backbone, and there it's change
first.Is there any way to cancel the events right now?
Suggestion: I think that we might consider using in CKE something like preventDefault in DOM for our events, which would mean that developer wants CKEditor to not fire default handling for the event.
Reasoning: I'm missing it a lot in CKE4, because now you have two solutions:
preventDefault
property in EventData, which is perfect candidate for R-, because it's not widely used, it's misleading or dozen of other reasons.Common scenarios:
A very good step forward, I can see that this proposition gives some powerful features and it's looking promising.
I would love to get much deeper into it, but some parts requires deep analysis and I already gave it considerable time, while A11ychecker timer is ticking. :P
I think we should prepare an integration sample, with Editor object utilizing PluginManager. It would load like 3 plugins, and then we would see how does it works, and most importantly "feels". : )
@jodator Templates are looking weird for me but after inspecting the internals I feel like they are very flexible, so i'm excited about it. I'm not used to such syntax, but all what worries me at this point. : D
@jodator, @mlewand regarding a View's template - it's something more than just a DOM builder - a template
property allows you to define a DOM structure but also to define all the bindings in one place. The problem with string-based templates is HTML parsing, which takes some time. While working with Backbone I had to do a lot of hacking to build a complex HTML structure, render it and bind all the events in a reasonable period of frame and I hope that working on DOM elements should help us avoid this problem. And regarding the syntax - it's inspired by JsonML a bit, but with several modifications.
@mlewand
bob.helpers.bindProp - is there any reason to reduce mutator only to object members? It seems to limit the functionality, I would allow a function to be passed to.
Sounds reasonable, I'll add this feature.
what does the bob stands for?
I was thinking how to name it properly since it's not just a DOM builder, nor it is a data binder, so bob
stands for Binder & Builder ^_^ (I was also considering "bnb", but "bob" sounds better). Still, ideas are welcome.
I don't like the fact that types are packed so tightly :c eg. MVC.Model, MVC.Space, MVC.PluginManagerMixin, MVC.Application specified in a single file doesn't sound like any fun to me.
It could be moved to separate files - no problem with that - just my laziness ^_^;
isn't change event supposed to occur after the change:
event?
I agree, we should start from specific handlers and then move to general ones.
Is there any way to cancel the events right now?
It's not possible right now, but could you give me a real life example for that? (I don't know CKE4 good enough to name some problems with events in the current version...)
@mlewand The PluginManager is another big thing we're working on right now, but it's gonna take some time to ship it since it has a lot of impact on how the whole architecture and build process works.
@jodator (https://github.com/jodator), @mlewand (https://github.com/mlewand) regarding a View's template - it's something more than just a DOM builder - a template property allows you to define a DOM structure but also to define all the bindings in one place. The problem with string-based templates is HTML parsing, which takes some time. While working with Backbone I had to do a lot of hacking to build a complex HTML structure, render it and bind all the events in a reasonable period of frame and I hope that working on DOM elements should help us avoid this problem. And regarding the syntax - it's inspired by JsonML a bit, but with several modifications.
Let me add that the object model of the template makes it easy to make modifications to it, like changing element names or adding attributes to them. See my recent changes to 5.js for an example of an extension to an existing template.
what does the bob stands for?
I was thinking how to name it properly since it's not just a DOM builder, nor it is a data binder, so bob stands for Binder & Builder ^_^ (I was also considering "bnb", but "bob" sounds better). Still, ideas are welcome.
bob is a funny name… we can justify it as “Bindings on Builder” ;)
I don't like the fact that types are packed so tightly :c eg. MVC.Model, MVC.Space, MVC.PluginManagerMixin, MVC.Application specified in a single file doesn't sound like any fun to me.
It could be moved to separate files - no problem with that - just my laziness ^_^;
True. We may have a single “mvc” folder with each class separated.
@gregpabian Thanks for rationale - I suspected that speed was the case for this. Anyway I'd like to see more complex examples of DOM structure to see how they can work
Also it can be Bob the DOM builder ;-)
@mlewand @fredck @gregpabian I like the sexual innuendo with bob, so it could stay ;)
Cool. Binding is cool. And is seems to be not over-complicated (yet). But I am very curious how it will work in the real application, I mean many concepts are cool when you have 1000 LOC and no real problems.
The one think I do not like is an array as an template and ending up with:
IconLinkButton.prototype.template[ 1 ].children.unshift( //...
As I understand the first element is the type of the element and the second are properties. Why name can not be one of the properties?
Why name can not be one of the properties?
It's a matter of readability - compare:
template: [
'a', {
className: Button.bindProp( 'model.active', 'getActiveClass' ),
onclick: 'click',
href: 'javascript:;',
children: [
[ 'span', {
text: Button.bindProp( 'model.text' )
} ]
]
}
]
with this:
template: {
tag: 'a',
className: Button.bindProp( 'model.active', 'getActiveClass' ),
onclick: 'click',
href: 'javascript:;',
children: [
{
tag: 'span',
text: Button.bindProp( 'model.text' )
}
]
}
In the first example it's easier to tell what element you're in, besides it allows us to use some shorthands, e.g.:
[ 'span', Button.bindProp( 'model.text' ) ]
instead of:
[ 'span', {
text: Button.bindProp( 'model.text' )
} ]
I still prefer second version. Especially when you will need to access some properties:
template.children[ 0 ].tag
is in my opinion more readable then:
template[ 1 ].children[ 0 ][ 0 ]
And I think we will access/merge templates a lot.
True. We may have a single “mvc” folder with each class separated.
I suggest to make standard for it. Each namespace should be represented by directory with the same name as namespace (so here we create MVC directory). And each constructor should be represented as single file with the same name (so in MVC directory we got file Model.js
).
While current template implementation is a bit tricky. I suggest to make some improvements (this is just prototype):
https://gist.github.com/adelura/dd86f474697626ee4f26
What I did:
Pros:
Cons:
##################################################
I wrote an email to the team few months ago comparing the DOM builder and HTML templates. Some of you couldn't read it then, so quoting myself:
##################################################
Hi,
I made a research and have some I think very interesting findings.
First of all, some blog posts.
I don't encourage you to read all that. I haven't done it myself (except the last one). Just scanned for some most important notes. It doesn't make sense to rely on these articles, because our case is significantly different than this of single page apps. We build DOM once, we keep templates inside JS, so we use JS logic (e.g. loops and conditions), we build UI and besides that create many small elements across the entire code base, etc.
I found many innerHTML vs DOM builders performance tests, but they were poor implementations and were missing the point (especially, our point).
First I implemented this test: http://jsperf.com/dom-builder-vs-innerhtml/2 One uses HTML string (taken directly from our UI), but no logic and no concatenation, second uses simple and naive DOM builder. There are two pairs of tests in fact - one checks 10x repeats of the template second floods DOM with single instances of layout. You'll see in the results why I separated these cases.
Then I implemented an improved version: http://jsperf.com/dom-builder-tpl-vs-innerhtml/2 I wanted to focus on the "repeating" and "templating" aspects. In the DOM builder I create a template DOM structure which is then cloned and data is filled in remembered places. Cloning is much faster than building from scratch and you can see that in the results.
As you can see in the results (if you can't see use FF instead of Chrome, sic!) the second implementation is incomparable with the naive one. Also, the loop case gives different results than the single element creation case. This means that innerHTML has long starting time, most likely because of the parser initialization. I'll get to that later.
I also created a version of test which is more close to createFromHtml vs not using it - http://jsperf.com/dom-builder-vs-innerhtml/3
Then I asked myself - what we actually do now.
grep -R "createFromHtml\|setHtml" core/* plugins/*
.tl;dr I'm for using DOM builder in CKE5. It has many advantages over HTML strings, it may improve performance in many cases and should not break it in others.
CKEditor loads on Chrome so well comparing to IE and Firefox, that the results I get from its awesome dev tools are completely useless for general view. I was trying to use FF's profiler, but it doesn't seem to work at all. We'll have to check if Firebug does any better. And also check IE.
Disclaimer: Since I didn't work on the prototype and I don't know the things that you discussed but which aren't reflected in the prototype's code, my comments may explain what you already know and/or skipped intentionally. Also, I know that you might be focused on the data binding and templates only, so my comments about MV* in general may exceed the scope of this prototype. However, to prevent the situation that something hasn't been said and then it's too late, I'm writing everything down.
The current prototype does not meet the two important requirements for the MV* architecture that we need:
Why isn't the prototype meeting these requirement?
How to solve these issues? The first thing to notice is that the model proposed in the prototype is actually only a bidirectional data binding. So this is the view model (VM) from the MVVM pattern which Fred was exploring. VM should not contain any logic and this is true now - it is just a set of attributes with plain setters, getters and events when something changes.
Besides the VM a component must have a model (M). The model implements the tricky logic which assures the validity and integrity of the data. Perfect example of the need of having a well defined model was the model in the autocomplete plugin which:
queryText
and queryRange
which always go together (so there's one method setQuery
to set them both),data
property which is reset by setQuery
and then set asynchronously or synchronously (it depends on the implementation) when the suggestion items are retrieved (e.g. from the server),selectedItemId
which is reset when query changes (so by the setQuery
method),selectItem
, selectFirstItem
, selectNextItem
which handle cases like the end of the list of items or an empty list.All properties are readonly and can only be changed by the methods what clearly defines the API. There are, however, examples of very simple models with no logic like this. In such cases there should be an easy way to define all those setters. For instance, we could have a generic setter and a list of model properties which may be changed by it:
Model.prototype = {
defineProperty: function( name ) {
Object.defineProperty( this, name, {
enumerable: true,
get: function() {
return this._properties[ name ];
},
set: function() {
throw new Error( 'Violation!' );
}
} );
},
set: function( name, value ) {
if ( name in this.setters )
this._properties[ name ] = value;
}
};
SomeModel.prototype = {
setters: { title: 1, height: 1 },
setQuery: function( queryText, queryRange ) {
this._properties.queryText = queryText;
this._properties.queryRange = queryRange;
}
};
Although, if we find this too complicated to use this._properties.propName
we could use this._propName
or resign from the violation error at all and base this restriction on the documentation (I'm OK with using the @readonly
tag).
The last two pieces of the puzzle are what the view and the controller do. Obviously, the view does not see the model. However, it can either user VM for everything or fire some events on itself. Additionally, does the view have methods or not? To answer that we have to understand the scope of communication. The types of situations we have:
vm.text
). The attr value may also depend on more complex logic.<button>
, another one on the <input type=checkbox>
and another one on <span tabindex=0>
. When they all share the same API for us - "something happened to me that I should change my state", that can be achieved by key press, click, value change, touch, etc. The view should play as a nice facade for that.Another note - there's a very thin line between what's a business logic and what's not, so what the view can do and must not do. Example: a button that doesn't know if now it can change its state on click. Let's say that in this case the decision whether the button can change the state is a logic that should be implemented by the model. So it might be considered incorrect to do:
template: [
'button', {
onclick: 'click'
}
],
click: function() {
this.vm.active = !this.vm.active;
},
Why? Because for some reason model may prohibit changing the state now. In such case there always must be a roundtrip to the model between something happened to the UI and the UI was updated. However, one can say that we may introduce VM.disabled
property which the view will check before changing the state, because whether the button changes state is the UI's thing (just like what happens with link between mouse down and mouse up - I mean the CSS'es :active
state). That might work in simpler cases and perhaps is the way to go, but, for instance, live validation whether button can be clicked may be too hard or too expensive. Though, the :active
state is more likely to be implemented by the view exclusively.
Furthermore, the obvious exception (but not so obvious to handle TBH) from the "must do a roundtrip" rule are changes which have immediate impact on the UI like typing in the input element. It is tricky sometimes to avoid infinite loops in such cases.
To distinguish between those two types of changes I would propose that if the user action does not cause a UI change (or the change was prevented by the UI), then we use an event (without passing a value). And when the user action is triggered after the value has changed (the typing example) then we use the VM property setter. The important part would be to define which properties of the VM the view can modify (that will be obvious when you check to which attribute changes of the VM the model listens).
Back to the question - should the view implement methods which the controller can use and should the view fire events which the controller/model can listen to? Or can we use the VM for everything? I don't have a strong opinion on that but I would use the VM for everything, so on the data changes (the first two points of the above list) and for the single actions (3rd and 4th points). Although, I'm OK with the alternative option too.
To wrap up:
preventDefault
and/or stopPropagation
.Model#attributes
will lead to name collissions. Better call it _attributes
. (BTW. attributes or properties?)Model#set
collide with the ability to set value directly what may be confusing. Couldn't it be just Model#add/define
with just one task - defining the property?
#set
actually can't be called more than once for the same attribute now.view.el
, which will only be executed when the event was targeted on specific set of elements inside the view.el
. We could use CSS selectors for that (like Backbone).getActiveClass
- I was reading the method without checking the template and I was confused what the value
is. Direct access to the VM would be much clearer, but I guess that we want to keep that sugar there. Can we make something to make it more obvious? Naming the argument after the VM's attribute name would help I think.EditorModel.caretPosition
and AutocompleteViewModel.caretPosition
, then if an alternative view implementation doesn't need the caret position it will be unnecessarily computed. To avoid that I put the code retrieving caret position in the view (because my view didn't have access to other models). Can it be solved in a better way? Do we care at all?The templates structure is OK. I would only consider extracting children array to the 3rd item of the parent array. Then it will be always ([]
around names means that this item is optional):
[ name, [attributesAndBindingObj], [childrenArray] ]
// Examples:
[ 'span' ]
[ 'span', [ 'a' ] ]
[ 'span', { className: 'foo' } ]
[ 'span', { className: 'foo' }, [ 'a' ] ]
Bindings are really cool (although I sometimes have problems with reading them quickly). I see that the methods don't need a context so we can make the code shorter:
var prop = mvc.View.bindProp;
var Button = mvc.View.extend( {
template: [
'button', {
onclick: 'click',
className: prop( 'model.active', 'getActiveClass' ),
text: prop( 'model.text' )
}
],
Object.defineProperty
:D. I never use it myself because it's so ugly.I suggest to make standard for it. Each namespace should be represented by directory with the same name as (...)
I totally agree I'd even presume that it's very obvious. It makes finding anything so much easier.
stands for Binder & Builder
Alright, now I get it. Eventually can't we name it simply a builder, and in it's docs mention that builder is also responsible for binding? Anyway as a results it's a just detail...
keep template in diffrent file
I love keeping markup in separate files, but given example, adds just soo much complexity and overhead. I'm fine with proposed template it's already an improvement opposed to the current templates, as they offer this ability to binding other props.
It's not possible right now, but could you give me a real life example for that? (I don't know CKE4 good enough to name some problems with events in the current version...)
I left it to the end, though it's the most important thing, just because it has more text.
I already gave some scenarios that I find common, but I didn't elaborate on them, as I want to save time in this thread.
dblclick
on an element. For links CKEditor opens link dialog, for Widgets it's gonna show widget edit dialog.
Developer might want to prevent these actions, but still not to break any further dblclick listeners, that he's not aware of.OK, I'm gonna answer starting from the end :)
Model#attributes will lead to name collissions. Better call it _attributes.
I'd go with _attributes
. BTW. have we considered using _
at the beginning of private properties/methods names?
(BTW. attributes or properties?)
TBD, but I'm following this convention: https://github.com/Reinmar/v5-mvc-research/blob/master/api/core.js#L42
Model#set collide with the ability to set value directly what may be confusing. Couldn't it be just Model#add/define with just one task - defining the property?
I'd go with Model#define(name, [value], [options])
API.
Other than that, #set actually can't be called more than once for the same attribute now.
That's right, by default a porperty's configurable
flag is set to false.
Missing - events delegation in the view. It should be possible to easily define a listener on e.g. the view.el, which will only be executed when the event was targeted on specific set of elements inside the view.el. We could use CSS selectors for that (like Backbone).
You want something like events
property in Backbone's View? How is it gonna associate with the existing bindings established using template
property?
Naming the argument after the VM's attribute name would help I think.
Yup, something like this would make more sense:
getActiveClass: function( active ) {
return active ? 'active' : '';
}
I think that we need a better classes binding. Imagine a pretty common case with multiple classes bound to the VM in different ways. Generating all the classes every time something changes is not cool.
I agree, e.g. a togglable class should care about itself only and not affect other classes.
However, if, in the controller I make a binding between EditorModel.caretPosition and AutocompleteViewModel.caretPosition, then if an alternative view implementation doesn't need the caret position it will be unnecessarily computed.
If the View is not accessing the ViewModel.caretPosition
then why would it be computed?
We may need some additional tools for modifying the templates.
True, the current solution gives us full control, but also makes some simple modification pretty tough - we have to think of some helpers here.
The templates structure is OK. I would only consider extracting children array to the 3rd item of the parent array. Then it will be always ([] around names means that this item is optional):
:+1: - this is almost the same syntax that JSONML uses and I'm all for following an existing convention
I haven't checked yet the Spaces, SpaceManager, Application and Commands classes. It would be cool if we could see a sample of them.
This wasn't the main goal of the prototype so not much to see there yet, besides, incoming changes in Views, Models and VMs will most likely have a lot of impact on their shapes.
A thing that I already miss is a short version for repetitive and ugly Object.defineProperty :D. I never use it myself because it's so ugly.
Again - we can introduce something like Model#define(prop, value, options)
to make it shorter.
OK, second bunch of comments.
The view contains application logic. It acts on UI events and modifies the model.
Application logic? Where? Alright, the name "View" here might be a bit misleading. It is not the exact representation of a View in MV* - it's more of a Presenter from MVP mixed with a View's concerns and that could be separated.
Everything in the model is fully visible. There's no encapsulation at all.
I haven't heard of such requirement when I started prototyping, besides it's hard to get a true encapsulation in JS and I can't tell if existing frameworks care about this much...
The controller only initializes the view and model. The model is totally empty. That means that all the rest is implemented by the view.
Hmm, where exactly?
The API which view must implement is not defined
What do you mean? What are your expectations on this?
The first thing to notice is that the model proposed in the prototype is actually only a bidirectional data binding. So this is the view model (VM) from the MVVM pattern which Fred was exploring.
It's the simplest Model implementation I could imagine, nothing stands in the way of adding some logic to it. (OK, when you were writing it, the Model missed 2 things I added later - extend API and handling of properties
object used to extend the instance so this might change the perception of a Model now).
VM should not contain any logic
Really? What about exposing methods used by a View, what about e.g. translating a Model's properties to a form understandable by Views? ViewModel is a middleman here and may need some logic.
The model implements the tricky logic which assures the validity and integrity of the data. Perfect example of the need of having a well defined model was the model in the autocomplete plugin which:
has two properties queryText and queryRange which always go together (so there's one method setQuery to set them both), has a data property which is reset by setQuery and then set asynchronously or synchronously (it depends on the implementation) when the suggestion items are retrieved (e.g. from the server), has a selectedItemId which is reset when query changes (so by the setQuery method), has a set of methods like selectItem, selectFirstItem, selectNextItem which handle cases like the end of the list of items or an empty list.
Most of this can be achieved using the existing Model implementation, short example:
var CustomModel = mvc.Model.extend({
initialize: function (attributes, properties) {
mvc.Model.prototype.initialze.call(this, attributes, properties);
this.on('change:query', function () {
this.selectedItemId = null;
this.data = null;
// ...
}, this );
},
selectItem: function () {
// ...
},
setQuery: function () {
// ...
}
});
Although, if we find this too complicated to use this._properties.propName we could use this._propName or resign from the violation error at all and base this restriction on the documentation (I'm OK with using the
@readonly
tag).
Let's avoid over-engineering things. Properties can still be accessed and modified using this._properties[name] so it guarantees nothing but additional complexity. Having well written documentation and standardized way of doing "things" should be enough.
The Model must change name to ViewModel.
I won't be so sure - first of all - I never said this is a MVVM pattern implementation... :) Besides, mvc.Model already does a model's job, but I agree it could to this better (e.g. better way to define attributes, computed attributes maybe?).
We need UI events -> VM events binding. This should include events delegation (listening on root for events on the children)
I'd love to get more suggestions on this piece.
it'd be good to have easy way to call preventDefault and/or stopPropagation.
preventDefault - fine, as long as we know what we're doing, but I'm still against stopPropagation...
We need a completely new Model class.
I don't agree, for the above reasons. What we really need is separation of Presenter's/Controller's concerns from the View and/or completely new class - ViewModel (but I'm still not sure how it would look like...)
We need a Controller class with tools for making M <-> VM bindings.
I don't think we need a Controller for this - a ViewModel could take care of the binding with its model.
I did some changes in the mvc-samples
branch:
Model#attributes
to Model#_attributes
Model#set
to Model#define
and added possibility to change a property's optionsI think that we need a better classes binding. Imagine a pretty common case with multiple classes bound to the VM in different ways. Generating all the classes every time something changes is not cool. I agree, e.g. a togglable class should care about itself only and not affect other classes.
I was thinking about this issue and found following use cases:
All of the above, except the last one is doable now. What we have to think about now is how to allow defining a mix of those case.
I'm thinking of using something like this:
...
template: [ 'a', {
classes: [
'foo',
'bar',
bindClassValue( 'model.foo' /*, mutatorFunction */ ),
bindClassToggle( 'hidden', 'model.foo' /*, conditionFunction */ )
]
}]
...
(Side note: I think that className
property should be still usable apart from the classes
.)
Now, while implementing bindClassToggle
helper seems to be pretty simple using Element.classList
, the other one with values might be tricky and require some additional effort (e.g. storing previous value to remove it later on change). So, what do you think?
Does bindClassValue
have to be responsible only for classes? I think that it might have name bindValue
and be responsible for other attiributes like href
, src
etc. We we might end up with somethink like this:
template: [ 'a', {
classes: [
'foo',
'bar',
bindValue( 'model.foo' /*, mutatorFunction */ ),
bindToggle( 'hidden', 'model.foo' /*, conditionFunction */ )
],
href: bindValue('model.link')
}]
(Side note: I think that className property should be still usable apart from the classes.)
Isn't class
more natural and more clear?
Does bindClassValue have to be responsible only for classes?
Yes, it uses Element.classList token list to add/remove classes so it won't work for other properties.
Isn't class more natural and more clear?
Yes, it is, but class
is a reserved word in JavaScript.
On second thought, it's possible to merge bindClassValue
with bindProp
and so I did, now you can do the following:
classes: [ 'foo', 'bar', bindProp( 'model.baz' ) ]
I also modified bindClassToggle
API to allow negating property value to toggle class by adding !
at the beginning of the property, example:
classes: [ 'foo', 'bar', bindClassToggle( 'hidden', '!model.visible' ) ]
Now if model.visible
is truthy, the hidden
class will be removed and added if the property is falsy.
I think that, at the current stage, most of the details about the MVC API will be developed once we'll have a better understanding on how we gonna organize the whole UI creation and handling.
We should have our ideas about MVC already in place, therefore we must proceed with other design topics and adapt our MVC infrastructure to them. I doubt we'll ever reach a final API right now.
Therefore, I've ported our previous generic documentation about MVC into our wiki. This is a first step, to make our MVC intentions official.
(Side note: I think that className property should be still usable apart from the classes.)
If you meant that both should be usable at the same time, then TBH I would avoid that. Either you use className
or classes
, your choice. I can't imagine how they could be synchronised. Unless, that's exactly what you meant :).
- a mix of above
All of the above, except the last one is doable now
A mix of above can be achieved by bindProp()
with a mutator. If the mutator returns a falsy value, then the class is removed. So with a mutator you can do everything...
... except binding one class (or actually anything) to two Model properties at the same time (the thing should be updated whenever any of these two properties change). It resembles merging two streams in the reactive paradigm. I think that in this case it could be achieved by the view defining a new Model property which will be a merge of two or more other properties. It does not even have to have a value in some cases - it can be a virtual property which has a corresponding change:
event which is fired whenever any of the source properties change.
PS. I've just realised that in the Model you have attributes, while the function is called bindProp()
(although, I don't know to what "prop" refers in this case) and we all call them properties (you even wrote Model#define( prop )
). So maybe the answer to:
(BTW. attributes or properties?)
Is "properties" because this seems to be more intuitive. HTML tags have attributes, classes, objects and all the rest have properties.
If you meant that both should be usable at the same time, then TBH I would avoid that. Either you use className or classes, your choice. I can't imagine how they could be synchronised. Unless, that's exactly what you meant :).
Yup, you can use one or another but not both at the same time - this would produce too many weird scenarios to cover.
A mix of above can be achieved by bindProp() with a mutator. If the mutator returns a falsy value, then the class is removed. So with a mutator you can do everything...
Yup, that's true for the current version.
... except binding one class (or actually anything) to two Model properties at the same time (the thing should be updated whenever any of these two properties change). It resembles merging two streams in the reactive paradigm. I think that in this case it could be achieved by the view defining a new Model property which will be a merge of two or more other properties. It does not even have to have a value in some cases - it can be a virtual property which has a corresponding change: event which is fired whenever any of the source properties change.
Or even something like this:
var CustomView = mvc.View.extend( {
initialize: function () {
mvc.View.prototype.initialize.apply( this, arguments );
this.listenTo( this.model, 'change:foo', this._fooBarChanged, this );
this.listenTo( this.model, 'change:bar', this._fooBarChanged, this );
},
template: [
'div', {
// a View already inherits from the Emitter so we can do the following
className: mvc.View.bindProp( 'fooBar', function ( model, value ) {
// do magic here...
} )
}
],
_fooBarChanged: function ( model, newValue, oldValue ) {
this.trigger( 'change:fooBar', model, newValue, oldValue );
}
} );
Written from my head, might not work :) Anyway, it's doable.
I've enlarged the docs to describe what we should expect to have in the model's state: https://github.com/ckeditor/ckeditor5-design/wiki/MVC#the-models-state
The implementation on MV* was refactored a couple of times but we finally came to a point where we're comfortable with the result. It can be now found in https://github.com/ckeditor/ckeditor5-ui.
On branch mvc-samples I added some usage examples of the MV* framework prototype I've been working on recently. This is an "umbrella" issue to discuss them and the whole idea.