polymer 1.0 .. importing a catalog element with a custom element throws an error - polymer-1.0

Had created and used my custom polymer element which is a table. Now, I want to use the check box element from their catalog in my table.
However, I keep getting this error when I reference the check box html file in my index page:
DuplicateDefinitionError: a type with name 'dom-module' is already
registered
This is how I have created my custom element:
<!-- Imports polymer -->
<link rel="import" href="polymer/polymer.html">
<script src="underscore-min.js"></script>
<!-- Defines element markup -->
<dom-module id="custom-table" >
<template>
<style>
ul {list-style-type:none; display:block}
ul li {display:inline; float:left; padding:20px; width:1.5em; border-bottom:1px solid #eee}
</style>
<h2>{{title}}</h2>
<table id="dataTable">
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</template>
</dom-module>
<!-- Registers custom element -->
<script>
Polymer({
is: 'custom-table',
// Fires when an instance of the element is created
created: function() {
},
// Fires when the local DOM has been fully prepared
ready: function() {
var context= this;
this.pageNo=0;
this.totalPages=0;
// set the default paging size:
if(this.page== null|| this.page==undefined)
this.page=10;
// delegate the change selection handler to the table body
this.$.tableBody.addEventListener("click",function(e){
if(e.target && e.target.nodeName == "INPUT") ;
{
context.changeSelection(e.target);
}
});
},
// 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");
},
loadData: function(columns,data){
this.data = data;
// add the selected property to the values
for(var i=0;i<this.data.length; i++) { this.data[i].Selected = false;}
this.filteredData=this.data;
this.columns = columns;
//initialize the filteredData
this.filteredData=data;
// calculate the total number of pages
this.totalPages= Math.ceil(data.length/this.page);
this.drawTableHeader();
_.defer(this.applyFilters,this);
_.defer(this.drawTableBody,this);
},
drawTableHeader:function(){
var columns = this.columns;
// load the header
var headTr = document.createElement('tr');
//add a blank header for the check box;
var th=document.createElement('th');
headTr.appendChild(th);
for(var i = 0; i<columns.length ;i++)
{
var td=document.createElement('th');
// if the column is sortable then add the event listener for sorting it
if(columns[i].Sortable)
{
td.addEventListener("click",function(){ this.sortBy(columns[i].Title); });
}
td.innerText = columns[i].Title;
headTr.appendChild(td);
}
this.$.tableHead.appendChild(headTr);
},
drawTableBody: function(context){
// this is a defered function
var context = context;
// get the number of items according to the current page number
var pageItems= context.filteredData.slice((context.page*context.pageNo),((context.page*context.pageNo)+context.page));
console.log(pageItems);
// print the page items
for(var i=0; i < pageItems.length; i++)
{
var currItem = pageItems[i];
var tr= document.createElement("tr");
// add the check box first
var checkbox= document.createElement("input");
checkbox.type="checkbox";
checkbox.checked=pageItems[i].Selected;
var ItemId = currItem.Id;
checkbox.setAttribute("data-ItemId",ItemId-1);
var td=document.createElement('td');
td.appendChild(checkbox);
tr.appendChild(td);
// for every column specified add a column to it
for(var j = 0; j< context.columns.length; j++)
{
var td=document.createElement("td");
td.innerText= pageItems[i][context.columns[j].Title];
tr.appendChild(td);
}
//append the row to the table;
context.$.tableBody.appendChild(tr);
} // end for i
},
applyFilters:function(context){
if(context.filter)
{
alert("filterApplied");
}
},
changeSelection:function(checkbox){
var ItemId = checkbox.getAttribute("data-ItemId");
this.data[ItemId].Selected= checkbox.checked;
console.log(this.data[ItemId]);
},
properties:{
title :String,
columns:Array,
data:Array,
page:Number,
filters:Object,
Selectable:Boolean
}
});
</script>
and here is what my index page looks like:
<!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">
<link rel="import" href="bower_components/paper-checkbox/paper-checkbox.html">
</head>
<body unresolved>
<!-- Runs custom element -->
<custom-table title="This is data table"></custom-table>
<script>
document.addEventListener("WebComponentsReady",function(){
var data = [{'Id':1,'firstName':'aman',age:25},{'Id':2,'firstName':'gupta',age:25}];
var cols = [{Title:'firstName',Sortable:true},{Title:'age',Sortable:false}];
var a = document.querySelector('my-element');
a.loadData(cols,data);
});
</script>
</body>
</html>
I've just started out with polymer and I'm not quite sure what's going on here..
Thank you in advance :)

