Error Logger for RequireJS - javascript

I'm building an application using RequireJS, and I need a centralized logger. This boils down to class structure, but I'm including my example to be specific. How can a subclass call a parent's error logging method?
Here is my app script:
define(['jquery', 'js/renderer', 'js/logger'], function($, Renderer, Logger) {
var App = Class.extend({
init: function(baseUrl) {
this.log = new Logger();
this.renderer = new Renderer();
}
});
return App;
});
Here is the renderer script:
define(['jquery'], function ($) {
try() {
var Renderer = Class.extend({
init: function() {
vor = {}; // bad script
}
});
return Renderer;
} catch(e) {
[How to throw the error to App]
}
});
Is it too abstract to put the try/catch around the class definition? Should I only put it around instructions? For example:
define(['jquery'], function ($) {
var Renderer = Class.extend({
init: function() {
try() {
vor = {}; // bad script
} catch(e) {
[How to throw the error to App]
}
}
});
return Renderer;
});
One option I'm exploring is passing the app class as an argument for the renderer init, but is that bad practice? For example, updating the app script to use:
this.renderer = new Renderer(this);
Then in the renderer script I'd have this:
....
init: function(app) {
app.log.error({message:"My Error"});

Related

knockout bindings multiple viewModels in magento 2

I've been using the following code to bind viewModels in other projects, and had to modify it a bit for magento 2. Maybe I've just been looking at it too long but heres my issue.
In the following snippet I get the error Cannot set property 'categoryViewModel' of undefined although logging it out it seems like it should be defined. Any ideas out there?
demoController.js
define('js/theme',[
'jquery',
'knockout',
'category',
'product',
'domReady!'
], function ($, ko, categoryView, productView) {
'use strict';
var demoController = {
init: function(){
var self = this;
var shouter = new ko.subscribable();
categoryView.categoryViewController(shouter);
productView.productViewController(shouter);
self.masterVM = (function(){
this.categoryViewModel = new categoryView.categoryViewModel();
this.productViewModel = new productView.productViewModel();
})();
ko.applyBindings(self.masterVM);
}
};
demoController.init();
});
categoryView.js
define([
'jquery',
'knockout'
], function($, ko) {
var categoryView = {
categoryViewController: function(shouter){
//var self = this;
this.categoryViewModel = function(){
this.foo = ko.observable('category!');
console.log('category view', this.foo() );
};
}
};
return categoryView;
});

How to structure a backbone application?

I am creating a backbone application which deals with user authentication and after successfully authenticating user loads the main app.
In the main app there is a main view which deals with loading menus and basic UI for app then router navigate to a page sub view which loads content dynamically into a container, the issue here is router navigate is called before base view is rendered and base view cannot find container to append content.
here is my appinit.js
/*global App, $*/
(function(){
'use strict';
window.Application = {
Models: {},
Collections: {},
Views: {},
Routers: {},
Configs: {},
init: function () {
var token = Cookies.get("access_token");
var router = new Application.Routers.Approuter;
Backbone.history.start();
//Check token to find if user is logged in or not
if(token!=undefined){
//Load base view for app
var appFrame = new Application.Views.Appframe;
} else {
//If user is not logged in load login view
router.navigate('login', {trigger: true});
}
}
};
$(document).ready(function () {
Application.init();
});
})();
My router file
/*global App, Backbone*/
Application.Routers = Application.Routers || {};
(function () {
'use strict';
Application.Routers.Approuter = Backbone.Router.extend({
routes: {
"login": "login",
"logout": "logout",
"products": "products"
},
login: function () {
new Application.Views.Login();
ReadyLogin.init();
},
products: function () {
var productsView = new Application.Views.Product;
$('#page-content-data').html(productsView.el);
},
logout: function () {
Cookies.remove('access_token');
Application.Configs.Users.token = '';
var router = new Application.Routers.Approuter;
router.navigate('login', {trigger: true});
}
});
})();
My base view file
/*global App, Backbone, JST*/
Application.Views = Application.Views || {};
(function () {
'use strict';
Application.Views.Appframe = Backbone.View.extend({
template: JST['app/scripts/templates/appframe.ejs'],
tagName: 'div',
initialize: function () {
_.bindAll(this, 'beforeRender', 'render', 'afterRender');
var _this = this;
this.render = _.wrap(this.render, function(render) {
_this.beforeRender();
render();
_this.afterRender();
return _this;
});
this.render();
},
beforeRender: function () {
},
render: function () {
var self = this;
var userModel = new Application.Models.Users;
userModel.fetch({ headers: {'Authorization' :'Bearer '+Application.Configs.Users.token} }).done(function () {
self.$el.html(self.template(userModel.toJSON()));
});
return this
},
afterRender: function(){
$('#page-container').html(this.el);
//After appending base load products route
var router = new Application.Routers.Approuter;
router.navigate('products', {trigger: true});
}
});
})();
I have been stuck on this since 3 days, Please help.
If you are using many iife's in a global module pattern:
var router = (function() {
return Backbone.Router.extend({ /** stuff... */ });
})();
var models = (function() {
return Backbone.Model.extend({});
})();
//more stuff
(function(router, models) {
var app = { router: router, models: models };
//init inside the iife
})(router, models);
If you want to keep everything in one iife
(function(router, models) {
var app = {
router: router(),
models: models()
};
//init in here
})(
function router() {
return Backbone.Router.extend({});
},
function models() {
return {
User: Backbone.Model.extend({}),
Foobar: Backbone.Model.extend({})
};
},
//etc...
);
But, this is messy, right? That's why bundlers exist :)
From my experience building backbone apps, I put all the navigation logic inside appRouter.
Try restructuring you app by transferring the various bits that you put in window.Application into the "" route and into appRouter.initialize. It might solve your problem. For instance something like this:
var AppRouter = new (Backbone.Router.extend({
routes: {
"": 'home',
"login": 'login'
},
initialize: function(){
//everything you need to initialize you app
},
start: function(){
Backbone.history.start();
},
home: function(){
//your initial routing logic (navigate to login if token is undefined)
},
login: function(){}
}));
$(function(){
AppRouter.start();
});

How to programatically inject html to qunit-fixture

I would like to programatically inject the html to test into the qunit-fixture. I have tried with $.load but the JS dealing with the HTML gets executed before the html is loaded.
The HTML:
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="../components/bower/qunit/qunit/qunit.js"></script>
<script>
QUnit.config.autostart = false;
</script>
<script data-main="tests" src="../components/bower/requirejs/require.js"></script>
The JS manipulating the html is the module I want to test:
define(['jquery'], function($) {
'use strict';
var Foo = {
_init: function() {
this._addClass();
},
_addClass: function() {
console.log('adding class testit');
$('.foo').addClass('testit');
},
greet: function() {
return "Hello";
}
};
Foo._init();
return {
greet : Foo.greet
};
});
And my test:
define(['foo'], function(foo) {
'use strict';
var Test = {
test: function() {
module( "module Foo");
asyncTest("foo class test", function() {
expect(1);
// foo() is executed before load is done :(
$('#qunit-fixture').load('../components/app/foo/foo.html', function(data) {
ok($('.foo').hasClass('testit'), ".foo should have class 'testit'");
QUnit.start();
});
});
}
};
return {
test: Test.test
};
});
Since the loading of the content is asynchronous you'll need to tell QUnit to wait before running. Note that this is just a guess at what your test harness might look like, it will likely need to be updated for your use case.
<!-- in your test HTML document -->
<script>
QUnit.config.autostart = false;
// your code to load the HTML dynamically in the fixture
// your test definitions
require(
[ "src/yourSourceCode", "tests/theTests" ],
function() {
QUnit.start(); // this starts the main QUnit code
}
);
</script>
UPDATE
Looks like the OP is, in fact, already stopping QUnit from running immediately, the problem (see the comment below) is that the module code runs before the HTML is loaded dynamically. I think this is because the _init() method is called inside the module. Instead, return the _init method as a property of the module and call it from the test:
define(['jquery'], function($) {
'use strict';
var Foo = {
_init: function() {
...
},
...
};
// Foo._init(); // don't do this here...
return {
greet : Foo.greet,
init : Foo._init // add the init method to your exports
};
});
Now, in your test you can do:
define(['foo'], function(foo) {
'use strict';
var Test = {
test: function() {
module( "module Foo");
asyncTest("foo class test", function() {
expect(1);
$('#qunit-fixture').load('../components/app/foo/foo.html', function(data) {
foo.init(); // we've moved the initialization to here...
ok($('.foo').hasClass('testit'), ".foo should have class 'testit'");
QUnit.start();
});
});
}
};
return {
test: Test.test
};
});

Extendable global config for an Angular module

I'm looking to include global options for an Angular directive/module.
I could use a .constant() (or simply an object of configs) in my module file, but since the module is designed for other people to include in their projects, and is installable via Bower, I don't like the idea of global options being blown away when the module gets an update. I'm aware that the .constant() could be included in another file, but then the user has to include it - rather I'd prefer that the module included everything (default values), and then the user could extend/modify if required.
I'm envisioning a similar approach as to a jQuery plugin pattern such as:
$('.myElement').myPlugin({
option1: '',
option2: ''
});
The Plugin
(function($) {
$.myPlugin = function( element, conf ) {
var $element = $(element);
var defaults = {
option1: '',
option2: '',
};
var config = $.extend( defaults, conf );
//...
};
$.fn.myPlugin = function(config) {
return this.each(function() {
if (undefined == $(this).data('myPlugin')) {
var plugin = new $.myPlugin(this, config);
$(this).data('myPlugin', plugin);
}
});
};
})(jQuery);
Your App and Configuring the Module
This is where we can define a configuration block and inject a provider. From this we can set our config options.
var myApp = angular.module( 'myApp', ['myModule'] )
myApp.config( function( myDirectiveConfigProvider ) {
myDirectiveConfigProvider.config = {
option1: 'A new setting'
//option2: 'A new setting'
};
// OR
myDirectiveConfigProvider.config.option1 = 'A new setting';
//myDirectiveConfigProvider.config.option2 = 'A new setting';
});
The Module
Within the module we can define a service to hold our default config options. This could also simply be included in the directive if you don't wish to inject it var config = {}.
We also define a Provider which will be injected into our configuration block.
Within the directive we simply need to extend config (injected service or var) with the providers.
angular.module( 'myModule', [] )
.value( 'config', {
'option1': 'my default setting',
'option2': 'my default setting'
})
.directive( 'myDirective', [ 'config', 'myDirectiveConfig', function( config, myDirectiveConfig ) {
return {
link: function( scope, element, attrs ) {
angular.extend( config, myDirectiveConfig.config );
console.log( config.option1 ); //'A new setting'
console.log( config.option2 ); //'my default setting'
}
}
}])
.provider( 'myDirectiveConfig', function() {
var self = this;
this.config = {};
this.$get = function() {
var extend = {};
extend.config = self.config;
return extend;
};
return this;
});

Backbone Marionette and RequireJS Modules

I'm beginning a large scale javascript application with Marionette. A Marionette application has a concept of application Modules and RequireJS is also used to break code into modules,
currently I have this for the start of my application:
require([ "jquery", "underscore", "backbone", "marionette" ],
function ($, _, Backbone, Marionette) {
$(function() {
App = new Marionette.Application();
App.addInitializer(function(options) {
App.addRegions({
mainArea: "#mainArea"
});
});
App.on("start", function() {
// done starting up, do stuff here
});
App.start();
});
});
If I wanted to add a view would I do something like the following in a file?
require([ "jquery", "underscore", "backbone", "marionette" ],
function($, _, Backbone, Marionette) {
App.module("FirstView", function(FirstView, App, Backbone, Marionette, $, _) {
return Marionette.ItemView.extend({
//define view stuff in here
});
});
});
I'm not sure how I'd get this code to actually run, any help is much appreciated
Marionette's modules are meant to be a simple alternative to RequireJS (and other) module formats. I would not recommend using them together, as noted in the wiki:
https://github.com/marionettejs/backbone.marionette/wiki/AMD-Modules-vs-Marionette's-Modules
IMHO I like to differ from the view point stated above "Marionette's modules are meant to be a simple alternative to RequireJS (and other) module formats."
I like to draw a comparison between Require.js modules and Marionette.js modules with C#'s assembly and namespace concepts. Marionette.js's modules help us group definitions of various building blocks based on functionality, while Require.js could be used to load / inject dependencies.
Again, this is my view / understanding (based on discussions with David Sulc on his book 'Structuring Backbone Code with RequireJS and Marionette Modules'), which has helped in my implementation. In a way we can use Marionette.js and Require.js together as described below.
The example below is a small Library Manager app (sample) which could be found online # https://github.com/srihari-sridharan/LibraryManagement. The code below (omitting insignificant bits and pieces) creates the application object and renders the list of books after initialization. Please find it here - https://github.com/srihari-sridharan/LibraryManagement/blob/master/app/js/app.js
define([
'marionette',
'modules/config/marionette/regions/dialog'], function (Marionette) {
// Create the application object
var LibraryManager = new Marionette.Application();
// Add regions to the application object
LibraryManager.addRegions({
//Header
headerRegion: "#header-region",
//Main
mainRegion: "#main-region",
//Footer
footerRegion: "footer-region",
//Overlay Dialog
dialogRegion: Marionette.Region.Dialog.extend({
el:"#dialog-region"
})
});
// Subscribe to Initialize After event.
LibraryManager.on('initialize:after', function() {
if(Backbone.history){
require(['modules/books/booksModule', 'modules/about/aboutModule'], function (){
Backbone.history.start();
if(LibraryManager.getCurrentRoute() === ''){
LibraryManager.trigger("books:list");
}
});
}
});
// Return the application object.
return LibraryManager;
});
Next we define the module / sub-modules based on the functionality. This will also have a module specific router and will wire controllers and handle routes. Note the require call to controllers. This code is present in https://github.com/srihari-sridharan/LibraryManagement/blob/master/app/js/modules/books/booksModule.js
define(['app'], function (LibraryManager) {
// Define a new module for Books - BooksModule
LibraryManager.module('BooksModule', function (BooksModule, LibraryManager, Backbone, Marionette, $, _) {
BooksModule.startWithParent = false;
BooksModule.onStart = function () {
console.log('Starting BooksModule.');
};
BooksModule.onStop = function () {
console.log('Stopping BooksModule.');
};
});
// Define a new module for a Router specific to BooksModule
LibraryManager.module('Routers.BooksModule', function (BooksModuleRouter, LibraryManager, Backbone, Marionette, $, _) {
BooksModuleRouter.Router = Marionette.AppRouter.extend({
appRoutes: {
'books': 'listBooks',
'books(?filter:=criterion)': 'listBooks',
'books/:id': 'showBook',
'books/:id/edit': 'editBook'
}
});
var executeAction = function (action, arg) {
LibraryManager.startSubModule('BooksModule');
action(arg);
LibraryManager.execute('set:active:header', 'books');
};
var API = {
// This is where we are using / referring to our controller
listBooks: function (criterion) {
require(['modules/books/list/listController'], function (ListController) {
executeAction(ListController.listBooks, criterion);
});
},
showBook: function (id) {
require(['modules/books/show/showController'], function (ShowController){
executeAction(ShowController.showBook, id);
});
},
editBook: function (id) {
require(['modules/books/edit/editController'], function (EditController) {
executeAction(EditController.editBook, id);
});
}
};
// Navigating routes.
LibraryManager.on('books:list', function () {
LibraryManager.navigate('books');
API.listBooks();
});
LibraryManager.on('books:filter', function(criterion) {
if(criterion){
LibraryManager.navigate('books?filter=' + criterion);
}
else{
LibraryManager.navigate('books');
}
});
LibraryManager.on('book:show', function (id) {
LibraryManager.navigate('books/' + id);
API.showBook(id);
});
LibraryManager.on("book:edit", function(id){
LibraryManager.navigate('books/' + id + '/edit');
API.editBook(id);
});
LibraryManager.addInitializer(function () {
new BooksModuleRouter.Router({
controller: API
});
});
});
return LibraryManager.BooksModuleRouter;
});
Finally we have the definitions for our views, models and controllers. These definitions will be tied to module / sub module objects.
The view code is shown below. Look at the .extend() methods. They are assigned to variables attached to the BooksModule.List.View sub module. https://github.com/srihari-sridharan/LibraryManagement/blob/master/app/js/modules/books/list/listView.js
define(['app',
'tpl!modules/books/list/templates/layout.html',
'tpl!modules/books/list/templates/panel.html',
'tpl!modules/books/list/templates/none.html',
'tpl!modules/books/list/templates/list.html',
'tpl!modules/books/list/templates/listItem.html'],
function (LibraryManager, layoutTemplate, panelTemplate, noneTemplate, listTemplate, listItemTemplate) {
LibraryManager.module('BooksModule.List.View', function(View, LibraryManager, Backbone, Marionette, $, _) {
View.Layout = Marionette.Layout.extend({
template: layoutTemplate,
regions:{
panelRegion: '#panel-region',
booksRegion: '#books-region'
}
});
View.Panel = Marionette.ItemView.extend({
// More code here!
});
View.Book = Marionette.ItemView.extend({
// More code here!
});
var NoBooksView = Marionette.ItemView.extend({
template: noneTemplate,
tagName: "tr",
className: "alert"
});
View.Books = Marionette.CompositeView.extend({
// More code here!
});
});
return LibraryManager.BooksModule.List.View; // Return the definition.
});
The controller code is shown below. This gets called from the code in booksModule.js. The controller definition is attached to BooksModule.List sub module.
define(['app', 'modules/books/list/listView'], function (LibraryManager, View) {
LibraryManager.module('BooksModule.List', function (List, LibraryManager, Backbone, Marionette, $, _) {
List.Controller = {
listBooks: function (criterion) {
require(['common/views', 'entities/book'], function (CommonViews) {
var loadingView = new CommonViews.Loading();
LibraryManager.mainRegion.show(loadingView);
var fetchingBooks = LibraryManager.request('book:entities');
var booksListLayout = new View.Layout();
var booksListPanel = new View.Panel();
require(['entities/common'], function (FilteredCollection) {
$.when(fetchingBooks).done(function (books) {
// More code here!
});
if(criterion){
filteredBooks.filter(criterion);
booksListPanel.once('show', function () {
booksListPanel.triggerMethod("set:filter:criterion", criterion);
});
}
var booksListView = new View.Books({
collection: filteredBooks
});
booksListPanel.on('books:filter', function (filterCriterion) {
filteredBooks.filter(filterCriterion);
LibraryManager.trigger("books:filter", filterCriterion);
});
booksListLayout.on("show", function(){
booksListLayout.panelRegion.show(booksListPanel);
booksListLayout.booksRegion.show(booksListView);
});
booksListPanel.on('book:new', function () {
require(["modules/books/new/newView"], function (NewView) {
// More code here!
});
LibraryManager.dialogRegion.show(view);
});
});
booksListView.on('itemview:book:show', function (childView, model) {
LibraryManager.trigger("book:show", model.get('id'));
});
booksListView.on('itemview:book:edit', function(childView, model) {
require(['modules/books/edit/editView'], function (EditView) {
// More code here!
LibraryManager.dialogRegion.show(view);
});
});
booksListView.on("itemview:book:delete", function (childView, model) {
model.destroy();
});
LibraryManager.mainRegion.show(booksListLayout);
});
});
});
}
}
});
return LibraryManager.BooksModule.List.Controller; // Return the definition.
});
Thus require.js modules and marionette modules can coexist. The following are the advantages.
Much cleaner organization of source code and clearer separation of concerns.
Module start and stop methods provide provision to initialize and cleanup objects.
When you model functionalities and sub-functionalities as modules and sub modules, we have more granular control over what resides in memory and what should not.
Also, module definition can be split across multiple files.
Please post your thoughts. Thanks for reading.
PS: Based on the above view point, please find the changes to your example below:
require([ "jquery", "underscore", "backbone", "marionette" ],
function($, _, Backbone, Marionette) {
App.module("FirstView", function(FirstView, App, Backbone, Marionette, $, _) {
FirstView.View = Marionette.ItemView.extend({
//define view stuff in here
});
return FirstView.View;
});
});

Categories