calling jQuery .remove() on custom UI widget causes infinite loop - jquery-ui

I have created custom jQuery UI widget called uiPopover, very similar to UI-dialog (in fact, most of the code is copy-paste from it). This widget has a custom destroy method that hides the widget and removes it from the DOM. Again, it's pretty much copy-paste from UI-dialog.
destroy: function() {
var self = this;
if (self.overlay) {
self.overlay.destroy();
}
self.close();
self.element
.removeData('popover');
self.uiPopover.remove();
console.log('afterRemove')
return self;
},
The weird thing is that this causes an infinite loop that throws some errors:
$('#element').popover();
$('#element').remove();
As far as I can see, the problem is that when I call .remove(), it automatically calls destroy() on my widget (this is built-in in jQuery UI) and the destroy methodd tries to call remove() again on my element, and then that tries to call destroy() again and so on..
However, the weird thing is that this doesn't happen with UI dialog. So when I do this:
$('#element').dialog();
$('#element').remove();
Everything is okay... There must be something wrong with my plugin, but I cannot figure out what.
Here is the full source of my plugin: https://gist.github.com/2208569

There's not much you can do about the recursive call to destroy(), aside from modifying jQuery UI itself. You can, however, break the chain by preventing remove() from being called again:
destroy: function() {
var self = this;
if (self.overlay) {
self.overlay.destroy();
}
self.close();
if (self.element.data("popover")) {
self.element.removeData("popover");
self.uiPopover.remove();
}
return self;
}
Note in passing that you don't have to copy and paste code in order to augment existing widgets, as the widget framework supports prototype inheritance. It would be interesting to know if your problem still occurs if you have your widget derive from $.ui.dialog instead of duplicating its code base.

Related

Are listeners like <svelte:window on:keydown={handleKeydown}/> component scoped or global?

If I add a key listener inside a component using
<svelte:window on:keydown={handleKeydown}/>
as per https://svelte.dev/tutorial/svelte-window , is the listener removed when the component is destroyed? I want to be sure I am not causing memory leaks of any kind.
Yes, if you want, you can look at the generated JS code and you will find this:
m(target, anchor) {
dispose = listen(window, "keydown", /*handleKeydown*/ ctx[0]);
},
...
...
d(detaching) {
dispose();
}
The m() method is the mounting function and the d() is the destroy. You can see the listener is added in the m() and then it is removed in the d(). This is simplified but you can find out more by investigating the Svelte source code :)
Good luck!

programatically adding an on-tap attribute referencing a dart function for a HtmlElement

I have a dart function:
_addSelection(HtmlElement ele){
ele.classes.add("selection");
}
I would either want 1 or 2 things to occur, either A) execute an on-tap and on-track function given the selection class.... OR Dynamically add the on-tap and on-track attributes referencing reflected dart functions.
I have 2 functions:
#reflectable
onTap(CustomEventWrapper cew, params){
//...
}
#reflectable
onTrack(CustomEventWrapper cew, params){
//...
}
I was looking at the HtmlElement class and documentation and I wasnt quite understanding how to do this.
Edit if I were using jQuery and Javascript, I would be doing something as simple as:
$(document).on("tap", function(){});
$(document).on("track", function(){});
Edit2 Added Angular Dart because both designs leverage Dart backend and markup front end.
You could do:
_addSelection(HtmlElement ele) {
ele.classes.add("selection");
ele.onTouchEnd.listen((TouchEvent touch) {
...
});
}
That would give you something close to the tap event. If you wanted to get really fancy you could also listen for onTouchStart and only call your onTap handler when the time between the start and end is small.

Querying child elements of a component when useShadowDom = false

I'm using useShadowDom: false with my components in an attempt to support more browsers without having to use the troublesome web_components polyfill. With the shadow DOM enabled, I would do something like this:
void onShadowRoot(ShadowRoot root) {
root.querySelector('.btn-go-back').onClick.listen((e) {
if (goBackHandler != null) {
goBackHandler();
}
});
}
onShadowRoot would run after my component's template was loaded and therefore all the components elements exist in the DOM. Without Shadow DOM enabled, I inject the component's root element in the constructor, and do something like this:
MyComponent(this._root){
_root.querySelector('.btn-go-back').onClick.listen((e) {
if (goBackHandler != null) {
goBackHandler();
}
});
}
This doesn't work because the component's template hasn't been loaded into the DOM yet, so the root element doesn't have any children to query yet.
I've tried implementing AttachAware, and querying the root element in the attach() method, and the template isn't loaded at that point either.
So, if I'm not using the shadow DOM, how can I know when the template has been loaded into the DOM so I can query elements within my component?
Edit
Attempting to use ShadowRootAware and onShadowRoot with useShadowRoot: false will result in the following error if you try to query against the provided ShadowRoot object:
Unsupported operation: Not supported
STACKTRACE:
#0 EmulatedShadowRoot._notSupported (package:angular/core_dom/emulated_shadow_root.dart:5:21)
#1 EmulatedShadowRoot.querySelector (package:angular/core_dom/emulated_shadow_root.dart:32:63)
I also tried a combination of:
Injecting the root element in the constructor and
querying against the root element within onShadowRoot which worked, kinda, but now I'm seeing this in the console output:
[WebPlatformShim] WARNING: Failed to set up Shadow DOM shim for [find-result].
InvalidCharacterError: The string contains invalid characters. '[find-result]' is not a valid attribute name.
So, for some reason, even with useShadowDom set to false on all my Components, it's still attempting to use the ShadowDom shim. I'm assuming this is because it is because I've implemented ShadowRootAware which constructs an EmulatedShadowRoot. So, I think I need a solution that avoids onShadowRoot
You can query the template of a emulated component as follows:
class Component implements ShadowRootAware {
Element el;
Component(this.el);
void onShadowRoot(_) {
this.el.querySelector('.blah');
}
}
The error message:
[WebPlatformShim] WARNING: Failed to set up Shadow DOM shim for [find-result].
is caused by the css shim. This is one of the limitations of the shim (you can use only element selectors).
You can disable the css shim. Then you will not see the error, but you won't have CSS encapsulation.
See more here:
https://github.com/angular/angular.dart/wiki/CSS-Shim
We schedule child elements querying on the next event loop iteration. I'm not aware about particular internal implementation details, but it reliably works fine for us:
MyComponent(Element root) {
// Schedule child elements querying on the next event loop iteration when
// AngularDart will render the child DOM.
new Future(() {
root.querySelector('.btn-go-back').onClick.listen((e) {
if (goBackHandler != null) {
goBackHandler();
}
});
});
}
You can also use onShadowRoot even with useShadowDom: false. However the parameter provided is not a ShadowRoot object.