I got what the problem is..
My custom element was referencing a different Polymer.html file.
Silly me :D

I'm using Polymer Starter Kit Yeoman generator on Windows and I had the same problem:
Error: DuplicateDefinitionError: a type with name 'dom-module' is already registered
This error is triggered in Firefox console. Chrome works fine.
The components created with the generator (example: yo polymer:el my-element) have this polymer.html import:
<link rel="import" href="..\..\bower_components/polymer/polymer.html">
The base path is described with "backslash".
In some custom polymer elements I created by myself, I imported polymer.html with:
<link rel="import" href="../../bower_components/polymer/polymer.html">
And I think this lead to a duplication of some kind. To solve the problem, I just changed all automatically created imports, using only forward slashes /.
Hope this helps someone.

Related

HERE maps - how to get all visible clusters?

I want to get array of currently visible clusters and then get each point data. I create method in React and it seems that method in theme for getClusterPresentation returns all possible clusters for all map zooms. How to get clusters data? This is my code:
const dataPoints = points.map(
point => new H.clustering.DataPoint(point.lat, point.lng, undefined, point),
);
const clusteredDataProvider = new H.clustering.Provider(dataPoints, {
clusteringOptions: {
eps: 32,
minWeight: 2,
},
});
const defaultTheme = clusteredDataProvider.getTheme();
clusteredDataProvider.setTheme({
getClusterPresentation: cluster => {
const clusterMarker = defaultTheme.getClusterPresentation(cluster);
return clusterMarker;
},
getNoisePresentation: noisePoint => {},
});
const layer = new H.map.layer.ObjectLayer(clusteredDataProvider);
map.addLayer(layer);
Please check the below code related to Marker Clustering. And you check same example in our Guide
/**
* Display clustered markers on a map
*
* Note that the maps clustering module https://js.api.here.com/v3/3.1/mapsjs-clustering.js
* must be loaded to use the Clustering
* #param {H.Map} map A HERE Map instance within the application
* #param {Object[]} data Raw data that contains airports' coordinates
*/
function startClustering(map, data) {
// First we need to create an array of DataPoint objects,
// for the ClusterProvider
var dataPoints = data.map(function (item) {
return new H.clustering.DataPoint(item.latitude, item.longitude);
});
// Create a clustering provider with custom options for clusterizing the input
var clusteredDataProvider = new H.clustering.Provider(dataPoints, {
clusteringOptions: {
// Maximum radius of the neighbourhood
eps: 32,
// minimum weight of points required to form a cluster
minWeight: 2
}
});
// Create a layer tha will consume objects from our clustering provider
var clusteringLayer = new H.map.layer.ObjectLayer(clusteredDataProvider);
// To make objects from clustering provder visible,
// we need to add our layer to the map
map.addLayer(clusteringLayer);
}
/**
* Boilerplate map initialization code starts below:
*/
// Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
apikey: window.apikey
});
var defaultLayers = platform.createDefaultLayers();
// Step 2: initialize a map
var map = new H.Map(document.getElementById('map'), defaultLayers.vector.normal.map, {
center: new H.geo.Point(30.789, 33.790),
zoom: 2,
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', () => map.getViewPort().resize());
// Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Step 4: create the default UI component, for displaying bubbles
var ui = H.ui.UI.createDefault(map, defaultLayers);
// Step 5: cluster data about airports's coordinates
// airports variable was injected at the page load
startClustering(map, airports);
#map {
width: 95%;
height: 450px;
background: grey;
}
#panel {
width: 100%;
height: 400px;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Marker Clustering</title>
<link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" />
<link rel="stylesheet" type="text/css" href="demo.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
<link rel="stylesheet" type="text/css" href="../template.css" />
<script type="text/javascript" src='../test-credentials.js'></script>
<script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>
<script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script>
<script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"></script>
<script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script>
<script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-clustering.js"></script>
<script type="text/javascript" src="./data/airports.js"></script>
</head>
<body id="markers-on-the-map">
<div class="page-header">
<h1>Marker Clustering</h1>
<p>Cluster multiple markers together to better visualize the data</p>
</div>
<p>This example displays a map showing the distribution of
airports across the world. The locations were obtained by using
the OpenFlights Airport Database.
Instead of adding a marker for each location, the data has been clustered,
and individual airports are only shown at higher zoom levels.</p>
<div id="map"></div>
<h3>Code</h3>
<p>Marker clustering requires the presence of the <code>mapsjs-clustering</code> module of the API.
The <code>H.clustering.Provider</code> class is used to load in data points and prepare them for clustering.
The result is added to the map as an additional layer using the <code>map.addLayer()</code> method.</p>
<script type="text/javascript" src='demo.js'></script>
</body>
</html>

Superimposed Pins on Bing Maps - How to display metadata of both or move pins apart at a certain zoom

I am generating an HTML/Javascript file from an Access365 database which plots pins on a Bing Map. Each of the pins has associated metadata about the location it is pinning which is viewed by clicking the pin. However, there can be pins which are at exactly the same location (lat and long) and only the metadata for the top pin is available.
How do I either show the combined metadata or have the pins move apart a bit say when the the user mouses over them? Does anyone know how to do that? I've gone round in circles looking at the MS documentation and can't find anything to help.
P.S. There is also a clusterLayer. If that needs removing to solve the problem that's Ok but it would be better if it could stay.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<!-- Reference to the Bing Maps SDK -->
<script type='text/javascript'
src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=[zzz]'
async defer></script>
<script type='text/javascript'>
function GetMap()
{
var map = new Microsoft.Maps.Map('#myMap', {center: new Microsoft.Maps.Location(53.50632, -7.2714), zoom:8});
var ourBlue = 'rgb(108, 162, 212)';
//Create an infobox at the center of the map but don't show it.
infobox = new Microsoft.Maps.Infobox(map.getCenter(), {
visible: false
});
//Assign the infobox to a map instance.
infobox.setMap(map);
var theLocations = [3];
var thePins = [3];
theLocations[0] = new Microsoft.Maps.Location(53.41, -7.1);
theLocations[1] = new Microsoft.Maps.Location(53.42, -7.1);
theLocations[2] = new Microsoft.Maps.Location(53.43, -7.1);
for (var i = 0; i < theLocations.length; i++){
var pin = new Microsoft.Maps.Pushpin(theLocations[i]);
pin.metadata = {
title: 'Pin ' + i, description: 'Description for pin' + i
};
Microsoft.Maps.Events.addHandler(pin, 'click', pushpinClicked);
Microsoft.Maps.Events.addHandler(pin, 'mouseover', splitOverlap);
Microsoft.Maps.Events.addHandler(pin, 'mouseout', function (e) {
e.target.setOptions({ color:'purple' });
});
thePins[i] = pin; //add pin to array of pins
}
Microsoft.Maps.loadModule("Microsoft.Maps.Clustering", function(){
clusterLayer = new Microsoft.Maps.ClusterLayer(thePins);
map.layers.insert(clusterLayer);
});
}
function splitOverlap(e) {
var ourBlue = 'rgb(108, 162, 212)';
e.target.setOptions({color:ourBlue});
}
}
function pushpinClicked(e) {
//Make sure the infobox has metadata to display.
if (e.target.metadata) {
//Set the infobox options with the metadata of the pushpin.
infobox.setOptions({
location: e.target.getLocation(),
title: e.target.metadata.title,
description: e.target.metadata.description,
visible: true
});
}
}
</script>
<script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=Arvr3LDJsmNB-2OGHl_egpbP9RbwsYKGKrktnPBC06G38T9q3CzsfmwK6GNoW7R_' async defer></script>
</head>
<body>
<div id="myMap" style="position:relative;width:600px;height:400px;"></div>
</body>
</html>
When you have clusters and you want to see individual metadate for items within the cluster there are two common approaches:
Have a popup that shows the first location metadata and buttons to step/page/tab through each item in the cluster.
Use the spider cluster visualization: https://bingmapsv8samples.azurewebsites.net/#Clustering_SpiderClusters

