Overriding Knockout View-Model function - backbone.js

I am using knockout and backbone in my application. My test-view.js look like this:
define([
"knockout",
"./base",
"./../viewmodels/test-vm",
"text!./../templates/test-template.html"
],
function(ko, BaseView, TestViewModel, template) {
var TestView = BaseView.extend({
template: template,
initialize: function() {
this.viewModel = new TestViewModel();
},
render: function(){
this.$el.html(template);
return this;
},
postRender: function() {
ko.applyBindings(this.viewModel, this.el);
}
});
return TestView;
});
test-template.html:
<button class="btn" data-bind="click: test">test</button>
and test-vm.js as follows:
define([],
function() {
function TestViewModel() {
var self = this;
self.test = function () {
alert("in test view model");
};
}
return TestViewModel;
});
When I click button, self.test is invoked. My question is how can I extend TestViewModel in another file and override test function to do some specific things? Thanks in advance!

I don't see any reason you can't use the commonly used "Classical inheritance with Object.create" approach as described here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// Define the base class
var TestViewModel = function () {
this.sharedProperty = true;
};
TestViewModel.prototype.test = function() {
log("testing TestViewModel");
};
// Define the extended class
var ExtendedTestViewModel = function() {
TestViewModel.call(this);
};
// Copy the prototype and reset the constructor
ExtendedTestViewModel.prototype = Object.create(TestViewModel.prototype);
ExtendedTestViewModel.constructor = ExtendedTestViewModel;
// Override the inherited `test` function
ExtendedTestViewModel.prototype.test = function() {
// To call the base method:
// (skip this if you want to completely override this function)
TestViewModel.prototype.test.call(this);
// Add functionality:
log("testing ExtendedTestViewModel");
};
// Create an instance
var etvm = new ExtendedTestViewModel();
// This will log both the inherited message, as well as the extended message
etvm.test();
// ExtendedTestViewModel has all properties that are in TestViewModel
log(etvm.sharedProperty);
// utils
function log(msg) {
var pre = document.createElement("pre");
pre.appendChild(document.createTextNode(msg));
document.body.appendChild(pre);
};

Related

How to refactor some function inside a Protractor Page Object or spec file into a separate file?

For PageObject, we have buttons/links in the bottom of the page. We have to scroll down to the bottom so as to click.
var ContactAddPage = function() {
...
this.btnAdd = element(by.id('btnAdd'));
this.add = function() {
scrollToBottom();
this.btnAdd.click();
};
function scrollToBottom(){
browser.executeScript('window.scrollTo(0,10000);');
}
};
Since scrollToBottom() will be used in many other page object, I assume we could refactor it out into a separator file and somehow require() it into the page object. But how to do that?
As for the spec files, there would be common functions like log in with different persona in beforeAll() and log out in afterAll(). And checking url is also common to each page:
function loginAs(role) {
var loginPage = new LoginPage();
loginPage.login(role);
}
function logOut() {
var lnkLogOff = element(by.linkText('Log off'));
if (element(by.linkText('Log off')).isPresent()) lnkLogOff.click();
}
function expectRouteToBe(expectUrl) {
expect(browser.getCurrentUrl()).toBe(browser.baseUrl + expectUrl);
}
The same question here: what is the correct way to move them out and require() them back?
You can extend modules by using exports and prototype inheritances... then you can have methods available in any or all of your pages. Eg. something like:
basePage.js:
var BasePage = function() {
this.loginAs = function(role) {
this.login(role);
};
...
};
module.exports = new BasePage();
And then extend any number of pages thusly:
var basePage = require('./basePage.js');
var MainPage = function() {
this.btnAdd = element(by.id('btnAdd'));
this.add = function() {
this.scrollToBottom();
this.btnAdd.click();
};
this.scrollToBottom = function() {
browser.executeScript('window.scrollTo(0,10000);');
};
};
MainPage.prototype = basePage;
module.exports = new MainPage();
And then in your spec, you can:
var mainPage = require("../pages/mainPage.js");
...
it('should login and click add', function() {
mainPage.loginAs(role);
mainPage.btnAdd.click()
...
});
Hope that helps...
We've had something similar in our set of protractor e2e tests. We've created a set of helper libraries for the reusable functionality used in tests.
Example:
create helpers.js file:
var Helpers = function() {
this.scrollToBottom = function () {
browser.executeScript('window.scrollTo(0,10000);');
};
};
module.exports = new Helpers();
require and use it in your Page Object:
var helpers = require("./helpers.js");
var ContactAddPage = function() {
this.btnAdd = element(by.id('btnAdd'));
this.add = function() {
helpers.scrollToBottom();
this.btnAdd.click();
};
};
module.exports = ContactAddPage;

