Getting Backbone.ChildViewContainer is not a constructor using browserify & es6 - backbone.js

I am trying to set up a marionette project using browserify and es6. When creating a CollectionView I am getting the error Uncaught TypeError: Backbone.ChildViewContainer is not a constructor.
Am I missing loading something? Can't seem to find anything about this on the internet.
Here is my collection view:
import {ItemView, CollectionView} from 'backbone.marionette';
import navTemplate from '../templates/navigation.hbs';
import navItemTemplate from '../templates/_navItem.hbs';
var NavigationItem = ItemView.extend({
template: navItemTemplate
});
var NavigationView = CollectionView.extend({
template: navTemplate,
childView: NavigationItem,
childViewContainer: '.left-navigation',
});
export default NavigationView;
and my layout that is creating it
import {LayoutView} from 'backbone.marionette';
import layoutTemplate from './templates/layout.hbs';
import NavigationView from './Views/navigation';
export default class AppLayout extends LayoutView {
constructor(options) {
super(options);
this.template = layoutTemplate;
}
regions() {
return {
'navigation': '.left-aside'
};
}
onRender() {
console.log(this.getRegion('navigation'));
this.getRegion('navigation').show(new NavigationView());
}
}
I am also using a shim to use backbone.radio but that shouldn't impact this:
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['backbone.marionette', 'backbone.radio', 'underscore'], factory);
} else if (typeof exports !== 'undefined') {
module.exports = factory(require('backbone.marionette'), require('backbone.radio'), require('underscore'));
} else {
factory(root.Backbone.Marionette, root.Backbone.Radio, root._);
}
}(this, function(Marionette, Radio, _) {
'use strict';
Marionette.Application.prototype._initChannel = function () {
this.channelName = _.result(this, 'channelName') || 'global';
this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);
};
}));
Edit: I have found that my compiled file has many copies of backbone so that might be the problem...

