Google Maps API simple Debugging - api

I want to use the google maps API to:
-search for an address on a map
-place a marker on a map and drag it
Combining some pieces of code i came up with this (apparently it is not working). Can somebody pleasy debug me?:D
Thanks!
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 50px;
padding: 0px
}
.controls {
margin-top: 16px;
border: 1px solid transparent;
border-radius: 2px 0 0 2px;
box-sizing: border-box;
-moz-box-sizing: border-box;
height: 32px;
outline: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
#pac-input {
background-color: #fff;
padding: 0 11px 0 13px;
width: 400px;
font-family: Roboto;
font-size: 15px;
font-weight: 300;
text-overflow: ellipsis;
}
#pac-input:focus {
border-color: #4d90fe;
margin-left: -1px;
padding-left: 14px; /* Regular padding-left + 1. */
width: 401px;
}
.pac-container {
font-family: Roboto;
}
#type-selector {
color: #fff;
background-color: #4d90fe;
padding: 5px 11px 0px 11px;
}
#type-selector label {
font-family: Roboto;
font-size: 13px;
font-weight: 300;
}
}
</style>
<title>Places search box</title>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places"></script>
<script>
// This example adds a search box to a map, using the Google Place Autocomplete
// feature. People can enter geographical searches. The search box will return a
// pick list containing a mix of places and predicted search terms.
function initialize() {
var markers = [];
var map = new google.maps.Map(document.getElementById('map-canvas'), {
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-33.8902, 151.1759),
new google.maps.LatLng(-33.8474, 151.2631));
map.fitBounds(defaultBounds);
// Create the search box and link it to the UI element.
var input = /** #type {HTMLInputElement} */(
document.getElementById('pac-input'));
map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
var searchBox = new google.maps.places.SearchBox(
/** #type {HTMLInputElement} */(input));
// [START region_getplaces]
// Listen for the event fired when the user selects an item from the
// pick list. Retrieve the matching places for that item.
google.maps.event.addListener(searchBox, 'places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
for (var i = 0, marker; marker = markers[i]; i++) {
marker.setMap(null);
}
// For each place, get the icon, place name, and location.
markers = [];
var bounds = new google.maps.LatLngBounds();
for (var i = 0, place; place = places[i]; i++) {
var image = {
url: place.icon,
size: new google.maps.Size(71, 71),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(17, 34),
scaledSize: new google.maps.Size(25, 25)
};
// Create a marker for each place.
var marker = new google.maps.Marker({
map: map,
icon: image,
title: place.name,
position: place.geometry.location
});
markers.push(marker);
bounds.extend(place.geometry.location);
}
map.fitBounds(bounds);
});
// [END region_getplaces]
// Bias the SearchBox results towards places that are within the bounds of the
// current map's viewport.
google.maps.event.addListener(map, 'bounds_changed', function() {
var bounds = map.getBounds();
searchBox.setBounds(bounds);
});
}
function addMyMarker()
{ //function that will add markers on button click
var marker = new google.maps.Marker({
position:mapCenter,
map: map,
draggable:true,
animation: google.maps.Animation.DROP,
title:"This a new marker!",
icon: "http://maps.google.com/mapfiles/ms/micons/blue.png"
});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
<style>
#target {
width: 345px;
}
</style>
<script type="text/javascript">
function addMyMarker()
{ //function that will add markers on button click
var marker = new google.maps.Marker({
position:mapCenter,
map: map,
draggable:true,
animation: google.maps.Animation.DROP,
title:"This a new marker!",
icon: "http://maps.google.com/mapfiles/ms/micons/blue.png"
});
}
</script>
</head>
<body>
<button id="drop" onClick="addMyMarker()">Drop Markers</button>
<input id="pac-input" class="controls" type="text" placeholder="Search Box">
<div id="map-canvas"></div>
</body>
</html>

First, make the map variable global, so you can access it from any function. Then make sure that you define the mapCenter (globally), like this:
new google.maps.LatLng(-33.8902, 151.1759)
Then make sure that you have only one addMyMarker function (just remove the second one).

Related

How to anchor position popup to left screen in openlayers 6

