Ember model lifecycle, and reverting to original state - javascript

I'm struggling to understand everything about the ember model lifecycle. I have created this jsfiddle to illustrate my problem. When clicking on one of the entries in the list, editing a value, and clicking the versions link to go back to the list, I get the following error:
Uncaught Error: Attempted to handle event loadedData on while in state rootState.loaded.updated.uncommitted. Called with {}
What is causing this? I get that the object state is now dirty, but how can I force a refresh of all objects when the list is opened?
Also, any suggestions on how to discard all changes to the properties if the form is not saved? I was thinking about cloning the object, using that clone in the edit form, and merging that with the original when saving. Not as easy as I first imagined.
Using latest ember and ember-data.

After quick discussion with #tchak, a solution could be to override the Version route's exit function, and rollback the current model.
App.VersionRoute = Ember.Route.extend({
exit: function() {
var controller = this.controllerFor(this.templateName),
content = controller.get('content');
if (content.get('isDirty')) {
content.get('transaction').rollback();
}
this._super();
}
});

Related

Ember Data - Lazy loading child data of a model without re-creating previously created objects again

I'm new to ember-data. I'm trying to load comment list from a API using multiple API calls. The comment list feature works like below,
A comment object can have a parent comment or children comments (replies)
All comments (children & parent) from different comment threads are list down in a single comment list using a 1st API call.
If user click on specific comment from above list it will prompt respective comment thread. Respective parent or children comments loading using 2nd API call
The comment model is implemented as below,
export default CommentModel.extend( {
parent: computed(function() {
return get(this, 'store').queryRecord('comment', {
_overrideURL: `comments/${get(this, 'id')}/parent`,
});
}),
children: computed(function() {
return get(this, 'store').query('comment', {
_overrideURL: `comments/${get(this, 'id')}/children`,
});
}),
...
As this implementation, if user click on child comment (reply) from the comment list, the 2nd API call with load the respective parent comment and parent comment will load its children comments again. That behaviour cause reload the comment list component in UI.
Is there any other way in ember-data to lazy load relationship without creating already existing objects?
If you really need to go that road, you may try to perform a findRecord instead of a queryRecord and use adapterOptions to customize your model's adapter urlForFindRecord method.
TL;DR
Why you shouldn't:
IMHO, you have a data flow problem in your proposed design.
You shouldn't be performing async code inside a computed property (nor returning immutable object as queryRecord response).
Tasks work great for that purpose.
You shouldn't be having your model to load data (that should be route's responsibility), which violates both MVC and DDAU principles.
There is this great article from 2015 on that
As a matter of fact since ember octane, you shouldn't be using computed properties at all, they have been replaced by actual getters and tracked properties.
More on that
Ember is a great framework, good luck on your journey!

Disambiguating bootstrap-switch state changes in knockout

I'm using bootstrap-switch together with the knockout binding handler referenced from this question shown below:
ko.bindingHandlers.bootstrapSwitchOn = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
$elem = $(element);
$elem.bootstrapSwitch();
// Set intial state
$elem.bootstrapSwitch('setState', ko.utils.unwrapObservable(valueAccessor()));
$elem.on('switch-change', function (e, data) {
// Update the model when changed.
valueAccessor()(data.value);
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var vStatus = $(element).bootstrapSwitch('status');
var vmStatus = ko.utils.unwrapObservable(valueAccessor());
if (vStatus != vmStatus) {
$(element).bootstrapSwitch('setState', vmStatus);
}
}
};
This seems to be working quite nicely and I've mocked up a fiddle to illustrate how I'm using it here:
http://jsfiddle.net/swervo/of0q42j0/5/
However, I have a few issues which I can't seem to solve in a satisfactory manner:
1) If I have an array of items in an ko.observable array I can put a click handler on all of them and have them call a function in the parent view model like this:
data-bind="click: $parent.clickHandler"
Which, when called, passes through the items own view model. This is really convenient for getting properties of the item that was clicked, eg., id. I've put a button in the fiddle above to illustrate how easy this is to do.
However, if I'm using the bootstrap-switch instead of a simple button the switch doesn't seem to know about it's parent and I can't find an elegant way of passing through the view model containing the switch to its parent - like you can with a button. I have tried giving each item in the array a reference to it's parent view model and this does work but creates a circular reference and thus doesn't seem like the correct approach.
2) In the application that I'm building the state of items in a list can be changed on a different clients - and the local state needs to update to reflect these remote clients. Equally the state can also be changed on the local client which is then propagated to other clients. My problem here is how to disambiguate between changes to state that have happened locally (ie., due to the user clicking on the switch), and changes that have happened remotely (ie., due to an update coming from the server). In my actual project I'm using knockout subscribe to listen for changes in the values linked to the switches like this:
viewModel.observableValue.subscribe(function(newValue) {
// test value on server and if it is different update
});
I want to avoid receiving an update from the server and then updating the server again (with the same state) when my switch changes to reflect the new state. At the moment I've fixed this by testing the server state (as implied in the code snippet above) before I send the update and if it is the same as the pending state update I discard it. (I've simulated a server update using a button in the referenced fiddle above).
Neither of my solutions to these problems feel elegant hence the question here.
Any help would be much appreciated.
I'm not sure what you mean by the 'the switch doesn't seem to know about it's parent'. Looking at http://knockoutjs.com/documentation/custom-bindings.html, I can see that init and update both have a 5th param, bindingContext that has the parent information, should you wish to access it.
Ahem, one of the projects we worked on the past had a toggle button that suffered from the same issue and it was fixed is a very simple way. For events that are generated locally, just attach a property to the object, like .local = true; and check for it in the update (or attach it in your REST handler) to distinguish local/vs REST. Don't forget to delete the property from the view model once done in update though.

