KO v2.1.0 beta available - please try it with your apps

1,229 views
Skip to first unread message

fla...@gmail.com

unread,
Mar 23, 2012, 7:51:05 PM3/23/12
to KnockoutJS
Hi all

The KO community has been hard at work for a few months now preparing
version 2.1.0. It's not 100% finalised, but it's very very close. To
enable a swift release, I'd love to get feedback from a wider audience
at this point.

So, you can get v2.1.0beta now from https://github.com/SteveSanderson/knockout/downloads
- please try it in your apps, and let us know of any surprises...

What's new?
=========
* Tonnes of performance improvements, especially to do with the KO
2.0.0 features (native templating, etc.)
* Support for AMD/CommonJS script loaders
* Builds correctly on Mac OS X
* Some new extensibility options (custom bindings can now work with
virtual elements, binding contexts can have custom properties)
* Bugfixes (stopped a rare memory leak condition, SELECT boxes no
longer flicker or scroll incorrectly on WebKit)
* A lot more small enhancements (e.g., 'css' binding no longer
prepends space to class attribute, isComputed function, attr:
className, use textareas to supply template text, works on XHTML docs
- see the commit log for full details :)

Special thanks to Michael Best for implementing a lot of this.

Mainly, if it's 100% backward compatible with your existing apps, and
is faster, then we've succeeded. Let us know!

Steve

Michael Best

unread,
Mar 23, 2012, 10:35:07 PM3/23/12
to KnockoutJS
Another enhancement is that you can access the current index (zero-
based) of an item within a foreach binding:

<div data-bind="foreach: myItems">
<span data-bind="text: $index"></span>
</div>

`$index` is stored as part of the binding context for each item and is
an observable. Thus if you wanted to output the one-based index, for
example, you'd do it like this:

<span data-bind="text: $index() + 1"></span>

-- Michael

On Mar 23, 1:51 pm, "st...@codeville.net" <fla...@gmail.com> wrote:
> What's new?

张子超

unread,
Mar 24, 2012, 12:20:38 AM3/24/12
to knock...@googlegroups.com
oh,yeah~  try it later~

BTW, where can I find the API document? 


--
Do not be evil.

benjamin.sa...@gmail.com

unread,
Mar 24, 2012, 10:57:27 AM3/24/12
to knock...@googlegroups.com
Big thumbs up!

Juan Vidal Pich

