openlayers 3 featureKey exists in featureChangeKeys - openlayers-3

I have an ol.interaction.Select acting on an ol.source.Vector which is within an ol.layer.Vector. I can select and unselect individual countries fine. I am using a dragbox to select multiple countries. If I select anywhere outside of the multiply selected countries, the currently selected get unselected. Excellent!
However, the problem is that if I select a currently selected country within the multiple, I get the AssertionError: Assertion failed: featureKey exists in featureChangeKeys
Here's my Vector layer:
_countrySelectSource = new ol.source.Vector({
url: 'vendor/openlayers/geojson/countries.json',
format: new ol.format.GeoJSON()
});
var countryLayer = new ol.layer.Vector({
title: 'Country Select',
visible: true,
type: 'interactive-layers',
source: _countrySelectSource
});
I add countryLayer to my map, _map.
I then create a _CountrySelect object that allows me to setActive(true|false) on the interactions related to my country selection.
_CountrySelect = {
init : function(){
this.select = new ol.interaction.Select();
_map.addInteraction(this.select);
this.dragbox = new ol.interaction.DragBox({
condition: ol.events.condition.platformModifierKeyOnly
});
_map.addInteraction(this.dragbox);
this.setEvents();
},
setEvents: function(){
var selectedFeatures = this.select.getFeatures();
var infoBox = document.getElementById('info');
var selfDragbox = this.dragbox;
selfDragbox.on('boxend', function() {
// features that intersect the box are added to the collection of
// selected features, and their names are displayed in the "info"
// div
var extent = selfDragbox.getGeometry().getExtent();
_countrySelectSource.forEachFeatureIntersectingExtent(extent, function(feature) {
selectedFeatures.push(feature);
_countryCodes.push(feature.getId());
});
infoBox.innerHTML = _countryCodes.join(', ');
});
// clear selection when drawing a new box and when clicking on the map
selfDragbox.on('boxstart', function() {
selectedFeatures.clear();
infoBox.innerHTML = ' ';
});
_map.on('singleclick', function(event) {
selectedFeatures.clear();
_countryCodes = [];
_map.forEachFeatureAtPixel(event.pixel,function(feature){
selectedFeatures.push(feature);
var id = feature.getId();
var index = _countryCodes.indexOf(id);
if ( index === -1 ) {
_countryCodes.push(feature.getId());
}
});
infoBox.innerHTML = _countryCodes.join(', ');
});
},
setActive: function(active){
this.select.setActive(active);
this.dragbox.setActive(active);
}
};
_CountrySelect.init();
I am not sure if this is an issue with OL3 or my code. Maybe there's an event I'm not handling? Maybe it's the ol.interaction.DragBox (no luck researching on the DragBox). Let me know what further information I can provide.

Related

Adding data labels (annotations?) to Google Charts (Visualizations API) drawn from a query