BreezeJS editing data not working

I was able to follow the instruction on adding data, that part was easy and understandable. But when I tried to follow instructions for editing data, I'm completely lost.
I am following the todo sample, which works quite well, but when I tried to add to my own project using the same principle, nothing works.
in my controller, I have the following:
function listenForPropertyChanged() {
// Listen for property change of ANY entity so we can (optionally) save
var token = dataservice.addPropertyChangeHandler(propertyChanged);
// Arrange to remove the handler when the controller is destroyed
// which won't happen in this app but would in a multi-page app
$scope.$on("$destroy", function () {
dataservice.removePropertyChangeHandler(token);
});
function propertyChanged(changeArgs) {
// propertyChanged triggers save attempt UNLESS the property is the 'Id'
// because THEN the change is actually the post-save Id-fixup
// rather than user data entry so there is actually nothing to save.
if (changeArgs.args.propertyName !== 'Id') { save(); }
}
}
The problem is that any time I change a control on the view, the propertyChanged callback function never gets called.
Here's the code from the service:
function addPropertyChangeHandler(handler) {
// Actually adds any 'entityChanged' event handler
// call handler when an entity property of any entity changes
return manager.entityChanged.subscribe(function (changeArgs) {
var action = changeArgs.entityAction;
if (action === breeze.EntityAction.PropertyChange) {
handler(changeArgs);
}
});
}
If I put a break point on the line:
var action = changeArgs.entityAction;
In my project, it never reaches there; in the todo sample, it does! It completely skips the whole thing and just loads the view afterwards. So none of my callback functions work at all; so really, nothing is subscribed.
Because of this, when I try to save changes, the manager.hasChanges() is always false and nothing happens in the database.
I've been trying for at least 3 days getting this to work, and I'm completely dumbfounded by how complicated this whole issue has been for me.
Note: I'm using JohnPapa's HotTowel template. I tried to follow the Todo editing functionality to a Tee.. and nothing is working the way I'd like it to.
Help would be appreciated.
The whole time I thought the problem was in the javascript client side end of things. Turned out that editing doesn't work when you created projected DTOs.
So in my server side, I created a query:
public IQueryable<PersonDTO> getPerson(){
return (from _person in ContextProvider.Context.Queries
select new PersonDTO
{
Id = _person.Id,
FirstName = _person.FirstName,
LastName = _person.LastName
}).AsQueryable();
}
Which just projected a DTO to send off to the client. This did work with my app in fetching data and populating things. So this is NOT wrong. Using this, I was able to add items and fetch items, but there's no information that allowed the entitymanager to know about the item. When I created an item, the entitymanager has a "createEntity" which allowed me to tell the entitymanager which item to use.. in my case:
manager.createEntity(person, initializeValues);
Maybe if there was a "manager.getEntity" maybe that would help?
Anyways, I changed the above query to get it straight from the source:
public IQueryable<Person> getPeople(){
return ContextProvider.Context.People;
}
Note ContextProvider is:
readonly EFContextProvider<PeopleEntities> ContextProvider =
new EFContextProvider<PeopleEntities>();
So the subscribe method in the javascript checks out the info that's retrieved straight from the contextual object.. interesting. Just wish I didn't spend 4 days on this.

Provide callback for custom component

I made a custom component which basically wraps a d3 line chart. Now I want to be able to register a callback for clicks on the lines in the chart.
I gave the component a #NgCallback parameter, which I then send events to:
class NetworkSummaryComponent implements NgShadowRootAware {
#NgCallback('callback')
Function callback;
void onShadowRoot(ShadowRoot shadowRoot) {
...
chart.callMethod('listen', ['line-click', (ev) {
var name = ev.callMethod('getLineName');
print(name);
callback({'name': name});
}]);
}
}
When using the component, I specify a function of my controller as callback:
<network-summary
...
callback="ctrl.lineClicked">
</network-summary>
However, that function is never actually called, put I know the callback arrives from the JS side because the print in the first snippet is executed.
If I instead specify the attribute as callback="ctrl.lineClicked()" I get a strange exception:
Closure call with mismatched arguments: function 'call'
I could not find any official documentation on how to properly do callbacks, so I'm not exactly sure what I'm doing wrong.. Any ideas?
It turns out that I had to explicitly name the expected arguments in the attributes:
<network-summary
...
callback="ctrl.lineClicked(name)">
</network-summary>
Hope this is useful to the next person having this problem.

Resources