unread,
Mar 24, 2012, 1:12:36 PM3/24/12
to KnockoutJS
Plugins are breaking when loaded from RequireJS.
I use RequireJS and use.js (https://github.com/tbranyen/use.js) to
declare the dependencies of non-AMD files.

I have this kind of definition in main.js:

require.config({
paths:
{
/*...*/
knockout: 'knockout-latest.debug',
'knockout-sortable': 'libs/knockout-sortable',
},
use:
{
/*...*/
'knockout-sortable':
{
deps: ['jqueryui', 'knockout']
}
}
});

Then in my usage of ko I do this:

require(["jquery", "knockout", "use!knockout-sortable"], function ($,
ko)
{
$(document).ready(function ()
{
ko.applyBindings(new ViewModel());
});
});

As ko is never defined in the global context, only properly
modularized code can access it. In particular, existing plugins fail.

I declared a module that just defines the global ko and put it as
dependency of legacy plugins, but it doesn't feel right.
Am I missing something here?
Message has been deleted

jga...@gmail.com

unread,
Mar 25, 2012, 8:40:45 AM3/25/12
to knock...@googlegroups.com
Looking good for me so far; still constructing my app so nothing surprising in my core knockout viewmodel code yet, but ajax databinds still working merrily with my most complex library - my tweaked custom binding for use with DataTables, themed for bootstrap (based upon https://github.com/CogShift/Knockout.Extensions and http://datatables.net/​blog/Twitter_Bootstrap_2).

Will have to add some debug code to calculate render times to see if its any quicker!

Great stuff though. Am loving my last few days with knockout.js so far, so much nicer than angular. 
Like the fix for no space on css class bindings, wondered why it was doing that :) Will followup if I find anything breaks as I continue dev.

thetrompf

unread,
Mar 25, 2012, 8:54:20 PM3/25/12
to knock...@googlegroups.com
It looks good for me.. great with the $index special variable for foreach bindings, I have been missing that a lot :D

Anyway, my applications work without any problems at all, and it is running pretty smooth.. Kudos

Olivier Lamy

unread,
Mar 26, 2012, 6:04:58 AM3/26/12
to knock...@googlegroups.com
Hello,
Just tested the upgrade.
FF shows me:

Mismatched anonymous define() module: function (koExports) { var ko = typeof koExports !== "undefined" ? koExports : {}; ko.exportSymbol = function (koPath, object) {var tokens = koPath.split(".");var target = ko;for (var i = 0; i < tokens.length - 1; i++) {target = target[tokens[i]];}target[tokens[tokens.length - 1]] = object;}; ko.exportProperty = function (owner, publicName, object) {owner[publicName] = object;}; ko.version = "2.1.0beta"; ko.exportSymbol("version", ko.version);
.....

??

Thanks,
--
Olivier

greg....@gmail.com

unread,
Mar 26, 2012, 12:10:24 PM3/26/12
to knock...@googlegroups.com
Binding attributes doesn't appear to be working in IE 8.  With Knockout 2, I have a set of radio button generated in a loop...

<!-- ko foreach: {data: $data.questionAnswers()} -->
<td class="option"><label class="label_radio"><input type="radio" data-bind="{attr: {name: 'q'+ $parent.questionID}, value: $data.answerID, checked: $data.selectedValue(), click: function(data, event){ $(event.target).focus(); $root.updateSurveyResponse(event.target, $data, $parent); return true; }}" /></label></td>
<!-- /ko -->

...With Knockout 2, I can then use $(this).attr('name') to get back q1, q2, etc.  With Knockout 2.1 beta, I get undefined for any attribute that is dynamically added in the foreach loop in IE 8.  It seems to work correctly with Chrome.

Michael Best

unread,
Mar 26, 2012, 4:48:00 PM3/26/12
to KnockoutJS
I'm haven't been able to reproduce the problem you describe. Could you
set up an example in jsfiddle that reproduces it?

-- Michael

Michael Best

unread,
Mar 26, 2012, 5:42:55 PM3/26/12
to KnockoutJS
You can use this fiddle to start with: http://jsfiddle.net/mbest/TPkaj/

Olivier Lamy

unread,
Mar 27, 2012, 3:54:43 AM3/27/12
to knock...@googlegroups.com
Ok require dependencies fixed.

But now I have some issues with binding (simpleGrid and sortable).

FF error: 


any idea ?


Thanks,
--
Olivier

greg....@gmail.com

unread,
Mar 27, 2012, 10:03:40 AM3/27/12
to knock...@googlegroups.com
I can't reproduce it in the Fiddle either.  I updated to jQuery 1.7.2 as well.  Maybe it's something different in there that's causing the issue.  I'll try reverting that to 1.7.1 to see if that fixes the problem.

greg....@gmail.com

unread,
Mar 27, 2012, 10:14:53 AM3/27/12
to knock...@googlegroups.com
I set the fiddle to use 1.7.2 of jQuery and it still works.  Now I'm really lost as to why my "real" code won't work with the 2.1 beta.

greg....@gmail.com

unread,
Mar 27, 2012, 10:16:41 AM3/27/12
to knock...@googlegroups.com
Here's the updated fiddle...


...and sorry about all the posts!

Casey Corcoran

unread,
Mar 27, 2012, 11:54:36 AM3/27/12
to knock...@googlegroups.com
I'm having issues in the app I'm building when upgrading from 2.0.
It's fairly complex so I'm not exactly sure what's going wrong but it
basically boils down to this...

I'm using observables as providers to the "template" binding like so:

<div data-bind="template:foo"></div>

Then in the viewModel (pseudo-code):

function changeModule() {
foo({
name:"myTemplate",
data: mySubViewModel,
afterRender: mySubViewModel.doTransitionsAndWhatNot
});
}

In 2.0 it's bulletproof but it seems to be flaky in 2.1 where
sometimes the "module" is never swapped out. I haven't seen any errors
in the console, and the fiddle where I tried to reproduce it worked
fine, so it's not clear-cut.

I'll try to get to the root of it and let you know what I find, in the
meantime I figured I would post about it to see if the synopsis rings
any bells with things that may have been changed in 2.1.

Thanks for all the hard work!
Casey

Michael Best

unread,
Mar 27, 2012, 4:12:39 PM3/27/12
to KnockoutJS
Casey,

I might be because 2.1.0 prevents recursive updates to computed
observables. That's the only thing I can think of that would cause
some updates to be lost. To verify it, use this copy of knockout-2.1.0
that includes a console call when an update is skipped due to
recursion:

https://github.com/downloads/mbest/knockout/knockout-2.1.0beta.recursive_log.debug.js

tcha...@googlemail.com

unread,
Mar 28, 2012, 5:41:24 AM3/28/12
to knock...@googlegroups.com
Hi, 
tried the current beta release but got some trouble with the "registerEventHandler". I am using the jQuery UI Datepicker and do something like this:

var element = "#dp";

        ko.utils.registerEventHandler(element, "change"function () {
            var observable = valueAccessor();
            
            //jQuery UI DatePicker
            var value = $.datepicker.formatDate("yy-mm-dd", $(element).datepicker("getDate")) + " 00:00:00";
            observable(value);
            viewModel.SetDate(value);
        });

Another example would be a dropdown menu that should be closed if someone clicks outside the menu e.g. the body. For this to handle i have defined the following event handler:

// close dropdown on clicking outside of control
		ko.utils.registerEventHandler($(element), "click"function (event) {
			var sender = event.srcElement ? event.srcElement : event.target;
			if( ! $(sender).hasClass("search-query"))
				eventTarget.fadeOut('fast');
		});

so with 2.0.0 there was no problem with that, but with 2.1. i am getting the following exception

Cannot call method 'toLowerCase' of undefined 

thrown by a changed implementation from this: 

function isClickOnCheckableElement(element, eventType) {
        if ((element.tagName != "INPUT") || !element.type) return false;
        if (eventType.toLowerCase() != "click") return false;
        var inputType = element.type.toLowerCase();
        return (inputType == "checkbox") || (inputType == "radio");
    }

to that: (the following snippet is from 2.1 beta)
function isClickOnCheckableElement(element, eventType) {
if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
if (eventType.toLowerCase() != "click") return false;
var inputType = element.type;
return (inputType == "checkbox") || (inputType == "radio");
}



that calls tagNameLower, but the elements tagName is not defined due to my usage of the "element" parameter in my eventHandlers.

So my question is how do i have to change my way of registering event handlers in custom binding handlers to get my code working.

dafasdf



Michael Best

unread,
Mar 28, 2012, 7:00:50 AM3/28/12
to KnockoutJS
Change it to ko.utils.registerEventHandler(element, "click", function
(event) {

-- Michael

On Mar 27, 11:41 pm, "A...@codeandsugar.com"

tcha...@googlemail.com

unread,
Mar 28, 2012, 8:03:11 AM3/28/12
to knock...@googlegroups.com
Hi Michael,

thank you for your suggestion. I tried that already for the line you mentioned, but i need to bind the event handler not to the element itself, but to any parent element, e.g. the document or the body. 

i tried the following selectors, but i still don't get it to work

registerEventHandler(element, "click", function () ... )
registerEventHandler("body", "click", function () ... ) 
registerEventHandler($(body), "click", function () ... ) 
registerEventHandler($(document), "click", function () ... ) 

the same with my "datepicker" event handler that needs to bind to an hidden input element instead of binding it to the
event sending/triggering element (the day that was clicked by the user) because i want to bind to the "change" event of the hidden input field.

rpn

unread,
Mar 28, 2012, 8:59:47 AM3/28/12
to knock...@googlegroups.com
registerEventHandler expect a DOM node, so it doesn't know how to operate on a jQuery result set.  However, you can get the first node in the result by doing:  $("myselector").get(0) or $("myselector")[0]

tcha...@googlemail.com

unread,
Mar 28, 2012, 9:31:56 AM3/28/12
to knock...@googlegroups.com
oh i am really sorry. i totally forgot to say: thank you people so much for this update! the performance is much better than in 2.0.0

tcha...@googlemail.com

unread,
Mar 28, 2012, 11:15:22 AM3/28/12
to knock...@googlegroups.com
thank you rpn, you saved my day :)

Jeff

unread,
Mar 28, 2012, 11:44:26 AM3/28/12
to KnockoutJS
Hi!

I'm excited about getting the v2.1 beta working because I think it is
supposed to fix an "IE dropdown flickering" issue that's afflicting an
MVC app I'm delivering soon.

However, when it tries to apply bindings in either Firefox or IE9, I
get the message "element.tagName is undefined"

occurring on line 252 inside the tagNameLower function (on the
"return" line):
tagNameLower: function(element) {
// For HTML elements, tagName will always be upper case; for XHTML
elements, it'll be lower case.
// Possible future optimization: If we know it's an element from an
XHTML document (not HTML),
// we don't need to do the .toLowerCase() as it will always be lower
case anyway.
return element.tagName.toLowerCase();
},

The DOM element it appears to be complaining about is this:

<TextNode textContent="\n ">

And I'm not even sure where this TextNode is coming from! :( It's
not in my source, so I wonder if a library or even the browser itself
is adding it.

Any advice? So eager to get this version working!

Jeff

Roy Jacobs

unread,
Mar 28, 2012, 12:33:49 PM3/28/12
to knock...@googlegroups.com
It seems to work flawlessly (in Chrome at least) and I'm definitely seeing some speedups in situations where we have lots of nested dependent observables. Kudos(*) to all involved!

(*) Not exchangeable for money

Casey Corcoran

unread,
Mar 28, 2012, 4:47:45 PM3/28/12
to knock...@googlegroups.com
Michael,

Thanks for the file and willingness to help. I ran it with the debug
file no problems, then switched back to the compressed beta file and
it worked as well. So the problem may have been something else in my
app (I was updating a lot of things yesterday) and all seems to be
working for now.

Regards,
Casey Corcoran

Jeff

unread,
Mar 28, 2012, 5:21:37 PM3/28/12
to KnockoutJS
I fixed that earlier issue with the v2.1 beta by just modifying the
tagNameLower function to return "textnode" if the tag's name was,
well, "TextNode" . . .

However, I am finding that there is a new, not-much-better issue with
dropdown lists in IE! Whenever I click on any of them, its contents
"jump" so that the selected <li> is at the top of the list. This is
really non-standard behavior and adversely affects the user
experience. Any ideas?

Jeff

Michael Best

unread,
Mar 28, 2012, 6:57:36 PM3/28/12
to KnockoutJS
On Mar 28, 11:21 am, Jeff <blaster...@gmail.com> wrote:
> I fixed that earlier issue with the v2.1 beta by just modifying the
> tagNameLower function to return "textnode" if the tag's name was,
> well, "TextNode" . . .

I opened an issue on Github about this:

> However, I am finding that there is a new, not-much-better issue with
> dropdown lists in IE!  Whenever I click on any of them, its contents
> "jump" so that the selected <li> is at the top of the list.  This is
> really non-standard behavior and adversely affects the user
> experience.  Any ideas?

Which version of IE? Would you be able to create an example to
reproduce it?

Thanks,
Michael

Michael Best

unread,
Mar 28, 2012, 6:58:14 PM3/28/12
to KnockoutJS


On Mar 28, 12:57 pm, Michael Best <mb...@dasya.com> wrote:
>
> I opened an issue on Github about this:

https://github.com/SteveSanderson/knockout/issues/411

Lou Gallo

unread,
Apr 2, 2012, 11:44:20 AM4/2/12
to KnockoutJS
I'm getting an error in the 2.1.0beta version:

"Cannot call method 'removeChild' of null." ( see annotation near
bottom of the pasted code below)

I don't get this error in 2.0.0 nor the earlier 1.2.1

It happens in IEv9, FireFox 11 and Chrome 18.

I'm using jQuery.tmpl.js

Let me know if you need more info.



ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode,
array, mapping, options, callbackAfterAddingNodes) {
// Compare the provided array against the previous one
array = array || [];
options = options || {};
var isFirstExecution = ko.utils.domData.get(domNode,
lastMappingResultDomDataKey) === undefined;
var lastMappingResult = ko.utils.domData.get(domNode,
lastMappingResultDomDataKey) || [];
var lastArray = ko.utils.arrayMap(lastMappingResult, function
(x) { return x.arrayEntry; });
var editScript = ko.utils.compareArrays(lastArray, array);

// Build the new mapping result
var newMappingResult = [];
var lastMappingResultIndex = 0;
var nodesToDelete = [];
var newMappingResultIndex = 0;
var nodesAdded = [];
var insertAfterNode = null;
for (var i = 0, j = editScript.length; i < j; i++) {
switch (editScript[i].status) {
case "retained":
// Just keep the information - don't touch the
nodes
var dataToRetain =
lastMappingResult[lastMappingResultIndex];

dataToRetain.indexObservable(newMappingResultIndex);
newMappingResultIndex =
newMappingResult.push(dataToRetain);
if (dataToRetain.domNodes.length > 0)
insertAfterNode =
dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
lastMappingResultIndex++;
break;

case "deleted":
// Stop tracking changes to the mapping for these
nodes

lastMappingResult[lastMappingResultIndex].dependentObservable.dispose();

// Queue these nodes for later removal

fixUpVirtualElements(lastMappingResult[lastMappingResultIndex].domNodes);

ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes,
function (node) {
nodesToDelete.push({
element: node,
index: i,
value: editScript[i].value
});
insertAfterNode = node;
});
lastMappingResultIndex++;
break;

case "added":
var valueToMap = editScript[i].value;
var indexObservable =
ko.observable(newMappingResultIndex);
var mapData =
mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap,
callbackAfterAddingNodes, indexObservable);
var mappedNodes = mapData.mappedNodes;

// On the first evaluation, insert the nodes at
the current insertion point
newMappingResultIndex = newMappingResult.push({
arrayEntry: editScript[i].value,
domNodes: mappedNodes,
dependentObservable:
mapData.dependentObservable,
indexObservable: indexObservable
});
for (var nodeIndex = 0, nodeIndexMax =
mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
var node = mappedNodes[nodeIndex];
nodesAdded.push({
element: node,
index: i,
value: editScript[i].value
});
if (insertAfterNode == null) {
// Insert "node" (the newly-created node)
as domNode's first child
ko.virtualElements.prepend(domNode, node);
} else {
// Insert "node" into "domNode"
immediately after "insertAfterNode"
ko.virtualElements.insertAfter(domNode,
node, insertAfterNode);
}
insertAfterNode = node;
}
if (callbackAfterAddingNodes)
callbackAfterAddingNodes(valueToMap,
mappedNodes, indexObservable);
break;
}
}

ko.utils.arrayForEach(nodesToDelete, function (node)
{ ko.cleanNode(node.element) });

var invokedBeforeRemoveCallback = false;
if (!isFirstExecution) {
if (options['afterAdd']) {
for (var i = 0; i < nodesAdded.length; i++)
options['afterAdd'](nodesAdded[i].element,
nodesAdded[i].index, nodesAdded[i].value);
}
if (options['beforeRemove']) {
for (var i = 0; i < nodesToDelete.length; i++)
options['beforeRemove'](nodesToDelete[i].element,
nodesToDelete[i].index, nodesToDelete[i].value);
invokedBeforeRemoveCallback = true;
}
}
if (!invokedBeforeRemoveCallback && nodesToDelete.length) {
var commonParent = nodesToDelete[0].element.parentNode;
for (var i = 0; i < nodesToDelete.length; i++)
commonParent.removeChild(nodesToDelete[i].element);
<----------------------------------------------------- stops here
}

// Store a copy of the array items we just considered so we
can difference it next time
ko.utils.domData.set(domNode, lastMappingResultDomDataKey,
newMappingResult);
}
})();