I'm creating a line chart by querying data entered into a Google Sheet, and I need to add data labels, i.e. the little numbers next to the points on the chart. I found plenty of documentation on how to do this with charts drawn from a manually entered data-table, but not from a query to a Google Sheet. Please help.
google.charts.load('current', {'packages':['corechart', 'line']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var query = new google.visualization.Query(
'URL'
);
query.setQuery('SELECT A, B OFFSET 0'); //select specific cells from the table
query.send(handleQueryResponse);
}
function handleQueryResponse(response) {
if (response.isError()) {
alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var data = response.getDataTable();
var options = {
title: '',
height : 250,
width : '100%',
}
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
correct, you want to add annotations.
this can be done using an annotation column role.
the annotation column role, should directly follow the series column it represents in the data table.
in this case, since you are getting the data from a query,
we can use a DataView to add the annotation using a calculated column.
first, we create the data view.
var view = new google.visualization.DataView(data);
then we use the setColumns method,
to add the column indexes from the query,
and our calculated column for the annotation.
view.setColumns([0, 1, {
calc: 'stringify',
sourceColumn: 1,
type: 'string',
role: 'annotation'
}]);
finally, we need to use the view to draw the chart.
chart.draw(view, options);
see following snippet...
google.charts.load('current', {
packages: ['corechart']
}).then(drawChart);
function drawChart() {
var query = new google.visualization.Query(
'URL'
);
query.setQuery('SELECT A, B OFFSET 0'); //select specific cells from the table
query.send(handleQueryResponse);
}
function handleQueryResponse(response) {
if (response.isError()) {
alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var data = response.getDataTable();
var options = {
title: '',
height : 250,
width : '100%',
};
// create data view with calculated annotation column
var view = new google.visualization.DataView(data);
view.setColumns([0, 1, {
calc: 'stringify',
sourceColumn: 1,
type: 'string',
role: 'annotation'
}]);
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(view, options); // <-- use view to draw the chart
}
Note: when using google.visualization.LineChart, you only need the 'corechart' package.
the 'line' package is for their material line chart --> google.charts.Line

Can't access KML features

I am loading a KML file locally and I have been able to add it to the map successfully. However, I want to interate over the features and can't seem to get anything to work. My code currently:
var myLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: '/kml/sample.kml',
format: new ol.format.KML()
})
});
// Iterate over features *NOT WORKING*
myLayer.getSource().forEachFeature(function(e) {
console.log(e);
})
Any pointers on how I can get the forEachFeature to function, or any alternative method, would be great.
The code in your question works fine, except that the features are loaded asynchronously. Most of the time it will first execute forEachFeature, which finds 0 features to loop through and afterwards the features are loaded.
You may find out that a single feature is loaded by listening for the addfeature event of the source and maybe you can make your desired changes there for each feature separately:
var id = 1;
myLayer.getSource().on('addfeature', function (ev_add) {
console.log(ev_add.feature);
ev_add.feature.once('change', function (ev_change) {
console.log(ev_change.target.getId());
});
ev_add.feature.setId(x);
x += 1;
});
If you must wait until all features are loaded, the change event of the layer can help:
myLayer.once('change', function () {
myLayer.getSource().forEachFeature(function (feature) {
console.log(feature);
});
});
Edit: You are right, the addfeature event handler has the event object as parameter. To your question about setting the ID while adding features, I think that this is again a problem of waiting until the changes are done. I made the amendments in the first snippet.
I found a way to get this to work. Not sure if it's the most efficient however:
var featProj = map.getView().getProjection();
var kmlFormat = new ol.format.KML();
var myLayer = new ol.layer.Vector();
var vectorSource = new ol.source.Vector({
loader: function() {
$.ajax( {
url: '/kml/my.kml',
success: function( data ) {
var features = kmlFormat.readFeatures( data, { featureProjection: featProj } );
vectorSource.addFeatures( features );
// iterate over features
vectorSource.forEachFeature( function( feature ) {
//do something
console.log( feature );
});
}
});
},
strategy: ol.loadingstrategy.bbox
});
myLayer.setSource( vectorSource );

Invalid Property on Extended FIORI Application

