Backbone collection parse never fires, collection.models should have 2 items but is 0 - backbone-events

Here is my MemberView.js ...
define([
'jquery',
'underscore',
'backbone',
'collections/MembersCollection',
'text!templates/memTemplate.html'
], function($, _, Backbone, MembersCollection, memTemplate) {
var MembersView = Backbone.View.extend({
el: $("#page"),
initialize: function() {
var that = this;
this.collection = new MembersCollection([]);
this.collection.fetch({
success : function(collection, response, options) {
that.render();
},
error: function(collection, response, options) {
console.log('members fetch error: '+response.responseText);
alert(response.responseText);
}
});
},
render: function() {
var data = { members : this.collection.models };
var compiledTemplate = _.template( memTemplate, data );
this.$el.html( compiledTemplate );
}
});
return MembersView;
});
Here is my MemberCollection.js ...
define([
'jquery',
'underscore',
'backbone',
'models/Member'
], function($, _, Backbone, Member) {
var MembersCollection = Backbone.Collection.extend({
model: Member,
initialize : function(models, options) { },
url : '/modular-backbone/server/member',
parse: function (response) {
console.log("In Parse=" + response.length);
return response;
}
});
return MembersCollection;
});
There is never a "In Parse=?" in the console so I have to assume collection.parse is not fireing. Also, if I put a break in the view.render method, collection.models is always a zero length array even though I can clearly see 2 Member records in the fetch success response. What am I missing?
Thanks a lot for your advice :-)

Unbelievable...
I went back and cleaned up a bunch of commented out lines in a few js files, ran the app again and now it's working perfectly. This makes no sense at all.

Related

requirejs + django_select2

