I'm importing a JSON layer to the stage.
var loadedobject = Kinetic.Node.create(data);
maplayer.add(loadedobject);
Code to change the content what I'm importing seems to do like fine: (BTW: .maplayer2 is the layer I have imported)
maplayer.get('.maplayer2')[0].setScale(browserwidthdifference);
maplayer.get('.maplayer2')[0].setY(browserheight/2);
maplayer.setDraggable(true);
However if do the code below it doesn't work:
maplayer.on('click', function() {
console.log('help');
});
maplayer.get('.maplayer2')[0].on('click', function() {
console.log('help');
});
I have let the background black of the imported layer so I can see the detection. However if I do:
backgroundlayer.on('click', function() {
console.log('help');
});
I get the click responses even if I click on the imported layer which is clearly on top of the other layer. maplayer.moveToTop() doens't work either. All alternations work but the click events won't work with the imported object.
If I create another object in maplayer it will response to it when I click on it, but the imported layer in the layer will still be not detected.
If you save all objects within a group object it will work fine! So when you save things do not do it within a Layer but in a Group object.
Related
I'm trying to trigger some functionality based on the click of a marker on a GeoJSON layer in Leaflet. The eventual functionality I'm trying to implement is a flyout, or scroll out type modal populated from the individual feature's JSON attributes. Essentially, I'm trying to implement the functionality in this Tutsplus Tutorial with dynamic feature content based on the marker click.
I THINK I've figured out most of the pieces I need, but I'm struggling with how to add a data attribute, specifically data-open, to the individual marker. Building on an earlier question of mine I've realized it's not enough to just update a DOM element's CSS, but rather my app should be implementing changes based on data attributes to fully get the functionality I want.
From this question I know that this should be done by extending the L.Icon class that Leaflet provides, but the answer is a bit too terse for my current JS skills. I apologize for this effectively being a "ELI5" of a previously asked question, but I'm not sure where the options and slug come into function. I think they're implied by the question, rather than the answer I'm citing and being set on the marker itself.
Here's a simplified version of the the click handler on my markers, which grabs and zooms to location, gets feature info, and populates that info to a div. The zoom functionality works, as does extracting and placing the feature info, but I'm struggling with how to connect the functionality to trigger the modal and place the div with the feature info over the map.
function zoomToFeature(e) {
var latLngs = [e.target.getLatLng()];
var markerBounds = L.latLngBounds(latLngs);
var street = e.target.feature.properties.str_addr;
document.getElementById('street').textContent = street;
mymap.fitBounds(markerBounds);
//where the modal trigger should be
document.getElementById('infoBox').classList.add('is-visible');
}
Here are the event listeners taken from the linked tutorial, which are currently not firing, but I have them working in a standalone implementation:
const openEls = document.querySelectorAll("[data-open]");
const closeEls = document.querySelectorAll("[data-close]");
const isVisible = "is-visible";
//this is the event I want to trigger on marker click
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
console.log(this);
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
So, where I'm trying to get is that when my markers are clicked, the trigger the modal to appear over the map. So, I think I'm missing connecting the marker click event with the event that triggers the modal. I think what's missing is adding the data attribute to the markers, or some way chain the events without the data attributes. As there's no direct way to add an attribute to the markers, I try to add slug option on my circle markers:
var circleMarkerOptions = {
radius: 2,
weight: 1,
opacity: 1,
fillOpacity: 0.8,
slug: 'open',
}
and If I read the previously asked question's answer correctly, than extending the Icon Class this way should add a data-open attribute.
L.Icon.DataMarkup = L.Icon.extend({
_setIconStyles: function(img, name) {
L.Icon.prototype._setIconStyles.call(this, img, name);
if (options.slug) {
img.dataset.slug = options.slug;
}
}
});
A stripped down version of my code is here (thanks #ghybs). My full implementation pulls the markers from a PostGIS table. It's a bit hard to see in the Plunker, but this code adds my class to my modal, but doesn't trigger the functionality. It does trigger the visibility if the class is manually updated to modal.is-visible, but the current implementation which renders modal is-visbile doesn't, which I think is because the CSS is interpreted on page load(?) and not in response to the update via the dev tools, while the concatenated css class matches extactly(?). When I do trigger the modal via the dev tools, the close modal listeners don't seem to work, so I'm also missing that piece of the puzzle.
So, it's a work-around to setting the data attribute, but I realized I was shoe-horning a solution where it wasn't needed. Assuming someone ends up with the same mental block. Appropriate listeners on the modal close button and another function passed to the existing marker click listener produce the desired functionality.
const closeM = document.querySelector(".close-modal");
closeM.addEventListener("click", closeMe);
var modal = document.getElementById('infoBox');
and
function modalAction(){
modal.style.display = 'block';
}
function closeMe(){
modal.style.display = 'none';
}
I have a 3D model being rendered on my site through an image rotator .xml config file. This feature works but I am attempting to render a completely different .xml in place of the previous file through a JS on change event.
I have done a fair bit of reading in order to solve this issue, although I have not found an answer. I have already tried to make the JQuery script into a function as seen below:
function updateModel(xml_file_path) {
console.log('updating room model...');
console.log('xml_file_path: ' + xml_file_path);
// clear past model
$("#wr360PlayerId").empty();
jQuery('#wr360PlayerId').rotator({
licenseFileURL: 'license.lic',
configFileURL: '/static/360_assets/' + xml_file_path,
graphicsPath: '/static/img/basic',
zIndexLayersOn: false,
responsiveBaseWidth: 600,
responsiveMinHeight: 0,
googleEventTracking: false,
});
console.log('rendering: ' + xml_file_path);
}
// clears the old model then updates the configFileURL to the new model
This was successful in clearing the previous model although when I inspect the new model the images used by the image rotator are not being loaded and nothing is displayed.
wr360 documentation
I've also read through the documentation for wr360 above and found a few different ways of loading the image rotator on my site. I've gone through each and attempted to make it update using similar methods as JQuery but each had their own oddities that were difficult to overcome.
There's not much to code to this as for most of it is created dynamically on page load, but I'll try to provide all code necessary below:
js
function updateModel(xml_file_path) {
console.log('updating room model...');
console.log('xml_file_path: ' + xml_file_path);
// clear past model
$("#wr360PlayerId").empty();
jQuery('#wr360PlayerId').rotator({
licenseFileURL: 'license.lic',
configFileURL: '/static/360_assets/' + xml_file_path,
graphicsPath: '/static/img/basic',
zIndexLayersOn: false,
responsiveBaseWidth: 600,
responsiveMinHeight: 0,
googleEventTracking: false,
});
console.log('rendering: ' + xml_file_path);
}
$(document).ready(function(){
$('#rooms').on('change', function() {
updateModel(room.xml_path);
console.log('model updated');
});
});
// truncated for simplicity
html
<div id="wr360PlayerId" class="wr360_player" style="background-color:#FFFFFF;">
</div>
The xml file path is getting passed correctly (checked by the console.log('xml_file_path: ' + xml_file_path);) it just doesn't render the second rotator.
$('#rooms') is a select field, and room.xml_path is the selected rooms .xml file path. With this being said, ideally, the on change event would show the selected model and if the selection changes again it should render the new model (instead of nothing like it currently does).
Either I am missing something or it is impossible to update a model without refreshing the page, either way, any help is appreciated!
You can actually use,
apiObj.reload(xml_path);
to simply reload the image rotator with a new xml file path.
I'm not sure why I can't get the button element using my UI hash. This is what my Layout looks like:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
ui: {
btnSave: "#btnSave"
},
events: {
"click #ui.btnSave": "onSave"
},
onInitialize: function () {
this.listenTo(App.vent, "DisableSaveButton", function(val) {
this.disableSaveButton(val);
},this);
},
disableSaveButton: function () {
this.ui.btnSave.prop("disabled",val).toggleClass("ui-state-disabled",val);
},
onSave: function () {
alert("saved!");
}
})
In VS2013, when my breakpoint hits the line inside disableSaveButton method, I entered $("#btnSave") into the Watch window and I was able to get the element back. I could tell because it had a length of 1. From this, I know the button is rendered. However, if I enter this.ui.btnSave into the Watch window, I would get an element with length of 0.
My BaseLayout object is basically a custom object extended from Marionette.Layout
Marionette version: 1.8.8
Any ideas why I can't find the button element using this.ui.btnSave?
Thanks in advance!
Got some help from a coworker and the issue might be because the element is out of scope. Basically, inside the Layout object, 'this' does not contain the element. We were able replace 'this.ui.btnSave' with '$("#btnSave",this.buttonset.el)' and that works fine. buttonset is the region that actually contains the html element.
This seems like an inconsistency because even though the ui hash didn't work, the click event utilizing the ui hash did work.
UPDATE 6/3/2015:
Another coworker of mine provided a better solution. Basically, in my Layout I use a display function to display my view. It looks something like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
}
})
Basically, I'm saying to set the html of my region, which is this.buttonset.el, to my template's html. As of now, my layout doesn't know any of the elements inside the region. It just contains a region which displays the elements. So there is some sort of disconnect between my layout and the elements in my region.
The correct solution, as opposed to my earlier workaround, is to simply add the following line of code at the end:
this.bindUIElements();
From Marionette Annotated Source:
This method binds the elements specified in the “ui” hash inside the
view’s code with the associated jQuery selectors.
So this final code looks like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
this.bindUIElements();
}
})
With this, I was able to finally able to retrieve my element using this.ui.btnSave.
I have a Single Page Application with a lot of stuff in it, using durandal. On one page I have a link that leads to a different page where 3d model is rendered. OrbitControls are used to make model turn etc. That takes away my default left click and right click. After leaving that page, it still keeps mouse bindings and my left click and right click become useless for some uses like - selecting an tag meaning that input tags cannot be accessed again.
I could release bindings and reset them if I knew how. There is a deactivate function which is called when that 3d window is closed, but I have no idea what piece of code to write there. Any help would be extremely useful. I doubt any code will be of any use so I won't put any.
Thank you!
Due to request, here is simplified viewmodel:
define(['services/logger'], function (logger) {
var vm = {
attached: attached
};
return vm;
function attached(view) {
var camera, cameraTarget, scene, renderer, controls;
init();
animate();
function init() {
...
controls = new THREE.OrbitControls(camera);
...
}
function animate(){...}
function render(){...}
}
}
View is extremely complicated, but pasted here in full:
<div id="canvasDiv" style="overflow: hidden; width:100%; height:100%">
</div>
Actually, it would probably be helpful to include your code to setup OrbitControls (I'm not familiar with it).
The best approach to this whole issue would probably be to write a Knockout custom binding (google if you don't know them). A custom binding is a great place to abstract away DOM manipulation, in your case for setting up OrbitControls.
Let's assume that you have a div on which you set up OrbitControls. You could then do something like the following:
HTML:
<div data-bind="myOrbitControlsBinding: { someSetting: true; someOtherSetting: false }"></div>
JavaScript:
ko.bindingHandlers.myOrbitControlsBinding = {
init: function (element, valueAccessor) {
var settings = ko.utils.unwrapObservable(valueAccessor());
setupOrbitControlsOnElement(element, settings); // This should be your setup code for OrbitControls
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
// Here, you should unbind the event handlers for mouse clicks. How you do this depends on how OrbitControls sets them up. Please refer to their documentation for this. Maybe there is a generic dispose function?
disposeOrbitControls(element);
});
}
}
Edit:
Ah, I didn't realize you use Three. I've quickly scanned their documentation to see if they use some sort of input module that captures the events. They don't seem to. Which probably means that some where in your code, there is the keyword 'addEventListener' (search for it). This will be where the events are caught.
Your view probably has a viewmodel attached to it since you're using durandal. Inside the viewmodel, add a 'deactivate' method (and return it). In this method, you need to remove the event listener again. You probably already guessed it, but the method is called removeEventListener (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.removeEventListener for an explanation)
I still highly recommend that you create a custom binding to setup Three. This will give you much more control over creation/deletion than you might have now. If you don't want to, make sure that Three is initialized inside the viewmodel as well, in the activate method.
Let me know if that helped, otherwise some viewmodel / three init code would be helpful.
I'm using ExtJS 3.2.1 and I need a component almost identical to the bundled HtmlEditor, with one exception: it must start editing the HTML source code directly. The reason I don't use a normal TextArea is that the user should be able to preview the result of his actions before submitting.
I've tried calling toggleSourceEdit(), as per ExtJS documentation, with no success. Debugging, I see that the editor object has the sourceEditMode property set to true, and the Source Edit button seems as if it was "pressed", but clicking on it does not render the typed HTML, and clicking it again goes to the Source Mode.
I've tried calling toggleSourceEdit() after the container show() method, on the container afterLayout listener and on the editor afterRender listener. I've tried also calling it on another button that I added to the container. The result is the same on every try.
The only other option I see is updating ExtJS to 3.3.0, but I haven't seem anything related on the changelogs. Either way, it's going to be my next step. EDIT: The app had another problems when updating, we'll make a bigger effort to update later. As of right now, we are using the HtmlEditor in its original setting.
Thanks!
ran into the same problem (using 3.3.0 by the way)
stumbled upon a fix by dumb luck. i have no idea why this works, but second time is the charm. call it twice in a row to achieve the desired effect..
HTMLEditor.toggleSourceEdit(true);
HTMLEditor.toggleSourceEdit(true);
hope that helps!
Rather calling toggleSourceEdit(), try to setup the configuration while you create HtmlEditor Object
Using toggleSourceEdit() caused some problems for me. One was that this seemed to put the editor somewhere in limbo between source edit and WYSIWYG mode unless I used a timeout of 250ms or so. It also puts the focus in that editor, and I don't want to start the form's focus in the editor, especially since it's below the fold and the browser scrolls to the focused html editor when it opens.
The only thing that worked for me was to extend Ext.form.HtmlEditor and then overwrite toggleSourceEdit, removing the focus command. Then adding a listener for toggling to the source editor when the component is initialized. This is for Ext 4.1 and up. For older versions, replace me.updateLayout() with me.doComponentLayout().
var Namespace = {
SourceEditor: Ext.define('Namespace.SourceEditor', {
extend: 'Ext.form.HtmlEditor',
alias: 'widget.sourceeditor',
initComponent: function() {
this.callParent(arguments);
},
toggleSourceEdit: function (sourceEditMode) {
var me = this,
iframe = me.iframeEl,
textarea = me.textareaEl,
hiddenCls = Ext.baseCSSPrefix + 'hidden',
btn = me.getToolbar().getComponent('sourceedit');
if (!Ext.isBoolean(sourceEditMode)) {
sourceEditMode = !me.sourceEditMode;
}
me.sourceEditMode = sourceEditMode;
if (btn.pressed !== sourceEditMode) {
btn.toggle(sourceEditMode);
}
if (sourceEditMode) {
me.disableItems(true);
me.syncValue();
iframe.addCls(hiddenCls);
textarea.removeCls(hiddenCls);
textarea.dom.removeAttribute('tabindex');
//textarea.focus();
me.inputEl = textarea;
} else {
if (me.initialized) {
me.disableItems(me.readOnly);
}
me.pushValue();
iframe.removeCls(hiddenCls);
textarea.addCls(hiddenCls);
textarea.dom.setAttribute('tabindex', -1);
me.deferFocus();
me.inputEl = iframe;
}
me.fireEvent('editmodechange', me, sourceEditMode);
me.updateLayout();
}
})
}
Then to use it:
Ext.create('Namespace.SourceEditor', {
/*regular options*/
listeners: {
initialize: function(thisEditor) {
thisEditor.toggleSourceEdit();
}
}
});
htmlEditor.toggleSourceEdit(true);
one time should be enough if you do this listening to the afterrender event of the editor.