Unable to move from JQuery.live() to JQuery.on() - jquery-ui

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() {});

Related

Add event handler to element in custom child component for MapContainer

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 ```

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() {});
}

Turbolinks unfriendly?

I totally get why Turbolinks 5 is awesome and if you're reading it, you probably do as well, but I am very frustrated with how badly it plays with the other scripts on the block.
To date, there is no simple explanation (human readable) that shows how to wrap existing jQuery scripts in a way that would allow them to function.
Take for example this one: https://github.com/Bttstrp/bootstrap-switch. It's well written, simple to understand. You load the js and css to your assets pipeline and instantiate it on some page.
# view.html.erb
<input type="checkbox" class="switch"> switch button
<script type="text/javascript">
$(".switch").bootstrapSwitch();
</script>
you go to view.html, click another page, click back and you see two buttons.
Next, you spend 5 hours looking for a way to have Turbolinks load the instance of bootstrapSwitch only once if not loaded before. Well, even if you do, the functionality will be gone. Clicking it will not work.
$(document).on("turbolinks:load", function()... will load it on every Turbolink visit, and for now, the only way I could make it work and not create duplicates was to disable cache on view.html with
<%= content_for :head do %>
<meta name="turbolinks-cache-control" content="no-cache">
<% end %>
Which feels kinda stupid.
I think it all has something to do with using idempotent - https://github.com/turbolinks/turbolinks#making-transformations-idempotent but how do you practically do this?
Could someone please take this simple plugin as an example and share a simple, elegant solution for making it work which we can then reproduce with other scripts?
Developing apps with Turbolinks does require a particular approach in order to get things running smoothly. Due to differences the way pages are loaded and cached, some patterns of running scripts won't behave in the same way with Turbolinks vs. without. This may seem unfriendly at first, and the "gotchas" can be frustrating, but I've found that with a little understanding, it encourages more organised, robust code :)
As you have figured out, the problem with duplicate switches is that the plugin is being called more than once on the same element. This is because Turbolinks caches a page just before navigating away from it, and so the cached version includes any dynamically added HTML[1] e.g. stuff added via plugins. When navigating back/forward, the cached version is restored, and the behaviour is duplicated :/
So how to fix this? When working with code which adds HTML or event listeners, it is generally a good idea to teardown behaviours before the page is cached. The Turbolinks event for that is turbolinks:before-cache. So your setup/teardown might be:
// app/assets/javascripts/switches.js
$(document)
.on('turbolinks:load', function () {
$('.switch').bootstrapSwitch()
})
.on('turbolinks:before-cache', function () {
$('.switch').bootstrapSwitch('destroy')
})
This is a bit difficult to test since all the setup and teardown is done in event handlers. What's more, there maybe many more cases like this, so to prevent repitition, you may want to introduce your own "mini-framework" for setting up and tearing down functionality. The following walks through creating a basic framework.
Here's is what we'll aim for: calling window.App.addFunction with a name and a function registers a function to call. That function gets the elements and calls the plugin. It returns an object with a destroy function for teardown:
// app/assets/javascripts/switches.js
window.App.addFunction('switches', function () {
var $switches = $('.switch').bootstrapSwitch()
return {
destroy: function () {
$switches.bootstrapSwitch('destroy')
}
}
})
The following implements addFunction, storing added functions in the functions property:
// app/assets/javascripts/application.js
// …
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
}
}
We'll call each function when the app initializes, and store the result of each function call in the results array if it exists:
// app/assets/javascripts/application.js
// …
var results = []
window.App = {
// …
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
}
}
Tearing down the app involves destroying calling destroy (if it exists) on any results:
// app/assets/javascripts/application.js
// …
window.App = {
// …
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
Finally we initialise and teardown the app:
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
So to put this all together:
;(function () {
var results = []
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
},
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
},
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
})()
Functions are now independent of the event handler that calls them. This decoupling has a couple of benefits. First it's more testable: functions are available in window.App.functions. You can also choose when to call your functions. For example, say you decide not to use Turbolinks, the only part you'd need to change would be when window.App.init is called.
[1] I think this is better than the default browser behaviour (where pressing "Back" returns the user back to the page as it was when it was first loaded). A Turbolinks "Back" returns the user back to the page as they left it, which is probably what a user expects.

View Renders before Knockout Binds Data

I am sure this has been asked before but alas I can't find it - any help to point me in the right direction would be greatly appreciated.
I am loading a view / view model with Durandal and during the Activate method I am calling for a single record to be fetched and then displayed. I am getting the record back (checked the XHR response and all data comes back ok) but I can only assume that the view is loading before the data is ready to display. No JS errors, no errors at all for that matter, but just a big whitespace in the middle of my screen, even where I know data should show...
ViewModel :
define(['durandal/system', 'services/logger', 'services/datacontext'], function (system, logger, datacontext) {
var aForm = ko.observable();
var initialized = false;
function activate(routeData) {
var id = parseInt(routeData.id);
return refresh(id);
};
function refresh(id) {
return datacontext.getaFormById(id, aForm);
}
var vm = {
activate: activate,
aForm: aForm,
title: "THE TITLE DISPLAYS JUST FINE"
}
return vm;
});
View :
<h3 class="page-title" data-bind="text: title"></h3>
<div class="container-fluid" data-bind="with: aForm">
<div class="row-fluid">
<div class="span12">
<strong>Testing</strong>
<strong data-bind="text: id"></strong>
</div>
</div>
<h2 data-bind="text: description"></h2>
<h2 data-bind="text: checkType().description"></h2>
</div>
I threw in the Testing strong and it doesn't even render, only the title does. Can anyone point me in the right direction? I would create a fiddle but it works when I hardcode the data in the ViewModel.
EDIT :
Ok after digging into it a bit more it is possible I am not returning the object properly, specifically in the data.entity return type. Would this cause the issue?
var getaFormById = function (aFormId, aFormObservable) {
var query = EntityQuery.from('aForms')
.where('id', '==', aFormId);
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (aObservable) {
aObservable(data.entity);
}
logger.log('Retrieved selected [aForm] from remote data source', data, system.getModuleId(datacontext), true);
}
}
You are not correctly filling your observable in your executeQuery success callback that is why your UI is not rendered correctly.
From the executeQuery documenation the successFunction called with the following argument
successFunction([data])
data Object
results Array of Entity
...
So you need to fill your observable from the data.results array and not form data.entity (which just returns undefined):
if (aObservable) {
aObservable(data.results[0]);
}
You can tell Durandal to hold off binding by returning a jQuery promise() from the activate function.
See this video by Ryan Keeter for the explanation: http://www.youtube.com/watch?v=w_XjWg6xL8I

JQuery function function wont work when page is returned via Partial view

I have this jquery function placed in my .js file.... As long as my page is loaded this works... but when i changed it to return as a partial view.... this alert wont work anymore... How is it gonna be working again ?
$(document).ready(function () {
var msg = '#ViewBag.Message';
if (msg == '1')
alert("New Time Shift has been saved.");
});
In my controller action...
if (Request.IsAjaxRequest())
return PartialView("_RecordList", userRecord); //alert wont work here...
return View(userRecord); //this will return the whole view thus the alert works here
The document ready function will not be executed from ajax requests.
You could extract the javascript code to a separated function part of the whole view. You can also create a callback function to be executed when the partial request succeeds, that will also call it:
$(document).ready(function () {
var msg = '#ViewBag.Message';
initFunction(msg);
});
function initFunction(msg){
if (msg == '1')
alert("New Time Shift has been saved.");
}
function partialRequestSuccess(data){
//store the message somewhere in the partial view, like a hidden div and get it using jquery
var msg = ...
initFunction(msg);
}
Then you could set the complete callback of the ajax request to call the success callback we have just created. If you are using the MVC ajax helpers, there is a Success parameter that you can set like:
#using(Ajax.BeginForm(new AjaxOptions{ OnSuccess = "partialRequestSuccess" }))
I have this jquery function placed in my .js file....
var msg = '#ViewBag.Message';
This is server code, that works only in *.cshtml files, this is minimum one problem.

Resources