Checking for any dirty Backbone model data within collection - javascript

I have a requirement to "nag" a user about unsaved changes when they switch between different Backbone collection models (by clicking on a table row). I've googled for "check backbone model dirty data" (for instance) and not found anything definitive.
I accomplished this using underscore's "some" and isEqual functionality, in a manner such as the following, "some()" being sufficient to determine if there are any un-saved changes (as opposed to what those precise changes might be), in particular because the model attribute is actually an array of objects.
var anyDirty = _.some(myCollection.models, function(model) {
return !_.isEqual(model.get('nodes'), model.previousAttributes()['nodes]);
});
I am new to Backbone and am wondering if this is an accepted sort of approach for adhoc checking for dirty model data. Or, does Backbone provide some sort of built in functionality for this purpose, that my initial attempts at googling did not reveal?

I have another attribute I need to monitor in addition to 'nodes', so I'm switching to using changedAttributes(): http://backbonejs.org/#Model-changedAttributes:
var anyDirty = _.some(myCollection.models, function(model) {
return model.changedAttributes();
});
What may make this an imperfect solution is that it seems like it will return an object of changedAttributes even if the attribute got changed back to it's original value. So it almost seems that what I need in the long run is to take a snapshot of the original data and compare against that. Still though, using model.changedAttributes() is a more concise alternative to what I first posted.

Related

Are single-lettered word models supported in EmberJS?

I cannot really pinpoint whether the issue is with Ember or Ember data, or if it's even an issue, but here's what happens:
Let's say you've got your model called tell_me_a_story. This will be the name that your JSON should provide, should you be using ActiveModelAdapter.
Regardless, when Ember or Ember Data process it internally, it'll camelize it and it becomes tellMeAStory, correctly indicating that "A" and "Story" are two separate words.
However, when internally it is decamelized to lookup for the model, the decamelize function will convert it into tell_me_astory.
This final behavior seems flawed to me, but when looking at the tests that derived this behavior, it is actually intended to manage acronyms in that fashion. (Compare the following example with the "innerHtml" that I would expect for camel casing multi-letter acronyms.)
QUnit.test("converts a camelized string into all lower case separated by underscores.", function() {
deepEqual(decamelize('innerHTML'), 'inner_html');
if (Ember.EXTEND_PROTOTYPES) {
deepEqual('innerHTML'.decamelize(), 'inner_html');
}
});
(Source in Ember Runtime)
So, which is the correct way to use single-letter words in models with Ember? Are they even supported?
Here's an example of what I'm trying to do:
// this comes from a separate data source, e.g. REST APIs
var myModelName = 'tell_me_a_story';
// this line will throw if the model file is named "tell-me-a-story"
if (!store.getById(myModelName, 1)) {
// this line will throw if the model file is named "tell-me-astory"
store.pushPayload(myModelName, myObject);
}
You can override the stores _normalizeTypeKey then alter the camelCase behaviour to become what you want (e.g. dasherized or just fix this one case).
You can also override the serialisers typeForRoot when going the other way - this lets you tell ember what the model key is (e.g. tellMeAStory) for a particular key in your data (e.g. tell_me_a_story).
It appears there is work underway to make everything work like the container does (which is dasherized)

Parse.com relations count

I want to query object from Parse DB through javascript, that has only 1 of some specific relation object. How can this criteria be achieved?
So I tried something like this, the equalTo() acts as a "contains" and it's not what I'm looking for, my code so far, which doesn't work:
var query = new Parse.Query("Item");
query.equalTo("relatedItems", someItem);
query.lessThan("relatedItems", 2);
It seems Parse do not provide a easy way to do this.
Without any other fields, if you know all the items then you could do the following:
var innerQuery = new Parse.Query('Item');
innerQuery.containedIn('relatedItems', [all items except someItem]);
var query = new Parse.Query('Item');
query.equalTo('relatedItems', someItem);
query.doesNotMatchKeyInQuery('objectId', 'objectId', innerQuery);
...
Otherwise, you might need to get all records and do filtering.
Update
Because of the data type relation, there are no ways to include the relation content into the results, you need to do another query to get the relation content.
The workaround might add a itemCount column and keep it updated whenever the item relation is modified and do:
query.equalTo('relatedItems', someItem);
query.equalTo('itemCount', 1);
There are a couple of ways you could do this.
I'm working on a project now where I have cells composed of users.
I currently have an afterSave trigger that does this:
const count = await cell.relation("members").query().count();
cell.put("memberCount",count);
This works pretty well.
There are other ways that I've considered in theory, but I've not used
them yet.
The right way would be to hack the ability to use select with dot
notation to grab a virtual field called relatedItems.length in the
query, but that would probably only work for me because I use PostGres
... mongo seems to be extremely limited in its ability to do this sort
of thing, which is why I would never make a database out of blobs of
json in the first place.
You could do a similar thing with an afterFind trigger. I'm experimenting with that now. I'm not sure if it will confuse
parse to get an attribute back which does not exist in its schema, but
I'll find out, by the end of today. I have found that if I jam an artificial attribute into the objects in the trigger, they are returned
along with the other data. What I'm not sure about is whether Parse will decide that the object is dirty, or, worse, decide that I'm creating a new attribute and store it to the database ... which could be filtered out with a beforeSave trigger, but not until after the data had all been sent to the cloud.
There is also a place where i had to do several queries from several
tables, and would have ended up with a lot of redundant data. So I wrote a cloud function which did the queries, and then returned a couple of lists of objects, and a few lists of objectId strings which
served as indexes. This worked pretty well for me. And tracking the
last load time and sending it back when I needed up update my data allowed me to limit myself to objects which had changed since my last query.

Angular input model conditionally reads from one property, writes to another

I have a Person class where edits made to the person must be verified by an admin user.
Each attribute has an "approved" and "tmp" version. Sometimes the "tmp" version is not set:
person = {first:'Bob', firstTmp:'Robert', last:'Dobbs', lastTmp:undefined}
When displaying the person, I want to display the "tmp" value if it is set, otherwise display the "approved" value. When writing, I want to write to the "tmp" value (unless logged in as an admin).
Ideally, this would not require a lot of custom markup, nor writing cover methods for each property (there are around 100 of them). Something like this would be nice:
<input ng-model="person.first"
tmp-model="person.firstTmp"
bypass-tmp="session.user.isAdmin" />
When displaying the value, display the tmp value if it is defined. Otherwise display the approved value.
When writing the value, write to the tmp value, unless logged in as an admin. Admins write directly to the approved value.
What's a good clean way to implement this in Angular?
Extend NgModelController somehow?
Use a filter/directive on the input?
Cover methods?
Just do the writing server-side?
I will try to go through your options one by one:
Extend NgModelController somehow?
I don't think this is a good idea. It won't be nice if something goes wrong and you don't know if you can even rely on something as basic as ng-model
Just do the writing server-side?
This would seem like the easier way (if you already know or find it easy to manage it in the back end), although the interaction would need a new request to the server.
Use a filter/directive on the input?
I believe this would be the best way to do it, as it is easy to understand what is going on by just taking a look at the markup. It's angular, you already know that some property like tmp-model is extending the markup.
Cover methods?
This would also be easy to implement, and you would be implementing some sort of "business logic" as a validator in your cover method.
Given that I've extended a bit in my answer, I can give you an inline example of the last one.
<input ng-model="person.firstTmp"
ng-init="person.firstTmp = person.firstTmp || person.first"
ng-change="updateProperty(person, 'first')" />
And on the controller, you could do something like:
$scope.updateProperty = function(person, propertyName) {
// The temporary property has already been changed, update the original one.
if($scope.session.user.isAdmin)
person[propertyName] = person[propertyName + 'Tmp'];
}

KnockoutJS: Stop a particular property from setting dirty flag

With some help from StackOverflow community I was able to get my dirty flag implementation to work, based on this example: http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html
It does exactly what I want, except for a single use case that I don't know how to solve.
Basically I have a select menu that gets automatically populated from the database. This select menu also has an option to make an Ajax call to my back end and have the list of options refreshed, database updated and return the result. This is where things get hairy for me.
First method works fine, however, it has to re-index and re-apply my entire viewModel and takes about 2-3 seconds, running on a local machine with 16gigs of ram and SSD.
jsondata.component.available_tags = result.available_tags;
ko.mapping.fromJS(jsondata, viewModel);
Second method also works, and pretty much instantaneous, however, it sets of isDirty() flag, which I would like to avoid, because this data is already coming from the database and I wont need to save it. I can not use isDirty.reset() method either, because if isDirty was set by something else before I clicked an menu option to update available_tags, it will reset that too. Which I would also like to avoid.
viewModel().component.available_tags(result.available_tags);
My question is: With the first method, can I force UI refresh with ko.mapping.fromJS() on a particular element and not entire dataset? Or, with a second method, can I avoid setting isDirty flag set when available_tags are updated? The twist is that I still need to keep available_tags as an observable, so the select menu is automatically generate/updated.
UPDATE: I was able to update mapping for that one single element with
ko.mapping.fromJS(result.available_tags, {}, viewModel().component.available_tags);
but that immediately set off isDirty flag... Argh
In addition to Tomalak's suggestions, which I totally agree with, maybe the toJSON method can help you out in similar cases where you don't want to split the model. If your dirty flag implementation uses ko.toJSON as a hash function, as Ryan Niemeyer's does, you can give your model (on which the dirty flag is active) a toJSON method, where you do something like this:
function MyObjectConstructor() {
this.someProperty = ko.observable();
this.somePropertyNotUsedInDirtyFlag = ko.observable();
}
MyObjectConstructor.prototype.toJSON = function () {
var result = ko.toJS(this);
delete result.somePropertyNotUsedInDirtyFlag;
return result;
};
Please be aware that this is also used to serialize the object in some other occassions, such as ajax calls. It's generally a handy function for removing computeds and such from your objects before using them in a different context.

With Backbone.js, how to Bind to an array's changes w/o needing to manually trigger?

I'm creating an invitation dialog that allows users to enter emails. Currently in the model I am creating an array to hold the emails:
initialize : function() {
this.model.set({
invite_email_array : new Array()
});
}
And then I'm adding/removing items in the view like so:
this.model.get('invite_email_array').push('email#domain.com');
Then problem is the binder is not being triggered when I either add or remove an email from the model. Here is my binder:
binder : function() {
model.on("change:invite_email_array", onInviteEmailArrayChange() )
}
The only way I was able to get the binding to trigger was to trigger it manually when I make updates.. an ugly hack
this.model.trigger("change:invite_email_array");
Any suggestions on a better way to maintain a list of emails and then be able to bind to the object on add/removes?
Thanks
In the example you give, you're bypassing set by altering the array directly. In order to trigger the change, you would need to set the altered array after pushing the new e-mail. Something to the effect of:
var arr = _.clone(this.model.get('invite_email_array'));
arr.push('email#domain.com');
this.model.set({ invite_email_array: arr });
As soon as you've introduced an array, however, it may be worth considering whether the view's design is really reflecting its intent. Collections (or arrays) of anything often signal that it's time to consider simplifying models or views. Even though a single e-mail seems too trivial assign to its own view/model, it may make sense to track an array of e-mails as a collection of "invitation" views and watch for changes accordingly.

Categories