Backbone.js - A "url" property or function must be specified - javascript

I read all the topics on here about the argument but I can't understand what's with this code, is some hours I'm trying to get a sense of it:
It says "Uncaught Error: A "url" property or function must be specified" when I fire events save and remove from the TranslationView.
I tried to figure out other codes but even adding explicitly the url property to the collection it doesn't work... Thank You in advance
/**
* Translation Collection - The document
* -- Collection of all translations in a document
*/
var Document = Backbone.Collection.extend({
model: Translation,
localStorage: new Backbone.LocalStorage("translations-db")
});
var Docs = new Document;
/**
* Translation View
* -- A single language version
* This is a version of translation
*/
var TranslationView = Backbone.View.extend({
template: _.template('<div class="cnt-translation"><span class="delete-btn">delete</span><span class="save-btn">save</span> Language: <input value="english" /><textarea id="translation_0" class="translation"></textarea></div>'),
events: {
'click span.delete-btn': 'remove',
'click span.save-btn': 'save'
},
//'chnage ul#main-menu #add': 'addText'
initialize: function(){
_.bindAll(this, 'render', 'unrender', 'remove','save');
this.listenTo(this.model, 'destroy', this.remove);
},
render: function(counter){
this.$el.html(this.template(this.model.toJSON()));
return this;
},
unrender: function(){
$(this.el).remove();
},
remove: function(){
console.log(this.model);
this.model.destroy();
},
save: function(){
console.log(this.model);
this.model.save();
console.log(localStorage);
}
});
/**
* Translation Main View
* -- The Application
* This is the top level piece of the app
*/
var AppView = Backbone.View.extend({
el: $('#application'),
type: 'localStorage', // in future also "remoteStorage"
events: {
'click #add_trans': 'createOnEnter',
'click #save_trans': 'saveTranslations',
'click #remove_trans': 'removeTranslation'
},
initialize: function(){
_.bindAll(this,
'render',
'saveTranslations',
'addTranslation'
);
this.listenTo(Docs, 'add', this.addTranslation);
this.listenTo(Docs, 'all', this.render);
this.listenTo(Docs, 'reset', this.reloadAll);
this.render();
console.log('initialized and texts loaded');
Docs.fetch();
},
....
render: function(){
var self = this;
/*
_(this.collection.models).each(function(translation){
self.appendTranslation(translation);
}, this);
*/
}
addTranslation: function(){
console.log('addTrans called');
var translation = new Translation();
translation.set({
id: 'translation_' + Docs.length,
language: 'english' // modify item defaults
});
var translationView = new TranslationView({ model: translation });
$(this.el).append(translationView.render().el);
console.log(Docs);
},
createOnEnter: function(e) {
Docs.create({title: 'new trans'});
}
});
var ENTER_KEY = 13;
var app = new AppView();
console.log(app);
})(jQuery);

Your problem is that you try to save/destroy a model object which was never associated to your local storage backed collection.
The local-storage plugin first looks for the localStorage property on the model if it finds none it looks on the model's collection for the localStorage if still no localStorage found it fallbacks to the default Backbone.Sync behaior which needs an url so you get the exception.
And you have an unassisted model object because you create one in your addTranslation:
var translationView = new TranslationView({ model: translation });
But you don't need to this because this method called when an item added to your collection and you get the newly added item as a parameter.
You just need to change your method use the parameter translation instead of creating a new one.
addTranslation: function(translation){
translation.set({
id: 'translation_' + Docs.length,
language: 'english' // modify item defaults
});
var translationView = new TranslationView({ model: translation });
$(this.el).append(translationView.render().el);
},

Related

Wrong backbone collection length. Can't each this collection

