Override method of an existing instance - javascript

I'm trying to extend an existing Durandal router plugin instance already created with the help of RequireJS.
The method map() should be overriden to add extra mapping parameters.
How should I access the original method from the modified one?
index.js
define([ 'durandal/app', 'app/childRouter'], function(
app, childRouter) {
childRouter.map([ {
route : 'details/:id',
moduleId : 'details/index',
}, {
route : 'details/tabs/base',
moduleId : 'details/tabs/base',
} ]);
childRouter.buildNavigationModel();
return {
router : childRouter
};
});
childRouter.js
define([ 'durandal/app', 'plugins/router'], function(
app, router) {
var childRouter = router.createChildRouter();
childRouter._map = childRouter.map;
childRouter.map = function(data) {
data.unshift({
route : [ '', 'grid' ],
moduleId : 'grid/index',
});
childRouter._map(data);//CALLS THE OVERRIDEN METHOD AGAIN INSTEAD OF ORIGINAL
};
return childRouter;
});

If you want to still call the original "map" function, you'll need to store it before you overwrite it. It's a bit "hacky" to replace functions in this way, but if you REALLY had to do it, this will work:
var childRouter = router.createChildRouter();
childRouter.originalMap = childRouter.map; // Save original
childRouter.map = function(data) {
data.unshift({
route : [ '', 'grid' ],
moduleId : 'grid/index',
});
childRouter.originalMap(data); // Call original
};

Related

How to override testClass methods with Siesta?