inherit backbone view declared in different .js file

Expanding my original question located here
If my Userview is in Userview.js file and I want to inherit from that class in AdminView.js file, how would I go about it.
I tried this, but would not fit my need as I don't have a class.
UPDATE 1:
define([
'modules/userdetail'
],
function(UserView) {
var adminView
adminView.Views.Content = UserView.Views.Content.extend({
initialize: function() {
//looking to override the fn that is declared in UserView
console.log("AAA");
},
});
}
UPDATE 2:
So digging deep, the User Detail is
define(
[ 'modules/baseClass'],
function(BaseClass) {
// Create a new module
//Create Model
//Create View
UserDetails.Views.Content = Backbone.View
.extend({
template :
initialize : function() {
this.model = new UserDetails.Model();
},
events : {
},
render : function(LayOut) {
return LayOut(this).render().then(this.pageReady);
},
pageReady : function() {
},
});
UserDetails.activate = function() {
app.router.navigate('UserDetails', true);
};
UserDetails.configureRouting = function() {
app.router.route('UserDetails', 'UserDetails',
function() {
layoutmanager.setView('#content',
new UserDetails.Views.Content())
.render();
});
};
return UserDetails;
});
ADMIN:
define([
'modules/baseclass',
'modules/UserDetail'
],
function(BaseClass, UserDetails) {
UserDetail.Views.Content = UserDetail.Views.Content.extend({
render:function(){
console.log("rendering");
UserDetail.Views.Content.prototype.render();
}
});
//create admin model
//admin view
AdminView.Views.Content = Backbone.View.extend({
template: "admin-template",
events: {
},
initialize: function() {
this.model = new AdminModel.Model();
},
render: function(manage) {
return manage(this).render().then(this.pageReady);
},
pageReady: function() {
});
},
AdminView.activate = function() {
app.router.navigate('adminview', true);
};
AdminView.configureRouting = function() {
app.router.route('adminview', 'adminview', function() {
layoutmanager.setView('#content', new AdminView.Views.Content()).render();
layoutmanager.setView('#userDetials', new UserDetials.Views.Content()).render();
});
};
if(app.router && app.router.route) {
AdminView.configureRouting();
}
return AdminView;
});
Now if I have to call the render of the userdetails from admin view, the render method fails as the param is undefined.
I am not well versed with where the para in render is defined as I looked through my code and have not found anything
Either include the script tag for Userview.js before the script tag for AdminView.js, or using a module system like requirejs or browserify where you can specify the two modules as dependencies.

How do I add a custom method to a backbone model?

I've tried:
initialize: function() {
if (this.get("id") == "modelOfInterest") {
var func = function() {
//do some stuff with the model
}
_.bind(func, this)
}
}
and
initialize: function() {
if (this.get("id") == "modelOfInterest") {
var func = function() {
//do some stuff with the model
}
this.on("func", func, this);
}
}
However in both cases:
myModelInstance.func(); //object has no method func
I'd prefer not to use _.bindAll().
I've edited the code above to show that I am trying to bind func to only one model. The model is initialize when it is added to a collection : all the models fire initialize at the same time and I just want to bind func to one of them.
Any reason not to do the obvious?
Model = Backbone.Model.extend({
func: function() {
},
})
Assign func as a property of your model in your if block.
var Model = Backbone.Model.extend({
initialize:function() {
if (this.get('id') === 1) {
this.func = function() {
// your logic here
};
this.on('func',this.func,this);
}
}
});
Static methods should be declared on a second dictionary within the .extend call:
SomeModel = Backbone.Model.extend({
initialize: function(){}
},{
someStaticFunction: function(){
//do something
}
});
http://danielarandaochoa.com/backboneexamples/blog/2013/11/13/declaring-static-methods-with-backbone-js/
Try this:
SomeModel = Backbone.Model.extend({
initialize: function(){},
someFunction: function(){
//do something
}
});
And this:
var model = new SomeModel();
model.someFunction();

Controller listenTo breaks when in callback method

I have the problem that the event "form:selectedForm" is calling the method "showForm" but when sending this to my view I am getting the following error: TypeError: e[t] is not a function.
This is stated in line 128 in the backbone.js script but I have no clue what he is doing there. It looks like that he is looking for a "to" or "on" event on the collection.
What I am doing wrong here?
MyController = Backbone.Marionette.Controller.extend({
initialize: function(options) {
this.options = options;
this.urls = options.urls;
this.mainRegion = options.mainRegion;
this.view = new MyLayout();
this.mainRegion.show(this.view);
this.view.render();
this.showSelectorView(this.view.formHeader);
},
showSelectorView : function(view) {
var forms = new MyForms();
forms = this.urls.loadForms;
var selectorView = new FormSelectorView({
collection: forms
});
forms.fetch();
this.listenTo(selectorView, "form:selectedForm", this.showForm);
view.show(selectorView);
},
showForm : function(models) {
console.log("showForm");
var form = new FormContentView({
collection: models
});
this.view.form.show(form);
}
});
MyLayout = Backbone.Marionette.Layout.extend({
template: Backbone.Marionette.TemplateCache.get('#content'),
regions: {
formHeader: "#selector",
form: "#formContent",
formContent: "#content",
formFooter: "#save",
formTemplates: "#templates"
}
});
FormSelectorView = Backbone.Marionette.ItemView.extend({
template: Backbone.Marionette.TemplateCache.get('form-selector-template'),
events : {
"click option" : "selectForm"
},
initialize : function() {
this.listenTo(this.collection, "sync", this.render, this);
},
selectForm : function(e) {
e.preventDefault();
var id = $(e.currentTarget).attr("name");
var item = this.collection.get(id);
this.trigger("form:selectedForm", item.attributes.fields);
}
});
I think the error is in your showSelector view function, you are overwriting your forms collection in the second line,
i think your intention in that line was to assing the url of the forms collection so my guess is that this will fix it:
showSelectorView : function(view) {
var forms = new MyForms();
forms.url = this.urls.loadForms; /// Im assuming you were trying to pass the url here
var selectorView = new FormSelectorView({
collection: forms
});
forms.fetch();
this.listenTo(selectorView, "form:selectedForm", this.showForm);
view.show(selectorView);
},

Backbone inheritance, merging the render() function

So I have this current situation:
app.Ui.ModalView = Backbone.View.extend({
events: {
},
initialize: function() {
},
render: function() {
var that = this;
var model = this.model.toJSON();
that.$el.html(that.template(_.extend(this.params || {}, {
model: model,
})));
return this;
}
});
and then the inherited view:
app.Views.childView = kf.Ui.ModalView.extend({
template: JST["templates/app/blah/blah-edit.html"],
events: {
},
initialize: function() {
var that = this;
this.events = _.extend({}, app.Ui.ModalView.prototype.events, this.events);
app.Ui.ModalView.prototype.initialize.apply(this, arguments);
},
render: function(){
// add extra logic in this render function, to run as well as the inherited render function?
}
});
So, I don't want to override the parent's render(), but to add extra functionality to it, how would I go about doing that?
Two ways to achieve this: Either you can add explicit support for overriding the behaviour by creating a "render hook" in the base class, or you'll have to call the overridden base method from the superclass method:
Render hook in base class:
app.Ui.ModalView = Backbone.View.extend({
render: function() {
//if this instance (superclass) defines an `onRender` method, call it
if(this.onRender) this.onRender();
//...other view code
}
}
app.Views.childView = kf.Ui.ModalView.extend({
onRender: function() {
//your custom code here
}
});
Call base class method from super class:
app.Views.childView = kf.Ui.ModalView.extend({
render: function() {
//your custom code here
//call the base class `render` method
kf.Ui.ModalView.prototype.render.apply(this, arguments);
}
});

Resources