I'm using Google Maps API to load multiple polygons into the map using the geoJSON data layer. Some of these polygons overlap in certain regions. When a user clicks on a point that is inside of multiple polygons, I want to display the properties (name, tags, etc) in an InfoBox with the click event.
I'm wanting to display the properties of all the polygons for a given point. Currently when I click on a point I can only see one polygon, despite the point being inside of multiple polygons.
How can I access all the properties of all the polygons with Google Maps API v3?
const map = useGoogleMap(); // google map instance
const polygons; // an array of polygons, example snippet below.
map.data.addGeoJson(polygons);
map.data.addListener('click', function(event) {
// how can i access other features underneath this clicked point
console.log(event.feature); // only returns "Geofence 1"
})
example GeoJson:
polygons = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Geofence 1"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-8.96484375,
-9.96885060854611
],
[
3.955078125,
-9.96885060854611
],
[
3.955078125,
-0.17578097424708533
],
[
-8.96484375,
-0.17578097424708533
],
[
-8.96484375,
-9.96885060854611
]
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "Geofence 2"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-6.591796875,
-8.320212289522944
],
[
2.197265625,
-8.320212289522944
],
[
2.197265625,
-1.9332268264771106
],
[
-6.591796875,
-1.9332268264771106
],
[
-6.591796875,
-8.320212289522944
]
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "Geofence 3"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-4.39453125,
-6.926426847059551
],
[
0.263671875,
-6.926426847059551
],
[
0.263671875,
-3.337953961416472
],
[
-4.39453125,
-3.337953961416472
],
[
-4.39453125,
-6.926426847059551
]
]
]
}
}
]
}
One option would be to use the containsLocation method in the geometry library.
containsLocation(point, polygon)
Parameters:
point: LatLng
polygon: Polygon
Return Value: boolean
Computes whether the given point lies inside the specified polygon.
Unfortunately that only works with native google.maps.Polygon objects not Data.Polygon objects. Translate the data in the feature into native google.maps.Polygon objects, push them on an array, then process through the array to see which polygon(s) the click is in.
create google.maps.Polygon for each polygon in the input (assumes only polygons)
var polygonArray = [];
map.data.addListener('addfeature', function(e) {
e.feature.getGeometry().getArray().forEach(function(latLngArry){
const polygon = new google.maps.Polygon({
map: map,
paths: latLngArry.getArray(),
clickable: false,
name: e.feature.getProperty("name") // save the data we want to output as an attribute
})
polygonArray.push(polygon);
})
on click check for which polygon(s) the click was in:
map.addListener('click', function(event) {
var content = "";
for (var i=0;i<polygonArray.length;i++) {
if (google.maps.geometry.poly.containsLocation(event.latLng, polygonArray[i])) {
if (content.length!=0)
content+=" : "
content += polygonArray[i].name;
}
}
console.log(content);
})
proof of concept fiddle
// This example uses the Google Maps JavaScript API's Data layer
// to create a rectangular polygon with 2 holes in it.
function initMap() {
const map = new google.maps.Map(document.getElementById("map"));
const infowindow = new google.maps.InfoWindow();
var bounds = new google.maps.LatLngBounds();
var polygonArray = [];
map.data.addListener('addfeature', function(e) {
console.log(e.feature.getGeometry().getArray().length);
e.feature.getGeometry().getArray().forEach(function(latLngArry) {
console.log(latLngArry.getArray())
const polygon = new google.maps.Polygon({
map: map,
paths: latLngArry.getArray(),
clickable: false,
name: e.feature.getProperty("name")
})
polygonArray.push(polygon);
})
processPoints(e.feature.getGeometry(), bounds.extend, bounds);
map.fitBounds(bounds);
});
const features = map.data.addGeoJson(polygons);
map.data.setMap(null);
map.addListener('click', function(event) {
var content = "";
for (var i = 0; i < polygonArray.length; i++) {
if (google.maps.geometry.poly.containsLocation(event.latLng, polygonArray[i])) {
if (content.length != 0)
content += " : "
content += polygonArray[i].name;
}
}
console.log(content);
document.getElementById('info').innerHTML = content;
infowindow.setPosition(event.latLng);
if (content.length == 0) content = "no GeoFence";
infowindow.setContent(content);
infowindow.open(map);
})
function processPoints(geometry, callback, thisArg) {
if (geometry instanceof google.maps.LatLng) {
callback.call(thisArg, geometry);
} else if (geometry instanceof google.maps.Data.Point) {
callback.call(thisArg, geometry.get());
} else {
geometry.getArray().forEach(function(g) {
processPoints(g, callback, thisArg);
});
}
}
}
const polygons = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"name": "Geofence 1"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-8.96484375, -9.96885060854611],
[
3.955078125, -9.96885060854611
],
[
3.955078125, -0.17578097424708533
],
[-8.96484375, -0.17578097424708533],
[-8.96484375, -9.96885060854611]
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "Geofence 2"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-6.591796875, -8.320212289522944],
[
2.197265625, -8.320212289522944
],
[
2.197265625, -1.9332268264771106
],
[-6.591796875, -1.9332268264771106],
[-6.591796875, -8.320212289522944]
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "Geofence 3"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-4.39453125, -6.926426847059551],
[
0.263671875, -6.926426847059551
],
[
0.263671875, -3.337953961416472
],
[-4.39453125, -3.337953961416472],
[-4.39453125, -6.926426847059551]
]
]
}
}
]
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 90%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Data Layer: Polygon</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=&v=weekly" defer></script>
<!-- jsFiddle will insert css and js -->
</head>
<body>
<div id="info"></div>
<div id="map"></div>
</body>
</html>
I was able to get this POC working with React and am sharing for next person who might be interested:
import { InfoWindow, useGoogleMap } from '#react-google-maps/api';
export const GeofencesContainer = () => {
const map = useGoogleMap();
// geofence = geoJSON feature collections
const [geofenceClickEventLatLng, setGeofenceClickEventLatLng] = useState({});
const [geofencesProperties, setGeofencesProperties] = useState([]);
const [isInfoWindowOpen, setIsInfoWindowOpen] = useState(false);
const [polygonArray, setPolygonArray] = useState([]);
const ref = useRef();
useEffect(() => {
let addFeatureListener;
const getGeofences = async () => {
if (true) {
if (ref.current !== 'geofenceEnabled') {
// get geofences from api
const geofences = await fetch.getGeofences();
// a listener to add feature, where we create a google.maps.polygon for every added feature, then add it to the polygon array
const addedPolygons = [];
addFeatureListener = map.data.addListener('addfeature', e => {
const featureGeometry = e.feature.getGeometry().getArray();
// for each geometry get the latLng array, which will be used to form a polygon
featureGeometry.forEach(latLngArray => {
const polygon = new window.google.maps.Polygon({
map,
paths: latLngArray.getArray(),
strokeWeight: 1,
fillColor: 'green',
clickable: false,
name: e.feature.getProperty('name')
});
addedPolygons.push(polygon);
});
setPolygonArray(addedPolygons);
});
// add the polygon to the map data layer (this will show the polygon on the map and cause the addfeature listener to fire)
map.data.addGeoJson(geofences);
map.data.setStyle({ fillColor: 'green', strokeWeight: 1 });
// we set map data to null so that we don't end up with 2 polygons on top of each other
map.data.setMap(null);
ref.current = 'geofenceEnabled';
}
} else {
ref.current = null;
for (const polygon of polygonArray) {
polygon.setMap(null);
}
setIsInfoWindowOpen(false);
}
};
getGeofences();
return () => {
if (addFeatureListener) {
addFeatureListener.remove();
}
};
}, [activeKmls]);
useEffect(() => {
let clickListener;
if (true) {
// a listener on click that checks whether the point is in a polygon and updates the necessary state to show the proper info window
clickListener = map.addListener('click', function(event) {
// this state is updated to identify the place where the info window will open
setGeofenceClickEventLatLng(event.latLng);
// for every polygon in the created polygons array check if it includes the clicked point, then update what to display in the info window
const selectedGeofences = [];
for (const polygon of polygonArray) {
if (window.google.maps.geometry.poly.containsLocation(event.latLng, polygon)) {
const name = polygon.name;
selectedGeofences.push({ key: name, name });
}
}
if (selectedGeofences.length) {
setIsInfoWindowOpen(true);
} else {
setIsInfoWindowOpen(false);
}
setGeofencesProperties(selectedGeofences);
});
}
return () => {
if (clickListener) {
clickListener.remove();
}
};
}, [activeKmls, polygonArray, altitudeUnit, map]);
return (
<Fragment>
{isInfoWindowOpen && (
<InfoWindow
position={geofenceClickEventLatLng}
onCloseClick={() => setIsInfoWindowOpen(false)}
zIndex={0}
options={{ maxWidth: '450' }}
>
<Fragment>
{geofencesProperties.map(geofenceProperties => {
return (
<Fragment key={geofenceProperties.key}>
<Title level={4}>{geofenceProperties.name}</Title>
</Fragment>
);
})}
</Fragment>
</InfoWindow>
)}
</Fragment>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Related
I have a MV2 extension with chrome.webRequest that works perfectly but fail on MV3 declarativeNetRequest getting around iframes.
The extension is like a multi-messenger that opens multiple iframes for various sites to merge in a single extension all popular messengers.
So I have a domain "example.com" and there I open multiple iframes, for example open an iframe with Twitter.com or Telegram.org.
Since twitter.com or telegram.org set the X-Frame-Options to DENY those iframes don't show anything.
With MV2 we could run chrome.webRequest and remove those headers:
chrome.webRequest.onHeadersReceived.addListener(
function (details)
{
if (details.tabId && (details.tabId === tabId || details.tabId === -1 || tabMultiId.includes(details.tabId))) {
var b = details.responseHeaders.filter((details) => !['x-frame-options', 'content-security-policy', 'x-content-security-policy', 'strict-transport-security', 'frame-ancestors'].includes(details.name.toLowerCase()));
b.forEach(function(e){
"set-cookie" === e.name && -1 !== e.value.indexOf("Secure") && (-1 !== e.value.indexOf("SameSite=Strict") ?
(e.value = e.value.replace(/SameSite=Strict/g, "SameSite=None"))
: -1 !== e.value.indexOf("SameSite=Lax")
? (e.value = e.value.replace(/SameSite=Lax/g, "SameSite=None"))
: (e.value = e.value.replace(/; Secure/g, "; SameSite=None; Secure")));
});
return {
responseHeaders: b
}
}
},
{
urls: [ "<all_urls>" ],
tabId: tabId
},
["blocking", "responseHeaders", "extraHeaders"]
);
I have tried to do exactly the same with MV3 but keep failing.
My 2 attemps:
async function NetRequest() {
var blockUrls = ["*://*.twitter.com/*","*://*.telegram.org/*"];
var tabId = await getObjectFromLocalStorage('tabId');
var tabMultiId = [];
tabMultiId = JSON.parse(await getObjectFromLocalStorage('tabMultiId'));
tabMultiId.push(tabId);
blockUrls.forEach((domain, index) => {
let id = index + 1;
chrome.declarativeNetRequest.updateSessionRules({
addRules:[
{
"id": id,
"priority": 1,
"action": { "type": "modifyHeaders",
"responseHeaders": [
{ "header": "X-Frame-Options", "operation": "remove" },
{ "header": "Frame-Options", "operation": "remove" },
{ "header": "content-security-policy", "operation": "remove" },
{ "header": "content-security-policy-report-only", "operation": "remove" },
{ "header": "x-content-security-policy", "operation": "remove" },
{ "header": "strict-transport-security", "operation": "remove" },
{ "header": "frame-ancestors", "operation": "remove" },
{ "header": "set-cookie", "operation": "set", "value": "SameSite=None; Secure" }
]
},
"condition": {"urlFilter": domain, "resourceTypes": ["image","media","main_frame","sub_frame","stylesheet","script","font","xmlhttprequest","ping","websocket","other"],
"tabIds" : tabMultiId }
}
],
removeRuleIds: [id]
});
});
}
async function launchWindow(newURL, windowDimensions, urlWindow, isIncognitoWindow, windowType) {
chrome.windows.create({ url: newURL, type: windowType, incognito: isIncognitoWindow, width: windowDimensions.width, height: windowDimensions.height, left: windowDimensions.left, top: windowDimensions.top },
async function (chromeWindow) {
if (urlWindow != "install" || urlWindow != "update") {
chrome.storage.local.set({ 'extensionWindowId': chromeWindow.id }, function () { });
chrome.storage.local.set({ 'tabId': chromeWindow.tabs[0].id }, function () { });
NetRequest();
}
});
}
Also tried:
const iframeHosts = [
'twitter.com', 'telegram.org'
];
const RULE = {
id: 1,
condition: {
initiatorDomains: ['example.com'],
requestDomains: iframeHosts,
resourceTypes: ['sub_frame', 'main_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
};
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [RULE.id],
addRules: [RULE],
});
Permissions:
"permissions": [
"system.display",
"scripting",
"activeTab",
"notifications",
"contextMenus",
"unlimitedStorage",
"storage",
"declarativeNetRequestWithHostAccess",
"webNavigation",
"alarms"
],
"host_permissions": [
"<all_urls>"
],
Any of this attempts worked.
Greetings and thank you very much for anyone that try to help.
You need to unregister service worker for the site and clear its cache using chrome.browsingData API.
Syntax for urlFilter is different, so your "*://*.twitter.com/*" is incorrect and should be "||twitter.com/", however a better solution is to use requestDomains because it allows specifying multiple sites in just one rule.
// manifest.json
"permissions": ["browsingData", "declarativeNetRequest"],
"host_permissions": ["*://*.twitter.com/", "*://*.telegram.org/"],
// extension script
async function configureNetRequest(tabId) {
const domains = [
'twitter.com',
'telegram.org',
];
const headers = [
'X-Frame-Options',
'Frame-Options',
];
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [1],
addRules: [{
id: 1,
action: {
type: 'modifyHeaders',
responseHeaders: headers.map(h => ({ header: h, operation: 'remove'})),
},
condition: {
requestDomains: domains,
resourceTypes: ['sub_frame'],
tabIds: [tabId],
},
}],
});
await chrome.browsingData.remove({
origins: domains.map(d => `https://${d}`),
}, {
cacheStorage: true,
serviceWorkers: true,
});
}
// Usage
chrome.windows.create({ url: 'about:blank' }, async w => {
await configureNetRequest(w.tabs[0].id);
await chrome.tabs.update(w.tabs[0].id, { url: 'https://some.real.url/' });
});
I am not able to figure out a way to change the material of a polygon entity from a GeoJsonDataSource. I would like to apply an image.
Here is an example using a color because I don't know how to embed an image on the online sandcastle:
var viewer = new Cesium.Viewer("cesiumContainer");
const poly = {
"type": "FeatureCollection",
"name": "MyPolygon",
"crs": {"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}},
"features": [
{"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[[ 10.746500009923748, 48.314700009648320, 500 ],
[ 10.747500009924019, 48.315700009648104, 500 ],
[ 10.747038310965864, 48.315905422444722, 550 ],
[ 10.746038315853207, 48.314905418639555, 550 ],
[ 10.746500009923748, 48.314700009648320, 500 ]]
]}}]};
const Promise0 = async () => {
try {
const dataSource = await Cesium.GeoJsonDataSource.load(poly, {
stroke: Cesium.Color.BLUE,
strokeWidth: 3
});
const Promise1 = async () => {
try {
const polygonalFrame = await viewer.dataSources.add(dataSource);
viewer.zoomTo(polygonalFrame);
const entities = polygonalFrame.entities.values;
for (var i = 0; i < entities.length; i++) {
const entity = entities[i];
entity.polygon.material = new Cesium.Material({
fabric : {
type : 'Color',
uniforms : {
color : new Cesium.Color(1.0, 0.0, 0.4, 0.5)
}
}
});
}
}
catch (err) {
console.log("Error: ", err);
}
};
Promise1();
}
catch (e) {
console.log("Error:", e);
}
};
Promise0();
The polygon remains yellow, which is the default color I think.
For the image material, I use this definition locally:
new Cesium.Material({
fabric : {
type : 'Image',
uniforms : {
image : './image.png'
}
}
});
I fixed it using this way of defining the PolygonGraphics' material in my entity:
new Cesium.ImageMaterialProperty({
image: './image.png',
alpha: 0.5
});
But I noticed that alpha blending doesn't work when I try to apply it on my whole image...
I'm using Vue JS and I'm trying to create markers for a Google map from JSON file.
This is the whole error message:
"Module build failed: SyntaxError: Unexpected token } in JSON at position 139
at JSON.parse ()
at Object.module.exports (E:\just\path\project\node_modules\json-loader\index.js:4:49)"
It says that the problem is at index.js at position 4:49 but I don't know what should be wrong. I have never edited this file.
Here is the script part of a file map.vue:
<script>
import { stanice } from '../json/data.json'
export default{
name: "Map",
data () {
return {
center: { lat: 50.425, lng: 14.907 },
infoContent: '',
infoWindowPos: null,
infoWinOpen: false,
currentMidx: null,
infoOptions: {
pixelOffset: {
width: 0,
height: -35
}
},
};
},
computed: {
markers() {
return stanice.map(({ location: { lat, lng }, name }) => ({
position: {
lat,
lng
},
name
}));
}
},
methods: {
getPosition: function(marker) {
return {
lat: parseFloat(marker.position.lat),
lng: parseFloat(marker.position.lng)
}
},
toggleInfoWindow: function(marker, index) {
this.infoWindowPos = this.getPosition(marker);
this.infoContent = marker.text;
if (this.currentMidx == index) {
this.infoWinOpen = !this.infoWinOpen;
}
else{
this.infoWinOpen = true;
this.currentMidx = index;
}
}
}
};
</script>
This is data.json file:
{
"stanice": [
{
"name": "Meteostanice 1",
"location": {
"lat": 50.43,
"lng": 14.91
}
},
{
"name": "Meteostanice 2",
"location": {
"lat": 50.43,
"lng": 14.915
}
},
{
"name": "Meteostanice 3",
"location": {
"lat": 50.43,
"lng": 14.92
}
}
]
}
And this is that index.js file located in json-loader folder:
module.exports = function (source) {
if (this.cacheable) this.cacheable();
var value = typeof source === "string" ? JSON.parse(source) : source;
value = JSON.stringify(value)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
return `module.exports = ${value}`;
}
Some stuff is in my native language so I hope it's not a problem ;)
Thanks everyone for help !
My api response looks like below
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"2050"
]
},
{
"How much do you sleep?": [
"Ajajajsjiskskskskdkdj"
]
}
],
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"10181"
]
},
{
"How much do you sleep?": [
"Ajskossooskdncpqpqpwkdkdkskkskskksksksksks"
]
}
]
Now in react, I want to export the results to a csv. I can do it by export-to-csv but the formatting is the issue here. I want the values of each question of a single response in one row under their labels(questions). So if I have two response like above I want to have export it in two rows, not 8 as there are 8 total questions.
Here is how I want it to get exported.
I have tried so far like this but no luck.
this is my export data function
exp =()=>{
const raw = []
console.log(this.state.data[0].sbm_id)
axios.get(`/dashboard/${this.props.proj_id}/whole_sub/`)
.then(res=>{
// console.log('1')
// console.log(res.data[0][0])
// console.log('2')
for (let i =0;i<this.state.data.length;i++){
for(let j = 0;j<res.data[0].length;j++){
// let sub=[]
//res.data[i][j].ID = this.state.data[i].sbm_id
raw.push(res.data[i][j])
}
}
}
)
let curr = this.state
curr.exp = raw
this.setState({exp:curr.exp})
}
Here is my export function
rawExport=()=>{
const csvExporter = new ExportToCsv(optionsExp);
csvExporter.generateCsv(this.state.exp);
}
First step is to flatten the initial nested array to get a homogeneously shaped array, then you keep on reducing it further.
const data = [
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"2050"
]
},
{
"How much do you sleep?": [
"Ajajajsjiskskskskdkdj"
]
}
],
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"10181"
]
},
{
"How much do you sleep?": [
"Ajskossooskdncpqpqpwkdkdkskkskskksksksksks"
]
}
]
];
const flattenArray = (arr) => [].concat.apply([], arr);
// Flatten the initial array
const flattenedArray = flattenArray(data);
// Keep on reducing the flattened array into an object
var res = flattenedArray.reduce((acc, curr) => {
const [key, val] = flattenArray(Object.entries(curr));
if (!acc[key]) {
acc[key] = [].concat(val);
} else {
val.forEach(x => {
if (!acc[key].includes(x)) {
acc[key].push(x);
}
});
}
return acc;
}, {});
console.log(res);
I have a sap.m splitApp where i have an overview of courses. By displaying a course you get detail information like the list of participants for that course. Currently it is only possible to display the participants of the same (one) course for all courses. How can i display the appropriate participants for each course.
If anyone has an idea that would be great :) Thanks.
This is my "Details.view"
sap.ui.jsview("tem_trainer.Details", {
/** Specifies the Controller belonging to this View.
* In the case that it is not implemented, or that "null" is returned, this View does not have a Controller.
* #memberOf tem_trainer.Details
*/
getControllerName : function() {
return "tem_trainer.Details";
},
onBeforeFirstShow: function(oEvent){
this.getController().onBeforeFirstShow(oEvent);
},
/** Is initially called once after the Controller has been instantiated. It is the place where the UI is constructed.
* Since the Controller is given to this method, its event handlers can be attached right away.
* #memberOf tem_trainer.Details
*/
createContent : function(oController) {
function dateDiffInDays(a, b) {
// Discard the time and time-zone information.
var utc1 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
var utc2 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
return Math.floor((utc2 - utc1) / (1000 * 60 * 60 * 24));
}
var oTimestamp = new sap.m.ObjectAttribute({
text: '{start} - {end}, {starttime} - {endtime}',
});
var oRoom = new sap.m.ObjectAttribute({
text: "{room}",
});
var oHeader = new sap.m.ObjectHeader({
title: "{name}",
number: "{start}",
numberUnit: "Start Date",
attributes: [
oTimestamp, oRoom
]
});
var oTable = new sap.m.Table("idRandomDataTable", {
headerToolbar : new sap.m.Toolbar({
content : [ new sap.m.Label({
text : "Participant List"
}), new sap.m.ToolbarSpacer({}), new sap.m.Button("idPersonalizationButton", {
icon : "sap-icon://person-placeholder"
}) ]
}),
columns : [
new sap.m.Column({
width : "2em",
header : new sap.m.Label({
text : "Firstname"
})
}),
new sap.m.Column({
width : "2em",
header : new sap.m.Label({
text : "Lastname"
})
}),
new sap.m.Column({
width : "2em",
header : new sap.m.Label({
text : "Job"
})
}),
new sap.m.Column({
width : "2em",
header : new sap.m.Label({
text : "Company"
})
})
]
});
var oModel1 = new sap.ui.model.json.JSONModel();
var model = sap.ui.getCore().getModel();
var aData = model.getProperty("/courses");
oModel1.setData({
modelData : aData
});
oTable.setModel(oModel1);
oTable.bindItems("/modelData/0/participant", new sap.m.ColumnListItem({
cells : [ new sap.m.Text({
text : "{firstname}"
}), new sap.m.Text({
text : "{lastname}"
}), new sap.m.Text({
text : "{job}",
}), new sap.m.Text({
text : "{company}",
}),]
}));
var oIconTabBar = new sap.m.IconTabBar({
items: [
new sap.m.IconTabFilter({
text: "General",
icon: "sap-icon://hint",
content: [
]
}),
new sap.m.IconTabFilter({
text: "Participants",
icon: "sap-icon://visits",
content: [
oTable
]
}),
]
});
return this.page = new sap.m.Page({
title: "Course Details",
content: [
oHeader, oIconTabBar
]
});
}
});
This is my "Details.controller"
sap.ui.controller("tem_trainer.Details", {
/**
* Called when a controller is instantiated and its View controls (if available) are already created.
* Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
* #memberOf tem_trainer.Details
*/
// onInit: function() {
//
// },
/**
* Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
* (NOT before the first rendering! onInit() is used for that one!).
* #memberOf tem_trainer.Details
*/
// onBeforeRendering: function() {
//
// },
/**
* Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
* This hook is the same one that SAPUI5 controls get after being rendered.
* #memberOf tem_trainer.Details
*/
// onAfterRendering: function() {
//
// },
/**
* Called when the Controller is destroyed. Use this one to free resources and finalize activities.
* #memberOf tem_trainer.Details
*/
// onExit: function() {
//
// }
onBeforeFirstShow: function(oEvent){
if(oEvent.data.bindingContext){
// Binding Kontext setzen
this.getView().page.setBindingContext(oEvent.data.bindingContext);
}
},
onListSelect: function(oEvent){
var oBindingContext = oEvent.getParameter(
"listItem").getBindingContext();
var sViewId = "detailCourse_" +
oEvent.getParameter(
"listItem").data("req_id");
sap.ui.getCore().getEventBus().publish(
"nav",
"to", {
viewName: "tem_trainer.Details",
viewId: sViewId,
data: {
bindingContext: oBindingContext
}
});
},
onListItemTap: function(oEvent){
var oBindingContext = oEvent.oSource.getBindingContext();
var sViewId = "detailCourse_" +
oEvent.oSource.data("req_id");
sap.ui.getCore().getEventBus().publish(
"nav",
"to", {
viewName: "tem_trainer.Details",
viewId: sViewId,
data: {
bindingContext: oBindingContext
}
});
},
onNavButtonTap: function(){
// Wird ausgeführt wenn die Hardwaretaste
// oder der Back-Button gedrückt wird
sap.ui.getCore().getEventBus().publish(
"nav", "back");
}
});
This is my "Courses.json"
{
"courses": [
{
"req_id": "1",
"name" : "ABAP OO Basics",
"start" : "20-08-2014",
"end" : "22-08-2014",
"starttime": "10:00:00",
"endtime": "16:00:00",
"status": "Booked",
"room": "Room CDE",
"adress" : "Adress No.1",
"street": "Street No.1",
"zip_code": "74892142578485",
"city": "City No.1",
"country": "Country No.1",
"phone": "0595726764675435497436497",
"fax":"12",
"cap_min": "10",
"cap_opt": "20",
"cap_max": "30",
"img": "./res/1.jpg",
"content": "Test",
"participant": [{ "firstname": "Maik",
"lastname": "Maier",
"job": "installer",
"company": "muster"
},
{ "firstname": "Marco",
"lastname": "Schmidt",
"job": "installer",
"company": "schmitt"
},
{ "firstname": "Hans",
"lastname": "Mueller",
"job": "installer",
"company": "muster"
},
{ "firstname": "Matthias",
"lastname": "Gottlieb",
"job": "installer",
"company": "schmitt"
}]
},
{
"req_id": "2",
"name" : "ABAP OO Basics II",
"start" : "22-08-2014",
"end" : "23-08-2014",
"starttime": "11:00:00",
"endtime": "14:00:00",
"status": "Booked",
"room": "Room XYZ",
"adress" : "Adress No.2",
"street": "Street No.2",
"zip_code": "2222",
"city": "City No.2",
"country": "Country No.2",
"phone": "22222",
"fax":"2222",
"cap_min": "10",
"cap_opt": "20",
"cap_max": "30",
"img": "./res/2.jpg",
"content": "Test",
"participant": [{ "firstname": "Name",
"lastname": "Name",
"job": "installer",
"company": "muster"
},
{ "firstname": "Name2",
"lastname": "Name2",
"job": "installer",
"company": "schmitt"
}]
}
]
}
EDIT: Here is my "Master.controller"
sap.ui.controller("tem_trainer.Master", {
/**
* Called when a controller is instantiated and its View controls (if available) are already created.
* Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
* #memberOf tem_trainer.Master
*/
// onInit: function() {
//
// },
/**
* Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
* (NOT before the first rendering! onInit() is used for that one!).
* #memberOf tem_trainer.Master
*/
onBeforeRendering: function() {
var oInbox = sap.ui.getCore().byId("inboxList");
oInbox.removeSelections();
var oJSONDataModel = new sap.ui.model.json.JSONModel();
oJSONDataModel.loadData("./json/Courses.json");
sap.ui.getCore().setModel(oJSONDataModel);
},
/**
* Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
* This hook is the same one that SAPUI5 controls get after being rendered.
* #memberOf tem_trainer.Master
*/
// onAfterRendering: function() {
//
// },
/**
* Called when the Controller is destroyed. Use this one to free resources and finalize activities.
* #memberOf tem_trainer.Master
*/
// onExit: function() {
//
// }
onBeforeFirstShow: function(oEvent) {
this.bindListData();
if(oEvent.data.title) {
this.getView().page.setTitle(oEvent.data.title);
}
},
bindListData: function(aFilters){
var that = this;
this.getView().oList.bindAggregation("items", {
path: "/courses",
factory: function(sId){
return new sap.m.StandardListItem(sId, {
icon : "sap-icon://course-program",
title: {
path:"name",
},
description: {
path:"start",
},
type: jQuery.device.is.phone?
sap.m.ListType.Navigation : sap.m.ListType.None,
customData: [
new sap.ui.core.CustomData({
key: "req_id",
value: "{req_id}"
}),
],
tap: [that.onListItemTap, that]
}).setInfoState(sap.ui.core.ValueState.Success);
},
filters: aFilters
});
},
onListSelect: function(oEvent){
var oBindingContext =
oEvent.getParameter(
"listItem"
).getBindingContext(),
sViewId = "detailCourse_" +
oEvent.getParameter(
"listItem").data("req_id");
// Ereignis an EventBus übergeben
sap.ui.getCore().getEventBus().publish(
"nav",
"to", {
viewName: "tem_trainer.Details",
viewId: sViewId,
data: {
bindingContext: oBindingContext
}
});
},
onListItemTap: function(oEvent){
// siehe onListSelect
var oBindingContext =
oEvent.oSource.getBindingContext(),
sViewId = "detailCourse_" +
oEvent.oSource.data("req_id");
sap.ui.getCore().getEventBus().publish(
"nav",
"to", {
viewName: "tem_trainer.Details",
viewId: sViewId,
data: {
bindingContext: oBindingContext
}
});
},
//Navigation Zurück
onNavButtonTap: function(){
sap.ui.getCore().getEventBus().publish(
"nav",
"back"
);
},
//Search Functionality
handleSearch: function(oEvent) {
this._updateList();
},
_updateList : function () {
var filters = [];
//var oView = this.getView();
// Filter für die Suche
//var searchString = oView.byId("searchField").getValue();
var searchString = sap.ui.getCore().byId("searchField").getValue();
if (searchString && searchString.length > 0) {
var filter = new sap.ui.model.Filter("name", sap.ui.model.FilterOperator.Contains, searchString);
filters.push(filter);
}
// List Binding updaten
//var list = oView.byId("inboxList");
var list = sap.ui.getCore().byId("inboxList");
var binding = list.getBinding("items");
binding.filter(filters);
},
});
EDIT: This is what i adjusted:
Details.controller
onInit : function() {
var bus = sap.ui.getCore().getEventBus();
bus.subscribe("nav", "to", this.navToHandler, this);
},
navToHandler : function(channelId, eventId, data) {
if (data && data.viewId) {
var oBindingContext = data.data.bindingContext;
this.getView().setBindingContext(oBindingContext);
}
},
//This is a function i already had and which is called by the view
onBeforeFirstShow: function(oEvent){
if(oEvent.data.bindingContext){
// Binding Kontext setzen
this.getView().page.setBindingContext(oEvent.data.bindingContext);
}
},
In my Details.view
var oModel1 = new sap.ui.model.json.JSONModel();
var model = sap.ui.getCore().getModel();
var aData = model.getProperty("/courses");
oModel1.setData({
modelData : aData
});
oTable.setModel(oModel1);
oTable.bindItems("participant", new sap.m.ColumnListItem({
cells : [ new sap.m.Text({
text : "{firstname}"
}), new sap.m.Text({
text : "{lastname}"
}), new sap.m.Text({
text : "{job}",
}), new sap.m.Text({
text : "{company}",
}),]
}));
Your requirement is similar to the demo app of Approve Purchase Orders.
See the json string:
https://openui5.hana.ondemand.com/test/resources/sap/m/demokit/poa/model/mock.json
Courses 1 : n Course 1 : n Participants
PurchaseOrderCollection 1 : n PurchaseOrder 1 :n PurchaseOrder_Items
You already did publish eventbus in your master controller when use selects the list item in the master view:
onListSelect: function(oEvent){
var oBindingContext =
oEvent.getParameter(
"listItem"
).getBindingContext(),
sViewId = "detailCourse_" +
oEvent.getParameter(
"listItem").data("req_id");
// Ereignis an EventBus übergeben
sap.ui.getCore().getEventBus().publish(
"nav",
"to", {
viewName: "tem_trainer.Details",
viewId: sViewId,
data: {
bindingContext: oBindingContext
}
});
},
You need to subscribe the eventbus in your Detail view controller to get the Binding Context of your list item and set the binding context for your detail view, in your case the single course.
onInit : function() {
var bus = sap.ui.getCore().getEventBus();
bus.subscribe("nav", "to", this.navToHandler, this);
},
navToHandler : function(channelId, eventId, data) {
if (data && data.viewId) {
var oBindingContext = data.data.bindingContext;
this.getView().setBindingContext(oBindingContext);
}
},
And adjust your binding paths of the table in the detail view, do not hard code 0.
oTable.bindItems("participant", new sap.m.ColumnListItem({
cells : [ new sap.m.Text({
text : "{firstname}"
}), new sap.m.Text({
text : "{lastname}"
}), new sap.m.Text({
text : "{job}",
}), new sap.m.Text({
text : "{company}",
}),]
}));