Event when an Component is added to the dom - dart

Say I have a AngularDart component that adds a div and an iframe to that div as it's template.
I have the element passed for the outer component in the components constructor
#Component(
selector: "input-html",
templateUrl: "packages/myproject/components/inputs/html.html",
useShadowDom: false
)
class HtmlComponent implements ShadowRootAware {
HtmlComponent(NgModel ngModel, Element element):super(ngModel, element){
}
}
I have shadowdom turned off because I'm using Bootstrap for styling and want the elements easily accessible for the bootstrap css.
My template code is along the lines of
<div>
<iframe id="my-iframe"></iframe>
</div>
It's more complicated than that, there's a bunch of buttons etc, as I'm porting a javascript html editor to angulardart.
My problem is, I need to get the iframe element, but whenever I query element.querySelector("#my-iframe") or even window.document.querySelector("#my-iframe") the object is null. I believe this is because the template hasn't been added to the DOM yet.
I need the iframe object because I need to set the iframe content for the HTML editor to work. There's a few other areas of my project that I wanted to get the template dom objects but couldn't either.
I've tried onShadowRoot, which worked in AngularDart 0.14 but no longer works in 1.0. I've tried ScopeAware and querying for the iframe when the scope is set, but that didn't work (ScopeAware fires before shadowroot event).
I have a hack that's messy that works, by using ng-show="init()" and in that init method I have
bool _initDone = false;
bool init() {
if(_initDone == false) {
iframe = element.querySelector("#my-iframe")
_initDone = true;
}
return true;
}
Which works, but it's messy and I don't like that solution and obviously isn't the correct way to do it.
Anyone know how I can achieve this in AngularDart 1.0?

I think onShadowRoot is the right place for the code to query the element. If it really doesn't work wrap it in a Future to add it as a task at the end of the event queue to delay it a bit more.
onShadowRoot() {
new Future(() {
querySelector(...);
});
}

Related

Access elements in Angular Dart

I have a top level element <x-app> with nested modal dialogs
<x-app>
<material-content>
...
</material-content>
<x-alert-dialog></x-alert-dialog>
</x-app>
where <x-alert-dialog> contains
<modal [visible]="dlgVisible" dialog-id="alert-dialog-modal">
<material-dialog class="alert-dialog">
....
</material-dialog>
</modal>
Generated HTML contains <x-app> and overlay container div which contains modals as on the image
What I need is to access <div pane-id="default-1"...> to change z-index. and I don't know how. I cannot access it in CSS as any reference via :host is not possible.
I tried to access it programatically in x-app component. I have
class AppComponent implements AfterViewInit {
#override
void ngAfterViewInit() {
var doc = getDocument();
var alertDlg = doc.querySelector(".alert-dialog");
var alertPane = alertDlg.parent;
}
}
But alertDlg is always null. I also tried var alertDlg = querySelector(".alert-dialog");
Is there any way to access the element?
I solved it by adding *ngIf="dlgVisible" to <modal> tag, so it's now
<modal *ngIf="dlgVisible" [visible]="dlgVisible" dialog-id="alert-dialog-modal">
This way the dialog is injected to/removed from DOM by the visibility flag. The reason why I wanted to change z-index was to get the alert and other app wide dialogs above other dialogs created later in other components.
Altering DOM solves this as the dialogs are inserted after (and thus displayed above) other dialogs. Hope this will help someone.

How to remove "on-click" from a custom Polymer component

