Ensure knockout binding gets processed first - javascript

I have a table created from an observable array. Table rows contains elements belonging each to one of a set of categories. To filter the table based on categories, there is a row of buttons.
Buttons can be active or inactive, indicated via a CSS class bound via knockout:
<button data-bind="click: filter.filterCategory, css: { filterOn: filter.category.isFiltered() }">Filter</button>
Filtering within the table is done by switching the display state of rows:
<tr data-bind="css: { nonDisplay: !table.category.isDisplayed() }">
</tr>
The click handler mainly sets the values of the two observables, in sequence e.g.
vm.filter.category.isFiltered(true);
vm.table.category.isDisplayed(false);
This works in principle.
The problem is that the indication that the filter button has been selected by the user is not given immediately, but dependent on the execution time of the filtering itself, i.e. the changes to the table.
With larger tables, and especially on mobile, this can mean delays of a couple of seconds.
I can live with the filtering itself taking this long, but the feedback needs to be immediate.
Is there a way to ensure that the change on vm.filter.category.isFiltered gets applied before the longer running change based on vm.table.category.isDisplayed is started?

This seems as an async fail.
You should implement a callback method parameter in the isFiltered method, something like this
var vm = vm || {};
vm.filter = vm.filter || {};
vm.filter.category = (function($){
var that = this;
that.isFiltered = function(booleanValue, callback) {
// Put your primary code here
console.log("this runs first");
// ...when the first code is done
callback();
};
that.isDisplayed = function(booleanValue) {
console.log("this runs second");
};
return that;
})($);
// Implement by stating a method as the second parameter.
vm.filter.category.isFiltered(true, function(){ vm.filter.category.isDisplayed(false); });
This will render
// this runs first
// this runs second

Related

Cannot reinitalize Sortable after ajax content update

