Add event handler to element in custom child component for MapContainer - react-leaflet-v3

I am using react-leaflet (v3.2.5) with leaflet (v1.8.0). We need pretty custom zoom buttons and one additional 'home' like button on the map, which look like this:
So I am trying to create a custom controls component like this (<FeatherIcon /> is another component from our codebase):
function MapControls() :JSX.Element {
const map = useMap();
return <div className="leaflet-top leaflet-right vertical-controls clickable">
<span className="control-icon-box" onClick={() => { console.log('in', map); map.zoomIn();}}><FeatherIcons iconId='plus-circle' className="feather vertical-control-icon" /></span>
<span className="control-icon-box" onClick={() => { map.zoomOut();}}><FeatherIcons iconId='minus-circle' className="feather vertical-control-icon" /></span>
<span className="control-icon-box"><FeatherIcons iconId='target' className="feather vertical-control-icon" /></span>
</div>
}
I want to use it like this
<MapContainer center={[buildingsData.geoCenter.y, buildingsData.geoCenter.x]} zoom={buildingsData.geoCenter.zoom} scrollWheelZoom={true}
zoomControl={false} zoomDelta={0.5}>
<!-- other stuff -->
<MapControls />
</MapContainer>
But the click handler is not added in the MapContainer context (like mentioned here).
Do I need to use something like
const minusRef = useRef(null);
useEffect(() => {
function minusClick() { /* doing stuff */ }
if (minusRef.current !== null) {
minusRef.current.addEventListener('click', minusClick);
}
return () => {
if (minusRef.current !== null) {
minusRef.current.removeEventLister('click', minusClick);
}
}
}, [map]);
This solution here is not really clear to me:
I am not sure how to achieve that using your approach where
react-leaflet's wrapper ZoomControl is not a child of Map wrapper when
you try to place it outside the Map wrapper.
However, for a small control like the ZoomControl, an easy solution
would be to create a custom Zoom component, identical to the original,
construct it easily using the native css style and after accessing the
map element, invoke the zoom in and out methods respectively.
In the below example I use react-context to save the map element after
the map loads:
const map = mapRef.current.leafletElement;
setMap(map); }, [mapRef, setMap]); ```
and then here use the map reference to make a custom Zoom component
identical to the native (for css see the demo):
``` const Zoom = () => { const { map } = useContext(Context);
// etc ```

Related

Binding click event with css class in angular 7

Is there any way to bind click event with class name in angular 7 like we used to with jquery?
In jquery I was able to do this
$('.my-class-name').on('click',function(){});
Is there something similar standard way in angular 7?
You can directly add the click event to the HTML tag which has the required class
In your case:
****** Method 1 ******
lets say you are writing the above className on a button, then
<button class="my-class-name" (click)="yourFunction($event)">
Your button
</button>
If you want to add it from the TS File only, then you can use the following method:
*** Method 2 ******
**STEP 1:**
// Create a view child template reference
#viewChild('someName', {static: false})
private someNameVC: ElementRef
**STEP 2:**
// Add this 'someName' to the HTML Element in .html FIle
<button class="my-class-name" #someName>
Your button
</button>
**STEP 3:**
// In your component.ts file, whenever you want to add the event listener, create a function and do the following :
yourMainFunction() {
if (someCondition) {
this.addEventListenerToButtonMethod();
......rest of the code
}
}
addEventListenerToButtonMethod() {
const parentElement: HTMLElement =
this.someNameVC.nativeElement.parentNode as HTMLElement;
// Here you can add your event listener
parentElement.addEventListener('click', function() {});
}

Angular 7 - Copying a List returned from SQL into an Array

I would like to convert results from an SQL method to an array so that I can use the filter option on the array in Angular 7.
I am still getting my feet wet in Angular (7) and I'm trying to set up a nested dropdown/select list where the first select list is for "Departments" and the selected value will return a result set for my "DeptTypes" dropdown/select list.
I am currently returning the data by sending the selected value (id) call my "changeData(id: number)" event in the ...component.ts. It successfully returns data, but there is sometimes a problem with the dropdown not always populating. It appears that it is a performance or speed issue due to always making the call to the SQL backend. So, I would like to return the data as an array so that I can use the .filter command, but there seem to be no option for that. So, what would be the best solution to my lil' problem? I was thinking about pushing the returned list into an [] array variable that would then allow me to use "push" but I cannot figure out the coding to do that. If there is a better way, by all means, enlighten me. Thanks.
// the calls in my component.ts
opportunityList() { // this is the for the first <select ...>
this.dashboardService.getOpportunities()
.subscribe((opportunities: Opportunities) => {
this.opportunities = opportunities;
},
error => {
// this.notificationService.printErrorMessage(error);
});
}
typeList(id: number) { // nested <select ...> that needs to populate
this.dashboardService.getTypesByOpportunityId(id)
.subscribe((types: Types) => {
this.types = types;
},
error => {
// this.notificationService.printErrorMessage(error);
});
}
changedata($event) { // called after selecting a value in the 1st <select ...>
// to remove previous selected items from second dropdown
//this.finaldata.splice(0);
// filter items and pass into finaldata
//this.finaldata = this.type2.filter(x => x.areaId == $event.target.value);
this.typeList($event.target.value);
this.finaldata = this.types;
}
// the HTML
<label for="areaId">Departments</label>
<select id="areaId" (change)="changedata($event)" [(ngModel)]="opportunityList" class="form-control">
<option *ngFor="let opp of opportunities" [value]="opp.areaId">{{ opp.areaName }}</option>
</select><br />
<label for="typeId">Department Area Types</label>
<select id="typeId" class="form-control">
<option *ngFor="let typeobj of finaldata" [value]="typeobj.typeId">{{ typeobj.typeName}}</option>
</select>
my ...service.ts
getTypesByDepartmentId(id: number): Observable<Types> {
const headers = new Headers();
headers.append('content-Type', 'application/json');
const authToken = localStorage.getItem('auth_token');
headers.append('Authorization', `Bearer ${authToken}`);
return this.http.get(this.baseUrl + '/dashboard/GetTypesByDepartmentId/' + id, { headers })
.map(response => response.json())
.catch(this.handleError);
}
the controller action
#region api/dashboard/GetTypesByDepartmentId
[HttpGet("{id}")]
public async Task <IActionResult> GetTypesByDepartmentId([FromRoute]int id)
{
// retrieve all revenue types
var model = await (from t in _appDbContext.Type where t.AreaId == id
select new
{
t.TypeId,
t.TypeName
}).ToArrayAsync();
return Ok(model);
}
#endregion
The code above, thru the service.ts returns the results, it is not always populating the "typeId". It's a "hit or miss." Sometimes the data is there and sometimes it's just blank.
I would like to go with returning all of the department area types and using an array and the "filter" command. for example:
this.finaldata = this.typeList.filter(x => x.areaId == event.target.value);
in the component.ts itself or a more proper way to handle this issue since "filter" does not work on this call.
Looks like I get to answer my own question. I can actually filter my returned observable ... the issue was that I was defining a variable that used an interface, reflecting my model. If I remove that and make it "any", I can then push it to an array:
types: any;
type2: Types[] = [];
public finaldata: any[] = [];
typeList() {
this.dashboardService.getTypes()
.subscribe((types: Types) => {
this.types = types;
this.type2.push({ areaId: this.types.areaId, typeId: this.types.typeId, typeName: this.types.typeName, areaName: this.types.areaName });
},
error => {
// this.notificationService.printErrorMessage(error);
});
}
changedata($event) {
// to remove previous selected items from second dropdown
this.finaldata.splice(0);
// filter items and pass into finaldata
this.finaldata = this.types.filter(x => x.areaId == $event.target.value);
}
So, I just call my typeList() in my "ngInit()" and it is taken care of.

Aurelia + Select2 custom element not propagating selected changed

I have created a custom element in Aurelia.
import {bindable, inject, customElement, bindingMode} from 'aurelia-framework';
import 'select2';
import * as $ from 'jquery';
import {BindingSignaler} from "aurelia-templating-resources";
#customElement('select2')
#inject(Element, BindingSignaler)
export class Select2CustomMultiselect {
#bindable name = null; // name/id of custom select
#bindable selected = null; // default selected values
#bindable ({defaultBindingMode: bindingMode.oneWay, attribute:"options"}) source:Array<{id:number, name:any}>= []; // array of options with id/name properties
#bindable placeholder = "";
#bindable allow_clear = true;
private $select2: $;
constructor(private element, private signaler:BindingSignaler) {
}
attached() {
let $select = $(this.element).find('select');
this.$select2 = $select.select2({theme: 'bootstrap', placeholder: this.placeholder});
// on any change, propagate it to underlying select to trigger two-way bind
this.$select2.on('change', (event) => {
if (event.originalEvent) { return; }
const select2Value = this.$select2.val();
if(select2Value == this.selected) { return; }
// dispatch to raw select within the custom element
var notice = new Event('change', {bubbles: true});
event.target.dispatchEvent(notice);
});
this.$select2.val(this.selected);//.trigger('change');
}
selectedChanged(newValue,oldValue){
console.log(newValue);
}
detached() {
$(this.element).find('select').select2('destroy');
}
}
And it's template:
<template>
<select value.two-way="selected" name.one-way="name" id.one-way="name" class="form-control" data-allow-clear.one-way="allow_clear" size="1">
<option></option>
<option repeat.for="src of source" model.bind="src.id">${src.name & t}</option>
</select>
</template>
I use the control like this:
<select2 name="payingBy" selected.two-way="model.countryId & validate" options.bind="countries" placeholder="${'Select' & t}" allow_clear="true"></select2>
And model is:
countries:Array<{id:number, name:string}> = [{id:1, name:"USA"}, {id:2, name:Canada'}];
model.countryId: number;
Now, everything works fine if I change the select and on initial binding.
But if i change the model.countryId from ie. 1 to 2, the change is not reflected in the select control, the control still displays "USA" as like 1 is selected.
Because 'selected' property is bind two-way I would expect it to update the select when it change. But it does not. Why?
What Am I doing wrong?
Please help
Ok, I implemented it like in this post:Custom Select2 Aurelia component
And it works perfectly.
That is because you are using the data version which expects an object, but you have set your select to work with the id value only. So you should use the val to pass the id.
selectedChanged(newValue, oldValue) {
console.log(newValue);
if (this.select2) {
this.select2.select2({
val: newValue, // << changed this from data: newValue
theme: 'bootstrap',
placeholder: this.placeholder
});
}

Passing data between angular controllers

Below is the sample code where I am trying to pass/sync the value between the two controllers. For the same in view I have a textbox and two place holders for showing the value of the textbox.
I have somehow achieved the behavior but still could not figure out why the code is not working in one place and doing well in another. I have mentioned 2 ways, one is working(second way) and one is not(first way).
I have used following:
1 service
2 controllers
1 view
Service
mcApp.factory('mcService', function($rootScope){
var service = {};
//variable 1
service.message = 'Default';
//function 1
service.getMessage = function(){
return this.message;
}
//function 2
service.setMessage = function(msg){
this.message = msg;
}
return service;
});
Controller - First way - Not working
mcApp.controller('mcController1', function ($scope, mcService) {
$scope.message = mcService.getMessage();
$scope.setmsg = function(msg){
mcService.setMessage(msg);
}
});
mcApp.controller('mcController2', function($scope, mcService){
$scope.message = mcService.getMessage();
});
View - First way - Not working
<div ng-app="mcApp">
<div ng-controller="mcController1">
<input type="text" ng-model="message" ng-change="setmsg(message)">
<p ng-bind="message"></p>
</div>
<div ng-controller="mcController2">
<p ng-bind="message"></p>
</div>
</div>
In above code I am updating the value of scope variable "message" by calling the service method "getMessage()" in "mcController2". However it is not getting updated in the view.
Below is the code where instead of directly using the service method "getMessage()" in "mcController2" I have assigned the service to the scope variable.
Controller - Second way - Working
mcApp.controller('mcController1', function ($scope, mcService) {
$scope.message = mcService.getMessage();
$scope.setmsg = function (msg) {
mcService.setMessage(msg);
}
});
mcApp.controller('mcController2', function ($scope, mcService) {
$scope.service = mcService;
});
View - Second way - Working
<div ng-app="mcApp">
<div ng-controller="mcController1">
<input type="text" ng-model="message" ng-change="setmsg(message)">
<p ng-bind="message"></p>
</div>
<div ng-controller="mcController2">
<p ng-bind="service.message"></p>
</div>
</div>
Please NOTE : Using the $rootScope.$broadcast in service and $scope.$on in controller in First way also gets the work done. But I am not able to figure out why Fisrt way is not working.
the first way not work because you pass the primitive object --- pass by value
-- pass by value if the old value has change the variable will not be reflect.
the second way worked well because you pass the complex object --- pass by reference .
-- pass by reference if the old value has change the variable will be reflect.
ex :
var x = {
name: "BeSaRa",
country: "Egypt",
like: [
'PHP',
'Javascript',
'MYSQL'
]
};
var v = x.name; // pass by value becouse the string is primitve object
var r = x.like; // pass by reference because the array is Complex Object
// try to change the name below this line
x.name = "Ahmed Mostafa";
console.log(v) // output --> "BeSaRa" it is not reflected by the change
// try to change the like property below this line
x.like.push("AngularJS"); // add AngularJS to the Array
console.log(r) // output ['PHP','Javascript','MYSQL','AngularJS'];
i hope you understand now :D
the primitives types in JavaScript:
Boolean
Null
Undefined
Number
String
Symbol (new in ECMAScript 6)
any thing else Complex :)

Unable to move from JQuery.live() to JQuery.on()

I am loading some html via $.get() into a Jquery-dialog-popup.
Upon clicking a link in the newly inserted html some function should be triggered.
This works with live() but NOT with on().
This works:
$(".remove").live("click", function () {
// enter ok
}
This does not:
$("div").on("click", ".remove", function () {
// or $("#delete").on("click", ".remove", function () {
// or $(".remove").on("click", function () {
// never enters...
});
html:
<div id="delete">
<a class="remove" href="#">link</a>
</div>
The on()-function works in case I am calling it directly from my main-template without loading the content into a dialog-window first via $.get.
To pre-bind events for dynamic content, they have to be bound to a pre-existing element.
So, if the <div id="delete"> is part of the dynamic content, then you shouldn't use it to bind the event. You can, however, bind to the container that the dynamic content is loaded into.
So, if the resulting HTML is:
<div id="contents">
<!-- start template -->
<div id="delete">
<a class="remove" href="#">link</a>
</div>
<!-- end template -->
</div>
Then, your JavaScript can be:
$('#contents').on('click', 'div a.remove', function () {
// ...
});
.live() uses document for this -- an element that exists until reload or redirect -- making the following lines equivalent:
$("a.remove").live("click", function () { /* ... */ });
$(document).on("click", "a.remove", function () { /* ... */ });
I can't see the whole code but I would bet that you're not putting the
$("div").on("click", ".remove", function () {
// or $("#delete").on("click", ".remove", function () {
// or $(".remove").on("click", function () {
// never enters...
}
part ONCE the new code has been inserted into the DOM.
You need to attach the event listeners to the new elements created. Live works because it works for existing and future elements like that.
EDIT:
If you want the click handler to work for an element that gets loaded dynamically, then you need to set the event handler on a parent object (that does not get loaded dynamically) and give it a selector that matches your dynamic object like this:
$('#parent').on("click", ".remove", function() {});

Resources