Using jquery-ias with async-loaded content - jquery-ias

I've somewhat successfully integrated the jQuery Infinite Ajax Scroll plugin into my development site - it is used twice, first on the thread list on the left, second when you load up an individual topic - but I'm having trouble with the second ias instance here.
Basically the content of a topic is loaded via $.get and then rendered into the page, and then I trigger setupThreadDetailDownwardScroll() in JS which creates an instance of ias:
var iasDetail = jQuery.ias({
container: "#reply-holder",
item: ".post",
pagination: ".threaddetail-pagination",
next: ".load-next-inner-link a",
delay: 2000,
});
if (iasDetail.extension) {
iasDetail.extension(new IASPagingExtension());
iasDetail.extension(new IASTriggerExtension({
text: 'More Replies',
html: '<div class="scroll-pager"><span>{text}</span></div>',
offset: 10,
}));
iasDetail.extension(new IASNoneLeftExtension({html: '<div class="scroll-message"><span>No more replies</span></div>'}));
iasDetail.extension(new IASHistoryExtension({
prev: '.load-previous-inner-link a',
}));
}
iasDetail.on('load', function() {
$('#reply-holder').append(scrollLoading);
})
iasDetail.on('rendered', function() {
$('.scroll-loading').remove();
iasDetail.unbind();
})
But the problem is that this only works with whatever the first topic you load is - you'll get working pagination in the first thread, but then it'll fallback to anchor links when you open the next thread.
So I figured that I needed to rebind ias once this new content is inserted, and this is why I have added the unbind() call in rendered, and then I re-call setupThreadDetailDownwardScroll() whenever another thread is loaded. This doesn't work either though.
Is there a correct procedure here?

You are using jQuery.ias(...) which binds to the scroll event of $(window). In your case you probably want to bind to an overflow div. Therefor you should specify the scrollContainer like this:
$('#scrollContainer').ias(...)
Edit:
Based on you comment I took another look at it and might have found an answer. When you call jQuery.ias({...}); IAS gets setup and waits for $(document).ready to initialize. You say you want to initialize IAS in your setupThreadDetailDownwardScroll function. You can try to initialize IAS yourself with the following code
iasDetail.initialize();

Related

How to properly use the new asynchronous fragment loading

OpenUI5 newbie here. I am trying to use OpenUI5 fragments, much like shown in the Walkthrough example, Step 16, in the documentation. I have a problem seeing how this can work properly.
The code below is a copy and paste from the Walkthrough example, Step 16, in the documentation:
onOpenDialog : function () {
var oView = this.getView();
// create dialog lazily
if (!this.byId("helloDialog")) {
// load asynchronous XML fragment
Fragment.load({
id: oView.getId(),
name: "sap.ui.demo.walkthrough.view.HelloDialog"
}).then(function (oDialog) {
// connect dialog to the root view of this component
oView.addDependent(oDialog);
oDialog.open();
});
} else {
this.byId("helloDialog").open();
}
}
Since the HelloDialog fragment is loaded asynchronously, it is clear that the onOpenDialog function may return control to the user before the dialog has been created and opened. It is in the nature of asynchronous calls that we must not make any assumptions about how long it will take until the asynchronous code is executed. Anything is possible. Therefore, we must allow for the possibility that the user has control over the web page for any amount of time before the dialog shows up, say, several seconds. What is the user going to do? They're going to click the button for opening the dialog again, and again, and again, thereby creating the dialog multiple times, subverting the intended logic of the code. To be honest, I am not sure if I would be comfortable having that kind of thing in my code. How should I deal with this?
in general, you're right, the dialog could take time to load but usually, the process takes a fraction of time and the user should not see any "loading" time.
This is possible only if your dialog load data asynchronously too.
If you really want to be sure to give graphical feedback to the user you could do like this:
onOpenDialog : function () {
var oView = this.getView();
// create dialog lazily
if (!this.byId("helloDialog")) {]
// set the view to busy state
oView.setBusy(true);
// load asynchronous XML fragment
Fragment.load({
id: oView.getId(),
name: "sap.ui.demo.walkthrough.view.HelloDialog"
}).then(function (oDialog) {
// remove the busy state
oView.setBusy(false);
// connect dialog to the root view of this component (models, lifecycle)
oView.addDependent(oDialog);
oDialog.open();
});
} else {
this.byId("helloDialog").open();
}
}

how to respond to user expanding a node?

I guess the answer is expand - but the expand-event does not seem to fire.
But let me start at the beginning: I have a nice tree and I'd like to use jBox to display information about certain nodes. I noticed that this worked only for nodes that were visible when the tree was created, but it did not work for nodes under collapsed nodes. So I thought I could use expandand assign an event-handler that would call jBoxto create the tooltips. But it did not work. I added a console.log to the `expand-handler and noticed that it never logged.
Am I specifying it incorrectly?
Fiddle here. The "SD"-Node has some items in it which should have a tooltip attached to the (i)-icon.
It doesn't fire because you are passing in a string:
"expand": "function(event, data) {...}"
You need to remove the double quotes, so that it is a function:
"expand": function(event, data) {...}
See updated fiddle: http://jsfiddle.net/pgh52m4w/3/
The same counts for the event "dblclick". Remove the double quotes there too.
Also, it is encouraged to use the .attach() method when attaching jBox. The attach method will check if this jBox was already attached to the element and only attaches it if it wasn't.
See the updated fiddle. I created a variable for the tooltip and reattach it in the expand event:
$(function() {
var treei = $("#tree").fancytree({
expand: function () {
myTooltip && myTooltip.attach(); // Reattaching Tooltip
}
// ...
});
var myTooltip = new jBox("Tooltip", { // get tooltips showing
attach: '[data-jbox-content]',
getTitle: "data-jbox-title",
getContent: "data-jbox-content"
});
});