I'm using Sortable to organise lists inside of parent groupings, which themselves are also sortable, similar to the multi example on their demo page, but with text. This works fine and uses code along the lines of:
var globObj = {};
function prepSortCats() {
globObj.subCatsGroup = [];
// Changing parent group order
globObj.sortyMainCats = Sortable.create(catscontainer, {
// options here omitted from example
onUpdate: function( /**Event*/ evt) {
// Send order to database. Works fine.
}
});
// Changing sub list order
globObj.subCatsGroup.forEach.call(document.getElementById('catscontainer').getElementsByClassName('subcatlist'), function(el) {
var sortySubCats = Sortable.create(el, {
// options here from example
onUpdate: function( /**Event*/ evt) {
// Send order to database. Works fine.
}
});
});
}
Which is called when the page loads using:
$(document).ready(function () {
// Sortable
prepSortCats();
});
All good so far. However, the user can introduce new elements into any of the lists (sub or parent). In brief, any new elements added by the user are first added to the database, then the relevant section of the page is refreshed using ajax to pull the updated content from the database and display that. The user sees their newly added items added to one of the existing lists. Ajax call is as follows:
function refreshCategories() {
var loadUrl = "page that pulls lists from database and formats it";
$("#catscontainer")
.html(ajax_load)
.load(loadUrl);
return false;
};
This works fine too. Except, Sortable no longer works. I can't drag any lists. My first thought was to destroy the existing Sortable instances and reinitialize them. Right after I called refreshCategories() I call the following:
if(globObj.sortyMainCats.length !== 0) {
globObj.sortyMainCats.destroy();
}
if(globObj.subCatsGroup.length !== 0) {
var i;
for (i = globObj.subCatsGroup.length - 1; i >= 0; i -= 1) {
globObj.subCatsGroup[i].destroy();
globObj.subCatsGroup.splice(i, 1);
}
}
prepSortCats();
But Sortable still has no effect. I introduced the global object (although controversial) so that I could target the Sortable instances outside their scope but I appear to have overlooked something. Any ideas? Apologies for not providing a working example. As I make various ajax calls to a server, I don't think this is possible here.
Update
I'm clearly misunderstanding some action that's taking place. Well, I should preface that by saying I missed that I could still organise the group/parent lists after reloading a section of the page by ajax with refreshCategories(). This is very much a secondary action to being able to sort the sub lists, which is what I noticed was broken and remains so.
But it did point out that although the entirety of $("#catscontainer") was being replaced with a refreshed version of the lists (and that's all it contains, list elements), Sortable still had some sort of instance running on it. I was under the understanding that it was somehow tied to the elements that were removed. Now I'm a bit more lost on how to get Sortable to either: (a) just start from scratch on the page, performing prepSortCats() as if it was a fresh page load and removing any previous Sortable instance, or (b) getting the remaining Sortable instance, after the ajax call to recognise the added elements.
Update 2
Making some progress.
Through trial and error I've found that right after calling refreshCategories(), calling globObj.sortyMainCats.destroy() is preventing even the group lists from being ordered. Then if I call prepSortCats() after this, I can move them again. But not the sub lists.
This isn't conclusive but it looks like I'm successfully destroying and reinitializing Sortable, which was my goal, but something about the ajax loaded elements isn't working with Sortable.
I was looking for the answer in the wrong place, being sure it was an issue with ajax loaded content and the dom having some inconsistencies with what Sortable expected.
Turns out it was an asynchronous problem. Or, to put it simpler, the section of the page being loaded by ajax wasn't quite ready when Sortable was being asked to be reinitalized.
For anyone having the same trouble, I changed:
$("#catscontainer")
.html(ajax_load)
.load(loadUrl);
to
$("#catscontainer")
.html(ajax_load)
.load(loadUrl, function() {
reinitSortable();
});
where reinitSortable() is just a function that fires off the destroy and prepSortCats() functions similar to how they're displayed above.

Dynamically Binding Items in ListVIew

In my Windows 8 JavaScript application, I have a ListView. I need to add either a message or link to a row, depending on what the current user's status is. So I essentially need to show or hide items depending on some flag. How, using the JavaScript API of the ListView, do I parse items at an item level? There is no collection of items on the ListView control per the MSDN, and I need to have access to the data and the item at the row level.
I'm sure I'm missing it somehow, just getting into this....
I'm not completely clear on what your trying to do, but I'll give it a shot.
If you need to conditionally show or hide items (or certain parts of an item) there and a couple of ways you can go.
The first is to create an imperative template render function. First, tell you ListView that its item template is a function with something like myListView.itemTemplate = myCustomFunction. Then write a function like:
function myCustomFunction(itemPromise) {
//you have to return a promise
return itemPromise.then(function (item) {
//get the right item template (declared in your HTML),
//render the item data into it, and return the result
var itemTemplate;
if (item.data.key === "foo")
itemTemplate = q("#fooItemTemplate", element); //return foo template
else if (item.data.key === "bar")
itemTemplate = q("#barItemTemplate", element);
return itemTemplate.winControl.render(item.data);
});
}
If one of the item templates has explicit style code to show or hide part and the other doesn't then you'll get the result you're looking for.
Another way is to wait until the ListView is finished loading and then traverse and manipulate the DOM according to your conditions.
To capture the moment your ListView finishes loading do this:
myListViewControl.onloadingstatechanged = function (e) {
if (myListViewControl.loadingState == "complete") {
...
}
}
The ListView goes through a few loading states as it's loading and the last is "complete". When it's complete, you can use the awesome power of CSS selectors and the new querySelector/querySelectorAll method that ECMAScript 5 gives us to find all of the ListView items like this:
myListView.querySelectorAll(".win-item")
That would return a NodeList of all of the ListView items and you can use your ninja skills in DOM manipulation to have your way with them.
Hope that helps!!
P.S. Check out codeSHOW to learn more HTML/JS dev in Windows 8 (aka.ms/codeshowapp | codeshow.codeplex.com)

How to store ajax values in a queue

I want to display some data which comes from my db using jQuery.ajax() (i.e. each one contains a title and a description) and I want this process to be done on a regular basis, say setInterval(func, 5000).
Now what I actually need is a js container (an array, a variable, whatever) in which I can store these items to and using another function, I want them to be displayed every 2 seconds.
So, in other words:
We have to have two functions and a container, if you will. The ajax function is fired every 5 seconds and appends its data (if any) into the container (a queue).
On the other hand, the second function displays the content of our container every 2 seconds and removes the displayed items from the container of course.
How can I implement this?
var queue = [];
function ajaxCall() {
$.when($.ajax(...)).done(function(data) {
///assuming your data json's outermost structure is an array
while(data[0]) {
queue.push(data.shift());
}
})
}
function publisher() {
var item = queue.shift();
if(item) {
//do your gubbins here
}
}
setInterval(ajaxCall,5000);
setInterval(publisher, 2000);
Why not using an Array. It can store a string, an object...
Take a look at this great blog post.
If you need queue, this post may help you....
first, set up a container like this one :
<div id="container">
<ul>
</ul>
</div>
then, in your .js :
//step 1. define global Array to hold the values
$(document).ready(function(){
var items = new Array();
//step 3. call the ajax every 5 second
var updateInterval = setInterval(function() {
get_the_data();
//don't forget to empty the array
items = [];
}, 5000);
//step 4. so now, with the data got refresh every 5 second, you just have to append this data into container every 2 second. use the .html method
//logically, in first two times invoke to this method (4 seconds) the container would get same data from array, but in third invocation (at 6 seconds) the container get the fresh one, and so on...
var insertToContainer = setInterval(function() {
for(i=0;i<items.length;i++){
$('#container ul').html('<li>' + items[i] + '</li>')
}
}, 2000);
});
//step 2. set up your ajax call
function get_the_data()
{
$.ajax({
//set your ajax code here...
success:function(data){
//on success put the data into the global array
for(i=0;i<data.length;i++){
items.push(data[i]);
}
}
});
}
I hope this would work for your project. Cheers :-)
PS: frankly speaking, I don't know why you wanted such implementation (i mean, 2 different update functions with different interval value), the more simple way is using just one invocation to the ajax call every 2 seconds as long as display it in container using .html , but it's just my opinion, It should be more complex logic behind your app. :-)

