Change script type to "text/babel" using requireJS - reactjs

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. ;)

Related

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}]]
}
},

`ReferenceError: Element is not defined` when running mocha tests on React+Onsenui app

While trying to setup a test environment I ran into the following problem. When I run the tests (using mocha ./src/test/.setup.js ./src/test/**.test.js), I get a Element is not defined error:
app\node_modules\onsenui\js\onsenui.js:603
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
^
ReferenceError: Element is not defined
at app\node_modules\onsenui\js\onsenui.js:603:34
at app\node_modules\onsenui\js\onsenui.js:359:7
at Array.forEach (native)
at initializeModules (app\node_modules\onsenui\js\onsenui.js:358:13)
at app\node_modules\onsenui\js\onsenui.js:908:5
(...)
How is this possible, isn't Element a basic DOM element?
The following versions are used:
enzyme#2.4.1
mocha#3.0.2
onsenui#2.0.0-rc.17
react#15.3.1
react-onsenui#0.7.5
The following .setup.js file is used:
require('babel-register')({
presets: ["react","airbnb","es2015"]
});
var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
documentRef = document;
And the test file is as follows:
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import * as Ons from 'react-onsenui';
import MainPage from '../react/MainPage/MainPage.jsx';
describe("Component MainPage", function() {
it("should have a Ons.Page component", function() {
const wrapper = mount(<MainPage />);
expect(wrapper.find(Ons.Page)).to.equal(true);
});
});
onsenui seems to be written for a browser enviroment, whilst you're executing it in a nodejs enviroment.
Element is a DOM api. nodejs has no DOM.
You could research using jsDom which is a node module that mimics the browser DOM for nodejs.
Edit:
I think this package could solve your problems: https://github.com/rstacruz/jsdom-global I checked, it should place a 'Element' property in the global.

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

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?

Marionette js routing - What am I doing wrong here? I'm getting error that route actions are undefined?

Just want to get some basic routing up and going. Having seen a number of examples I thought the code below should work, but when I run I get the error "Unable to get property 'doChat' of undefined or null reference". Do I have the initialization sequence wrong?
require(["marionette", "jquery.bootstrap", "jqueryui"], function (Marionette) {
window.App = new Marionette.Application();
App.start();
App.addRegions({
//add some regions here
});
//Set up routing
var AppRouter = Marionette.AppRouter.extend({
appRoutes: {
"": "doDefault",
"chat": "doChat"
},
doDefault: function () {
alert("doing default...")
},
doChat: function () {
alert("doing chat...")
}
});
var router = new AppRouter();
//History
if (Backbone.history) {
Backbone.history.start();
}
})
The AppRouter allows two types of routes, standard backbone routes as defined in the routes property and routes which call functions in another object defined in the appRoutes property.
So to get you above code working, you can do one of two things. The quickest is simply to change the appRoutes property to routes which will do normal backbone routing. The second option is to create another object and pass that to the AppRouter as the controller during instantiation:
var myController = {
doDefault: function () {
alert("doing default...")
},
doChat: function () {
alert("doing chat...")
}
}
var router = new AppRouter({
controller: myController
});
This is detailed in the AppRouter documentation.

Backbone.Marionette controller not working

I have a Backbone.Marionette app configured using the AppRouter as well as the EventAggregator.
The initializer starts the router, and then history. I know for sure that my EventAggregator is set up properly - MyVent.trigger('abc') works properly in the console. The AppRouter also seems to be working properly as navigating to an undefined URL results in a 404, as expected.
Am I missing something?
//Initializer
MyApp.addInitializer(function(options){
//do stuff here
router = new MyRouter(MyController);
console.log('routing started!');
MyVent.trigger('routing:started'); <-- this works
});
//EventAggregator
MyVent = new Backbone.Marionette.EventAggregator();
MyVent.on('contactUs', function(){
console.log('ContactUs received by MyVent!');
startContactUsModal();
Backbone.history.navigate("contactus/");
});
MyVent.on('bookNow', function(){
console.log('BookNow received by MyVent!');
startBookNowModal();
Backbone.history.navigate("booknow/");
});
MyVent.on('home', function(){
console.log('home received by MyVent!');
startHome();
console.log('after starthome on myvent');
});
MyVent.on('routing:started', function(){
console.log('routing:started recieved at MyVent!');
if( ! Backbone.History.started) Backbone.history.start();
console.log('Backbone.history sucessfully started!');
});
//Controller
MyController = {
homeMethods:function(){
console.log('home receieved at mycontroller');
MyVent.trigger('home')
},
booknowMethods:function(){
MyVent.trigger('bookNow')
},
contactusMethods:function(){
MyVent.trigger('contactUs')
}
};
//Router
MyRouter = Backbone.Marionette.AppRouter.extend({
controller: MyController,
routes: {
'' : 'homeMethods',
'tours' : 'toursMethods',
'booknow' : 'booknowMethods',
'contactus' : 'contactusMethods'
},
});
WOW! What a stupid mistake - at least I'm getting faster at identifying these.
Declaring routes in AppRouter, is different than in the Backbone router.
Marionette: appRoutes
Regular Backbone: routes

Resources