Dynamic configuration of nested (asynch-forcing) callbacks from json file - javascript

I have a single page d3.js based browser application with a (likely) varying number of dynamically loaded elements. Ideally I'd like these configured in their entirety from json files, but converting the currently hard-coded nested callbacks (intended to force asynch behaviour, see the lower code block below) is proving something of a stumbling block.
function load_page_element(element, force_asynch) {
d3.json(("path_to/" + element + ".json"), function(data) {
// Attach the element to the DOM
// Set up some event handling
// Enjoy the fireworks
});
force_asynch();
};
// Hard-coded nested asynch-forcing callbacks
load_page_element("colours", function() {
load_page_element("sounds", function() {
load_page_element ("tools", function() {
load_page_element ("tutorials", function() {
load_page_element ("videos", function() {
load_page_element ("games", function() {
console.log("Asynch element loading completed, in order.");
});
});
});
});
});
});
I anticipate some kind of recursive call structure into which I can plug the element names ("colours", "sounds" etc), but have yet to hit on a mechanism that actually works. Presumably this has all been done before, but darned if I can find any examples.. Any suggestions are welcome..

Use queue.js for this. See also this example. The code looks like this:
queue()
.defer(request, "1.json")
.defer(request, "2.json")
.defer(request, "3.json")
.awaitAll(ready);
function ready(error, results1, results2, results3) {
// do something
}

Related

Using a .load() Method to Detect and Deliver Multiple wrap() Functions Within?

I have developed a script that's intentional purpose is to grab (load) the first entry of content from the listed url source locations.
At those specific locations, whatever the first entry turns up to be, I would like the iframe (if detected within that entry) to have the corresponding wrap function applied to it, (as shown below).
Here's an example of the script:
$("#embed-content-div1").load("https://theurl.com/urltosource1/ #entry:first");
$("#embed-content-div2").load("https://theurl.com/urltosource2/ #entry:first", function(data) {
$(function() {
$("iframe.content1").wrap("<div class='content1-wrap'></div>");
});
$(function() {
$("iframe.content2").wrap("<div class='content2-wrap'></div>");
});
$(function() {
$("iframe.content3").wrap("<div class='content3-wrap'></div>");
});
});
However, this particular script doesn't seem to be working consistently, and has malfunctioned on certain occasions in Chrome and Safari. It only seems to work best when the entire page is refreshed.
Is there a better way to achieve this, ensuring it would deliver a better, cross-browser performance overall?
Since you are loading the same content into each element you should probably look inside each element for the iframes to wrap. load() is asynchronous so the timing can be off when you only use one of the callbacks
Rather than duplicate the code for each you can do it in a simple wrapper function
function loadContent( selector, url){
var $cont = $(selector);
$cont.load(url, function(){
$.each([1,2,3], function(_, num){
$cont.find('iframe.content' + num).wrap('<div class="content'+ num +'-wrap"></div>')
});
});
}
loadContent('#embed-content-div1','https://theurl.com/urltosource1/ #entry:first')
loadContent('#embed-content-div2','https://theurl.com/urltosource2/ #entry:first')
So, I figured out a solution which both repaired and improved the performance of the script:
$(document).ready(function() {
$("#embed-content-div2").load("https://theurl.com/urltosource2/ #entry:first", function(data) {
$(function() {
$("iframe.content1").wrap("<div class='content1-wrap'></div>");
});
$(function() {
$("iframe.content2").wrap("<div class='content2-wrap'></div>");
});
$(function() {
$("iframe.content3").wrap("<div class='content3-wrap'></div>");
});
});
});
$("#embed-content-div1").load("https://theurl.com/urltosource1/ #entry:first");

Delay Loading Custom Bindings

