Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
todomvc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Eugene Shen
todomvc
Commits
c5e53bc8
Commit
c5e53bc8
authored
Apr 26, 2014
by
Pascal Hartig
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Upgrade Knockout to v3.1.0
parent
0a50f32a
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
951 additions
and
287 deletions
+951
-287
architecture-examples/knockoutjs/bower.json
architecture-examples/knockoutjs/bower.json
+1
-1
architecture-examples/knockoutjs/bower_components/component-knockout-passy/knockout.js
...tjs/bower_components/component-knockout-passy/knockout.js
+930
-273
architecture-examples/knockoutjs/bower_components/director/build/director.js
...es/knockoutjs/bower_components/director/build/director.js
+19
-12
architecture-examples/knockoutjs/index.html
architecture-examples/knockoutjs/index.html
+1
-1
No files found.
architecture-examples/knockoutjs/bower.json
View file @
c5e53bc8
...
...
@@ -3,7 +3,7 @@
"version"
:
"0.0.0"
,
"dependencies"
:
{
"todomvc-common"
:
"~0.1.4"
,
"
knockout.js"
:
"~3.0
.0"
,
"
component-knockout-passy"
:
"~3.1
.0"
,
"director"
:
"~1.2.0"
}
}
architecture-examples/knockoutjs/bower_components/
knockout.js/knockout.debug
.js
→
architecture-examples/knockoutjs/bower_components/
component-knockout-passy/knockout
.js
View file @
c5e53bc8
// Knockout JavaScript library v3.0.0
// (c) Steven Sanderson - http://knockoutjs.com/
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
/*!
* Knockout JavaScript library v3.1.0
* (c) Steven Sanderson - http://knockoutjs.com/
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
(
function
(){
var
DEBUG
=
true
;
...
...
@@ -17,15 +19,15 @@ var DEBUG=true;
if
(
typeof
require
===
'
function
'
&&
typeof
exports
===
'
object
'
&&
typeof
module
===
'
object
'
)
{
// [1] CommonJS/Node.js
var
target
=
module
[
'
exports
'
]
||
exports
;
// module.exports is for Node.js
factory
(
target
);
factory
(
target
,
require
);
}
else
if
(
typeof
define
===
'
function
'
&&
define
[
'
amd
'
])
{
// [2] AMD anonymous module
define
([
'
exports
'
],
factory
);
define
([
'
exports
'
,
'
require
'
],
factory
);
}
else
{
// [3] No module loader (plain <script> tag) - put directly in global namespace
factory
(
window
[
'
ko
'
]
=
{});
}
}(
function
(
koExports
){
}(
function
(
koExports
,
require
){
// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
var
ko
=
typeof
koExports
!==
'
undefined
'
?
koExports
:
{};
...
...
@@ -44,17 +46,35 @@ ko.exportSymbol = function(koPath, object) {
ko
.
exportProperty
=
function
(
owner
,
publicName
,
object
)
{
owner
[
publicName
]
=
object
;
};
ko
.
version
=
"
3.
0
.0
"
;
ko
.
version
=
"
3.
1
.0
"
;
ko
.
exportSymbol
(
'
version
'
,
ko
.
version
);
ko
.
utils
=
(
function
()
{
var
objectForEach
=
function
(
obj
,
action
)
{
function
objectForEach
(
obj
,
action
)
{
for
(
var
prop
in
obj
)
{
if
(
obj
.
hasOwnProperty
(
prop
))
{
action
(
prop
,
obj
[
prop
]);
}
}
};
}
function
extend
(
target
,
source
)
{
if
(
source
)
{
for
(
var
prop
in
source
)
{
if
(
source
.
hasOwnProperty
(
prop
))
{
target
[
prop
]
=
source
[
prop
];
}
}
}
return
target
;
}
function
setPrototypeOf
(
obj
,
proto
)
{
obj
.
__proto__
=
proto
;
return
obj
;
}
var
canSetPrototype
=
({
__proto__
:
[]
}
instanceof
Array
);
// Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
var
knownEvents
=
{},
knownEventTypesByEventName
=
{};
...
...
@@ -98,7 +118,7 @@ ko.utils = (function () {
arrayForEach
:
function
(
array
,
action
)
{
for
(
var
i
=
0
,
j
=
array
.
length
;
i
<
j
;
i
++
)
action
(
array
[
i
]);
action
(
array
[
i
]
,
i
);
},
arrayIndexOf
:
function
(
array
,
item
)
{
...
...
@@ -112,15 +132,19 @@ ko.utils = (function () {
arrayFirst
:
function
(
array
,
predicate
,
predicateOwner
)
{
for
(
var
i
=
0
,
j
=
array
.
length
;
i
<
j
;
i
++
)
if
(
predicate
.
call
(
predicateOwner
,
array
[
i
]))
if
(
predicate
.
call
(
predicateOwner
,
array
[
i
]
,
i
))
return
array
[
i
];
return
null
;
},
arrayRemoveItem
:
function
(
array
,
itemToRemove
)
{
var
index
=
ko
.
utils
.
arrayIndexOf
(
array
,
itemToRemove
);
if
(
index
>
=
0
)
if
(
index
>
0
)
{
array
.
splice
(
index
,
1
);
}
else
if
(
index
===
0
)
{
array
.
shift
();
}
},
arrayGetDistinctValues
:
function
(
array
)
{
...
...
@@ -137,7 +161,7 @@ ko.utils = (function () {
array
=
array
||
[];
var
result
=
[];
for
(
var
i
=
0
,
j
=
array
.
length
;
i
<
j
;
i
++
)
result
.
push
(
mapping
(
array
[
i
]));
result
.
push
(
mapping
(
array
[
i
]
,
i
));
return
result
;
},
...
...
@@ -145,7 +169,7 @@ ko.utils = (function () {
array
=
array
||
[];
var
result
=
[];
for
(
var
i
=
0
,
j
=
array
.
length
;
i
<
j
;
i
++
)
if
(
predicate
(
array
[
i
]))
if
(
predicate
(
array
[
i
]
,
i
))
result
.
push
(
array
[
i
]);
return
result
;
},
...
...
@@ -170,16 +194,13 @@ ko.utils = (function () {
}
},
extend
:
function
(
target
,
source
)
{
if
(
source
)
{
for
(
var
prop
in
source
)
{
if
(
source
.
hasOwnProperty
(
prop
))
{
target
[
prop
]
=
source
[
prop
];
}
}
}
return
target
;
},
canSetPrototype
:
canSetPrototype
,
extend
:
extend
,
setPrototypeOf
:
setPrototypeOf
,
setPrototypeOfOrExtend
:
canSetPrototype
?
setPrototypeOf
:
extend
,
objectForEach
:
objectForEach
,
...
...
@@ -262,7 +283,7 @@ ko.utils = (function () {
// Rule [A]
while
(
continuousNodeArray
.
length
&&
continuousNodeArray
[
0
].
parentNode
!==
parentNode
)
continuousNodeArray
.
s
plice
(
0
,
1
);
continuousNodeArray
.
s
hift
(
);
// Rule [B]
if
(
continuousNodeArray
.
length
>
1
)
{
...
...
@@ -346,21 +367,7 @@ ko.utils = (function () {
registerEventHandler
:
function
(
element
,
eventType
,
handler
)
{
var
mustUseAttachEvent
=
ieVersion
&&
eventsThatMustBeRegisteredUsingAttachEvent
[
eventType
];
if
(
!
mustUseAttachEvent
&&
typeof
jQuery
!=
"
undefined
"
)
{
if
(
isClickOnCheckableElement
(
element
,
eventType
))
{
// For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
// it toggles the element checked state *after* the click event handlers run, whereas native
// click events toggle the checked state *before* the event handler.
// Fix this by intecepting the handler and applying the correct checkedness before it runs.
var
originalHandler
=
handler
;
handler
=
function
(
event
,
eventData
)
{
var
jQuerySuppliedCheckedState
=
this
.
checked
;
if
(
eventData
)
this
.
checked
=
eventData
.
checkedStateBeforeEvent
!==
true
;
originalHandler
.
call
(
this
,
event
);
this
.
checked
=
jQuerySuppliedCheckedState
;
// Restore the state jQuery applied
};
}
if
(
!
mustUseAttachEvent
&&
jQuery
)
{
jQuery
(
element
)[
'
bind
'
](
eventType
,
handler
);
}
else
if
(
!
mustUseAttachEvent
&&
typeof
element
.
addEventListener
==
"
function
"
)
element
.
addEventListener
(
eventType
,
handler
,
false
);
...
...
@@ -382,13 +389,14 @@ ko.utils = (function () {
if
(
!
(
element
&&
element
.
nodeType
))
throw
new
Error
(
"
element must be a DOM node when calling triggerEvent
"
);
if
(
typeof
jQuery
!=
"
undefined
"
)
{
var
eventData
=
[];
if
(
isClickOnCheckableElement
(
element
,
eventType
))
{
// Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
eventData
.
push
({
checkedStateBeforeEvent
:
element
.
checked
});
}
jQuery
(
element
)[
'
trigger
'
](
eventType
,
eventData
);
// For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the
// event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.)
// IE doesn't change the checked state when you trigger the click event using "fireEvent".
// In both cases, we'll use the click method instead.
var
useClickWorkaround
=
isClickOnCheckableElement
(
element
,
eventType
);
if
(
jQuery
&&
!
useClickWorkaround
)
{
jQuery
(
element
)[
'
trigger
'
](
eventType
);
}
else
if
(
typeof
document
.
createEvent
==
"
function
"
)
{
if
(
typeof
element
.
dispatchEvent
==
"
function
"
)
{
var
eventCategory
=
knownEventTypesByEventName
[
eventType
]
||
"
HTMLEvents
"
;
...
...
@@ -398,15 +406,13 @@ ko.utils = (function () {
}
else
throw
new
Error
(
"
The supplied element doesn't support dispatchEvent
"
);
}
else
if
(
useClickWorkaround
&&
element
.
click
)
{
element
.
click
();
}
else
if
(
typeof
element
.
fireEvent
!=
"
undefined
"
)
{
// Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
// so to make it consistent, we'll do it manually here
if
(
isClickOnCheckableElement
(
element
,
eventType
))
element
.
checked
=
element
.
checked
!==
true
;
element
.
fireEvent
(
"
on
"
+
eventType
);
}
else
}
else
{
throw
new
Error
(
"
Browser doesn't support triggering events
"
);
}
},
unwrapObservable
:
function
(
value
)
{
...
...
@@ -438,7 +444,7 @@ ko.utils = (function () {
// we'll clear everything and create a single text node.
var
innerTextNode
=
ko
.
virtualElements
.
firstChild
(
element
);
if
(
!
innerTextNode
||
innerTextNode
.
nodeType
!=
3
||
ko
.
virtualElements
.
nextSibling
(
innerTextNode
))
{
ko
.
virtualElements
.
setDomNodeChildren
(
element
,
[
d
ocument
.
createTextNode
(
value
)]);
ko
.
virtualElements
.
setDomNodeChildren
(
element
,
[
element
.
ownerD
ocument
.
createTextNode
(
value
)]);
}
else
{
innerTextNode
.
data
=
value
;
}
...
...
@@ -687,16 +693,13 @@ ko.utils.domNodeDisposal = new (function () {
callbacks
[
i
](
node
);
}
//
Also e
rase the DOM data
//
E
rase the DOM data
ko
.
utils
.
domData
.
clear
(
node
);
// Special support for jQuery here because it's so commonly used.
// Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
// so notify it to tear down any resources associated with the node & descendants here.
if
((
typeof
jQuery
==
"
function
"
)
&&
(
typeof
jQuery
[
'
cleanData
'
]
==
"
function
"
))
jQuery
[
'
cleanData
'
]([
node
]);
// Perform cleanup needed by external libraries (currently only jQuery, but can be extended)
ko
.
utils
.
domNodeDisposal
[
"
cleanExternalData
"
](
node
);
//
Also c
lear any immediate-child comment nodes, as these wouldn't have been found by
//
C
lear any immediate-child comment nodes, as these wouldn't have been found by
// node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
if
(
cleanableNodeTypesWithDescendants
[
node
.
nodeType
])
cleanImmediateCommentTypeChildren
(
node
);
...
...
@@ -748,6 +751,14 @@ ko.utils.domNodeDisposal = new (function () {
ko
.
cleanNode
(
node
);
if
(
node
.
parentNode
)
node
.
parentNode
.
removeChild
(
node
);
},
"
cleanExternalData
"
:
function
(
node
)
{
// Special support for jQuery here because it's so commonly used.
// Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
// so notify it to tear down any resources associated with the node & descendants here.
if
(
jQuery
&&
(
typeof
jQuery
[
'
cleanData
'
]
==
"
function
"
))
jQuery
[
'
cleanData
'
]([
node
]);
}
}
})();
...
...
@@ -821,7 +832,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
}
ko
.
utils
.
parseHtmlFragment
=
function
(
html
)
{
return
typeof
jQuery
!=
'
undefined
'
?
jQueryHtmlParse
(
html
)
// As below, benefit from jQuery's optimisations where possible
return
jQuery
?
jQueryHtmlParse
(
html
)
// As below, benefit from jQuery's optimisations where possible
:
simpleHtmlParse
(
html
);
// ... otherwise, this simple logic will do in most common cases.
};
...
...
@@ -838,7 +849,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
// jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
// for example <tr> elements which are not normally allowed to exist on their own.
// If you've referenced jQuery we'll use that rather than duplicating its code.
if
(
typeof
jQuery
!=
'
undefined
'
)
{
if
(
jQuery
)
{
jQuery
(
node
)[
'
html
'
](
html
);
}
else
{
// ... otherwise, use KO's own parsing logic.
...
...
@@ -944,6 +955,22 @@ ko.extenders = {
});
},
'
rateLimit
'
:
function
(
target
,
options
)
{
var
timeout
,
method
,
limitFunction
;
if
(
typeof
options
==
'
number
'
)
{
timeout
=
options
;
}
else
{
timeout
=
options
[
'
timeout
'
];
method
=
options
[
'
method
'
];
}
limitFunction
=
method
==
'
notifyWhenChangesStop
'
?
debounce
:
throttle
;
target
.
limit
(
function
(
callback
)
{
return
limitFunction
(
callback
,
timeout
);
});
},
'
notify
'
:
function
(
target
,
notifyWhen
)
{
target
[
"
equalityComparer
"
]
=
notifyWhen
==
"
always
"
?
null
:
// null equalityComparer means to always notify
...
...
@@ -957,6 +984,26 @@ function valuesArePrimitiveAndEqual(a, b) {
return
oldValueIsPrimitive
?
(
a
===
b
)
:
false
;
}
function
throttle
(
callback
,
timeout
)
{
var
timeoutInstance
;
return
function
()
{
if
(
!
timeoutInstance
)
{
timeoutInstance
=
setTimeout
(
function
()
{
timeoutInstance
=
undefined
;
callback
();
},
timeout
);
}
};
}
function
debounce
(
callback
,
timeout
)
{
var
timeoutInstance
;
return
function
()
{
clearTimeout
(
timeoutInstance
);
timeoutInstance
=
setTimeout
(
callback
,
timeout
);
};
}
function
applyExtenders
(
requestedExtenders
)
{
var
target
=
this
;
if
(
requestedExtenders
)
{
...
...
@@ -976,6 +1023,7 @@ ko.subscription = function (target, callback, disposeCallback) {
this
.
target
=
target
;
this
.
callback
=
callback
;
this
.
disposeCallback
=
disposeCallback
;
this
.
isDisposed
=
false
;
ko
.
exportProperty
(
this
,
'
dispose
'
,
this
.
dispose
);
};
ko
.
subscription
.
prototype
.
dispose
=
function
()
{
...
...
@@ -984,28 +1032,32 @@ ko.subscription.prototype.dispose = function () {
};
ko
.
subscribable
=
function
()
{
ko
.
utils
.
setPrototypeOfOrExtend
(
this
,
ko
.
subscribable
[
'
fn
'
]);
this
.
_subscriptions
=
{};
ko
.
utils
.
extend
(
this
,
ko
.
subscribable
[
'
fn
'
]);
ko
.
exportProperty
(
this
,
'
subscribe
'
,
this
.
subscribe
);
ko
.
exportProperty
(
this
,
'
extend
'
,
this
.
extend
);
ko
.
exportProperty
(
this
,
'
getSubscriptionsCount
'
,
this
.
getSubscriptionsCount
);
}
var
defaultEvent
=
"
change
"
;
ko
.
subscribable
[
'
fn
'
]
=
{
var
ko_subscribable_fn
=
{
subscribe
:
function
(
callback
,
callbackTarget
,
event
)
{
var
self
=
this
;
event
=
event
||
defaultEvent
;
var
boundCallback
=
callbackTarget
?
callback
.
bind
(
callbackTarget
)
:
callback
;
var
subscription
=
new
ko
.
subscription
(
this
,
boundCallback
,
function
()
{
ko
.
utils
.
arrayRemoveItem
(
this
.
_subscriptions
[
event
],
subscription
);
}.
bind
(
this
));
var
subscription
=
new
ko
.
subscription
(
self
,
boundCallback
,
function
()
{
ko
.
utils
.
arrayRemoveItem
(
self
.
_subscriptions
[
event
],
subscription
);
});
// This will force a computed with deferEvaluation to evaluate before any subscriptions
// are registered.
if
(
self
.
peek
)
{
self
.
peek
();
}
if
(
!
this
.
_subscriptions
[
event
])
this
.
_subscriptions
[
event
]
=
[];
this
.
_subscriptions
[
event
].
push
(
subscription
);
if
(
!
self
.
_subscriptions
[
event
])
self
.
_subscriptions
[
event
]
=
[];
self
.
_subscriptions
[
event
].
push
(
subscription
);
return
subscription
;
},
...
...
@@ -1013,19 +1065,61 @@ ko.subscribable['fn'] = {
event
=
event
||
defaultEvent
;
if
(
this
.
hasSubscriptionsForEvent
(
event
))
{
try
{
ko
.
dependencyDetection
.
begin
();
ko
.
dependencyDetection
.
begin
();
// Begin suppressing dependency detection (by setting the top frame to undefined)
for
(
var
a
=
this
.
_subscriptions
[
event
].
slice
(
0
),
i
=
0
,
subscription
;
subscription
=
a
[
i
];
++
i
)
{
// In case a subscription was disposed during the arrayForEach cycle, check
// for isDisposed on each subscription before invoking its callback
if
(
subscription
&&
(
subscription
.
isDisposed
!==
true
)
)
if
(
!
subscription
.
isDisposed
)
subscription
.
callback
(
valueToNotify
);
}
}
finally
{
ko
.
dependencyDetection
.
end
();
ko
.
dependencyDetection
.
end
();
// End suppressing dependency detection
}
}
},
limit
:
function
(
limitFunction
)
{
var
self
=
this
,
selfIsObservable
=
ko
.
isObservable
(
self
),
isPending
,
previousValue
,
pendingValue
,
beforeChange
=
'
beforeChange
'
;
if
(
!
self
.
_origNotifySubscribers
)
{
self
.
_origNotifySubscribers
=
self
[
"
notifySubscribers
"
];
self
[
"
notifySubscribers
"
]
=
function
(
value
,
event
)
{
if
(
!
event
||
event
===
defaultEvent
)
{
self
.
_rateLimitedChange
(
value
);
}
else
if
(
event
===
beforeChange
)
{
self
.
_rateLimitedBeforeChange
(
value
);
}
else
{
self
.
_origNotifySubscribers
(
value
,
event
);
}
};
}
var
finish
=
limitFunction
(
function
()
{
// If an observable provided a reference to itself, access it to get the latest value.
// This allows computed observables to delay calculating their value until needed.
if
(
selfIsObservable
&&
pendingValue
===
self
)
{
pendingValue
=
self
();
}
isPending
=
false
;
if
(
self
.
isDifferent
(
previousValue
,
pendingValue
))
{
self
.
_origNotifySubscribers
(
previousValue
=
pendingValue
);
}
});
self
.
_rateLimitedChange
=
function
(
value
)
{
isPending
=
true
;
pendingValue
=
value
;
finish
();
};
self
.
_rateLimitedBeforeChange
=
function
(
value
)
{
if
(
!
isPending
)
{
previousValue
=
value
;
self
.
_origNotifySubscribers
(
value
,
beforeChange
);
}
};
},
hasSubscriptionsForEvent
:
function
(
event
)
{
return
this
.
_subscriptions
[
event
]
&&
this
.
_subscriptions
[
event
].
length
;
},
...
...
@@ -1038,9 +1132,26 @@ ko.subscribable['fn'] = {
return
total
;
},
isDifferent
:
function
(
oldValue
,
newValue
)
{
return
!
this
[
'
equalityComparer
'
]
||
!
this
[
'
equalityComparer
'
](
oldValue
,
newValue
);
},
extend
:
applyExtenders
};
ko
.
exportProperty
(
ko_subscribable_fn
,
'
subscribe
'
,
ko_subscribable_fn
.
subscribe
);
ko
.
exportProperty
(
ko_subscribable_fn
,
'
extend
'
,
ko_subscribable_fn
.
extend
);
ko
.
exportProperty
(
ko_subscribable_fn
,
'
getSubscriptionsCount
'
,
ko_subscribable_fn
.
getSubscriptionsCount
);
// For browsers that support proto assignment, we overwrite the prototype of each
// observable instance. Since observables are functions, we need Function.prototype
// to still be in the prototype chain.
if
(
ko
.
utils
.
canSetPrototype
)
{
ko
.
utils
.
setPrototypeOf
(
ko_subscribable_fn
,
Function
.
prototype
);
}
ko
.
subscribable
[
'
fn
'
]
=
ko_subscribable_fn
;
ko
.
isSubscribable
=
function
(
instance
)
{
return
instance
!=
null
&&
typeof
instance
.
subscribe
==
"
function
"
&&
typeof
instance
[
"
notifySubscribers
"
]
==
"
function
"
;
...
...
@@ -1049,40 +1160,67 @@ ko.isSubscribable = function (instance) {
ko
.
exportSymbol
(
'
subscribable
'
,
ko
.
subscribable
);
ko
.
exportSymbol
(
'
isSubscribable
'
,
ko
.
isSubscribable
);
ko
.
dependencyDetection
=
(
function
()
{
var
_frames
=
[];
ko
.
computedContext
=
ko
.
dependencyDetection
=
(
function
()
{
var
outerFrames
=
[],
currentFrame
,
lastId
=
0
;
// Return a unique ID that can be assigned to an observable for dependency tracking.
// Theoretically, you could eventually overflow the number storage size, resulting
// in duplicate IDs. But in JavaScript, the largest exact integral value is 2^53
// or 9,007,199,254,740,992. If you created 1,000,000 IDs per second, it would
// take over 285 years to reach that number.
// Reference http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html
function
getId
()
{
return
++
lastId
;
}
function
begin
(
options
)
{
outerFrames
.
push
(
currentFrame
);
currentFrame
=
options
;
}
function
end
()
{
currentFrame
=
outerFrames
.
pop
();
}
return
{
begin
:
function
(
callback
)
{
_frames
.
push
(
callback
&&
{
callback
:
callback
,
distinctDependencies
:[]
});
},
begin
:
begin
,
end
:
function
()
{
_frames
.
pop
();
},
end
:
end
,
registerDependency
:
function
(
subscribable
)
{
if
(
currentFrame
)
{
if
(
!
ko
.
isSubscribable
(
subscribable
))
throw
new
Error
(
"
Only subscribable things can act as dependencies
"
);
if
(
_frames
.
length
>
0
)
{
var
topFrame
=
_frames
[
_frames
.
length
-
1
];
if
(
!
topFrame
||
ko
.
utils
.
arrayIndexOf
(
topFrame
.
distinctDependencies
,
subscribable
)
>=
0
)
return
;
topFrame
.
distinctDependencies
.
push
(
subscribable
);
topFrame
.
callback
(
subscribable
);
currentFrame
.
callback
(
subscribable
,
subscribable
.
_id
||
(
subscribable
.
_id
=
getId
()));
}
},
ignore
:
function
(
callback
,
callbackTarget
,
callbackArgs
)
{
ignore
:
function
(
callback
,
callbackTarget
,
callbackArgs
)
{
try
{
_frames
.
push
(
null
);
begin
(
);
return
callback
.
apply
(
callbackTarget
,
callbackArgs
||
[]);
}
finally
{
_frames
.
pop
();
end
();
}
},
getDependenciesCount
:
function
()
{
if
(
currentFrame
)
return
currentFrame
.
computed
.
getDependenciesCount
();
},
isInitial
:
function
()
{
if
(
currentFrame
)
return
currentFrame
.
isInitial
;
}
};
})();
ko
.
exportSymbol
(
'
computedContext
'
,
ko
.
computedContext
);
ko
.
exportSymbol
(
'
computedContext.getDependenciesCount
'
,
ko
.
computedContext
.
getDependenciesCount
);
ko
.
exportSymbol
(
'
computedContext.isInitial
'
,
ko
.
computedContext
.
isInitial
);
ko
.
observable
=
function
(
initialValue
)
{
var
_latestValue
=
initialValue
;
...
...
@@ -1091,7 +1229,7 @@ ko.observable = function (initialValue) {
// Write
// Ignore writes if the value hasn't changed
if
(
!
observable
[
'
equalityComparer
'
]
||
!
observable
[
'
equalityComparer
'
]
(
_latestValue
,
arguments
[
0
]))
{
if
(
observable
.
isDifferent
(
_latestValue
,
arguments
[
0
]))
{
observable
.
valueWillMutate
();
_latestValue
=
arguments
[
0
];
if
(
DEBUG
)
observable
.
_latestValue
=
_latestValue
;
...
...
@@ -1105,12 +1243,13 @@ ko.observable = function (initialValue) {
return
_latestValue
;
}
}
if
(
DEBUG
)
observable
.
_latestValue
=
_latestValue
;
ko
.
subscribable
.
call
(
observable
);
ko
.
utils
.
setPrototypeOfOrExtend
(
observable
,
ko
.
observable
[
'
fn
'
]);
if
(
DEBUG
)
observable
.
_latestValue
=
_latestValue
;
observable
.
peek
=
function
()
{
return
_latestValue
};
observable
.
valueHasMutated
=
function
()
{
observable
[
"
notifySubscribers
"
](
_latestValue
);
}
observable
.
valueWillMutate
=
function
()
{
observable
[
"
notifySubscribers
"
](
_latestValue
,
"
beforeChange
"
);
}
ko
.
utils
.
extend
(
observable
,
ko
.
observable
[
'
fn
'
]);
ko
.
exportProperty
(
observable
,
'
peek
'
,
observable
.
peek
);
ko
.
exportProperty
(
observable
,
"
valueHasMutated
"
,
observable
.
valueHasMutated
);
...
...
@@ -1126,6 +1265,12 @@ ko.observable['fn'] = {
var
protoProperty
=
ko
.
observable
.
protoProperty
=
"
__ko_proto__
"
;
ko
.
observable
[
'
fn
'
][
protoProperty
]
=
ko
.
observable
;
// Note that for browsers that don't support proto assignment, the
// inheritance chain is created manually in the ko.observable constructor
if
(
ko
.
utils
.
canSetPrototype
)
{
ko
.
utils
.
setPrototypeOf
(
ko
.
observable
[
'
fn
'
],
ko
.
subscribable
[
'
fn
'
]);
}
ko
.
hasPrototype
=
function
(
instance
,
prototype
)
{
if
((
instance
===
null
)
||
(
instance
===
undefined
)
||
(
instance
[
protoProperty
]
===
undefined
))
return
false
;
if
(
instance
[
protoProperty
]
===
prototype
)
return
true
;
...
...
@@ -1157,7 +1302,7 @@ ko.observableArray = function (initialValues) {
throw
new
Error
(
"
The argument passed when initializing an observable array must be an array, or null, or undefined.
"
);
var
result
=
ko
.
observable
(
initialValues
);
ko
.
utils
.
e
xtend
(
result
,
ko
.
observableArray
[
'
fn
'
]);
ko
.
utils
.
setPrototypeOfOrE
xtend
(
result
,
ko
.
observableArray
[
'
fn
'
]);
return
result
.
extend
({
'
trackArrayChanges
'
:
true
});
};
...
...
@@ -1265,6 +1410,12 @@ ko.utils.arrayForEach(["slice"], function (methodName) {
};
});
// Note that for browsers that don't support proto assignment, the
// inheritance chain is created manually in the ko.observableArray constructor
if
(
ko
.
utils
.
canSetPrototype
)
{
ko
.
utils
.
setPrototypeOf
(
ko
.
observableArray
[
'
fn
'
],
ko
.
observable
[
'
fn
'
]);
}
ko
.
exportSymbol
(
'
observableArray
'
,
ko
.
observableArray
);
var
arrayChangeEventName
=
'
arrayChange
'
;
ko
.
extenders
[
'
trackArrayChanges
'
]
=
function
(
target
)
{
...
...
@@ -1327,9 +1478,9 @@ ko.extenders['trackArrayChanges'] = function(target) {
function
getChanges
(
previousContents
,
currentContents
)
{
// We try to re-use cached diffs.
// The
only scenario where pendingNotifications > 1 is when using the KO 'deferred updates' plugin,
//
which without this check would not be compatible with arrayChange notifications. Without that
//
plugin, notifications are always
issued immediately so we wouldn't be queueing up more than one.
// The
scenarios where pendingNotifications > 1 are when using rate-limiting or the Deferred Updates
//
plugin, which without this check would not be compatible with arrayChange notifications. Normally,
//
notifications are
issued immediately so we wouldn't be queueing up more than one.
if
(
!
cachedDiff
||
pendingNotifications
>
1
)
{
cachedDiff
=
ko
.
utils
.
compareArrays
(
previousContents
,
currentContents
,
{
'
sparse
'
:
true
});
}
...
...
@@ -1349,7 +1500,7 @@ ko.extenders['trackArrayChanges'] = function(target) {
offset
=
0
;
function
pushDiff
(
status
,
value
,
index
)
{
diff
.
push
({
'
status
'
:
status
,
'
value
'
:
value
,
'
index
'
:
index
})
;
return
diff
[
diff
.
length
]
=
{
'
status
'
:
status
,
'
value
'
:
value
,
'
index
'
:
index
}
;
}
switch
(
operationName
)
{
case
'
push
'
:
...
...
@@ -1374,13 +1525,15 @@ ko.extenders['trackArrayChanges'] = function(target) {
var
startIndex
=
Math
.
min
(
Math
.
max
(
0
,
args
[
0
]
<
0
?
arrayLength
+
args
[
0
]
:
args
[
0
]),
arrayLength
),
endDeleteIndex
=
argsLength
===
1
?
arrayLength
:
Math
.
min
(
startIndex
+
(
args
[
1
]
||
0
),
arrayLength
),
endAddIndex
=
startIndex
+
argsLength
-
2
,
endIndex
=
Math
.
max
(
endDeleteIndex
,
endAddIndex
);
endIndex
=
Math
.
max
(
endDeleteIndex
,
endAddIndex
),
additions
=
[],
deletions
=
[];
for
(
var
index
=
startIndex
,
argsIndex
=
2
;
index
<
endIndex
;
++
index
,
++
argsIndex
)
{
if
(
index
<
endDeleteIndex
)
pushDiff
(
'
deleted
'
,
rawArray
[
index
],
index
);
deletions
.
push
(
pushDiff
(
'
deleted
'
,
rawArray
[
index
],
index
)
);
if
(
index
<
endAddIndex
)
pushDiff
(
'
added
'
,
args
[
argsIndex
],
index
);
additions
.
push
(
pushDiff
(
'
added
'
,
args
[
argsIndex
],
index
)
);
}
ko
.
utils
.
findMovesInArrayComparison
(
deletions
,
additions
);
break
;
default
:
...
...
@@ -1389,11 +1542,12 @@ ko.extenders['trackArrayChanges'] = function(target) {
cachedDiff
=
diff
;
};
};
ko
.
dependentObservable
=
function
(
evaluatorFunctionOrOptions
,
evaluatorFunctionTarget
,
options
)
{
ko
.
computed
=
ko
.
dependentObservable
=
function
(
evaluatorFunctionOrOptions
,
evaluatorFunctionTarget
,
options
)
{
var
_latestValue
,
_
hasBeenEvaluated
=
fals
e
,
_
needsEvaluation
=
tru
e
,
_isBeingEvaluated
=
false
,
_suppressDisposalUntilDisposeWhenReturnsFalse
=
false
,
_isDisposed
=
false
,
readFunction
=
evaluatorFunctionOrOptions
;
if
(
readFunction
&&
typeof
readFunction
==
"
object
"
)
{
...
...
@@ -1409,15 +1563,21 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
if
(
typeof
readFunction
!=
"
function
"
)
throw
new
Error
(
"
Pass a function that returns the value of the ko.computed
"
);
function
addSubscriptionToDependency
(
subscribable
)
{
_subscriptionsToDependencies
.
push
(
subscribable
.
subscribe
(
evaluatePossiblyAsync
));
function
addSubscriptionToDependency
(
subscribable
,
id
)
{
if
(
!
_subscriptionsToDependencies
[
id
])
{
_subscriptionsToDependencies
[
id
]
=
subscribable
.
subscribe
(
evaluatePossiblyAsync
);
++
_dependenciesCount
;
}
}
function
disposeAllSubscriptionsToDependencies
()
{
ko
.
utils
.
arrayForEach
(
_subscriptionsToDependencies
,
function
(
subscription
)
{
_isDisposed
=
true
;
ko
.
utils
.
objectForEach
(
_subscriptionsToDependencies
,
function
(
id
,
subscription
)
{
subscription
.
dispose
();
});
_subscriptionsToDependencies
=
[];
_subscriptionsToDependencies
=
{};
_dependenciesCount
=
0
;
_needsEvaluation
=
false
;
}
function
evaluatePossiblyAsync
()
{
...
...
@@ -1425,9 +1585,12 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
if
(
throttleEvaluationTimeout
&&
throttleEvaluationTimeout
>=
0
)
{
clearTimeout
(
evaluationTimeoutInstance
);
evaluationTimeoutInstance
=
setTimeout
(
evaluateImmediate
,
throttleEvaluationTimeout
);
}
else
}
else
if
(
dependentObservable
.
_evalRateLimited
)
{
dependentObservable
.
_evalRateLimited
();
}
else
{
evaluateImmediate
();
}
}
function
evaluateImmediate
()
{
if
(
_isBeingEvaluated
)
{
...
...
@@ -1438,11 +1601,15 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
return
;
}
// Do not evaluate (and possibly capture new dependencies) if disposed
if
(
_isDisposed
)
{
return
;
}
if
(
disposeWhen
&&
disposeWhen
())
{
// See comment below about _suppressDisposalUntilDisposeWhenReturnsFalse
if
(
!
_suppressDisposalUntilDisposeWhenReturnsFalse
)
{
dispose
();
_hasBeenEvaluated
=
true
;
return
;
}
}
else
{
...
...
@@ -1454,38 +1621,63 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
try
{
// Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
// Then, during evaluation, we cross off any that are in fact still being used.
var
disposalCandidates
=
ko
.
utils
.
arrayMap
(
_subscriptionsToDependencies
,
function
(
item
)
{
return
item
.
target
;});
ko
.
dependencyDetection
.
begin
(
function
(
subscribable
)
{
var
inOld
;
if
((
inOld
=
ko
.
utils
.
arrayIndexOf
(
disposalCandidates
,
subscribable
))
>=
0
)
disposalCandidates
[
inOld
]
=
undefined
;
// Don't want to dispose this subscription, as it's still being used
else
addSubscriptionToDependency
(
subscribable
);
// Brand new subscription - add it
var
disposalCandidates
=
_subscriptionsToDependencies
,
disposalCount
=
_dependenciesCount
;
ko
.
dependencyDetection
.
begin
({
callback
:
function
(
subscribable
,
id
)
{
if
(
!
_isDisposed
)
{
if
(
disposalCount
&&
disposalCandidates
[
id
])
{
// Don't want to dispose this subscription, as it's still being used
_subscriptionsToDependencies
[
id
]
=
disposalCandidates
[
id
];
++
_dependenciesCount
;
delete
disposalCandidates
[
id
];
--
disposalCount
;
}
else
{
// Brand new subscription - add it
addSubscriptionToDependency
(
subscribable
,
id
);
}
}
},
computed
:
dependentObservable
,
isInitial
:
!
_dependenciesCount
// If we're evaluating when there are no previous dependencies, it must be the first time
});
_subscriptionsToDependencies
=
{};
_dependenciesCount
=
0
;
try
{
var
newValue
=
evaluatorFunctionTarget
?
readFunction
.
call
(
evaluatorFunctionTarget
)
:
readFunction
();
}
finally
{
ko
.
dependencyDetection
.
end
();
// For each subscription no longer being used, remove it from the active subscriptions list and dispose it
for
(
var
i
=
disposalCandidates
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
disposalCandidates
[
i
])
_subscriptionsToDependencies
.
splice
(
i
,
1
)[
0
].
dispose
();
if
(
disposalCount
)
{
ko
.
utils
.
objectForEach
(
disposalCandidates
,
function
(
id
,
toDispose
)
{
toDispose
.
dispose
();
});
}
_needsEvaluation
=
false
;
}
_hasBeenEvaluated
=
true
;
if
(
!
dependentObservable
[
'
equalityComparer
'
]
||
!
dependentObservable
[
'
equalityComparer
'
]
(
_latestValue
,
newValue
))
{
if
(
dependentObservable
.
isDifferent
(
_latestValue
,
newValue
))
{
dependentObservable
[
"
notifySubscribers
"
](
_latestValue
,
"
beforeChange
"
);
_latestValue
=
newValue
;
if
(
DEBUG
)
dependentObservable
.
_latestValue
=
_latestValue
;
// If rate-limited, the notification will happen within the limit function. Otherwise,
// notify as soon as the value changes. Check specifically for the throttle setting since
// it overrides rateLimit.
if
(
!
dependentObservable
.
_evalRateLimited
||
dependentObservable
[
'
throttleEvaluation
'
])
{
dependentObservable
[
"
notifySubscribers
"
](
_latestValue
);
}
}
}
finally
{
ko
.
dependencyDetection
.
end
();
_isBeingEvaluated
=
false
;
}
if
(
!
_
subscriptionsToDependencies
.
length
)
if
(
!
_
dependenciesCount
)
dispose
();
}
...
...
@@ -1500,7 +1692,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
return
this
;
// Permits chained assignments
}
else
{
// Reading the value
if
(
!
_hasBeenEvaluated
)
if
(
_needsEvaluation
)
evaluateImmediate
();
ko
.
dependencyDetection
.
registerDependency
(
dependentObservable
);
return
_latestValue
;
...
...
@@ -1508,13 +1700,15 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
}
function
peek
()
{
if
(
!
_hasBeenEvaluated
)
// Peek won't re-evaluate, except to get the initial value when "deferEvaluation" is set.
// That's the only time that both of these conditions will be satisfied.
if
(
_needsEvaluation
&&
!
_dependenciesCount
)
evaluateImmediate
();
return
_latestValue
;
}
function
isActive
()
{
return
!
_hasBeenEvaluated
||
_subscriptionsToDependencies
.
length
>
0
;
return
_needsEvaluation
||
_dependenciesCount
>
0
;
}
// By here, "options" is always non-null
...
...
@@ -1523,20 +1717,36 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
disposeWhenOption
=
options
[
"
disposeWhen
"
]
||
options
.
disposeWhen
,
disposeWhen
=
disposeWhenOption
,
dispose
=
disposeAllSubscriptionsToDependencies
,
_subscriptionsToDependencies
=
[],
_subscriptionsToDependencies
=
{},
_dependenciesCount
=
0
,
evaluationTimeoutInstance
=
null
;
if
(
!
evaluatorFunctionTarget
)
evaluatorFunctionTarget
=
options
[
"
owner
"
];
ko
.
subscribable
.
call
(
dependentObservable
);
ko
.
utils
.
setPrototypeOfOrExtend
(
dependentObservable
,
ko
.
dependentObservable
[
'
fn
'
]);
dependentObservable
.
peek
=
peek
;
dependentObservable
.
getDependenciesCount
=
function
()
{
return
_
subscriptionsToDependencies
.
length
;
};
dependentObservable
.
getDependenciesCount
=
function
()
{
return
_
dependenciesCount
;
};
dependentObservable
.
hasWriteFunction
=
typeof
options
[
"
write
"
]
===
"
function
"
;
dependentObservable
.
dispose
=
function
()
{
dispose
();
};
dependentObservable
.
isActive
=
isActive
;
ko
.
subscribable
.
call
(
dependentObservable
);
ko
.
utils
.
extend
(
dependentObservable
,
ko
.
dependentObservable
[
'
fn
'
]);
// Replace the limit function with one that delays evaluation as well.
var
originalLimit
=
dependentObservable
.
limit
;
dependentObservable
.
limit
=
function
(
limitFunction
)
{
originalLimit
.
call
(
dependentObservable
,
limitFunction
);
dependentObservable
.
_evalRateLimited
=
function
()
{
dependentObservable
.
_rateLimitedBeforeChange
(
_latestValue
);
_needsEvaluation
=
true
;
// Mark as dirty
// Pass the observable to the rate-limit code, which will access it when
// it's time to do the notification.
dependentObservable
.
_rateLimitedChange
(
dependentObservable
);
}
};
ko
.
exportProperty
(
dependentObservable
,
'
peek
'
,
dependentObservable
.
peek
);
ko
.
exportProperty
(
dependentObservable
,
'
dispose
'
,
dependentObservable
.
dispose
);
...
...
@@ -1568,7 +1778,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
// Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
// removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose).
if
(
disposeWhenNodeIsRemoved
&&
isActive
())
{
if
(
disposeWhenNodeIsRemoved
&&
isActive
()
&&
disposeWhenNodeIsRemoved
.
nodeType
)
{
dispose
=
function
()
{
ko
.
utils
.
domNodeDisposal
.
removeDisposeCallback
(
disposeWhenNodeIsRemoved
,
dispose
);
disposeAllSubscriptionsToDependencies
();
...
...
@@ -1591,6 +1801,12 @@ ko.dependentObservable['fn'] = {
};
ko
.
dependentObservable
[
'
fn
'
][
protoProp
]
=
ko
.
dependentObservable
;
// Note that for browsers that don't support proto assignment, the
// inheritance chain is created manually in the ko.dependentObservable constructor
if
(
ko
.
utils
.
canSetPrototype
)
{
ko
.
utils
.
setPrototypeOf
(
ko
.
dependentObservable
[
'
fn
'
],
ko
.
subscribable
[
'
fn
'
]);
}
ko
.
exportSymbol
(
'
dependentObservable
'
,
ko
.
dependentObservable
);
ko
.
exportSymbol
(
'
computed
'
,
ko
.
dependentObservable
);
// Make "ko.computed" an alias for "ko.dependentObservable"
ko
.
exportSymbol
(
'
isComputed
'
,
ko
.
isComputed
);
...
...
@@ -1712,7 +1928,7 @@ ko.exportSymbol('toJSON', ko.toJSON);
}
},
writeValue
:
function
(
element
,
value
)
{
writeValue
:
function
(
element
,
value
,
allowUnset
)
{
switch
(
ko
.
utils
.
tagNameLower
(
element
))
{
case
'
option
'
:
switch
(
typeof
value
)
{
...
...
@@ -1734,19 +1950,19 @@ ko.exportSymbol('toJSON', ko.toJSON);
}
break
;
case
'
select
'
:
if
(
value
===
""
)
if
(
value
===
""
||
value
===
null
)
// A blank string or null value will select the caption
value
=
undefined
;
if
(
value
===
null
||
value
===
undefined
)
element
.
selectedIndex
=
-
1
;
for
(
var
i
=
element
.
options
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
ko
.
selectExtensions
.
readValue
(
element
.
options
[
i
])
==
value
)
{
element
.
selectedIndex
=
i
;
var
selection
=
-
1
;
for
(
var
i
=
0
,
n
=
element
.
options
.
length
,
optionValue
;
i
<
n
;
++
i
)
{
optionValue
=
ko
.
selectExtensions
.
readValue
(
element
.
options
[
i
]);
// Include special check to handle selecting a caption with a blank string value
if
(
optionValue
==
value
||
(
optionValue
==
""
&&
value
===
undefined
))
{
selection
=
i
;
break
;
}
}
// for drop-down select, ensure first is selected
if
(
!
(
element
.
size
>
1
)
&&
element
.
selectedIndex
===
-
1
)
{
element
.
selectedIndex
=
0
;
if
(
allowUnset
||
selection
>=
0
||
(
value
===
undefined
&&
element
.
size
>
1
))
{
element
.
selectedIndex
=
selection
;
}
break
;
default
:
...
...
@@ -1902,7 +2118,7 @@ ko.expressionRewriting = (function () {
});
if
(
propertyAccessorResultStrings
.
length
)
processKeyValue
(
'
_ko_property_writers
'
,
"
{
"
+
propertyAccessorResultStrings
.
join
(
"
,
"
)
+
"
}
"
);
processKeyValue
(
'
_ko_property_writers
'
,
"
{
"
+
propertyAccessorResultStrings
.
join
(
"
,
"
)
+
"
}
"
);
return
resultStrings
.
join
(
"
,
"
);
}
...
...
@@ -2157,6 +2373,336 @@ ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
ko
.
exportSymbol
(
'
virtualElements.prepend
'
,
ko
.
virtualElements
.
prepend
);
ko
.
exportSymbol
(
'
virtualElements.setDomNodeChildren
'
,
ko
.
virtualElements
.
setDomNodeChildren
);
(
function
(
undefined
)
{
var
loadingSubscribablesCache
=
{},
// Tracks component loads that are currently in flight
loadedDefinitionsCache
=
{};
// Tracks component loads that have already completed
ko
.
components
=
{
get
:
function
(
componentName
,
callback
)
{
var
cachedDefinition
=
getObjectOwnProperty
(
loadedDefinitionsCache
,
componentName
);
if
(
cachedDefinition
)
{
// It's already loaded and cached. Reuse the same definition object.
// Note that for API consistency, even cache hits complete asynchronously.
setTimeout
(
function
()
{
callback
(
cachedDefinition
)
},
0
);
}
else
{
// Join the loading process that is already underway, or start a new one.
loadComponentAndNotify
(
componentName
,
callback
);
}
},
clearCachedDefinition
:
function
(
componentName
)
{
delete
loadedDefinitionsCache
[
componentName
];
},
_getFirstResultFromLoaders
:
getFirstResultFromLoaders
};
function
getObjectOwnProperty
(
obj
,
propName
)
{
return
obj
.
hasOwnProperty
(
propName
)
?
obj
[
propName
]
:
undefined
;
}
function
loadComponentAndNotify
(
componentName
,
callback
)
{
var
subscribable
=
getObjectOwnProperty
(
loadingSubscribablesCache
,
componentName
),
completedAsync
;
if
(
!
subscribable
)
{
// It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
subscribable
=
loadingSubscribablesCache
[
componentName
]
=
new
ko
.
subscribable
();
beginLoadingComponent
(
componentName
,
function
(
definition
)
{
loadedDefinitionsCache
[
componentName
]
=
definition
;
delete
loadingSubscribablesCache
[
componentName
];
// For API consistency, all loads complete asynchronously. However we want to avoid
// adding an extra setTimeout if it's unnecessary (i.e., the completion is already
// async) since setTimeout(..., 0) still takes about 16ms or more on most browsers.
if
(
completedAsync
)
{
subscribable
[
'
notifySubscribers
'
](
definition
);
}
else
{
setTimeout
(
function
()
{
subscribable
[
'
notifySubscribers
'
](
definition
);
},
0
);
}
});
completedAsync
=
true
;
}
subscribable
.
subscribe
(
callback
);
}
function
beginLoadingComponent
(
componentName
,
callback
)
{
getFirstResultFromLoaders
(
'
getConfig
'
,
[
componentName
],
function
(
config
)
{
if
(
config
)
{
// We have a config, so now load its definition
getFirstResultFromLoaders
(
'
loadComponent
'
,
[
componentName
,
config
],
function
(
definition
)
{
callback
(
definition
);
});
}
else
{
// The component has no config - it's unknown to all the loaders.
// Note that this is not an error (e.g., a module loading error) - that would abort the
// process and this callback would not run. For this callback to run, all loaders must
// have confirmed they don't know about this component.
callback
(
null
);
}
});
}
function
getFirstResultFromLoaders
(
methodName
,
argsExceptCallback
,
callback
,
candidateLoaders
)
{
// On the first call in the stack, start with the full set of loaders
if
(
!
candidateLoaders
)
{
candidateLoaders
=
ko
.
components
[
'
loaders
'
].
slice
(
0
);
// Use a copy, because we'll be mutating this array
}
// Try the next candidate
var
currentCandidateLoader
=
candidateLoaders
.
shift
();
if
(
currentCandidateLoader
)
{
var
methodInstance
=
currentCandidateLoader
[
methodName
];
if
(
methodInstance
)
{
var
wasAborted
=
false
,
synchronousReturnValue
=
methodInstance
.
apply
(
currentCandidateLoader
,
argsExceptCallback
.
concat
(
function
(
result
)
{
if
(
wasAborted
)
{
callback
(
null
);
}
else
if
(
result
!==
null
)
{
// This candidate returned a value. Use it.
callback
(
result
);
}
else
{
// Try the next candidate
getFirstResultFromLoaders
(
methodName
,
argsExceptCallback
,
callback
,
candidateLoaders
);
}
}));
// Currently, loaders may not return anything synchronously. This leaves open the possibility
// that we'll extend the API to support synchronous return values in the future. It won't be
// a breaking change, because currently no loader is allowed to return anything except undefined.
if
(
synchronousReturnValue
!==
undefined
)
{
wasAborted
=
true
;
// Method to suppress exceptions will remain undocumented. This is only to keep
// KO's specs running tidily, since we can observe the loading got aborted without
// having exceptions cluttering up the console too.
if
(
!
currentCandidateLoader
[
'
suppressLoaderExceptions
'
])
{
throw
new
Error
(
'
Component loaders must supply values by invoking the callback, not by returning values synchronously.
'
);
}
}
}
else
{
// This candidate doesn't have the relevant handler. Synchronously move on to the next one.
getFirstResultFromLoaders
(
methodName
,
argsExceptCallback
,
callback
,
candidateLoaders
);
}
}
else
{
// No candidates returned a value
callback
(
null
);
}
}
// Reference the loaders via string name so it's possible for developers
// to replace the whole array by assigning to ko.components.loaders
ko
.
components
[
'
loaders
'
]
=
[];
ko
.
exportSymbol
(
'
components
'
,
ko
.
components
);
ko
.
exportSymbol
(
'
components.get
'
,
ko
.
components
.
get
);
ko
.
exportSymbol
(
'
components.clearCachedDefinition
'
,
ko
.
components
.
clearCachedDefinition
);
})();
(
function
(
undefined
)
{
// The default loader is responsible for two things:
// 1. Maintaining the default in-memory registry of component configuration objects
// (i.e., the thing you're writing to when you call ko.components.register(someName, ...))
// 2. Answering requests for components by fetching configuration objects
// from that default in-memory registry and resolving them into standard
// component definition objects (of the form { createViewModel: ..., template: ... })
// Custom loaders may override either of these facilities, i.e.,
// 1. To supply configuration objects from some other source (e.g., conventions)
// 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic.
var
defaultConfigRegistry
=
{};
ko
.
components
.
register
=
function
(
componentName
,
config
)
{
if
(
!
config
)
{
throw
new
Error
(
'
Invalid configuration for
'
+
componentName
);
}
if
(
ko
.
components
.
isRegistered
(
componentName
))
{
throw
new
Error
(
'
Component
'
+
componentName
+
'
is already registered
'
);
}
defaultConfigRegistry
[
componentName
]
=
config
;
}
ko
.
components
.
isRegistered
=
function
(
componentName
)
{
return
componentName
in
defaultConfigRegistry
;
}
ko
.
components
.
unregister
=
function
(
componentName
)
{
delete
defaultConfigRegistry
[
componentName
];
ko
.
components
.
clearCachedDefinition
(
componentName
);
}
ko
.
components
.
defaultLoader
=
{
'
getConfig
'
:
function
(
componentName
,
callback
)
{
var
result
=
defaultConfigRegistry
.
hasOwnProperty
(
componentName
)
?
defaultConfigRegistry
[
componentName
]
:
null
;
callback
(
result
);
},
'
loadComponent
'
:
function
(
componentName
,
config
,
callback
)
{
var
errorCallback
=
makeErrorCallback
(
componentName
);
possiblyGetConfigFromAmd
(
errorCallback
,
config
,
function
(
loadedConfig
)
{
resolveConfig
(
componentName
,
errorCallback
,
loadedConfig
,
callback
);
});
},
'
loadTemplate
'
:
function
(
componentName
,
templateConfig
,
callback
)
{
resolveTemplate
(
makeErrorCallback
(
componentName
),
templateConfig
,
callback
);
},
'
loadViewModel
'
:
function
(
componentName
,
viewModelConfig
,
callback
)
{
resolveViewModel
(
makeErrorCallback
(
componentName
),
viewModelConfig
,
callback
);
}
};
var
createViewModelKey
=
'
createViewModel
'
;
// Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it
// into the standard component definition format:
// { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }.
// Since both template and viewModel may need to be resolved asynchronously, both tasks are performed
// in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure,
// so this is implemented manually below.
function
resolveConfig
(
componentName
,
errorCallback
,
config
,
callback
)
{
var
result
=
{},
makeCallBackWhenZero
=
2
,
tryIssueCallback
=
function
()
{
if
(
--
makeCallBackWhenZero
===
0
)
{
callback
(
result
);
}
},
templateConfig
=
config
[
'
template
'
],
viewModelConfig
=
config
[
'
viewModel
'
];
if
(
templateConfig
)
{
possiblyGetConfigFromAmd
(
errorCallback
,
templateConfig
,
function
(
loadedConfig
)
{
ko
.
components
.
_getFirstResultFromLoaders
(
'
loadTemplate
'
,
[
componentName
,
loadedConfig
],
function
(
resolvedTemplate
)
{
result
[
'
template
'
]
=
resolvedTemplate
;
tryIssueCallback
();
});
});
}
else
{
tryIssueCallback
();
}
if
(
viewModelConfig
)
{
possiblyGetConfigFromAmd
(
errorCallback
,
viewModelConfig
,
function
(
loadedConfig
)
{
ko
.
components
.
_getFirstResultFromLoaders
(
'
loadViewModel
'
,
[
componentName
,
loadedConfig
],
function
(
resolvedViewModel
)
{
result
[
createViewModelKey
]
=
resolvedViewModel
;
tryIssueCallback
();
});
});
}
else
{
tryIssueCallback
();
}
}
function
resolveTemplate
(
errorCallback
,
templateConfig
,
callback
)
{
if
(
typeof
templateConfig
===
'
string
'
)
{
// Markup - parse it
callback
(
ko
.
utils
.
parseHtmlFragment
(
templateConfig
));
}
else
if
(
templateConfig
instanceof
Array
)
{
// Assume already an array of DOM nodes - pass through unchanged
callback
(
templateConfig
);
}
else
if
(
isDocumentFragment
(
templateConfig
))
{
// Document fragment - use its child nodes
callback
(
ko
.
utils
.
makeArray
(
templateConfig
.
childNodes
));
}
else
if
(
templateConfig
[
'
element
'
])
{
var
element
=
templateConfig
[
'
element
'
];
if
(
isDomElement
(
element
))
{
// Element instance - copy its child nodes
callback
(
ko
.
utils
.
cloneNodes
(
element
.
childNodes
));
}
else
if
(
typeof
element
===
'
string
'
)
{
// Element ID - find it, then copy its child nodes
var
elemInstance
=
document
.
getElementById
(
element
);
if
(
elemInstance
)
{
callback
(
ko
.
utils
.
cloneNodes
(
elemInstance
.
childNodes
));
}
else
{
errorCallback
(
'
Cannot find element with ID
'
+
element
);
}
}
else
{
errorCallback
(
'
Unknown element type:
'
+
element
);
}
}
else
{
errorCallback
(
'
Unknown template value:
'
+
templateConfig
);
}
}
function
resolveViewModel
(
errorCallback
,
viewModelConfig
,
callback
)
{
if
(
typeof
viewModelConfig
===
'
function
'
)
{
// Constructor - convert to standard factory function format
// By design, this does *not* supply componentInfo to the constructor, as the intent is that
// componentInfo contains non-viewmodel data (e.g., the component's element) that should only
// be used in factory functions, not viewmodel constructors.
callback
(
function
(
params
/*, componentInfo */
)
{
return
new
viewModelConfig
(
params
);
});
}
else
if
(
typeof
viewModelConfig
[
createViewModelKey
]
===
'
function
'
)
{
// Already a factory function - use it as-is
callback
(
viewModelConfig
[
createViewModelKey
]);
}
else
if
(
'
instance
'
in
viewModelConfig
)
{
// Fixed object instance - promote to createViewModel format for API consistency
var
fixedInstance
=
viewModelConfig
[
'
instance
'
];
callback
(
function
(
params
,
componentInfo
)
{
return
fixedInstance
;
});
}
else
if
(
'
viewModel
'
in
viewModelConfig
)
{
// Resolved AMD module whose value is of the form { viewModel: ... }
resolveViewModel
(
errorCallback
,
viewModelConfig
[
'
viewModel
'
],
callback
);
}
else
{
errorCallback
(
'
Unknown viewModel value:
'
+
viewModelConfig
);
}
}
function
isDomElement
(
obj
)
{
if
(
window
[
'
HTMLElement
'
])
{
return
obj
instanceof
HTMLElement
;
}
else
{
return
obj
&&
obj
.
tagName
&&
obj
.
nodeType
===
1
;
}
}
function
isDocumentFragment
(
obj
)
{
if
(
window
[
'
DocumentFragment
'
])
{
return
obj
instanceof
DocumentFragment
;
}
else
{
return
obj
&&
obj
.
nodeType
===
11
;
}
}
function
possiblyGetConfigFromAmd
(
errorCallback
,
config
,
callback
)
{
if
(
typeof
config
[
'
require
'
]
===
'
string
'
)
{
// The config is the value of an AMD module
if
(
require
||
window
[
'
require
'
])
{
(
require
||
window
[
'
require
'
])([
config
[
'
require
'
]],
callback
);
}
else
{
errorCallback
(
'
Uses require, but no AMD loader is present
'
);
}
}
else
{
callback
(
config
);
}
}
function
makeErrorCallback
(
componentName
)
{
return
function
(
message
)
{
throw
new
Error
(
'
Component
\'
'
+
componentName
+
'
\'
:
'
+
message
);
};
}
ko
.
exportSymbol
(
'
components.register
'
,
ko
.
components
.
register
);
ko
.
exportSymbol
(
'
components.isRegistered
'
,
ko
.
components
.
isRegistered
);
ko
.
exportSymbol
(
'
components.unregister
'
,
ko
.
components
.
unregister
);
// Expose the default loader so that developers can directly ask it for configuration
// or to resolve configuration
ko
.
exportSymbol
(
'
components.defaultLoader
'
,
ko
.
components
.
defaultLoader
);
// By default, the default loader is the only registered component loader
ko
.
components
[
'
loaders
'
].
push
(
ko
.
components
.
defaultLoader
);
})();
(
function
()
{
var
defaultBindingAttributeName
=
"
data-bind
"
;
...
...
@@ -2251,10 +2797,11 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
// any child contexts, must be updated when the view model is changed.
function
updateContext
()
{
// Most of the time, the context will directly get a view model object, but if a function is given,
// we call the function to retrieve the view model. If the function accesses any obsevables
(or i
s
//
itself an observable)
, the dependency is tracked, and those observables can later cause the binding
// we call the function to retrieve the view model. If the function accesses any obsevables
or return
s
//
an observable
, the dependency is tracked, and those observables can later cause the binding
// context to be updated.
var
dataItem
=
isFunc
?
dataItemOrAccessor
()
:
dataItemOrAccessor
;
var
dataItemOrObservable
=
isFunc
?
dataItemOrAccessor
()
:
dataItemOrAccessor
,
dataItem
=
ko
.
utils
.
unwrapObservable
(
dataItemOrObservable
);
if
(
parentContext
)
{
// When a "parent" context is given, register a dependency on the parent context. Thus whenever the
...
...
@@ -2279,7 +2826,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
// See https://github.com/SteveSanderson/knockout/issues/490
self
[
'
ko
'
]
=
ko
;
}
self
[
'
$rawData
'
]
=
dataItemOr
Accessor
;
self
[
'
$rawData
'
]
=
dataItemOr
Observable
;
self
[
'
$data
'
]
=
dataItem
;
if
(
dataItemAlias
)
self
[
dataItemAlias
]
=
dataItem
;
...
...
@@ -2297,7 +2844,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
}
var
self
=
this
,
isFunc
=
typeof
(
dataItemOrAccessor
)
==
"
function
"
,
isFunc
=
typeof
(
dataItemOrAccessor
)
==
"
function
"
&&
!
ko
.
isObservable
(
dataItemOrAccessor
)
,
nodes
,
subscribable
=
ko
.
dependentObservable
(
updateContext
,
null
,
{
disposeWhen
:
disposeWhen
,
disposeWhenNodeIsRemoved
:
true
});
...
...
@@ -2352,7 +2899,12 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
// Similarly to "child" contexts, provide a function here to make sure that the correct values are set
// when an observable view model is updated.
ko
.
bindingContext
.
prototype
[
'
extend
'
]
=
function
(
properties
)
{
return
new
ko
.
bindingContext
(
this
[
'
$rawData
'
],
this
,
null
,
function
(
self
)
{
// If the parent context references an observable view model, "_subscribable" will always be the
// latest view model object. If not, "_subscribable" isn't set, and we can use the static "$data" value.
return
new
ko
.
bindingContext
(
this
.
_subscribable
||
this
[
'
$data
'
],
this
,
null
,
function
(
self
,
parentContext
)
{
// This "child" context doesn't directly track a parent observable view model,
// so we need to manually set the $rawData value to match the parent.
self
[
'
$rawData
'
]
=
parentContext
[
'
$rawData
'
];
ko
.
utils
.
extend
(
self
,
typeof
(
properties
)
==
"
function
"
?
properties
()
:
properties
);
});
};
...
...
@@ -2480,7 +3032,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
}
}
});
cyclicDependencyStack
.
pop
()
;
cyclicDependencyStack
.
length
--
;
}
// Next add the current binding
result
.
push
({
key
:
bindingKey
,
handler
:
binding
});
...
...
@@ -2516,14 +3068,12 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
var
provider
=
ko
.
bindingProvider
[
'
instance
'
],
getBindings
=
provider
[
'
getBindingAccessors
'
]
||
getBindingsAndMakeAccessors
;
if
(
sourceBindings
||
bindingContext
.
_subscribable
)
{
// When an obsevable view model is used, the binding context will expose an observable _subscribable value.
// Get the binding from the provider within a computed observable so that we can update the bindings whenever
// the binding context is updated
.
// the binding context is updated or if the binding provider accesses observables
.
var
bindingsUpdater
=
ko
.
dependentObservable
(
function
()
{
bindings
=
sourceBindings
?
sourceBindings
(
bindingContext
,
node
)
:
getBindings
.
call
(
provider
,
node
,
bindingContext
);
// Register a dependency on the binding context
// Register a dependency on the binding context to support obsevable view models.
if
(
bindings
&&
bindingContext
.
_subscribable
)
bindingContext
.
_subscribable
();
return
bindings
;
...
...
@@ -2533,9 +3083,6 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
if
(
!
bindings
||
!
bindingsUpdater
.
isActive
())
bindingsUpdater
=
null
;
}
else
{
bindings
=
ko
.
dependencyDetection
.
ignore
(
getBindings
,
provider
,
[
node
,
bindingContext
]);
}
}
var
bindingHandlerThatControlsDescendantBindings
;
...
...
@@ -2650,6 +3197,11 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
};
ko
.
applyBindings
=
function
(
viewModelOrBindingContext
,
rootNode
)
{
// If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
if
(
!
jQuery
&&
window
[
'
jQuery
'
])
{
jQuery
=
window
[
'
jQuery
'
];
}
if
(
rootNode
&&
(
rootNode
.
nodeType
!==
1
)
&&
(
rootNode
.
nodeType
!==
8
))
throw
new
Error
(
"
ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node
"
);
rootNode
=
rootNode
||
window
.
document
.
body
;
// Make "rootNode" parameter optional
...
...
@@ -2683,6 +3235,87 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
ko
.
exportSymbol
(
'
contextFor
'
,
ko
.
contextFor
);
ko
.
exportSymbol
(
'
dataFor
'
,
ko
.
dataFor
);
})();
(
function
(
undefined
)
{
var
componentLoadingOperationUniqueId
=
0
;
ko
.
bindingHandlers
[
'
component
'
]
=
{
'
init
'
:
function
(
element
,
valueAccessor
,
ignored1
,
ignored2
,
bindingContext
)
{
var
currentViewModel
,
currentLoadingOperationId
,
disposeAssociatedComponentViewModel
=
function
()
{
var
currentViewModelDispose
=
currentViewModel
&&
currentViewModel
[
'
dispose
'
];
if
(
typeof
currentViewModelDispose
===
'
function
'
)
{
currentViewModelDispose
.
call
(
currentViewModel
);
}
// Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
currentLoadingOperationId
=
null
;
};
ko
.
utils
.
domNodeDisposal
.
addDisposeCallback
(
element
,
disposeAssociatedComponentViewModel
);
ko
.
computed
(
function
()
{
var
value
=
ko
.
utils
.
unwrapObservable
(
valueAccessor
()),
componentName
,
componentParams
;
if
(
typeof
value
===
'
string
'
)
{
componentName
=
value
;
}
else
{
componentName
=
ko
.
utils
.
unwrapObservable
(
value
[
'
name
'
]);
componentParams
=
ko
.
utils
.
unwrapObservable
(
value
[
'
params
'
]);
}
if
(
!
componentName
)
{
throw
new
Error
(
'
No component name specified
'
);
}
var
loadingOperationId
=
currentLoadingOperationId
=
++
componentLoadingOperationUniqueId
;
ko
.
components
.
get
(
componentName
,
function
(
componentDefinition
)
{
// If this is not the current load operation for this element, ignore it.
if
(
currentLoadingOperationId
!==
loadingOperationId
)
{
return
;
}
// Clean up previous state
disposeAssociatedComponentViewModel
();
// Instantiate and bind new component. Implicitly this cleans any old DOM nodes.
if
(
!
componentDefinition
)
{
throw
new
Error
(
'
Unknown component
\'
'
+
componentName
+
'
\'
'
);
}
cloneTemplateIntoElement
(
componentName
,
componentDefinition
,
element
);
var
componentViewModel
=
createViewModel
(
componentDefinition
,
element
,
componentParams
),
childBindingContext
=
bindingContext
[
'
createChildContext
'
](
componentViewModel
);
currentViewModel
=
componentViewModel
;
ko
.
applyBindingsToDescendants
(
childBindingContext
,
element
);
});
},
null
,
{
disposeWhenNodeIsRemoved
:
element
});
return
{
'
controlsDescendantBindings
'
:
true
};
}
};
ko
.
virtualElements
.
allowedBindings
[
'
component
'
]
=
true
;
function
cloneTemplateIntoElement
(
componentName
,
componentDefinition
,
element
)
{
var
template
=
componentDefinition
[
'
template
'
];
if
(
!
template
)
{
throw
new
Error
(
'
Component
\'
'
+
componentName
+
'
\'
has no template
'
);
}
var
clonedNodesArray
=
ko
.
utils
.
cloneNodes
(
template
);
ko
.
virtualElements
.
setDomNodeChildren
(
element
,
clonedNodesArray
);
}
function
createViewModel
(
componentDefinition
,
element
,
componentParams
)
{
var
componentViewModelFactory
=
componentDefinition
[
'
createViewModel
'
];
return
componentViewModelFactory
?
componentViewModelFactory
.
call
(
componentDefinition
,
componentParams
,
{
element
:
element
})
:
componentParams
;
// Template-only component
}
})();
var
attrHtmlToJavascriptMap
=
{
'
class
'
:
'
className
'
,
'
for
'
:
'
htmlFor
'
};
ko
.
bindingHandlers
[
'
attr
'
]
=
{
'
update
'
:
function
(
element
,
valueAccessor
,
allBindings
)
{
...
...
@@ -2739,7 +3372,7 @@ ko.bindingHandlers['checked'] = {
elemValue
=
useCheckedValue
?
checkedValue
()
:
isChecked
;
// When we're first setting up this computed, don't change any model state.
if
(
!
shouldSet
)
{
if
(
ko
.
computedContext
.
isInitial
()
)
{
return
;
}
...
...
@@ -2798,8 +3431,7 @@ ko.bindingHandlers['checked'] = {
var
isValueArray
=
isCheckbox
&&
(
ko
.
utils
.
unwrapObservable
(
valueAccessor
())
instanceof
Array
),
oldElemValue
=
isValueArray
?
checkedValue
()
:
undefined
,
useCheckedValue
=
isRadio
||
isValueArray
,
shouldSet
=
false
;
useCheckedValue
=
isRadio
||
isValueArray
;
// IE 6 won't allow radio buttons to be selected unless they have a name
if
(
isRadio
&&
!
element
.
name
)
...
...
@@ -2808,13 +3440,11 @@ ko.bindingHandlers['checked'] = {
// Set up two computeds to update the binding:
// The first responds to changes in the checkedValue value and to element clicks
ko
.
dependentObservable
(
updateModel
,
null
,
{
disposeWhenNodeIsRemoved
:
element
});
ko
.
computed
(
updateModel
,
null
,
{
disposeWhenNodeIsRemoved
:
element
});
ko
.
utils
.
registerEventHandler
(
element
,
"
click
"
,
updateModel
);
// The second responds to changes in the model value (the one associated with the checked binding)
ko
.
dependentObservable
(
updateView
,
null
,
{
disposeWhenNodeIsRemoved
:
element
});
shouldSet
=
true
;
ko
.
computed
(
updateView
,
null
,
{
disposeWhenNodeIsRemoved
:
element
});
}
};
ko
.
expressionRewriting
.
twoWayBindings
[
'
checked
'
]
=
true
;
...
...
@@ -3007,37 +3637,37 @@ ko.bindingHandlers['html'] = {
ko
.
utils
.
setHtml
(
element
,
valueAccessor
());
}
};
var
withIfDomDataKey
=
ko
.
utils
.
domData
.
nextKey
();
// Makes a binding like with or if
function
makeWithIfBinding
(
bindingKey
,
isWith
,
isNot
,
makeContextCallback
)
{
ko
.
bindingHandlers
[
bindingKey
]
=
{
'
init
'
:
function
(
element
)
{
ko
.
utils
.
domData
.
set
(
element
,
withIfDomDataKey
,
{});
return
{
'
controlsDescendantBindings
'
:
true
};
},
'
update
'
:
function
(
element
,
valueAccessor
,
allBindings
,
viewModel
,
bindingContext
)
{
var
withIfData
=
ko
.
utils
.
domData
.
get
(
element
,
withIfDomDataKey
),
dataValue
=
ko
.
utils
.
unwrapObservable
(
valueAccessor
()),
'
init
'
:
function
(
element
,
valueAccessor
,
allBindings
,
viewModel
,
bindingContext
)
{
var
didDisplayOnLastUpdate
,
savedNodes
;
ko
.
computed
(
function
()
{
var
dataValue
=
ko
.
utils
.
unwrapObservable
(
valueAccessor
()),
shouldDisplay
=
!
isNot
!==
!
dataValue
,
// equivalent to isNot ? !dataValue : !!dataValue
isFirstRender
=
!
withIfData
.
savedNodes
,
needsRefresh
=
isFirstRender
||
isWith
||
(
shouldDisplay
!==
withIfData
.
didDisplayOnLastUpdate
);
isFirstRender
=
!
savedNodes
,
needsRefresh
=
isFirstRender
||
isWith
||
(
shouldDisplay
!==
didDisplayOnLastUpdate
);
if
(
needsRefresh
)
{
if
(
isFirstRender
)
{
withIfData
.
savedNodes
=
ko
.
utils
.
cloneNodes
(
ko
.
virtualElements
.
childNodes
(
element
),
true
/* shouldCleanNodes */
);
// Save a copy of the inner nodes on the initial update, but only if we have dependencies.
if
(
isFirstRender
&&
ko
.
computedContext
.
getDependenciesCount
())
{
savedNodes
=
ko
.
utils
.
cloneNodes
(
ko
.
virtualElements
.
childNodes
(
element
),
true
/* shouldCleanNodes */
);
}
if
(
shouldDisplay
)
{
if
(
!
isFirstRender
)
{
ko
.
virtualElements
.
setDomNodeChildren
(
element
,
ko
.
utils
.
cloneNodes
(
withIfData
.
savedNodes
));
ko
.
virtualElements
.
setDomNodeChildren
(
element
,
ko
.
utils
.
cloneNodes
(
savedNodes
));
}
ko
.
applyBindingsToDescendants
(
makeContextCallback
?
makeContextCallback
(
bindingContext
,
dataValue
)
:
bindingContext
,
element
);
}
else
{
ko
.
virtualElements
.
emptyNode
(
element
);
}
withIfData
.
didDisplayOnLastUpdate
=
shouldDisplay
;
didDisplayOnLastUpdate
=
shouldDisplay
;
}
},
null
,
{
disposeWhenNodeIsRemoved
:
element
});
return
{
'
controlsDescendantBindings
'
:
true
};
}
};
ko
.
expressionRewriting
.
bindingRewriteValidators
[
bindingKey
]
=
false
;
// Can't rewrite control flow bindings
...
...
@@ -3052,6 +3682,7 @@ makeWithIfBinding('with', true /* isWith */, false /* isNot */,
return
bindingContext
[
'
createChildContext
'
](
dataValue
);
}
);
var
captionPlaceholder
=
{};
ko
.
bindingHandlers
[
'
options
'
]
=
{
'
init
'
:
function
(
element
)
{
if
(
ko
.
utils
.
tagNameLower
(
element
)
!==
"
select
"
)
...
...
@@ -3072,12 +3703,13 @@ ko.bindingHandlers['options'] = {
var
selectWasPreviouslyEmpty
=
element
.
length
==
0
;
var
previousScrollTop
=
(
!
selectWasPreviouslyEmpty
&&
element
.
multiple
)
?
element
.
scrollTop
:
null
;
var
unwrappedArray
=
ko
.
utils
.
unwrapObservable
(
valueAccessor
());
var
includeDestroyed
=
allBindings
.
get
(
'
optionsIncludeDestroyed
'
);
var
captionPlaceholder
=
{};
var
arrayToDomNodeChildrenOptions
=
{};
var
captionValue
;
var
filteredArray
;
var
previousSelectedValues
;
if
(
element
.
multiple
)
{
previousSelectedValues
=
ko
.
utils
.
arrayMap
(
selectedOptions
(),
ko
.
selectExtensions
.
readValue
);
}
else
{
...
...
@@ -3089,7 +3721,7 @@ ko.bindingHandlers['options'] = {
unwrappedArray
=
[
unwrappedArray
];
// Filter out any entries marked as destroyed
var
filteredArray
=
ko
.
utils
.
arrayFilter
(
unwrappedArray
,
function
(
item
)
{
filteredArray
=
ko
.
utils
.
arrayFilter
(
unwrappedArray
,
function
(
item
)
{
return
includeDestroyed
||
item
===
undefined
||
item
===
null
||
!
ko
.
utils
.
unwrapObservable
(
item
[
'
_destroy
'
]);
});
...
...
@@ -3103,7 +3735,6 @@ ko.bindingHandlers['options'] = {
}
}
else
{
// If a falsy value is provided (e.g. null), we'll simply empty the select element
unwrappedArray
=
[];
}
function
applyToObject
(
object
,
predicate
,
defaultValue
)
{
...
...
@@ -3126,7 +3757,7 @@ ko.bindingHandlers['options'] = {
previousSelectedValues
=
oldOptions
[
0
].
selected
?
[
ko
.
selectExtensions
.
readValue
(
oldOptions
[
0
])
]
:
[];
itemUpdate
=
true
;
}
var
option
=
d
ocument
.
createElement
(
"
option
"
);
var
option
=
element
.
ownerD
ocument
.
createElement
(
"
option
"
);
if
(
arrayEntry
===
captionPlaceholder
)
{
ko
.
utils
.
setTextContent
(
option
,
allBindings
.
get
(
'
optionsCaption
'
));
ko
.
selectExtensions
.
writeValue
(
option
,
undefined
);
...
...
@@ -3142,6 +3773,13 @@ ko.bindingHandlers['options'] = {
return
[
option
];
}
// By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
// problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
arrayToDomNodeChildrenOptions
[
'
beforeRemove
'
]
=
function
(
option
)
{
element
.
removeChild
(
option
);
};
function
setSelectionCallback
(
arrayEntry
,
newOptions
)
{
// IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
// That's why we first added them without selection. Now it's time to set the selection.
...
...
@@ -3163,8 +3801,13 @@ ko.bindingHandlers['options'] = {
}
}
ko
.
utils
.
setDomNodeChildrenFromArrayMapping
(
element
,
filteredArray
,
optionForArrayItem
,
null
,
callback
);
ko
.
utils
.
setDomNodeChildrenFromArrayMapping
(
element
,
filteredArray
,
optionForArrayItem
,
arrayToDomNodeChildrenOptions
,
callback
);
ko
.
dependencyDetection
.
ignore
(
function
()
{
if
(
allBindings
.
get
(
'
valueAllowUnset
'
)
&&
allBindings
[
'
has
'
](
'
value
'
))
{
// The model value is authoritative, so make sure its value is the one selected
ko
.
selectExtensions
.
writeValue
(
element
,
ko
.
utils
.
unwrapObservable
(
allBindings
.
get
(
'
value
'
)),
true
/* allowUnset */
);
}
else
{
// Determine if the selection has changed as a result of updating the options list
var
selectionChanged
;
if
(
element
.
multiple
)
{
...
...
@@ -3182,8 +3825,11 @@ ko.bindingHandlers['options'] = {
// Ensure consistency between model value and selected option.
// If the dropdown was changed so that selection is no longer the same,
// notify the value or selectedOptions binding.
if
(
selectionChanged
)
ko
.
dependencyDetection
.
ignore
(
ko
.
utils
.
triggerEvent
,
null
,
[
element
,
"
change
"
]);
if
(
selectionChanged
)
{
ko
.
utils
.
triggerEvent
(
element
,
"
change
"
);
}
}
});
// Workaround for IE bug
ko
.
utils
.
ensureSelectElementIsRenderedCorrectly
(
element
);
...
...
@@ -3294,6 +3940,7 @@ ko.bindingHandlers['value'] = {
&&
element
.
autocomplete
!=
"
off
"
&&
(
!
element
.
form
||
element
.
form
.
autocomplete
!=
"
off
"
);
if
(
ieAutoCompleteHackNeeded
&&
ko
.
utils
.
arrayIndexOf
(
eventsToCatch
,
"
propertychange
"
)
==
-
1
)
{
ko
.
utils
.
registerEventHandler
(
element
,
"
propertychange
"
,
function
()
{
propertyChangedFired
=
true
});
ko
.
utils
.
registerEventHandler
(
element
,
"
focus
"
,
function
()
{
propertyChangedFired
=
false
});
ko
.
utils
.
registerEventHandler
(
element
,
"
blur
"
,
function
()
{
if
(
propertyChangedFired
)
{
valueUpdateHandler
();
...
...
@@ -3313,18 +3960,20 @@ ko.bindingHandlers['value'] = {
ko
.
utils
.
registerEventHandler
(
element
,
eventName
,
handler
);
});
},
'
update
'
:
function
(
element
,
valueAccessor
)
{
var
valueIsSelectOption
=
ko
.
utils
.
tagNameLower
(
element
)
===
"
select
"
;
'
update
'
:
function
(
element
,
valueAccessor
,
allBindings
)
{
var
newValue
=
ko
.
utils
.
unwrapObservable
(
valueAccessor
());
var
elementValue
=
ko
.
selectExtensions
.
readValue
(
element
);
var
valueHasChanged
=
(
newValue
!==
elementValue
);
if
(
valueHasChanged
)
{
var
applyValueAction
=
function
()
{
ko
.
selectExtensions
.
writeValue
(
element
,
newValue
);
};
if
(
ko
.
utils
.
tagNameLower
(
element
)
===
"
select
"
)
{
var
allowUnset
=
allBindings
.
get
(
'
valueAllowUnset
'
);
var
applyValueAction
=
function
()
{
ko
.
selectExtensions
.
writeValue
(
element
,
newValue
,
allowUnset
);
};
applyValueAction
();
if
(
valueIsSelectOption
)
{
if
(
newValue
!==
ko
.
selectExtensions
.
readValue
(
element
))
{
if
(
!
allowUnset
&&
newValue
!==
ko
.
selectExtensions
.
readValue
(
element
))
{
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
// because you're not allowed to have a model value that disagrees with a visible UI selection.
ko
.
dependencyDetection
.
ignore
(
ko
.
utils
.
triggerEvent
,
null
,
[
element
,
"
change
"
]);
...
...
@@ -3334,6 +3983,8 @@ ko.bindingHandlers['value'] = {
// to apply the value as well.
setTimeout
(
applyValueAction
,
0
);
}
}
else
{
ko
.
selectExtensions
.
writeValue
(
element
,
newValue
);
}
}
}
...
...
@@ -3718,7 +4369,8 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
:
new
ko
.
bindingContext
(
ko
.
utils
.
unwrapObservable
(
dataOrBindingContext
));
// Support selecting template as a function of the data being rendered
var
templateName
=
typeof
(
template
)
==
'
function
'
?
template
(
bindingContext
[
'
$data
'
],
bindingContext
)
:
template
;
var
templateName
=
ko
.
isObservable
(
template
)
?
template
()
:
typeof
(
template
)
==
'
function
'
?
template
(
bindingContext
[
'
$data
'
],
bindingContext
)
:
template
;
var
renderedNodesArray
=
executeTemplate
(
targetNodeOrNodeArray
,
renderMode
,
templateName
,
bindingContext
,
options
);
if
(
renderMode
==
"
replaceNode
"
)
{
...
...
@@ -3800,15 +4452,18 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
return
{
'
controlsDescendantBindings
'
:
true
};
},
'
update
'
:
function
(
element
,
valueAccessor
,
allBindings
,
viewModel
,
bindingContext
)
{
var
templateName
=
ko
.
utils
.
unwrapObservable
(
valueAccessor
()),
options
=
{},
shouldDisplay
=
true
,
var
value
=
valueAccessor
(),
dataValue
,
templateComputed
=
null
;
options
=
ko
.
utils
.
unwrapObservable
(
value
),
shouldDisplay
=
true
,
templateComputed
=
null
,
templateName
;
if
(
typeof
templateName
!=
"
string
"
)
{
options
=
templateName
;
templateName
=
ko
.
utils
.
unwrapObservable
(
options
[
'
name
'
]);
if
(
typeof
options
==
"
string
"
)
{
templateName
=
value
;
options
=
{};
}
else
{
templateName
=
options
[
'
name
'
];
// Support "if"/"ifnot" conditions
if
(
'
if
'
in
options
)
...
...
@@ -3855,6 +4510,24 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
ko
.
exportSymbol
(
'
setTemplateEngine
'
,
ko
.
setTemplateEngine
);
ko
.
exportSymbol
(
'
renderTemplate
'
,
ko
.
renderTemplate
);
// Go through the items that have been added and deleted and try to find matches between them.
ko
.
utils
.
findMovesInArrayComparison
=
function
(
left
,
right
,
limitFailedCompares
)
{
if
(
left
.
length
&&
right
.
length
)
{
var
failedCompares
,
l
,
r
,
leftItem
,
rightItem
;
for
(
failedCompares
=
l
=
0
;
(
!
limitFailedCompares
||
failedCompares
<
limitFailedCompares
)
&&
(
leftItem
=
left
[
l
]);
++
l
)
{
for
(
r
=
0
;
rightItem
=
right
[
r
];
++
r
)
{
if
(
leftItem
[
'
value
'
]
===
rightItem
[
'
value
'
])
{
leftItem
[
'
moved
'
]
=
rightItem
[
'
index
'
];
rightItem
[
'
moved
'
]
=
leftItem
[
'
index
'
];
right
.
splice
(
r
,
1
);
// This item is marked as moved; so remove it from right list
failedCompares
=
r
=
0
;
// Reset failed compares count because we're checking for consecutive failures
break
;
}
}
failedCompares
+=
r
;
}
}
};
ko
.
utils
.
compareArrays
=
(
function
()
{
var
statusNotInOld
=
'
added
'
,
statusNotInNew
=
'
deleted
'
;
...
...
@@ -3928,25 +4601,10 @@ ko.utils.compareArrays = (function () {
}
}
if
(
notInSml
.
length
&&
notInBig
.
length
)
{
// Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
// smlIndexMax keeps the time complexity of this algorithm linear.
var
limitFailedCompares
=
smlIndexMax
*
10
,
failedCompares
,
a
,
d
,
notInSmlItem
,
notInBigItem
;
// Go through the items that have been added and deleted and try to find matches between them.
for
(
failedCompares
=
a
=
0
;
(
options
[
'
dontLimitMoves
'
]
||
failedCompares
<
limitFailedCompares
)
&&
(
notInSmlItem
=
notInSml
[
a
]);
a
++
)
{
for
(
d
=
0
;
notInBigItem
=
notInBig
[
d
];
d
++
)
{
if
(
notInSmlItem
[
'
value
'
]
===
notInBigItem
[
'
value
'
])
{
notInSmlItem
[
'
moved
'
]
=
notInBigItem
[
'
index
'
];
notInBigItem
[
'
moved
'
]
=
notInSmlItem
[
'
index
'
];
notInBig
.
splice
(
d
,
1
);
// This item is marked as moved; so remove it from notInBig list
failedCompares
=
d
=
0
;
// Reset failed compares count because we're checking for consecutive failures
break
;
}
}
failedCompares
+=
d
;
}
}
ko
.
utils
.
findMovesInArrayComparison
(
notInSml
,
notInBig
,
smlIndexMax
*
10
);
return
editScript
.
reverse
();
}
...
...
@@ -3954,7 +4612,6 @@ ko.utils.compareArrays = (function () {
})();
ko
.
exportSymbol
(
'
utils.compareArrays
'
,
ko
.
utils
.
compareArrays
);
(
function
()
{
// Objective:
// * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
...
...
@@ -3981,7 +4638,7 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
// Replace the contents of the mappedNodes array, thereby updating the record
// of which nodes would be deleted if valueToMap was itself later removed
mappedNodes
.
splice
(
0
,
mappedNodes
.
length
)
;
mappedNodes
.
length
=
0
;
ko
.
utils
.
arrayPushAll
(
mappedNodes
,
newMappedNodes
);
},
null
,
{
disposeWhenNodeIsRemoved
:
containerNode
,
disposeWhen
:
function
()
{
return
!
ko
.
utils
.
anyDomNodeIsAttachedToDocument
(
mappedNodes
);
}
});
return
{
mappedNodes
:
mappedNodes
,
dependentObservable
:
(
dependentObservable
.
isActive
()
?
dependentObservable
:
undefined
)
};
...
...
@@ -4144,7 +4801,7 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
// Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
// which KO internally refers to as version "2", so older versions are no longer detected.
var
jQueryTmplVersion
=
this
.
jQueryTmplVersion
=
(
function
()
{
if
(
(
typeof
(
jQuery
)
==
"
undefined
"
)
||
!
(
jQuery
[
'
tmpl
'
]))
if
(
!
jQuery
||
!
(
jQuery
[
'
tmpl
'
]))
return
0
;
// Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
try
{
...
...
architecture-examples/knockoutjs/bower_components/director/build/director.js
View file @
c5e53bc8
//
// Generated on
Sun Dec 16 2012 22:47:05
GMT-0500 (EST) by Nodejitsu, Inc (Using Codesurgeon).
// Version 1.
1.9
// Generated on
Fri Dec 27 2013 12:02:11
GMT-0500 (EST) by Nodejitsu, Inc (Using Codesurgeon).
// Version 1.
2.2
//
(
function
(
exports
)
{
/*
* browser.js: Browser specific functionality for director.
*
...
...
@@ -201,7 +200,7 @@ Router.prototype.init = function (r) {
this
.
handler
=
function
(
onChangeEvent
)
{
var
newURL
=
onChangeEvent
&&
onChangeEvent
.
newURL
||
window
.
location
.
hash
;
var
url
=
self
.
history
===
true
?
self
.
getPath
()
:
newURL
.
replace
(
/.*#/
,
''
);
self
.
dispatch
(
'
on
'
,
url
);
self
.
dispatch
(
'
on
'
,
url
.
charAt
(
0
)
===
'
/
'
?
url
:
'
/
'
+
url
);
};
listener
.
init
(
this
.
handler
,
this
.
history
);
...
...
@@ -210,7 +209,7 @@ Router.prototype.init = function (r) {
if
(
dlocHashEmpty
()
&&
r
)
{
dloc
.
hash
=
r
;
}
else
if
(
!
dlocHashEmpty
())
{
self
.
dispatch
(
'
on
'
,
dloc
.
hash
.
replace
(
/^#
/
,
''
));
self
.
dispatch
(
'
on
'
,
'
/
'
+
dloc
.
hash
.
replace
(
/^
(
#
\/
|#|
\/)
/
,
''
));
}
}
else
{
...
...
@@ -363,11 +362,16 @@ function regifyString(str, params) {
out
+=
str
.
substr
(
0
,
matches
.
index
)
+
matches
[
0
];
}
str
=
out
+=
str
.
substr
(
last
);
var
captures
=
str
.
match
(
/:
([^\/]
+
)
/ig
),
length
;
var
captures
=
str
.
match
(
/:
([^\/]
+
)
/ig
),
capture
,
length
;
if
(
captures
)
{
length
=
captures
.
length
;
for
(
var
i
=
0
;
i
<
length
;
i
++
)
{
str
=
str
.
replace
(
captures
[
i
],
paramifyString
(
captures
[
i
],
params
));
capture
=
captures
[
i
];
if
(
capture
.
slice
(
0
,
2
)
===
"
::
"
)
{
str
=
capture
.
slice
(
1
);
}
else
{
str
=
str
.
replace
(
capture
,
paramifyString
(
capture
,
params
));
}
}
}
return
str
;
...
...
@@ -485,20 +489,22 @@ Router.prototype.dispatch = function(method, path, callback) {
Router
.
prototype
.
invoke
=
function
(
fns
,
thisArg
,
callback
)
{
var
self
=
this
;
var
apply
;
if
(
this
.
async
)
{
_asyncEverySeries
(
fns
,
function
apply
(
fn
,
next
)
{
apply
=
function
(
fn
,
next
)
{
if
(
Array
.
isArray
(
fn
))
{
return
_asyncEverySeries
(
fn
,
apply
,
next
);
}
else
if
(
typeof
fn
==
"
function
"
)
{
fn
.
apply
(
thisArg
,
fns
.
captures
.
concat
(
next
));
}
},
function
()
{
};
_asyncEverySeries
(
fns
,
apply
,
function
()
{
if
(
callback
)
{
callback
.
apply
(
thisArg
,
arguments
);
}
});
}
else
{
_every
(
fns
,
function
apply
(
fn
)
{
apply
=
function
(
fn
)
{
if
(
Array
.
isArray
(
fn
))
{
return
_every
(
fn
,
apply
);
}
else
if
(
typeof
fn
===
"
function
"
)
{
...
...
@@ -506,7 +512,8 @@ Router.prototype.invoke = function(fns, thisArg, callback) {
}
else
if
(
typeof
fn
===
"
string
"
&&
self
.
resource
)
{
self
.
resource
[
fn
].
apply
(
thisArg
,
fns
.
captures
||
[]);
}
});
};
_every
(
fns
,
apply
);
}
};
...
...
@@ -686,7 +693,7 @@ Router.prototype.mount = function(routes, path) {
function
insertOrMount
(
route
,
local
)
{
var
rename
=
route
,
parts
=
route
.
split
(
self
.
delimiter
),
routeType
=
typeof
routes
[
route
],
isRoute
=
parts
[
0
]
===
""
||
!
self
.
_methods
[
parts
[
0
]],
event
=
isRoute
?
"
on
"
:
rename
;
if
(
isRoute
)
{
rename
=
rename
.
slice
((
rename
.
match
(
new
RegExp
(
self
.
delimiter
))
||
[
""
])[
0
].
length
);
rename
=
rename
.
slice
((
rename
.
match
(
new
RegExp
(
"
^
"
+
self
.
delimiter
))
||
[
""
])[
0
].
length
);
parts
.
shift
();
}
if
(
isRoute
&&
routeType
===
"
object
"
&&
!
Array
.
isArray
(
routes
[
route
]))
{
...
...
architecture-examples/knockoutjs/index.html
View file @
c5e53bc8
...
...
@@ -52,7 +52,7 @@
<p>
Part of
<a
href=
"http://todomvc.com"
>
TodoMVC
</a></p>
</footer>
<script
src=
"bower_components/todomvc-common/base.js"
></script>
<script
src=
"bower_components/
knockout.js/knockout.debug
.js"
></script>
<script
src=
"bower_components/
component-knockout-passy/knockout
.js"
></script>
<script
src=
"bower_components/director/build/director.js"
></script>
<script
src=
"js/app.js"
></script>
</body>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment