Is there a way to suppress model validation in Backbone.js when a new model is first created?
In my app, I have a collection with an arbitrary number of models, which are represented as list items. The user can click a button on each item, which inserts a new empty item beneath the current item. The empty item is failing validation, obviously, because I don't want an empty item being saved later.
There's no way for me to know what sensible defaults might be when I'm creating the new item, so prepopulating the new model with valid data doesn't seem like an option.
Any suggestions?
Update: While working on a tangentially related problem, I realized that I was using Backbone.js version 0.9.0. When this version was released, other people had the same problem I was having, and they complained in this issue on GitHub.
Jeremy modified validation in 0.9.1 to fix this. Adding a (temporarily) empty model to a collection is a valid real-world usecase. You could handle the new, empty model in the view, but if you're managing a list of items like I am, that forces you to have a collection of item views (including the empty one) in addition to your collection of must-be-valid models. That's a really clunky workaround for an otherwise straightforward scenario. Glad this got fixed.
You're not supposed to add invalid models :)
Digging a bit in Backbone source code (0.9.1 at least) showed that the mechanism can be circumvented by passing options to your add method:
var Mod=Backbone.Model.extend({
validate: function(attrs,opts) {
if (opts.init) return;
return "invalid";
}
});
var Col=Backbone.Collection.extend({
model:Mod
});
var c=new Col();
c.add({},{init:true});
console.log(c.length);
A Fiddle: http://jsfiddle.net/jZeYB/
Warning : it may break things down the line.
Do you need to add the model to the collection right away? I presume that validation fails because you add it to the collection immediately.
Instead, when the button is pressed you could just create the view and blank model. When the model validates you add it to the collection. You would need a submit button/mechanism on the new row to add it to the collection (which invokes validation automatically).
Related
I have a database with 10,000 items, to which you can add and remove while the app is running.
I have a ListBox that displays at most 100 items, and supports paging.
You can filter and sort on the 10,000 items, which needs to be immediately reflected in the listbox.
I have a button that randomly selects an item as long as it passes the filters.
What is the best set of collections/views to use for this kind of operation?
So far, my first step will be to create an ObservableCollection of ALL items in the database which we will call MainOC.
Then create a List of all items that match the filter by parsing MainOC which we will call FilteredList.
Then create a ListCollectionView based on the above List that holds the first 100 items.
CONS:
You have to recreate the ListCollectionView every time a sort operation is applied.
You have to recreate the ListCollectionView every time you page.
You have to recreate the ListCollectionView every time a filter is changed.
You have to recreate the ListCollectionView every time an item is added or removed to MainOC.
Is there a better approach that I am missing?
For example, I see that you can apply filters to a ListCollectionView. Should I populate my ListCollectionView with all 10,000 items? But then how can I limit how many items my ListBox is displaying?
Should I be doing my filtering and sorting directly against the database? I could build FilteredList directly off the database and create my ListCollectionView based off that, but this still has all the cons listed above.
Looking for any input you can provide!
This is a problem which is easily solved using DynamicData . Dynamic data is based on rx so if you are not familiar with the wonderful Rx I suggest you start learning it. There is quite a bit of a learning curve but but the rewards are huge.
Anyway back to my answer, the starting point of dynamic data is to get some data into a cache which is constructed with a key as follows
var myCache = new SourceCache<MyObject, MyId>(myobject=>myobject.Id)
Obviously being a cache there are methods to add, update and remove so I will not show those here.
Dynamic data provides a load of extensions and some controllers to dynamically interrogate the data. For paging we need a few elements to solve this problem
//this is an extension of observable collection optimised for dynamic data
var collection = new ObservableCollectionExtended<MyObject>();
//these controllers enable dynamically changing filter, sort and page
var pageController = new PageController();
var filterController = new FilterController<T>();
var sortController = new SortController<T>();
Create a stream of data using these controllers and bind the result to the collection like this.
var mySubscription = myCache.Connect()
.Filter(filterController)
.Sort(sortController)
.Page(pageController)
.ObserveOnDispatcher() //ensure we are on the UI thread
.Bind(collection)
.Subscribe() //nothing happens until we subscribe.
At any time you can change the parameters of the controllers to filter, sort, page and bind the data like follows
//to change page
pageController.Change(new PageRequest(1,100));
//to change filter
filterController.Change(myobject=> //return a predicate);
//to change sort
sortController .Change( //return an IComparable<>);
And as if by magic the observable collection will self-maintain when any of the controller parameters change or when any of the data changes.
The only thing you now have to consider is the code you need for loading the database data into the cache.
In the near future I will create a working example of this functionality.
For more info on dynamic data see
Dynamic data on Github
Wpf demo app
Breeze & Angular & MV*
I get an invoice object and expand it's necessary properties: Customer, Details, etc.
To access detail properties is easy, invoice.detail[n].property. And saving changes to existing properties (1 - n) is also easy. In my UI, I simply loop through my object vm.invoice.details to get & display all existing details, bind them to inputs, edit at will, call saveChanges(), done!
(keep in mind, in this UI, I need to complete the following too....)
Now, I have blank inputs for a new detail I need to insert.
However, I need to insert a new detail into the existing array of invoice details.
For example: invoice #5 has 3 details (detail[0], detail[1], detail[2]). I need to insert into this existing invoice, detail[3], and call saveChanges()
I've tried to call the manger.createEntity('invoice') but it complains about FK constraints. I know you can pass values as a second argument in createEntity('obj', newvalues)...but is that the correct and only method?
Seems like this should all be much easier but, well, I am at a loss so, please help where you can. TIA!
Take a look at the DocCode sample which has tests for all kinds of scenarios including this one.
Perhaps the following provides the insight you're looking for:
function addNewDetail() {
var newDetail = manager.createEntity('Detail', {
invoice: vm.currentInvoice,
... other initial values
});
// the newDetail will show up automatically if the view is bound to vm.details
}
Notice that I'm initializing the parent invoice navigation property. Alternatively, I could just set the Detail entity's FK property inside the initializer:
...
invoiceId: vm.currentInvoice.id,
...
Either way, Breeze will add the new detail to the details collection of the currentInvoice.
Your question spoke in terms of inserting the new Detail. There is no need to insert the new Detail manually and you can't manage the sort order of the vm.currentInvoice.details property any way.
Breeze has no notion of sort order for collection navigation properties.
If you need to display the details in a particular order you could add a sorting filter to your angular binding to vm.currentInvoice.details.
Make sure you have correct EntityName, because sometimes creating entity is a not as simple as it seems.Before working with entities see
http://www.getbreezenow.com/documentation/creating-entities
I will suggest you to look ur metadata file, go to last line of your file, you can see the field named "entitySet"
"entitySet":{"name":"Entity_Name","entityType":"Self.Entity_Name"}
check the entityName here i took as "Entity_Name" and then try to create the entity and use this name
manger.createEntity('Entity_Name');
I have a collection, and the collection.models returns an array of models. However, when I call collection.get(someId) (and this id is the id of the model that is in the collection.models array), I get undefined. Looking at collection._byId, it looks like an empty object.
How do I properly populate _byId, so that I can use get? Or perhaps I'm doing something wrong when initializing my collection, which is why _byId is empty.
I'm a little late, but hopefully this is still useful to some other people.
Collection._byId is just a normal js hash object. There's really nothing fancy about it. If you want Collection.get to work, just add all the models into the _byId hash.
Inside the collection's scope:
var someId = '123'; // any id will do
this._byId[someId] = someModel; // someModel.id = '123'
console.log(!!this.get(someId)); // should return true
Since I'm using this with Rails, the default json generated by Rails doesn't work well with Backbone. I don't know why I didn't see it while trying to learn Backbone. Anyway, you could either:
Change the way Rails generates its JSON
Change the way your Backbone app reads the JSON.
Sounds like the OP had a slightly different problem, but I experienced a similar issue and thought I'd post what worked for me.
Like the original issue, collection.models contained the right model, but in my case, the _byId hash contained a cid version of the model that wasn't empty. Nevertheless, _byId didn't contain a model with normal id (there's usually two version - an id one and a cid one), so I wasn't able to use collection.get(id) to retrieve it. My problem became a bit clearer when I read up about cid. From the docs:
Client ids are handy when the model has not yet been saved to the server, and does not yet have its eventual true id, but already needs to be visible in the UI.
I didn't think it was a problem with waiting for the server as my cid model and the collection.model had the correct ids. However passing in { wait : true } as an option in collection.create fixed this issue for me.
I have a button that when clicked, will create a JSONstore using a url provided. The store is then loaded into a grid. If the button is clicked again, it adds all the information again (so it is listed twice). I want it so that when the user clicks the button, it clears the store/grid and then adds everything.
Any ideas on how to accomplish this?
Thanks,
EDIT
ExtJS 3
datasetStore.removeAll();
datasetStore.loadData(datasetsArray);
It will be useful to see some code examples (and extjs version), but you can simply use loadRecords method (http://docs.sencha.com/ext-js/4-0/#!/api/Ext.data.JsonStore-method-loadRecords):
grid.store.loadRecords([array of your new records here], {addRecords: false});
{addRecords: false} indicates that existing records will be removed first.
for ExtJs4: simply doe store.loadRecords([ array ]). In version 4.2.2 the store.proxy has NO clear() method so that doesn't work (was proposed in other examples elsewhere...)
If you want to to totally clear store and proxy, pass an empty array. This is handy because in some cases you want to clear the store and removeAll only moves the data to an array managed internally in the store so when later doing a sync on a gridStore which shows only 1 record, you may see your controller flooded with a bunch of records marked for removal!!
I am building a silverlight app. My requirement is to have Users select a number of skills that apply to them, from a larger list of skills.
Tables: Candidate => CandidateSkills; SkillsCategories => Skills. I think the schema is self explanatory. The front end will show all skills (grouped into different categories), and when the candidate logs in, only his selected skills will show in the check boxes. Fairly simple.
My question: Do I bring all the Skill entities to the front end and then get the CandidateSkill entities, loop through them and set the checkboxes accordingly or is their a easier/better way?
Thanks
I recommend building a class to use as a ViewModel. The class should contain at least a property to indicate whether the item is selected, the text to present, and either the model entity itself or its key.
You can create the set of view model objects by left-joining the set of all skills to the individual candidate's skills, and setting IsSelected to the result of a non-null test on the candidate skill.
You can then bind directly to the ViewModel.
I had a similar situation (Users to Permissions instead of Candidates to Skills) once, and I used this resource as a starting point. I hope it helps.
In my case, I had a "Save" button which, upon click, would run some code-behind code to iterate through the selected items and submit them to my Web service. Without knowing the details of your data and service implementation, I'll not clutter up the post with the nitty-gritty details.
Best of luck!
Comments for Discussion
Here is a pseudo-LINQ procedure creating view models by issuing two database calls:
var userskills = database.CandidateSkills
.Where(cs => cs.UserId == someUserId)
.Select(cs => cs.SkillId)
.ToList();
var skills = from s in database.Skills
select new CandidateSkillViewModel()
{
Text = s.SkillName,
IsSelected = userskills.Contains(s.SkillId),
Value = s.SkillId
};
mylist.ItemsSource = skills;
This would give you a bindable data source. Ultimately, using this pattern, you'll have to translate selections/deselections into inserts/deletes by hand. For me, I do this in the handler for the button click. I retrieve a fresh set of candidate skills, iterate through the items of the list, and insert/delete instances of CandidateSkill as needed.
I realize that depending on a button click to resolve my viewmodel state into database operations might not be considered by purists to be complete MVVM, but it worked for me.
I hope this helps a little more.