BackboneJS - in destroy method: how to cancel firing events in done callback? - javascript

Question about a bit more advanced version of BackboneJS Todo List
BackboneJS Todo List http://todomvc.com/examples/backbone/
On click "X" link I have simple handler (events['click .destroy'] = 'clear',):
clear: function () {
this.model.destroy()
},
As I can see, I can do something like this:
clear: function () {
this.model.destroy()
.fail(function(err) {})
.done(function(resData) {
// HERE I would like to cancel firing other events, like "destroy".
})
},
In done callback I can get info if there was some problem while deleting row from table. In current case - it is Doctrine2 exception related to Foreign Key Constraint. I catch it in my todos.php, set {success: false}, and wish in done callback use it to prevent HTML element "li" to be deleted from Todo list, as it happens now.
Is that so, when method model.destroy is called, no matter failed it or succeded, event "destroy" is fired, and because of that - element "li" is been removed from Todo List ?
Maybe I should use "request" event for collection?
What technique is best practice?
Could you please paste here some example (or link to example)?

Related

How show easyautocomplete dialog only if cursor is in input

I'm trying set easyautocomplete on my input. Dataset i am geting from ajax json and there is some delay. If user is too fast and writes for example "Adam" and pushes tab, cursor skips to next input, but after easyautocomplete shows dialog on previous input and doesn´t hide it. Is there any way how to show easyautocomplete dialog only when i have cursor in input?
var options = {
minCharNumber: 3,
url: function(phrase) {
return "data?q=" + phrase;
},
getValue: function(element) {
return element.surname + " " + element.name;
},
template: {
type: "description",
fields: {
description: "phone"
}
},
ajaxSettings: {
dataType: "json",
method: "POST",
data: {
dataType: "json"
}
},
list: {
onClickEvent: function() {
/*Some action*/
},
hideAnimation: {
type: "slide", //normal|slide|fade
time: 400,
callback: function() {}
}
},
requestDelay: 400
};
$(".autoComplete").easyAutocomplete(options);
Minimum, Complete, Verifiable, Example
In order to easily see this result, you'll have to open up your dev tools and throttle your network traffic so the ajax connection takes a little while
Here's a Demo of the issue in jsFiddle
Handling Library Events (Doesn't Work)
My initial thought was you could handle this during some of the EAC lifecycle events that fired, like the onLoadEvent or onShowListEvent:
var options = {
url: "https://raw.githubusercontent.com/KyleMit/libraries/gh-pages/libraries/people.json",
getValue: "name",
list: {
match: {
enabled: true
},
onLoadEvent: function() {
console.log('LoadEvent', this)
},
onShowListEvent: function() {
console.log('ShowListEvent', this)
}
},
};
However, these methods don't seem to provide an option to alter the control flow and prevent future events
Updating Source Code (Works)
Peeking into the library's source code, Easy AutoComplete does the following ...
Handles keyup events
Which then calls loadData
Which fires an AJAX request with the provided URL
depending on the network and server speed, any amount of time can pass before step 4, and the input could lose focus
Once the ajax promise is returned, will call showContainer()
Which triggers the "show.eac" event on the container
Which then opens the list with the selected animation
During step 6, we could add one last check to confirm the selected input still has focus before actually opening, which would look like this:
$elements_container.on("show.eac", function() {
// if input has lost focus, don't show container
if (!$field.is(":focus")) {return}
// ... rest of method ...
Here's a working demo in Plunker which modifies the library's source code in a new file
Unfortunately, that's not a great practice as it leaves you fragile to future changes and transfers ownership of the lib maintenance to your codebase. But it does work.
I created Pull Request #388 with the proposed changes, so hopefully a long term fix within the library itself will be available at some point in the future.
Wrapper (Recommended for now)
If you don't want to muck with third party code, there are some weird workarounds to mess with the internal execution. We can't modify the showContainer method since it's inside a closure.
We can add another listener on the show.eac event so we can be a part of the event pipeline, however there are some challenges here too. Ideally, we'd like to fire before the library handler is executed and conditionally stop propagation. However, initializing EAC will both a) create the container we have to listen to and also b) attach an event listener.
Note: Event handlers in jQuery are fired in the order they are attached!
So, we have to wait until after the lib loads to attach our handler, but that means we'll only fire after the list is already displayed.
From the question How to order events bound with jQuery, we can poke into the jQuery internals and re-order attached events so we can fire our handler before the library's handler is called. That'll look like this:
$._data(element, 'events')["show"].reverse()
Note: Both e.stopPropagation() and e.preventDefault() won't work since they prevent future events; instead we need to take more immediate action with e.stopImmediatePropagation()
So the wrapper would look like this:
$("#countries").easyAutocomplete(options);
$(".easy-autocomplete-container").on("show.eac", function(e) {
var inputId = this.id.replace('eac-container-','')
var isFocused = $("#"+inputId).is(":focus")
if (!isFocused ) {
e.stopImmediatePropagation()
}
});
$(".easy-autocomplete-container").each(function() {
$._data(this, 'events')["show"].reverse()
})
Here's a working demo in CodePen

Trigger event in Backbone.js and Saiku

I'm new to backbone. I have been looking it has been used in Saiku. I came across the below line.
Saiku.session.trigger('workspace:new', { workspace: this });
Is 'workspace:new' an event? How does backbone trigger recognize it as an event?
Short answer: yes, workspace:new is an event.
Backbone has several built-in events that you can listen for. But you can also trigger custom events, as this code does. The event is identified by only a string (in this case, "workspace:new"). When you call trigger on an object that inherits from Backbone's Event Module, that event "happens." As a second parameter to trigger, you can pass some data about the event, anything you want accessible from the event handler function.
Then, usually somewhere else, there will be code waiting for that event to happen. That is set up by calling the .on or .listenTo methods.
Here's a basic example: (See it in action on JSBin)
var model = new Backbone.Model();
model.on('my-event', function (data) {
console.log("my-event happened!");
console.log(data);
});
model.trigger('my-event');
model.trigger('my-event', 'some-data');
model.trigger('my-event', { anything: 'works' });

Programmatically selecting actionsheet throws error

I am using uitest.js (built-on Jasmine.js) to test our Kendo UI Mobile application. I am displaying an actionsheet and programmatically selecting on of the options. This works ok in the app but throws an error that fails the test.
I am using an action sheet like this:
<ul data-role="actionsheet" id="marketplace-price-actions" >
<li class="km-actionsheet-title">Select Price</li>
<li>$$$</li>
<li>$$</li>
<li>$</li>
</ul>
and In my spec I am selecting one of the options like this:
$("#marketplace-price-actions li a").eq(2).mousedown().mouseup();
and this works, yet throws the following error:
TypeError: "undefined is not a function"
I have created a jsfiddle that displays this at http://jsfiddle.net/zkent/DD6vj/2/. Be sure to open the console.
EDIT Based on the selected answer, the error was from passing values to the functions. I chose to create separate callbacks. See http://jsfiddle.net/zkent/DD6vj/4/.
It looks like you're not supposed to pass parameters to your action. I'm not sure why it's implemented this way, but this is causing your error (which also happens if you simply click on it, so it's not related to Jasmine).
As far as I can see, you have three options:
Use a separate callback for each item
Modify the ActionSheet source code to supply the clicked element to your action
Use a closure over the value you pass and return a handler
Option 3 seems to be the best solution if you only need to pass one value but want to avoid code repetition due to multiple handlers.
Please note that I haven't tested the following solutions at all, so use at your own risk.
For option #2, something like this might work:
kendo.mobile.ui.ActionSheet.fn._click = (function (click) {
return function (e) {
if (e.isDefaultPrevented()) {
return;
}
var action = $(e.currentTarget).data("action");
if (action) {
kendo.getter(action)(window)({
target: this.target,
context: this.context,
element: e.currentTarget // pass in the element that was clicked on
});
}
e.preventDefault();
this.close();
}
})(kendo.mobile.ui.ActionSheet.fn._click);
That way you'd at least know which element was clicked on and you could add data attributes to pass data, if you wanted, e.g.:
<li>$$</li>
which you could then read in your handler:
function alertme(e) {
console.log(e);
console.log($(e.element).attr("data-value"));
}
(demo)
For option #3, you would simply define your action as:
function alertme(val) {
return function(e) {
console.log(e);
console.log(val);
};
}
and your element would be as it was:
<li>$$
(demo)

How to handle multiple invalid events being fired from collection.create() in backbone.js

I am adding a model using the collection.create method. I have over-ridden the model.validate method and the error window pops up I get the correct error messages.
Everything seems to work great until I click the save button a third of fourth time. The invalid event gets fired for every previous invalid model. I noticed the collection did not clean up after itself when the invalid event fired so I added the line model.collection.pop() hoping that would solve it.
The invalid event is still fired off n number of times. N being the number of times I have attempted to create a new model until I reload the app. I found that I should only display the error message if the model being passed in has a collection object on it. And now everything works but this seems a bit janky.
I tried adding model.stopListening() inside the invalid event method. No luck though. I assume this has something to do with me not completely cleaning up these partial or invalid models.
createNewAsset: (event) ->
#collection.on "invalid", (model, error) =>
console.log "invalid fired"
unless model.collection is undefined
errView = new MyApp.Views.Error(collection: error)
$("body").append(errView.render().el)
model.collection.pop()
#collection.on "sync", ->
Backbone.history.navigate("assets", true)
#collection.create
name: #$el.find("#new_asset_name").val()
Clarification Update:
The above code works for the end user, but I have some zombie models or collections firing off n number of events. N being the number of times the user has clicked the save button.
I don't think your problem is that you have stray models, your problem is that you're binding a new anonymous "invalid" callback to the collection every time createNewAsset is called.
You should just bind your "invalid" and "sync" handlers once in initialize:
initialize: ->
# You could still use anonymous functions here.
#listenTo(#collection, 'invalid', #bad_model)
#listenTo(#collection, 'sync', #synced)
#...
bad_model: (model, error) ->
console.log('invalid fired')
#...
synced: ->
Backbone.history.navigate('assets', true)
And then your createNewAsset becomes simply this:
createNewAsset: (event) ->
#collection.create
name: #$('#new_asset_name').val()
I also switched your #$el.find() to #$() which is a standard built in short cut for #$el.find.

Backbone.js Model previous method not working?

Strangely I noticed that the model previous method is not working the way I thought.. it keeps returning the same value as the get. I think that something is wrong with my code or backbone.js is not upgrading the this._previousAttributes when change event is fired.
model = new Backbone.Model()
model.set({attr1: 123})
alert(model.previous("attr1")) //alert 123 instead of undefined
alert(model.get("attr1"))
model.set({attr1: 312})
alert(model.previous("attr1")) //alert 321 instead of 123
alert(model.get("attr1"))
http://jsfiddle.net/wLKBk/
What am I doing wrong?
The previous method is only useful while a "change" event is happening:
previous model.previous(attribute)
During a "change" event, this method can be used to get the previous value of a changed attribute.
The previous method is only useful inside a "change" event handler; similar things apply to hasChanged, changedAttributes, and previousAttributes.
You're trying to use previous when you're not inside an event handler so you get nonsense. If you want to know what has changed in a model and you need to know outside of "change" event handlers, then you'll have to track it yourself.

Categories

Resources