Zero21xxx

unread,
Apr 2, 2012, 3:05:22 PM4/2/12
to knock...@googlegroups.com
I've upgraded to the beta version and I have run into an issue. I've got an observable array that get's populated via an AJAX call. When the call comes back, I map the server values to client-side object and then set the model value via self.roles(mappedRoles). This is properly rendered to the DOM. I have a simple custom binding for the draggable jQuery 

ko.bindingHandlers.jqui_draggable = {
    init: function (element, valueAccessor) {
        var options = valueAccessor() || {};
        $(element).draggable(options);
    }
};

Here is a sample of the HTML:
<div id="parties-roles" style="width: auto; padding-right: 10px">
<h2>
Roles</h2>
<!-- ko foreach: roles -->
<div style="width: 200px" class="draggable" data-bind="jqui_draggable:{
'revert': 'invalid',
'containment': '#partiesDockets',
'helper': 'clone'
},attr:{'id':'role'+id}">
<span data-bind="text:name"></span>
</div>
<!-- /ko -->
</div>


In IE8, only the first element that gets rendered works as a proper draggable item, the others just sit there. If I roll my version of KO back to 2.0.0.1, it works correctly again with no other changes. I've tried to recreate the issue with jsFiddle, but I was unable to. It works fine in Chrome.

