I am using UI-Router to develop a web application. I have a piece of code looking something like this:
$transitions.onStart({}, function(transition){
let toState = transition._targetState._identifier;
let fromState = transition._fromState._identifier;//this doesn't work
})
As you can see, the transition object contains a property called _targetState, but doesn't seem to contain a property indicating the from state. So how can I detect the from state.
The methods to() and from() of the transition object are needed.
$transitions.onStart({}, function(transition){
let toState = transition.to().name;
let fromState = transition.from().name;
})
Related
I defined a new type of model element as a plug-in; let's refer to it as Foo. A Foo node in the model should translate to a section element in the view. So far, so good. I managed to do that by defining simple conversion rules. I also managed to define a new FooCommand that transforms (renames) selected blocks to Foo.
I got stuck trying to have attributes on those Foo model nodes be translated to attributes on the view elements (and vice-versa). Suppose Foos have an attribute named fooClass which should map to the view element's class attribute.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
I can successfully receive parameters in FooCommand, but I can't seem to set them on the blocks being processed by the command:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Below is the code for the init function in the Foo plugin, including the model→view and view→model conversions:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
When I try to run the command via
editor.execute('foo', { fooClass: 'green-foo' })
I can see that the green-foo value is available to FooCommand, but the modelElement in the model→view conversion, on the other hand, has no fooClass attribute.
I'm sure I'm missing the point here and misusing the APIs. I'd be really thankful if someone could shed some light on this issue. I can provide more details, as needed.
Follow-up after initial suggestions
Thanks to #Reinmar and #jodator for their suggestion regarding configuring the document schema to allow for the custom attribute. I really thought that would have taken care of it, but no. It may have been a necessary step anyway, but I'm still unable to get the attribute value from the model element during the model→view conversion.
First, let me add an important piece of information I had left out: the CKEditor5's version I'm working with is 1.0.0-alpha2. I am aware several of the APIs are bound to change, but I would still like to get things working with the present version.
Model→view conversion
If I understand it correctly, one can either pass a string or a function to the toElement call. A question about using the latter: what exactly are the parameters passed to the function? I assumed it would be the model element (node?) to be converted. Is that the case? If so, why is the attribute set on that node via batch.setAttribute (inside a document.enqueueChanges) not available when requested? Should it be?
A sequencing problem?
Additional testing seems to indicate there's some kind of order-of-execution issue happening. I've observed that, even though the attribute is not available when I first try to read it from the modelElement parameter, it will be so if I read it again later. Let me try to illustrate the situation below. First, I'll modify the conversion code to make it use some dummy value in case the attribute value is not available when read:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Now I reload the page and execute the following instructions on the console:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Even though it looks like the expected output is being produced, a glance at the generated view element shows the problem:
<section class="naught"/>
Lastly, even if I try to reset the fooClass attribute on the model element, the change is not reflected on the view element. Why is that? Shouldn't changes made via enqueueChanges cause the view to update?
Sorry for the very long post, but I'm trying to convey as many details as I can. Here's hoping someone will spot my mistake or misunderstanding of how the CKEditor 5's API actually works.
View not updating?
I turned to Document's events and experimented with the changesDone event. It successfully addresses the "timing" issue, as it consistently triggers only after all changes have been processed. Still, the problem of the view not updating in response to a change in the model remains. To make it clear, the model does change, but the view does not reflect that. Here is the call:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
To be 100% sure I wrote the whole feature myself. I use the 1.0.0-beta.1 API which is completely different than what you had.
Basically – it works. It isn't 100% correct yet, but I'll get to that.
How to convert an element+attribute pair?
The thing when implementing a feature which needs to convert element + attribute is that it requires handling the element and attribute conversion separately as they are treated separately by CKEditor 5.
Therefore, in the code below you'll find that I used elementToElement():
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
So a converter between model's <foo> element and view's <section> element. This is a two-way converter so it handles upcasting (view -> model) and downcasting (model -> view) conversion.
NOTE: It doesn't handle the attribute.
Theoretically, as the view property you could write a callback which would read the model element's attribute and create view element with this attribute set too. But that wouldn't work because such a configuration would only make sense in case of downcasting (model -> view). How could we use that callback to downcast a view structure?
NOTE: You can write converters for downcast and upcast pipelines separately (by using editor.conversion.for()), in which case you could really use callbacks. But it doesn't really make sense in this case.
The attribute may change independently!
The other problem is that let's say you wrote an element converter which sets the attribute at the same time. Tada, you load <section class=ohmy> and gets <foo class=ohmy> in your model.
But then... what if the attribute will change in the model?
In the downcast pipeline CKEditor 5 treats element changes separately from attribute changes. It fires them as separate events. So, when your FooCommand is executed on a heading it calls writer.rename() and we get the following events in DowncastDispatcher:
remove with <heading>
insert:section
But then the attribute is changed too (writer.setAttribute()), so we also get:
setAttibute:class:section
The elementToElement() conversion helper listens to insert:section event. So it's blind to setAttribute:class:selection.
Therefore, when you change the value of the attribute, you need the attributeToAttribute() conversion.
Sequencing
I didn't want to reply to your question before we released 1.0.0-beta.1 because 1.0.0-beta.1 brought the Differ.
Before 1.0.0-beta.1 all changes were converted immediately when they were applied. So, rename() would cause immediate remove and insert:section events. At this point, the element that you got in the latter one wouldn't have the class attribute set yet.
Thanks to the Differ we're able to start the conversion once all the changes are applied (after change() block is executed). This means that the insert:section event is fired once the model <foo> element has the class attribute set already. That's why you could write a callback-based converters... bur you shouldn't :D
The code
import { downcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
This code works quite well, except the fact that it converts the class attribute on any possible element that has it. That's because I had to use very generic downcastAttributeToAttribute() and upcastAttributeToAttribute() converters because of a bug that I found (EDIT: it's fixed and will be available in 1.0.0-beta.2). The commented out piece of code is how you it should be defined if everything worked fine and it will work in 1.0.0-beta.2.
It's sad that we missed such a simple case, but that's mainly due to the fact that all our features... are much more complicated than this.
I am wondering what is the use of asObservable:
As per docs:
An observable sequence that hides the identity of the
source sequence.
But why would you need to hide the sequence?
When to use Subject.prototype.asObservable()
The purpose of this is to prevent leaking the "observer side" of the Subject out of an API. Basically to prevent a leaky abstraction when you don't want people to be able to "next" into the resulting observable.
Example
(NOTE: This really isn't how you should make a data source like this into an Observable, instead you should use the new Observable constructor, See below).
const myAPI = {
getData: () => {
const subject = new Subject();
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subject.next({ type: 'message', data });
source.onOtherMessage = (data) => subject.next({ type: 'othermessage', data });
return subject.asObservable();
}
};
Now when someone gets the observable result from myAPI.getData() they can't next values in to the result:
const result = myAPI.getData();
result.next('LOL hax!'); // throws an error because `next` doesn't exist
You should usually be using new Observable(), though
In the example above, we're probably creating something we didn't mean to. For one, getData() isn't lazy like most observables, it's going to create the underlying data source SomeWeirdDataSource (and presumably some side effects) immediately. This also means if you retry or repeat the resulting observable, it's not going to work like you think it will.
It's better to encapsulate the creation of your data source within your observable like so:
const myAPI = {
getData: () => return new Observable(subscriber => {
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subscriber.next({ type: 'message', data });
source.onOtherMessage = (data) => subscriber.next({ type: 'othermessage', data });
return () => {
// Even better, now we can tear down the data source for cancellation!
source.destroy();
};
});
}
With the code above, any behavior, including making it "not lazy" can be composed on top of the observable using RxJS's existing operators.
A Subject can act both as an observer and an observable.
An Obervable has 2 methods.
subscribe
unsubscribe
Whenever you subscribe to an observable, you get an observer which has next, error and complete methods on it.
You'd need to hide the sequence because you don't want the stream source to be publicly available in every component. You can refer to #BenLesh's example, for the same.
P.S. : When I first-time came through Reactive Javascript, I was not able to understand asObservable. Because I had to make sure I understand the basics clearly and then go for asObservable. :)
In addition to this answer I would mention that in my opinion it depends on the language in use.
For untyped (or weakly typed) languages like JavaScript it might make sense to conceal the source object from the caller by creating a delegate object like asObservable() method does. Although if you think about it it won't prevent a caller from doing observable.source.next(...). So this technique doesn't prevent the Subject API from leaking, but it indeed makes it more hidden form the caller.
On the other hand, for strongly typed languages like TypeScript the method asObservable() doesn't seem to make much sense (if any).
Statically typed languages solve the API leakage problem by simply utilizing the type system (e.g. interfaces). For example, if your getData() method is defined as returning Observable<T> then you can safely return the original Subject, and the caller will get a compilation error if attempting to call getData().next() on it.
Think about this modified example:
let myAPI: { getData: () => Observable<any> }
myAPI = {
getData: () => {
const subject = new Subject()
// ... stuff ...
return subject
}
}
myAPI.getData().next() // <--- error TS2339: Property 'next' does not exist on type 'Observable<any>'
Of course, since it all compiles to JavaScript in the end of the day there might still be cases when you want to create a delegate. But my point is that the room for those cases is much smaller then when using vanilla JavaScript , and probably in majority of cases you don't need that method.
(Typescript Only) Use Types Instead of asObservable()
I like what Alex Vayda is saying about using types instead, so I'm going to add some additional information to clarify.
If you use asObservable(), then you are running the following code.
/**
* Creates a new Observable with this Subject as the source. You can do this
* to create customize Observer-side logic of the Subject and conceal it from
* code that uses the Observable.
* #return {Observable} Observable that the Subject casts to
*/
asObservable(): Observable<T> {
const observable = new Observable<T>();
(<any>observable).source = this;
return observable;
}
This is useful for Javascript, but not needed in Typescript. I'll explain why below.
Example
export class ExampleViewModel {
// I don't want the outside codeworld to be able to set this INPUT
// so I'm going to make it private. This means it's scoped to this class
// and only this class can set it.
private _exampleData = new BehaviorSubject<ExampleData>(null);
// I do however want the outside codeworld to be able to listen to
// this data source as an OUTPUT. Therefore, I make it public so that
// any code that has reference to this class can listen to this data
// source, but can't write to it because of a type cast.
// So I write this
public exampleData$ = this._exampleData as Observable<ExampleData>;
// and not this
// public exampleData$ = this._exampleData.asObservable();
}
Both do the samething, but one doesn't add additional code calls or memory allocation to your program.
❌this._exampleData.asObservable();❌
Requires additional memory allocation and computation at runtime.
✅this._exampleData as Observable<ExampleData>;✅
Handled by the type system and will NOT add additional code or memory allocation at runtime.
Conclusion
If your colleagues try this, referenceToExampleViewModel.exampleData$.next(new ExampleData());, then it will be caught at compile time and the type system won't let them, because exampleData$ has been casted to Observable<ExampleData> and is no longer of type BehaviorSubject<ExampleData>, but they will be able to listen (.subscribe()) to that source of data or extend it (.pipe()).
This is useful when you only want a particular service or class to be setting the source of information. It also helps with separating the input from the output, which makes debugging easier.
I have a Map like so:
var AppState = can.Map.extend({
sites: null
});
appstate = new AppState();
the list gets populated like this:
Site.findAll({}, function(sites) {
appstate.attr('sites', sites);
});
which I pass to a control like this:
new SummaryCtrl("#summaryCtrl", {sites:appstate.compute('sites')});
The control looks like this:
var SummaryCtrl = can.Control.extend({
'{sites} change': function(ev, type, sites) { //this doesn't fire
var recent = sites.slice(0,25);
var siteCount = sites.length;
this.element.html(can.view('summaryTpl', {siteCount:siteCount, recent:recent}));
}
});
Then I do this:
var newsite = {blah1:'blahblah', blah2:'blahblah'};
appstate.sites.unshift(newsite);
But the '{sites} change' function doesn't fire. Any idea why? Thanks!
Figured out a solution - not sure if its the best solution, but its ok for my use.
I realized a compute of a list does not have the same interface as the list. Intuitively, I thought it would. So instead of passing a compute of the list to a control, just pass the list itself and use .replace and .unshift on the list as needed.
If you need to use a map, that is fine, but again, don't pass in computes of the properties on the map, just reference the property itself and initialize as an empty list like so:
var State = can.Map.extend({
sites: new can.List()
});
http://jsfiddle.net/c7tdma5k/25/
The reason this doesn't work is because the compute only changes when the sites property changes, not the individual items in sites.
If you created the compute like:
var sites = can.compute(function(){
var sites = appState.attr("sites");
sites.attr("length")
return new List().replace(sites)
})
This would work. The compute would change anytime an item was inserted or removed in the list. However, your answer is a better way of doing this.
Is it possible to know if Backbone.Model.set() has changed/not-changed anything without events if possible? Reason being: if I use events it will look like:
listen to change:something
do something if value has changed
model.set("something", "value")
But what if change does not happen? How do I know that? Also with event handlers, I need to remove them approperately. For example, if I do it this way, I need to remove the handler if a change does not happen
Here is a simple solution that hides Events, into a simple synchronous function that returns if set has an effect.
I choose to implement this in a different function name, but you can also override the default set behavior.
var M = Backbone.Model.extend({
setThatLetsYouKnow: function(key, value){
var thisSetHasEffect = { flag: false};
this.listenToOnce(this, "change", function(){
thisSetHasEffect.flag = true;
});
this.set(key, value);
return thisSetHasEffect.flag;
}
});
And the result is:
var m = new M();
m.setThatLetsYouKnow("key",2)// return true
m.setThatLetsYouKnow("key",2)// reutrn fasle
Of Course, you need to add support to all different kind of set argument, this is just the Idea.
I've read a lot about mocking/stubbing/faking - and still hit my mental roadblocks.
I'm trying to adapt MVP (Model View Presenter) with a "fun" weight loss tracking system I'm building for my own Fatty McFatter-self. I'm trying to TDD & 'by the book' this but hit many mental blocks and stall out.
I am building my Presenter and mocking my Service & View at the moment. Here's my test: again note: service and view are mocked with Moq
[Test]
public void GetLog_WithExistingDate_ViewSetWithExistingLog()
{
WeightLogModel model = new WeightLogModel
{
EntryDate = DateTime.Now,
Waist = 42,
Weight = 242
};
service.Setup(x => x.GetLog(It.IsAny<DateTime>())).Returns(model);
presenter.Display(DateTime.Now);
IWeightLogView myView = view.Object;
Assert.AreEqual(model.Weight, myView.Weight);
}
and in my Presenter - this is my Display method:
public void Display(DateTime date)
{
var weightLog = service.GetLog(date);
if(weightLog == null) return;
View.EntryDate = weightLog.EntryDate;
View.Waist = weightLog.Waist;
View.Weight = weightLog.Weight;
}
Now - if I debug as Display is being called - I see the weightLog is filled with the correct info I've setup in the mock. But as it's suppose to set View.EntryDate, View.Waist, etc - the View values never change. They stay zero or 0001/1/1
Is there some way to make it work? Or is this just a bad test and I'm floundering in confusion?
Thanks to Phil for starting me in motion. Although I didn't want to explicitly set what I was going to return - I wanted the mock view to behave like my view. You can have the mocked setter behave as normal by calling SetupProperty --> view.SetupProperty(x => x.Weight) //in my case... here's the test that will now pass asserting the weight was set
[Test]
public void GetLog_WithExistingDate_ViewSetWithExistingLog()
{
WeightLogModel model = new WeightLogModel
{
EntryDate = DateTime.Now,
Waist = 42,
Weight = 242
};
service.Setup(x => x.GetLog(It.IsAny<DateTime>())).Returns(model);
// I ADDED THIS ONE LINE
view.SetupProperty(x => x.Weight);
presenter.Display(DateTime.Now);
IWeightLogView myView = view.Object;
Assert.AreEqual(model.Weight, myView.Weight);
}
You are not showing all your setup code here, nor the dependencies between classes.
However if you are indeed mocking the view called "myView", it's going to return what you have the mock set up to return, or defaults for each type if you haven't specified anything for it to return (which sounds like what is happening).
From your comment:
I am trying to setup the
service.GetLog(date) to return the
WeightLogModel I have in the test. My
thinking is that doing so - would make
that WeightLogModel available in my
presenter
So far that seems like it is working from your original question.
to assign to my mocked view - where
View.EntryDate = weightLog.EntryDate
.... in this case weightLog is what is
setup in the test.... I hope I'm clear
as to where my head is... I'm not
saying I'm right - this is what my
thinking is though.
Where are you going wrong is where you say "to assign to my mocked view". It's not clear from your code whether or not the View property is in fact your mocked view (because your code is incomplete).
Although, in this case, it actually doesn't matter. If the View property is in fact a mock, it will only return what you tell it to return--its properties are not going to behave like "normal" properties.
So the following will fail without explicit setup:
mockView.MyProperty = "hello";
Assert.AreEqual("hello", mock.MyProperty);