I have a popup when click to layer on map and popup displayed at selected position. I want to show popup to left screen, how can i do that. Please help me, my english not good. Thanks
My popup show like this
Popup here
I want when click popup will show left screen, not center
If you want the popup on center left, you could do it like this:
<html>
<head>
<meta charset="utf-8" />
<style>
.map {
height: 100%;
width: 100%;
}
html,
body {
height: 100%
}
.ol-popup {
background-color: white;
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
min-width: 280px;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
</style>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.13.0/build/ol.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.13.0/css/ol.css">
</head>
<body>
<div id="popup" class="ol-popup">
<div id="popup-content"></div>
</div>
<div id="map" class="map"></div>
<script type="text/javascript">
const container = document.getElementById('popup');
const content = document.getElementById('popup-content');
document.addEventListener("DOMContentLoaded", function () {
drawMap();
});
function drawMap() {
const osmLayer = new ol.layer.Tile({
source: new ol.source.OSM({
attributions: '© OpenStreetMap',
})
});
map = new ol.Map({
target: 'map',
layers: [
osmLayer,
],
view: new ol.View(),
});
const popup = new ol.Overlay({
element: document.getElementById('popup'),
});
map.addOverlay(popup);
map.getView().fit([0,0,0,0]);
map.on('click', function (evt) {
const element = popup.getElement();
const popupMap = popup.getMap();
const coordinate = evt.coordinate;
const viewExtent = popupMap.getView().calculateExtent();
const centerPoint = ol.extent.getCenter(viewExtent);
content.innerHTML = '<p>You clicked here:</p><code>' + coordinate + '</code>';
popup.setPosition( [viewExtent[0], centerPoint[1]] );
});
}
</script>
</body>
</html>
Remove position absolute and determine the position:
https://jsfiddle.net/ve5mn0by/5/
EDIT:
without Openlayers Overlay, just HTML:
map.on('click', function (evt) {
const container = document.createElement('div');
const content = document.createElement('div');
container.style.width = '10rem';
container.style.height = '100%'
//container.style.backgroundColor = '#FF0000';
container.style.position = 'absolute';
container.style.display = 'flex';
content.style.width = '100%';
content.style.height = '10rem'
content.style.backgroundColor = '#FFFF00';
content.style.alignSelf = 'center';
container.appendChild(content);
document.body.appendChild(container);
});

ArcGIS Api For Javascript Filter Features Combined With Query Features

Iv been having trouble trying to combine the functionality between Filter by attribute and Query Features from a FeatureLayerView. Both samples are below;
https://developers.arcgis.com/javascript/latest/sample-code/featurefilter-attributes/index.html
https://developers.arcgis.com/javascript/latest/sample-code/featurelayerview-query/index.html
The aim is to apply the chosen filter (Filter feature by attribute), and this filter be applied to the Query FeatureLayerView showing in the side panel.
We have currently got both functionality working correctly in one sample, however they are still working in isolation from each other. I have added the code we have below.
Any help would be much appreciated.
<script src="https://js.arcgis.com/4.15/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#seasons-filter {
height: 160px;
width: 160px;
width: 100%;
visibility: hidden;
}
.season-item {
width: 100%;
padding: 12px;
text-align: center;
vertical-align: baseline;
cursor: pointer;
height: 40px;
height: 50px;
}
.season-item:focus {
background-color: dimgrey;
}
.season-item:hover {
background-color: dimgrey;
}
.panel-container {
position: relative;
width: 100%;
height: 100%;
}
.panel-side {
padding: 2px;
box-sizing: border-box;
width: 300px;
height: 100%;
position: absolute;
top: 0;
right: 0;
color: #fff;
background-color: rgba(0, 0, 0, 0.6);
overflow: auto;
z-index: 60;
}
.panel-side h3 {
padding: 0 20px;
margin: 20px 0;
}
.panel-side ul {
list-style: none;
margin: 0;
padding: 0;
}
.panel-side li {
list-style: none;
padding: 10px 20px;
}
.panel-result {
cursor: pointer;
margin: 2px 0;
background-color: rgba(0, 0, 0, 0.3);
}
.panel-result:hover,
.panel-result:focus {
color: orange;
background-color: rgba(0, 0, 0, 0.75);
}
</style>
<script>
require([
"esri/views/MapView",
"esri/Map",
"esri/layers/FeatureLayer",
"esri/widgets/Expand"
], function(MapView, Map, FeatureLayer, Expand) {
let floodLayerView;
let graphics;
const listNode = document.getElementById("nyc_graphics");
const popupTemplate = {
// autocasts as new PopupTemplate()
title: "{NAME} in {COUNTY}",
content: [
{
type: "fields",
fieldInfos: [
{
fieldName: "B12001_calc_pctMarriedE",
label: "% Married",
format: {
places: 0,
digitSeparator: true
}
}
]
}
]
};
// flash flood warnings layer
const layer = new FeatureLayer({
url:
"https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/ACS_Marital_Status_Boundaries/FeatureServer/2",
outFields: ["NAME", "GEOID"], // used by queryFeatures
popupTemplate: popupTemplate
});
const map = new Map({
basemap: "gray-vector",
layers: [layer]
});
const view = new MapView({
map: map,
container: "viewDiv",
center: [-73.95, 40.702],
zoom: 11
});
const seasonsNodes = document.querySelectorAll(`.season-item`);
const seasonsElement = document.getElementById("seasons-filter");
// click event handler for seasons choices
seasonsElement.addEventListener("click", filterBySeason);
// User clicked on Winter, Spring, Summer or Fall
// set an attribute filter on flood warnings layer view
// to display the warnings issued in that season
function filterBySeason(event) {
const selectedSeason = event.target.getAttribute("data-season");
floodLayerView.filter = {
where: "State = '" + selectedSeason + "'"
};
}
view.whenLayerView(layer).then(function(layerView) {
// flash flood warnings layer loaded
// get a reference to the flood warnings layerview
floodLayerView = layerView;
// set up UI items
seasonsElement.style.visibility = "visible";
const seasonsExpand = new Expand({
view: view,
content: seasonsElement,
expandIconClass: "esri-icon-filter",
group: "top-left"
});
//clear the filters when user closes the expand widget
seasonsExpand.watch("expanded", function() {
if (!seasonsExpand.expanded) {
floodLayerView.filter = null;
}
});
view.ui.add(seasonsExpand, "top-left");
});
// Start Of Side Bar Element
view.whenLayerView(layer).then(function(layerView) {
layerView.watch("updating", function(value) {
if (!value) {
// wait for the layer view to finish updating
// query all the features available for drawing.
layerView
.queryFeatures({
geometry: view.extent,
returnGeometry: true,
orderByFields: ["NAME"]
})
.then(function(results) {
graphics = results.features;
const fragment = document.createDocumentFragment();
graphics.forEach(function(result, index) {
const attributes = result.attributes;
const name = attributes.NAME;
// Create a list zip codes in NY
const li = document.createElement("li");
li.classList.add("panel-result");
li.tabIndex = 0;
li.setAttribute("data-result-id", index);
li.textContent = name;
fragment.appendChild(li);
});
// Empty the current list
listNode.innerHTML = "";
listNode.appendChild(fragment);
})
.catch(function(error) {
console.error("query failed: ", error);
});
}
});
});
// listen to click event on the zip code list
listNode.addEventListener("click", onListClickHandler);
function onListClickHandler(event) {
const target = event.target;
const resultId = target.getAttribute("data-result-id");
// get the graphic corresponding to the clicked zip code
const result =
resultId && graphics && graphics[parseInt(resultId, 10)];
if (result) {
// open the popup at the centroid of zip code polygon
// and set the popup's features which will populate popup content and title.
view
.goTo(result.geometry.extent.expand(2))
.then(function() {
view.popup.open({
features: [result],
location: result.geometry.centroid
});
})
.catch(function(error) {
if (error.name != "AbortError") {
console.error(error);
}
});
}
}
});
</script>
What you can do is filter the graphics you obtain with the query with a simple condition. Using both examples of ArcGIS, I put together what I think you are trying to get.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>Filter and Query - 4.15</title>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.15/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.15/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#seasons-filter {
height: 160px;
width: 100%;
visibility: hidden;
}
.season-item {
width: 100%;
padding: 12px;
text-align: center;
vertical-align: baseline;
cursor: pointer;
height: 40px;
}
.season-item:focus {
background-color: dimgrey;
}
.season-item:hover {
background-color: dimgrey;
}
#titleDiv {
padding: 10px;
}
#titleText {
font-size: 20pt;
font-weight: 60;
padding-bottom: 10px;
}
.panel-container {
position: relative;
width: 100%;
height: 100%;
}
.panel-side {
padding: 2px;
box-sizing: border-box;
width: 300px;
height: 100%;
position: absolute;
top: 0;
right: 0;
color: #fff;
background-color: rgba(0, 0, 0, 0.6);
overflow: auto;
z-index: 60;
}
.panel-side h3 {
padding: 0 20px;
margin: 20px 0;
}
.panel-side ul {
list-style: none;
margin: 0;
padding: 0;
}
.panel-side li {
list-style: none;
padding: 10px 20px;
}
.panel-result {
cursor: pointer;
margin: 2px 0;
background-color: rgba(0, 0, 0, 0.3);
}
.panel-result:hover,
.panel-result:focus {
color: orange;
background-color: rgba(0, 0, 0, 0.75);
}
</style>
<script>
require([
"esri/views/MapView",
"esri/Map",
"esri/layers/FeatureLayer",
"esri/widgets/Expand"
], function(MapView, Map, FeatureLayer, Expand) {
const listNode = document.getElementById("list_graphics");
const seasonsNodes = document.querySelectorAll(`.season-item`);
const seasonsElement = document.getElementById("seasons-filter");
let layer, map, view;
let selectedSeason = null;
let floodLayerView;
let graphics = null;
// functions
const filterBySeason = function (event) {
selectedSeason = event.target.getAttribute("data-season");
floodLayerView.filter = {
where: "Season = '" + selectedSeason + "'"
};
updateList();
};
const updateList = function () {
if (!graphics) {
return;
}
const fragment = document.createDocumentFragment();
graphics.forEach(function(result, index) {
const attributes = result.attributes;
if (!selectedSeason || attributes.SEASON === selectedSeason) {
const name = attributes.IssueDate;
// Create the list
const li = document.createElement("li");
li.classList.add("panel-result");
li.tabIndex = 0;
li.setAttribute("data-result-id", index);
li.textContent = name;
fragment.appendChild(li);
}
});
// Empty the current list
listNode.innerHTML = "";
listNode.appendChild(fragment);
};
// flash flood warnings layer
layer = new FeatureLayer({
portalItem: {
id: "f9e348953b3848ec8b69964d5bceae02"
},
outFields: ["SEASON", "IssueDate"]
});
map = new Map({
basemap: "gray-vector",
layers: [layer]
});
view = new MapView({
map: map,
container: "viewDiv",
center: [-98, 40],
zoom: 10
});
// click event handler for seasons choices
seasonsElement.addEventListener("click", filterBySeason);
view.whenLayerView(layer).then(function(layerView) {
/*
filter
*/
floodLayerView = layerView;
// set up UI items
seasonsElement.style.visibility = "visible";
const seasonsExpand = new Expand({
view: view,
content: seasonsElement,
expandIconClass: "esri-icon-filter",
group: "top-left"
});
//clear the filters when user closes the expand widget
seasonsExpand.watch("expanded", function() {
if (!seasonsExpand.expanded) {
floodLayerView.filter = null;
}
});
view.ui.add(seasonsExpand, "top-left");
view.ui.add("titleDiv", "bottom-left");
/*
query
*/
layerView.watch("updating", function(value) {
if (!value) {
// wait for the layer view to finish updating
// query all the features available for drawing.
layerView
.queryFeatures({
geometry: view.extent,
returnGeometry: true,
orderByFields: ["IssueDate"]
})
.then(function (results) {
graphics = results.features;
updateList();
})
.catch(function(error) {
console.error("query failed: ", error);
});
}
});
});
/*
query
*/
// listen to click event on list items
listNode.addEventListener("click", onListClickHandler);
function onListClickHandler(event) {
const target = event.target;
const resultId = target.getAttribute("data-result-id");
// get the graphic corresponding to the clicked item
const result =
resultId && graphics && graphics[parseInt(resultId, 10)];
if (result) {
// open the popup at the centroid of polygon
// and set the popup's features which will populate popup content and title.
view
.goTo(result.geometry.extent.expand(2))
.then(function() {
view.popup.open({
features: [result],
location: result.geometry.centroid
});
})
.catch(function(error) {
if (error.name != "AbortError") {
console.error(error);
}
});
}
};
});
</script>
</head>
<body>
<div class="panel-container">
<div id="seasons-filter" class="esri-widget">
<div class="season-item visible-season" data-season="Winter">Winter</div>
<div class="season-item visible-season" data-season="Spring">Spring</div>
<div class="season-item visible-season" data-season="Summer">Summer</div>
<div class="season-item visible-season" data-season="Fall">Fall</div>
</div>
<div class="panel-side esri-widget">
<ul id="list_graphics">
<li>Loading…</li>
</ul>
</div>
<div id="viewDiv"></div>
<div id="titleDiv" class="esri-widget">
<div id="titleText">Flash Floods by Season</div>
<div>Flash Flood Warnings (2002 - 2012)</div>
</div>
</div>
</body>
</html>
I just move things a bit so that each time the user filter with the seasons the list of the side panel is updated.
Now, you will see that I do not query on a new season, I just filter the graphics that we already have.
Each time a new query is made, the list is going to filter in the same manner.
Im also trying to add an option within the dropdown list to clear or filters and reset the side panel. I have added an on-click event, which clears the filters, but it doesn't reset the side panel.
Any help would be much appreciated.
require([
"esri/views/MapView",
"esri/Map",
"esri/layers/FeatureLayer",
"esri/widgets/Expand"
], function(MapView, Map, FeatureLayer, Expand) {
const listNode = document.getElementById("list_graphics");
const seasonsNodes = document.querySelectorAll(`.season-item`);
const seasonsElement = document.getElementById("seasons-filter");
let layer, map, view;
let selectedSeason = null;
let floodLayerView;
let graphics = null;
// functions
const filterBySeason = function (event) {
selectedSeason = event.target.getAttribute("data-season");
floodLayerView.filter = {
where: "Season = '" + selectedSeason + "'"
};
document
.getElementById("filterReset")
.addEventListener("click", function() {
floodLayerView.filter = selectedSeason;
});
updateList();
};
const updateList = function () {
if (!graphics) {
return;
}
const fragment = document.createDocumentFragment();
graphics.forEach(function(result, index) {
const attributes = result.attributes;
if (!selectedSeason || attributes.SEASON === selectedSeason) {
const name = attributes.IssueDate;
// Create the list
const li = document.createElement("li");
li.classList.add("panel-result");
li.tabIndex = 0;
li.setAttribute("data-result-id", index);
li.textContent = name;
fragment.appendChild(li);
}
});
// Empty the current list
listNode.innerHTML = "";
listNode.appendChild(fragment);
};
// flash flood warnings layer
layer = new FeatureLayer({
portalItem: {
id: "f9e348953b3848ec8b69964d5bceae02"
},
outFields: ["SEASON", "IssueDate"]
});
map = new Map({
basemap: "gray-vector",
layers: [layer]
});
view = new MapView({
map: map,
container: "viewDiv",
center: [-98, 40],
zoom: 10
});
// click event handler for seasons choices
seasonsElement.addEventListener("click", filterBySeason);
view.whenLayerView(layer).then(function(layerView) {
/*
filter
*/
floodLayerView = layerView;
// set up UI items
seasonsElement.style.visibility = "visible";
const seasonsExpand = new Expand({
view: view,
content: seasonsElement,
expandIconClass: "esri-icon-filter",
group: "top-left"
});
//clear the filters when user closes the expand widget
view.ui.add(seasonsExpand, "top-left");
view.ui.add("titleDiv", "bottom-left");
/*
query
*/
layerView.watch("updating", function(value) {
if (!value) {
// wait for the layer view to finish updating
// query all the features available for drawing.
layerView
.queryFeatures({
geometry: view.extent,
returnGeometry: true,
orderByFields: ["IssueDate"]
})
.then(function (results) {
graphics = results.features;
updateList();
})
.catch(function(error) {
console.error("query failed: ", error);
});
}
});
});
/*
query
*/
// listen to click event on list items
listNode.addEventListener("click", onListClickHandler);
function onListClickHandler(event) {
const target = event.target;
const resultId = target.getAttribute("data-result-id");
// get the graphic corresponding to the clicked item
const result =
resultId && graphics && graphics[parseInt(resultId, 10)];
if (result) {
// open the popup at the centroid of polygon
// and set the popup's features which will populate popup content and title.
view
.goTo(result.geometry.extent.expand(2))
.then(function() {
view.popup.open({
features: [result],
location: result.geometry.centroid
});
})
.catch(function(error) {
if (error.name != "AbortError") {
console.error(error);
}
});
}
};
});
</script>```

iText7 / iText7.pdfhtml - generating accessible tagged PDF from HTML results in incorrect tab order

Here's an HTML snippet:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
body {
font-family: Calibri,Arial,Tahoma,Helvetica,sans-serif;
color: black;
font-size: 12pt;
width: 595pt;
}
h1 {
color: #553c74;
font-size: 18pt;
font-weight: bold;
}
h1.title {
display: inline;
font-size: 28pt;
font-weight: bold;
color: #553c74;
}
h2 {
color: #553c74;
font-size: 16pt;
font-weight: bold;
}
h3 {
color: #00759b;
font-size: 12pt;
font-weight: bold;
}
h3.requiredAction {
color: black;
font-weight: bold;
margin-top: 21px;
}
.headingLeft {
float: left;
}
.contentRight {
float: left;
margin-left: 10px;
margin-top: 17px;
}
.topSection {
margin-top: 22px;
}
.clearBoth {
clear: both;
}
.symbolstandardmet {
color: #009639;
font-family: webdings;
font-size: 18pt;
}
.standardmet {
color: #009639;
}
.standard {
font-size: 16pt;
font-weight: bold;
margin-top: 0;
}
.pageBreakBefore {
margin-top: 0px;
}
.premisesAddress {
font-size: 18pt;
color: #553c74;
line-height: 0px;
}
.pageBreakBefore {
page-break-before: always;
}
#contentWrapperBody {
padding-left: 8pt;
padding-right: 45pt;
}
h1 {
display: inline-block;
}
#page:first {
margin-top: 170pt;
}
</style>
<title>Report</title>
</head>
<body id="contentWrapperBody">
<h1>Name:</h1>
<span id="lblAddress" class="premisesAddress">Address Line 1, Address line 2, City, Postcode</span>
<div class="clearBoth"></div>
<h2 class="headingLeft">Reference:</h2>
<div class="contentRight topSection">123456789</div>
<div class="clearBoth"></div>
<h2 class="headingLeft">Type:</h2>
<div class="contentRight topSection">The type</div>
<div class="clearBoth"></div>
<h2 class="headingLeft">Date:</h2>
<div class="contentRight topSection">12/04/2019</div>
<div class="clearBoth"></div>
<h2>Context</h2>
<div>
<p>A paragraph of context info here.</p>
</div>
<h2>Overall outcome</h2>
<h3>
<span id="lblOverallStandardsMet" class="standardmet"><span class="symbolstandardmet">a</span>Standards met</span>
</h3>
<h3 class="headingLeft requiredAction">Required Action:</h3>
<div class="contentRight topSection"><span id="lblRequiredAction">None</span></div>
<div class="clearBoth"></div>
<p>Follow this link to find out what the possible outcomes mean</p>
</body>
</html>
Here's the code:
public static class DocConverter
{
private const string fontWebdings = "~/content/fonts/webdings.ttf";
private const string fontCalibri = "~/content/fonts/calibrib.ttf";
[Flags]
public enum DocOptions
{
None = 0,
DisplayTitle = 1,
AddHeaderPageOne = 2,
AddHeaderAllPages = 4,
AddLineBottomEachPage = 8
}
public static byte[] ConvertToPdfWithTags(string html, string title, string docOptions)
{
DocOptions documentOptions = DocOptions.None;
if (!string.IsNullOrEmpty(docOptions))
{
int options;
if (int.TryParse(docOptions, out options))
documentOptions = (DocOptions)options;
}
PdfFontFactory.RegisterDirectory(System.Web.Hosting.HostingEnvironment.MapPath("~/content/fonts/"));
ConverterProperties props = new ConverterProperties();
FontProvider fp = new FontProvider();
fp.AddDirectory(System.Web.Hosting.HostingEnvironment.MapPath("~/content/fonts/"));
props.SetFontProvider(fp);
props.SetTagWorkerFactory(new DefaultTagWorkerFactory());
using (var workStream = new MemoryStream())
{
using (var pdfWriter = new PdfWriter(workStream, new WriterProperties().AddUAXmpMetadata().SetPdfVersion
(PdfVersion.PDF_2_0).SetFullCompressionMode(true)))
{
PdfDocument pdfDoc = new PdfDocument(pdfWriter);
pdfDoc.GetCatalog().SetLang(new PdfString("en-GB"));
pdfDoc.GetCatalog().SetViewerPreferences(new PdfViewerPreferences().SetDisplayDocTitle(true));
//This event handler used for adding background images. Also where I've tried setting the tab order on pdfPage
//if (documentOptions > 0)
// pdfDoc.AddEventHandler(PdfDocumentEvent.END_PAGE, new PublicReportHeaderFooter(documentOptions, title));
//Set meta tags
var pdfMetaData = pdfDoc.GetDocumentInfo();
pdfMetaData.AddCreationDate();
pdfMetaData.GetProducer();
pdfMetaData.SetCreator("iText Software");
//Set the document to be tagged
pdfDoc.SetTagged();
using (var document = HtmlConverter.ConvertToDocument(html, pdfDoc, props))
{
//Can do more with document here if necessary
}
//Returns the written-to MemoryStream containing the PDF.
return workStream.ToArray();
}
}
}
}
If I open up the resulting PDF in Acrobat Pro and do an accessibility report and check the reading order, it jumps from the name on the first line, to the context much further down, rather than being in top-bottom, left-right order :
It appears to be due to the "float: left" style on some of the elements, but I'm not sure how to get around it. I've tried playing with page.Put(PdfName.Tag, PdfName.S) and page.SetTabOrder(PdfName.S), putting in different parameters (C or R, for example), but these appear to make no difference. I'm using C# iText7 version 7.1.9 and iText7.pdfhtml version 2.1.6. Any help appreciated.
The more I look at the documentation, the more I think this is just a bug. The reading order in the accessible PDF should match the structural order of the HTML document by default, but the float: left styles seem to throw things out. I can't find a way to raise a bug in GitHub for this project, but I've found a workaround for anyone else with the issue, which is simply to add "float: left" to all other elements as necessary, along with
<div style="clear: both;"></div>
between the elements that don't actually need the float. This way the accessible tagged order comes out correctly. However, this did throw up another problem which was that the elements with "float: left" no longer appeared on a new page where "page-break-before: always" was also set. I had to add another empty div element before these elements with the page-break-before style set to fix this. Hopefully the iText people will see this and either fix these issues, if they're bugs, or respond with a better answer if I've just got things wrong.

I would like to delay something after it's been clicked

I'm creating a multiple choice question program and I have it so that once the correct answer is chosen a new question is generated.
Although I want it to go to the next question automatically, I want it to delay for about 0.5 seconds so the user can see that their answer is correct.
I chose to remove the class and replace it with another class so that the background changes colour. Once the new question comes up I want all the colours to return to normal, so I once again remove the new class and replace it with the old class.
If I don't advance to the new question automatically, the colours come up just the way I like them, but if I create it so that it moves on automatically, I am unable to keep the display the same.
After searching through the forums I read that setTimeout should work, but I haven't had much success. I have also tried doing animations so that it takes time, but that didn't work for me either. The animations worked fine, but it still went on to the new set of questions.
I'll include the whole program as it might be better but the section I'm working on is under the function check().
I've been trying to figure out how to delay something for a long time but have been completely unsuccessful. Oh, please be kind to me, I have very little experience. I have only learned how to do javascript by doing the khan academy course. Thanks!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Project: listening to sounds </title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- ***************** CSS styles ***************** -->
<style>
body {
font-family: comic sans ms, sans-serif;
background-image: url("background.jpg");
background-color: rgb(216, 252, 252);
}
form {
font-size: 1.2em;
}
#text {
background-color: wheat;
width: 150px;
height: 25px;
color: blue;
font-size: 1em;
}
.SoundBite {
float: left;
clear: none;
position: absolute;
top: 130px;
left: 100px;
padding: 5px;
background: darkblue;
color: white;
height: 120px;
width: 200px;
border-radius: 50px;
margin: auto;
text-align: center;
vertical-align: middle;
font-size: 5em;
}
.SoundBite:hover {
background-color: darkgreen;
cursor: pointer;
}
.Score {
float: right;
clear: none;
position: absolute;
top: 50px;
left: 140px;
padding: 5px;
background: darkblue;
border-color: pink;
border: 5px;
opacity: 0.8;
color: white;
height: 45px;
width: 120px;
border-radius: 50px;
margin: auto;
text-align: center;
vertical-align: middle;
font-size: 2em;
pointer-events: none;
}
.Answer {
position: absolute;
color: white;
height: 45px;
width: 120px;
padding: 5px;
border-radius: 25px;
margin: auto;
text-align: center;
vertical-align: middle;
font-size: 2em;
background-color: darkblue;
}
.AnswerCorrect {
position: absolute;
color: white;
height: 45px;
width: 120px;
padding: 5px;
border-radius: 25px;
margin: auto;
text-align: center;
vertical-align: middle;
font-size: 2em;
background-color: green;
}
.Answer:hover {
background-color: #e44404;
cursor: pointer;
}
.AnswerWrong {
position: absolute;
color: white;
height: 45px;
width: 120px;
padding: 5px;
border-radius: 25px;
margin: auto;
text-align: center;
vertical-align: middle;
font-size: 2em;
background-color: red;
}
#Answer1 {
top: 20px;
left: 400px;
}
#Answer2 {
top: 85px;
left: 400px;
}
#Answer3 {
top: 150px;
left: 400px;
}
#Answer4 {
top: 220px;
left: 400px;
}
#Answer5 {
top: 290px;
left: 400px;
}
</style>
</head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<body>
<audio id="audio" src="audio/rug.mp3" autostart="false" ></audio>
<audio id = "win" src="audio/win.mp3" autostart = "true"></audio>
<audio id = "lose" src = "audio/lose.mp3" autstart = "true"></audio>
<a onclick="playSound();"><div span class="SoundBite"><i class="fa fa-file-sound-o" id="audio" src="audio/rug.mp3" autostart="false" style="font-size:64px;color:skyblue"></i></div></a>
<a onclick ="checkAnswer = 1; check();"><div class = "Answer" id="Answer1">1</div>
<a onclick ="checkAnswer = 2; check();"><div class = "Answer" id="Answer2">2</div>
<a onclick ="checkAnswer = 3; check();"><div class = "Answer" id="Answer3">3</div>
<a onclick ="checkAnswer = 4; check();"><div class = "Answer" id="Answer4">4</div>
<a onclick ="checkAnswer = 5; check();"><div class = "Answer" id="Answer5">5</div>
<div class = "Score">Score</div>
<!-- <a onclick ="next();"><div id = "Next">Start</div> -->
<!-- ********************************* Javascript programming follows ********************************* -->
<script>
$(function() {
});
// declare the variables to be used ... do I need global variables? Maybe I should think about these...
var word, ext, directory, wordPosition, decoyWordPosition, answerPosition, decoyAnswerPosition, answer, checkAnswer, correct, incorrect, tries;
directory = "audio/";
ext = ".mp3";
correct = 0;
incorrect = 0;
tries = 0;
// list of words that are spoken
word = ["dam", "dog", "dug", "cat", "cot", "cut", "ran", "rot", "rug"];
wordPosition = Math.floor(Math.random()*word.length); // returns a random array wordPosition
/***************************************************************************
** Functions:
**
**************************************************************************/
// function to display word
function displayWord() {
//$(".SoundBite").text(word[wordPosition]);
//$("#audio").attr("src", directory+word[wordPosition]+ext);
}
// function to display the answer in one of the positions that are assigned
function displayAnswer(answerNumber, wordNumber) {
$("#Answer"+answerNumber).text(word[wordNumber]);
$("#audio").attr("src", directory+word[wordPosition]+ext);
}
// function to play the sound
function playSound() {
var sound = document.getElementById("audio");
sound.play();
}
// function to play a winning sound
function win() {
var sound = document.getElementById("win");
sound.play();
}
// function to play a losing sound
function lose() {
var sound = document.getElementById("lose");
sound.play();
}
function timer() {
new Date().toLocaleTimeString();
}
function check() {
if (answer == checkAnswer) {
//$("#Answer"+checkAnswer).animate({height: "45px", opacity: '0'})
//.animate({height: "45px", opacity: '1.0'});
$("#Answer"+checkAnswer).animate({height: "45px", opacity: '0'})
.animate({height: "45px", opacity: '1.0'})
.removeClass("Answer").addClass("AnswerCorrect");
win();
correct++;
tries++;
refresh();
next();
// need a delay function -- can't get it to work.
} else {
$("#Answer"+checkAnswer).removeClass("Answer").addClass("AnswerWrong"); //css({'background-color': 'red'});
lose();
incorrect++;
tries++;
}
// write in the score
$(".Score").text(Math.round(correct/tries*100)+"%");
}
// making a function to populate the answers
function populateAnswers() {
for (var i = 0; i < 6; i++) {
if (answerPosition < 6) {
if (decoyWordPosition == wordPosition) {
answer=answerPosition
}
displayAnswer(answerPosition, decoyWordPosition);
answerPosition++;
decoyWordPosition++;
if (decoyWordPosition >= word.length) { // want to make sure that the words are within the array
decoyWordPosition = 1 // reset to the beginning to 'wrap' the array.
}
} else {
answerPosition = 1;
}
}
}
// using a random generator to place the answer in a random spot 1 through 4
function randomGenerator() {
answerPosition = Math.floor(Math.random()*4)+1;
}
// returns a random array wordPosition
function randomWord() {
wordPosition = Math.floor(Math.random()*word.length);
}
function refresh() {
for (var i = 1; i <= 5; i++) {
$("#Answer"+i).removeClass("AnswerWrong").addClass("Answer");
$("#Answer"+i).removeClass("AnswerCorrect").addClass("Answer");
}
}
/***************************************************************************
* program as a function *
**************************************************************************/
function next() {
$("#Next").text("Continue");
randomWord();
randomGenerator();
// make the decoy answers randomly
if (wordPosition == 0 || wordPosition == 1) {
// In case the array is at the beginning: make the decoy start at the same spot as the wordPosition
decoyWordPosition = wordPosition;
} else {
// start the decoy word after the word
decoyWordPosition = wordPosition -2;
}
populateAnswers();
}
next();
</script>
</body>
</html>
Just for other people who are trying to find the solution to the setTimeout feature.
I was using it like this:
setTimeout(myFunction(), 2000);
However, it won't work with the brackets after the function. You need to omit those brackets:
setTimeout(myFunction, 2000);

how to access parent component scope from a child components scope in ember?

I'm curious if this is even possible in ember. This is an easy thing to do in angular ( plunkr: http://plnkr.co/edit/O2e0ukyXdKMs4FcgKGmX?p=preview ):
The goal is to make an easy to use, generic, reusable accordion api for api consumers.
The api I want the caller to be able to use is this (just like the angular api):
{{#ember-accordion listOfAccordionPaneObjects=model}}
{{#ember-accordion-heading}}
heading template html {{accordionPaneObject.firstName}}
{{/ember-accordion-heading}}
{{#ember-accordion-body}}
this is the accordion body {{accordionPaneObject.lastName}}
{{/ember-accordion-body}}
{{/ember-accordion}}
Here is a working example I wrote using angular:
<!doctype html>
<html ng-app="angular-accordion">
<head>
<style>
.angular-accordion-header {
background-color: #999;
color: #ffffff;
padding: 10px;
margin: 0;
line-height: 14px;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: pointer;
text-decoration: none;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}
.angular-accordion-container {
height: 100%;
width: 100%;
}
.angular-accordion-pane {
padding: 2px;
}
.angularaccordionheaderselected {
background-color: #bbb;
color: #333;
font-weight: bold;
}
.angular-accordion-header:hover {
text-decoration: underline !important;
}
.angularaccordionheaderselected:hover {
text-decoration: underline !important;
}
.angular-accordion-pane-content {
padding: 5px;
overflow-y: auto;
border-left: 1px solid #bbb;
border-right: 1px solid #bbb;
border-bottom: 1px solid #bbb;
-webkit-border-bottom-left-radius: 5px;
-webkit-border-bottom-right-radius: 5px;
-moz-border-radius-bottomleft: 5px;
-moz-border-radius-bottomright: 5px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.angulardisabledpane {
opacity: .2;
}
</style>
</head>
<body style="margin: 0;">
<div style="height: 90%; width: 100%; margin: 0;" ng-controller="outerController">
<angular-accordion list-of-accordion-pane-objects="outerControllerData">
<pane>
<pane-header>Header {{accordionPaneObject}}</pane-header>
<pane-content>Content {{accordionPaneObject}}</pane-content>
</pane>
</angular-accordion>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script>
<script>
angular.module('angular-accordion', [])
.directive('angularAccordion', function() {
var template = '';
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div>' +
'<div ng-transclude class="angular-accordion-container" ng-repeat="accordionPaneObject in listOfAccordionPaneObjects"></div>' +
'</div>',
controller: ['$scope', function($scope) {
var panes = [];
this.addPane = function(pane) {
panes.push(pane);
};
}],
scope: {
listOfAccordionPaneObjects: '='
}
};
})
.directive('pane', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-transclude class="angular-accordion-pane"></div>'
};
})
.directive('paneHeader', function() {
return {
restrict: 'E',
require: '^angularAccordion',
transclude: true,
replace: true,
link: function(scope, iElement, iAttrs, controller) {
controller.addPane(scope);
scope.toggle = function() {
scope.expanded = !scope.expanded;
};
},
template: '<div ng-transclude class="angular-accordion-header" ng-click="toggle()"></div>'
};
})
.directive('paneContent', function() {
return {
restrict: 'EA',
require: '^paneHeader',
transclude: true,
replace: true,
template: '<div ng-transclude class="angular-accordion-pane-content" ng-show="expanded"></div>'
};
})
.controller('outerController', ['$scope', function($scope) {
$scope.outerControllerData = [1, 2, 3];
}]);
</script>
</body>
</html>
here's where I'm stuck doing the same with ember:
index.html
<!DOCTYPE html>
<html>
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.9/require.js" data-main="main.js"></script>
</body>
</html>
main.js
require.config({
paths: {
'ember': 'bower_components/ember/ember',
'handlebars': 'bower_components/handlebars/handlebars',
'jquery': 'bower_components/jquery/jquery',
'text': 'bower_components/requirejs-text/text'
},
shim: {
ember: {
deps: ['jquery', 'handlebars'],
exports: 'Ember'
}
}
});
define(function(require) {
var Ember = require('ember'),
EmberAccordionComponent = require('src/EmberAccordionComponent'),
EmberAccordionTemplate = require('text!templates/ember-accordion.hbs'),
EmberAccordionHeaderTemplate = require('text!templates/ember-accordion-header.hbs'),
EmberAccordionBodyTemplate = require('text!templates/ember-accordion-body.hbs'),
ApplicationTemplate = require('text!templates/application.hbs'),
IndexTemplate = require('text!templates/index.hbs');
var App = Ember.Application.create({
LOG_STACKTRACE_ON_DEPRECATION : true,
LOG_BINDINGS : true,
LOG_TRANSITIONS : true,
LOG_TRANSITIONS_INTERNAL : true,
LOG_VIEW_LOOKUPS : true,
LOG_ACTIVE_GENERATION : true
});
Ember.TEMPLATES = {};
Ember.TEMPLATES['application'] = Ember.Handlebars.compile(ApplicationTemplate);
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(IndexTemplate);
Ember.TEMPLATES['components/ember-accordion'] = Ember.Handlebars.compile(EmberAccordionTemplate);
Ember.TEMPLATES['components/ember-accordion-header'] = Ember.Handlebars.compile(EmberAccordionHeaderTemplate);
Ember.TEMPLATES['components/ember-accordion-body'] = Ember.Handlebars.compile(EmberAccordionBodyTemplate);
App.EmberAccordionComponent = EmberAccordionComponent;
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{
name: 'Bob'
},
{
name: 'Jill'
}]
}
})
});
EmberAccordionComponent.js
define(function(require) {
require('ember');
var EmberAccordionComponent = Ember.Component.extend({});
return EmberAccordionComponent;
});
application.hbs
{{outlet}}
ember-accordion-header.hbs
<div style="color: blue;">
{{yield}}
</div>
ember-accordion-body.hbs
<div style="color: green;">
{{yield}}
</div>
index.hbs
{{#ember-accordion listOfAccordionPaneObjects=model}}
{{#ember-accordion-header}}
{{log this.constructor}}
{{log this}}
Header {{accordionPaneObject.name}}
{{/ember-accordion-header}}
{{#ember-accordion-body}}
Body {{accordionPaneObject.name}}
{{/ember-accordion-body}}
{{/ember-accordion}}
ember-accordion.hbs
{{#each accordionPaneObject in listOfAccordionPaneObjects}}
{{yield}}
{{/each}}
--
This is tricky to debug. So putting in the:
{{log this.constructor}}
and the:
{{log this}}
into the:
{{#ember-accordion-header}}
outputs the following:
Class.model = undefined (why?)
Ember.ArrayController
I've tried overriding the private _yield method of Ember.Component as suggested by this article ( http://www.thesoftwaresimpleton.com/blog/2013/11/21/component-block/ ):
var EmberAccordionHeaderComponent = Ember.Component.extend({
_yield: function(context, options) {
var get = Ember.get,
view = options.data.view,
parentView = this._parentView,
template = get(this, 'template');
if (template) {
Ember.assert("A Component must have a parent view in order to yield.", parentView);
view.appendChild(Ember.View, {
isVirtual: true,
tagName: '',
_contextView: parentView,
template: template,
context: get(view, 'context'), // the default is get(parentView, 'context'),
controller: get(view, 'controller'), // the default is get(parentView, 'context'),
templateData: { keywords: parentView.cloneKeywords() }
});
}
}
});
but when I do this I still don't have access to accordionPaneObject in my child component scope, and my {{log this.constructor}} now points to: .EmberAccordionHeaderComponent
So it looks like I'm getting somewhere, I just need to go one more level up.
When I try that using this code in EmberAccordionHeaderComponent.js:
var EmberAccordionHeaderComponent = Ember.Component.extend({
_yield: function(context, options) {
var get = Ember.get,
view = options.data.view,
parentView = this._parentView,
grandParentView = this._parentView._parentView,
template = get(this, 'template');
if (template) {
Ember.assert("A Component must have a parent view in order to yield.", parentView);
view.appendChild(Ember.View, {
isVirtual: true,
tagName: '',
_contextView: parentView,
template: template,
context: get(grandParentView, 'context'), // the default is get(parentView, 'context'),
controller: get(grandParentView, 'controller'), // the default is get(parentView, 'context'),
templateData: { keywords: parentView.cloneKeywords() }
});
}
}
});
I still don't access to accordionPaneObject in, but now I see {{log this.constructor}} outputting .EmberAccordionComponent. So it appears I'm in the right scope, but the data still doesn't bind.
Interestingly enough, if I use any of these variations of reassigning context and controller in my overridden _yield, I can access the data I am after in the console using:
this._parentView._context.content
I updated your code with some comments please give a look http://emberjs.jsbin.com/ivOyiZa/1/edit.
Javascript
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{ head: "foo head", body: "foo body " },
{ head: "bar head", body: "bar body " },
{ head: "ya head", body: "yo body " }
];
}
});
App.EmberAccordionComponent = Ember.Component.extend({
// each accordion header/body item, will have a instance of that view.
// so we can isolate the expanded state for each accordion header/body
emberAccordionItemView: Ember.View.extend({
expanded: false
}),
_yield: function(context, options) {
var get = Ember.get,
view = options.data.view,
parentView = this._parentView,
template = get(this, 'template');
if (template) {
Ember.assert("A Component must have a parent view in order to yield.", parentView);
view.appendChild(Ember.View, {
isVirtual: true,
tagName: '',
_contextView: parentView,
template: template,
context: get(view, 'context'), // the default is get(parentView, 'context'),
controller: get(view, 'controller'), // the default is get(parentView, 'context'),
templateData: { keywords: parentView.cloneKeywords() }
});
}
}
});
App.EmberAccordionHeaderComponent = Ember.Component.extend({
classNames: ['ember-accordion-header'],
click: function() {
// here we toggle the emberAccordionItemView.expanded property
this.toggleProperty('parentView.expanded');
}
});
Templates
<script type="text/x-handlebars" data-template-name="index">
{{#ember-accordion listOfAccordionPaneObjects=model}}
{{#ember-accordion-header}}
{{head}} <!-- each object passed in listOfAccordionPaneObjects=model can be accessed here -->
{{/ember-accordion-header}}
{{#ember-accordion-body}}
{{body}} <!-- each object passed in listOfAccordionPaneObjects=model can be accessed here -->
{{/ember-accordion-body}}
{{/ember-accordion}}
</script>
<script type="text/x-handlebars" data-template-name="components/ember-accordion">
{{#each listOfAccordionPaneObjects itemViewClass="view.emberAccordionItemView"}}
<div class="ember-accordion-container">
<div class="ember-accordion-pane">
{{yield}}
</div>
</div>
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="components/ember-accordion-header">
{{yield}}
</script>
<script type="text/x-handlebars" data-template-name="components/ember-accordion-body">
<!-- when EmberAccordionHeaderComponent.click is called, the expanded property change and the content can be visible or not, based on expanded truth -->
{{#if parentView.expanded}}
<div class="ember-accordion-pane-content">
{{yield}}
</div>
{{/if}}
</script>
Css
.ember-accordion-header {
background-color: #999;
color: #ffffff;
padding: 10px;
margin: 0;
line-height: 14px;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: pointer;
text-decoration: none;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}
.ember-accordion-container {
height: 100%;
width: 100%;
}
.ember-accordion-pane {
padding: 2px;
}
.emberaccordionheaderselected {
background-color: #bbb;
color: #333;
font-weight: bold;
}
.ember-accordion-header:hover {
text-decoration: underline !important;
}
.emberaccordionheaderselected:hover {
text-decoration: underline !important;
}
.ember-accordion-pane-content {
padding: 5px;
overflow-y: auto;
border-left: 1px solid #bbb;
border-right: 1px solid #bbb;
border-bottom: 1px solid #bbb;
-webkit-border-bottom-left-radius: 5px;
-webkit-border-bottom-right-radius: 5px;
-moz-border-radius-bottomleft: 5px;
-moz-border-radius-bottomright: 5px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.emberdisabledpane {
opacity: .2;
}
Yes, it's easy to do.
Here's a really simplistic, un-styled example, where it's on hover instead of click, but click is in the jsbin if you uncomment it, and comment out the mouseenter/mouseleave functions.
http://emberjs.jsbin.com/ijEwItO/3/edit
<script type="text/x-handlebars" data-template-name="components/unicorn-accordian">
<ul>
{{#each item in content itemController='unicornItem' itemView='unicornItem'}}
<li>{{item.title}}
{{#if bodyVisible}}
<br/>
{{item.body}}
{{/if}}
</li>
{{/each}}
</ul>
</script>
App.UnicornAccordianComponent = Em.Component.extend();
App.UnicornItemController = Em.ObjectController.extend({
bodyVisible: false
});
App.UnicornItemView = Em.View.extend({
mouseEnter: function(){
this.set('controller.bodyVisible', true);
},
mouseLeave: function(){
this.set('controller.bodyVisible', false);
}
});
Surely a much easier-to-implement solution is to pass the view (or other parent) as an argument to the component. This will give you access to all the properties of the view whilst still retaining the advantages of using a contained component. For example:
{{#ember-accordion listOfAccordionPaneObjects=model info=view}}{{!-- Pass view in here--}}
{{log view.info}}{{!-- This will log what view.parentView would have done--}}
{{ember-accordion-heading firstName=accordionPaneObject.firstName}}
{{ember-accordion-body lastName=accordionPaneObject.lastName}}
{{/ember-accordion}}
Your header template would look something like this:
Header template html here {{firstName}}
And your body template would look something like this:
Body html here {{lastName}}