Javascript Closures and *static* classes problem

I have a static class which contains an array of callback functions, I then have a few other classes that are used to interact with this static class...
Here is a simple example of the static class:
var SomeStaticInstance = {};
(function(staticInstance) {
var callbacks = {};
staticInstance.addCallback = function(callback) { callbacks.push(callback); }
staticInstance.callAllCallbacks = function() { /* call them all */ }
}(SomeStaticInstance));
Then here is an example of my other classes which interact with it:
function SomeClassOne() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someCallback); }
this.someCallback = function() { /* Do something */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
function SomeClassTwo() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someOtherCallback); }
this.someOtherCallback = function() { /* Do something else */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
Now the problem I have is that when I call either class and tell it to activateCallbacks() the classes only activate the callbacks within their own scope, i.e SomeClassOne would call someCallback() but not someOtherCallback() and vice versa, now I am assuming it is something to do with the scope of the closures, however I am not sure how to get the behaviour I am after...
I have tried turning the static class into a regular class and then passing it into the 2 classes via the constructor, but still get the same issue...
So my question is how do I get the classes to raise all the callbacks
-- EDIT --
Here is an example displaying the same issue as I am getting on my actual app, I have put all script code into the page to give a clearer example:
http://www.grofit.co.uk/other/pubsub-test.html
It is a simple app with 2 presenters and 2 views... one view is concerned with adding 2 numbers at the top of the page, the 2nd view is concerned with taking that total and multiplying it and showing a result.
The 3rd party library I am using is PubSubJS, and the first presenter listens for an event to tell it that the one of the boxes has changed and re-totals the top row. The 2nd presenter listens for when the multiply or total at the top changes, then recalculates the bottom one. Now the first presenter recalculates correctly, and the 2nd presenter will correctly recalculate whenever the multiply box changes, HOWEVER! It will NOT recalculate when the total on the top changes, even thought it should receive the notification...
Anyway take a quick look through the source code on the page to see what I mean...
First, I think you want var callbacks = [] (an array instead of an object) since you're using callbacks.push().
I'm not sure I understand your problem. The way your classes are structured, you can achieve what you want by instantiating both classes and calling addCallbackToStaticInstance() on both new objects. E.g.,
var one = new SomeClassOne();
var two = new SomeClassTwo();
one.addCallbackToStaticInstance();
two.addCallbackToStaticInstance();
one.activateCallbacks();
Then, as above, you can call activateCallbacks() from either object.
If you're saying you want to be able to call activateCallback() after instantiating only one of the classes, you really have to rethink your approach. I'd start with moving addCallbackToStaticInstance() and activateCallbacks() into their own class.
This is a very odd way of doing things, but your main problem is that your callbacks object it not part of SomeStaticInstance, it is defined within an anonymous closure. Also your callbacks object {} should be an array [].
try staticInstance.callbacks = []; instead of var callbacks = {};
and
staticInstance.addCallback = function(callback) {
this.callbacks.push(callback);
}

Binding multiple events of the same type?

Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.

Categories

Resources