I have a custom button component done in Polymer Dart:
<div id="buttonDiv">
<my-button id="traceButton"
mode="icon" faicon="fa-comment-o"
toolTip="Print a simple comment"
disabled="false" on-click="{{ traceSomething }}">
</my-button>
</div>
I'm trying to copy/paste this button somewhere else. So a user defines it somwhere, and I basically move it by way of getting $['buttonDiv'].children then inserting it somewhere else. The problem is that {{ traceSomething }} is now irrelevant since it's not part of the new parent. I get errors saying that the parent object, which is another polymer component doesn't have an instance getter "traceSomething".
My question is, is there a way to remove "traceSomething" before I insert it somwhere else? I tried removing the "onClick" event listeners, but the buttons still wants to call that function upon click. Also, I've tried adding a preventDefault, etc, like in: In Dart, if I listen to a click event with two listeners, how do I know which happens first?
But, no luck.
I'm not sure what you mean by copy/past. Do you clone the element, or do you just append it to some other elements children.
Anyway, I don't think you can remove the event listener if it was added declaratively. If you add it imperatively it is easy to remove and readd later.
import 'dart:async';
...
StreamSubscription subsc;
#override
attached() {
super.attached();
subscr = onClick.listen((e) => (this.parentNode as ShadowRoot).host.traceSomething(e));
}
#override
detached() {
super.detached();
if(subscr != null) {
subscr.cancel();
}
}
See also https://stackoverflow.com/a/22168745/217408 about accessing the parent of a Polymer element (for Dart Polymer <= 0.16.x)

Why do I lose draggable after drag?