Bing Map V8 Cluster Pass real time data

I am just starting with Bing map. Gone through few examples in official documentation.
Example from Bing map V8 official documentation
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<script type='text/javascript'
src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap' async defer></script>
<script type="text/javascript">
var map, clusterLayer;
function GetMap() {
map = new Microsoft.Maps.Map('#myMap',{
credentials: 'Your Bing Maps Key',
zoom: 3
});
Microsoft.Maps.loadModule("Microsoft.Maps.Clustering", function () {
//Generate 3000 random pushpins in the map view.
var pins = Microsoft.Maps.TestDataGenerator.getPushpins(3000, map.getBounds());
//Create a ClusterLayer with options and add it to the map.
clusterLayer = new Microsoft.Maps.ClusterLayer(pins, {
clusteredPinCallback: customizeClusteredPin
});
map.layers.insert(clusterLayer);
});
}
function customizeClusteredPin(cluster) {
//Add click event to clustered pushpin
Microsoft.Maps.Events.addHandler(cluster, 'click', clusterClicked);
}
function clusterClicked(e) {
if (e.target.containedPushpins) {
var locs = [];
for (var i = 0, len = e.target.containedPushpins.length; i < len; i++) {
//Get the location of each pushpin.
locs.push(e.target.containedPushpins[i].getLocation());
}
//Create a bounding box for the pushpins.
var bounds = Microsoft.Maps.LocationRect.fromLocations(locs);
//Zoom into the bounding box of the cluster.
//Add a padding to compensate for the pixel area of the pushpins.
map.setView({ bounds: bounds, padding: 100 });
}
}
</script>
</head>
<body>
<div id="myMap" style="position:relative;width:600px;height:400px;"></div>
</body>
</html>
In above bing map cluster example how to replace the TestDataGenerator data with realtime data JSON like below
mapData = [{"Name":"Point: 0","Latitude":22.0827,"Longitude":80.2707},
{"Name":"Point: 1","Latitude":24.0827,"Longitude":80.2707},
{"Name":"Point: 2","Latitude":26.0827,"Longitude":80.2707},
{"Name":"Point: 3","Latitude":28.0827,"Longitude":80.2707},
{"Name":"Point: 4","Latitude":20.0827,"Longitude":80.2707},
{"Name":"Point: 5","Latitude":22.0827,"Longitude":82.2707},
{"Name":"Point: 6","Latitude":30.0827,"Longitude":80.2707},
{"Name":"Point: 7","Latitude":22.0827,"Longitude":84.2707},
{"Name":"Point: 8","Latitude":32.0827,"Longitude":84.2707},
{"Name":"Point: 9","Latitude":18.0827,"Longitude":80.2707}];
When I pass above object in ClusterLayer I am getting following error
Uncaught TypeError: i[t].getLocation is not a function(…)
You have to loop through your data and turn it into pushpins. Here's a code sample:
var pins = [];
for(var i = 0;i < mapData.length;i++){
var pin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(mapData[i].Latitude, mapData[i].Longitude));
//Store the original data object in the pushpins metadata so that you can access other properties like Name.
pin.metedata = mapData[i];
pins.push(pin);
}
//Now "pins" is an array of pushpins. Add them to the map or to the clustering layer.