We are implementing an extended My Quotations Fiori application. Basically we added a new field Sales Order to the UI. The field fetches data from the backend so we also extended our OData service. On the first view, we can successfully call the data. But whenever we navigate to the next view via clicking Edit button, we get this error
Property 'SalesOrder' is invalid. Choose "Refresh" to update pricing information.
Anyone has an idea on how to solve this?
Here is our custom code for S3 view controller. We used WEB IDE to create the extension btw. The second function is for the creation of the Sales Order whenever the quotation has no associated SO tied to it.
manageSalesOrderFields: function() {
alert("manageSalesOrderFields");
var salesOrderId = "";
// hide all fields
view.byId("salesOrderLabel").setVisible(false);
view.byId("salesOrderText").setVisible(false);
view.byId("triggerSalesOrderLabel").setVisible(false);
view.byId("triggerSalesOrderButton").setVisible(false);
$.getJSON("/sap/opu/odata/sap/zlord_my_quotation_srv/QuotationHeaderSet('" + quotationId + "')",
function(data) {
alert("enterHere");
salesOrderId = data.d.SalesOrder;
alert(salesOrderId);
if (salesOrderId !== "" ){
view.byId("salesOrderLabel").setVisible(true);
view.byId("salesOrderText").setVisible(true);
}else{
view.byId("triggerSalesOrderLabel").setVisible(true);
view.byId("triggerSalesOrderButton").setVisible(true);
view.byId("triggerSalesOrderButton").detachPress(sap.ui.controller("...").createSalesOrder);
view.byId("triggerSalesOrderButton").attachPress(sap.ui.controller("...").createSalesOrder);
}
});
},
createSalesOrder: function () {
var createSalesOrderDialog = new sap.m.Dialog("createSoDialog", {
title: "Create Sales Order",
icon: "sap-icon://sales-order",
content: [
new sap.ui.core.HTML({content:"<p style='margin:0;padding: 16px;'>Do want to create a sales order?</p>"})
],
buttons:[
new sap.m.Button({
text: "Yes",
press : function() {
var oModel = new sap.ui.model.odata.ODataModel('/sap/opu/odata/sap/zlord_my_quotation_srv/');
var oParameter = {
"QuotationID" : quotationId
};
oModel.callFunction('/CreateSalesOrder', 'GET', oParameter, 'null',
function (oData, oResponse) {
var responseMessage = JSON.stringify(oResponse.body);
var responseMessageStart = responseMessage.search('<d:Message>');
var responseMessageEnd = responseMessage.search('</d:Message>');
responseMessage = responseMessage.substring(responseMessageStart + 11, responseMessageEnd);
//show MessageToast
sap.m.MessageToast.show(responseMessage);
view.byId("triggerSalesOrderLabel").setVisible(false);
view.byId("triggerSalesOrderButton").setVisible(false);
console.log(responseMessage);
},
function (oError) {
sap.m.MessageToast.show('Error - see log');
console.log(oError);
}
);
createSalesOrderDialog.close();
createSalesOrderDialog.destroy();
}
}),
new sap.m.Button({
text: "No",
press : function() {
createSalesOrderDialog.close();
createSalesOrderDialog.destroy();
}
})
]
});
createSalesOrderDialog.open();
}
We didn't edit anything on the next view controller (CreateQuotations.view.controller.js) since it is not relevant for us to show the SO number on that view.
The error is because of this line:
salesOrderId = data.d.SalesOrder;
How to fix?
Step 1 : Check results first in network tab for the call:
/sap/opu/odata/sap/zlord_my_quotation_srv/QuotationHeaderSet('quotationIdId');
Sample:
Step 2: Check the results hierarchy . How?
console.log(data); //in success call
Step 3: Then restructure your statement to something like this
salesOrderId = data.d.results[0].SalesOrder;
Hope this helps!

Select2 - adding custom choice in single selection mode (tags:false)

Is it possible to setup select2 control to accept custom choice
in single selection mode (tags:false) ?
When typing your choice in the search box instead of seeing "No results found",
just see what you typing in and select it by click or pressing enter.
Well - I finally found solution myself.
I override original SelectAdapter adding new choice every time you type in
and delete previous temporary choices:
$.fn.select2.amd.require(['select2/data/select', 'select2/utils'],
function (SelectAdapter, Utils) {
function XSelectAdapter($element, options) {
XSelectAdapter.__super__.constructor.call(this, $element, options);
}
Utils.Extend(XSelectAdapter, SelectAdapter);
XSelectAdapter.prototype.query = function (params, callback) {
var data = [];
var self = this;
var $options = this.$element.children();
var have_exact_match = false;
$options.each(function () {
var $option = $(this);
if (!$option.is('option') && !$option.is('optgroup')) {
return;
}
var option = self.item($option);
if (option.xtemp === true) {
$(this).remove(); // previously typed-in choice - delete
return;
}
if (option.term === params.term) {
have_exact_match = true; // will not choice if have exact match
}
var matches = self.matches(params, option);
if (matches !== null) {
data.push(matches);
}
});
if (!have_exact_match) {
self.addOptions(this.option({selected: false, id: params.term, text: params.term, xtemp: true}));
data.push({selected: false, id: params.term, text: params.term});
}
callback({
results: data
});
};
$('#your_select2').select2({dataAdapter:XSelectAdapter});
});

