Cancel create model in collection. Fight with duplicate - javascript

need create a handler, which not give create duplicate.
Collection = Backbone.Collection.extend({
model : Model,
initialize : function() {
this.on('add', this.actionCreate, this);
},
actionCreate : function(model, collection, param) {
// handler...
}
});
collection = new Collection();
collection.add({ chanel : 'starladder' });
// Should not be created because the parameter 'chanel' already exists with this value
collection.add({ chanel : 'starladder});
I all ready try in handler write somsing like:
if( this.where({ chanel : model.get('chanel').length } ) { model.destroy(); }
but not working.

There are many ways to go about that :)
First, you could simply do the check before the add! Then no need to worry about using Collection.remove() or Model.destroy()
if(!this.collection.findWhere({chanel: 'starladder'}))
...add...
As a side note, here you probably want Collection.remove() rather than Model.destroy() because your newly added Model doesn't seem to have an id, so there is no need for a possible server round-trip.
Second, your handler seems to have a few issues. .where() will return an array, so you need to do a check on the .length of the return value to see if it's > 0. Or you can use findWhere as above which will do the same as where but return the first value of that array, so you don't need to worry about doing a [0] to get to that model.
It's hard to tell what would be the best way to do what you want to achieve without more details, but it seems you are using chanel as a kind of unique ID...? so why not use the idAttribute property to let Backbone know that this is what defines your model in an unique way? Obviously, that only works if you don't real IDs to begin with...
If you need both id and chanel to be unique, then I'd just do a check before the add as you proposed and be on my way + obviously add the same uniqueness condition on the server side, because you never know + don't allow any way in the GUI for the user to pick the same value twice (remove values from select/dropdown/autocomplete, whatever).

Related

Backbone.js getting a list to ensure only one instance of a certain model exists

Forgive me, I am new to backbone, and the MVC javascript concept.
So, I am making a comments system:
createComment: function () {
// create new comment model
var comment = new CommentModel({});
// render form view right after new button
var formview = new FormView({model: comment});
this.$el.after(formview.render().$el);
// add saved model to collection after form was submitted successfully
formview.on('success', this.handleFormSuccess, this);
// finally, return false to stop event propagation
return false;
},
What I can't understand is how to get a list of comments which have been rendered, but have not been sent to the collection. See, I want to ensure that only one comment box is opened at once.
My approach is to do a check to see how many comments are open, and close everyone except the current model.
Using Backbone.js & Underscore, how to get a count of items from the model? seems to give advice for how to do this after the model hits a collection.
I am very new with backbone, so it is entirely possible I am in the exact wrong direction with this.
How do I get the list?
As Joe suggested, I think your problem is this line:
formview.on('success', this.handleFormSuccess, this);
However, I don't think his suggestion (of changing "success" to "sync") will work either, because formview is a View, not a Model or Collection, so it doesn't even have an on method.
What does have an on method is the view's element, so you can do:
formview.$el.on('success', this.handleFormSuccess, this);
Two problems with that though:
jQuery is lame and doesn't let you set the context like that
"success" isn't a form event; you want this code to trigger on "submit"
so to fix those two issues you need to change the line to:
formview.$el.on('submit', _(this.handleFormSuccess).bind(this));
Alternatively you could also call:
_(this).bindAll('handleFormSuccess');
in FormView's initialize, which would make it so that you don't need to bind this.handleFormSuccess):
formview.$el.on('submit', this.handleFormSuccess);
Hope that helps.

jQuery Plugin - Public method - Data only targeting one element

I'm trying to write a plugin that will select multiple elements and then apply some private methods to them (see code below). Then I also want to give the user the ability to trigger the activation of the plugin's methods manually with a .activate() function.
Here is my code :
MARKUP : https://github.com/simonwalsh/jquery.imagepox/blob/master/demo/index.html
JS : https://github.com/simonwalsh/jquery.imagepox/blob/master/dist/jquery.imagepox.js
Basically, when I select multiple items and then try to use the manual activation like so :
$(".pox-wrapper").imagepox({ // NOTE: selects two elements
manualActivation: true
});
var manual = $(".pox-wrapper").data('imagepox');
setTimeout(function(){
manual.activate();
}, 5000);
It will only apply the activate() method to the first element in the query...
This is my first jQuery plugin and I've been able to handle everything so far but I'm not sure about this one or even if it is the right way to effectively call a public method. I also tried using a custom event with an event listener in the plugin but it still only applies the methods on the first element in the page.
Thanks in advance :)
its not your plugin's fault. data does not work like that, it doesnt know how to return data from a collection of elements. Because think about it, each element in the collection contains its own data object!
So when you call data on a collection, it returns the data from the first one. The quick solution would be to change the innards of the setTimeout into a loop over all the elements in the set and call activate on them.
setTimeout(function(){
$(".pox-wrapper").each(function(){
$(this).data('imagepox').activate();
})
}, 5000);
It seems to me that you want to add functions to collections of jquery objects. This is the usecase of a jquery plugin. You can create a lightweight one like this:
$.fn.imagepox.activate = function(){ //do this after you create your plugin!
return this.each(function(){
var $this = $(this);
var data = $this.data('imagepox');
if(data){
data.activate();
}
});
};
now you can call it like this:
$(".pox-wrapper").imagepox.activate()

