I'm trying to start using moddule pattern in my JS code from the beginning but I have problems to understand how to perform this kind of code design.
This is a simple event:
$('#docTable').on('dblclick', 'tbody tr.line', function (event) {
$('#modal1').modal({ keyboard: false, backdrop: "static", dismiss: "modal" });
$('#modal1').modal('show');
});
I've created a couple of JS files. View.js:
var task = window.task || {};
task.View = (function () {
function View(rootElement) {
var dom = {
table: $('#docTable'),
},
callbacks = {
onsubmit: undefined
};
return {
};
}
return View;
}());
and Controller.js:
$(document).on('ready', function () {
var View = task.View(document);
});
but I have no idea how to continue and catch the dblclick event.
Could anybody please help me?
Thanks in advance.
You can create 'class' View and add event binding to its prototype. After that you can use it on multiple tables. If you want to have access to element in table you can add classes to them and find them in defineDOM method:
View.js
var task = window.task || {};
task.View = function (table) {
this.$table = $(table);
this.init();
};
task.View.prototype ={
init: function () {
this.defineDOM();
this.bindEvents();
},
defineDOM: function() {
// Search for DOM elements in context of table element
this.$button = $('.docButton', this.$table);
this.$links = $('.docLinks', this.$table);
},
bindEvents: function () {
this.$table.on('dblclick', 'tbody tr.line', this.onDblClick.bind(this))
},
onDblClick: function () {
$('#modal1').modal({ keyboard: false, backdrop: "static", dismiss: "modal" });
$('#modal1').modal('show');
}
}
Usage
$(document).on('ready', function () {
new task.View('#docTable');
});
Related
I have my javascript code like this . Inside that I have an init() function and in that function I have an options JSON object and in that object I have a function defined as objectselected(). How I call that function in a button click event
I have tried like this WorkFlow.init().options.Objectselected() but it is not working,
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
},
}
How to call that function.
One way around is you can return options and than call it.
init: function () {
var options = {
...your code..}
return options;
},
and call it than
var options = WorkFlow.init();
options.Objectselected();
As it stands, you have no access to options because it's a local variable - that is, local to its scope.
To access its contents, you'll need to return it from init().
Think about it:
WorkFlow.init()
Currently this returns undefined, because your init() returns nothing. You're trying to chain like in jQuery, but that relies on the API always returning the instance. Your path finds a dead-end at init().
To fix this, have init() return options - or at least the part of it you want to access from outside - an "export".
So (basic example)
init: function() {
var options {
my_func: function() { }, //<-- we want outside access to this
private: 'blah' //<-- this can stay private - leave it out of the export
}
//return an export, exposing only what we need to
return {
my_func: options.my_func
}
}
You need to return options as it is inside init function's scope
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
return options;
},
}
And call it as WorkFlow.init().objectSelected();
Building on Patrick's comment, you'd need to return options from the init function:
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
...
options.initialize();
return options;
},
}
I've a custom directive which is used to upload/import a binary file. The directive listens for a change event on an <input type="file"../> element.
So now I've a test which triggers a change event, which works fine and do have code coverage apart from the body of reader.onload() fn. So, can someone guide me on what to do so that ...onload() fn is trigger via unit test.
here is the listener within directive:
element.bind('change', function(changeEvt){
var reader = new FileReader();
var result = {
filename: changeEvt.target.files[0].name
};
reader.onload = function (loadEvent) {
scope.$apply(function () {
result.data = loadEvent.target.result;
scope.fileSelected({content: result});
});
};
reader.readAsArrayBuffer(changeEvt.target.files[0]);
});
test I've so far:
describe('file import', function () {
beforeEach(inject(function ($compile) {
scope.testOnFileSelected = jasmine.createSpy('testOnFileSelected');
eventListener = jasmine.createSpy();
spyOn(windowMock, 'FileReader').and.returnValue({
addEventListener: eventListener,
readAsArrayBuffer : function() {
//return console.log(file);
}
});
elm = angular.element('<div id="testImportBtn"><my-file-select-button id="testFileSelect" caption="buttonText" file-selected="testOnFileSelected(content)" ng-disabled="testDisabled"></my-file-select-button></div>');
$compile(elm)(scope);
scope.$digest();
}));
fit('should render the button and be visible', function () {
var button = elm.find('#testFileSelect');
button .triggerHandler({type: 'change', target: {files: [{name: 'some.tar.gz'}]}});
expect(windowMock.FileReader).toHaveBeenCalled();
//expect(eventListener).toHaveBeenCalled(); Fails
//expect(scope.testOnFileSelected).toHaveBeenCalledWith({data: {}, fileName: 'some.tar.gz'}); fails
});
});
Here is a view of code coverage:
You could have add onload function in spy and call directly after fileReader invoked.
spyOn(window, 'FileReader').and.returnValue({
onload: function() {
},
readAsArrayBuffer : function() {
//return console.log(file);
}
});
and "it" block you can call onload function like,
var mockedLoadEvent = { //eventdetails }
expect(window.FileReader).toHaveBeenCalled();
window.FileReader().onload(mockedLoadEvent);
This will call your custom onload function in controller/Service.
complete code below:
describe('file import', function () {
beforeEach(inject(function ($compile) {
scope.testOnFileSelected = jasmine.createSpy('testOnFileSelected');
eventListener = jasmine.createSpy();
spyOn(window, 'FileReader').and.returnValue({
onload: function() {
},
readAsArrayBuffer : function() {
//return console.log(file);
}
});
elm = angular.element('<div id="testImportBtn"><my-file-select-button id="testFileSelect" caption="buttonText" file-selected="testOnFileSelected(content)" ng-disabled="testDisabled"></my-file-select-button></div>');
$compile(elm)(scope);
scope.$digest();
}));
it('should render the button and be visible', function () {
var button = elm.find('#testFileSelect');
button .triggerHandler({type: 'change', target: {files: [{name: 'some.tar.gz'}]}});
var mockedLoadEvent = { //eventdetails }
expect(window.FileReader).toHaveBeenCalled();
window.FileReader().onload(mockedLoadEvent);
});
});
I've created 2 separate views, 1 to render the template and the other one is where I bind the events, then I tried merging them into one in which case it causes an Uncaught TypeError: Object [object Object] has no method 'template'. It renders the template and the events are working as well, but I get the error.
edit.js, this is the combined view, which I think it has something to do with their el where the error is coming from
window.EditView = Backbone.View.extend ({
events: {
"click #btn-save" : "submit"
},
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
},
submit: function () {
console.log('editing');
$.ajax({ ... });
return false;
}
});
var editView = new EditView();
signin.js, this is the view that I can't merge because of the el being used by the ajax call and in SigninView's $(this.el) which causes the rendering of the templates faulty
window.toSigninView = Backbone.View.extend ({
el: '#signin-container',
events: {
"click #btn-signin" : "submit"
},
initialize: function() {
console.log('Signin View');
},
submit: function() {
$.ajax({ ... });
return false;
}
});
var toSignin = new toSigninView();
window.SigninView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
and I use utils.js to call my templates
window.utils = {
loadTpl: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('templates/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
In my Router.js, this is how I call the rendering of templates
editProfile: function() {
if (!this.editView) {
this.editView = new EditView();
}
$('#global-container').html(this.editView.el);
},
utils.loadTpl (['SigninView', 'EditView'],
function() {
appRouter = new AppRouter();
Backbone.history.start();
});
I think that I figured out your problem.
First merge your views and delete the line var toSignin = new toSigninView();
Second modify your utils.js code like this :
window[view].prototype.template = _.template(data);
new window[view]();
I like the idea of using Singleton mentioned here http://www.adobe.com/devnet/html5/articles/javascript-design-patterns-pt1-singleton-composite-facade.html:
var Namespace = {
Util: {
util_method1: function() {…},
util_method2: function() {…}
},
Ajax: {
ajax_method: function() {…}
},
some_method: function() {…}
};
Let's say I have to add some methods and new namespace too (Namespace.Util2) later, how can I add methods without modifying the above code
It is simply:
Namespace.Util.newUtilMethod = function () { };
To add a new namespace,
Namespace.Util2 = { /* definitions */ };
namespace.util.newFunc = function () { };
or, if you're using jquery and want to add a bunch at once:
var newStuff = {
newThing1: function () {...},
newThing2: function () {...},
newThing3: function () {...}
};
$.extend(namespace.util, newStuff);
Looked around SO but couldn't find anything useful, so..
I have a Backbone.js contacts model with a contact card view. This view has many inputs where you can edit the contacts information.
I have many other forms on the page that are NOT backbone models, so they use a 'save button' to save. I basically want this save button to also trigger Contacts.CardView.saveCard(); (which could possibly be FileApp.cardView.saveCard as well? -- some of my code is below.
Is there any way to do this? I thought I could just use the following, but it seems it won't bind an event to anything outside the view?:
events: {
"change input": "change",
"click #save": "saveCard"
},
$('#save').click(function() {
FileApp.cardView.saveCard;
_SAVE.save();
})
CardView
window.Contacts.CardView = Backbone.View.extend({
events: {
"click #save": "saveCard" // doesnt work because #save is outside the view?
},
saveCard: function(e) {
this.model.set({
name:$('#name').val()
});
if (this.model.isNew()) {
var self = this;
FileApp.contactList.create(this.model, {
success:function () {
FileApp.navigate('contacts/' + self.model.id, false);
}
});
} else {
this.model.save();
}
return false;
}
}
Router:
var FileRouter = Backbone.Router.extend({
contactCard:function (id) {
if (this.contactList) {
this.cardList = new Contacts.CardCollection();
var self = this;
this.cardList.fetch({
data: {
"id":id
},
success: function(collection, response) {
if (self.cardView) self.cardView.close();
self.cardView = new Contacts.CardView({
model: collection.models[0]
});
self.cardView.render();
}
});
} else {
CONTACT_ID = id;
this.list();
}
}
});
var FileApp = new FileRouter();
One option is to create your own Events object for this case:
// Before initializing views, etc.
var formProxy = {};
_.extend(formProxy, Backbone.Events);
// Add the listener in the initialize for the CardView
window.Contacts.CardView = Backbone.View.extend({
initialize : function() {
formProxy.on('save', this.saveCard, this);
},
saveCard: function() {
this.model.set({
name:$('#name').val()
});
if (this.model.isNew()) {
var self = this;
FileApp.contactList.create(this.model, {
success:function () {
FileApp.navigate('contacts/' + self.model.id, false);
}
});
} else {
this.model.save();
}
return false;
}
}
// Save
$('#save').click(function() {
formProxy.trigger('save');
});
See: http://documentcloud.github.com/backbone/#Events