I'm working on an Single Page Application and we're using Knockout quite extensively. We've currently got a list of item that can be clicked, and upon doing so they'll load some content into a modal container. The image below illustrates the different items that'll trigger various content to be displayed:
The content of these containers differs substantially, and can have many different custom bindings spread over several tabs. The items in the image are fairly simple and just use Knockout Components but when we start displaying the modal contents they are much more heavy on the JavaScript hence using bindings.
I've recently added in lazy loading of the JavaScript and HTML templates required by the components and this has worked really well. I've had to use a custom component loader as for various reasons we don't want to use require or similar AMD module loader.
Now I'm faced with the same issue with custom knockout bindings, as I expect we could end up with 100 hundred bindings quite easily as this product expands. Unfortunately there doesn't seem to be an obvious way to load custom bindings in a lazy way like components though, and I'm trying to figure out if there's a way to do this, and what the best way would be. Note that I also don't know the name of the binding up front all of the time, sometimes I may wish to load them dynamically based on the name of an observable.
The only things I've managed to find of note so far, are that there is a ko.getBindingHandler() function which can be overridden, but it requires a synchronous load of a binding handler.
I have thought of an approach to try and do this, but it uses components and feels like a really backward way of achieving my end goal. It'd be something like this:
Replace a usual custom binding:
<div data-bind="lineChart: $data"/>
with
<div data-bind="component { name: compName, params: { vm: $data } }"/>
I'd then use a custom component loader, which is actually just loading the binding handler JavaScript, and writing out essentially a placeholder div with the custom binding in:
var bindingLoader = {
getConfig: function(name, callback) {
if(name.startsWith("binding-")) {
callback({ binding: name.replace("binding-", ""), jsUrl: "/bindings/" + name });
return;
}
callback(null);
},
loadComponent(name, componentConfig, callback) {
var obj = { };
obj.template = '<div data-bind="' + componentConfig.name + ': $data"/>';
$.ajax({ url: componentConfig.jsUrl, dataType: "text" })
.done(function(data)) {
(new Function(data))();
callback(obj);
});
}
}
I'm sure however there must be a better way of achieving this, but I can't think of any other options right now.
I've also answered this question on Github.
#Jeroen is right that there's no built-in way to asynchronously load custom bindings. But any binding can "lazily" perform its own action, which is what the component binding does. By overwriting ko.getBindingHandler, we can detect bindings that haven't yet been loaded, and start the loading process, then return a wrapper binding handler that applies the binding once it's loaded.
var originalGetBindingHandler = ko.getBindingHandler;
ko.getBindingHandler = function (bindingKey) {
// If the binding handler is already known about then return it now
var handler = originalGetBindingHandler(bindingKey);
if (handler) {
return handler;
}
if (bindingKey.startsWith("dl-")) {
bindingKey = bindingKey.replace("dl-", "");
if (ko.bindingHandlers[bindingKey]) {
return ko.bindingHandlers[bindingKey];
}
// Work out the URL at which the binding handler should be loaded
var url = customBindingUrl(bindingKey);
// Load the binding from the URL
var loading = $.getScript(url);
return ko.bindingHandlers["dl-" + bindingKey] = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
// Once the binding is loaded, apply it to the element
loading.done(function() {
var binding = {};
binding[bindingKey] = valueAccessor;
ko.applyBindingAccessorsToNode(element, binding);
});
// Assumes that all dynamically loaded bindings will want to control descendant bindings
return { controlsDescendantBindings: true };
}
}
}
};
http://jsfiddle.net/mbest/e718a123/
AFAIK: No, there is no generic way to lazily load custom bindings.
There are however a lot of options, but we can not recommend any specific one because they'll heavily depend on context. To summarize a few examples:
If possible you can use those bindings inside components, and lazily load the components;
Depending on what your binding handler does, it can itself delay loading until the latest needed time (e.g. in the init you'll merely register an event callback that will actually load the things you want to load);
If you properly use if bindings, any custom bindings inside of that will not be evaluated until needed. The same for foreach bindings, which will not apply custom bindings for array items unless those items are there.
You can call applyBindings to specific parts of the DOM only when you're ready to do so.
Et cetera. But again, your question borders on being too broad. Create one (or more?) new questions with actual scenario's, tell us why / how you'd need your custom binding to load lazily, and tell us what approaches you've tried and why they didn't work.

Creating a jQuery "object" that is available between _layout and views in ASP.NET MVC

I am coding in ASP.NET MVC 5.2, and using jQuery as my primary script library. I am having a bit of a problem though, with the disparity between _Layout and views that use that layout.
Essentially, it goes like this
_Layout has some script that needs to run (initial wiring, progress bar, splash screen, etc)
Inheriting View has some script that needs to run (unique to that view)
_Layout has additional scripts that need to run after the view's unique scripts.
I have been trying a lot of ways to solve this, but it is actually proving to be a big problem. I have been frequently told that I should not create objects on the global namespace, so I am wondering if there are any other options to creating a script object that I can access in both views that isn't as damaging as global objects.
I have tried promises, and that is getting frustrating. I have tried events, and that doesn't really help because I cannot figure out what to attach the events to. I am told not to attach them to $(document), but that is really one of the only things that will be shared between the view and the layout.
I understand that global objects are not considered good in javascript, but at this point I'm not sure what other options I have to make sure things execute in the right order.
Update
The issue is more about "tooling" than it is about run time. It is true that when the actual view loads and runs, it is all pressed into one big happy page, and would work just fine. The issue is mostly that I have to split up the logic in the tooling (Visual Studio) to keep it from throwing errors and getting confused.
So I suppose it is more accurate to say it is a pseudo-problem.
I have attempted to split up the logic like this, but I think this is just another way of declaring a global object. I got the idea from the Q.js library.
Tasks.js
(function(definition) {
// assign the task system
tasks = definition();
})(function() {
var list = [];
function tasks() {
};
tasks.start = start;
tasks.enqueue = enqueue;
/*
* start the task queue.
*/
function start() {
// make sure to raise a started event for things that need
// to monitor it.
$(this).trigger("started");
};
function enqueue(f) {
// add the potential function to the queue to be processed later.
list.push(f);
$(this).trigger("enqueue", { item: f });
};
return tasks;
});
example usage
$(function(){
$(tasks).on("started", function(){
console.log("event called");
});
console.log("tasks", tasks);
tasks.start();
});
There are a number of ways you could go about this:
Use RequireJs to define Tasks as a module, then:
require(['tasks'], function(tasks){
$(tasks).on("started", function(){
console.log("event called");
});
console.log("tasks", tasks);
tasks.start();
});
Use a global object, but namespace it:
Ciel = Ciel || {};
Ciel.tasks = Ciel.tasks || function(){
var list = [];
...
};
Tie your data to a specific dom element:
<div class="ciel-tasks"></div>
...
$(function() { $('.ciel-tasks').each(function() {
var tasks = $(this);
...
});
It's not really clear what you're describing. From JavaScript's perspective there's no such thing as "_Layout" and "Inheriting View." There's only the resulting DOM delivered to the browser. Any JavaScript code within that DOM can operate on anything else in that DOM. So I'm not sure what any of this has to do with global namespace, events, $(document), etc. Perhaps you're overcomplicating the issue by assuming disparity between your views when, client side, no such disparity exists?
_Layout has additional scripts that need to run after the view's unique scripts.
This sounds like it's just a matter of providing callbacks for operations so that they internally execute in the correct order. For example, if the desired order is:
Layout executes initializeLayout()
View executes initializeView()
Layout executes completeLayout()
Then you can pass these to one another as callbacks and the functions can internally execute those callbacks. So in your Layout you might have something like this at the very top (such as in the header, as long as it's before the view is rendered):
<script type="text/javascript">
function initializeView(){} // placeholder for view-specific initialization
</script>
Then at the bottom with the rest of your scripts:
initializeLayout();
initializeView(completeLayout);
What this does is provide your views with an opportunity to overwrite that initializeView function. If the view defines its own function called initializeView then that one will be executed instead of the placeholder one defined in the layout (remembering that the layout and the view are all one page to JavaScript).
(This also assumes you've elsewhere defined a completeLayout function, since that's what you want to execute after the view is initialized.)
Then in your view you can define that overwriting function:
function initializeView(callback) {
// do some stuff...
if (typeof callback == 'function') {
callback();
}
}
That will execute your view initialization code and then when it's complete will invoke the callback which was provided by the layout, so the layout will then execute its post-view-initialization code. (Naturally, if any of this "initialization" code is asynchronous, you'll want to invoke callbacks in response to those asynchronous callbacks, etc.)

What is the preferred pattern for re-binding jQuery-style UI interfaces after AJAX load?

This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here

What are the pros and cons of timeouts vs counting down with a content loading asynchronous call in JavaScript / jQuery

To my way of thinking, I might be over-engineering with the recursive solution.
Wait 2 seconds for first set of modules to be loaded:
function loadContents() {
$('[data-content]:not(.loaded)').each( function() {
$(this).load($(this).data('content'));
$(this).addClass('loaded');
});
}
loadContents();
setInterval(loadContents, 2000);
When all of first set of modules are loaded, check for new modules:
function loadContents() {
var notLoaded = $('[data-content]:not(.loaded)'),
notLoadedLength = notLoaded.length;
notLoaded.each( function(i,element) {
$(element).addClass('loaded');
$(element).load($(element).data('content'), function() {
// one more has been loaded
notLoadedLength--;
if (notLoadedLength == 0) {
alert("countdown good");
loadContents();
}
});
});
}
loadContents();
You should be able to do all of this with success handlers and no polling with timers.
You don't specify exactly what you want to do, but if you want to load multiple things in parallel and know when they are all loaded, then you can just keep some sort of state on how many have been loaded and when the count shows that they are all now loaded, you will know you're done.
If you want to load them sequentially, then you can just load the next one from each success handler. It's probably easiest to create a list of things to be loaded and just have a generic success handler that gets the next one in the list and kicks off it's load and removes it from the list. When the list of remaining items to load is empty, you're done.
Edit: Looking further at your code, it looks like you're loading them all in parallel. You can just create a success handler for each one that is loading, add the loaded class in that success handler and see how many more have not yet finished. I would suggest this:
function loadContents() {
$('[data-content]:not(.loaded)').each( function() {
var obj = $(this); // save in local variable in function closure so we can reference it in the success handler
obj.load(obj.data('content'), function() {
obj.addClass('loaded');
if ($('[data-content]:not(.loaded)').length == 0) {
// all pieces of content are now loaded
} else {
// some pieces of content are still loading
}
});
});
}
loadContents();
Edit 2: OK, based on your comments, I now understand the problem better. I would scope loadContents to a parent of the DOM tree and then call it on the newly loaded content from the success handler. This will work for inifinite levels and it's safe because it only ever calls itself once for any given parent of the DOM tree. When there is no new content to load, it just has nothing to do and thus doesn't call itself any more. Here's what I would recommend:
function loadContents(root) {
$('[data-content]:not(.loaded)', root).each( function() {
var obj = $(this); // save in local variable in function closure so we can reference it in the success handler
obj.load(obj.data('content'), function() {
obj.addClass('loaded');
loadContents(obj); // now load any contents from the newly loaded content
});
});
}
loadContents(document.body); // start it off by looking in the whole DOM tree

Categories

Resources