Creating javascript objects using jQuery's each() method

Im curious about what might be a larger question than I think.
I am using the following code to listen for 'keyup' on a group of text input fields. If the user stops typing for a given amount of time, I send the data to a controller using AJAX.
I decided to try my hand at OOP in javascript to accomplish this. This is because I want a new instance of the timer method for each input field. (To be absolutely clear, Im very new to OOP in javascript so this might be dreadful. Let me know.)
Here is the main class with its methods:
function FieldListener(entity){
t = this;
t.typingTimer; // Timer identifier
t.doneTypingInterval = 1000; // Time in ms. e.g.; 5000 = 5secs
t.entity = entity;
entity.bind("keyup", function(){t.setTimer();});
}
FieldListener.prototype.setTimer = function(){
t = this;
// User is still typing, so clear the timer.
clearTimeout(t.typingTimer);
// Get the field name, e.g.; 'username'
t.entityType = t.entity.attr("name");
// If the value is empty, set it to a single space.
if(!(t.val = t.entity.val())){
t.val = ' ';
}
t.noticeSpan = t.entity.siblings("span");
// Display 'waiting' notice to user.
t.noticeSpan.html('...')
t.typingTimer = setTimeout(function(){t.doneTyping();},t.doneTypingInterval);
}
FieldListener.prototype.doneTyping = function(){
// Encode for passing to ajax route.
t = this;
valueType = encodeURIComponent(t.entityType);
value = encodeURIComponent(t.val);
$.ajax({
url: '/check/'+valueType+'/'+value,
type: 'GET',
processData: false
})
.done(function(validationMessage){
t.noticeSpan.html(validationMessage);
})
.fail(function(){
t.noticeSpan.html("Something went wrong. Please try again.");
});
}
So from here I'd like to be able to create an object of the FieldListener class for every input field.
I know I can do it easily if I have an id for each like so:
var fieldListener = new FieldListener($("#someFieldID"));
But I'd like to iterate over every field with a given class name. Something close to this perhaps?:
i = 0;
$(".info-field").each(function(){
i = new FieldListener($(this));
});
But that doesn't work (and doesn't look very nice).
Any thoughts? (Im also curious about critiques/improvements to the class/methods code as well.)
edit: As per #ChrisHerring's question: The issue is that it seems to create the object but only for the last element in the each() method. So the span associated with the last input field with the class '.info-field' displays the validationMessage returned from AJAX regardless of which field I am typing in.
UPDATE:
It seems like something is wrong with the creation of new objects. For example, if, rather than iterating through the each() method, I simply follow one class initiation with another, like so:
var fieldListener1 = new FieldListener($("#someFieldID"));
var fieldListener2 = new FieldListener($("#someOtherFieldID"));
that fieldListener2 overwrites variables being saved when initiating fieldListener1. This means that when I type into the input field with id "#someFieldID", it behaves as if I am typing into the input field with id "#someOtherFieldID". Thoughts?
UPDATE #2 (solved for now):
It seems that I have solved the issue for now. I needed to add 'var' before 't = this;' in the FieldListener class. Any comments/critiques are still welcome of course. ;)
The t variable is global. The function for the "keyup" event is evaluated dynamically which means it picks up the last value of t.
Change
t = this;
to
var t = this;
I think you want an array of FieldListener objects.
var myListeners = [];
i = 0;
$(".info-field").each(function(){
myListeners[i] = new FieldListener($(this));
i++
});
This'll give you a list of FieldListeners, where myListeners[0] is the listener for the first .info-field on the page, myListeners[1] is the listener for the second, etc.
Edit: It would appear you have solved the problem. This answer may still come in handy later on, though, so I won't delete it. =)
I think you should be using jquery's .on() to handle the binding.
$(body).on({
keyup: function () { HandleKeyUpEvent($(this)); },
keydown: function () { HandleKeyDownEvent($(this)); }
}, ".info-field");
I realize this is a departure from your original coding idea (using prototypes) but it will still be OOP, if that's what you intented to do.