I'm using Bryntum Siesta for UI testing an ExtJS app. I've created a TestClass and aim to use its methods for different views. Whole actions of test are same only some specific things are changing such as package, view, grid names. Here is some snippets from Test Suite:
Main Test Class
var isDisplaying = 'Grid is displaying now.';
var collapseDesc = 'Collapse Navbar';
Class('Siesta.Test.ListScreen', {
isa : Siesta.Test.ExtJS,
methods: {
navigation: function (callback) {
var t = this;
t.chain(
{waitForCQ: 'treelist[itemId=navigationTreeList]'},
function (next) {
t.click('treelist[itemId=navigationTreeList]');
next();
},
{click: '>> treelistitem[_text=Package_Name]'},
{click: '>> treelistitem[_text=Package_Submodule]', desc: 'Package Submodule'+isDisplaying},
{click: '#main-navigation-btn => .fa-navicon', desc: collapseDesc},
function (next) {
console.log('navigation func log');
next();
},
callback
)
}
}
});
And this testClass calling from Package_Submodule and getting success:
describe('UI Testing: Submodule List Screen', function (t) {
//Extended method for navigation to submodule
t.it('Should open: Submodule Grid', function (t) {
t.chain(
{
navigation: t.next
}
)
});
});
The thing here is I want to call same TestClass method for another Submodule and override several things such as Package_Name and Package_Submodule. How can i be success to do this?
Thanks in advance
UPDATE through JackSamura's answer:
Dear #SamuraiJack I've refactored the Main Class (ListScreen) and inserted has attribute. As well modified the harness with config property but unfortunately it did not override myPackageName or mySubModule. Instead of i got this error:
Waiting for element ">> treelistitem[_text=packageName]" to appear
As well I've tried to use function arguments but it did not work too. Could you please give an idea why I couldn't override new values?
Main Class (Updated):
var isDisplaying = 'Grid is displaying now.';
var collapseDesc = 'Collapse Navbar';
Class('Siesta.Test.ListScreen', {
isa : Siesta.Test.ExtJS,
has : {
myPackageName : 'packageName',
mySubModule : 'subModule'
},
methods: {
navigation: function (callback) {
var t = this;
t.chain(
{waitForCQ: 'treelist[itemId=navigationTreeList]'},
function (next) {
t.click('treelist[itemId=navigationTreeList]');
next();
},
{click: '>> treelistitem[_text='+this.myPackageName+']'},
{click: '>> treelistitem[_text='+this.mySubModule+']', desc: this.mySubModule+isDisplaying},
{click: '#main-navigation-btn => .fa-navicon', desc: collapseDesc},
function (next) {
console.log('navigation func log');
next();
},
callback
)
}
}
});
index.js:
group: 'UI Tests',
items: [
{
group: 'Submodule List Screen',
testClass: Siesta.Test.ListScreen,
items: [
{
title : 'Submodule1',
hostPageUrl : localApp,
url : '02-ui-tests/02_01-submodule-list-screen/submodule1-list.t.js',
config : {
myPackageName : 'Package1',
mySubModule : 'Submodule1'
}
},
You can do it in 2 ways:
1) Add arguments to the "navigation" method:
// callback should be the last one
navigation: function (packageName, packageSubModule, callback) {
Probably self-explanatory
2) A bit more complex - add new attributes to your custom test class:
Class('Siesta.Test.ListScreen', {
isa : Siesta.Test.ExtJS,
has : {
// values goes into prototype, like in Ext
myPackageName : 'packageName',
mySubModule : 'subModule'
},
methods: {
Then you can refer to those attributes in "navigation" method as usual: this.myPackageName
Then, to override, you can either create a new test class (subclassing Siesta.Test.ListScreen) and re-define the attributes in it, or alternatively, use the config property of the test descriptor:
harness.start(
{
url : 'mytest.t.js',
config : {
myPackageName : 'value1',
mySubModule : 'value2'
}
},
...
)
Hint: To get the answer faster - post it to the Siesta forum: https://www.bryntum.com/forum/viewforum.php?f=20
UPDATE:
The errors you got are probably because "navigation" method is launched from the sub test (every "t.it()" or "t.describe()" section creates a separate "subtest"). Those sub tests won't have the config applied - it is applied only to the top-level test. One solution would be to copy the attribute values:
// in the "methods" of the custom test class
processSubTestConfig : function (config) {
var cfg = this.SUPER(config)
cfg.myPackage = this.myPackage
...
return cfg
},
But that is already advanced Siesta internals coding. Probably just using function arguments will be simpler..

Backbone routes doesn't work with Laravel

I dont't know exactly where the error/s is/are.
I'm doing a Single Page App, this is the context:
I have a resource controller in Laravel that watch this route "domain.dev/v1/"
Laravel serves the first page/view "/public/views/layouts/application.blade.php"
Mustache views are stored under "public/views/" and they are loaded synchronously when they are called by the Backbone Router (I've modified the "app/config/view.php" file to serve the views from bakcbone)
In backbone, the Router controls every URI change, even pushstate and the respective Mustache views. Everything seems to work fine, but if you type the direct URI for a user o list or users...you only see JSON returned by the server, not the corresponding Backbone View, in other words, I dont know Which is not doing the correct work, the Laravel Router or the Backbone Router. Or is it a Laravel configuration?
This is my code so far:
// app/routes.php
Route::group(['prefix' => 'v1'],function (){
Route::resource('users','V1\UsersController');
Route::get('/', function()
{
return View::make('layouts.application')->nest('content', 'app');
});
});
// app/controllers/V1/UsersController.php
namespace V1;
//import classes that are not in this new namespace
use BaseController;
use User;
use View;
use Input;
use Request;
class UsersController extends \BaseController {
public function index()
{
return $users = User::all(['id','email','name']) ;
}
public function show($id)
{
$user = User::find($id,['id','email','name']);
if(is_null($user)){
return array(
'id' => 0,
'email' => 'fake email'
);
}
return $user;
}
// public/js/namespaces.js
(function(){
window.App = {
Models : {},
Collections : {},
Views : {},
Router : {}
};
window.Templator = function (mustacheView){
return $.ajax({
url: '/views/'+mustacheView,
async : false,
type: 'GET',
}).responseText;
};
window.Vent = _.extend({},Backbone.Events);
})();
// public/js/backbone/router.js
App.Router = Backbone.Router.extend({
routes : {
'' : 'home',
'users' : 'showAll',
'users/:id' : 'showUser',
'login' : 'showLoginForm'
},
home: function (){
Vent.trigger('home:index');
},
showAll : function (){
Vent.trigger('users:showAll');
},
showUser: function (id){
Vent.trigger('users:show',id);
},
showLoginForm : function (){
Vent.trigger('login:form');
}
});
// public/js/app.js
$(function() {
$(document).on("click", "a", function(e){
var href = $(this).attr("href");
if (href != '#') {
e.preventDefault();
Backbone.history.navigate(href,{trigger:true});
}
});
new App.Views.AllUsers;
new App.Views.Index;
new App.Views.Login;
new App.Router;
Backbone.history.start({
pushState: true,
silent: true,
root: '/v1'
});
});
So, if I type this URI "domain.dev/v1/users" on the nav bar, shows list of users in JSON and the view associated in backbone is not displayed.
Any suggestions?
Have a look at my answer I just gave in a similar question about Angular: Angular and Laravel
In the text, just mentally replace Angular with Backbone.
How to pass the route from Laravel to Backbone:
The base view, that is returned from Laravel for the '/' route needs to have something like this somewhere in its <head> before backbone.js is included:
<script>
var myRoute = "{{ $route }}";
</script>
This blade template creates a Javascript variable. Then, after you declared your routes in Backbone, add this:
Backbone.history.start(); // You should already have this line, so just add the next one
App.nav.navigate(myRoute);
This should do the trick.

pass parameters to backbone fetch url to deal with a non-standard api

I am trying to manipulate backbone's fetch method to to deal with a bit of a non-standard api. The way the api works is as follows:
api/products/[page]?param1=val&param2=val
ex:
api/products/2?budget=low&categories=all
would be equivalent to getting the second page of results for which the budget is low and all categories are included.
I can pass the parameters after the query string just fine through the format:
self.productsItemsCollection.fetch({ success : onDataHandler, dataType: "json", data: { budget: 'low', categories: 'all' } });
but I'm not sure what to do about the pagination, since it comes before the ? question mark.
Here is how the collection is set up:
define([
'underscore',
'backbone',
'models/products/ProductsItemsModel'
], function(_, Backbone, ProductsItemsModel){
var ProductsItemsCollection = Backbone.Collection.extend({
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function() {
return '/api/products/'; //MH - need to pass page number to be appended to this url
},
parse : function(data) {
debugger;
return data.items;
}
});
return ProductsItemsCollection;
});
How do I include the pagination in backbone's fetch command given this api URL structure?
You're on the right track in that Backbone can use the return value of a function as its 'url' value. What I personally would do, is set a page property on the collection (referenced through something like this.page), and include that in the output of the url function.
initialize: function() {
this.page = 1; // Or whatever the default should be
},
url: function() {
return '/api/products/ + this.page;
}
The problem then becomes updating the page property, which can be as simple as 'ProductsItemsCollection.page = 2;'. Personally, I would also add a second fetch method to wrap the page update and fetch into a single method call.
fetch2: function(page, options) {
if (page) {
this.page = page;
}
return this.fetch(options);
}
Just few notes to your code. I think you don't need to define page number into your Collection. According to MVC pattern it's more suitable for Controller. Collection just should get parameter and return some data according to it. Meanwhile Backbone doesn't provide classic MVC Controller, but you can use for this purpose Backbone.View. So structure of your application could looks something like this:
// Collection
define([
'backbone',
'models/products/ProductsItemsModel'
], function(Backbone, ProductsItemsModel){
return Backbone.Collection.extend({
// I don't know what exactly your Model does, but if you don't override Backbone.Model with your own methods you don't really need to define it into your collection.
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function(page) {
return '/api/products/' + page;
},
parse : function(data) {
return data.items;
}
});
});
And then in your View you can fetch needed page and render it:
define([
'jquery',
'backbone',
'ProductsItemsCollection'
], function($, Backbone, ProductsItemsCollection){
return Backbone.View.extend({
events: {
// Your logic to get page number from your pagination.
'click .pagination': 'getPageNumber'
}
collection: new ProductsItemsCollection(),
initialize : function() {
this.listenTo(this.collection, 'reset', this.render);
// initial loading collection
this.load(1); // load page #1
},
render: function () {
// your render code
}
// Example function to show how you could get page number.
getPageNumber: function(e) {
var pageNumber = $(e.currentTarget).data('pageNumber');
load(pageNumber);
},
load: function(page) {
url: this.collection.url(page),
data: {
budget: 'low',
categories: 'all'
}
}
});
});
Something like that. So in this View you just make initialization of your Collection and make initial loading. Then all you should make is passing page number to your load function.
I read these answers, i guess they make sense but this is what i went with. just really simple:
app.WorkOrder = Backbone.Collection.extend({
model: app.WorkOrderDetail,
urlRoot: '/m2/api/w/',
getWorkOrder: function(workorder_id, options) {
this.url = this.urlRoot + workorder_id;
return this.fetch(options);
}
});
Then in the view i do this:
app.AppView = Backbone.View.extend({
el: '#workorderapp',
initialize: function () {
app.workOrder.getWorkOrder(workorder_id, {
success:function(data) {
//...do something with data
}
});
},
});

Backbone.js and local storage . A "url" property or function must be specified

I'm improving my knowledge about Backbone.js and have this code sample taken from a tutorial. (http://bardevblog.wordpress.com/2012/01/16/understanding-backbone-js-simple-example/)
This example will not access the server for now, so to simulate the retrieval of data from the server I have a file name movies.json.
What I am trying to do:
Add json data in local storage (using localStorage adapter)
For this I am using Backbone.ajaxSync, Which Is Given to the alias Backbone.sync by the localStorage adapter: I created the method refreshFromServer () to do this
The reason for doing this is that I'm trying to implement a way to get data only one time (and only refresh when i need to)
My issues:
  I'm having an error "Uncaught Error: 'url' property or function must be specified" when I call refreshFromServer ().
I do not understand why because I set the url collection. (url : "scripts/data/movies.json" )
Sample code
var Theater = {
Models : {},
Collections : {},
Views : {},
Templates : {}
}
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model : Theater.Models.Movie,
localStorage : new Backbone.LocalStorage("MovieStore"), // Unique name within your app.
url : "scripts/data/movies.json",
refreshFromServer : function() {
return Backbone.ajaxSync.apply(this, arguments);
},
initialize : function() {
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movies").html())
Theater.Views.Movies = Backbone.View.extend({
el : $("#mainContainer"),
template : Theater.Templates.movies,
initialize : function() {
this.collection.bind("reset", this.render, this);
},
render : function() {
console.log("render")
console.log(this.collection.length);
}
})
Theater.Router = Backbone.Router.extend({
routes : {
"" : "defaultRoute"
},
defaultRoute : function() {
console.log("defaultRoute");
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({
collection : Theater.movies
});
Theater.movies.refreshFromServer();
//Theater.movies.fetch();
console.log(Theater.movies.length)
}
})
var appRouter = new Theater.Router();
Backbone.history.start();
Notes:
If a comment localStorage property in the collection
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model : Theater.Models.Movie,
//localStorage : new Backbone.LocalStorage("MovieStore")
...
});
and then in router call normal fetch method
Theater.Router = Backbone.Router.extend({
routes : {
"" : "defaultRoute"
},
defaultRoute : function() {
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({
collection : Theater.movies
});
//Theater.movies.refreshFromServer();
Theater.movies.fetch();
}
})
I can see the json list correctly in my view
If I use the localStorage property in the collection and then call the standard fetch () method, I see only an empty list (I think it is normal as it is read from the local storage and is empty)
The error only occurs when using the method refreshFromServer () witch use Backbone.ajaxSync (alias for backbone.sync)
Err... my bad. The refreshFromServer implementation is from my answer to your earlier question., and it's completely, uselessly wrong.
Backbone.sync expects arguments (method, model, options), but as it stands, it doesn't get what it needs from refreshFromServer because the refresh method simply sends forward whatever arguments it gets. Sorry for the mistake.
The correct, working implementation would be:
refreshFromServer : function(options) {
return Backbone.ajaxSync('read', this, options);
}
It can be used either via success / error callbacks passed to the options hash:
this.collection.refreshFromServer({ success: function() { /* refreshed... */ });
Or via the jqXHR Promise API:
this.collection.refreshFromServer().done(function() { /* refreshed... */ })
Or not signing up for callbacks and waiting for the collection reset event like in your example:
this.collection.bind("reset", this.render, this);
this.collection.refreshFromServer();
This should work. Please let me know if it doesn't. I fixed my answer in the previous question too, in case someone stumbles onto it.
Edit: To save the data to local storage after refreshing you need to manually save each of the models:
var collection = this.collection;
collection.refreshFromServer({success: function(freshData) {
collection.reset(freshData);
collection.each(function(model) {
model.save();
});
}});

backbone.js, handlebars error : this._input.match is not a function

I'm new to backbone.js and handlebars and I'm having a problem getting my template to render out the data.
Here is my collection and model data from tagfeed.js module:
// Create a new module.
var Tagfeed = app.module();
// Default model.
Tagfeed.Model = Backbone.Model.extend({
defaults : {
name : '',
image : ''
}
});
// Default collection.
Tagfeed.Collection = Backbone.Collection.extend({
model : Tagfeed.Model,
url : Api_get('api/call')
});
Tagfeed.TagView = Backbone.LayoutView.extend({
template: "tagfeed/feed",
initialize: function() {
this.model.bind("change", this.render, this);
},
render: function(template, context) {
return Handlebars.compile(template)(context);
}
});
Then in my router I have:
define([
// Application.
"app",
// Attach some modules
"modules/tagfeed"
],
function(app, Tagfeed) {
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"index.html": "index"
},
index: function() {
var collection = new Tagfeed.Collection();
app.useLayout('main', {
views: {
".feed": new Tagfeed.TagView({
collection: collection,
model: Tagfeed.Model,
render: function(template, context) {
return Handlebars.compile(template)(context);
}
})
}
});
}
});
return Router;
});
THis successfully makes a call to the api, makes a call to get my main template, and makes the call to get the feed template HTML. If I don't include that render(template, context) function, then it renders on the page as the straight up HTML that I have in the feed template with the {{ name }} still included. however when its included, I get the error
TypeError: this._input.match is not a function
[Break On This Error]
match = this._input.match(this.rules[rules[i]]);
and if I examine the variables that get passed into the appLayout views render function for feed, I see that the template var is a function, and the context var is undefined, then it throws that error.
Any ideas what I'm doing wrong? I know I have at least one problem here, probably more.
Since you're using requirejs, you can use the text module to externalise your templates or better still pre-compile them and include them in your view. Check out http://berzniz.com/post/24743062344/handling-handlebars-js-like-a-pro
E.g. using pre-compiled templates
// router.js
define(['views/tag_feed', 'templates/feed'], function(TagFeedView) {
var AppRouter = Backbone.Router.extend({
// ...
});
})
// tag_feed.js
define(['collections/tag_feed'], function() {
return Backbone.View.extend({
// ...
render: function() {
this.$el.html(
Handlebars.templates.feed({
name: '...'
})
);
}
});
})
For reference I've created simple boilerplate for a backbone/require/handlebars setup https://github.com/nec286/backbone-requirejs-handlebars

Categories

Resources