First of all, I'm quite a noob so please excuse me if I ask stupid questions, or if the same question has already been answered elsewhere: I might not know the rights terms to effectively search for a topic.
So here's my problem. I'm trying to create a dashboard using Polymer. Therefore, I'll have a nav bar / menu with many option (contracts, calendars, admin page...). While looking at the polymer starter kit and its demo, we are told to put all the pages related to the navigation drawer within the index.html file, between <section> markups.
However, these pages may contain a lot of code, and there will be many pages (12 at the moment). I fear the index.html will soon become humongous, which will probably mean "hard to maintain" and "long loading time".
So my question is the following: is there a way to easily split the page app into multiple html files? Like creating a new custom element and importing it in the header, then using it between the <section> markups?
Okay so, following the advices I've been given here, I've read about HTMLimport, and the tutorial about "Lazy loading" on Chrome developpers' youtube and here's what I did (it is based on polymer starter kit). Problem: it does not work :(
Clicking on "Contracts" in the navbar does nothing. I don't get it :/
Help me please!
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My awesome page</title>
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="elements/elements.html">
</head>
<body unresolved>
<!-- build:remove -->
<span id="browser-sync-binding"></span>
<!-- endbuild -->
<template is="dom-bind" id="app">
<paper-menu class="app-menu" attr-for-selected="data-route" selected="[[route]]">
<a data-route="contracts" href="{{baseUrl}}contracts">
<iron-icon icon="description"></iron-icon>
<span>Contracts</span>
</a>
</paper-menu>
<div class="content">
<iron-pages id="iron" attr-for-selected="data-route" selected="{{route}}">
<section data-route="contracts" tabindex="-1">
<page-contracts id="contracts"></page-contracts>
</section>
<!-- lots of other <section> here -->
</iron-pages>
</div>
</paper-scroll-header-panel>
</paper-drawer-panel>
</template>
<script src="scripts/app.js"></script>
</body>
</html>
and here's the routing element:
<script src="../bower_components/page/page.js"></script>
<script>
window.addEventListener('WebComponentsReady', function() {
// We use Page.js for routing. This is a Micro
// client-side router inspired by the Express router
// More info: https://visionmedia.github.io/page.js/
// Removes end / from app.baseUrl which page.base requires for production
if (window.location.port === '') { // if production
page.base(app.baseUrl.replace(/\/$/, ''));
}
// Middleware
function scrollToTop(ctx, next) {
app.scrollPageToTop();
next();
}
function closeDrawer(ctx, next) {
app.closeDrawer();
next();
}
function setFocus(selected){
document.querySelector('section[data-route="' + selected + '"] .page-title').focus();
}
// Routes
page('*', scrollToTop, closeDrawer, function(ctx, next) {
next();
});
/* other routing here */
page('/contrats', function() {
if (Polymer.isInstance(this.$.contrats)) {
app.route = "contrats";
return;
}
Polymer.base.importHref(
"/page-contrats/page-contrats.html", function() {
app.route = "contrats";
return;
}
)
});
/* other routing here */
// 404
page('*', function() {
app.$.toast.text = 'Impossible to find: ' + window.location.href + '. Redirecting to dashboard';
app.$.toast.show();
page.redirect(app.baseUrl);
});
// add #! before urls
page({
hashbang: true
});
});
</script>
Your routing page needds to look like this:
<script src="../bower_components/page/page.js"></script>
<script>
window.addEventListener('WebComponentsReady', function() {
// We use Page.js for routing. This is a Micro
// client-side router inspired by the Express router
// More info: https://visionmedia.github.io/page.js/
// Removes end / from app.baseUrl which page.base requires for production
if (window.location.port === '') { // if production
page.base(app.baseUrl.replace(/\/$/, ''));
}
// Middleware
function scrollToTop(ctx, next) {
app.scrollPageToTop();
next();
}
function closeDrawer(ctx, next) {
app.closeDrawer();
next();
}
function setFocus(selected){
document.querySelector('section[data-route="' + selected + '"] .page-title').focus();
}
// Routes
page('*', scrollToTop, closeDrawer, function(ctx, next) {
next();
});
/* other routing here */
page('/contrats', function() {
app.route = 'contrats';
setFocus(app.route);
});
/* other routing here */
// 404
page('*', function() {
app.$.toast.text = 'Impossible to find: ' + window.location.href + '. Redirecting to dashboard';
app.$.toast.show();
page.redirect(app.baseUrl);
});
// add #! before urls
page({
hashbang: true
});
});
</script>
In your elements.html you need to import the page:
<link rel="import" href="/page-contrats/page-contrats.html">
And your enement needs to look like this:
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../elements.html">
<dom-module id="contrats">
<template>
<style include="shared-styles"></style>
<style>
:host {
display: block;
}
</style>
<your-code-here>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'contrats',
});
})();
</script>
</dom-module>
Hope I helped.
Related
[$injector:modulerr] Failed to instantiate module starter due to: [$injector:nomod] Module 'starter' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
I started getting this bug in my console while I was working on a new feature for my Ionic app. I have gone over my modules, moved some script tags around, and I still can't figure this out. I have seen other people with this error put the ng-app in the head, which I did and it didn't work. I hadn't touched the ng-app or the angular.module at the top of app.js when I started getting this error. Can someone help point me in the right direction?
Error: [$injector:modulerr] Failed to instantiate module starter due to:
[$injector:modulerr] Failed to instantiate module starter.services due to:
[$injector:nomod] Module 'starter.services' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.5.3/$injector/nomod?p0=starter.services
minErr/<#http://localhost:8100/lib/ionic/js/ionic.bundle.js:13443:12
module/<#http://localhost:8100/lib/ionic/js/ionic.bundle.js:15409:17
ensure#http://localhost:8100/lib/ionic/js/ionic.bundle.js:15333:38
module#http://localhost:8100/lib/ionic/js/ionic.bundle.js:15407:14
loadModules/<#http://localhost:8100/lib/ionic/js/ionic.bundle.js:17899:22
forEach#http://localhost:8100/lib/ionic/js/ionic.bundle.js:13696:11
loadModules#http://localhost:8100/lib/ionic/js/ionic.bundle.js:17883:5
loadModules/<#http://localhost:8100/lib/ionic/js/ionic.bundle.js:17900:40
forEach#http
app.js
// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('starter', ['ionic', 'starter.services'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
if(window.cordova && window.cordova.plugins.Keyboard) {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
// Don't remove this line unless you know what you are doing. It stops the viewport
// from snapping when text inputs are focused. Ionic handles this internally for
// a much nicer keyboard experience.
cordova.plugins.Keyboard.disableScroll(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
}
});
})
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: '/app',
templateUrl: "templates/menu.html",
controller: 'AppCtrl',
abstract: true
})
.state('app.home', {
url: '/home',
views: {
'menuContent': {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
}
}
});
$urlRouterProvider.otherwise('/app/home');
})
.controller('AppCtrl', function($scope, WC){
var Woocommerce = WC.WC();
Woocommerce.get('products/categories', function(err, data, res){
console.log(res);
})
})
.controller('AppCtrl', function(){
})
.controller('HomeCtrl', function(){
})
services.js
angular.module('starter.services',[])
.service('WC', function(){
return {
WC: function(){
var Woocommerce = new WoocommerceAPI({
url: 'http://samarth.cloudapp.net',
consumerKey: 'ck_98def17ffa4f32048cb5906d1de4fb35a2cc646a',
consumerSecret: 'cs_858089ad34205ced8dd85f91e28ad88677c85644',
wpAPI: true, //or false if you want to use the legacy API v3
version: 'wc/v2' //or wc/v1
})
return Woocommerce;
}
}});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title></title>
<link rel="manifest" href="manifest.json">
<!-- un-comment this code to enable service worker
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.log('Error', err));
}
</script>-->
<link href="lib/ionic/css/ionic.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
<link href="css/ionic.app.css" rel="stylesheet">
-->
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<!-- cordova script (this will be a 404 during development) -->
<script src="cordova.js"></script>
<!-- your app's js -->
<script src="js/app.js"></script>
</head>
<body ng-app="starter">
<ion-nav-view></ion-nav-view>
</body>
</html>
menu.html
<ion-side-menus>
<ion-side-menu-content>
<ion-nav-bar class = "bar-stable">
<ion-nav-back-button> </ion-nav-back-button>
<ion-nav-buttons side = "left">
<button class = "button button-icon button-clear ion-navicon" menu-toggle = "left">
</button>
</ion-nav-buttons>
<ion-nav-buttons side = "right">
<button class = "button button-icon button-clear ion-android-cart">
<span class = "badge badge-assertive"> 2 </span>
</button>
</ion-nav-buttons>
</ion-nav-bar>
<ion-nav-view name = "menuContent"> </ion-nav-view>
</ion-side-menu-content>
<ion-side-menu>
<ion-list>
<ion-item> Login </ion-item>
<ion-item> Home </ion-item>
<ion-item> Browse </ion-item>
<ion-item class = "item-divider">Categories</ion-item>
</ion-list>
</ion-side-menu>
</ion-side-menus>
I think you forgot to add services.js file in index.html, can you add the below line in index.html after app.js like below.
`<!-- your app's js -->
<script src="js/app.js"></script>
<script src="js/services.js"></script>`
I am trying to read local json file and convert it to HTML layout
I was able to read JSON from local but struggling to read and render on UI
HTML:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.rawgit.com/download/polymer-cdn/1.5.0/lib/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="https://cdn.rawgit.com/download/polymer-cdn/1.5.0/lib/polymer/polymer.html">
<link rel="import" href="https://cdn.rawgit.com/download/polymer-cdn/1.5.0/lib/iron-ajax/iron-ajax.html">
</head>
<body>
<template is="dom-bind">
<iron-ajax
auto
url="properties.json"
last-response="{{data}}"
handle-as="json">
</iron-ajax>
<div><span>{{data}}</span></div>
<!-- <template is="dom-repeat" items="{{data}}">
<div><span>{{item.name}}</span></div>
</template> -->
</template>
<script>
(function (document) {
'use strict';
var app = document.querySelector('#app');
window.addEventListener('WebComponentsReady', function() {
var ironAjax = document.querySelector('iron-ajax');
ironAjax.addEventListener('response', function() {
console.log(ironAjax.lastResponse);
});
ironAjax.generateRequest();
});
})(document);
</script>
</body>
</html>
properties.json file
{
"main":{
"header":{
"label" :"Employee Template",
"logo":"abc.png"
},
"footer":{
"label" :"2017 All rights reserved.",
"author":"abc"
},
"menu":[
{
"label":"Add",
"url":"/Add"
},
{
"label":"Delete",
"url":"/Delete"
}
]
}
I am trying to render the above json as below HTML
<header><h1>{{Employee.header.label}}</h1>
<img src={{Employee.header.logo}}>
</header>
<ul>
<li>{{Employee.menu[0].label}}</li>
<li>{{Employee.menu[0].label}}</li>
</ul>
<footer><h1>{{Employee.footer.label}}</h1>
<div>{{Employee.footer.author}}></div>
</footer>
I tried iron-ajax for rendering, but it works only on array of objects and not reading through the json and didnt find much information on documentation
At first, you are propably missing main in all bindings? according to your json the hiearchy is main.header.label and not header.label.
Another issue: When you are pointing to some index of array in bindings you are using {{Employee.menu[0].label}} but it should be {{Employee.menu.0.label}}. Remember this, because once you will use array mutation methods, it will be same.
If your JSON's structure is always like that and never changes, then it's super simple and you have already did, what you wanted. If your JSON's structure can change to something more complex, then you will have to propably write your own parser. which means, iterating all keys insinde json, creating elements and putting content in it. It will be a little bit harder, but it is possible.
I don't know how your json can look like so i won't write you parser. You will have to try it on your own.
It will start something like:
for(var key in this.Employee.main) {
var el = document.createElement(key);
...
}
here is an fiddle with an working example of your code: fiddle
The fiddle is ab bit different since i have no clue how to do ajax in fiddle but your code sould look like this:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.rawgit.com/download/polymer-
cdn/1.5.0/lib/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="https://cdn.rawgit.com/download/polymer-
cdn/1.5.0/lib/polymer/polymer.html">
<link rel="import" href="https://cdn.rawgit.com/download/polymer-
cdn/1.5.0/lib/iron-ajax/iron-ajax.html">
</head>
<body>
<template is="dom-bind">
<iron-ajax
auto
url="properties.json"
last-response="{{data}}"
handle-as="json">
</iron-ajax>
<header><h1>{{Employee.header.label}}</h1>
<img src={{Employee.header.logo}}>
</header>
<ul>
<li>{{data.main.menu.0.label}}</li>
<li>{{data.main.menu.0.label}}</li>
</ul>
<footer><h1>{{data.main.footer.label}}</h1>
<div>{{data.main.footer.author}}></div>
</footer>
</template>
<script>
(function (document) {
'use strict';
var app = document.querySelector('#app');
window.addEventListener('WebComponentsReady', function() {
var ironAjax = document.querySelector('iron-ajax');
ironAjax.addEventListener('response', function() {
console.log(ironAjax.lastResponse);
});
ironAjax.generateRequest();
});
})(document);
</script>
</body>
</html>`
I am playing around with adding in an Angular-UI router which is working perfectly when I click on links within my application. For example, if I go from / to /feed/9 it will load in the /partials/post.html file into the ui-view div and I can then use the '9' held in $stateParams to populate the template with the data from post 9. However if I refresh the page, the site breaks and Angular tries to load index.html as the ng-app.js file? I have no idea what is happening here. I've uploaded some screenshots to demonstrate this and I've included my node server, angular routing and the relevant html partials. I have no idea where this is going wrong so I can provide any additional data and any help would be greatly appreciated!
Working fine when coming from another link on '/'
On refresh!!
Node - server.js
var = /* Dependencies and vars */;
mongoose.connect(dbConfig.url, dbConfig.options);
app.use(express.static(__dirname + '/public'));
app.use(morgan('dev'));
app.use(bodyParser());
app.use(flash());
require('./routes/api.js')(app); //For CRUD operations on the database
require('./routes/api_proc.js')(app); //Protected endpoints for CDN
require('./routes/api_ext.js')(app); //For getting data from GCal, fb, Twitter and Instagram
/* The following code is a url rewrite to pass all
further get requests that aren't defined in the
above routing files through the index page and
hence through the Angular 'frontend' routes */
app.use(function(req, res) {
res.sendFile(__dirname + '/public/index.html');
});
app.listen(port);
Angular ng-app.js
var app = angular.module('app', ['ui.bootstrap', 'ngResource', 'ui.router']);
//Using state ui-router
// ROUTES
app.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url : '/',
templateUrl : 'partials/home.html'
})
/* ... */
.state('feed', {
url : '/feed',
templateUrl : 'partials/feed.html'
})
.state('post', {
url : '/feed/{id:.*}',
templateUrl : 'partials/post.html',
controller: 'postController'
})
$locationProvider.html5Mode(true);
});
app.factory("Feed", function($resource) {
return $resource("/api/feed/:id", {}, {
query: {
isArray: true
}
});
});
app.controller("postController", function($scope, Feed, $stateParams) {
var feed = Feed.query();
feed.$promise.then(function(promiseData) {
postArray = promiseData.slice(0,promiseData.length);
$scope.feed = promiseData;
$scope.id = $stateParams.id;
});
});
index.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<!-- CDN -->
<!-- Angular, Bootstrap, Angular modules, etc. -->
<!-- Styles -->
<!-- Angular Script import -->
<script type="text/javascript" src="ng-app.js"></script>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<nav><!--Bootstrap nav--></nav>
<div ui-view></div>
<footer></footer>
<script>
//For Bootstrap tooltips which are in some of the partials.
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
$('[rel=tooltip]').tooltip();
});
</script>
</body>
</html>
/partials/post.html
<div class="container-fluid main-content">
<header class="banner" class="row">
<h1 class="page-title">{{id}}</h1>
</header>
<!-- Main page info -->
</div>
I think you have relative paths pointing to your css files.
When you load page from /feed/9 then links are invalid.
Maybe it happens also for templates referenced from angular.
I have a Polymer web app which I've generated with Yeoman. The below fragment shows how pages are defined in the index.html:
<iron-pages attr-for-selected="data-route" selected="{{route}}">
<section data-route="home">
<paper-material elevation="1">
<my-greeting></my-greeting>
</paper-material>
</section>
<section data-route="users">
<paper-material elevation="1">
<h2 class="paper-font-display2">Users</h2>
<p>This is the users section</p>
Rob
</paper-material>
</section>
So basically you define a section for each page.
Is it possible to define these sections as references to some external files? I would not want to add everything in the main index.html as it will get messy pretty soon.
It is possible but not in HTML unless you use ajax/ iFrames(No) to load the content and that would get ugly and would stop crawlers from properly crawling all your pages.
You would need to convert your pages to Java, Python, PHP, .net frameworks.
Since you sound new to this, I would just advise you to use WordPress.
There are many template there and it is easy to create and customise in the future.
Typically client side routing is used in single page applications, where the server-side code is primarily used to provide a RESTful API the client-side code uses via Ajax.
Then you would be look at using Angluar.js
Update:
Here's a cleaner version to do this:
<dom-module id="dyn-import">
<template>
<div id="container">
<link rel="import" href="{{href}}"/>
</div>
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'dyn-import',
properties: {
href: {
type: String,
notify: true
}
},
attached: function() {
var container = this.$.container;
var link = Polymer.dom(container).querySelector('link[rel="import"]');
link.addEventListener('load', function() {
var importedDoc = link.import;
var importedNode = document.importNode(importedDoc.firstChild, true);
container.innerHTML = importedNode.innerHTML;
});
}
});
})();
The imported HTML can contain Polymer elements, that's working fine. The whole thing doesn't seem to be working on Safari. Also, I still seem to have some issues around relative URLS (say the imported HTML fragment references another Polymer element using HTML import - it does seem to work, but there's an error on the console and the URL in the error seems to have been constructed like this: web appreciation host + relative url which is clearly wrong).
I have finally solved this by writing a Polymer component (well, after all it's a Polymer application :)
<dom-module id="dyn-import">
<template>
<div id="{{id}}">
<link rel="import" href="{{href}}"/>
</div>
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'dyn-import',
properties: {
href: {
type: String,
notify: true
},
id: {
type: String,
notify: true
}
},
ready: function() {
this.id = "ID_" + new Date().getTime();
},
attached: function() {
var sel = d3.select('#'+this.id);
var link = sel.select('link[rel="import"]');
link[0][0].addEventListener('load', function() {
var importedDoc = link[0][0].import;
var importedNode = document.importNode(importedDoc.firstChild, true);
sel.html(importedNode.innerHTML);
});
}
});
})();
</script>
This can then be used in the index.html as follows:
<section data-route="contact">
<paper-material elevation="1">
<dyn-import href="elements/contacts.html"></dyn-import>
</paper-material>
</section>
I have one further concern, is it a search engine friendly solution?
I've created a test polymer element where in I was figuring out how to use use arrays in templates. My code does not work and the documentation for 1.0 doesn't really talk much about how to use repeat in template tags.
my element:
<!-- Imports polymer -->
<link rel="import" href="polymer/polymer.html">
<!-- Defines element markup -->
<dom-module id="my-element" >
<template>
<style>
my-element
</style>
<h2>{{data}}</h2>
<ul>
<template repeat={{column in columns}} bind>
<li>{{column}}</li>
</template>
</ul>
</template>
</dom-module>
<!-- Registers custom element -->
<script>
Polymer({
is: 'my-element',
// Fires when an instance of the element is created
created: function() {
},
// Fires when the local DOM has been fully prepared
ready: function() {},
// Fires when the element was inserted into the document
attached: function() {},
// Fires when the element was removed from the document
detached: function() {},
// Fires when an attribute was added, removed, or updated
attributeChanged: function(name, type) {
alert("changed");
},
properties:{
data :String,
columns:Array
}
});
</script>
and the index.html page where I'm using the element:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title><my-repo></title>
<!-- Imports polyfill -->
<script src="webcomponents-lite.min.js"></script>
<!-- Imports
custom element -->
<link rel="import" href="my-element.html">
<!-- Runs custom element -->
<my-element users = '{{[1,2,3,4]}}' data="This is a polymer table"></my-element>
Please let me know what's wrong with my code!!
You have to use
<template is="dom-repeat" items="{{users}}">
<li>{{item}}</li>
</template>
And in main file:
<my-element users="[1,2,3,4]" data="This is a polymer table"></my-element>
You can search Youtube for Polycast, a series by Google Developers where they're talking about Polymer for beginners and showing cool tricks.
Polymer 1.0 does not allow expressions in data binding. The problem is in:
<my-element users = '{{[1,2,3,4]}}' ...>
You need to replace {{[1,2,3,4]}} with a property. Something like this:
<template is="dom-bind">
<my-element users = '{{myarray}}' data="This is a polymer table"></my-element>
</template>
<script>
(function() {
var template = document.querySelector('template[is="dom-bind"]');
template.myarray = [1,2,3,4];
})();
</script>