Sorry for my bad English. Tell me why the following happens:
I have some backbone collection:
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
success: this.fetchSuccess(this),
error: this.fetchError
});
},
fetchSuccess: function( collect_model ) {
new BackgroundsView ({ collection : collect_model });
},
fetchError: function() {
throw new Error("Error fetching backgrounds");
}
});
And some view:
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.render();
},
render: function() {
console.log(this.collection);
this.collection.each( function (background) {
console.log(background);
//var backgroundView = new BackgroundView ({ model: background });
//this.$el.append(backgroundView.render().el);
});
}
});
now i creating collection
var backgrounds = new Models.Backgrounds();
but when I must render this view, in the process of sorting the collection its length is 0, but should be two. This log I see at console. How is this possible? What am I doing wrong??
You are creating the view before the collection fetch is successfull. Your code should be:
initialize: function() {
this.fetch({
success: this.fetchSuccess,
//------------------------^ do not invoke manually
error: this.fetchError
});
},
fetchSuccess: function(collection, response) {
new BackgroundsView ({ collection : collection});
},
You should let backbone call fetchSuccess when the fetch succeeds. Right now you're invoking the funcion immediately and passing the return value undefined as success callback.
This looks like a wrong pattern. Your data models shouldn't be aware of/controlling the presentation logic.
You have a view floating around without any reference to it. You should be creating a view instance with reference(for example from a router, or whatever is kick starting your application) and passing the collection to it. Then fetch the collection from it's initialize method and render after the fetch succeeds. Collection can be referenced via this.collection inside view.
Alternatively you can fetch the collection from router itself and then create view instance. Either way collection/model shouldn't be controlling views.
If the code is structured in the following way, the problem is solved. It was necessary to add a parameter reset to fetch.
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
reset : true,
});
}
});
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.collection.each( function (background) {
var backgroundView = new BackgroundView ({ model: background });
this.$el.append(backgroundView.render().el);
}, this);
$('#view_list').empty();
$('#view_list').append(this.$el);
return this;
}
});

Backbone collection change event fires, but nothing changes in the view

I am running the following view:
app.OrganisationTab = Backbone.View.extend({
el : "#organisations",
template : _.template( $("#tpl-groups-list").html() ),
events : {
"click .js-edit-group" : "editGroup"
},
initialize: function() {
this.listenTo(this.collection, 'change', this.change);
var that = this;
this.collection.fetch({
success: function() {
that.render();
}
})
},
change: function() {
//this.$el.empty();
console.log("collection has changed");
},
render:function() {
this.$el.empty();
this.addAll();
return this;
},
addAll: function() {
this.collection.each(this.addOne, this);
},
addOne: function(model) {
var view = new app.GroupEntry({
model: model
});
this.$el.append(view.render().el);
},
editGroup: function(e) {
e.preventDefault();
var elm = $(e.currentTarget),
that = this;
$('#myModal').on('hidden.bs.modal', function () {
$('.modal-body').remove();
});
var organisation = this.collection.findWhere({ id : String(elm.data('groupid')) });
var members = organisation.get('users');
organisation.set('members', new app.UserCollection(members));
var projects = organisation.get('projects');
organisation.set('projects', new ProjectCollection(projects));
var orgForm = new app.createOrganisationForm({
model : organisation,
});
$('#myModal').modal({
backdrop: 'static',
keyboard: false
});
}
});
This view triggers a new view, and in that I can change a model save it (sends a PUT) and I can get in my console, collection has changed. If I console.log this collection I can see that the collection has changed. If I try and re-render the page all I see are the models as they were without the edits.
Why would this be happening, when clearly the collection is getting changes as it fires the events and I can see it when I log the collection?
After reading your comment:
No sorry on collection change I try to run render() which should empty
the container, and add all the models...but it seems to render the old
collection again.
You're getting this problem because you are overriding the success handler for the fetch call. That success callback is triggered before the models are placed in the collection. You need to listen to the sync event if you want render after the collection has been synchronized with the server (models are updated after fetch).
Update initialize to:
initialize: function() {
this.listenTo(this.collection, 'change', this.change);
this.listenTo(this.collection, 'sync', this.render);
this.collection.fetch();
},

BackboneJS Uncaught Error: A "url" property or function must be specified