I came across this problem with Webpack. I was able to work around it by specifying an alias in webpack.config.js
resolve: {
extensions: ['', '.js', '.ts'],
alias: {
'backbone': 'backbone.marionette/node_modules/backbone'
}
},
i.e. making sure all references to backbone use the one installed as a dependency of Marionette.
I've never used Browserify, but perhaps you can do something similar by installing aliasify and adding this to your package.json:
{
"aliasify": {
"aliases": {
"backbone": "backbone.marionette/node_modules/backbone"
}
}

Was getting this same error this morning when using an older JSPM config file pointing to Marionette 2.4.1 and Backbone 1.2.1, and Google linked me here... Updating to the more recent Marionette 2.4.5 and Backbone 1.3.2 releases seemed to fix things for me. What versions are you running?

Related

React inside LWC

I know there is several posts on that, but they does not answer the below question.
I have built a small POC to integrate react into LWC, I have used lwc-react-webpack-demo as a start base, and was able to create the React todo list example inside an LWC.
I wanted to expand my POC to something a little bit bigger like the following template, I was able to bundle it with webpack but when loading it into the LWC I am getting the following error:
Cannot read property 'importScripts' of undefined [Cannot read
property 'importScripts' of undefined]
I have debugged the issue and find out it fails here:
if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
Since "g" is undefined:
/******/ /* webpack/runtime/global */
/******/ !function() {
/******/ __webpack_require__.g = (function() {
/******/ if (typeof globalThis === 'object') return globalThis;
/******/ try {
/******/ return this || new Function('return this')();
/******/ } catch (e) {
/******/ if (typeof window === 'object') return window;
/******/ }
/******/ })();
/******/ }();
I assume it's because of the shadow DOM? or/and the fact that webpack is generating several files (not like the small example where I have only one bundle js file) and it's trying to import them?
Any ideas on how to solve it? or maybe the best approach here is to go with the Visualforce approach (lightning:container might work as well, but I don't like the iframe approach)?
LWC:
import { LightningElement } from "lwc";
import { loadScript } from "lightning/platformResourceLoader";
import REACT_TEMPLATE from "#salesforce/resourceUrl/reacttemplate";
export default class ReactTemplateContainer extends LightningElement {
connectedCallback() {
loadScript(this, REACT_TEMPLATE)
.then(() => {
mount(this.template.querySelector("div"), {});
})
.catch((error) => {
console.error("LOAD SCRIPT ERROR::", error);
});
}
}

NgUpgrade: Unable to use templateUrl when upgrading Angular1 components

I want to upgrade a ng1 component to be used inside an ng2 component.
If I use just a template string the ng1 component, to be upgraded, it works. However, if I switch to using a templateUrl instead, the app crashes and give me this error:
angular.js:13920 Error: loading directive templates asynchronously is not supported
at RemoteUrlComponent.UpgradeComponent.compileTemplate (upgrade-static.umd.js:720)
at RemoteUrlComponent.UpgradeComponent (upgrade-static.umd.js:521)
at new RemoteUrlComponent (remote-url.component.ts:11)
at new Wrapper_RemoteUrlComponent (wrapper.ngfactory.js:7)
at View_AppComponent1.createInternal (component.ngfactory.js:73)
at View_AppComponent1.AppView.create (core.umd.js:12262)
at TemplateRef_.createEmbeddedView (core.umd.js:9320)
at ViewContainerRef_.createEmbeddedView (core.umd.js:9552)
at eval (common.umd.js:1670)
at DefaultIterableDiffer.forEachOperation (core.umd.js:4653)
Here is a plunk demonstrating my issue:
https://plnkr.co/edit/2fXvfc?p=info
I've followed the Angular 1 -> 2 upgrade guide and it seems that this code should work. I'm not quite sure why its not working.
After trying require with requireJS and the text plugin which did not work for me, I managed to make it work using 'ng-include' as follow:
angular.module('appName').component('nameComponent', {
template: `<ng-include src="'path_to_file/file-name.html'"></ng-include>`,
I hope this helps!
I found a quite cheap solution for the issue.
Just use template: require('./remote-url.component.html') instead of templateUrl: './remote-url.component.html' and it should work just fine!
This is really frustating because the Angular upgrade documentation specifically says it's ok to use templateUrl. Never mentions this async issue. I've found a way around it by using the $templateCache. I didn't want to change my angular 1 directive because it is used my angular 1 apps and will also be used by angular 4 apps. So I had to find a way to modify it on the fly. I used $delegate, $provider, and $templateCache. My code is below. I also use this to remove the replace attribute since it is deprecated.
function upgradeDirective(moduleName, invokedName) {
/** get the invoked directive */
angular.module(moduleName).config(config);
config.$inject = ['$provide'];
decorator.$inject = ['$delegate', '$templateCache'];
function config($provide) {
$provide.decorator(invokedName + 'Directive', decorator);
}
function decorator($delegate, $templateCache) {
/** get the directive reference */
var directive = $delegate[0];
/** remove deprecated attributes */
if (directive.hasOwnProperty('replace')){
delete directive.replace;
}
/** check for templateUrl and get template from cache */
if (directive.hasOwnProperty('templateUrl')){
/** get the template key */
var key = directive.templateUrl.substring(directive.templateUrl.indexOf('app/'));
/** remove templateUrl */
delete directive.templateUrl;
/** add template and get from cache */
directive.template = $templateCache.get(key);
}
/** return the delegate */
return $delegate;
}
}
upgradeDirective('moduleName', 'moduleDirectiveName');
Most of the answers given here involve pre-loading the template in some way so as to make it available synchronously to the directive.
If you want to avoid doing this - e.g. if you have a large AngularJS application that contains many templates, and you don't want to download them all up front - you can simply wrap your directive in a synchronously loaded version instead.
E.g., if you have a directive called myDirective, which has an asynchronously loaded templateUrl which you don't want to download up front, you can do this instead:
angular
.module('my-module')
.directive('myDirectiveWrapper', function() {
return {
restrict: 'E',
template: "<my-directive></my-directive>",
}
});
Then your Upgraded Angular directive just needs to supply 'myDirectiveWrapper' instead of 'myDirective' in it's super() call to the extended UpgradeComponent.
A pretty low-tech solution to this issue is to load your templates up in your index.html, and assign them IDs that match the templateUrls the directives are looking for, ie:
<script type="text/ng-template" id="some/file/path.html">
<div>
<p>Here's my template!</p>
</div>
</script>
Angular then automatically puts the template into the $templateCache, which is where UpgradeComponent's compileTemplate is looking for the template to begin with, so without changing the templateUrl in your directive, things will work because the id matches the templateUrl.
If you check the source code of UpgradeComponent (see below), you can see commented out code that deals with fetching the url, so it must be in the works, but for the time being this could be a viable solution and even a scriptable one.
private compileTemplate(directive: angular.IDirective): angular.ILinkFn {
if (this.directive.template !== undefined) {
return this.compileHtml(getOrCall(this.directive.template));
} else if (this.directive.templateUrl) {
const url = getOrCall(this.directive.templateUrl);
const html = this.$templateCache.get(url) as string;
if (html !== undefined) {
return this.compileHtml(html);
} else {
throw new Error('loading directive templates asynchronously is not supported');
// return new Promise((resolve, reject) => {
// this.$httpBackend('GET', url, null, (status: number, response: string) => {
// if (status == 200) {
// resolve(this.compileHtml(this.$templateCache.put(url, response)));
// } else {
// reject(`GET component template from '${url}' returned '${status}: ${response}'`);
// }
// });
// });
}
} else {
throw new Error(`Directive '${this.name}' is not a component, it is missing template.`);
}
}
If you don't want to modify your Webpack configuration, the quick/dirty solution is to use the raw-loader import syntax:
template: require('!raw-loader!./your-template.html')
As a workaround I used $templateCache and $templateRequest to put templates in $templateCache for Angular needed templates, on AngularJS run as follows:
app.run(['$templateCache', '$templateRequest', function($templateCache, $templateRequest) {
var templateUrlList = [
'app/modules/common/header.html',
...
];
templateUrlList.forEach(function (templateUrl) {
if ($templateCache.get(templateUrl) === undefined) {
$templateRequest(templateUrl)
.then(function (templateContent) {
$templateCache.put(templateUrl, templateContent);
});
}
});
}]);
I've created a method utility to solve this issue.
Basically it adds the template url content to angular's templateCache,
using requireJS and "text.js":
initTemplateUrls(templateUrlList) {
app.run(function ($templateCache) {
templateUrlList.forEach(templateUrl => {
if ($templateCache.get(templateUrl) === undefined) {
$templateCache.put(templateUrl, 'temporaryValue');
require(['text!' + templateUrl],
function (templateContent) {
$templateCache.put(templateUrl, templateContent);
}
);
}
});
});
What you should do is put this method utility in appmodule.ts for example, and then create a list of templateUrls that you are about to upgrade from your angular directive, for example:
const templateUrlList = [
'/app/#fingerprint#/common/directives/grid/pGrid.html',
];
I use webpack's require.context for this:
templates-factory.js
import {resolve} from 'path';
/**
* Wrap given context in AngularJS $templateCache
* #param ctx - A context module
* #param dir - module directory
* #returns {function(...*): void} - AngularJS Run function
*/
export const templatesFactory = (ctx, dir, filename) => {
return $templateCache => ctx.keys().forEach(key => {
const templateId = (() => {
switch (typeof filename) {
case 'function':
return resolve(dir, filename(key));
case 'string':
return resolve(dir, filename);
default:
return resolve(dir, key);
}
})();
$templateCache.put(templateId, ctx(key));
});
};
app.html-bundle.js
import {templatesFactory} from './templates-factory';
const ctx = require.context('./', true, /\.html$/);
export const AppHtmlBundle = angular.module('AppHtmlBundle', [])
.run(templatesFactory(ctx, __dirname))
.name;
Don't forget to add html-loader to your webpack.config.js:
[{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
minimize: false,
root: path.resolve(__dirname, './src')
}
}
}]
Also you may need to convert relative paths to absolute one. I use my self-written babel plugin ng-template-url-absolutify for this purpose:
[{
test: /\.(es6|js)$/,
include: [path.resolve(__dirname, 'src')],
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: [
'#babel/plugin-syntax-dynamic-import',
['ng-template-url-absolutify', {baseDir: path.resolve(__dirname, 'src'), baseUrl: ''}]
],
presets: [['#babel/preset-env', {'modules': false}]]
}
},

SystemJS / TypeScript, how to combine multiple classes into a single module

I asked a similar question a while ago when I was trying requireJS here, I'm not sure what I did then is correct or not, but I cannot get it working with systemJS.
I'm trying to create a single module file which contains export for multiple classis for a given module.
This is the sample module (called module1).
import {Module1ComponentA} from "./a.component";
import {Module1ComponentB} from "./b.component";
export * from "./a.component";
export * from "./b.component";
angular.module("module1", [])
.component("module1ComponentA", new Module1ComponentA())
.component("module1ComponentB", new Module1ComponentB())
;
I can then reference either component using
import {Module1ComponentA} from './module1/module1
The problem is I get an exception when loading the app.
Error: TypeError: i.setters[l] is not a function
at exportStar_1 (http://127.0.0.1:8080/output/module1/module1.js:10:9)
at Array.setters.b_component_1 (http://127.0.0.1:8080/output/module1/module1.js:16:17)
at execute (http://127.0.0.1:8080/output/module1/a.component.js:14:13)
Error loading http://127.0.0.1:8080/output/boot.js
If instead I do not include the 'export' statement but references the imports by their source file, then it loads.
This is the SystemJS config
<script>
System.config({
packages: {
output: {
format: 'register',
defaultExtension: 'js'
}
},
map: {
app: 'output/boot.js',
module1: 'output/module1/module1.js'
},
meta: {
'app': { deps: [ 'module1' ] },
'*.js': {
scriptLoad: true,
}
}
});
System.import('output/boot.js')
.then(null, console.error.bind(console));
</script>
This is the generated js file for the module
System.register(["./a.component", "./b.component"], function(exports_1, context_1) {
"use strict";
var __moduleName = context_1 && context_1.id;
var a_component_1, b_component_1;
function exportStar_1(m) {
var exports = {};
for(var n in m) {
if (n !== "default") exports[n] = m[n];
}
exports_1(exports);
}
return {
setters:[
function (a_component_1_1) {
a_component_1 = a_component_1_1;
exportStar_1(a_component_1_1);
},
function (b_component_1_1) {
b_component_1 = b_component_1_1;
exportStar_1(b_component_1_1);
}],
execute: function() {
angular.module("module1", [])
.component("module1ComponentA", new a_component_1.Module1ComponentA())
.component("module1ComponentB", new b_component_1.Module1ComponentB());
}
}
});
//# sourceMappingURL=module1.js.map
Any suggestion on how to do this ?

Change script type to "text/babel" using requireJS

I am using requireJS to load React components but I was getting the error "Uncaught SyntaxError: Unexpected token <" because the script type for the file is "text/javascript" instead of "text/babel". To solve this I have tried to set the scriptType as explained by requireJS docs and explained in this question, but I'm unable to get it working or find a good example of how to make this work.
requireConfig.js:
requirejs.config({
baseUrl: 'scripts/',
paths:
{
jquery: 'jquery-1.9.0',
react: 'libs/build/react',
reactdom: 'libs/build/react-dom',
browser: '//cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min',
inputWindow: 'inputWindow/inputWindow'
},
scriptType: {
'inputWindow': "text/babel"
}
});
define(function (require) {
var InputWindow = require('inputWindow');
InputWindow.initialize();
});
inputWindow.js:
define(function(require){
var React = require('react');
var ReactDOM = require('reactdom');
var InputWindow = React.createClass({
render: function(){
return(<div>
{this.props.message}
</div>)
}
});
function initialize(){
ReactDOM.render(<InputWindow message="Hello World!"/>, document.getElementById('inputWindowDiv'))
}
return {
initialize: initialize,
}
})
When I configure requireConfig.js with the section
scriptType:{
'inputWindow':'text/babel'
}
then the file inputWindow.js is loaded into index.html with the tag
type="[Object Object]"
until requireJS times out.
screen capture of inputWindow.js loaded with type=[Object Object]
Instead of
scriptType: {
'inputWindow': "text/babel"
}
try
scriptType: 'text/babel'
It should work. Right now you're trying to stringify an object so no wonder it doesn't work. ;)

Problems with application startup and Backbone.Marionette Modules

Unfortunately, I've problems while understanding the startup of Backbone.Marionette modules and submodules. The initializers are called multiple times instead of being called one time each.
What do I need to do to make things work in foreseeable manner?
PP = new Backbone.Marionette.Application();
PP.bind('start', function() {
console.log('application start');
PP.module('Routing').start();
PP.module('Products').start();
});
PP.module('Routing', {
startWithApp: false,
define: function(Routing, PP, Backbone, Marionette, $, _) {
Routing.addInitializer(function() {
console.log('Routing.init');
});
}
});
PP.module('Routing.ProductsRouting', {
startWithApp: false,
define: function(ProductsRouting, PP, Backbone, Marionette, $, _) {
ProductsRouting.addInitializer(function() {
console.log('ProductsRouting.init');
});
}
});
PP.module('Products', {
startWithApp: false,
define: function(Products, PP, Backbone, Marionette, $, _) {
Products.addInitializer(function() {
console.log('Products.init');
});
}
});
$(function() {
PP.start();
});
(code also available as JSFiddle)
The code above outputs this lines in the console:
application start
Routing.init
ProductsRouting.init
Routing.init
ProductsRouting.init
Products.init
Products.init
And this is what I expected:
application start
Routing.init
Products.init
And if you decide to automatically start all the modules with your app (startWithApp: true in all modules and without manually starting Routing and Products modules) the output is this:
Routing.init
ProductsRouting.init
ProductsRouting.init
Products.init
application start
this is fixed w/ v0.9.7 https://github.com/derickbailey/backbone.marionette/blob/master/changelog.md#v097-view-commit-logs
The problem is solved by implementing this github pull request on Backbone.Marionette. But maybe Derick Bailey (creator of Backbone.Marionette) has his own opinions on this?
In case anyone is still having an issue with modules seeming to load in the wrong order - the solution for me was the location of Backbone.history.start().
Here was my problem:
bootstrap.js
App.start();
App.js
App = new Backbone.Marionette.Application();
var _AppRouter = Backbone.AppRouter.extend({
appRoutes: {
"" : "getStarted"
},
controller: App
});
App.getStarted = function() {
console.log(App.MyModule.myMethod); // undefined
App.MyModule.myMethod();
}
App.addInitializer(function() {
new _AppRouter();
Backbone.history.start();
});
App.MyModule.js
App.module("MyModule", function(MyModule, App) {
MyModule.myMethod = function() {
console.log("in myMethod"); // never gets called
}
});
App.MyModule.myMethod is undefined in this example, so nothing happens when the app starts.
The problem, I discovered, is where I was calling Backbone.history.start(). I moved this call to my bootstrap.js file, so that I'm only calling my routes after all of my app modules have been properly initialized.
bootstrap.js
App.start();
Backbone.history.start();
Easy peazy.

Resources