Make Backbone.js Model change "partially" silent?

When my "chartModel" changes I want to update the "globalModel".
chartModel.bind("change", updateGlobalModel);
updateGlobalModel(){
globalModel.set(obj)
}
And vice versa, I want my chartModel to update when the globalModel changes.
globalModel.bind("change", updateChartModel);
updateChartModel(){
chartModel.set(obj)
}
This results in a feedback loop when setting the globalModel. I could prevent this by setting {silent:true}.
But here comes the problem. I have another Model that is dependent on the change event:
globalModel.bind("change", updateOtherModel);
How can I alert this model of the change but not the former one (to avoid the feedback loop)?
UPDATE:
For now, I decided to generate a specific ID for each set call:
set : function(attrs, options) {
if(!("setID" in attrs)){
attrs.setID = myApp.utils.uniqueID(); //newDate.getTime();
}
Backbone.Model.prototype.set.call(this, attrs, options);
},
This way, I can always generate a "setID" attribute from anywhere in my application. If the setID is still the same when fetching something from the model, I know there could be risk for a feedback loop.
Better late than never..
The easiest way to do this is by using a flag. For example, when setting something in globalModel, you could also change a property on the model to indicate that you've changed something. You can then verify the value of this flag in updateChartModel. For example:
chartModel.bind("change", updateGlobalModel);
function updateGlobalModel() {
if (!flag) {
globalModel.set(obj);
flag = true;
}
}
Probably very similar to what you've ended up doing with your setID. As an aside, underscore has a uniqueId function built in.
Another thing that you can do, which is much cleaner, is to pass an option with your sets calls.
chartModel.set(obj, { notify : false });
Yes, you can pass any options you want, you're not just limited to { silent : true }. See this discussion on github for more. Then, you check for the existence of this property where you handle change events like so:
function updateGlobalModel(model, options){
// explicitly check for false since it will otherwise be undefined and falsy
// you could reverse it.. but I find this simpler
if (options.notify !== false) {
globalModel.set(obj)
}
}
and in your third (and other models), you can just forego this check.
The final option is of course to look at your design. If these two models are so closely related that they must be kept in sync with each other, maybe it makes sense to merge their functionality. Alternatively, you could split the common functionality out. This all depends heavily on your particular situation.
My knowledge is limited, so maybe I shouldn't be answering, but I would try to pass a reference to chartModel when it's created that refers to the "other" model that you want updated. Then trigger an event on updateChartModel() and make sure your "other" model is bound on that event.
The question I have is: does the silent object mute all events? Or just model related ones? This obviously wouldn't work if all events are muted.

How can I tell if a model has not been changed in Backbone.js?

This may be a result of misuse of the component, though I don't think so.
I have an issue where a View updates a model in Backbone JS and calls the model's Set method so that it may verify it's input.
In theory there are two results to such an action: Error and Change.
Both events work as prescribed.
But in fact there is a third event: No change.
That is, if the input has not been changed at all, I can't tell after calling Set because no error will be thrown but nor will a change event, as nothing has actually changed- but I still want to know about such a case.
Is there a way for me to do this?
The reason is that there is an action I want performed only if no error occurs, but there is no way for me to know (without a change event) that the model has attempted to set the new values and ended with no result as it all happens asynchronously.
Thanks!
Every Backbone model has a hasChanged method:
hasChanged model.hasChanged([attribute])
Has the model changed since the last "change" event? If an attribute is passed, returns true if that specific attribute has changed.
Perhaps you can use that to check your third possibility.
BTW, the callbacks aren't asynchronous. The error and changed callbacks are triggered and return before set returns:
set : function(attrs, options) {
//...
// Run validation.
if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
//...
// Update attributes.
for (var attr in attrs) {
var val = attrs[attr];
if (!_.isEqual(now[attr], val)) {
now[attr] = val;
delete escaped[attr];
this._changed = true;
if (!options.silent) this.trigger('change:' + attr, this, val, options);
}
}
The _performValidation call triggers the error callbacks, the this.trigger calls will call the per-attribute callbacks.
In this case, you may need to dance around Model.set() a little bit to get where you want. If you are using this functionality, then you should have defined a validate() method on your model.
http://documentcloud.github.com/backbone/#Model-validate
So you can call this method directly...
// something happens and we need to update the model to "newvalues"
if (model.validate(newvalues)) {
model.trigger('error')
} else {
model.trigger('change')
}
model.set(newvalues)
That way you will always at least get 'change' or 'error' out of it, even if it's the same. You will also still get the existing events from set.

Categories

Resources