I'm building a wagtail / django app using requirejs as js assets combiner, for the site front end.
I'm using it because I've ever been in a kind of JS dependencies hell, where nothing works because of multiple versions of same libs loaded, from different django apps... (I don't even know if it is a good solution)
I've to tell that I'm not a JS expert, and I've none arround me :(
I'm using the good old templates to render the pages, not using angular, react, riot nor vue : I'm a pretty old school dev :)
I've already adapted some scripts to use require, but I'm stuck for now...
I've installed the django_select2 application, and I'm trying to adapt the django_select2.js asset.
I've loaded select2 through bower, and I've updaetd my config.js:
"shim": {
select2: {
deps: ["jquery"],
exports: "$.fn.select2"
}
},
paths: {
...
select2: "select2/dist/js/select2"
}
Then I'm trying to adapt the django_select2.js:
require(['jquery', 'select2'], function ($, select2) {
return (function ($) {
var init = function ($element, options) {
$element.select2(options);
};
var initHeavy = function ($element, options) {
var settings = $.extend({
ajax: {
data: function (params) {
var result = {
term: params.term,
page: params.page,
field_id: $element.data('field_id')
}
var dependentFields = $element.data('select2-dependent-fields')
if (dependentFields) {
dependentFields = dependentFields.trim().split(/\s+/)
$.each(dependentFields, function (i, dependentField) {
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
})
}
return result
},
processResults: function (data, page) {
return {
results: data.results,
pagination: {
more: data.more
}
}
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoSelect2 = function (options) {
var settings = $.extend({}, options);
$.each(this, function (i, element) {
var $element = $(element);
if ($element.hasClass('django-select2-heavy')) {
initHeavy($element, settings);
} else {
init($element, settings);
}
});
return this;
};
$(function () {
$('.django-select2').djangoSelect2();
});
}($));
});
I'm having a Mismatched anonymous define() when running my page in the browser...
I'me realy not a JS expert, I'm coding by trial and error... Could anyone help me with this ?
Thanks !
OK, I have an auto-response...
I've inherited the mixin:
class _Select2Mixin(Select2Mixin):
def _get_media(self):
"""
Construct Media as a dynamic property.
.. Note:: For more information visit
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
"""
return forms.Media(js=('django_select2/django_select2.js', ),
css={'screen': (settings.SELECT2_CSS,)})
media = property(_get_media)
class _Select2MultipleWidget(_Select2Mixin, forms.SelectMultiple):
pass
Then I can use the widget:
class DocumentationSearchForm(forms.Form):
...
document_domains = forms.ModelMultipleChoiceField(required=False,
label=_('Document domains'),
queryset=NotImplemented,
widget=_Select2MultipleWidget)
I've set my config.js file for path:
requirejs.config({
paths: {
jquery: 'jquery/dist/jquery',
select2: "select2/dist/js"
},
shim: {
"select2/select2": {
deps: ["jquery"],
exports: "$.fn.select2"
}
}
});
Then I've overridden the django_select2.js file, to wrap the content in a require satement:
require(['jquery', 'select2/select2'], function ($) {
(function ($) {
var init = function ($element, options) {
$element.select2(options);
};
var initHeavy = function ($element, options) {
var settings = $.extend({
ajax: {
data: function (params) {
var result = {
term: params.term,
page: params.page,
field_id: $element.data('field_id')
}
var dependentFields = $element.data('select2-dependent-fields')
if (dependentFields) {
dependentFields = dependentFields.trim().split(/\s+/)
$.each(dependentFields, function (i, dependentField) {
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
})
}
return result
},
processResults: function (data, page) {
return {
results: data.results,
pagination: {
more: data.more
}
}
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoSelect2 = function (options) {
var settings = $.extend({}, options);
$.each(this, function (i, element) {
var $element = $(element);
if ($element.hasClass('django-select2-heavy')) {
initHeavy($element, settings);
} else {
init($element, settings);
}
});
return this;
};
$(function () {
$('.django-select2').djangoSelect2();
});
}($));
});
That's all, folks !

Decouple Knockout dependencies in Jasmine unit tests

I am introducing javascript unit tests to my company's application. I've worked predominantly with AngularJS, but their framework of choice is Knockout. The code is relatively modular in design (thanks to Knockout), so I assumed it would be easy to add unit tests around the code (much like Angular is).
However, there is a further complication: the system uses require.js as an IoC container. Also, my test runner is Chutzpah, which is headless/in the Visual Studio Test Explorer. I have tried to mock out the dependencies using SquireJS, but I run into a snag when trying to instantiate the ViewModel: it wants certain dependencies that are set up in the Component and inherited. I try to load the component, which has further dependencies, and so on and so on. Below I have a dumbed-down version of the setup that outlines the point. What is the best way to instantiate this view model so that I can mock out the dependencies with Squire and run my tests?
Component
define(function (require) {
var Boiler = require('Boiler'),
BaseViewModel = require('../../../../core/baseClasses/BaseViewModel'),
viewTemplate = require('text!./view.html'),
viewModel = require('./viewModel'),
nls = require('i18n!./nls/resources');
var Component = function (initializingParameters) {
//...blah blah blah setup things
};
return Component;
});
ViewModel
define(function (require) {
var Constants = require('../../../../core/constants');
var momDate = moment().format('MMM DD, YYYY');
var Utils = require("../../../../model/utils");
var utils = new Utils();
var otherComponentUtils = require("../../../otherModule/otherComponent/OtherComponentUtils");
var otherComponentUtils = new OtherComponentUtils();
var ViewModel = function (globalContext, moduleContext, dataContext, domElement) {
var Self = this;
Self.super(moduleContext, dataContext, resPickerContext);
var constants = new Constants();
var utils = require("../../../../model/utils");
var Utils = new utils();
Self.Items = ko.observableArray().extend({ throttle: 500 });
//a bunch more Knockout object setup
Self.GetAuthorizationTypesArray = function (itemId) {
dataContext.dataRequestHandler(dataContext.serviceConstants.GetTransactionTypes, { ItemId: itemId },
function (data) {
if (data != null && data.length !== 0) {
Self.TransactionTypeArray(data);
var transactionTypeArray = Self.TransactionTypeArray();
if (transactionTypeArray.length) {
var paymentInfo = Self.PaymentInfo();
if (paymentInfo !== null && paymentInfo !== undefined && paymentInfo.IsSpecial) {
var childThing = Self.ChildThing();
dataContext.dataRequestHandler(dataContext.serviceConstants.GetChild, { ChildId: childThing.ChildId, SpecialId: childThing.SpecialID }, function (data) {
var child = data[0];
var specialTypeId = child.ListId;
if (specialTypeId === 13)
Self.BigThing.Type(1);
else
Self.BigThing.Type(2);
}, Self.ShowError);
}
}
}
},
Self.ShowError);
}
return ViewModel;
});
chutzpah.json
{
"Framework": "jasmine",
"TestHarnessReferenceMode": "AMD",
"TestHarnessLocationMode": "SettingsFileAdjacent",
"RootReferencePathMode": "SettingsFileDirectory",
"References": [
{ "Path": "../../myWebApp/Scripts/libs/assets/plugins/jquery-1.10.2.min.js" },
{ "Path": "../../myWebApp/scripts/libs/moment/moment.min.js" },
{ "Path": "../../myWebApp/Scripts/libs/require/require.js" },
{ "Path": "unittest.main.js" }
],
"Tests": [
{
"Path": "app",
"Includes": [ "*.tests.js" ]
}
]
}
unittest.main.js
"use strict";
require.config({
paths: {
'jasmine': ['jasmine/jasmine'],
'jasmine-html': ['jasmine/jasmine-html'],
'jasmine-boot': ['jasmine/boot'],
squire: 'Squire'
},
shim: {
'jasmine-html': {
deps : ['jasmine']
},
'jasmine-boot': {
deps : ['jasmine', 'jasmine-html']
},
'squire': {
exports: 'squire'
}
},
config: {
text: {
useXhr: function (url, protocol, hostname, port) { return true },
//Valid values are 'node', 'xhr', or 'rhino'
env: 'xhr'
}
},
waitSeconds: 0
});
viewModel.tests.js
define(function (require) {
var testTargetPath = '../../myWebApp/Scripts/app/modules/thisModule/myViewModel';
describe('My View Model', function () {
var mockDataContext;
var mockResponseData;
var injector;
var viewModel;
beforeEach(function () {
var Squire = require('Squire');
injector = new Squire();
});
beforeEach(function () {
mockResponseData = {};
mockDataContext = {
dataRequestHandler: function (url, data, onSuccess) {
onSuccess(mockResponseData[url]);
},
serviceConstants: {
GetTransactionTypes: 'getTransactionTypes',
GetChild: 'getNewPolicy'
}
};
injector.mock("dataContext", mockDataContext);
});
beforeEach(function (done) {
injector.require([testTargetPath], function (ViewModel) {
viewModel = new ViewModel();
done();
});
});
it('gets authorization type array', function () {
spyOn(mockDataContext, 'dataRequestHandler').and.callThrough();
mockResponseData = {
'getTransactionTypes': [
{ name: 'auth type 1', TransactionTypeId: 90210 },
{ name: 'auth type 2', TransactionTypeId: 42 },
]
};
viewModel.GetAuthorizationTypesArray(9266);
expect(mockDataContext.dataRequestHandler).toHaveBeenCalledWith('getTransactionTypes', { ItemId: 9266 });
expect(viewModel.TransactionTypeArray()).toBe(mockResponseData);
});
});
});
To be specific, in the ViewModel, when the tests run, it complains that super is undefined.
Alright, turns out there were a number of issues I had to deal with:
At first I thought the failing on super was because of an ES6
compatibility issue (since PhantomJS only supports ES5). However,
it turns out that the project is not using ES6, but
inheritance.js, which manually builds the super dependencies.
That setup is done within the component section of Knockout in our
solution, so I replicated it within the unit tests.
I was not properly setting up Squire to inject my dependencies, and
corrected that.
I did not make any changes to my component or view model, and my Chutzpah configuration stayed the same. However, unittest.main.js was updated, as were my tests:
unittest.main.js
"use strict";
require.config({
paths: {
'jasmine': ['jasmine/jasmine'],
'jasmine-html': ['jasmine/jasmine-html'],
'jasmine-boot': ['jasmine/boot'],
squire: 'Squire',
"baseViewModel": '../../myWebApp/Scripts/app/core/baseClasses/BaseViewModel'
},
shim: {
'jasmine-html': {
deps : ['jasmine']
},
'jasmine-boot': {
deps : ['jasmine', 'jasmine-html']
},
'squire': {
exports: 'squire'
}
},
config: {
text: {
useXhr: function (url, protocol, hostname, port) { return true },
//Valid values are 'node', 'xhr', or 'rhino'
env: 'xhr'
}
},
waitSeconds: 0
});
viewModel.tests.js
define(function (require) {
var testTargetPath = '../../myWebApp/Scripts/app/modules/thisModule/myViewModel';
describe('View Model', function () {
var mockGlobalContext;
var mockModuleContext;
var mockDataContext;
var mockResponseData;
var injector;
var viewModel;
var mockUtils;
beforeEach(function () {
var Squire = require('Squire');
injector = new Squire();
});
beforeEach(function () {
mockResponseData = {};
mockGlobalContext = {};
mockUtils = {};
mockModuleContext = {};
mockDataContext = {
dataRequestHandler: function (url, data, onSuccess) {
onSuccess(mockResponseData[url]);
},
serviceConstants: {
GetTransactionTypes: 'getTransactionTypes',
GetChild: 'getNewPolicy'
}
};
injector.mock("../../../../model/utils", function () { return mockUtils; });
spyOn(mockDataContext, 'dataRequestHandler').and.callThrough();
});
beforeEach(function (done) {
injector.require([testTargetPath], function (ViewModel) {
var BaseViewModel = require('baseViewModel');
BaseObject.Create(ViewModel, BaseViewModel, [mockGlobalContext, mockModuleContext, mockDataContext]);
viewModel = new ViewModel(mockGlobalContext, mockModuleContext, mockDataContext);
done();
});
});
it('gets authorization type array', function () {
mockResponseData = {
'getGatewayTransactionTypes': [
{ name: 'auth type 1', TransactionTypeId: 90210 },
{ name: 'auth type 2', TransactionTypeId: 42 },
]
};
viewModel.GetAuthorizationTypesArray(9266);
expect(mockDataContext.dataRequestHandler).toHaveBeenCalledWith('getGatewayTransactionTypes', { ItemId: 9266 }, jasmine.any(Function), viewModel.ShowError);
expect(viewModel.TransactionTypeArray()).toEqual(mockResponseData['getGatewayTransactionTypes']);
});
});
});
With these changes, the test runs successfully and passes.

How to make external template for backbone view

I am new to things like angular and backbone and am trying to understand the structure better. I built some views but if I leave any spaces in the template chunk it breaks everything.
var HomeView = Backbone.View.extend({
template: '<h1>Home</h1><p>This is the first test. I think this is the way.</p>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var AboutView = Backbone.View.extend({
template: '<h1>About</h1><p>This is the second test. I think this is the way.</p>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var CoolView = Backbone.View.extend({
template: '<h1>Cool</h1><p>This is the third test. I think this is the way.</p>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homeRoute',
'home': 'homeRoute',
'about': 'aboutRoute',
'cool': 'coolRoute',
},
homeRoute: function () {
var homeView = new HomeView();
$("#content").html(homeView.el);
},
aboutRoute: function () {
var aboutView = new AboutView();
$("#content").html(aboutView.el);
},
coolRoute: function () {
var coolView = new CoolView();
$("#content").html(coolView.el);
}
});
var appRouter = new AppRouter();
Backbone.history.start();
Is there a way to make this pull from external templates outside of the javascript. What's best practices if the pages are very elaborate?
Here is my jsfiddle link.
https://jsfiddle.net/galnova/k4ox8yap/14/
You can specify your template in your html
<script id='CoolViewTpl' type='template'>
<h1>Cool</h1><p>This is the third test. I think this is the way.</p>
</script>
Then in your render func, simply select the template by ID and get the content.
var CoolView = Backbone.View.extend({
template: "#CoolViewTpl",
initialize: function () {
this.render();
},
render: function () {
this.$el.html($(this.template).html());
}
});
If you are using a javascript module loader like RequireJs (which you'll probably find yourself doing when the application gets more complicated!) then the templates can be loaded from an external source using the RequireJs text plugin.
For example you might have a file called home.js which could look like:
require([ "backbone", "underscore", "text!templates/template.html" ],
function( Backbone, _, template ) {
return Backbone.View.extend({
template: _.template( template ),
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
}
);
Then an app.js file could contain your application logic and require your views:
require([ "backbone", "jquery", "home.js" ],
function( Backbone, $, HomeView ) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homeRoute',
'home': 'homeRoute',
// etc
},
homeRoute: function () {
var homeView = new HomeView();
$("#content").html(homeView.el);
}, // etc

Delay ember view render till $getJSON isLoaded

The problem with this code is that the render code is entered twice, and the buffer is not where I expect it. Even when I get the buffer, the stuff I push in is not rendered to the screen.
App.FilterView = Ember.View.extend({
init: function() {
var filter = this.get('filter');
this.set('content', App.ViewFilter.find(filter));
this._super();
},
render: function(buffer) {
var content = this.get('content');
if(!this.get('content.isLoaded')) { return; }
var keys = Object.keys(content.data);
keys.forEach(function(item) {
this.renderItem(buffer,content.data[item], item);
}, this);
}.observes('content.isLoaded'),
renderItem: function(buffer, item, key) {
buffer.push('<label for="' + key + '"> ' + item + '</label>');
}
});
And the App.ViewFilter.find()
App.ViewFilter = Ember.Object.extend();
App.ViewFilter.reopenClass({
find: function(o) {
var result = Ember.Object.create({
isLoaded: false,
data: ''
});
$.getJSON("http://localhost:3000/filter/" + o, function(response) {
result.set('data', response);
result.set('isLoaded', true);
});
return result;
}
});
I am getting the data I expect and once isLoaded triggers, everything runs, I am just not getting the HTML in my browser.
As it turns out the answer was close to what I had with using jquery then() on the $getJSON call. If you are new to promises, the documentation is not entirely straight forward. Here is what you need to know. You have to create an object outside the promise - that you will return immediately at the end and inside the promise you will have a function that updates that object once the data is returned. Like this:
App.Filter = Ember.Object.extend();
App.Filter.reopenClass({
find: function(o) {
var result = Ember.Object.create({
isLoaded: false,
data: Ember.Object.create()
});
$.getJSON("http://localhost:3000/filter/" + o).then(function(response) {
var controls = Em.A();
var keys = Ember.keys(response);
keys.forEach(function(key) {
controls.pushObject(App.FilterControl.create({
id: key,
label: response[key].label,
op: response[key].op,
content: response[key].content
})
);
});
result.set('data', controls);
result.set('isLoaded', true);
});
return result;
}
});
Whatever the function inside then(), is the callback routine that will be called once the data is returned. It needs to reference the object you created outside the $getJSON call and returned immediately. Then this works inside the view:
didInsertElement: function() {
if (this.get('content.isLoaded')) {
var model = this.get('content.data');
this.createFormView(model);
}
}.observes('content.isLoaded'),
createFormView: function(data) {
var self = this;
var filterController = App.FilterController.create({ model: data});
var filterView = Ember.View.create({
elementId: 'row-filter',
controller: filterController,
templateName: 'filter-form'
});
self.pushObject(filterView);
},
You can see a full app (and bit more complete/complicated) example here

Getting a sub-collection of a model in a template with Underscore and Backbone

I have a JSon like this that is a collection of issues:
{
"id": "505",
"issuedate": "2013-01-15 06:00:00",
"lastissueupdate": "2013-01-15 13:51:08",
"epub": false,
"pdf": true,
"price": 3,
"description": "Diese Version ist nur als PDF verfügbar.",
"downloadable": true,
"renderings": [
{
"height": 976,
"width": 1024,
"sourcetype": "pdf",
"pagenr": 0,
"purpose": "newsstand",
"relativePath": null,
"size": 0,
"url": "a/url/image.jpg"
},
{
"height": 488,
"width": 512,
"sourcetype": "pdf",
"pagenr": 0,
"purpose": "newsstand",
"relativePath": null,
"size": 0,
"url": "a/url/image.jpg"
}
}
I'm using backbone and I want to access the sub-elements (renderings).
I overwrite the parse method to tell that my 'renderings' is a new collection, this part works fine.
Where I'm getting some problems is when i pass my collection of issues in my template. Then I can make a for each on the issues and accessing his attributes (id, issuedate, lastissueupdate,etc) but how can I access the renderings of my issues ?
My View:
define([
'jquery',
'underscore',
'backbone',
'text!templates/home/homeTemplate.html',
'collections/issues/IssueCollection'
], function($, _, Backbone, homeTemplate, IssueCollection){
var HomeView = Backbone.View.extend({
el: $("#page"),
initialize:function(){
var that = this;
var onDataHandler = function(issues){
that.render();
}
this.issues = new IssueCollection();
this.issues.fetch({
success:onDataHandler,
error:function(){
alert("nicht gut")
}
});
},
render: function(){
$('.menu li').removeClass('active');
$('.menu li a[href="#"]').parent().addClass('active');
var compiledTemplate = _.template(homeTemplate, {_:_, issues: this.issues.models});
this.$el.html(compiledTemplate);
}
});
return HomeView;
});
Then I have a very basic template.
To resume, I have a collection ISSUES of models ISSUE containing a collection of RENDERINGS and I want to access the model RENDER in my template.
edit:
IssueCollection
define([
'underscore',
'backbone',
'models/issue/IssueModel'
], function(_, Backbone, IssueModel){
var IssueCollection = Backbone.Collection.extend({
model: IssueModel,
url: function(){
return '/padpaper-ws/v1/pp/fn/issues';
},
parse: function(response){
return response.issues;
}
//initialize : function(models, options) {},
});
return IssueCollection;
});
IssueModel
define([
'underscore',
'backbone',
'collections/renderings/RenderingsCollection'
], function(_, Backbone, RenderingsCollection) {
var IssueModel = Backbone.Model.extend({
parse: function(response){
response.renderings = new RenderingsCollection(response.renderings);
return response;
},
set: function(attributes, options) {
if (attributes.renderings !== undefined && !(attributes.renderings instanceof RenderingsCollection)) {
attributes.renderings = new RenderingsCollection(attributes.renderings);
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
});
return IssueModel;
});
RenderingsCollection
define([
'underscore',
'backbone',
'models/rendering/RenderingModel'
], function(_, Backbone, RenderingModel){
var RenderingsCollection = Backbone.Collection.extend({
model: RenderingModel
//initialize : function(models, options) {},
});
return RenderingsCollection;
});
RenderingModel
define([
'underscore',
'backbone'
], function(_, Backbone) {
var RenderingModel = Backbone.Model.extend({
return RenderingModel;
});
Thank's !!
You could use the Collection#toJSON method to have a complete representation of your whole data. Still, you'd have to use model.attributes.key to access the attributes of your models. So I'd not so much recommend it. Instead, I'd say you should build your own JSON object (using the toJSON method recursively). (I'll update with some code if it's not clear)
Edit:
var data = [], renderings;
for(var i=0; i<issues.length; i++) {
data.push(issues.models[i].toJSON());
data[i].renderings = [];
renderings = issues.models[i].get('renderings');
for(var j=0; j<renderings.length; j++) {
data[i].renderings.push(renderings.models[j].toJSON());
}
}
for example would give you the whole data, and you could access any attribute easily.
Actually I could use a double _.each in my template like this, giving my models from the view.
<% _.each(issues, function(issue) { %>
.... code ...
<% _.each(issue.get('renderings'), function(rendering) { %>
And then to access my renderings attributes we do like this:
<%= rendering.url %>
for example, not "get" like the first time.
Thank's for your participation!