How to refresh the Table View Data in Titanium Studio

I've created an app using tabBar. I've created a separate search Window Containing a searchBar and a TableView to display the recent search items. Whenever the return event is fired a new window called searchresult.js opens up displaying the data searched. When I click on the back button it goes from searchresult.js-->searchpage.js, but the problem is the table for recent data gets updated in the database but it isn't showing on the table and I've to go to the main page and open the searchpage.js again to see the correct data... Pls help...Thanx in advance
I've used the following code: searchpage.js
//*** Search Field ***
var search = Titanium.UI.createSearchBar({
barColor:'#000',
showCancel:true,
height:43,
hintText:'Name of the part you want to search',
autocorrect:true,
top:0,
});
content.add(search);
//*** Table For Recent Search ***
var db = Titanium.Database.install('car.db','dbversion1');
var sql = db.execute ('SELECT * FROM search_history ORDER BY id DESC LIMIT 0, 10');
var data = [];
while (sql.isValidRow()){
var searchQuery = sql.fieldByName('search_query');
var selectedCategory = sql.fieldByName('selected_category');
var searchID = sql.fieldByName ('id');
data.push({title: searchQuery});
sql.next();
}
var searchTable = Titanium.UI.createTableView({
headerTitle:'RECENT SEARCH',
data: data,
});
Ti.API.info(searchTable.title);
content.add(searchTable);
//Search Action
search.addEventListener('blur', function(e) {
Titanium.API.info('search bar:blur received');
});
search.addEventListener('cancel', function(e) {
Titanium.API.info('search bar:cancel received');
search.blur();
});
search.addEventListener('return', function(e){
var insertSql = db.execute('INSERT INTO search_history (search_query, selected_category) VALUES ("' + search.value + '", 1)');
var win = Titanium.UI.createWindow({
backgroundColor:'#ffffff',
url:'searchresult.js',
title: 'Search Result',
searchValue: search.value
});
Ti.API.info(search.value);
search.blur();
Titanium.UI.currentTab.open(win, {animation:true});
});
// Back Button Action
bckButton.addEventListener('click', function(e){
if (Ti.Android){
win.close();
}else{
win.close({animated:true});
}
});
and searchresult.js
// *** Content ***
var content = Titanium.UI.createView({
backgroundColor:'#fff',
height:'100%',
width:'100%',
layout:'vertical'
});
wrapper.add(content);
var searchQuery = win.searchValue;
var db = Titanium.Database.install('car.db', 'dbversion1');
var sql = db.execute ("SELECT * FROM part_category WHERE part_name LIKE \'%"+ searchQuery +"%\'");
var data = [];
while(sql.isValidRow()){
var partName = sql.fieldByName ('part_name');
var partID = sql.fieldByName ('id');
data.push({title: partName, hasChild:true, id:partID, url:'partsubcategory.js'});
sql.next()
};
var resultTable = Titanium.UI.createTableView({
data : data,
});
content.add(resultTable);
// Back Button Action
bckButton.addEventListener('click', function(e){
if (Ti.Android){
win.close();
}else{
win.close({animated:true});
}
});
you should save the search results in a data structure and fire an event after the search is complete. Any other tables that you want to update should be listening for that event and update when it recieves the event.
I believe there is an example of this in the training documentation for tiBountyHunter
http://docs.appcelerator.com/titanium/latest/#!/guide/Event_Handling

Resources