I am getting this error . I am able to preform read, and remove functions using BackboneJs , but i am having error when i execute the add method any help will be appreciated.
JSfiddel path is http://jsfiddle.net/2wjdcgky/
BackboneJS Uncaught Error: A "url" property or function must be specified
$(function() {
Model
var modelContact = Backbone.Model.extend({
defaults: function() {
return {
Id: 0,
Name: "",
Address: ""
};
},
idAttribute: "Id"
});
ModelCollection
var contactCollection = Backbone.Collection.extend({
model: modelContact,
url: function() {
return 'api/Contact';
},
add: function(model) {
this.sync("create", model); // Error On create
},
remove: function(model) {
this.sync("delete", model); //Runs Fine
}
});
var contacts = new contactCollection;
View
var contactView = Backbone.View.extend({
tagName: "tr",
events: {
"click a.destroy": "clear"
},
template: _.template($("#newContacttemplate").html()),
initialize: function() {
this.model.on("change", this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
clear: function(e) {
contacts.remove(this.model); // runs fine
}
});
Main View
var main = Backbone.View.extend({
el: $("#contactApp"),
events: {
"click #btnsave": "CreateNewContact"
},
initialize: function() {
this.Nameinput = this.$("#contactname");
this.Addressinput = this.$("#contactaddress");
contacts.on("add", this.AddContact, this);
contacts.on("reset", this.AddContacts, this);
contacts.fetch();
},
AddContact: function (contact) {
console.log("AddContact");
var view = new contactView({ model: contact });
this.$("#tblcontact tbody").append(view.render().el);
},
AddContacts: function () {
console.log("AddContacts");
contacts.each(this.AddContact);
},
CreateNewContact: function (e) {
console.log(e);
//Generate an error "BackboneJS Uncaught Error: A "url" property or function must be specified"
contacts.add({ Name: this.Nameinput.val(), Address: this.Addressinput.val() });
}
});
var m = new main;
});
Your JSFiddle was missing Backbone references and all.
Working update: http://jsfiddle.net/apt7hchL/2/
Much simpler code (no need to define those add and remove methods on the collection!). Also more common Javascript coding style conventions.
Please note I had to manually generate an "Id" attribute to allow creating more than one contact. As you are making Id = 0 by default, second model with same is not added, as Backbone sees a model with id=0 is already in the collection.
When you want to save, call the model.save() method. Don't call sync manually, you'll normally don't need to!
For the model to be saved to the database before being added to the collection, use:
createNewContact: function (e) {
e.preventDefault();
var self = this;
var newContact = new ContactModel({
Name: this.$("#name").val(),
Address: this.$("#address").val()
});
newContact.save({ success: function(model){
self.collection.add(model);
});
//clear form
this.$("#name").val("");
this.$("#address").val("");
}
Sync method tries to sync to a server setup to handle it, with CRUD abilities. If thats not what you're looking for, and you just want to display this information on the client side, instead of using sync, you should use Collection.add(model) and Collection.remove(model)

Memory leak when filtering a backbone collection

I have a filter working on my backbone collection. Type a search in the search box and the list live filters. Works great, or so I thought. When I looked at the memory heap snapshot in chrome, I can see the memory leaking with each search... 6 megs 8 megs... before long the heap snapshots are 100+ megs.
I have isolated the problem in the view below. If I comment out the this.listenTo in the initialize function I no longer seem to leak memory.
So my question is how do I keep these event listeners and the live filtering on the collection without leaking.
var View = Backbone.View.extend({
tagName: 'tr',
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
events: {
'click .edit': 'edit',
'click .delete': 'delete',
},
edit: function() { /* EDIT */ },
delete: function() {
this.model.destroy(); //backbone
},
render: function () {
var template = _.template( ProductTemplate )
this.$el.html( template({ this.model.toJSON() }) )
return this;
}
})
var ListView = Backbone.View.extend({
initialize: function()
{
this.collection = new Collection( Products ) //products are bootstrapped on load
},
render: function (terms)
{
this.$el.html( ListTemplate );
var filtered = Shop.products.collection.search(terms)
_.each(filtered, this.addOne, this)
//append list to table
$('#products').html( this.el )
return this
},
addOne: function (product)
{
this.$el.find('tbody').append(
new View({ model: product }).render().el
)
return this
},
});
var Collection = Backbone.Collection.extend({
model: Model,
search : function(letters){
//set up a RegEx pattern
var pattern = new RegExp(letters,"gi")
//filter the collection
return this.filter(function(model)
{
if(letters == "") return true //if search string is empty return true
return pattern.test(model.attributes['Product']['name'])
});
}
});
SOLVED:
This is my new search method. I am no longer filtering the collection and re-rendering. I simply loop over the collection, and if a model matches the search we trigger a 'show' event, if it is not in the search we trigger a 'hide' event. Then we subscribe to these events in the view and act accordingly.
search function from the collection:
search : function(query){
//set up a RegEx pattern
var pattern = new RegExp(query,"gi")
//filter the collection
this.each(function(model){
if ( pattern.test(model.attributes['Product']['name']) ){
model.trigger('show')
}
else{
model.trigger('hide')
}
});
}
The new view:
var ProductView = Backbone.View.extend({
tagName: 'tr',
initialize: function() {
this.listenTo(this.model, 'show', this.show);
this.listenTo(this.model, 'hide', this.hide);
},
hide: function()
{
this.$el.addClass('hide')
},
show: function()
{
this.$el.removeClass('hide')
},
render: function ()
{
var template = _.template( ProductTemplate )
this.$el.html( template( {data: this.model.toJSON(), Utils: Shop.utils} ) )
return this;
}
});
To expand on what #mu already commented on, you're not removing views that you've created. They're not in the DOM, but they're still hanging around in memory because they have a reference to your models (therefore, the garbage collector will not remove them for you).
You have a couple options:
Keep track of all the views that are being instantiated by addOne and remove them each time render is called.
Make your code show/hide views rather than instantiate/destroy each time the filter criteria is changed. This is more work, but is certainly the more optimal solution.

Loading initial data in Backbone.js

I'm new to backbone.js and MVC so apologise if this is a silly question...
I have been experimenting with some of the backbone.js tutorials out there and am trying to work out how to load an initial set of data onto the page.
If anyone could point me in the right direction or show me the what I'm missing below, it would be greatly appreciated!
Thanks!
The code is below or at: http://jsfiddle.net/kiwi/kgVgY/1/
The HTML:
Add list item
The JS:
(function($) {
Backbone.sync = function(method, model, success, error) {
success();
}
var Item = Backbone.Model.extend({
defaults: {
createdOn: 'Date',
createdBy: 'Name'
}
});
var List = Backbone.Collection.extend({
model: Item
});
// ------------
// ItemView
// ------------
var ItemView = Backbone.View.extend({
tagName: 'li',
// name of tag to be created
events: {
'click span.delete': 'remove'
},
// `initialize()` now binds model change/removal to the corresponding handlers below.
initialize: function() {
_.bindAll(this, 'render', 'unrender', 'remove'); // every function that uses 'this' as the current object should be in here
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
},
// `render()` now includes two extra `span`s corresponding to the actions swap and delete.
render: function() {
$(this.el).html('<span">' + this.model.get('planStartDate') + ' ' + this.model.get('planActivity') + '</span> <span class="delete">[delete]</span>');
return this; // for chainable calls, like .render().el
},
// `unrender()`: Makes Model remove itself from the DOM.
unrender: function() {
$(this.el).remove();
},
// `remove()`: We use the method `destroy()` to remove a model from its collection.
remove: function() {
this.model.destroy();
}
});
// ------------
// ListView
// ------------
var ListView = Backbone.View.extend({
el: $('body'),
// el attaches to existing element
events: {
'click button#add': 'addItem'
},
initialize: function() {
_.bindAll(this, 'render', 'addItem', 'appendItem'); // every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.render();
},
render: function() {
_(this.collection.models).each(function(item) { // in case collection is not empty
appendItem(item);
}, this);
},
addItem: function() {
var item = new Item();
var planStartDate = $('#planStartDate').val();
var planActivity = $('#planActivity').val();
item.set({
planStartDate: planStartDate,
planActivity: planActivity
});
this.collection.add(item);
},
appendItem: function(item) {
var itemView = new ItemView({
model: item
});
$('ul', this.el).append(itemView.render().el);
}
});
var listView = new ListView();
})(jQuery);
Thanks.
Here's the modified example: http://jsfiddle.net/kgVgY/2/
You create the collection first with the data you want
var list = new List([{
createdOn: 'Jan',
createdBy: 'John',
planStartDate: "dfd",
planActivity: "dfdfd"
}]);
and then pass the collection to the view you want
var listView = new ListView({collection: list});
That's about all you had wrong in this code. Few minor unrelated notes:
You were using _(this.collection.models).each. Backbone collections use underscore to expose all those functions on themselves, so that is equivalent to this.collection.each
You don't really need the "unrender" method on the ItemView but since you aren't using that I'm guessing you're using it for debugging.

Categories

Resources