I'm building a modular javascript application (RequireJS/Backbone), and am looking for some advice on best practices for propagating server-side URLs into the JS application for use in client side templating, API requests, etc.
Inject it into the base template that kicks off the Javascript application?
API request specifically for this purpose?
Would love to hear solutions others have used. Thanks!
You can render a <script> tag in the body, define a module and put your URLs there in any way you like.
After that you can require it inside your modules (.js files).
HTML (e.g. your application layout):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Inline RequireJS define</title>
</head>
<body>
<h1 id="foo-holder"></h1>
<a id="sign-up-link" href="#"></a>
<script>
// This is rendered by your server-side application:
define('data/config', function(require, exports, module) {
exports.Url = {
HOME: 'https://mysite.com',
SIGN_IN: 'https://mysite.com/sign-in',
SIGN_UP: 'https://mysite.com/sign-up',
LOG_OUT: 'https://mysite.com/log-out'
};
exports.foo = 'bar';
});
</script>
</body>
</html>
JavaScript (somewhere in your module):
// This is somewhere in your JavaScript module
require(['data/config'], function(Config) {
$('#foo-holder').text(Config.foo);
$('#sign-up-link')
.text(Config.Url.SIGN_UP)
.attr('href', Config.Url.SIGN_UP);
});
DEMO
Another trick could be done with some kind of attribute-binding.
In your layout:
<a data-url="HOME" href="#">Home</a>
<a data-url="SIGN_IN" href="#">Sign In</a>
<a data-url="SIGN_UP" href="#">Sign Up</a>
<a data-url="LOG_OUT" href="#">Log Out</a>
<script>
// This is rendered by your server-side application:
define('data/config', function(require, exports, module) {
exports.Url = {
HOME: 'https://mysite.com',
SIGN_IN: 'https://mysite.com/sign-in',
SIGN_UP: 'https://mysite.com/sign-up',
LOG_OUT: 'https://mysite.com/log-out'
};
exports.foo = 'bar';
});
</script>
In your JavaScript file:
// This is somewhere in your JavaScript module
require(['data/config'], function(Config) {
$('a[data-url]').each(function() {
var $a,
urlConstant,
url;
$a = $(this);
urlConstant = $a.data('url');
url = Config.Url[urlConstant];
if(url) {
$a.attr('href', url);
}
});
});
DEMO
Related
I have a Node.JS+Express backend, and am trying to set up an angular frontend.
I have a basic HTML template called common.html:
<!DOCTYPE HTML>
<html>
<head>
<title>Watson-Amadeus</title>
<link rel = "stylesheet" href = "css/index.css" />
<link rel = "stylesheet" href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel = "script" href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.3/angular-ui-router.min.js"></script>
<script src="js/amadeus.js"></script>
</head>
<body ng-app = "watson_amadeus">
<div ui-view>
</div>
</body>
</html>
js/amadeus.js then looks like
var app = angular.module("watson_amadeus", ["ui.router"]);
app.config(($stateProvider) => {
var introState = {
name: "intro",
url: "/intro",
templateUrl: "../index.html"
}
var amadeusState = {
name: "amadeus",
url: "/amadeus",
template: "../watson-amadeus.html"
}
$stateProvider.state(introState);
$stateProvider.state(amadeusState);
});
app.controller("amadeus_controller", ($scope) => {
$scope.messages = [
{text: "Yo"},
{text: "sup?"}
];
console.log("controller");
$scope.send_message = () => {
$scope.messages.push({text: $scope.msg_to_send});
$scope.msg_to_send = "";
};
});
intro.html and watson-amadeus.html are then just partials with some html in them. My Node server code looks like this:
var express = require('express'),
amadeus = require('./rest/amadeus.js'),
body_parser = require('body-parser');
var app = express();
app.use(body_parser.json());
app.use(body_parser.urlencoded({extended: false}));
app.use(express.static("public")); // Use static pages from the public directory
app.post("/api/amadeus/", amadeus.send_message);
app.get("*", (req, res) => {
res.sendFile(__dirname + "/public/common.html");
});
app.listen(8080); // Set the server to listen on port 8080
Finally, my directory structure is reasonably straightforward:
project_dir
public
js
amadeus.js
common.html
index.html
watson-amadeus.html
node_server.js
package.json
When I start the web server and try http://localhost:8080/intro, I am greeted with just a blank screen, and looking at the page source shows that it is just loading common.html without putting the index.html content into the viewport as it should. What is more; when I do http://localhost:8080/, I get the contents of index.html, but without the parts from common.html.
I'm new to angular, so this may be something fundamental but I don't know where I have messed up.
Once you defined app.use(express.static('public'));, express server will load index.html automatically, so app.get("*", (req, res) => { res.sendFile(__dirname + "/public/common.html"); }); won't run at all.
I would recommend rename common.html to index.html since it is your major html.
By default ui-route will add#! to url, try http://10.8.8.8:8080/, after index.html has been loaded successfully, the url actually changed to http://10.8.8.8:8080/#!/.
Hence, you can access your /intro by http://10.8.8.8:8080/#!/intro.
There will be lots of effort to take if you want to remove #!, you can research next step. Good luck!
I have situation where I would like to configure component in html code. I have the following structure.
game.html which is served as in url like example.com/game/7999 which should show page for game 7999.
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<base href="/">
<title>Providence</title>
<script src="/js/angular.js"></script>
<script src="/data-access/data-access.module.js"></script>
<script src="/data-access/data-access.service.js"></script>
<script src="/score-info/score-info.module.js"></script>
<script src="/score-info/score-info.component.js"></script>
<script src="/js/game.js"></script>
</head>
<body>
<div ng-controller="myController">
<p> {{ game_id }} </p>
<score-info game_id="{{ game_id }}"></score-info>
</div>
</body>
Corresponding game.js, which seem to work as game_id shows up correctly.
angular.module('myApp', [
'dataAccess',
'scoreInfo' ],
function($locationProvider){
$locationProvider.html5Mode(true);
});
angular.
module('myApp').
controller('myController', function($scope, $location) {
var split_res = $location.path().split('/');
var game_id = split_res[split_res.length-1];
$scope.game_id = game_id
});
My problem lies in component where I'm unable to inject the game_id. Here's score-info.component.js where the game_id does not become visible.
angular.
module('scoreInfo').
component('scoreInfo', {
templateUrl : '/score-info/score-info.template.html',
controller : function ScoreInfoController(dataAccess) {
self = this;
console.log(self.game_id) // self.game_id == undefined
dataAccess.game(self.game_id).then(function(game) {
self.game = game;
});
},
bindings : {
game_id : '<'
}
});
I noticed that some earlier answers recommended using a separate service of wiring up controller and component. That does not work for me as I need to be able to include varying number of scoreInfo -blocks in a single page.
I'm going to answer this myself. The answer was provided by JB Nizet in comments.
First problem was naming related. The code needs to stick with angular.js' naming convention and use gameId: '<' and use <score-info game-id="game_id">
Also < binding must have the reference in the element without curly braces: <score-info game-id="game_id">
Finally, the components controller needs to take in to the account breaking changes between angular 1.5 -branch and 1.6 -branch. See angular CHANGELOG. Specifically ScoreInfoController becomes
function ScoreInfoController(dataAccess) {
self = this;
self.$onInit = function() {
dataAccess.game(self.game_id).then(function(game) {
self.game = game;
})
}
I am trying to get ng-route working with a google-apps-script web app. I have managed to get basic angular.js functionality working with google-apps-script, but I can't seem to get ng-route to work. I have placed ng-view tags inside a page and have included a separate JavaScript page that contains the routeProvider function.
The ng-view never gets rendered and as far as I can make out the routeProvider does not get called.
Can anyone offer any advice on using ng-route with google-apps-script or suggest another way of rendering a partial html page with google-apps-script
Any answers greatly appreciated.
Have simplified my code and added below:
Code.gs
function doGet(e) {
var template = HtmlService.createTemplateFromFile('Index');
// Build and return HTML in IFRAME sandbox mode.
return template.evaluate()
.setTitle('Web App Window Title')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
return url;
}
index.html
<!-- Use a templated HTML printing scriptlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
<html>
<body ng-app="myApp">
<h1>NG View</h1>
<ng-view></ng-view>
<p>angular check {{'is' + 'working!'}}</p>
<? var url = getScriptUrl();?>
<p id="urlid"><?=url?></p>
</body>
</html>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<?!= HtmlService.createHtmlOutputFromFile('JavaScript').getContent(); ?>
Javascript.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://code.angularjs.org/1.3.15/angular.js"></script>
<script src="https://code.angularjs.org/1.3.15/angular-route.js"> </script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
<script>
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider){
console.log('routeProvider config');
var url = document.getElementById("urlid").innerHTML;
console.log('routeProvider config->' +url);
$routeProvider.when("/",
{
templateUrl: url+"?page=_app.html",
controller: "AppCtrl",
controllerAs: "app"
}
);
})
.controller('AppCtrl', function() {
var self = this;
self.message = "The app routing is working!";
});
</script>
_app.html
<div>
<h1>{{ app.message }}</h1>
</div>
When this runs angular check {{'is' + 'working!'}} works fine, but the ng-view does not get rendered the java console shows:
Error: [$sce:insecurl] Blocked loading resource from url not allowed by $sceDelegate policy.
The first obstacle is "sce"
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Refer link https://docs.angularjs.org/api/ng/service/$sce#trustAsResourceUrl
For the purpose of investigation, I disabled sce (this is not recommended, though)
$sceProvider.enabled(false);
Now the error is shifted to XMLHttpRequest cannot load... No 'Access-Control-Allow-Origin' header is present on the requested resource.
XHR requests to the Google Apps Script server are forbidden
Refer link https://developers.google.com/apps-script/guides/html/restrictions
Google Apps is delivering the files from a different origin than the scripts.google.com and angular js client code is not able to fetch the partial htmls from the same origin.
I guess approach of ng-view is not feasible given the restrictions placed by google apps.
Here is the final modified code
<script>
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider,$sceProvider){
$sceProvider.enabled(false);
console.log('routeProvider config');
var url = document.getElementById("urlid").innerHTML;
console.log('routeProvider config->' +url);
$routeProvider.when("/",
{
templateUrl: url+"?page=_app.html",
controller: "AppCtrl",
controllerAs: "app"
}
);
})
.controller('AppCtrl', function() {
var self = this;
self.message = "The app routing is working!";
});
</script>
There has been some time since the question, but I'll post a reply either way.
If your partial html page is not too complicated and big, you can use template instead of templateUrl in the routeProvider, plus create a variable with the html you want to show. Something like this below:
var partial_page = "<span>partial page</span>"
$routeProvider.when("/",
{
template: partial_page,
controller: "AppCtrl",
controllerAs: "app"
}
It worked for me, but I wouldn't advise doing so for a complicated partial page as it may become difficult to read the code
I am having trouble setting up ng-view. This is my first mean stack app. I got everything working within index.html. However, when I set up ng-view I am getting errors stating that I have my javascripts in a public folder. My index.html is in the html folder. I have set up an additional folder in views called templates to house my additional pages
"GET http://localhost:3000/templates/home.html 500 (Internal Server Error)"
Inside of my html I have set up ng-view
<!doctype html>
<html lang="en" ng-app='myApp'>
<head>
<meta charset="UTF-8">
<title>Caffeine app</title>
<!-- styles -->
<link href="http://netdna.bootstrapcdn.com/bootswatch/3.3.2/yeti/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="stylesheets/style.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="container>
<div ng-view>
</div>
</div>
<!-- scripts -->
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="libs/angular/angular.min.js"></script>
<script src="libs/angular-route/angular-route.min.js"></script>
<script src="javascripts/main2.js" type="text/javascript"></script>
</body>
</html>
In my public js folder I have set up my factory, config, and controllers. I am using swig.
var app = angular.module('myApp', ['ngRoute'], function ($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
});
app.config(function($routeProvider,$locationProvider){
$routeProvider
.when('/home',{
templateUrl:'templates/home.html',
controller:'myController'
})
.when('/drinkLibrary',{
templateUrl:'templates/drinkLibrary.html',
controller:'DrinkLibraryController'
})
.otherwise({
redirectTo: '/home'
})
$locationProvider.hashPrefix('!');
});
app.factory('Drink',function($http) {
var Drink = function(name,description,caffeineLevel) {
this.name = name;
this.description = description;
this.caffeineLevel = caffeineLevel;
}
return Drink;
})
app.controller('HomeController',function($scope){
console.log('home');
})
app.controller('DrinkLibraryController',function($scope){
console.log('drinkLibrary');
})
app.controller('myController', function($scope,Drink,$http ) {
var init = function() {
$scope.defaultForm = {
beverageName: "",
description: "",
caffeine: ""
};
}
init();
// $scope.defaultForm = defaultForm;
$scope.allDrinkList = [];
$scope.drinkList= function(obj) {
var newdrink = new Drink(obj.beverageName,obj.description,obj.caffeine);
$scope.allDrinkList.push(newdrink);
console.log($scope.allDrinkList);
init();
$http.post('/api/drinks',obj).
success(function(data){
console.log(data)
$scope.message = 'success';
}).
error(function(data){
console.log('error');
})
};
});
Inside of my routes folder I am making sure to render the index
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index');
});
module.exports = router;
In doing a mean stack I must remember to set up routes on the server and client side. My templates are in the view. I am rendering my view through express I need to also render my templates in the same manner.
app.use('templates/:templateid', routes);
I am using the express generator so through the routes I called a get request and set the url to the templates folder. Next, I identified the template id as a param. This saves me from setting up each page ex(home,library, about).
router.get('/templates/:templateid' ,function(req,res,next){
res.render('templates/' + req.params.templateid);
})
I've an app that retrieve server data using ajax. I've tested in localhost, the loader work fine, but when I install my extension and click on the browser action popup, the loader won't show. The little popup delayed for 2 second and shows the result.
popup.html
<div class="cssLoader" ng-show="loader">Fetching...</div>
js
app.controller('MainControl', function($scope, $http){
$scope.loader = true;
$http({
url: "http://www.corsproxy.com/mydomain.net/items.php",
method: "GET",
}).success(function(data) {
$scope.data = data;
$scope.loader = false;
});
});
Without seeing more of your code it is difficult to know for sure. Nonetheless, my suspicion (based upon the fact that your code works outside of the Chrome extension environment but not inside that environment) is that since you're operating in a Chrome Extension environment, you'll need to include the ng-csp directive (see Chrome documentation or Angular documentation).
I developed an Angular app inside a chrome extension and I needed to use ng-csp in order for Angular to load and fully function properly.
Essentially, Chrome extensions (and even more apps) place a number of restrictive security permissions on the browser environment and ng-csp tells Angular to operate in a way that is more consistent with a strict CSP.
I have included an example below that shows loading the entire Angular application properly:
<!DOCTYPE html>
<html ng-app="myApp" ng-csp>
<head>
<meta charset="utf-8">
<title>My Extension</title>
<link href="css/index.css" rel="stylesheet">
<!-- Include in the next line your Angular library code -->
<script type="text/javascript" src="js/angular-lib.js"></script>
<!-- Include in the next line your custom Angular code such as the $http to load the loader -->
<script type="text/javascript" src="js/myapp.js"></script>
</head>
<body>
<!-- Place your HTML code for the 'Fetching' anywhere here in the body -->
</body>
</html>
According to the docs, CSP "is necessary when developing things like Google Chrome Extensions" (more info can be found on the linked page).
Furthermore, besides defining ng-csp on your root element, there is on more crucial point (which affects ngShow/ngHide):
CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically includes some CSS rules (e.g. ngCloak). To make those directives work in CSP mode, include the angular-csp.css manually.
I found this to be necessary only if the angular.js script is defined inside the app's context.
In any case, here is the source code of minimal demo extension that seems to work fine for me:
Structure:
extension-root-dir/
|_____manifest.json
|_____popup.html
|_____popup.js
|_____angular.js
|_____angular-csp.css
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"browser_action": {
"default_title": "Test Extension",
//"default_icon": {
// "19": "img/icon19.png",
// "38": "img/icon38.png"
//},
"default_popup": "popup.html"
}
}
popup.html:
<!DOCTYPE html>
<html>
<head>
<title>Test Extension</title>
<link rel="stylesheet" href="angular-csp.css" />
</head>
<body ng-csp ng-app="myApp" ng-controller="mainCtrl">
<div ng-show="loader">Fetching...</div>
<div ng-hide="loader">{{status}}</div>
<script src="angular.js"></script>
<script src="popup.js"></script>
</body>
</html>
popup.js:
var app = angular.module('myApp', []);
app.controller('mainCtrl', function ($http, $scope) {
$scope.loader = true;
$http({
url: "http://www.corsproxy.com/mydomain.net/items.php",
method: "GET"
}).finally(function () {
$scope.loader = false;
}).then(function (response) {
$scope.data = response.data;
$scope.status = 'Success !';
}, function (response) {
$scope.status = 'ERROR !';
});
});
(BTW, I am using AngularJS v1.2.16.)