How do I know when component's DOM is updated and finished transitions in Ractive?

I'm trying to wrap IScroll library into Ractive component. Is there a way to be notified when component's DOM changed and finished any transitions so that I can update the scroller? I see the following ways to achieve this:
Declare dependencies (of the component's template) explicitly, like
<Scroll context="{{...}}" > </Scroll>
I can observe context variable, but still can't wait for transitions to finish.
Patch Ractive.set() so that it emits custom event when used:
// Broadcast promise returned by `set`.
var oldset = Ractive.prototype.set;
Ractive.prototype.set = function () {
var ret = oldset.apply(this, arguments);
this.fire('set', ret);
return ret;
};
Then, in the component's initialization code, I can subscribe to the set event:
this._parent.on('set', function (ret){
ret.then(update);
});
This will not work for Ractive.push() and other methods. Also this will notify my component about all changes made by set-ting something, not only about those affecting component's DOM. Finally, I must explicitly refer to component's parent using this._parent, which means my components can not be nested.
So, is there are better way to achieve this in Ractive?
The initialization options allow oncomplete (or ractive.on('complete'... if you prefer) for initial render. See http://docs.ractivejs.org/latest/lifecycle-events
All of the data modification methods, set, animate, push, slice, etc. return a promise that will not be called until DOM is updated and transitions have completed.
Here's a simple example (http://jsfiddle.net/ctfyes7t/):
{{#if show}}
<li intro='fade:{ duration: 2000 }'>ta da!</li>
{{/if}}
r.set('show', true).then(function(){
// called when fade intro complete
});
The problem can be approached from another direction: changes to the specific parts of DOM can be observed using MutationObserver.
So, I end up with the following approach to wrapping external widgets:
basically, we get component's top node (and initialize 3rd party code) using intro transition, and then in oncomplete callback register observer with something like this:
this.observer = new MutationObserver(update);
this.observer.observe(this.node, {
childList: true,
subtree: true,
characterData: true,
attributes: true, // transitions may change attributes
});
where function update updates 3rd-party widget.
Here is the source code of the component.

Meteor: Elements from CollectionA re-rendering when I insert to CollectionB

I'm attempting to fade-in new elements in a reactive {{#each}} of the comments posted by users.
I have a code sample at https://gist.github.com/3119147 of a very simple comments section (textarea and new comment insert code not included, but it's very boilerplate.). Included is a snippet of CSS where I give .comment.fresh { opacity: 0; }, and then in my script, I have:
Template.individual_comment.postedago_str = function() {
var id = this._id;
Meteor.defer(function() {
$('#commentid_'+id+'.fresh').animate({'opacity':'1'}, function() {
$(this).removeClass('fresh');
});
});
return new Date(this.time).toString();
};
Which seems like a terrible place to execute an animation. My thinking is that each time a new comment is rendered, it will need to call all my Template.individual_comment.* functions, so that's why my animation defers from one of those. However, Meteor is calling Template.individual_comment.postedago_str() each time a different collection (Likes) is inserted to. This means I click the Like button, and my whole list of comments flashes white and fades back in (very annoying!).
I read the Meteor documentation and tried to figure out how to better slice up my templates so only chunks will update, and I added id="" attributes everywhere that seemed reasonable.. still this bug. Anyone know what's going on?
TIA!
As a workaround, you could wrap an {{if}} block around the fresh class on individual comments, that would check the comment's creation time and only add the fresh class in the first place if the comment is actually recent. Something like:
<div class="comment{{#if isActuallyFresh}} fresh{{/if}}" id="commentid_{{_id}}">
And then define the isActuallyFresh function:
Template.individual_comment.isActuallyFresh = function() {
if ((new Date().getTime() - this.time) < 300000) // less than 5 minutes old
return true;
else
return false;

Create jQuery ui resizable instance using selector added to DOM by jQuery

I'm trying to start a jquery ui resizable instance, but using a selector added to the DOM by jquery itself. This is a basic example of my script:
HTML:
<div class='lyr'></div>
jQuery:
// Add class
$('lyr').addClass('fixed');
// Resizable
$('.fixed').resizable({
aspectRatio: true,
handles: 'all'
});
I've thought about using something along the lines of live() or bind() but I have no event to bind to. Any help appreciated.
I have used the LiveQuery plugin - http://brandonaaron.net/code/livequery/docs in the past to be able to target elements added to the dom, like in your case.
If I've got this right, you want anything on the page which has the class "fixed" to be resizable, even if the class is added after the page has loaded? You're right that live, bind and delegate won't help here.
I can think of two possibilities, neither lovely.
First, set up a live "mouseenter" event which will make the element resizable if it wasn't before:
$(body).delegate(".fixed", "mouseenter", function(ev) {
var target = $(ev.target);
if (target.data("resizable")) return;
target.resizable({
aspectRatio: true,
handles: 'all'
});
})
This gets us round the problem of having no event to bind to.
Alternatively, you could monkeypatch jQuery.fn.addClass:
var classRe = new RegExp(c + className + \b);
._addClass = jQuery.fn.addClass;
jQuery.fn.addClass = function(className) {
if (classRe.test(classname)) {
if (this.data("resizable")) return;
this.resizable({
aspectRatio: true,
handles: 'all'
});
}
jQuery.fn._addClass.apply(this, arguments);
}
Of course this will only work if the class is added through the addClass method.
Also in your example,
$('lyr').addClass('fixed');
Should probably be:
$('.lyr').addClass('fixed');