backbone console log event triggering - events

I've got a underscore/backbone/require application and I would like to output all events that are triggered through backbone to the console (in other words: pass each event through console.log function). I've tried wrapping it with underscore and manually replacing the function. Neither this:
console.log(Backbone.Events.trigger);
var trigger = Backbone.Events.trigger;
Backbone.Events.trigger = function(name) {
console.log('Event', name, 'triggered.');
trigger.apply(this, arguments);
}
nor this:
Backbone.Events.trigger = _.wrap(Backbone.Events.trigger, function(func) {
console.log('EVENT:', Array.prototype.slice.call(arguments));
func(Array.prototype.slice.call(arguments));
});
console.log(Backbone.Events.trigger);
worked. I'd appreciate a javascript (not coffeescript) solution.

Your wrappings fail because Backbone mixes in Backbone.Events behavior on Backbone.Model, Backbone.Collection, etc. For example, Backbone.Model is defined as
var Model = Backbone.Model = function(attributes, options) {
...
};
_.extend(Model.prototype, Events, {
...
};
This means that when you redefine Backbone.Events.trigger, it is already too late.
But all is not lost! You won't be able to redefine all trigger methods in one go, but you can redefine them on class level:
Backbone.Model.prototype.trigger = function() {
console.log('Event', arguments);
Backbone.Events.trigger.apply(this, arguments);
}
and a demo http://jsfiddle.net/nikoshr/G2Qfn/
For a given class, you can override the trigger method:
var M = Backbone.Model.extend({
trigger: function() {
console.log('Event', arguments);
Backbone.Model.prototype.trigger.apply(this, arguments);
}
});
http://jsfiddle.net/nikoshr/G2Qfn/1/
or for a given instance
var M = Backbone.Model.extend({});
var m = new M();
m.trigger = function() {
console.log('Event', arguments);
M.prototype.trigger.apply(this, arguments);
}
http://jsfiddle.net/nikoshr/G2Qfn/2/

Related

Jasmine spyOn function call inside function under test

I am trying to use Jasmine to test the following function:
var Pdba = Class.create();
Pdba.prototype = {
getChangeGroup: function(userId) {
var query = 'active=true^u_change_group=true^u_organization=false^';
var exGroup = new CompanyGroup();
var groups = exGroup.getGroupsByQuery(userId, query); //want to spy/mock this call
if (groups.next()) {
return groups.sys_id.toString();
}
return '';
}
type: 'Pdba'
};
I want to SpyOn the getGroupsByQuery() call, so that it doesn't make the actual call. Below is a collection of various things I have been trying, mostly just to see if I can "spy" and see that it has been called, then work on overriding so that I can replace the call with my own data.
describe('my suite of getChangeGroup tests', function() {
var expPdba;
var validUserId = 'user1';
var expGrp;
var ggbqMoc
beforeEach(function() {
expPdba = new global.Pdba();
coGrp = new CompanyGroup();
spyOn(coGrp, 'getGroupsByQuery');
ggbqMoc = jasmine.createSpy('getGroupsByQuery');
});
it('should return \'\' for empty userId', function() {
coPdba.getChangeGroup('');
expect(coGrp.getGroupsByQuery).toHaveBeenCalled();
expect(ggbqMoc).toHaveBeenCalled();
});
});
Is this possible or do I need to change the function under test to take a 'CompanyGroup' as a parameter?
Thank you
I'm assuming you are using jasmine v3. The syntax for creating a spy is pretty weird now- you have to pass a string that refers to the name of the variable you want to create a spy for, and then you pass an array of function names that should be spied on.
Try this:
describe('my suite of getChangeGroup tests', function() {
var expPdba;
var validUserId = 'user1';
var expGrp;
var spy;
beforeEach(function() {
expPdba = new global.Pdba();
coGrp = new CompanyGroup();
spy = jasmine.createSpyObj('coGrp', ['getGroupsByQuery'])
});
it('should return \'\' for empty userId', function() {
coPdba.getChangeGroup('');
expect(coGrp.getGroupsByQuery).toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
});
});

How can I pass values loaded by ajax to components?

I'm passing a value as a parameter to a component.
<badge-button params="badge: oarBadge"></badge-button>
Here is the viewModel containing oarBadge:
function AppViewModel() {
var self = this;
self.oarBadge = ko.observable();
$.getJSON('/guy.json', function(data) {
var badge = new Badge('wood oar', data.badges.oar, false);
self.oarBadge(badge);
// self.oarBadge().has() returns true so the badge is being properly created with data
// returned by the ajax call
});
} // AppViewModel()
Here is the Badge constructor:
function Badge(name, has, active) {
var self = this;
self.name = ko.observable(name);
self.has = ko.observable(has);
self.active = ko.observable(active);
self.disabled = ko.computed(function() {
return self.has();
});
self.toggleActive = function() {
self.active(!self.active())
};
self.toggleHas = function() {
self.has(!self.has());
};
}
Here is the component's viewModel:
ko.components.register('badge-button', {
viewModel: function(params) {
var self = this;
self.badge = params.badge();
self.open = function() {
self.badge.toggleHas();
self.badge.toggleActive();
}
},
template:
'<img class="ui image" src="http://fakeimg.pl/300/" data-bind="click: open, css: { disabled: badge.disabled }" >'
});
When the page loads, I get an error telling me that badge is undefined.
Full example: https://gist.github.com/guyjacks/5a8763ff71f90e3fe8b4b153ed9a5283
Try setting a default object before the ajax call is completed, also you should assign the observable itself not the evaluation for the observable, so instead of doing this:
self.badge = params.badge();
You should do it like this:
self.badge = params.badge;
Otherwise your variable won't be updated once the ajax request is completed.
Here is a small example: https://jsfiddle.net/b0bdru1u/1/
Note: As far as I know the disable binding won't work in images

Update knockout viewmodel when uploading documents via ajax

I'm trying to use knockout for a view where I'm uploading documents and showing a list. For this I'm using jquery.form.js in order to upload them using ajax. I've changed that to use knockout and my viewmodel looks like this
var ViewModel = function (groups) {
var self = this;
self.groups = ko.observableArray(ko.utils.arrayMap(groups, function (group) {
return {
planName: ko.observable(group.Key),
documentList: ko.observableArray(ko.utils.arrayMap(group.Values, function (value) {
return {
document: ko.observable(new Document(value))
};
}))
};
}));
var options = {
dataType: 'json',
success: submissionSuccess
};
self.add = function () {
$('#addForm').ajaxSubmit(options);
return false;
};
function submissionSuccess(result) {
alert('success');
}
};
Having one Document function for doing the mapping. I'm stuck when receiving the Json data from the controller. The result is correct, a list of objects in the same format I'm receiving on first load but I don't know how to "refresh" the viewmodel to use this new list.
Don't know if using the ko mapping plugin would make it easier as I have never used it and don't even know if it's applicable for this.
The controller method, in case is relevant, is this (if something else neede let me know althoug won't have access to the code in the next hours)
[HttpPost]
public ActionResult AddDocument(AddDocumentViewModel viewModel)
{
var partyId = Utils.GetSessionPartyId();
if (viewModel.File.ContentLength > Utils.GetKBMaxFileSize * 1024)
ModelState.AddModelError("File", String.Format("The file exceeds the limit of {0} KB", Utils.GetKBMaxFileSize));
if (ModelState.IsValid)
{
_documentsManager.AddDocument(viewModel, partyId);
if (Request.IsAjaxRequest())
{
var vm = _displayBuilder.Build(partyId);
return Json(vm.Documents);
}
return RedirectToAction("Index");
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddDocument", newViewModel);
}
Thanks
EDIT: I came up with this code which seems to work (this function is inside the ViewModel one
function submissionSuccess(result) {
self.groups(ko.utils.arrayMap(result, function (group) {
return {
planName: ko.observable(group.Key),
documentList: ko.utils.arrayMap(group.Values, function (value) {
return {
document: new Document(value)
};
})
};
}));
};
Are you sure the documentList and document need to be observables themselves ?
To update the list you can push to it as you'd do on a regular array.
You could try something like this:
function submissionSuccess(result) {
self.groups.removeAll();
$.each(result, function(index, value) {
var documentList = [];
$.each(value.Values, function(index, value) {
documentList.push(new Document(value));
});
var group = {
planName:value.Key,
documentList: documentList
};
self.groups.push(group);
});
};

Bootstrap typeahead suggestions replaced when navigation

I'm using Bootstrap Typeahead to suggest som search results. The results are returned from a ajax ressource, and since this resource creates a delay, I'm experiencing a unfortunate effect.
Example:
If typing a 4 letter word, the suggestions will appear after 2 letters, I can then go through the results with the keys up/down, but suddenly the suggestions will reload because the last request has finished.
Is there any way to "cancel" any remaining, if user is currently using the keys up/down to go through the suggestions?
('#query').typeahead({
items: 4,
source: function (query,process) {
map = {};
$.getJSON('/app_dev.php/ajax/autosuggest/'+query, function (data) {
vehicles = [];
$.each(data, function(i,vehicle){
map[vehicle.full] = vehicle;
vehicles.push(vehicle.full);
});
process(vehicles);
});
},
updater: function (item) {
// do something here when item is selected
},
highlighter: function (item) {
return item;
},
matcher: function (item) {
return true;
}
});
I think the following will satisfy your needs (its hard to reproduce exactly) :
There is no easy way to abort a delayed response, but you could extend typeahead as I figured out here (without modifying bootstrap.js)
The concept is to catch keydown, detect if the event is KEY_UP or KEY_DOWN, set a flag is_browsing, and then abort process if is_browsing is true (that is, if the user has hitted KEY_UP or KEY_DOWN and no other keys afterwards).
Extending typeahead :
// save the original function object
var _superTypeahead = $.fn.typeahead;
// add is_browsing as a new flag
$.extend( _superTypeahead.defaults, {
is_browsing: false
});
// create a new constructor
var Typeahead = function(element, options) {
_superTypeahead.Constructor.apply( this, arguments )
}
// extend prototype and add a _super function
Typeahead.prototype = $.extend({}, _superTypeahead.Constructor.prototype, {
constructor: Typeahead
, _super: function() {
var args = $.makeArray(arguments)
// call bootstrap core
_superTypeahead.Constructor.prototype[args.shift()].apply(this, args)
}
//override typeahead original keydown
, keydown: function (e) {
this._super('keydown', e)
this.options.is_browsing = ($.inArray(e.keyCode, [40,38])>-1)
}
//override process, abort if user is browsing
, process: function (items) {
if (this.options.is_browsing) return
this._super('process', items)
}
});
// override the old initialization with the new constructor
$.fn.typeahead = $.extend(function(option) {
var args = $.makeArray(arguments),
option = args.shift()
// this is executed everytime element.modal() is called
return this.each(function() {
var $this = $(this)
var data = $this.data('typeahead'),
options = $.extend({}, _superTypeahead.defaults, $this.data(), typeof option == 'object' && option)
if (!data) {
$this.data('typeahead', (data = new Typeahead(this, options)))
}
if (typeof option == 'string') {
data[option].apply( data, args )
}
});
}, $.fn.typeahead);
This typeahead-extension could be placed anywhere, eg in a <script type="text/javascript"> -section
Testing the extension :
<input type="text" id="test" name="test" placeholder="type some text" data-provide="typeahead">
<script type="text/javascript">
$(document).ready(function() {
var url='typeahead.php';
$("#test").typeahead({
items : 10,
source: function (query, process) {
return $.get(url, { query: query }, function (data) {
return process(data.options);
});
}
});
});
</script>
A "serverside" PHP script that returns a lot of randomized options with forced delay, typeahead.php :
<?
header('Content-type: application/json');
$JSON='';
sleep(3); //delay execution in 3 secs
for ($count=0;$count<30000;$count++) {
if ($JSON!='') $JSON.=',';
//create random strings
$s=str_shuffle("abcdefghijklmnopq");
$JSON.='"'.$s.'"';
}
$JSON='{ "options": ['.$JSON.'] }';
echo $JSON;
?>
It really seems to work for me. But I cannot be sure that it will work in your case. Let me now if you have success or not.

calling inner function of plugin

Here is my plugin
(function($){
$.fn.myPlugin = function(options){
var defaults = {
width: 800
};
var defaults = $.extend(defaults, options);
var self = this;
function init(obj){
/*Initialize object*/
self.myPlugin.doAnimation(600,400);
}
$.fn.myPlugin.doAnimation = function(lV, rV){
/*Doing some animation work*/
}
return this.each(function(options){
init(this);
});
}
})(jQuery);
I am trying like this
var t = $('#id1').myPlugin();
t.doAnimation(); //getting error here, t.doAnimation is not a function
You can't. You're not returning the plugin. You're returning the jQuery object with the matched element(s) (like you probably should). The testing function is private to the myPlugin function anyway.
To call a method against a jQuery object, you would need to extend jQuery like you did for your myPlugin(), as in:
function($){
$.fn.myPlugin = function(options) {
...
}
$.fn.testing = function(options) {
...
}
})(jQuery);
Of course this would be completely separate from the original plugin.
I don't know what your plugin does, but if you need to share some data between plugins on a per-element basis, you could probably use jQuery's .data() method.

Resources