About Jeditable jquery plugin in the context of Backbone.View - javascript

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
});
});

Related

Overriding JS function

this is an original function from Framework I'm working with. It's a Python-based framework with a little bit Javascript. and as I do not really know much about JS I want to ask a question.
How can I override init: function (parent, options) part.
in that function, I need to change just one thing.
line 57
link
display_order: ORDER.ASC to ORDER.DESC
so i need somehow to call this functions in super and change arguments
var Thread = Widget.extend({
className: 'o_mail_thread',
events: {
"click a": "on_click_redirect",
"click img": "on_click_redirect",
"click strong": "on_click_redirect",
"click .o_thread_show_more": "on_click_show_more",
"click .o_thread_message_needaction": function (event) {
var message_id = $(event.currentTarget).data('message-id');
this.trigger("mark_as_read", message_id);
},
"click .o_thread_message_star": function (event) {
var message_id = $(event.currentTarget).data('message-id');
this.trigger("toggle_star_status", message_id);
},
"click .o_thread_message_reply": function (event) {
this.selected_id = $(event.currentTarget).data('message-id');
this.$('.o_thread_message').removeClass('o_thread_selected_message');
this.$('.o_thread_message[data-message-id=' + this.selected_id + ']')
.addClass('o_thread_selected_message');
this.trigger('select_message', this.selected_id);
event.stopPropagation();
},
"click .oe_mail_expand": function (event) {
event.preventDefault();
var $message = $(event.currentTarget).parents('.o_thread_message');
$message.addClass('o_message_expanded');
this.expanded_msg_ids.push($message.data('message-id'));
},
"click .o_thread_message": function (event) {
$(event.currentTarget).toggleClass('o_thread_selected_message');
},
"click": function () {
if (this.selected_id) {
this.unselect();
this.trigger('unselect_message');
}
},
},
init: function (parent, options) {
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
display_order: ORDER.ASC,
display_needactions: true,
display_stars: true,
display_document_link: true,
display_avatar: true,
shorten_messages: true,
squash_close_messages: true,
display_reply_icon: false,
});
this.expanded_msg_ids = [];
this.selected_id = null;
},
If you're unable or not interested in changing the source code, you can pass
{display_order: ORDER.DESC} as the options parameter when creating this class.

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)

Backbone: check if View is closed

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();
}
}
});

Backbone models overwriting each other

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);

Backbone: restore focus after rendering

I've got a little problem here:
In my Backbone.js app I save changes in a content editable on blur. This means, that when pressing the tab key the whole view is re-rendered and I loose the focus on the next element. How can I restore this?
You can maintain a property, either in the view (as a plain attribute, as in the example below) or model, to store the currently focused element. Whenever focus changes, update the property.
After re-rendering stuff, set the focus to the element manually.
Here is a minimal code:
var myView = Backbone.View.extend({
el: $('#formElement'),
initialize: function() {
_.bindAll(this);
}
events: {
'focus input': "updateFocus"
},
updateFocus: function(event) {
this.focusedElem = $(event.target);
},
render: function() {
// After rendering is complete
this.focusedElem.focus();
}
});
I use a dedicated ViewModel and View for every input. It has a special readValue/writeValue methods which update element instead of recreating it. It looks this way:
var TextInput = Backbone.Model.extend({ // abstract
defaults: {
value: '', // text
visible: true, // determines if input element is visible
readonly: false, // determines if input element is read only
enabled: true, // determines if input element is enabled
delay: 750 // view/model sync latency
}
});
var TextInputView = Backbone.View.extend({
template: _.template($('#text-input').html()),
initialize: function (options) {
this.model.bind('change:visible', this.render, this);
this.model.bind('change:readonly', this.render, this);
this.model.bind('change:enabled', this.render, this);
this.model.bind('change:value', this.readValue, this);
},
events: {
'change input': 'writeValue',
'keyup input': 'writeValue'
},
render: function () {
$(this.el).html(this.template(this.model))
.find('input')
.prop({
readonly: this.model.get('readonly'),
disabled: !this.model.get('enabled')
})
.toggleClass('hidden', !this.model.get('visible'));
this.readValue();
return this;
},
changeTimer: null,
writeValue: function () {
if (this.changeTimer)
clearTimeout(this.changeTimer);
var that = this;
this.changeTimer = setTimeout(function () {
that.model.set({ value: that.$('input').val() });
}, this.model.get('delay'));
},
readValue: function () {
if (this.$('input').val() != this.model.get('value'))
this.$('input').val(this.model.get('value'));
}
});
I found that I wanted it to go to the "next" element after rendering. Also, you can't remember an element in JQuery that gets removed from the DOM. So I record the name of the input instead of the input itself. Combining the previous answers you can do something similar to below. Remember I have some assumptions in there, like names on the inputs and that I search within the fieldset.
getNextInputForName = function(desiredName) {
var desiredElement = false;
var foundElement;
$("fieldset input").each(function(index) {
if (desiredElement) {
foundElement = $(this);
return false;
}
if ($(this).attr("name") === desiredName) {
desiredElement = true;
}
});
return foundElement;
}
var myView = Backbone.View.extend({
el: $('#formElement'),
initialize: function() {
_.bindAll(this);
}
events: {
'focus input': "updateFocus"
},
updateFocus: function(event) {
this.focusedElem = $(event.target).attr("name");
},
render: function() {
// After rendering is complete
if( this.focusedElem ) {
getNextInputForName(this.focusedElem).focus();
}
}
});

Categories

Resources