Michael Best

unread,
Apr 2, 2012, 4:22:14 PM4/2/12
to KnockoutJS
Others have report that. We're tracking this issue on Github:
https://github.com/SteveSanderson/knockout/issues/413

gaffe

unread,
Apr 2, 2012, 4:34:38 PM4/2/12
to KnockoutJS
These are all very welcome improvements, thanks!

This version works well as far as I can tell so far,in a few days I'll
know for sure.

Not sure if I am the only one running into this but have you had any
requests to, in a future version, allow the checked binding to work
with objects like the other bindings do? I find myself going through
contortions to use checkboxes in my application, often needing to do
things like:

<input type="radio" name="LayoutGroup" data-bind="attr: {value:
$data.Id()}, checked: viewModel.layoutId, click: viewModel.getCounts" /
>

Am I missing something? Putting an object into the attr: {value:
doesn't seem to work. Do I really need to maintain a separate object
list then lookup the object by ID each time just to get at it and
access other properties of that object?



Maps

unread,
Apr 3, 2012, 9:58:50 AM4/3/12
to knock...@googlegroups.com
Hello,

I tested the beta version and I seem to be having some problems related to the new behavior of preventing recursive updates to computed observables.

I am using a dirty flag implementation based on the basic dirty flag example on: http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html

The behavior is that the dirty flag stops updating properly when the UI updates the view model. If I use the debug version provided below, the console log refers to the dirty flag code.

Will the "prevent recursive updates" be the new behavior from now on?
Can I change/configure the recursion level, or should I plan on changing the dirty flag implementation?

Thank you for a great library!

rpn

unread,
Apr 3, 2012, 10:13:46 AM4/3/12
to knock...@googlegroups.com
@Maps - can you show how you have implemented your dirty flag in a fiddle?  Here is one with the latest KO and dirty flag:  http://jsfiddle.net/rniemeyer/s47ck/ 

Maps

unread,
Apr 3, 2012, 1:12:26 PM4/3/12
to knock...@googlegroups.com
@rpn
Hello, I took a look at your fiddle and after seeing that you are using the "smarts" version of the dirty flag, I did some tests with that version, and it seems the smart version does not suffer from the recursion behavior.

The recursion only happened on some pages/view models of the application, maybe related with the instruction "ko.toJS(root); //just for subscriptions".

Anyway, for the moment I think that I will have to switch to the smart version of the flag.

Thank you for your help
Reply all
Reply to author
Forward
0 new messages