I just migrated to backbone and have a strange behaviour.
I attach draggable to an element which is created by a script, thus not directly available in DOM.
EDIT:
The element that is created is .nav, $("#viewer") as container is already in the DOM.
In plain jQuery i used .on and mousemove event for this and it worked.
With backbone I use the same in the initialize method:
initialize: function(options) {
this.viewer = $("#viewer");
this.viewer.on("mousemove", '.nav', function() {
$(this).draggable();
});
This seems to work, but only one time.
After dragging the element one time, I can't drag it anymore.
Are there conflicts with the events? Am I missing something?
You have to refer to $('#viewer') after you've called render(). initialize is called before render, and so the DOM element doesn't exist.
Also, use this.$('#viewer'), and it will grab the element (after render) even if it hasn't been appended to your page's DOM.
myView = new ExampleView({ model: myModel });
$(body).append(myView.render().el);
myView.onRender();
// -------------
// Now on your view:
onRender: function() {
this.viewer = this.$('#viewer');
this.viewer.on("mousemove", '.nav', function() {
$(this).draggable();
});
},
UPDATE
You can also, to make such things simpler, customize Backbone to automatically call the onRender() function after rendering, by triggering an event or something.
Marionette.js (a Backbone.js extension) has this built in and I use it all the time.
The solution finally was pretty easy:
make sure you dont use outdated versions of backbone.js and underscore!!
After i updated the versions to latest I made it work with:
render: function() {
this.viewer.on("mouseover", '.nav', function() {
if (!$(this).data("init")) {
$(this).data("init", true);
$(this).draggable();
}
});
Probably still not very elegant but i couldnt made the suggested onRender method from dc2 work.

jQuery UI in Backbone View adds elements, but doesn't respond to events

I'm building an app in which I'm using Django on the backend and jQuery UI/Backbone to build the front. I'm pulling a Django-generated form into a page with jQuery.get() inside of a Backbone View. That part works fine, but now I want to add some jQuery UI stuff to the form (e.g. a datepicker, some buttons that open dialogs, etc). So, here's the relevant code:
var InstructionForm = Backbone.View.extend({
render: function() {
var that = this;
$.get(
'/tlstats/instruction/new/',
function(data) {
var elements = $(data);
$('#id_date', elements).datepicker();
that.$el.html(elements.html());
}
};
return this;
}
});
The path /tlstats/instruction/new/ returns an HTML fragment with the form Django has generated. What's happening is that input#id_date is getting the hasDatePicker class added and the datepicker div is appended to my <body> element (both as expected), but when I click on input#id_date, nothing happens. No datepicker widget appears, no errors in the console. Why might this be happening?
Also, somewhat off-topic, but in trying to figure this problem out on my own, I've come across several code examples where people are doing stuff like:
$(function() {
$('#dialog').dialog(...);
...
});
Then later:
var MyView = Backbone.View.extend({
initialize(): function() {
this.el = $('#dialog');
}
});
Isn't this defeating the purpose of Backbone, having all that jQuery UI code completely outside any Backbone structure? Or do I misunderstand the role of Backbone?
Thanks.
I think your problem is right here:
$('#id_date', elements).datepicker();
that.$el.html(elements.html());
First you bind the datepicker with .datepicker() and then you throw it all away by converting your elements to an HTML string:
that.$el.html(elements.html());
and you put that string into $el. When you say e.html(), you're taking a wrapped DOM object with event bindings and everything else and turning into a simple piece of HTML in a string, that process throws away everything (such as event bindings) that isn't simple HTML.
Either give .html() the jQuery object itself:
$('#id_date', elements).datepicker();
that.$el.html(elements);
or bind the datepicker after adding the HTML:
that.$el.html(elements);
that.$('#id_date').datepicker();

Creating a jQuery UI sortable in an iframe

On a page I have an iframe. In this iframe is a collection of items that I need to be sortable. All of the Javascript is being run on the parent page. I can access the list in the iframe document and create the sortable by using context:
var ifrDoc = $( '#iframe' ).contents();
$( '.sortable', ifrDoc ).sortable( { cursor: 'move' } );
However, when trying to actually sort the items, I'm getting some aberrant behavior. As soon as an item is clicked on, the target of the script changes to the outer document. If you move the mouse off of the iframe, you can move the item around and drop it back by clicking, but you can not interact with it within the iframe.
Example: http://robertadamray.com/sortable-test.html
So, is there a way to achieve what I want to do - preferably without having to go hacking around in jQuery UI code?
Dynamically add jQuery and jQuery UI to the iframe (demo):
$('iframe')
.load(function() {
var win = this.contentWindow,
doc = win.document,
body = doc.body,
jQueryLoaded = false,
jQuery;
function loadJQueryUI() {
body.removeChild(jQuery);
jQuery = null;
win.jQuery.ajax({
url: 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js',
dataType: 'script',
cache: true,
success: function () {
win.jQuery('.sortable').sortable({ cursor: 'move' });
}
});
}
jQuery = doc.createElement('script');
// based on https://gist.github.com/getify/603980
jQuery.onload = jQuery.onreadystatechange = function () {
if ((jQuery.readyState && jQuery.readyState !== 'complete' && jQuery.readyState !== 'loaded') || jQueryLoaded) {
return false;
}
jQuery.onload = jQuery.onreadystatechange = null;
jQueryLoaded = true;
loadJQueryUI();
};
jQuery.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js';
body.appendChild(jQuery);
})
.prop('src', 'iframe-test.html');
​
Update: Andrew Ingram is correct that jQuery UI holds and uses references to window and document for the page to which jQuery UI was loaded. By loading jQuery / jQuery UI into the iframe, it has the correct references (for the iframe, rather than the outer document) and works as expected.
Update 2: The original code snippet had a subtle issue: the execution order of dynamic script tags isn't guaranteed. I've updated it so that jQuery UI is loaded after jQuery is ready.
I also incorporated getify's code to load LABjs dynamically, so that no polling is necessary.
Having played with their javascript a bit, Campaign Monitor solves this by basically having a custom version of jQuery UI. They've modified ui.mouse and ui.sortable to replace references to document and window with code that gets the document and window for the element in question. document becomes this.element[0].ownerDocument
and they have a custom jQuery function called window() which lets them replace window with this.element.window() or similar.
I don't know why your code isn't working. Looks like it should be.
That said, here are two alternative ways to implement this feature:
If you can modify the iframe
Move your JavaScript from the parent document into iframe-test.html. This may be the cleanest way because it couples the JavaScript with the elements its actually executing on.
http://dl.dropbox.com/u/3287783/snippets/rarayiframe/sortable-test.html
If you only control the parent document
Use the jQuery .load() method to fetch the content instead of an HTML iframe.
http://dl.dropbox.com/u/3287783/snippets/rarayiframe2/sortable-test.html
Instead of loading jQuery and jQueryUI inside the iFrame and evaluating jQueryUI interactions both in parent and child - you can simply bubble the mouse events to the parent's document:
var ifrDoc = $( '#iframe' ).contents();
$('.sortable', ifrDoc).on('mousemove mouseup', function (event) {
$(parent.document).trigger(event);
});
This way you can evaluate all your Javascript on the parent's document context.

Resources