What I have:
I have a edit function in which I rerender my view;
MyView = Backbone.View.extend({
edit: function (view) {
......
view.render();
}
});
What a problem:
There is an usecase in which during edit function view can be closed, so I MUST NOT invoke view.render() at the end of edit function.
Question:
How to check if view has already been closed inside edit function? Something like:
MyView = Backbone.View.extend({
edit: function (view) {
......
if (!view.isClosed())
view.render();
}
});
For #net.uk.sweet:
I use Bootstrap X-Editable. With help of it I can modify test fields. To modify text field I just need to click on it, change value, and then click outside of it (outside of text field). In such case method success will be invoked.
textEditor: function(view) {
$(this).editable({
type: 'textarea',
mode: 'inline',
onblur: 'submit',
showbuttons: false,
inputclass: 'edit-comments-text-input',
validate: function(value) {
if (!value.trim()) {
return 'Can not be empty!';
}
},
success: function(response, newValue){
//modify the comment
comment.text = newValue.trim();
//rerender
if (!view.isClosed()) //This line is what I need, but view hasn't isClosed method ((
view.render();
}
});
}
Also it is worth to say that user can close view by clicking on close button or by clicking outside of view.
Problem use case:
User click on x-editable field
Change text
Click close button
What happens in such case:
Two actions:
View is closed
success method, which invoke view.render(), BUT MUST NOT!
Summary:
I need to check inside of my success method if view has been closed.
Coarse solution:
Seems I find some solition, not the best one, of course.
if ($(view.el).hasClass('in'))
view.render();
MyView = Backbone.View.extend({
initialize : function() {
this.setClosed(false);
},
setClosed : function(booleanValue) {
this.closed = booleanValue;
},
getClosed : function() {
return this.closed;
},
edit: function () {
var view = this;
//......
if (!view.getClosed()) {
view.render();
}
}
});
Related
I'm trying to update only a part of a template used by a Backbone view when a collection is updated. The code below successfully executes the search() and render_list() methods. Furthermore, console.log(html) shows the full template's html. But when I execute the replaceWith, it replaces the selector with empty. If I replace $(selector,html) with a string (ie: 'test'), it successfully replaces with 'test'.
So, for some reason, the $(selector, html) selector isn't doing what it's meant to. What is further weird is that the images within the updated html selector are requested by the browser even though none of the updated html is inserted on into the document.
define([
'jquery',
'underscore',
'backbone',
'collections/tracks',
'collections/genres',
'text!templates/search_view_title.html',
'text!templates/search_view.html'
],function($,_,Backbone,Tracks_collection,Genres_collection,Search_view_title_template,Search_view_template){
var Search_view = Backbone.View.extend({
el: $('#app'),
events: {
'change #genre_select': function(){this.search('genre')},
'click #search_btn': function(){this.search('search')}
},
template: _.template(Search_view_template),
initialize: function(){
// SET SOME IMPORTANT LAYOUT SETTINGS
$('#pagetitle').html(_.template(Search_view_title_template));
$('body').css('padding-top','124px');
this.genre_collection = new Genres_collection();
this.listenTo(this.genre_collection,'update',this.render);
this.genre_collection.fetch();
this.collection = new Tracks_collection();
this.listenTo(this.collection,'update',this.render_list);
},
search: function(searchtype){
switch(searchtype){
case 'genre':
console.log('genre changed');
this.collection.fetch({
data: {limit: 30, type:'genre',genre_id:$('#genre_select').val()}
});
break;
case 'search':
console.log('search changed');
this.collection.fetch({
data: {limit: 30, type:'search',keyword:$('#keyword').val()}
});
break;
}
console.log(this.collection);
},
render_list: function(){
var that = this;
console.log('render list');
var html = that.template({genres: this.genre_collection.models,tracks: this.collection.models});
console.log(html);
var selector = '#tracklist';
console.log($(selector,html));
that.$el.find(selector).replaceWith($(selector,html));
return this;
},
render: function(){
// MAKE 'THIS' ACCESSIBLE
var that = this;
console.log('render');
that.$el.find('#container').html(that.template({genres: this.genre_collection.models}));
return this;
}
});
return Search_view;
});
Without the HTML templates in hand, I can just assume things.
This is closer to how I would do it:
var Search_view = Backbone.View.extend({
el: $('#app'),
events: {
'change #genre_select': 'onGenreChange',
'click #search_btn': 'onSearchClick'
},
template: _.template(Search_view_template),
initialize: function() {
// SET SOME IMPORTANT LAYOUT SETTINGS
$('#pagetitle').html(Search_view_title_template);
// Do this in css
$('body').css('padding-top', '124px');
this.genre_collection = new Genres_collection();
this.genre_collection.fetch();
this.collection = new Tracks_collection();
this.listenTo(this.genre_collection, 'update', this.render);
this.listenTo(this.collection, 'update', this.render_list);
},
render: function() {
console.log('render');
this.$('#container').html(this.template({
genres: this.genre_collection.models
}));
return this;
},
render_list: function() {
console.log('render list');
var html = this.template({
genres: this.genre_collection.models,
tracks: this.collection.models
});
console.log(html);
var $selector = this.$('#tracklist');
console.log($selector);
$selector.replaceWith($(html));
return this;
},
////////////
// events //
////////////
onSearchClick: function() {
console.log('search changed');
this.collection.fetch({
data: { limit: 30, type: 'search', keyword: $('#keyword').val() }
});
},
onGenreChange: function() {
console.log('genre changed');
this.collection.fetch({
data: { limit: 30, type: 'genre', genre_id: $('#genre_select').val() }
});
},
});
$('#pagetitle').html(_.template(Search_view_title_template));
The _.template function returns a function, which itself returns the rendered template when called.
It can be confused with this.template which often contains the result of _.template and is ready to be called (this.template(data)).
Split your callbacks, functions are cheap and unnecessary switch are ugly.
I made your search into onGenreChange and onSearchClick.
$('body').css('padding-top','124px');
Try to avoid that, it can be easily done with CSS, or even inline <style> tag or inline style="" attribute. If it's necessary for you as it's related to a dynamic behavior, create a class (e.g. search-class) in a css file, then toggle the class with jQuery, moving the "design" responsability out of the js:
$('body').toggleClass('search-class');
var that = this;
This is only necessary when dealing with callbacks where the context (this) is different in the callback. In Backbone, most of the time, it's avoidable as the context option is often available and automatically set on most (like the events callbacks).
this.$el.find(selector)
This is equivalent to this.$(selector). Just a little shortcut.
.replaceWith($(selector,html));
replaceWith expects a htmlString or Element or Array or jQuery.
$(selector, html) expects a selector and a context. You want $(html) to transform your html string into a jQuery element.
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)
I've got a collection of Delivery models called DeliveryList. When I add or edit a Delivery, all attributes of the previously added or edited Delivery are overwritten by the attributes of the new one.
Curiously, if I reload the page after saving a model with this line of code:
// Hacky way to get around the models overwriting each other
location.reload();
The model will not be overwritten by newly created or edited models.
Any thoughts on why this is happening?
Here's the rest of my code:
var DeliveryView = Marionette.ItemView.extend({
initialize: function () {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
_.bindAll(this, "editDeliveryOption", "saveAllFields");
},
onRender: function() {
if (this.model.isNew()) {
this.editDeliveryOption();
this.$el.addClass("new");
}
},
template: "#delivery-item-template",
events: {
"click #removeThis": "removeDeliveryOption",
"click #editThis": "editDeliveryOption"
},
saveAllFields: function() {
var value = $("#optionName input").val();
this.model.save({ optionName: value });
var value = $("#shipToState option:selected").val();
this.model.save({ shipToState: value });
var value = $("#zipCodes input").val();
this.model.save({ zipCodes: value });
var value = $("#perOrderFee input").val();
this.model.save({ perOrderFee: value });
var value = $("#perItemFee input").val();
this.model.save({ perItemFee: value });
// After done editing, remove the view from the dom
this.editDeliveryForm.remove();
// Show the new option
this.$el.removeClass("new");
// Hacky way to get around the models overwriting each other
location.reload();
},
editDeliveryOption: function () {
this.editDeliveryForm = new Backbone.Form({
template: _.template($("#editDeliveryTemplate").html()),
model: this.model
}).render();
layout.editDelivery.show(this.editDeliveryForm);
$("#triggerEditDelivery").fancybox({
'afterClose': this.saveAllFields,
}).click();
// This button in Fancybox isn't working
$("#saveDelivery").click(function() {
this.saveAllFields;
});
},
removeDeliveryOption: function () {
this.model.destroy();
}
});
var DeliveriesView = Marionette.CompositeView.extend({
initialize: function () {
this.collection.fetch();
this.listenTo(this.collection, 'change', this.changThis);
},
changeThis: function () {
alert("it changed");
},
template: "#deliveries-view-template",
itemView: DeliveryView,
events: {
"click #addShipping": "addDeliveryOption",
},
addDeliveryOption: function() {
this.collection.create();
},
// Specify a jQuery selector to put the itemView instances in to
itemViewContainer: "#deliveries",
});
Thanks EmptyArsenal and mu is too short for pointing me in the right direction.
What ended up being the problem was the fancybox call:
$("#triggerEditDelivery").fancybox({
'afterClose': this.saveAllFields,
}).click();
Every time I added a new field, it kept binding a saveAllFields method call to #triggerEditDelivery. Therefore, every time I clicked #triggerEditDelivery for a new Delivery, it would save all them to the currently open one.
Here's my fix:
$("#triggerEditDelivery").fancybox({
helpers: {
overlay: { closeClick: false }
}
}).click();
$("#saveDelivery").click(this.saveAllFields);
$("#cancelDelivery").click(this.cancelDeliveryOption);
I'm going to ask another newbie question. I have come across multiple ways for a child to reference functions and data defined at the parent class level, but I'm not sure what is the recommended way. Below is an example that I am dealing with, but this topic is generally important for me since I do not have a good understanding of reference and scope of parents and children. How can I reference functions and data of a parent from a child of a child element?
As usual, any help will be highly appreciated.
Mohammad
San Jose, CA
/******
This is a floating panel with a formpanel inside it, that has an email field and a button to process the email.
When the button is tapped, I want to call the processEmails() function from the button.
******/
Ext.define('myapp.view.ForwardPanel', {
extend : 'Ext.Panel',
xtype : 'forwardPanel',
initialize: function() {
this.callParent(arguments);
var btn = {
xtype: 'button',
ui: 'action',
text: 'Send',
listeners: {
tap: function(){
processEmails(); //<- **How can I reference and call this function?**
// This function is defined at the parent class level
// (indicated at the bottom of the code listing)
}}
};
var form = {
items: [{
xtype: 'fieldset',
instructions: 'Enter multiple emails separated by commas',
title: 'Forward this message.',
items: [ {
xtype: 'emailfield',
name: 'email',
label: 'Email(s)'
},btn] // The button is added to the form here
}]
};
this.add(form);
},
// this is the parent level function I want to call upon button tap.
processEmails: function(){
console.log('Processing email...');
}
});
I'm not sure about a "right" way to do it, but this is what I would do, and what I see most of the time in the Sencha forum and in their own code:
Ext.define('myapp.view.ForwardPanel', {
...
initialize: function() {
this.callParent(arguments);
var me = this; // <---- reference to the "ForwardPanel" instance
var btn = {
xtype: 'button',
ui: 'action',
text: 'Send',
listeners: {
tap: function(){
me.processEmails(); // <-- notice the "me" in front
}
}
};
...
},
// this is the parent level function I want to call upon button tap.
processEmails: function(){
console.log('Processing email...');
}
});
You can make use of prototype to achieve it:
myapp.view.MyView.prototype.processEmails.call(this);
Here is a working fiddle: http://www.senchafiddle.com/#MvABI
Using the Jeditable plugin,
is possible to create, very easily, a submit and cancel button.
Here's a very simple code example (*)
Now let's suppose in MyView (Backbone.View) I would like to trigger the event click on the button submit which is created by Jeditable.
Here's the code regarding the Backbone.View (**).
When I trigger the event "click .submitBtn" the value of $('.edit_area').text is empty string.
In order to fix this issue I implemented the following code (* **)
Do you have some smart idea to improve the code of (* **)? I don't like callback using setTimeout.
(*)
$('.edit_area').editable(function(value, settings) {
return(value);
}, {
type : 'textarea',
submit : '<div class="submitBtn">Ok</div>'
cancel : '<div class="submitBtn">Undo</div>'
});
(**)
MyView = Backbone.View.extend({
events: {
"click .edit_area" : "edit",
"click .submitBtn" : "close"
},
});
(* **)
close: function close ()
{
var that = this;
console.log($(this.el).find("[data-tid='editable']").text()); // empty string
setTimeout(function () {
console.log($(that.el).find("[data-tid='editable']").text()); // update string
that.model.save({
name: $(that.el).find("[data-tid='editable']").text()
});
}, 0);
},
in the initialize function
$('.edit_area').editable(this.close, {
type : 'textarea',
submit : 'OK',
});
Close function definition
close:function(value, settings) {
this.model.save({
name: value
});
});
Complete Code
var editableview = Backbone.View.extend({
initialize: function () {
_.bind(this.close, this);
},
render: function () {
$(this.el).find('.edit_area').editable(this.close, {
type: 'textarea',
submit: '<div class="submitBtn">Ok</div>'
cancel: '<div class="submitBtn">Undo</div>'
});
},
close: function (value, settings) {
this.model.save({
name: value
});
});
});
Var That = This is wrong. This is the DOM not the backbone view. You can do:
$('.edit_area').editable(this.close, {
type : 'textarea',
submit : 'OK',
submitdata: {view: this},
});
"view" in the hash would be the backbone view. It can be accessed in the close function.
close:function(value, settings) {
settings.submitdata.view.model.save({
name: value
});
});