Computed vs Observes on Controller in Ember

It was always my understanding that .observes('someProperty') and .property('someProperty') worked exactly the same, except that the former is used for triggering function calls and the latter is used to keep object properties up to date.
But now I'm having a problem. My controller code looks like this:
_logChange: function(){
console.log('model array observer fired');
}.observes('model.#each'),
statsData: function(){
console.log('statsData being updated');
...
return someArray;
}.property('model.#each')
The observer and computed property both watch model.#each but for some reason, the observer fires on every model change and the property only updates TWICE before mysteriously going dead. statsData is calculated once on initial page load, and once on the first route transition, then after that, none of the transitions (with the changes in the underlying model they make) affect it.
What's going on here? Shouldn't they respond to change in the same way?
Note that I am using the statsData property in my template.
observers fire immediately, computed's fire as part of the run loop and scheduled in a debounced fashion. Currently all you're watching is that you add or remove an item to the collection, not whether or not a property on one of the items in the collection has changed. If you want to watch a particular property, you need to specify it.
statsData: function(){
console.log('statsData being updated');
...
return someArray;
}.property('model.#each.cost')
if you just want to watch the collection changing you should just use []
statsData: function(){
console.log('statsData being updated');
...
return someArray;
}.property('model.[]')
Thanks to the lovely folks on Ember IRC, I was able to figure it out. The problem was that I was passing statsData to a component, like this: {{common-statistics values=statsData}} and in the component, I had this function:
_validateValues: function(){
var values = this.get('values');
if(!values || !Ember.isArray(values) || values.length === 0)
{
this.set('values',[]);
}
}.on('willInsertElement')
which is, as you can see, setting values if it's not what the component is expecting. Unfortunately, this was affecting statsData on the controller as well, thanks to this JavaScript language feature. By setting statsData in the component, I was breaking the computed property on the controller.
So it was never a problem with Ember at all. I just failed to realize that object properties on Ember objects behave the same way they do on "regular JavaScript objects."

Ember.js - Updating a Bound Property without Triggering Local Observer

I've created an ember component that wraps an editor (CKEditor). The editor's values are updated via setData() and getData() accessors. I want to implement two-directional binding in my ember control so that edits to the component's "content" field flow in and out of the control.
So far, I'm able to get it going one way easily - but my attempts to go bidirectional are very messy. I can set up an observer on the property and have it update the control. However, when I try to set the property when the controller's "change" event is called, it causes the observer to be triggered. That, in turn causes the editors "change" event to trigger and so on. Welcome to Loopy Land.
I know that there are ways to get around this - but everything that I've been trying has me coming up short. It seems hacky - not elegant like the rest of Ember. Can anyone suggest some examples that demonstrates the preferred pattern for this?
Thanks!
--
(Thanks David - Here is some Additional Information)
I've been trying the bound property thing. It works great for outbound updates (from the editor control to another bound textarea on the page) but when inbound the page starts to bog down.
When I initialize the CKEditor, I reference a component that I installed that adds a 'change' event:
editor.on('change', this.updateContent.bind(this));
Here is the update content event:
updateContent: function() {
this.set('_content', this.get('editor').getData());
},
And then, the bound property:
content: function(key, val, previous)
{
if (arguments.length > 1)
{
this.set('_content', val);
var editor = this.get('editor');
if (editor) editor.setData(val);
}
return this.get('_content');
}.property('_content'),
It sounds like you are attempting to update a computed property from your control. If you have a computed property of fullName which depends on firstName and lastName, then it gets confusing if your UI updates the dependencies and not the computed property.
But if you really need to update the computed result, then look at the "Setting Computed Properties" section in the Ember docs (http://emberjs.com/guides/object-model/computed-properties/) and it shows you how you can use the input to the computed property to update its dependencies.
Not sure if this addresses your requirement, but if not pls submit a snippet of what's looping and what needs to be updated.

Missing view.context or templateContext in an Ember event handler

I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)

Categories

Resources