Render simple array using Mustache.Js

Having a array like below
var arrNames = ["Stackoverflow","StackExchange","Webmaster","Programmers"];
how should a template look for working with mustache.js javascript template. I tried below but no clues
{{#}}{{key}}{{/}}
From the documentation:
When looping over an array of strings, a . can be used to refer to the current item in the list.
Template:
{{#musketeers}}
* {{.}}
{{/musketeers}}
View:
{
"musketeers": ["Athos", "Aramis", "Porthos", "D'Artagnan"]
}
Output:
Athos
Aramis
Porthos
D'Artagnan
var tpl = document.getElementById('simple').innerHTML,
view = {
items: ['Stackoverflow', 'StackExchange', 'Webmaster', 'Programmers']
};
document.getElementById('output').innerHTML = Mustache.to_html(tpl, view);
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.js"></script>
<script type="template" id="simple">
<h1>Array Values</h1>
<ul>
{{#items}}
<li>{{.}}</li>
{{/items}}
</ul>
</script>
<div id="output"></div>

jQuery mobile calendar with 3-state day colours

I am looking at creating an event and reservation system.
I found the Stack Overflow question jQuery - Mobile date picker control which shows jquery-mobile-datebox and jQuery-Mobile-Themed-DatePicker.
I want to display a calendar where certain dates I get from the server are
available
not available
reserved
When a reserved or available date is touched, I want to show times - there can be more than one time per day. The user can then click on a time to reserve it which would hit off an Ajax request.
jQuery UI datepicker, for example, has
onSelect: function(date, inst) {
From what I can see in the above pickers, what I need is not readily available. Before I start hacking them myself:
Which one would lend itself best to what I want?
Are there perhaps better ones out there that already serve my needs?
UPDATE:
Firebug gave me
<div class="ui-datebox-griddate ui-corner-all ui-btn-up-e" data-date="25" data-theme="e">25</div>
where ui-btn-up-e can be changed from a - e.
Now I need to find out if data-theme also needs to be changed
$('.ui-datebox-griddate').click(function () {
alert($(this).attr("class"));
}
What is the nicest way to toggle through three of the classes and save the state each time?
$('.ui-datebox-griddate').toggle(
function () {
$(this).????? // change ui-btn-up-? to ui-btn-up-a
$.get(...)
},
function () {
$(this).????? // change ui-btn-up-a to ui-btn-up-b
$.get(...)
},
function () {
$(this).????? // change ui-btn-up-b to ui-btn-up-c
$.get(...)
}
);
UPDATE: NOTE: When I click, the calendar change the date, reloading the calendar completely. Perhaps I need to stop that :(
What is the nicest way to toggle through three of the classes and save the state each time?
Something like:
$('.ui-datebox-griddate').click(function (e) {
var $this = $(this);
var cycle = ["ui-btn-up-a", "ui-btn-up-b", "ui-btn-up-c"];
if (typeof $this.data("ui-btn-cycle") == "undefined" ) {
this.className = this.className.replace(/ui-btn-up-./, cycle[0]);
$this.data("ui-btn-cycle", cycle[0]);
}
for (var i=0; i<cycle.length; i++) {
if ( $this.hasClass(cycle[i]) ) {
$this.removeClass(cycle[i]).addClass(cycle[i % cycle.length]);
$this.data("ui-btn-cycle", [i % cycle.length]);
break;
}
}
$.get( ... );
e.preventDefault() // stop default click behaviour
});
This can cycle though an arbitrary amount of classes. The current state would be available through calling .data("ui-btn-cycle") on the respective element.
This is even nicer:
$('.ui-datebox-griddate')
.each(function () {
var cycle = ["ui-btn-up-a", "ui-btn-up-b", "ui-btn-up-c"];
$(this).data("ui-btn-cycle", cycle);
this.className = this.className.replace(/ui-btn-up-./, cycle[0]);
})
.click(function (e) {
var cycle = $(this).data("ui-btn-cycle");
$(this).removeClass(cycle[0]).addClass(cycle[1]);
cycle.push(cycle.shift());
e.preventDefault();
});
The current state would always be .data("ui-btn-cycle")[0] on the respective element. See it working here: http://jsfiddle.net/Tomalak/mAH4n/
Based on what J.T.Sage said I thought I would have a play with jQuery Mobile Calendar. I think I have something which could potentially be extended to fulfil your requirements. I am not sure to what extent multi-colour themeing would be possible (without extensive modifications).
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQueryMobile - DateBox Demos</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
<link type="text/css" href="http://dev.jtsage.com/cdn/datebox/latest/jquery.mobile.datebox.min.css" rel="stylesheet" />
<!-- NOTE: Script load order is significant! -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.1.min.js"></script>
<script type="text/javascript">
$( document ).bind( "mobileinit", function(){ $.mobile.page.prototype.options.degradeInputs.date = 'text'; });
</script>
<script type="text/javascript" src="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js"></script>
<script type="text/javascript" src="http://dev.jtsage.com/cdn/datebox/latest/jquery.mobile.datebox.min.js"></script>
<script type="text/javascript">
$('#page').live('pagecreate', function(event) {
$('#mydate').bind('change', function () {
alert($(this).val());
});
});
</script>
</head>
<body>
<div id="page" data-role="page">
<div data-role="content">
<input name="mydate" id="mydate" type="date" data-role="datebox" data-options='{"mode": "calbox", "calHighToday": false, "calHighPicked": false, "useInline": true, "useInlineHideInput": true, "highDates": ["2011-06-25", "2011-06-27", "2011-07-04"]}'></input>
</div>
</div>
</html>
UPDATE
I suppose the highDates mechanism could be bypassed completely and the individual days uniquely targeted. The plugin maintains a JavaScript Date object of the last date selected (or today if nothing has been selected) - so it should be possible to get the current month and iterate through all your matching data updating the matching days in the current month as appropriate (e.g. replacing the setColours method below with something that is data/state aware).
<script type="text/javascript">
$('#page').live('pagecreate', function(event) {
$('#mydate').bind('change', function () {
//alert($(this).val());
alert($('#mydate').data('datebox').theDate);
});
setColours();
$('#mydate').bind('datebox', function (e, pressed) {
setColours();
});
$('.ui-datebox-gridplus, .ui-datebox-gridminus').bind('vclick', function(){
// To handle changing months
setColours();
//alert($('#mydate').data('datebox').theDate);
});
function setColours(){
$('div.ui-datebox-griddate[data-date=25][data-theme]').css({"background-color":"red", "background-image":"none", "color" : "white"});
$('div.ui-datebox-griddate[data-date=26][data-theme]').css({"background-color":"green", "background-image":"none", "color" : "white"});
$('div.ui-datebox-griddate[data-date=27][data-theme]').css({"background-color":"blue", "background-image":"none", "color" : "white"});
}
});
</script>

Resources