Ember Data - rollback if navigating away from a form - ember.js

My application has new / edit forms for a set of entities read from a backend.
When I open such a form, and fill out / edit some fields, then navigate away, the records appear changed in the entity lists, even though I did not commit those changes. Reloading the app (which reloads the data from the backend) fixes the issue, but is not an option.
I've tried doing some transaction rollbacks in the form view's willDestroyElement, but this seems fundamentally wrong since it gets called even after successful form submits (and actually crashes with Attempted to handle event rollback on X while in state rootState.loaded.updated.inFlight).
How would I go about ignoring all unsubmitted form changes (similar to pressing the Cancel button, which performs a transaction rollback), for any use case that involves navigating away from the forms?
Using Ember rc5, Ember Data 0.13.

When exiting the form route, check the state of the record. If its (isNew OR isDirty) and its NOT isSaving, rollback:
App.FormRoute = Ember.Route.extend({
deactivate: function() {
var model = this.controllerFor('form');
if ( (model.get('isNew') || model.get('isDirty')) && (!model.get('isSaving')) ) {
model.rollback();
}
}
});

Related

EmberJS saving data

My App uses Fixture data (will port to Localstorage later) and I need to implement 'save' method depending on a user click. The user click is an action that maps to the View and from therein the view, it gets transferred to the controller in order to persist info to the model, essentially the template has:
<button {{action 'save' this target='view'}}>Save</button> <!-- note that the 'this' keyword I am sending corresponds to a specific instance of the model that is in a collection, done using an {{#each model}} loop
The view has
actions:{
save:function(card){
// perform some UI activity
this.get('controller').send('save',card); // card representing 'this'
}
}
The controller has:
actions:{
save:function(card){
console.log("controller reached"); // does execute
card.save(); // spits out an error
}
}
Everything works fine however the card.save() method call does not work in the controller. I mean, all I am trying to do is to persist the specific 'card' to my data, but it keeps spitting:
Uncaught TypeError: Object # has no method 'save'
What am I missing here ?
Side notes:
The Controller returns a collection of models
The corresponding'view' for the controller also has the 'edit' partial loaded in it.
So when the user chooses to 'edit' a specific model, it doesn't
transition into a different URL, rather it loads the 'editing' form
within the same URL.
This means that the model in this specific
controller is essentially the collection and I only want to save the
specific model that is being edited.
It appears as if you aren't using any client side record management library (such as ember-data or ember-model). That being said, your instances of App.Card aren't really instances of anything, they are just POJOs and there is no save method defined on a POJO.
It sounds like you'll want to do some research into ember data, or ember model (I'd suggest ember data, http://emberjs.com/guides/models/)
If you don't want to use either, you can just use ajax calls to save data:
save:function(card){
console.log("controller reached"); // does execute
// card; // spits out an error
$.ajax({... , data:$(card), ...});
}
If it's fixture and you have no intention of saving it anywhere, and it's just a dry run, add an ugly alert or log
save:function(card){
console.log("controller reached"); // does execute
console.log('pretend to save ' + card);
alert('pretend to save ' + card);
}

Force ember data store.find to load from server

Is there a nice way to force Ember Data to load the resource from server eaven if it has it already in store ?
I have a simple show user action that do store.find('users',id) the model is loaded only once at first attempt to display a page the second time i go my model is loaded from the store which is normal ember data behaviour i know. However i need to load it each time.
edit:
the only way i found is to do this :
#store.find('user',{id: params.user_id}).then (users)->
users.get('firstObject')
however it forces me to implement a "fake" show action on my index action ...
I think this... http://emberjs.com/api/data/classes/DS.Model.html#method_reload
model.reload()
Good luck
Additionally you can call getById which will return any instance of that record that exists, or null, then call unloadRecord to remove it from the cache. I like Edu's response as well though, then I wouldn't have to worry about the record existing somewhere else. Maybe I'd use getById then reload that way any references that had a reference to the user got updated. (pardon my errant coffeescript, if it's wrong).
user = #store.find('user', params.user_id)
if (user)
#store.unloadRecord(user)
Hot off the presses, thanks to machty:
There's a new method getting added as part of the query params feature going into beta this weekend called Route.refresh()...
/**
Refresh the model on this route and any child routes, firing the
`beforeModel`, `model`, and `afterModel` hooks in a similar fashion
to how routes are entered when transitioning in from other route.
The current route params (e.g. `article_id`) will be passed in
to the respective model hooks, and if a different model is returned,
`setupController` and associated route hooks will re-fire as well.
An example usage of this method is re-querying the server for the
latest information using the same parameters as when the route
was first entered.
Note that this will cause `model` hooks to fire even on routes
that were provided a model object when the route was initially
entered.
#method refresh
#return {Transition} the transition object associated with this
attempted transition
#since 1.4.0
*/
You can do this in the setupController hook, using a promise, and the reload method mentioned by Edu.
setupController: ->
#store.find('myModel', 1).then (myModel) ->
myModel.reload()
If you are sure that records to display will change after a certain action then you can call this.refresh() method in your Route. For example:
ProductsRoute = Ember.Route.extend
model: ->
#store.find 'product',
activated: true
actions:
accept: (product) ->
if not product.get('activated')
product.set 'activated', true
product.save()
.catch (err) ->
console.log err
product.rollback()
.then =>
#refresh()
ignore: (product) ->
if not product.get('ignored')
product.set 'ignored', true
product.save()
.catch (err) ->
console.log err
product.rollback()
.then =>
#refresh()
If actions are called from child route - e.g. products/proposed - models will be reloaded for parent route and also child routes.
I think that what you are looking for is DS.Store#fetchById

Ember Data: Delete a record without removing it from view before commit

Take the following:
FooController = Ember.ObjectController.extend
# Model is set, view is rendered, button is clicked
someDeleteAction: ->
# Object is destroyed & removed from view
#get('content').deleteRecord();
# Uh-oh... error happens here, but object
# remains 'deleted' according to ember
#get('content.transaction').commit();
Is there a way to prevent an object from being destroyed and removed from the view?
Bonus points: On a scale of 1 to Chinese Water Torture... how much fun is ember-data?
My solution:
Override DS.Model.deleteRecord so it contacts the server first, then commit the transaction.

saving form with associated model

It seams simple, but I'm stuck on this one. I have a single controller, and single view dedicated to order model, which has nested client. I create empty records on setupController:
route:
ShowroomApp.OrdersRoute = Ember.Route.extend
model: ->
ShowroomApp.Order.createRecord()
setupController: (controller,model) ->
model.set("client", ShowroomApp.Client.createRecord() )
controller.set("content", model )
controller:
save: ->
#content.store.commit()
On OrdersController i have save action to commit changes made in the form. It results in two separate POST requests each one for each model, but the association doesn't build itself. Orders model saves first, and client_id is obviously null because client doesn't exists yet. Later goes Client post and saves the client, but Order model doesn't know about it and stays without client.
Is there any solution to that?
Thanks,
J
This is due to an outstanding ember-data issue - RESTAdapter: Allow new parent, child to be saved at once
Check out Tom Dale's Comment for a possible workaround. It involves manually adding support for saving both records at once.
As an alternative, you might consider adding a onCreate callback to the parent record, then creating the child in the callback. If you are ok with having a second commit() this will get the job done.

Sitecore Web Forms for Marketers and DMS - not recording campaigns, goals and dropout info

In WFFM there is an option so that, when someone abandons the form, any data that was entered in the form itself is recorded and should be accessible via the Dropout Report.
I have a WFFM for which I have turned on Analytics and turned on the dropout feature. Unfortunately I don't see any data being recorded in the DB and the Dropout Report is visible, but empty.
I see from the javascript code included in the WFFM folder that a series of AJAX calls are supposed to save the fields on blur events -- with calls to /sitecore modules/web/Web Forms for Marketers/Tracking.aspx
I tried debugging the Javascript code, but the method supposed to post the info to /sitecore modules/web/Web Forms for Marketers/Tracking.aspx is never being called. Can you think of any reasons for this code not to work? Also, does anyone know which table this information is supposed to be recorded? Is it the fields table in the WFFM DB?
Finally, even though I have turned on analytics on this particular WFFM form and I have associated a campaign and a goal to the submission of the form, none of these is being recorded. I see that the data entered in the form is stored successfully and is displaying in the Data Report, but no info about the Campaign nor the Goal are recorded in the DB.
I even checked manually directly in the DMS DB running:
select top 10
p.DateTime, p.UrlText, cp.CampaignName
,i.Url, vi.VisitId
from pages p
inner join ItemUrls i on p.ItemId = i.ItemId
inner join Visits vi on vi.VisitId = p.VisitId
inner join GeoIps g on vi.Ip = g.Ip
left join Campaigns cp on cp.CampaignId = vi.CampaignId
order by p.DateTime desc
This one shows that the page where the form is rendered is being hit, but no campaign is associated to the visit.
Then I tried the following:
select pe.datetime, ped.Name, pg.UrlText from PageEvents pe
inner join PageEventDefinitions ped on ped.PageEventDefinitionId = pe.PageEventDefinitionId
inner join Pages pg on pg.PageId = pe.PageId
order by pe.DateTime desc
But I don't see any entry for this particular campaign nor for the goal (while I see entries for other campaigns and goals associated to non-WFFM Sitecore items)
Any advice would be greatly appreciated!
Thanks,
Francesco
EDIT
The sc.webform.js file contains this method:
_create: function () {
var self = this,
options = this.options;
if (options.tracking) {
this.element.find("input[type!='submit'], select, textarea")
.bind('focus', function (e) { self.onFocusField(e, this) })
.bind('blur change', function (e) { self.onBlurField(e, this) });
this.element.find("select")
.change(function () { $scw.webform.controls.updateAnalyticsListValue(this) });
this.element.find("input[type='checkbox'], input[type='radio']")
.click(function () { $scw.webform.controls.updateAnalyticsListValue(this) });
}
this.element.find(".scfDatePickerTextBox").each(function () { $scw.webform.controls.datePicker(this) });
},
This is supposed to be called by the form on sc.webform widget initialization. It should bind the focus and blur change events for all input fields, drop downs and text areas. Unfortunately, when I tried to put a break point inside this method, it never gets called.
SECOND EDIT
Interesting. I figured out that the whole thing should start from this line of Javascript code embedded in the page that contains the WFFM form:
<script type="text/javascript">
$scwhead.ready(function() {
$scw('#form_A8BF483419174F97A2830E12CBCF7E4F').webform({formId: "{A8BF4834-1917-4F97-A283-0E12CBCF7E4F}",pageId: "{21C24144-B964-4FBA-8388-D9B90EBBC17C}",eventCountId: "pagecolumns_0_columncontent_0_bottomrow_0_form_A8BF483419174F97A2830E12CBCF7E4F_form_A8BF483419174F97A2830E12CBCF7E4F_eventcount",tracking: true})
});
</script>
Once I put a break point here, I was finally able to trace into the _create method of the jQuery.UI widget defined in sc.webform.js. The code that calls _create is actually inside the jQuery.UI library. Kinda makes sense, right?
Finally, the code inside _create is executed, the blur events are bound to the TrackEvents method, also defined within the widget:
_trackEvents: function(events) {
$scw.ajax({
type: 'POST',
url: "/sitecore modules/web/Web Forms for Marketers/Tracking.aspx" + location.search,
data: {track: JSON.stringify(events)},
dataType: 'json'
});
What doesn't make sense is that now, even though I can finally see trackEvents being called whenever I tab from field to field in the WFFM form (why wasn't working before it's a mistery to me), I don't see any data recorded in the WFFM DB. I even tried a quick query in the DB:
select f.Timestamp, f.StorageName, fi.Value, fi.FieldName
from Form f
inner join Field fi on f.Id = fi.FormId
order by f.Timestamp desc, FieldName
Does anybody know where is Tracking.aspx supposed to save the captured field informations?
This may be silly to ask, but did you configure the data source correctly for your WFFM? I mean, obviously, you're using WFFM..but is it set to use SQL or is it using the "file" that WFFM uses by default as it's database.
like this to use SQL:
<!-- MSSQL-->
<formsDataProvider type="Sitecore.Forms.Data.DataProviders.WFMDataProvider,Sitecore.Forms.Core">
<param desc="connection string">Database=Sitecore_WebForms;Data Source=xxx;user id=xxx;password=xxx;Connect Timeout=30</param>
</formsDataProvider>
<!-- SQLite -->
<!--<formsDataProvider type="Sitecore.Forms.Data.DataProviders.SQLite.SQLiteWFMDataProvider,Sitecore.Forms.Core">
<param desc="connection string">Data Source=/data/sitecore_webforms.db;version=3;BinaryGUID=true</param>
</formsDataProvider>-->
If you don't configure that correctly, I'm wondering if somehow data is being recorded in one place but not another? Also, another question I have is to ask if this is a dev environment, are you running webforms in live mode? It just seems to me like this is a configuration issue.
We are experiencing the exact same problem on 6.5 update 6 and WFFM 2.3.3 rev. 111209. We can see the asynchronous calls to the server including the probably well formed json object containing the correct event.
Example:
track:[{"fieldId":"{E0A0BCDD-85E1-4D8D-9E76-5ABD240423C9}","type":"Field Completed","value":"test","formId":"{0F3B57C1-1B6A-43B9-A5A6-2E958C168B31}","pageId":"{025AFF68-62B9-42CE-B49F-0C36311E1976}","ticks":16}]
We don't see any of the dropouts arrive in the database, though...
Have you made sure your campaigns and goals have been deployed? If you have switched databases they may not be. To redeploy do this:
For each Goal in System -> Marketing Center -> Goals
Change the workflow state to draft
Save
Then in the review ribbon click deploy.
This will create an entry in the pageeventdefinition table and allow
you to query.
Don't forget to do the same for campaigns.