diff --git a/README.md b/README.md index 20acc469a2..42f031a66d 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,158 @@ -DefinitelyTyped -=============== - -The repository for *high quality* TypeScript type definitions. - -Use a definition file like this: - -``` -/// -``` - -Contributor Guidelines ----------------------- - -See the section: [How to contribute](https://github.com/borisyankov/DefinitelyTyped/wiki/How-to-contribute) - -Other means to get the definitions ----------------------------------- -* [TypeScript definition package manager](https://github.com/Diullei/tsd) - -* [tsdpm](http://www.tsdpm.com/) - Online search - -> NuGet package(s) coming soon - -List of Definitions -------------------- -* [Ace Cloud9 Editor](http://ace.ajax.org/) (by [Diullei Gomes](https://github.com/Diullei)) -* [AmCharts](http://www.amcharts.com/) (by [Covobonomo](https://github.com/covobonomo/)) -* [AngularJS](http://angularjs.org) (by [Diego Vilar](https://github.com/diegovilar)) ([wiki](https://github.com/borisyankov/DefinitelyTyped/wiki/AngularJS-Definitions-Usage-Notes)) -* [async](https://github.com/caolan/async) (by [Boris Yankov](https://github.com/borisyankov)) -* [Backbone.js](http://backbonejs.org/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Bootbox](https://github.com/makeusabrew/bootbox) (by [Vincent Bortone](https://github.com/vbortone/)) -* [Bootstrap](http://twitter.github.com/bootstrap/) (by [Boris Yankov](https://github.com/borisyankov)) -* [bootstrap-notify](https://github.com/Nijikokun/bootstrap-notify) (by [Blake Niemyjski](https://github.com/niemyjski)) -* [bootstrap.datepicker](https://github.com/eternicode/bootstrap-datepicker) (by [Boris Yankov](https://github.com/borisyankov)) -* [Box2DWeb](http://code.google.com/p/box2dweb/) (by [Josh Baldwin](https://github.com/jbaldwin/)) -* [Breeze](http://www.breezejs.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [CasperJS](http://casperjs.org) (by [Jed Hunsaker](https://github.com/jedhunsaker)) -* [Cheerio](https://github.com/MatthewMueller/cheerio) (by [Bret Little](https://github.com/blittle)) -* [Chosen](http://harvesthq.github.com/chosen/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Chrome](http://developer.chrome.com/extensions/) (by [Matthew Kimber](https://github.com/matthewkimber)) -* [CodeMirror](http://codemirror.net) (by [Fran�ois de Campredon](https://github.com/fdecampredon)) -* [d3.js](http://d3js.org/) (from TypeScript samples) -* [domo](http://domo-js.com/) (by [Steve Fenton](https://github.com/Steve-Fenton)) -* [EaselJS](http://www.createjs.com/#!/EaselJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) -* [ember.js](http://emberjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [EpicEditor](http://epiceditor.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Express](http://expressjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Fabric.js](http://fabricjs.com/) (by [Oliver Klemencic](https://github.com/oklemencic/)) -* [Fancybox](http://fancybox.net/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Finite State Machine](https://github.com/jakesgordon/javascript-state-machine) (by [Boris Yankov](https://github.com/borisyankov)) -* [FlexSlider](http://www.woothemes.com/flexslider/) (by [Diullei Gomes](https://github.com/Diullei)) -* [Foundation](http://foundation.zurb.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [glDatePicker](http://glad.github.com/glDatePicker/) (by [D�niel Tar](https://github.com/qcz)) -* [GreenSock Animation Platform (GSAP)](http://www.greensock.com/get-started-js/) (by [Robert S.](https://github.com/codeBelt)) -* [GoogleMaps](https://developers.google.com/maps/) (by [Esben Nepper](https://github.com/eNepper)) -* [Hammer.js](http://eightmedia.github.com/hammer.js/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Handlebars](http://handlebarsjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Highcharts](http://www.highcharts.com/) (by [damianog](https://github.com/damianog)) -* [History.js](https://github.com/balupton/History.js/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Humane.js](http://wavded.github.com/humane-js/) (by [John Vrbanac](https://github.com/jmvrbanac)) -* [i18next](http://i18next.com/) (by [Maarten Docter](https://github.com/mdocter)) -* [Impress.js](https://github.com/bartaz/impress.js) (by [Boris Yankov](https://github.com/borisyankov)) -* [iScroll](http://cubiq.org/iscroll-4) (by [Boris Yankov](https://github.com/borisyankov)) -* [Jasmine](http://pivotal.github.com/jasmine/) (by [Boris Yankov](https://github.com/borisyankov)) -* [jQRangeSlider](http://ghusse.github.com/jQRangeSlider) (by [D�niel Tar](https://github.com/qcz)) -* [jQuery](http://jquery.com/) (from TypeScript samples) -* [jQuery Mobile](http://jquerymobile.com) (by [Boris Yankov](https://github.com/borisyankov)) -* [jQuery UI](http://jqueryui.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [jQuery.BBQ](http://benalman.com/projects/jquery-bbq-plugin/) (by [Adam R. Smith](https://github.com/sunetos)) -* [jQuery.contextMenu](http://medialize.github.com/jQuery-contextMenu/) (by [Natan Vivo](https://github.com/nvivo/)) -* [jQuery.clientSideLogging](https://github.com/remybach/jQuery.clientSideLogging/) (by [Diullei Gomes](https://github.com/diullei/)) -* [jQuery.Cookie](https://github.com/carhartl/jquery-cookie) (by [Roy Goode](https://github.com/RoyGoode)) -* [jQuery.Cycle](http://jquery.malsup.com/cycle/) (by [Fran�ois Guillot](http://fguillot.developpez.com/)) -* [jQuery.dynatree](http://code.google.com/p/dynatree/) (by [Fran�ois de Campredon](https://github.com/fdecampredon)) -* [jQuery.Flot](http://www.flotcharts.org/) (by [Matt Burland](https://github.com/burlandm)) -* [jQuery.form](http://malsup.com/jquery/form/) (by [Fran�ois Guillot](http://fguillot.developpez.com/)) -* [jQuery.Globalize](https://github.com/jquery/globalize) (by [Boris Yankov](https://github.com/borisyankov)) -* [jQuery.jNotify](http://jnotify.codeplex.com) (by [James Curran](https://github.com/jamescurran/)) -* [jQuery.simplePagination](https://github.com/flaviusmatis/simplePagination.js) (by [Natan Vivo](https://github.com/nvivo/)) -* [jQuery.timeago](http://timeago.yarp.com/) (by [Fran�ois Guillot](http://fguillot.developpez.com/)) -* [jQuery.Timepicker](http://fgelinas.com/code/timepicker/) (by [Anwar Javed](https://github.com/anwarjaved)) -* [jQuery.Transit](http://ricostacruz.com/jquery.transit/) (by [MrBigDog2U](https://github.com/MrBigDog2U)) -* [jQuery.Validation](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) (by [Boris Yankov](https://github.com/borisyankov)) -* [jQuery.Watermark](http://jquery-watermark.googlecode.com) (by [Anwar Javed](https://github.com/anwarjaved)) -* [JSONEditorOnline](https://github.com/josdejong/jsoneditoronline) (by [Vincent Bortone](https://github.com/vbortone/)) -* [KeyboardJS](https://github.com/RobertWHurst/KeyboardJS) (by [Vincent Bortone](https://github.com/vbortone/)) -* [Knockback](http://kmalakoff.github.com/knockback/) (by [Marcel Binot](https://github.com/docgit)) -* [Knockout.js](http://knockoutjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Knockout.Mapping](https://github.com/SteveSanderson/knockout.mapping) (by [Boris Yankov](https://github.com/borisyankov)) -* [Knockout.Postbox](https://github.com/rniemeyer/knockout-postbox) (by [Judah Gabriel](https://github.com/JudahGabriel)) -* [Knockout.Validation](https://github.com/ericmbarnard/Knockout-Validation) (by [Dan Ludwig](https://github.com/danludwig)) -* [Knockout.Viewmodel](http://coderenaissance.github.com/knockout.viewmodel/) (by [Oisin Grehan](https://github.com/oising)) -* [ko.editables](http://romanych.github.com/ko.editables/) (by [Oisin Grehan](https://github.com/oising)) -* [KoLite](https://github.com/CodeSeven/kolite) (by [Boris Yankov](https://github.com/borisyankov)) -* [Leaflet](https://github.com/Leaflet/Leaflet) (by [Vladimir](https://github.com/rgripper)) -* [Libxmljs](https://github.com/polotek/libxmljs) (by [François de Campredon](https://github.com/fdecampredon)) -* [linq.js](http://linqjs.codeplex.com/) (by [Marcin Najder](https://github.com/marcinnajder)) -* [Marked](https://github.com/chjj/marked) (by [William Orr](https://github.com/worr)) -* [Modernizr](http://modernizr.com/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Moment.js](https://github.com/timrwood/moment) (by [Michael Lakerveld](https://github.com/Lakerfield)) -* [Mousetrap](http://craig.is/killing/mice) (by [D�niel Tar](https://github.com/qcz)) -* [Mustache.js](https://github.com/janl/mustache.js) (by [Boris Yankov](https://github.com/borisyankov)) -* [Node.js](http://nodejs.org/) (from TypeScript samples) -* [node_redis](https://github.com/mranney/node_redis) (by [Boris Yankov](https://github.com/borisyankov)) -* [node-sqlserver](https://github.com/WindowsAzure/node-sqlserver) (by [Boris Yankov](https://github.com/borisyankov)) -* [Numeral.js](https://github.com/adamwdraper/Numeral-js) (by [Vincent Bortone](https://github.com/vbortone/)) -* [PhantomJS](http://phantomjs.org) (by [Jed Hunsaker](https://github.com/jedhunsaker)) -* [PhoneGap](http://phonegap.com) (by [Boris Yankov](https://github.com/borisyankov)) -* [Platform](https://github.com/bestiejs/platform.js) (by [Jake Hickman](https://github.com/JakeH)) -* [PouchDB](http://pouchdb.com) (by [Bill Sears](https://github.com/MrBigDog2U/)) -* [PreloadJS](http://www.createjs.com/#!/PreloadJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) -* [QUnit](http://qunitjs.com/) (by [Diullei Gomes](https://github.com/Diullei)) -* [Restify](https://github.com/mcavage/node-restify) (by [Bret Little](https://github.com/blittle)) -* [Rx.js](http://rx.codeplex.com/) (by [gsino](http://www.codeplex.com/site/users/view/gsino)) -* [Raphael](http://raphaeljs.com/) (by [CheCoxshall](https://github.com/CheCoxshall)) -* [require.js](http://requirejs.org/) (by [Josh Baldwin](https://github.com/jbaldwin/)) -* [Sammy.js](http://sammyjs.org/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Select2](http://ivaynberg.github.com/select2/) (by [Boris Yankov](https://github.com/borisyankov)) -* [SignalR](http://www.asp.net/signalr) (by [Boris Yankov](https://github.com/borisyankov)) -* [Sinon](http://sinonjs.org/) (by [William Sears](https://github.com/mrbigdog2u)) -* [socket.io](http://socket.io) (by [William Orr](https://github.com/worr)) -* [SockJS](https://github.com/sockjs/sockjs-client) (by [Emil Ivanov](https://github.com/vladev)) -* [SoundJS](http://www.createjs.com/#!/SoundJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) -* [Spin](http://fgnass.github.com/spin.js/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Store.js](https://github.com/marcuswestin/store.js/) (by [Vincent Bortone](https://github.com/vbortone/)) -* [Sugar](http://sugarjs.com/) (by [Josh Baldwin](https://github.com/jbaldwin/)) -* [SwipeView](http://cubiq.org/swipeview) (by [Boris Yankov](https://github.com/borisyankov)) -* [Teechart](http://www.steema.com) (by [Steema](http://www.steema.com)) -* [three.js](http://mrdoob.github.com/three.js/) (by [Kon](http://phyzkit.net/)) -* [Toastr](https://github.com/CodeSeven/toastr) (by [Boris Yankov](https://github.com/borisyankov)) -* [TweenJS](http://www.createjs.com/#!/TweenJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) -* [tween.js](https://github.com/sole/tween.js/) (by [Adam R. Smith](https://github.com/sunetos)) -* [twitter-bootstrap-wizard](https://github.com/VinceG/twitter-bootstrap-wizard) (by [Blake Niemyjski](https://github.com/niemyjski)) -* [Ubuntu Unity Web API](https://launchpad.net/libunity-webapps) (by [John Vrbanac](https://github.com/jmvrbanac)) -* [Underscore.js](http://underscorejs.org/) (by [Boris Yankov](https://github.com/borisyankov)) -* [Underscore.js (Typed)](http://underscorejs.org/) (by [Josh Baldwin](https://github.com/jbaldwin/)) -* [Viewporter](https://github.com/zynga/viewporter) (by [Boris Yankov](https://github.com/borisyankov)) -* [Vimeo](http://developer.vimeo.com/player/js-api) (by [Daz Wilkin](https://github.com/DazWilkin/)) -* [WebRTC](http://dev.w3.org/2011/webrtc/editor/webrtc.html) (by [Ken Smith](https://github.com/smithkl42)) -* [YouTube](https://developers.google.com/youtube/) (by [Daz Wilkin](https://github.com/DazWilkin/)) -* [Zynga Scroller](https://github.com/zynga/scroller) (by [Boris Yankov](https://github.com/borisyankov)) -* [ZeroClipboard](https://github.com/jonrohan/ZeroClipboard) (by [Eric J. Smith](https://github.com/ejsmith)) - -Requested Definitions ---------------------- -* [Rickshaw](https://github.com/shutterstock/rickshaw) -* [Livestamp.js](https://github.com/mattbradley/livestampjs) -* [jQuery ScrollTo](https://github.com/balupton/jquery-scrollto) -* [Crossfilter](https://github.com/square/crossfilter) -* [dc.js](https://github.com/NickQiZhu/dc.js) -* [google.visualizations](https://developers.google.com/chart/) -* [Tags Manager](http://welldonethings.com/tags/manager) -* [Prelude.ls](http://gkz.github.com/prelude-ls/) -* [MooTools](http://mootools.net/) -* [Lo-Dash](http://lodash.com/) +DefinitelyTyped +=============== + +The repository for *high quality* TypeScript type definitions. + +Use a definition file like this: + +``` +/// +``` + +Contributor Guidelines +---------------------- + +See the section: [How to contribute](https://github.com/borisyankov/DefinitelyTyped/wiki/How-to-contribute) + +Other means to get the definitions +---------------------------------- +* [TypeScript definition package manager](https://github.com/Diullei/tsd) + +* [tsdpm](http://www.tsdpm.com/) - Online search + +> NuGet package(s) coming soon + +List of Definitions +------------------- +* [Ace Cloud9 Editor](http://ace.ajax.org/) (by [Diullei Gomes](https://github.com/Diullei)) +* [AmCharts](http://www.amcharts.com/) (by [Covobonomo](https://github.com/covobonomo/)) +* [AngularJS](http://angularjs.org) (by [Diego Vilar](https://github.com/diegovilar)) ([wiki](https://github.com/borisyankov/DefinitelyTyped/wiki/AngularJS-Definitions-Usage-Notes)) +* [async](https://github.com/caolan/async) (by [Boris Yankov](https://github.com/borisyankov)) +* [Backbone.js](http://backbonejs.org/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Bootbox](https://github.com/makeusabrew/bootbox) (by [Vincent Bortone](https://github.com/vbortone/)) +* [Bootstrap](http://twitter.github.com/bootstrap/) (by [Boris Yankov](https://github.com/borisyankov)) +* [bootstrap-notify](https://github.com/Nijikokun/bootstrap-notify) (by [Blake Niemyjski](https://github.com/niemyjski)) +* [bootstrap.datepicker](https://github.com/eternicode/bootstrap-datepicker) (by [Boris Yankov](https://github.com/borisyankov)) +* [Box2DWeb](http://code.google.com/p/box2dweb/) (by [Josh Baldwin](https://github.com/jbaldwin/)) +* [Breeze](http://www.breezejs.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [CasperJS](http://casperjs.org) (by [Jed Hunsaker](https://github.com/jedhunsaker)) +* [Cheerio](https://github.com/MatthewMueller/cheerio) (by [Bret Little](https://github.com/blittle)) +* [Chosen](http://harvesthq.github.com/chosen/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Chrome](http://developer.chrome.com/extensions/) (by [Matthew Kimber](https://github.com/matthewkimber)) +* [CodeMirror](http://codemirror.net) (by [Fran�ois de Campredon](https://github.com/fdecampredon)) +* [d3.js](http://d3js.org/) (from TypeScript samples) +* [domo](http://domo-js.com/) (by [Steve Fenton](https://github.com/Steve-Fenton)) +* [EaselJS](http://www.createjs.com/#!/EaselJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) +* [ember.js](http://emberjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [EpicEditor](http://epiceditor.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Express](http://expressjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Fabric.js](http://fabricjs.com/) (by [Oliver Klemencic](https://github.com/oklemencic/)) +* [Fancybox](http://fancybox.net/) (by [Boris Yankov](https://github.com/borisyankov)) +* [File API: Directories and System](http://www.w3.org/TR/file-system-api/) (by [Kon](http://phyzkit.net/)) +* [File API: Writer](http://www.w3.org/TR/file-writer-api/) (by [Kon](http://phyzkit.net/)) +* [Finite State Machine](https://github.com/jakesgordon/javascript-state-machine) (by [Boris Yankov](https://github.com/borisyankov)) +* [FlexSlider](http://www.woothemes.com/flexslider/) (by [Diullei Gomes](https://github.com/Diullei)) +* [Foundation](http://foundation.zurb.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [glDatePicker](http://glad.github.com/glDatePicker/) (by [D�niel Tar](https://github.com/qcz)) +* [GreenSock Animation Platform (GSAP)](http://www.greensock.com/get-started-js/) (by [Robert S.](https://github.com/codeBelt)) +* [GoogleMaps](https://developers.google.com/maps/) (by [Esben Nepper](https://github.com/eNepper)) +* [Hammer.js](http://eightmedia.github.com/hammer.js/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Handlebars](http://handlebarsjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Highcharts](http://www.highcharts.com/) (by [damianog](https://github.com/damianog)) +* [History.js](https://github.com/balupton/History.js/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Humane.js](http://wavded.github.com/humane-js/) (by [John Vrbanac](https://github.com/jmvrbanac)) +* [i18next](http://i18next.com/) (by [Maarten Docter](https://github.com/mdocter)) +* [Impress.js](https://github.com/bartaz/impress.js) (by [Boris Yankov](https://github.com/borisyankov)) +* [iScroll](http://cubiq.org/iscroll-4) (by [Boris Yankov](https://github.com/borisyankov)) +* [Jasmine](http://pivotal.github.com/jasmine/) (by [Boris Yankov](https://github.com/borisyankov)) +* [jQRangeSlider](http://ghusse.github.com/jQRangeSlider) (by [D�niel Tar](https://github.com/qcz)) +* [jQuery](http://jquery.com/) (from TypeScript samples) +* [jQuery Mobile](http://jquerymobile.com) (by [Boris Yankov](https://github.com/borisyankov)) +* [jQuery UI](http://jqueryui.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [jQuery.BBQ](http://benalman.com/projects/jquery-bbq-plugin/) (by [Adam R. Smith](https://github.com/sunetos)) +* [jQuery.contextMenu](http://medialize.github.com/jQuery-contextMenu/) (by [Natan Vivo](https://github.com/nvivo/)) +* [jQuery.clientSideLogging](https://github.com/remybach/jQuery.clientSideLogging/) (by [Diullei Gomes](https://github.com/diullei/)) +* [jQuery.Cookie](https://github.com/carhartl/jquery-cookie) (by [Roy Goode](https://github.com/RoyGoode)) +* [jQuery.Cycle](http://jquery.malsup.com/cycle/) (by [Fran�ois Guillot](http://fguillot.developpez.com/)) +* [jQuery.dynatree](http://code.google.com/p/dynatree/) (by [Fran�ois de Campredon](https://github.com/fdecampredon)) +* [jQuery.Flot](http://www.flotcharts.org/) (by [Matt Burland](https://github.com/burlandm)) +* [jQuery.form](http://malsup.com/jquery/form/) (by [Fran�ois Guillot](http://fguillot.developpez.com/)) +* [jQuery.Globalize](https://github.com/jquery/globalize) (by [Boris Yankov](https://github.com/borisyankov)) +* [jQuery.jNotify](http://jnotify.codeplex.com) (by [James Curran](https://github.com/jamescurran/)) +* [jQuery.simplePagination](https://github.com/flaviusmatis/simplePagination.js) (by [Natan Vivo](https://github.com/nvivo/)) +* [jQuery.timeago](http://timeago.yarp.com/) (by [Fran�ois Guillot](http://fguillot.developpez.com/)) +* [jQuery.Timepicker](http://fgelinas.com/code/timepicker/) (by [Anwar Javed](https://github.com/anwarjaved)) +* [jQuery.Transit](http://ricostacruz.com/jquery.transit/) (by [MrBigDog2U](https://github.com/MrBigDog2U)) +* [jQuery.Validation](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) (by [Boris Yankov](https://github.com/borisyankov)) +* [jQuery.Watermark](http://jquery-watermark.googlecode.com) (by [Anwar Javed](https://github.com/anwarjaved)) +* [JSONEditorOnline](https://github.com/josdejong/jsoneditoronline) (by [Vincent Bortone](https://github.com/vbortone/)) +* [KeyboardJS](https://github.com/RobertWHurst/KeyboardJS) (by [Vincent Bortone](https://github.com/vbortone/)) +* [Knockback](http://kmalakoff.github.com/knockback/) (by [Marcel Binot](https://github.com/docgit)) +* [Knockout.js](http://knockoutjs.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Knockout.Mapping](https://github.com/SteveSanderson/knockout.mapping) (by [Boris Yankov](https://github.com/borisyankov)) +* [Knockout.Postbox](https://github.com/rniemeyer/knockout-postbox) (by [Judah Gabriel](https://github.com/JudahGabriel)) +* [Knockout.Validation](https://github.com/ericmbarnard/Knockout-Validation) (by [Dan Ludwig](https://github.com/danludwig)) +* [Knockout.Viewmodel](http://coderenaissance.github.com/knockout.viewmodel/) (by [Oisin Grehan](https://github.com/oising)) +* [ko.editables](http://romanych.github.com/ko.editables/) (by [Oisin Grehan](https://github.com/oising)) +* [KoLite](https://github.com/CodeSeven/kolite) (by [Boris Yankov](https://github.com/borisyankov)) +* [Leaflet](https://github.com/Leaflet/Leaflet) (by [Vladimir](https://github.com/rgripper)) +* [Libxmljs](https://github.com/polotek/libxmljs) (by [François de Campredon](https://github.com/fdecampredon)) +* [linq.js](http://linqjs.codeplex.com/) (by [Marcin Najder](https://github.com/marcinnajder)) +* [Marked](https://github.com/chjj/marked) (by [William Orr](https://github.com/worr)) +* [Modernizr](http://modernizr.com/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Moment.js](https://github.com/timrwood/moment) (by [Michael Lakerveld](https://github.com/Lakerfield)) +* [Mousetrap](http://craig.is/killing/mice) (by [D�niel Tar](https://github.com/qcz)) +* [Mustache.js](https://github.com/janl/mustache.js) (by [Boris Yankov](https://github.com/borisyankov)) +* [Node.js](http://nodejs.org/) (from TypeScript samples) +* [node_redis](https://github.com/mranney/node_redis) (by [Boris Yankov](https://github.com/borisyankov)) +* [node-sqlserver](https://github.com/WindowsAzure/node-sqlserver) (by [Boris Yankov](https://github.com/borisyankov)) +* [Numeral.js](https://github.com/adamwdraper/Numeral-js) (by [Vincent Bortone](https://github.com/vbortone/)) +* [PhantomJS](http://phantomjs.org) (by [Jed Hunsaker](https://github.com/jedhunsaker)) +* [PhoneGap](http://phonegap.com) (by [Boris Yankov](https://github.com/borisyankov)) +* [Platform](https://github.com/bestiejs/platform.js) (by [Jake Hickman](https://github.com/JakeH)) +* [PouchDB](http://pouchdb.com) (by [Bill Sears](https://github.com/MrBigDog2U/)) +* [PreloadJS](http://www.createjs.com/#!/PreloadJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) +* [QUnit](http://qunitjs.com/) (by [Diullei Gomes](https://github.com/Diullei)) +* [Restify](https://github.com/mcavage/node-restify) (by [Bret Little](https://github.com/blittle)) +* [Rx.js](http://rx.codeplex.com/) (by [gsino](http://www.codeplex.com/site/users/view/gsino)) +* [Raphael](http://raphaeljs.com/) (by [CheCoxshall](https://github.com/CheCoxshall)) +* [require.js](http://requirejs.org/) (by [Josh Baldwin](https://github.com/jbaldwin/)) +* [Sammy.js](http://sammyjs.org/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Select2](http://ivaynberg.github.com/select2/) (by [Boris Yankov](https://github.com/borisyankov)) +* [SignalR](http://www.asp.net/signalr) (by [Boris Yankov](https://github.com/borisyankov)) +* [Sinon](http://sinonjs.org/) (by [William Sears](https://github.com/mrbigdog2u)) +* [socket.io](http://socket.io) (by [William Orr](https://github.com/worr)) +* [SockJS](https://github.com/sockjs/sockjs-client) (by [Emil Ivanov](https://github.com/vladev)) +* [SoundJS](http://www.createjs.com/#!/SoundJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) +* [Spin](http://fgnass.github.com/spin.js/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Store.js](https://github.com/marcuswestin/store.js/) (by [Vincent Bortone](https://github.com/vbortone/)) +* [Sugar](http://sugarjs.com/) (by [Josh Baldwin](https://github.com/jbaldwin/)) +* [SwipeView](http://cubiq.org/swipeview) (by [Boris Yankov](https://github.com/borisyankov)) +* [Teechart](http://www.steema.com) (by [Steema](http://www.steema.com)) +* [three.js](http://mrdoob.github.com/three.js/) (by [Kon](http://phyzkit.net/)) +* [Toastr](https://github.com/CodeSeven/toastr) (by [Boris Yankov](https://github.com/borisyankov)) +* [TweenJS](http://www.createjs.com/#!/TweenJS) (by [Pedro Ferreira](https://bitbucket.org/drk4)) +* [tween.js](https://github.com/sole/tween.js/) (by [Adam R. Smith](https://github.com/sunetos)) +* [twitter-bootstrap-wizard](https://github.com/VinceG/twitter-bootstrap-wizard) (by [Blake Niemyjski](https://github.com/niemyjski)) +* [Ubuntu Unity Web API](https://launchpad.net/libunity-webapps) (by [John Vrbanac](https://github.com/jmvrbanac)) +* [Underscore.js](http://underscorejs.org/) (by [Boris Yankov](https://github.com/borisyankov)) +* [Underscore.js (Typed)](http://underscorejs.org/) (by [Josh Baldwin](https://github.com/jbaldwin/)) +* [Viewporter](https://github.com/zynga/viewporter) (by [Boris Yankov](https://github.com/borisyankov)) +* [Vimeo](http://developer.vimeo.com/player/js-api) (by [Daz Wilkin](https://github.com/DazWilkin/)) +* [WebRTC](http://dev.w3.org/2011/webrtc/editor/webrtc.html) (by [Ken Smith](https://github.com/smithkl42)) +* [YouTube](https://developers.google.com/youtube/) (by [Daz Wilkin](https://github.com/DazWilkin/)) +* [Zynga Scroller](https://github.com/zynga/scroller) (by [Boris Yankov](https://github.com/borisyankov)) +* [ZeroClipboard](https://github.com/jonrohan/ZeroClipboard) (by [Eric J. Smith](https://github.com/ejsmith)) + +Requested Definitions +--------------------- +* [Rickshaw](https://github.com/shutterstock/rickshaw) +* [Livestamp.js](https://github.com/mattbradley/livestampjs) +* [jQuery ScrollTo](https://github.com/balupton/jquery-scrollto) +* [Crossfilter](https://github.com/square/crossfilter) +* [dc.js](https://github.com/NickQiZhu/dc.js) +* [google.visualizations](https://developers.google.com/chart/) +* [Tags Manager](http://welldonethings.com/tags/manager) +* [Prelude.ls](http://gkz.github.com/prelude-ls/) +* [MooTools](http://mootools.net/) +* [Lo-Dash](http://lodash.com/) diff --git a/filesystem/filesystem-tests.ts b/filesystem/filesystem-tests.ts new file mode 100644 index 0000000000..8f175db98d --- /dev/null +++ b/filesystem/filesystem-tests.ts @@ -0,0 +1,49 @@ +/// +// http://www.w3.org/TR/file-system-api/ + +// 2. Introduction +declare function getAsText(file:File): void; +declare function writeDataToLogFile(fileWriter:FileWriterSync): void; + +function useAsyncFS(fs:FileSystem):void { + // see getAsText example in [FILE-API-ED]. + fs.root.getFile("already_there.txt", null, function (f:FileEntry): void{ + + // In the example of the specification, there is a following code: + // + // getAsText(f.file()); + // + // It seems wrong because f is ASYNCRONOUS file system. + f.file(getAsText); + + }); + + // But now we can also write to the file; see [FILE-WRITER-ED]. + fs.root.getFile("logFile", {create: true}, function (f:FileEntry): void{ + f.createWriter(writeDataToLogFile); + }); +} +window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function(fs:FileSystem): void{ + useAsyncFS(fs); +}); + +// In a worker: + +var tempFS:FileSystemSync = window.requestFileSystemSync(window.TEMPORARY, 1024 * 1024); +var logFile:FileEntrySync = tempFS.root.getFile("logFile", {create: true}); +var writer:FileWriterSync = logFile.createWriter(); +writer.seek(writer.length); +writeDataToLogFile(writer); + + +// 5.2 The Flags dictionary +var fsSync:FileSystemSync = window.requestFileSystemSync(window.TEMPORARY, 1024 * 1024); +// Get the data directory, creating it if it doesn't exist. +var dataDir:DirectoryEntrySync = fsSync.root.getDirectory("data", {create: true}); + +// Create the lock file, if and only if it doesn't exist. +try { + var lockFile:FileEntrySync = dataDir.getFile("lockfile.txt", {create: true, exclusive: true}); +} catch (ex) { + // It already exists, or something else went wrong. +} diff --git a/filesystem/filesystem.d.ts b/filesystem/filesystem.d.ts index 69477b13db..444f67a47c 100644 --- a/filesystem/filesystem.d.ts +++ b/filesystem/filesystem.d.ts @@ -1,6 +1,9 @@ // Type Definitions for File API: Directories and System (File System API) -// Specification: http://www.w3.org/TR/file-system-api/ +// Project: http://www.w3.org/TR/file-system-api/ // Definitions by: Kon +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// interface LocalFileSystem { @@ -8,7 +11,7 @@ interface LocalFileSystem { * Used for storage with no guarantee of persistence. */ TEMPORARY:number; - + /** * Used for storage that should not be removed by the user agent without application or user permission. */ @@ -32,7 +35,7 @@ interface LocalFileSystem { resolveLocalFileSystemURL(url:string, successCallback:EntryCallback, errorCallback?:ErrorCallback):void; /** - * see resolveLocalFileSystemURL. + * see requestFileSystem. */ webkitRequestFileSystem(type:number, size:number, successCallback:FileSystemCallback, errorCallback?:ErrorCallback):void; } diff --git a/filewriter/filewriter-tests.ts b/filewriter/filewriter-tests.ts new file mode 100644 index 0000000000..f70e76087d --- /dev/null +++ b/filewriter/filewriter-tests.ts @@ -0,0 +1,16 @@ +/// + +// http://www.w3.org/TR/file-writer-api/ +function writeFile(writer:FileWriter): void{ + function done(evt:Event): void{ + alert("Write completed."); + } + function error(evt:Event): void{ + alert("Write failed:" + evt); + } + + var b:Blob = new Blob(); + writer.onwrite = done; + writer.onerror = error; + writer.write(b); +} \ No newline at end of file diff --git a/filewriter/filewriter.d.ts b/filewriter/filewriter.d.ts new file mode 100644 index 0000000000..2257ee9eb8 --- /dev/null +++ b/filewriter/filewriter.d.ts @@ -0,0 +1,176 @@ +// Type Definitions for File API: Writer +// Project: http://www.w3.org/TR/file-writer-api/ +// Definitions by: Kon +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/** + * This interface provides methods to monitor the asynchronous writing of blobs to disk using progress events [PROGRESS-EVENTS] and event handler attributes. + * This interface is specified to be used within the context of the global object (Window [HTML5]) and within Web Workers (WorkerUtils [WEBWORKERS-ED]). + */ +interface FileSaver extends EventTarget { + /** + * When the abort method is called, user agents must run the steps below: + *
    + *
  1. If readyState == DONE or readyState == INIT, terminate this overall series of steps without doing anything else.
  2. + *
  3. Set readyState to DONE.
  4. + *
  5. If there are any tasks from the object's FileSaver task source in one of the task queues, then remove those tasks.
  6. + *
  7. Terminate the write algorithm being processed.
  8. + *
  9. Set the error attribute to a DOMError object of type "AbortError".
  10. + *
  11. Fire a progress event called abort
  12. + *
  13. Fire a progress event called writeend
  14. + *
  15. Terminate this algorithm.
  16. + *
+ */ + abort():void; + + /** + * The blob is being written. + * @readonly + */ + INIT:number; + + /** + * The object has been constructed, but there is no pending write. + * @readonly + */ + WRITING:number; + + /** + * The entire Blob has been written to the file, an error occurred during the write, or the write was aborted using abort(). The FileSaver is no longer writing the blob. + * @readonly + */ + DONE:number; + + /** + * The FileSaver object can be in one of 3 states. The readyState attribute, on getting, must return the current state, which must be one of the following values: + *
    + *
  • INIT
  • + *
  • WRITING
  • + *
  • DONE
  • + *
      + * @readonly + */ + readyState:number; + + /** + * The last error that occurred on the FileSaver. + * @readonly + */ + error:DOMError; + + /** + * Handler for writestart events + */ + onwritestart:Function; + + /** + * Handler for progress events. + */ + onprogress:Function; + + /** + * Handler for write events. + */ + onwrite:Function; + + /** + * Handler for abort events. + */ + onabort:Function; + + /** + * Handler for error events. + */ + onerror:Function; + + /** + * Handler for writeend events. + */ + onwriteend:Function; +} + +var FileSaver: { + /** + * When the FileSaver constructor is called, the user agent must return a new FileSaver object with readyState set to INIT. + * This constructor must be visible when the script's global object is either a Window object or an object implementing the WorkerUtils interface. + */ + new(data:Blob): FileSaver; +} + +/** + * This interface expands on the FileSaver interface to allow for multiple write actions, rather than just saving a single Blob. + */ +interface FileWriter extends FileSaver { + /** + * The byte offset at which the next write to the file will occur. This must be no greater than length. + * A newly-created FileWriter must have position set to 0. + */ + position:number; + + /** + * The length of the file. If the user does not have read access to the file, this must be the highest byte offset at which the user has written. + */ + length:number; + + /** + * Write the supplied data to the file at position. + * @param data The blob to write. + */ + write(data:Blob):void; + + /** + * Seek sets the file position at which the next write will occur. + * @param offset If nonnegative, an absolute byte offset into the file. If negative, an offset back from the end of the file. + */ + seek(offset:number):void; + + /** + * Changes the length of the file to that specified. If shortening the file, data beyond the new length must be discarded. If extending the file, the existing data must be zero-padded up to the new length. + * @param size The size to which the length of the file is to be adjusted, measured in bytes. + */ + truncate(size:number):void; +} + +/** + * This interface lets users write, truncate, and append to files using simple synchronous calls. + * This interface is specified to be used only within Web Workers (WorkerUtils [WEBWORKERS]). + */ +interface FileWriterSync { + /** + * The byte offset at which the next write to the file will occur. This must be no greater than length. + */ + position:number; + + /** + * The length of the file. If the user does not have read access to the file, this must be the highest byte offset at which the user has written. + */ + length:number; + + /** + * Write the supplied data to the file at position. Upon completion, position will increase by data.size. + * @param data The blob to write. + */ + write(data:Blob):void; + + /** + * Seek sets the file position at which the next write will occur. + * @param offset An absolute byte offset into the file. If offset is greater than length, length is used instead. If offset is less than zero, length is added to it, so that it is treated as an offset back from the end of the file. If it is still less than zero, zero is used. + */ + seek(offset:number):void; + + /** + * Changes the length of the file to that specified. If shortening the file, data beyond the new length must be discarded. If extending the file, the existing data must be zero-padded up to the new length. + * Upon successful completion: + *
        + *
      • length must be equal to size.
      • + *
      • position must be the lesser of + *
          + *
        • its pre-truncate value,
        • + *
        • size.
        • + *
        + *
      • + *
      + * @param size The size to which the length of the file is to be adjusted, measured in bytes. + */ + truncate(size:number):void; +} diff --git a/jasmine/jasmine-tests.ts b/jasmine/jasmine-tests.ts index 32339676a6..46507d68de 100644 --- a/jasmine/jasmine-tests.ts +++ b/jasmine/jasmine-tests.ts @@ -60,7 +60,7 @@ describe("Included matchers:", () => { foo: 'foo' }; expect(a.foo).toBeDefined(); - expect(a.bar).not.toBeDefined(); + expect((a).bar).not.toBeDefined(); }); it("The `toBeUndefined` matcher compares against `undefined`", () => { @@ -68,7 +68,7 @@ describe("Included matchers:", () => { foo: 'foo' }; expect(a.foo).not.toBeUndefined(); - expect(a.bar).toBeUndefined(); + expect((a).bar).toBeUndefined(); }); it("The 'toBeNull' matcher compares against null", () => { @@ -120,7 +120,7 @@ describe("Included matchers:", () => { return 1 + 2; }; var bar = () => { - return a + 1; + //return a + 1; }; expect(foo).not.toThrow(); expect(bar).toThrow(); @@ -439,7 +439,7 @@ describe("Asynchronous specs", () => { currentWindowOnload(null); } - document.querySelector('.version').innerHTML = jasmineEnv.versionString(); + (document.querySelector('.version')).innerHTML = jasmineEnv.versionString(); execJasmine(); }; diff --git a/jasmine/jasmine.d.ts b/jasmine/jasmine.d.ts index 0d16559e92..56f41b48f1 100644 --- a/jasmine/jasmine.d.ts +++ b/jasmine/jasmine.d.ts @@ -23,7 +23,6 @@ declare function runs(asyncMethod: Function): void; declare function waitsFor(latchMethod: () => bool, failureMessage: string, timeout?: number): void; declare function waits(timeout?: number): void; - declare module jasmine { var Clock: Clock; @@ -165,6 +164,8 @@ declare module jasmine { toBeLessThan(expected): bool; toBeGreaterThan(expected): bool; toBeCloseTo(expected, precision): bool; + toContainHtml(expected: string): bool; + toContainText(expected: string): bool; toThrow(expected? ): bool; not: Matchers; @@ -291,4 +292,6 @@ declare module jasmine { Clock: Clock; util: Util; } + + export var HtmlReporter: any; } \ No newline at end of file diff --git a/jquery/jquery-tests.ts b/jquery/jquery-tests.ts index 29da1c542d..e69795b92b 100644 --- a/jquery/jquery-tests.ts +++ b/jquery/jquery-tests.ts @@ -2240,3 +2240,15 @@ $('#item').click(function(e) { if (e.ctrlKey) { console.log('control pressed'); } if (e.altKey) { console.log('alt pressed'); } }); + +function test_addBack() { + $('li.third-item').nextAll().addBack().css('background-color', 'red'); + + $("div.left, div.right").find("div, div > p").addClass("border"); + + // First Example + $("div.before-addback").find("p").addClass("background"); + + // Second Example + $("div.after-addback").find("p").addBack().addClass("background"); +} \ No newline at end of file diff --git a/jquery/jquery.d.ts b/jquery/jquery.d.ts index 0924ce6bb6..9cc0fb9cb5 100644 --- a/jquery/jquery.d.ts +++ b/jquery/jquery.d.ts @@ -274,7 +274,10 @@ interface JQueryStatic { *******/ proxy(fn : (...args: any[]) => any, context: any, ...args: any[]): any; proxy(context: any, name: string, ...args: any[]): any; - Deferred(fn? : (d: JQueryDeferred) => any): JQueryDeferred; + Deferred: { + (fn?: (d: JQueryDeferred) => any): JQueryDeferred; + new(fn?: (d: JQueryDeferred) => any): JQueryDeferred; + }; Event(name:string, eventProperties?:any): JQueryEventObject; /********* @@ -371,6 +374,10 @@ interface JQuery { addClass(classNames: string): JQuery; addClass(func: (index: any, currentClass: any) => string): JQuery; + // http://api.jquery.com/addBack/ + addBack(selector?: string): JQuery; + + attr(attributeName: string): string; attr(attributeName: string, value: any): JQuery; attr(map: { [key: string]: any; }): JQuery; diff --git a/knockout/all-tests.ts b/knockout/all-tests.ts new file mode 100644 index 0000000000..3fe7a471f1 --- /dev/null +++ b/knockout/all-tests.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/knockout/knockout.d.ts b/knockout/knockout.d.ts index 58479d846e..aefcc797a4 100644 --- a/knockout/knockout.d.ts +++ b/knockout/knockout.d.ts @@ -81,6 +81,9 @@ interface KnockoutObservableArrayStatic { (): KnockoutObservableArray; (value: any[]): KnockoutObservableArray; + + new(): KnockoutObservableArray; + new(value: any[]): KnockoutObservableArray; } interface KnockoutObservableArray extends KnockoutObservableArrayFunctions { @@ -99,10 +102,17 @@ interface KnockoutObservableStatic { (value: number): KnockoutObservableNumber; (value: bool): KnockoutObservableBool; (value?: any): KnockoutObservableAny; + + new(value: string): KnockoutObservableString; + new(value: Date): KnockoutObservableDate; + new(value: number): KnockoutObservableNumber; + new(value: bool): KnockoutObservableBool; + new(value?: any): KnockoutObservableAny; } /** use as method to get/set the value */ interface KnockoutObservableBase extends KnockoutObservableFunctions { + getSubscriptionsCount(): number; } /** use as method to get/set the value @@ -229,56 +239,184 @@ interface KnockoutExtenders { interface KnockoutUtils { + ////////////////////////////////// + // utils.domManipulation.js + ////////////////////////////////// + + simpleHtmlParse(html: string); + + jQueryHtmlParse(html: string); + + parseHtmlFragment(html: string); + + setHtml(node: Element, html: string): void; + + setHtml(node: Element, html: () => string): void; + + ////////////////////////////////// + // utils.domData.js + ////////////////////////////////// + + domData: { + get (node: Element, key: string); + + set (node: Element, key: string, value: any); + + getAll(node: Element, createIfNotFound: bool); + + clear(node: Element); + }; + + ////////////////////////////////// + // utils.domNodeDisposal.js + ////////////////////////////////// + + domNodeDisposal: { + addDisposeCallback(node: Element, callback: Function); + + removeDisposeCallback(node: Element, callback: Function); + + cleanNode(node: Element); + + removeNode(node: Element); + }; + + ////////////////////////////////// + // utils.js + ////////////////////////////////// + fieldsIncludedWithJsonPost: any[]; arrayForEach(array: any[], action: (any) => void ): void; + arrayIndexOf(array: any[], item: any): number; + arrayFirst(array: any[], predicate: (item) => bool, predicateOwner?: any): any; + arrayRemoveItem(array: any[], itemToRemove: any): void; + arrayGetDistinctValues(array: any[]): any[]; + arrayMap(array: any[], mapping: (item) => any): any[]; + arrayFilter(array: any[], predicate: (item) => bool): any[]; + arrayPushAll(array: any[], valuesToPush: any[]): any[]; extend(target, source); emptyDomNode(domNode): void; + moveCleanedNodesToContainerElement(nodes: any[]): HTMLElement; + cloneNodes(nodesArray: any[], shouldCleanNodes: bool): any[]; + setDomNodeChildren(domNode: any, childNodes: any[]): void; + replaceDomNodes(nodeToReplaceOrNodeArray: any, newNodesArray: any[]): void; + setOptionNodeSelectionState(optionNode: any, isSelected: bool): void; + stringTrim(str: string): string; + stringTokenize(str: string, delimiter: string): string; + stringStartsWith(str: string, startsWith: string): string; + domNodeIsContainedBy(node: any, containedByNode: any): bool; + domNodeIsAttachedToDocument(node: any): bool; + tagNameLower(element: any): string; + registerEventHandler(element: any, eventType: any, handler: Function): void; + triggerEvent(element: any, eventType: any): void; + unwrapObservable(value: any): any; + toggleDomNodeCssClass(node: any, className: string, shouldHaveClass: bool): void; + setTextContent(element: any, textContent: string): void; + setElementName(element: any, name: string): void; + ensureSelectElementIsRenderedCorrectly(selectElement); + forceRefresh(node: any): void; + ensureSelectElementIsRenderedCorrectly(selectElement: any): void; + range(min: any, max: any): any; + makeArray(arrayLikeObject: any): any[]; + getFormFields(form: any, fieldName: string): any[]; + parseJson(jsonString: string): any; + stringifyJson(data: any, replacer: Function, space: string): string; + postJson(urlOrForm: any, data: any, options: any): void; - setHtml(node: Element, html: string): void; - setHtml(node: Element, html: () => string): void; ieVersion: number; - isIe6: bool; - isIe7: bool; - domNodeDisposal; + isIe6: bool; + + isIe7: bool; } +////////////////////////////////// +// templateSources.js +////////////////////////////////// + +interface KnockoutTemplateSourcesDomElement { + + text(valueToWrite?); + + data(key, valueToWrite?); +} + + +interface KnockoutTemplateSources { + + domElement: KnockoutTemplateSourcesDomElement; + + anonymousTemplate: { + + prototype: KnockoutTemplateSourcesDomElement; + + new (element: Element): KnockoutTemplateSourcesDomElement; + }; +}; + +////////////////////////////////// +// nativeTemplateEngine.js +////////////////////////////////// + +interface KnockoutNativeTemplateEngine { + + renderTemplateSource(templateSource, bindingContext, options?); +} + +////////////////////////////////// +// templateEngine.js +////////////////////////////////// + +interface KnockoutTemplateEngine extends KnockoutNativeTemplateEngine { + + createJavaScriptEvaluatorBlock(script: string); + + makeTemplateSource(template, templateDocument); + + renderTemplate(template, bindingContext, options, templateDocument); + + isTemplateRewritten(template, templateDocument): bool; + + rewriteTemplate(template, rewriterCallback, templateDocument); +} + +///////////////////////////////// interface KnockoutStatic { utils: KnockoutUtils; @@ -305,6 +443,84 @@ interface KnockoutStatic { dataFor(node: any): any; removeNode(node: Element); cleanNode(node: Element); + renderTemplate(template: Function, viewModel: any, options?: any, target?: any, renderMode?: any); + renderTemplate(template: string, viewModel: any, options?: any, target?: any, renderMode?: any); + + ////////////////////////////////// + // templateSources.js + ////////////////////////////////// + + templateSources: KnockoutTemplateSources; + + ////////////////////////////////// + // templateEngine.js + ////////////////////////////////// + + templateEngine: { + + prototype: KnockoutTemplateEngine; + + new (): KnockoutTemplateEngine; + }; + + ////////////////////////////////// + // templateRewriting.js + ////////////////////////////////// + + templateRewriting: { + + ensureTemplateIsRewritten(template, templateEngine, templateDocument); + + memoizeBindingAttributeSyntax(htmlString: string, templateEngine: KnockoutTemplateEngine); + + applyMemoizedBindingsToNextSibling(bindings); + }; + + ////////////////////////////////// + // nativeTemplateEngine.js + ////////////////////////////////// + + nativeTemplateEngine: { + + prototype: KnockoutNativeTemplateEngine; + + new (): KnockoutNativeTemplateEngine; + + instance: KnockoutNativeTemplateEngine; + }; + + ////////////////////////////////// + // jqueryTmplTemplateEngine.js + ////////////////////////////////// + + jqueryTmplTemplateEngine: { + + prototype: KnockoutTemplateEngine; + + renderTemplateSource(templateSource, bindingContext, options); + + createJavaScriptEvaluatorBlock(script: string): string; + + addTemplate(templateName, templateMarkup); + }; + + ////////////////////////////////// + // templating.js + ////////////////////////////////// + + setTemplateEngine(templateEngine: KnockoutNativeTemplateEngine); + + renderTemplate(template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode); + + renderTemplateForEach(template, arrayOrObservableArray, options, targetNode, parentBindingContext); + + expressionRewriting: { + bindingRewriteValidators: any; + }; + + ///////////////////////////////// + + bindingProvider: any; } declare var ko: KnockoutStatic; \ No newline at end of file diff --git a/knockout/tests/knockout-templatingBehaviors-tests.ts b/knockout/tests/knockout-templatingBehaviors-tests.ts new file mode 100644 index 0000000000..b000015f2e --- /dev/null +++ b/knockout/tests/knockout-templatingBehaviors-tests.ts @@ -0,0 +1,886 @@ +/// +/// +/// + +declare var $; + +var dummyTemplateEngine = function (templates?) { + var inMemoryTemplates = templates || {}; + var inMemoryTemplateData = {}; + + function dummyTemplateSource(id) { + this.id = id; + } + dummyTemplateSource.prototype = { + text: function(val) { + if (arguments.length >= 1) + inMemoryTemplates[this.id] = val; + return inMemoryTemplates[this.id]; + }, + data: function(key, val) { + if (arguments.length >= 2) { + inMemoryTemplateData[this.id] = inMemoryTemplateData[this.id] || {}; + inMemoryTemplateData[this.id][key] = val; + } + return (inMemoryTemplateData[this.id] || {})[key]; + } + } + + this.makeTemplateSource = function(template) { + if (typeof template == "string") + return new dummyTemplateSource(template); // Named template comes from the in-memory collection + else if ((template.nodeType == 1) || (template.nodeType == 8)) + return new ko.templateSources.anonymousTemplate(template); // Anonymous template + }; + + this.renderTemplateSource = function (templateSource, bindingContext, options) { + var data = bindingContext['$data']; + options = options || {}; + var templateText = templateSource.text(); + if (typeof templateText == "function") + templateText = templateText(data, options); + + templateText = options.showParams ? templateText + ", data=" + data + ", options=" + options : templateText; + var templateOptions = options.templateOptions; // Have templateOptions in scope to support [js:templateOptions.foo] syntax + + var result; + //with (bindingContext) + { + //with (data || {}) + { + //with (options.templateRenderingVariablesInScope || {}) + { + // Dummy [renderTemplate:...] syntax + result = templateText.replace(/\[renderTemplate\:(.*?)\]/g, function (match, templateName) { + return ko.renderTemplate(templateName, data, options); + }); + + + var evalHandler = function (match, script) { + try { + var evalResult = eval(script); + return (evalResult === null) || (evalResult === undefined) ? "" : evalResult.toString(); + } catch (ex) { + throw new Error("Error evaluating script: [js: " + script + "]\n\nException: " + ex.toString()); + } + } + + // Dummy [[js:...]] syntax (in case you need to use square brackets inside the expression) + result = result.replace(/\[\[js\:([\s\S]*?)\]\]/g, evalHandler); + + // Dummy [js:...] syntax + result = result.replace(/\[js\:([\s\S]*?)\]/g, evalHandler); + } + } + } + + // Use same HTML parsing code as real template engine so as to trigger same combination of IE weirdnesses + // Also ensure resulting nodelist is an array to mimic what the default templating engine does, so we see the effects of not being able to remove dead memo comment nodes. + return ko.utils.arrayPushAll([], ko.utils.parseHtmlFragment(result)); + }; + + this.rewriteTemplate = function (template, rewriterCallback) { + // Only rewrite if the template isn't a function (can't rewrite those) + var templateSource = new ko.templateSources.anonymousTemplate(template); //this.makeTemplateSource(template); + if (typeof templateSource.text() != "function") + return ko.templateEngine.prototype.rewriteTemplate.call(this, template, rewriterCallback); + }; + this.createJavaScriptEvaluatorBlock = function (script) { return "[js:" + script + "]"; }; +}; +dummyTemplateEngine.prototype = new ko.templateEngine(); + +describe('Templating', function() { + beforeEach(function() { + ko.setTemplateEngine(new ko.nativeTemplateEngine()); + }); + //beforeEach(jasmine.prepareTestNode); + var testNode: any; + + it('Template engines can return an array of DOM nodes', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ x: [document.createElement("div"), document.createElement("span")] })); + ko.renderTemplate("x", null); + }); + + it('Should not be able to render a template until a template engine is provided', function () { + var threw = false; + ko.setTemplateEngine(undefined); + try { ko.renderTemplate("someTemplate", {}) } + catch (ex) { threw = true } + expect(threw).toEqual(true); + }); + + it('Should be able to render a template into a given DOM element', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "ABC" })); + ko.renderTemplate("someTemplate", null, null, testNode); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("ABC"); + }); + + it('Should be able to render an empty template', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ emptyTemplate: "" })); + ko.renderTemplate("emptyTemplate", null, null, testNode); + expect(testNode.childNodes.length).toEqual(0); + }); + + it('Should be able to access newly rendered/inserted elements in \'afterRender\' callaback', function () { + var passedElement, passedDataItem; + var myCallback = function(elementsArray, dataItem) { + expect(elementsArray.length).toEqual(1); + passedElement = elementsArray[0]; + passedDataItem = dataItem; + } + var myModel = {}; + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "ABC" })); + ko.renderTemplate("someTemplate", myModel, { afterRender: myCallback }, testNode); + expect(passedElement.nodeValue).toEqual("ABC"); + expect(passedDataItem).toEqual(myModel); + }); + + it('Should automatically rerender into DOM element when dependencies change', function () { + var dependency = new ko.observable("A"); + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: function () { + return "Value = " + dependency(); + } + })); + + ko.renderTemplate("someTemplate", null, null, testNode); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("Value = A"); + + dependency("B"); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("Value = B"); + }); + + it('Should not rerender DOM element if observable accessed in \'afterRender\' callaback is changed', function () { + var observable = new ko.observable("A"), count = 0; + var myCallback = function(elementsArray, dataItem) { + observable(); // access observable in callback + }; + var myTemplate = function() { + return "Value = " + (++count); + }; + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: myTemplate })); + ko.renderTemplate("someTemplate", {}, { afterRender: myCallback }, testNode); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("Value = 1"); + + observable("B"); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("Value = 1"); + }); + + it('If the supplied data item is observable, evaluates it and has subscription on it', function () { + var observable = new ko.observable("A"); + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: function (data) { + return "Value = " + data; + } + })); + ko.renderTemplate("someTemplate", observable, null, testNode); + expect(testNode.innerHTML).toEqual("Value = A"); + + observable("B"); + expect(testNode.innerHTML).toEqual("Value = B"); + }); + + it('Should stop updating DOM nodes when the dependency next changes if the DOM node has been removed from the document', function () { + var dependency = new ko.observable("A"); + var template = { someTemplate: function () { return "Value = " + dependency() } }; + ko.setTemplateEngine(new dummyTemplateEngine(template)); + + ko.renderTemplate("someTemplate", null, null, testNode); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("Value = A"); + + testNode.parentNode.removeChild(testNode); + dependency("B"); + expect(testNode.childNodes.length).toEqual(1); + expect(testNode.innerHTML).toEqual("Value = A"); + }); + + it('Should be able to render a template using data-bind syntax', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "template output" })); + testNode.innerHTML = "
      "; + ko.applyBindings(null, testNode); + expect(testNode.childNodes[0].innerHTML).toEqual("template output"); + }); + + it('Should be able to tell data-bind syntax which object to pass as data for the template (otherwise, uses viewModel)', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "result = [js: childProp]" })); + testNode.innerHTML = "
      "; + ko.applyBindings({ someProp: { childProp: 123} }, testNode); + expect(testNode.childNodes[0].innerHTML).toEqual("result = 123"); + }); + + it('Should re-render a named template when its data item notifies about mutation', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "result = [js: childProp]" })); + testNode.innerHTML = "
      "; + + var myData = ko.observable({ childProp: 123 }); + ko.applyBindings({ someProp: myData }, testNode); + expect(testNode.childNodes[0].innerHTML).toEqual("result = 123"); + + // Now mutate and notify + myData().childProp = 456; + myData.valueHasMutated(); + expect(testNode.childNodes[0].innerHTML).toEqual("result = 456"); + }); + + it('Should stop tracking inner observables immediately when the container node is removed from the document', function() { + var innerObservable = ko.observable("some value"); + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "result = [js: childProp()]" })); + testNode.innerHTML = "
      "; + ko.applyBindings({ someProp: { childProp: innerObservable} }, testNode); + + expect(innerObservable.getSubscriptionsCount()).toEqual(1); + ko.removeNode(testNode.childNodes[0]); + expect(innerObservable.getSubscriptionsCount()).toEqual(0); + }); + + it('Should be able to pick template via an observable model property', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ + firstTemplate: "First template output", + secondTemplate: "Second template output" + })); + + var chosenTemplate = ko.observable("firstTemplate"); + testNode.innerHTML = "
      "; + ko.applyBindings({ chosenTemplate: chosenTemplate }, testNode); + expect(testNode.childNodes[0].innerHTML).toEqual("First template output"); + + chosenTemplate("secondTemplate"); + expect(testNode.childNodes[0].innerHTML).toEqual("Second template output"); + }); + + it('Should be able to pick template as a function of the data item using data-bind syntax, with the binding context available as a second parameter', function () { + var templatePicker = function(dataItem, bindingContext) { + // Having the entire binding context available means you can read sibling or parent level properties + expect(bindingContext.$parent.anotherProperty).toEqual(456); + return dataItem.myTemplate; + }; + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "result = [js: childProp]" })); + testNode.innerHTML = "
      "; + ko.applyBindings({ someProp: { childProp: 123, myTemplate: "someTemplate" }, templateSelectorFunction: templatePicker, anotherProperty: 456 }, testNode); + expect(testNode.childNodes[0].innerHTML).toEqual("result = 123"); + }); + + it('Should be able to chain templates, rendering one from inside another', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: "outer template output, [renderTemplate:innerTemplate]", // [renderTemplate:...] is special syntax supported by dummy template engine + innerTemplate: "inner template output " + })); + testNode.innerHTML = "
      "; + ko.applyBindings(null, testNode); + expect(testNode.childNodes[0]).toContainHtml("outer template output, inner template output 123"); + }); + + it('Should rerender chained templates when their dependencies change, without rerendering parent templates', function () { + var observable = new ko.observable("ABC"); + var timesRenderedOuter = 0, timesRenderedInner = 0; + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: function () { timesRenderedOuter++; return "outer template output, [renderTemplate:innerTemplate]" }, // [renderTemplate:...] is special syntax supported by dummy template engine + innerTemplate: function () { timesRenderedInner++; return observable() } + })); + testNode.innerHTML = "
      "; + ko.applyBindings(null, testNode); + expect(testNode.childNodes[0]).toContainHtml("outer template output, abc"); + expect(timesRenderedOuter).toEqual(1); + expect(timesRenderedInner).toEqual(1); + + observable("DEF"); + expect(testNode.childNodes[0]).toContainHtml("outer template output, def"); + expect(timesRenderedOuter).toEqual(1); + expect(timesRenderedInner).toEqual(2); + }); + + it('Should stop tracking inner observables referenced by a chained template as soon as the chained template output node is removed from the document', function() { + var innerObservable = ko.observable("some value"); + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: "outer template output, [renderTemplate:innerTemplate]", + innerTemplate: "result = [js: childProp()]" + })); + testNode.innerHTML = "
      "; + ko.applyBindings({ someProp: { childProp: innerObservable} }, testNode); + + expect(innerObservable.getSubscriptionsCount()).toEqual(1); + ko.removeNode(document.getElementById('innerTemplateOutput')); + expect(innerObservable.getSubscriptionsCount()).toEqual(0); + }); + + it('Should handle data-bind attributes from inside templates, regardless of element and attribute casing', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "" })); + ko.renderTemplate("someTemplate", null, null, testNode); + expect(testNode.childNodes[0].value).toEqual("Hi"); + }); + + it('Should handle data-bind attributes that include newlines from inside templates', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "" })); + ko.renderTemplate("someTemplate", null, null, testNode); + expect(testNode.childNodes[0].value).toEqual("Hi"); + }); + + it('Data binding syntax should be able to reference variables put into scope by the template engine', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "" })); + ko.renderTemplate("someTemplate", null, { templateRenderingVariablesInScope: { message: "hello"} }, testNode); + expect(testNode.childNodes[0].value).toEqual("hello"); + }); + + it('Data binding syntax should be able to use $element in binding value', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "
      " })); + ko.renderTemplate("someTemplate", null, null, testNode); + expect(testNode.childNodes[0]).toContainText("DIV"); + }); + + it('Data binding syntax should be able to use $context in binding value to refer to the context object', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "
      " })); + ko.renderTemplate("someTemplate", {}, null, testNode); + expect(testNode.childNodes[0]).toContainText("true"); + }); + + it('Data binding syntax should defer evaluation of variables until the end of template rendering (so bindings can take independent subscriptions to them)', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ + someTemplate: "[js: message = 'goodbye'; undefined; ]" + })); + ko.renderTemplate("someTemplate", null, { templateRenderingVariablesInScope: { message: "hello"} }, testNode); + expect(testNode.childNodes[0].value).toEqual("goodbye"); + }); + + it('Data binding syntax should use the template\'s \'data\' object as the viewModel value (so \'this\' is set correctly when calling click handlers etc.)', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ + someTemplate: "" + })); + var viewModel = { + didCallMyFunction : false, + someFunctionOnModel : function() { this.didCallMyFunction = true } + }; + ko.renderTemplate("someTemplate", viewModel, null, testNode); + var buttonNode = testNode.childNodes[0]; + expect(buttonNode.tagName).toEqual("BUTTON"); // Be sure we're clicking the right thing + buttonNode.click(); + expect(viewModel.didCallMyFunction).toEqual(true); + }); + + it('Data binding syntax should permit nested templates, and only bind inner templates once', function() { + // Will verify that bindings are applied only once for both inline (rewritten) bindings, + // and external (non-rewritten) ones + var originalBindingProvider = ko.bindingProvider.instance; + ko.bindingProvider.instance = { + nodeHasBindings: function(node, bindingContext) { + return (node.tagName == 'EM') || originalBindingProvider.nodeHasBindings(node, bindingContext); + }, + getBindings: function(node, bindingContext) { + if (node.tagName == 'EM') + return { text: ++model.numBindings }; + return originalBindingProvider.getBindings(node, bindingContext); + } + }; + + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: "Outer
      ", + innerTemplate: "Inner via inline binding: " + + "Inner via external binding: " + })); + var model = { numBindings: 0 }; + testNode.innerHTML = "
      "; + ko.applyBindings(model, testNode); + expect(model.numBindings).toEqual(2); + expect(testNode.childNodes[0]).toContainHtml("outer
      inner via inline binding: 2inner via external binding: 1
      "); + + ko.bindingProvider.instance = originalBindingProvider; + }); + + it('Data binding syntax should support \'foreach\' option, whereby it renders for each item in an array but doesn\'t rerender everything if you push or splice', function () { + var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: "Frank"}]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "
      The item is [js: personName]
      " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      the item is bob
      the item is frank
      "); + var originalBobNode = testNode.childNodes[0].childNodes[0]; + var originalFrankNode = testNode.childNodes[0].childNodes[1]; + + myArray.push({ personName: "Steve" }); + expect(testNode.childNodes[0]).toContainHtml("
      the item is bob
      the item is frank
      the item is steve
      "); + expect(testNode.childNodes[0].childNodes[0]).toEqual(originalBobNode); + expect(testNode.childNodes[0].childNodes[1]).toEqual(originalFrankNode); + }); + + it('Data binding \'foreach\' option should apply bindings within the context of each item in the array', function () { + var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: "Frank"}]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item is " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("the item is bobthe item is frank"); + }); + + it('Data binding \'foreach\' options should only bind each group of output nodes once', function() { + var initCalls = 0; + (ko.bindingHandlers).countInits = { init: function() { initCalls++ } }; + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "" })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: [1,2,3] }, testNode); + expect(initCalls).toEqual(3); // 3 because there were 3 items in myCollection + }); + + it('Data binding \'foreach\' should handle templates in which the very first node has a binding', function() { + // Represents https://github.com/SteveSanderson/knockout/pull/440 + // Previously, the rewriting (which introduces a comment node before the bound node) was interfering + // with the array-to-DOM-node mapping state tracking + ko.setTemplateEngine(new dummyTemplateEngine({ mytemplate: "
      " })); + testNode.innerHTML = "
      "; + + // Bind against initial array containing one entry. UI just shows "original" + var myArray = ko.observableArray(["original"]); + ko.applyBindings({ items: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      original
      "); + + // Now replace the entire array contents with one different entry. + // UI just shows "new" (previously with bug, showed "original" AND "new") + myArray(["new"]); + expect(testNode.childNodes[0]).toContainHtml("
      new
      "); + }); + + it('Data binding \'foreach\' should handle chained templates in which the very first node has a binding', function() { + // See https://github.com/SteveSanderson/knockout/pull/440 and https://github.com/SteveSanderson/knockout/pull/144 + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: "
      [renderTemplate:innerTemplate]x", // [renderTemplate:...] is special syntax supported by dummy template engine + innerTemplate: "inner " + })); + testNode.innerHTML = "
      "; + + // Bind against initial array containing one entry. + var myArray = ko.observableArray(["original"]); + ko.applyBindings({ items: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      original
      inner 123x"); + + // Now replace the entire array contents with one different entry. + myArray(["new"]); + expect(testNode.childNodes[0]).toContainHtml("
      new
      inner 123x"); + }); + + it('Data binding \'foreach\' should handle templates in which the very first node has a binding but it does not reference any observables', function() { + // Represents https://github.com/SteveSanderson/knockout/issues/739 + // Previously, the rewriting (which introduces a comment node before the bound node) was interfering + // with the array-to-DOM-node mapping state tracking + ko.setTemplateEngine(new dummyTemplateEngine({ mytemplate: "
      [js:name()]
      " })); + testNode.innerHTML = "
      "; + + // Bind against array, referencing an observable property + var myItem = { name: ko.observable("a") }; + ko.applyBindings({ items: [myItem] }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      a
      "); + + // Modify the observable property and check that UI is updated + // Previously with the bug, it wasn't updated because the removal of the memo comment caused the array-to-DOM-node computed to be disposed + myItem.name("b"); + expect(testNode.childNodes[0]).toContainHtml("
      b
      "); + }); + + it('Data binding \'foreach\' option should apply bindings with an $index in the context', function () { + var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: "Frank"}]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item # is " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("the item # is 0the item # is 1"); + }); + + it('Data binding \'foreach\' option should update bindings that reference an $index if the list changes', function () { + var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: "Frank"}]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item is " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("the item bobis 0the item frankis 1"); + + var frank = myArray.pop(); // remove frank + expect(testNode.childNodes[0]).toContainHtml("the item bobis 0"); + + myArray.unshift(frank); // put frank in the front + expect(testNode.childNodes[0]).toContainHtml("the item frankis 0the item bobis 1"); + + }); + + it('Data binding \'foreach\' option should accept array with "undefined" and "null" items', function () { + var myArray = new ko.observableArray([undefined, null]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item is " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("the item is undefinedthe item is null"); + }); + + it('Data binding \'foreach\' option should update DOM nodes when a dependency of their mapping function changes', function() { + var myObservable = new ko.observable("Steve"); + var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: myObservable }, { personName: "Another" }]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "
      The item is [js: ko.utils.unwrapObservable(personName)]
      " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      the item is bob
      the item is steve
      the item is another
      "); + var originalBobNode = testNode.childNodes[0].childNodes[0]; + + myObservable("Steve2"); + expect(testNode.childNodes[0]).toContainHtml("
      the item is bob
      the item is steve2
      the item is another
      "); + expect(testNode.childNodes[0].childNodes[0]).toEqual(originalBobNode); + + // Ensure we can still remove the corresponding nodes (even though they've changed), and that doing so causes the subscription to be disposed + expect(myObservable.getSubscriptionsCount()).toEqual(1); + myArray.splice(1, 1); + expect(testNode.childNodes[0]).toContainHtml("
      the item is bob
      the item is another
      "); + myObservable("Something else"); // Re-evaluating the observable causes the orphaned subscriptions to be disposed + expect(myObservable.getSubscriptionsCount()).toEqual(0); + }); + + it('Data binding \'foreach\' option should treat a null parameter as meaning \'no items\'', function() { + var myArray = new ko.observableArray(["A", "B"]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "hello" })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0].childNodes.length).toEqual(2); + + // Now set the observable to null and check it's treated like an empty array + // (because how else should null be interpreted?) + myArray(null); + expect(testNode.childNodes[0].childNodes.length).toEqual(0); + }); + + it('Data binding \'foreach\' option should accept an \"as\" option to define an alias for the iteration variable', function() { + // Note: There are more detailed specs (e.g., covering nesting) associated with the "foreach" binding which + // uses this templating functionality internally. + var myArray = new ko.observableArray(["A", "B"]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "[js:myAliasedItem]" })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainText("AB"); + }); + + it('Data binding \'foreach\' option should stop tracking inner observables when the container node is removed', function() { + var innerObservable = ko.observable("some value"); + var myArray = new ko.observableArray([{obsVal:innerObservable}, {obsVal:innerObservable}]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item is [js: ko.utils.unwrapObservable(obsVal)]" })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(innerObservable.getSubscriptionsCount()).toEqual(2); + + ko.removeNode(testNode.childNodes[0]); + expect(innerObservable.getSubscriptionsCount()).toEqual(0); + }); + + it('Data binding \'foreach\' option should stop tracking inner observables related to each array item when that array item is removed', function() { + var innerObservable = ko.observable("some value"); + var myArray = new ko.observableArray([{obsVal:innerObservable}, {obsVal:innerObservable}]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item is [js: ko.utils.unwrapObservable(obsVal)]" })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(innerObservable.getSubscriptionsCount()).toEqual(2); + + myArray.splice(1, 1); + expect(innerObservable.getSubscriptionsCount()).toEqual(1); + myArray([]); + expect(innerObservable.getSubscriptionsCount()).toEqual(0); + }); + + it('Data binding syntax should omit any items whose \'_destroy\' flag is set (unwrapping the flag if it is observable)', function() { + var myArray = new ko.observableArray([{ someProp: 1 }, { someProp: 2, _destroy: 'evals to true' }, { someProp : 3 }, { someProp: 4, _destroy: ko.observable(false) }]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "
      someProp=[js: someProp]
      " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      someprop=1
      someprop=3
      someprop=4
      "); + }); + + it('Data binding syntax should include any items whose \'_destroy\' flag is set if you use includeDestroyed', function() { + var myArray = new ko.observableArray([{ someProp: 1 }, { someProp: 2, _destroy: 'evals to true' }, { someProp : 3 }]); + ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "
      someProp=[js: someProp]
      " })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ myCollection: myArray }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      someprop=1
      someprop=2
      someprop=3
      "); + }); + + it('Data binding syntax should support \"if\" condition', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ myTemplate: "Value: [js: myProp().childProp]" })); + testNode.innerHTML = "
      "; + + var viewModel = { myProp: ko.observable({ childProp: 'abc' }) }; + ko.applyBindings(viewModel, testNode); + + // Initially there is a value + expect(testNode.childNodes[0]).toContainText("Value: abc"); + + // Causing the condition to become false causes the output to be removed + viewModel.myProp(null); + expect(testNode.childNodes[0]).toContainText(""); + + // Causing the condition to become true causes the output to reappear + viewModel.myProp({ childProp: 'def' }); + expect(testNode.childNodes[0]).toContainText("Value: def"); + }); + + it('Data binding syntax should support \"ifnot\" condition', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ myTemplate: "Hello" })); + testNode.innerHTML = "
      "; + + var viewModel = { shouldHide: ko.observable(true) }; + ko.applyBindings(viewModel, testNode); + + // Initially there is no output (shouldHide=true) + expect(testNode.childNodes[0]).toContainText(""); + + // Causing the condition to become false causes the output to be displayed + viewModel.shouldHide(false); + expect(testNode.childNodes[0]).toContainText("Hello"); + + // Causing the condition to become true causes the output to disappear + viewModel.shouldHide(true); + expect(testNode.childNodes[0]).toContainText(""); + }); + + it('Data binding syntax should support \"if\" condition in conjunction with foreach', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ myTemplate: "Value: [js: myProp().childProp]" })); + testNode.innerHTML = "
      "; + + var viewModel = { myProp: ko.observable({ childProp: 'abc' }) }; + ko.applyBindings(viewModel, testNode); + expect(testNode.childNodes[0].childNodes[0].nodeValue).toEqual("Value: abc"); + expect(testNode.childNodes[0].childNodes[1].nodeValue).toEqual("Value: abc"); + expect(testNode.childNodes[0].childNodes[2].nodeValue).toEqual("Value: abc"); + + // Causing the condition to become false causes the output to be removed + viewModel.myProp(null); + expect(testNode.childNodes[0]).toContainText(""); + + // Causing the condition to become true causes the output to reappear + viewModel.myProp({ childProp: 'def' }); + expect(testNode.childNodes[0].childNodes[0].nodeValue).toEqual("Value: def"); + expect(testNode.childNodes[0].childNodes[1].nodeValue).toEqual("Value: def"); + expect(testNode.childNodes[0].childNodes[2].nodeValue).toEqual("Value: def"); + }); + + it('Should be able to populate checkboxes from inside templates, despite IE6 limitations', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "" })); + ko.renderTemplate("someTemplate", null, { templateRenderingVariablesInScope: { isChecked: true } }, testNode); + expect(testNode.childNodes[0].checked).toEqual(true); + }); + + it('Should be able to populate radio buttons from inside templates, despite IE6 limitations', function () { + ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "" })); + ko.renderTemplate("someTemplate", null, { templateRenderingVariablesInScope: { someValue: 'abc' } }, testNode); + expect(testNode.childNodes[0].checked).toEqual(true); + }); + + it('Should be able to render a different template for each array entry by passing a function as template name, with the array entry\'s binding context available as a second parameter', function() { + var myArray = new ko.observableArray([ + { preferredTemplate: 1, someProperty: 'firstItemValue' }, + { preferredTemplate: 2, someProperty: 'secondItemValue' } + ]); + ko.setTemplateEngine(new dummyTemplateEngine({ + firstTemplate: "
      Template1Output, [js:someProperty]
      ", + secondTemplate: "
      Template2Output, [js:someProperty]
      " + })); + testNode.innerHTML = "
      "; + + var getTemplate = function(dataItem, bindingContext) { + // Having the item's binding context available means you can read sibling or parent level properties + expect(bindingContext.$parent.anotherProperty).toEqual(123); + + return dataItem.preferredTemplate == 1 ? 'firstTemplate' : 'secondTemplate'; + }; + ko.applyBindings({ myCollection: myArray, getTemplateModelProperty: getTemplate, anotherProperty: 123 }, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      template1output, firstitemvalue
      template2output, seconditemvalue
      "); + }); + + it('Data binding \'templateOptions\' should be passed to template', function() { + var myModel = { + someAdditionalData: { myAdditionalProp: "someAdditionalValue" }, + people: new ko.observableArray([ + { name: "Alpha" }, + { name: "Beta" } + ]) + }; + ko.setTemplateEngine(new dummyTemplateEngine({myTemplate: "
      Person [js:name] has additional property [js:templateOptions.myAdditionalProp]
      "})); + testNode.innerHTML = "
      "; + + ko.applyBindings(myModel, testNode); + expect(testNode.childNodes[0]).toContainHtml("
      person alpha has additional property someadditionalvalue
      person beta has additional property someadditionalvalue
      "); + }); + + it('If the template binding is updated, should dispose any template subscriptions previously associated with the element', function() { + var myObservable = ko.observable("some value"), + myModel = { + subModel: ko.observable({ myObservable: myObservable }) + }; + ko.setTemplateEngine(new dummyTemplateEngine({myTemplate: "The value is [js:myObservable()]"})); + testNode.innerHTML = "
      "; + ko.applyBindings(myModel, testNode); + + // Right now the template references myObservable, so there should be exactly one subscription on it + expect(testNode.childNodes[0]).toContainText("The value is some value"); + expect(myObservable.getSubscriptionsCount()).toEqual(1); + var renderedNode1 = testNode.childNodes[0].childNodes[0]; + + // By changing the object for subModel, we force the data-bind value to be re-evaluated and the template to be re-rendered, + // setting up a new template subscription, so there have now existed two subscriptions on myObservable... + myModel.subModel({ myObservable: myObservable }); + expect(testNode.childNodes[0].childNodes[0]).not.toEqual(renderedNode1); + + // ...but, because the old subscription should have been disposed automatically, there should only be one left + expect(myObservable.getSubscriptionsCount()).toEqual(1); + }); + + it('Should be able to specify a template engine instance using data-bind syntax', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ theTemplate: "Default output" })); // Not going to use this one + var alternativeTemplateEngine = new dummyTemplateEngine({ theTemplate: "Alternative output" }); + + testNode.innerHTML = "
      "; + ko.applyBindings({ chosenEngine: alternativeTemplateEngine }, testNode); + + expect(testNode.childNodes[0]).toContainText("Alternative output"); + }); + + it('Should be able to bind $data to an alias using \'as\'', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ + myTemplate: "ValueLiteral: [js:item.prop], ValueBound: " + })); + testNode.innerHTML = "
      "; + ko.applyBindings({ someItem: { prop: 'Hello' } }, testNode); + expect(testNode.childNodes[0]).toContainText("ValueLiteral: Hello, ValueBound: Hello"); + }); + + it('Data-bind syntax should expose parent binding context as $parent if binding with an explicit \"data\" value', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ + myTemplate: "ValueLiteral: [js:$parent.parentProp], ValueBound: " + })); + testNode.innerHTML = "
      "; + ko.applyBindings({ someItem: {}, parentProp: 'Hello' }, testNode); + expect(testNode.childNodes[0]).toContainText("ValueLiteral: Hello, ValueBound: Hello"); + }); + + it('Data-bind syntax should expose all ancestor binding contexts as $parents', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: "
      ", + middleTemplate: "
      ", + innerTemplate: "(Data:[js:$data.val], Parent:[[js:$parents[0].val]], Grandparent:[[js:$parents[1].val]], Root:[js:$root.val], Depth:[js:$parents.length])" + })); + testNode.innerHTML = "
      "; + + ko.applyBindings({ + val: "ROOT", + outerItem: { + val: "OUTER", + middleItem: { + val: "MIDDLE", + innerItem: { val: "INNER" } + } + } + }, testNode); + expect(testNode.childNodes[0].childNodes[0]).toContainText("(Data:INNER, Parent:MIDDLE, Grandparent:OUTER, Root:ROOT, Depth:3)"); + }); + + it('Should not be allowed to rewrite templates that embed anonymous templates', function() { + // The reason is that your template engine's native control flow and variable evaluation logic is going to run first, independently + // of any KO-native control flow, so variables would get evaluated in the wrong context. Example: + // + //
      + // ${ somePropertyOfEachArrayItem } <-- This gets evaluated *before* the foreach binds, so it can't reference array entries + //
      + // + // It should be perfectly OK to fix this just by preventing anonymous templates within rewritten templates, because + // (1) The developer can always use their template engine's native control flow syntax instead of the KO-native ones - that will work + // (2) The developer can use KO's native templating instead, if they are keen on KO-native control flow or anonymous templates + + ko.setTemplateEngine(new dummyTemplateEngine({ + myTemplate: "
      Childprop: [js: childProp]
      " + })); + testNode.innerHTML = "
      "; + + var didThrow = false; + try { + ko.applyBindings({ someData: { childProp: 'abc' } }, testNode); + } catch(ex) { + didThrow = true; + expect(ex.message).toEqual("This template engine does not support anonymous templates nested within its templates"); + } + expect(didThrow).toEqual(true); + }); + + it('Should not be allowed to rewrite templates that embed control flow bindings', function() { + // Same reason as above + ko.utils.arrayForEach(['if', 'ifnot', 'with', 'foreach'], function(bindingName) { + ko.setTemplateEngine(new dummyTemplateEngine({ myTemplate: "
      Hello
      " })); + testNode.innerHTML = "
      "; + + var didThrow = false; + ko.utils.domData.clear(testNode); + try { ko.applyBindings({ someData: { childProp: 'abc' } }, testNode) } + catch (ex) { + didThrow = true; + expect(ex.message).toEqual("This template engine does not support the '" + bindingName + "' binding within its templates"); + } + if (!didThrow) + throw new Error("Did not prevent use of " + bindingName); + }); + }); + + it('Data binding syntax should permit nested templates using virtual containers (with arbitrary internal whitespace and newlines)', function() { + ko.setTemplateEngine(new dummyTemplateEngine({ + outerTemplate: "Outer ", + innerTemplate: "Inner via inline binding: " + })); + var model = { }; + testNode.innerHTML = "
      "; + ko.applyBindings(model, testNode); + expect(testNode.childNodes[0]).toContainHtml("outer inner via inline binding: sometext"); + }); + + it('Should be able to render anonymous templates using virtual containers', function() { + ko.setTemplateEngine(new dummyTemplateEngine()); + testNode.innerHTML = "Start Childprop: [js: childProp] End"; + ko.applyBindings({ someData: { childProp: 'abc' } }, testNode); + expect(testNode).toContainHtml("start childprop: abcend"); + }); + + it('Should be able to use anonymous templates that contain first-child comment nodes', function() { + // This represents issue https://github.com/SteveSanderson/knockout/issues/188 + // (IE < 9 strips out leading comment nodes when you use .innerHTML) + ko.setTemplateEngine(new dummyTemplateEngine({})); + testNode.innerHTML = "start
      hello
      "; + ko.applyBindings(null, testNode); + expect(testNode).toContainHtml('start
      hellohello
      '); + }); + + it('Should allow anonymous templates output to include top-level virtual elements, and will bind their virtual children only once', function() { + delete (ko.bindingHandlers).nonexistentHandler; + var initCalls = 0; + (ko.bindingHandlers).countInits = { init: function () { initCalls++ } }; + testNode.innerHTML = "
      "; + ko.applyBindings(null, testNode); + expect(initCalls).toEqual(1); + }); + + it('Should not throw errors if trying to apply text to a non-rendered node', function() { + // Represents https://github.com/SteveSanderson/knockout/issues/660 + // A can't go directly into a , so modern browsers will silently strip it. We need to verify this doesn't + // throw errors during unmemoization (when unmemoizing, it will try to apply the text to the following text node + // instead of the node you intended to bind to). + // Note that IE < 9 won't strip the ; instead it has much stranger behaviors regarding unexpected DOM structures. + // It just happens not to give an error in this particular case, though it would throw errors in many other cases + // of malformed template DOM. + ko.setTemplateEngine(new dummyTemplateEngine({ + myTemplate: " " // The whitespace after the closing span is what triggers the strange HTML parsing + })); + testNode.innerHTML = "
      "; + ko.applyBindings(null, testNode); + // Since the actual template markup was invalid, we don't really care what the + // resulting DOM looks like. We are only verifying there were no exceptions. + }); +}) \ No newline at end of file diff --git a/knockout/knockout-tests.ts b/knockout/tests/knockout-tests.ts similarity index 96% rename from knockout/knockout-tests.ts rename to knockout/tests/knockout-tests.ts index d669af816e..97a4c8776f 100644 --- a/knockout/knockout-tests.ts +++ b/knockout/tests/knockout-tests.ts @@ -1,576 +1,574 @@ -/// -/// - -declare var $; - -function test_creatingVMs() { - var myViewModel = { - personName: ko.observable('Bob'), - personAge: ko.observable(123) - }; - ko.applyBindings(myViewModel); - ko.applyBindings(myViewModel, document.getElementById('someElementId')); - - myViewModel.personName(); - myViewModel.personName('Mary'); - myViewModel.personAge(50); - - myViewModel.personName.subscribe(function (newValue) { - alert("The person's new name is " + newValue); - }); - - var subscription = myViewModel.personName.subscribe(function (newValue) { }); - subscription.dispose(); -} - -function test_computed() { - function AppViewModel() { - var self = this; - - self.firstName = ko.observable('Bob'); - self.lastName = ko.observable('Smith'); - self.fullName = ko.computed(function () { - return self.firstName() + " " + self.lastName(); - }); - } - - function MyViewModel() { - this.firstName = ko.observable('Planet'); - this.lastName = ko.observable('Earth'); - - this.fullName = ko.computed({ - read: function () { - return this.firstName() + " " + this.lastName(); - }, - write: function (value) { - var lastSpacePos = value.lastIndexOf(" "); - if (lastSpacePos > 0) { - this.firstName(value.substring(0, lastSpacePos)); - this.lastName(value.substring(lastSpacePos + 1)); - } - }, - owner: this - }); - } - - function MyViewModel() { - this.price = ko.observable(25.99); - - this.formattedPrice = ko.computed({ - read: function () { - return '$' + this.price().toFixed(2); - }, - write: function (value) { - value = parseFloat(value.replace(/[^\.\d]/g, "")); - this.price(isNaN(value) ? 0 : value); - }, - owner: this - }); - } - - function MyViewModel() { - this.acceptedNumericValue = ko.observable(123); - this.lastInputWasValid = ko.observable(true); - - this.attemptedValue = ko.computed({ - read: this.acceptedNumericValue, - write: function (value) { - if (isNaN(value)) - this.lastInputWasValid(false); - else { - this.lastInputWasValid(true); - this.acceptedNumericValue(value); - } - }, - owner: this - }); - } - - ko.applyBindings(new MyViewModel()); -} - -class GetterViewModel { - private _selectedRange: KnockoutObservableAny; - - constructor() { - this._selectedRange = ko.observable(); - } - - public get range() : KnockoutObservableAny { - return this._selectedRange; - } -} - -function testGetter() { - var model = new GetterViewModel(); - - model.range.subscribe((range: number) => { - }); -} - -function test_observableArrays() { - var myObservableArray = ko.observableArray(); - myObservableArray.push('Some value'); - var anotherObservableArray = ko.observableArray([ - { name: "Bungle", type: "Bear" }, - { name: "George", type: "Hippo" }, - { name: "Zippy", type: "Unknown" } - ]); - - myObservableArray().length; - myObservableArray()[0]; - - myObservableArray.indexOf('Blah'); - myObservableArray.push('Some new value'); - myObservableArray.pop(); - myObservableArray.unshift('Some new value'); - myObservableArray.shift(); - myObservableArray.reverse(); - myObservableArray.sort(function (left, right) { return left.lastName == right.lastName ? 0 : (left.lastName < right.lastName ? -1 : 1) }); - myObservableArray.splice(1, 3); - - myObservableArray.remove('Blah'); - myObservableArray.remove(function (item) { return item.age < 18 }); - myObservableArray.removeAll(['Chad', 132, undefined]); - myObservableArray.removeAll(); - myObservableArray.destroy('Blah'); - myObservableArray.destroy(function (someItem) { return someItem.age < 18 }); - myObservableArray.destroyAll(['Chad', 132, undefined]); - myObservableArray.destroyAll(); - - ko.utils.arrayForEach(myObservableArray(), function (item) { }); -} - -// You have to extend knockout for your own handlers -interface KnockoutBindingHandlers { - yourBindingName: KnockoutBindingHandler; - slideVisible: KnockoutBindingHandler; - hasFocus: KnockoutBindingHandler; - allowBindings: KnockoutBindingHandler; - withProperties: KnockoutBindingHandler; - randomOrder: KnockoutBindingHandler; -} - -function test_bindings() { - var currentProfit = ko.observable(150000); - ko.applyBindings({ - people: [ - { firstName: 'Bert', lastName: 'Bertington' }, - { firstName: 'Charles', lastName: 'Charlesforth' }, - { firstName: 'Denise', lastName: 'Dentiste' } - ] - }); - var viewModel = { availableCountries: ko.observableArray(['France', 'Germany', 'Spain']) }; - viewModel.availableCountries.push('China'); - - ko.bindingHandlers.yourBindingName = { - init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - }, - update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - } - }; - ko.bindingHandlers.slideVisible = { - update: function (element, valueAccessor, allBindingsAccessor) { - var value = valueAccessor(), allBindings = allBindingsAccessor(); - var valueUnwrapped = ko.utils.unwrapObservable(value); - var duration = allBindings.slideDuration || 400; - if (valueUnwrapped == true) - $(element).slideDown(duration); - else - $(element).slideUp(duration); - }, - init: function (element, valueAccessor) { - var value = ko.utils.unwrapObservable(valueAccessor()); - $(element).toggle(value); - }, - update: function (element, valueAccessor, allBindingsAccessor) { - } - }; - ko.bindingHandlers.hasFocus = { - init: function (element, valueAccessor) { - $(element).focus(function () { - var value = valueAccessor(); - value(true); - }); - $(element).blur(function () { - var value = valueAccessor(); - value(false); - }); - }, - update: function (element, valueAccessor) { - var value = valueAccessor(); - if (ko.utils.unwrapObservable(value)) - element.focus(); - else - element.blur(); - } - }; - ko.bindingHandlers.allowBindings = { - init: function (elem, valueAccessor) { - var shouldAllowBindings = ko.utils.unwrapObservable(valueAccessor()); - return { controlsDescendantBindings: !shouldAllowBindings }; - } - }; - ko.bindingHandlers.withProperties = { - init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - var newProperties = valueAccessor(), - innerBindingContext = bindingContext.extend(newProperties); - ko.applyBindingsToDescendants(innerBindingContext, element); - return { controlsDescendantBindings: true }; - } - }; - ko.bindingHandlers.withProperties = { - init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - var newProperties = valueAccessor(), - childBindingContext = bindingContext.createChildContext(viewModel); - ko.utils.extend(childBindingContext, newProperties); - ko.applyBindingsToDescendants(childBindingContext, element); - return { controlsDescendantBindings: true }; - } - }; - ko.bindingHandlers.randomOrder = { - init: function (elem, valueAccessor) { - var child = ko.virtualElements.firstChild(elem), - childElems = []; - while (child) { - childElems.push(child); - child = ko.virtualElements.nextSibling(child); - } - ko.virtualElements.emptyNode(elem); - while (childElems.length) { - var randomIndex = Math.floor(Math.random() * childElems.length), - chosenChild = childElems.splice(randomIndex, 1); - ko.virtualElements.prepend(elem, chosenChild[0]); - } - } - }; - - var node, containerElem, nodeToInsert, insertAfter, nodeToPrepend, arrayOfNodes; - ko.virtualElements.allowedBindings.mySuperBinding = true; - ko.virtualElements.emptyNode(containerElem); - ko.virtualElements.firstChild(containerElem); - ko.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter); - ko.virtualElements.nextSibling(node); - ko.virtualElements.prepend(containerElem, nodeToPrepend); - ko.virtualElements.setDomNodeChildren(containerElem, arrayOfNodes); -} - -// Have to define your own extenders -interface KnockoutExtenders { - logChange(target, option); - numeric(target, precision); - required(target, overrideMessage); -} - -interface KnockoutObservableArrayFunctions { - filterByProperty(propName, matchValue): KnockoutComputed; -} - -declare var validate; - -function test_more() { - var viewModel = { - firstName: ko.observable("Bert"), - lastName: ko.observable("Smith"), - pets: ko.observableArray(["Cat", "Dog", "Fish"]), - type: "Customer", - hasALotOfPets: null - }; - viewModel.hasALotOfPets = ko.computed(function () { - return this.pets().length > 2 - }, viewModel); - var plainJs = ko.toJS(viewModel); - - ko.extenders.logChange = function (target, option) { - target.subscribe(function (newValue) { - console.log(option + ": " + newValue); - }); - return target; - }; - - ko.extenders.numeric = function (target, precision) { - var result = ko.computed({ - read: target, - write: function (newValue) { - var current = target(), - roundingMultiplier = Math.pow(10, precision), - newValueAsNum = isNaN(newValue) ? 0 : parseFloat(newValue), - valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier; - - if (valueToWrite !== current) { - target(valueToWrite); - } else { - if (newValue !== current) { - target.notifySubscribers(valueToWrite); - } - } - } - }); - - result(target()); - - return result; - }; - - function AppViewModel(one, two) { - this.myNumberOne = ko.observable(one).extend({ numeric: 0 }); - this.myNumberTwo = ko.observable(two).extend({ numeric: 2 }); - } - - ko.applyBindings(new AppViewModel(221.2234, 123.4525)); - - ko.extenders.required = function (target, overrideMessage) { - - target.hasError = ko.observable(); - target.validationMessage = ko.observable(); - - function validate(newValue) { - target.hasError(newValue ? false : true); - target.validationMessage(newValue ? "" : overrideMessage || "This field is required"); - } - - validate(target()); - - target.subscribe(validate); - - return target; - }; - - function AppViewModel(first, last) { - this.firstName = ko.observable(first).extend({ required: "Please enter a first name" }); - this.lastName = ko.observable(last).extend({ required: "" }); - } - - ko.applyBindings(new AppViewModel("Bob", "Smith")); - - var first; - this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" }); - - var upperCaseName = ko.computed(function () { - return name.toUpperCase(); - }).extend({ throttle: 500 }); - - function AppViewModel() { - this.instantaneousValue = ko.observable(); - this.throttledValue = ko.computed(this.instantaneousValue) - .extend({ throttle: 400 }); - - this.loggedValues = ko.observableArray([]); - this.throttledValue.subscribe(function (val) { - if (val !== '') - this.loggedValues.push(val); - }, this); - } - - function GridViewModel() { - this.pageSize = ko.observable(20); - this.pageIndex = ko.observable(1); - this.currentPageData = ko.observableArray(); - - ko.computed(function () { - var params = { page: this.pageIndex(), size: this.pageSize() }; - $.getJSON('/Some/Json/Service', params, this.currentPageData); - }, this); - } - this.setPageSize = function (newPageSize) { - this.pageSize(newPageSize); - this.pageIndex(1); - } - - ko.computed(function () { - var params = { page: this.pageIndex(), size: this.pageSize() }; - $.getJSON('/Some/Json/Service', params, this.currentPageData); - }, this).extend({ throttle: 1 }); - - $(".remove").click(function () { - viewModel.pets.remove(ko.dataFor(this)); - }); - $(".remove").live("click", function () { - viewModel.pets.remove(ko.dataFor(this)); - }); - - $("#people").delegate(".remove", "click", function () { - - var context = ko.contextFor(this), - parentArray = context.$parent.people || context.$parent.children; - - parentArray.remove(context.$data); - - return false; - }); - $("#people").delegate(".add", "click", function () { - var context = ko.contextFor(this), - childName = context.$data.name() + " child", - parentArray = context.$data.people || context.$data.children; - - context.$root.addChild(childName, parentArray); - - return false; - }); - ko.observableArray.fn.filterByProperty = function (propName, matchValue) { - return ko.computed(function () { - var allItems = this(), matchingItems = []; - for (var i = 0; i < allItems.length; i++) { - var current = allItems[i]; - if (ko.utils.unwrapObservable(current[propName]) === matchValue) - matchingItems.push(current); - } - return matchingItems; - }, this); - } - function Task(title, done) { - this.title = ko.observable(title); - this.done = ko.observable(done); - } - - function AppViewModel() { - this.tasks = ko.observableArray([ - new Task('Find new desktop background', true), - new Task('Put shiny stickers on laptop', false), - new Task('Request more reggae music in the office', true) - ]); - - this.doneTasks = this.tasks.filterByProperty("done", true); - } - - ko.applyBindings(new AppViewModel()); - this.doneTasks = ko.computed(function () { - var all = this.tasks(), done = []; - for (var i = 0; i < all.length; i++) - if (all[i].done()) - done.push(all[i]); - return done; - }, this); -} - -function test_mappingplugin() { - var viewModel = { - serverTime: ko.observable(), - numUsers: ko.observable() - } - var data = { - serverTime: '2010-01-07', - numUsers: 3 - }; - viewModel.serverTime(data.serverTime); - viewModel.numUsers(data.numUsers); - - var viewModel = ko.mapping.fromJS(data); - ko.mapping.fromJS(data, viewModel); - var unmapped = ko.mapping.toJS(viewModel); - - var viewModel = ko.mapping.fromJS(data); - ko.mapping.fromJS(data, viewModel); - - var myChildModel = function (data) { - ko.mapping.fromJS(data, {}, this); - - this.nameLength = ko.computed(function () { - return this.name().length; - }, this); - } - - var oldOptions = ko.mapping.defaultOptions().include; - ko.mapping.defaultOptions().include = ["alwaysIncludeThis"]; - - var oldOptions = ko.mapping.defaultOptions().copy; - ko.mapping.defaultOptions().copy = ["alwaysCopyThis"]; - - var someObject; - ko.mapping.fromJS(data, {}, someObject); - ko.mapping.fromJS(data, {}, this); - - var alice, aliceMappingOptions, bob, bobMappingOptions; - var viewModel = ko.mapping.fromJS(alice, aliceMappingOptions); - ko.mapping.fromJS(bob, bobMappingOptions, viewModel); - - var obj; - var result = ko.mapping.fromJS(obj, { - key: function (item) { - return ko.utils.unwrapObservable(item.id); - } - }); - - result.mappedRemove({ id: 2 }); - var newItem = result.mappedCreate({ id: 3 }); -} - -// Define your own functions -interface KnockoutSubscribableFunctions { - publishOn(topic: string): any; - subscribeTo(topic: string): any; -} - -interface KnockoutBindingHandlers { - isolatedOptions: KnockoutBindingHandler; -} - -function test_misc() { - // define dummy vars - var callback: any; - var target: any; - var topic: any; - var vm: any; - var value: any; - - var postbox = new ko.subscribable(); - postbox.subscribe(callback, target, topic); - - postbox.subscribe(function (newValue) { - this.latestTopic(newValue); - }, vm, "mytopic"); - postbox.notifySubscribers(value, "mytopic"); - - ko.subscribable.fn.publishOn = function (topic) { - this.subscribe(function (newValue) { - postbox.notifySubscribers(newValue, topic); - }); - - return this; - }; - - this.myObservable = ko.observable("myValue").publishOn("myTopic"); - - ko.subscribable.fn.subscribeTo = function (topic) { - postbox.subscribe(this, null, topic); - - return this; - }; - - this.observableFromAnotherVM = ko.observable().subscribeTo("myTopic"); - - postbox.subscribe(function (newValue) { - this(newValue); - }, this, topic); - - ko.bindingHandlers.isolatedOptions = { - init: function (element, valueAccessor) { - var args = arguments; - ko.computed({ - read: function () { - ko.utils.unwrapObservable(valueAccessor()); - ko.bindingHandlers.options.update.apply(this, args); - }, - owner: this, - disposeWhenNodeIsRemoved: element - }); - } - }; - - ko.subscribable.fn.publishOn = function (topic) { - this.subscribe(function (newValue) { - postbox.notifySubscribers(newValue, topic); - }); - - return this; - }; - - this.myObservable = ko.observable("myValue").publishOn("myTopic"); - - var x = ko.observableArray([1, 2, 3]); - - var element; - ko.utils.domNodeDisposal.addDisposeCallback(element, function () { - $(element).datepicker("destroy"); - }); +/// +/// + +declare var $; + +function test_creatingVMs() { + var myViewModel = { + personName: ko.observable('Bob'), + personAge: ko.observable(123) + }; + ko.applyBindings(myViewModel); + ko.applyBindings(myViewModel, document.getElementById('someElementId')); + + myViewModel.personName(); + myViewModel.personName('Mary'); + myViewModel.personAge(50); + + myViewModel.personName.subscribe(function (newValue) { + alert("The person's new name is " + newValue); + }); + + var subscription = myViewModel.personName.subscribe(function (newValue) { }); + subscription.dispose(); +} + +function test_computed() { + function AppViewModel() { + var self = this; + + self.firstName = ko.observable('Bob'); + self.lastName = ko.observable('Smith'); + self.fullName = ko.computed(function () { + return self.firstName() + " " + self.lastName(); + }); + } + + function MyViewModel() { + this.firstName = ko.observable('Planet'); + this.lastName = ko.observable('Earth'); + + this.fullName = ko.computed({ + read: function () { + return this.firstName() + " " + this.lastName(); + }, + write: function (value) { + var lastSpacePos = value.lastIndexOf(" "); + if (lastSpacePos > 0) { + this.firstName(value.substring(0, lastSpacePos)); + this.lastName(value.substring(lastSpacePos + 1)); + } + }, + owner: this + }); + } + + function MyViewModel() { + this.price = ko.observable(25.99); + + this.formattedPrice = ko.computed({ + read: function () { + return '$' + this.price().toFixed(2); + }, + write: function (value) { + value = parseFloat(value.replace(/[^\.\d]/g, "")); + this.price(isNaN(value) ? 0 : value); + }, + owner: this + }); + } + + function MyViewModel() { + this.acceptedNumericValue = ko.observable(123); + this.lastInputWasValid = ko.observable(true); + + this.attemptedValue = ko.computed({ + read: this.acceptedNumericValue, + write: function (value) { + if (isNaN(value)) + this.lastInputWasValid(false); + else { + this.lastInputWasValid(true); + this.acceptedNumericValue(value); + } + }, + owner: this + }); + } + + ko.applyBindings(new MyViewModel()); +} + +class GetterViewModel { + private _selectedRange: KnockoutObservableAny; + + constructor() { + this._selectedRange = ko.observable(); + } + + public range: KnockoutObservableAny; +} + +function testGetter() { + var model = new GetterViewModel(); + + model.range.subscribe((range: number) => { + }); +} + +function test_observableArrays() { + var myObservableArray = ko.observableArray(); + myObservableArray.push('Some value'); + var anotherObservableArray = ko.observableArray([ + { name: "Bungle", type: "Bear" }, + { name: "George", type: "Hippo" }, + { name: "Zippy", type: "Unknown" } + ]); + + myObservableArray().length; + myObservableArray()[0]; + + myObservableArray.indexOf('Blah'); + myObservableArray.push('Some new value'); + myObservableArray.pop(); + myObservableArray.unshift('Some new value'); + myObservableArray.shift(); + myObservableArray.reverse(); + myObservableArray.sort(function (left, right) { return left.lastName == right.lastName ? 0 : (left.lastName < right.lastName ? -1 : 1) }); + myObservableArray.splice(1, 3); + + myObservableArray.remove('Blah'); + myObservableArray.remove(function (item) { return item.age < 18 }); + myObservableArray.removeAll(['Chad', 132, undefined]); + myObservableArray.removeAll(); + myObservableArray.destroy('Blah'); + myObservableArray.destroy(function (someItem) { return someItem.age < 18 }); + myObservableArray.destroyAll(['Chad', 132, undefined]); + myObservableArray.destroyAll(); + + ko.utils.arrayForEach(myObservableArray(), function (item) { }); +} + +// You have to extend knockout for your own handlers +interface KnockoutBindingHandlers { + yourBindingName: KnockoutBindingHandler; + slideVisible: KnockoutBindingHandler; + hasFocus: KnockoutBindingHandler; + allowBindings: KnockoutBindingHandler; + withProperties: KnockoutBindingHandler; + randomOrder: KnockoutBindingHandler; +} + +function test_bindings() { + var currentProfit = ko.observable(150000); + ko.applyBindings({ + people: [ + { firstName: 'Bert', lastName: 'Bertington' }, + { firstName: 'Charles', lastName: 'Charlesforth' }, + { firstName: 'Denise', lastName: 'Dentiste' } + ] + }); + var viewModel = { availableCountries: ko.observableArray(['France', 'Germany', 'Spain']) }; + viewModel.availableCountries.push('China'); + + ko.bindingHandlers.yourBindingName = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + }, + update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + } + }; + ko.bindingHandlers.slideVisible = { + update: function (element, valueAccessor, allBindingsAccessor) { + var value = valueAccessor(), allBindings = allBindingsAccessor(); + var valueUnwrapped = ko.utils.unwrapObservable(value); + var duration = allBindings.slideDuration || 400; + if (valueUnwrapped == true) + $(element).slideDown(duration); + else + $(element).slideUp(duration); + }, + init: function (element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + $(element).toggle(value); + }, + update: function (element, valueAccessor, allBindingsAccessor) { + } + }; + ko.bindingHandlers.hasFocus = { + init: function (element, valueAccessor) { + $(element).focus(function () { + var value = valueAccessor(); + value(true); + }); + $(element).blur(function () { + var value = valueAccessor(); + value(false); + }); + }, + update: function (element, valueAccessor) { + var value = valueAccessor(); + if (ko.utils.unwrapObservable(value)) + element.focus(); + else + element.blur(); + } + }; + ko.bindingHandlers.allowBindings = { + init: function (elem, valueAccessor) { + var shouldAllowBindings = ko.utils.unwrapObservable(valueAccessor()); + return { controlsDescendantBindings: !shouldAllowBindings }; + } + }; + ko.bindingHandlers.withProperties = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + var newProperties = valueAccessor(), + innerBindingContext = bindingContext.extend(newProperties); + ko.applyBindingsToDescendants(innerBindingContext, element); + return { controlsDescendantBindings: true }; + } + }; + ko.bindingHandlers.withProperties = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + var newProperties = valueAccessor(), + childBindingContext = bindingContext.createChildContext(viewModel); + ko.utils.extend(childBindingContext, newProperties); + ko.applyBindingsToDescendants(childBindingContext, element); + return { controlsDescendantBindings: true }; + } + }; + ko.bindingHandlers.randomOrder = { + init: function (elem, valueAccessor) { + var child = ko.virtualElements.firstChild(elem), + childElems = []; + while (child) { + childElems.push(child); + child = ko.virtualElements.nextSibling(child); + } + ko.virtualElements.emptyNode(elem); + while (childElems.length) { + var randomIndex = Math.floor(Math.random() * childElems.length), + chosenChild = childElems.splice(randomIndex, 1); + ko.virtualElements.prepend(elem, chosenChild[0]); + } + } + }; + + var node, containerElem, nodeToInsert, insertAfter, nodeToPrepend, arrayOfNodes; + ko.virtualElements.allowedBindings.mySuperBinding = true; + ko.virtualElements.emptyNode(containerElem); + ko.virtualElements.firstChild(containerElem); + ko.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter); + ko.virtualElements.nextSibling(node); + ko.virtualElements.prepend(containerElem, nodeToPrepend); + ko.virtualElements.setDomNodeChildren(containerElem, arrayOfNodes); +} + +// Have to define your own extenders +interface KnockoutExtenders { + logChange(target, option); + numeric(target, precision); + required(target, overrideMessage); +} + +interface KnockoutObservableArrayFunctions { + filterByProperty(propName, matchValue): KnockoutComputed; +} + +declare var validate; + +function test_more() { + var viewModel = { + firstName: ko.observable("Bert"), + lastName: ko.observable("Smith"), + pets: ko.observableArray(["Cat", "Dog", "Fish"]), + type: "Customer", + hasALotOfPets: null + }; + viewModel.hasALotOfPets = ko.computed(function () { + return this.pets().length > 2 + }, viewModel); + var plainJs = ko.toJS(viewModel); + + ko.extenders.logChange = function (target, option) { + target.subscribe(function (newValue) { + console.log(option + ": " + newValue); + }); + return target; + }; + + ko.extenders.numeric = function (target, precision) { + var result = ko.computed({ + read: target, + write: function (newValue) { + var current = target(), + roundingMultiplier = Math.pow(10, precision), + newValueAsNum = isNaN(newValue) ? 0 : parseFloat(newValue), + valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier; + + if (valueToWrite !== current) { + target(valueToWrite); + } else { + if (newValue !== current) { + target.notifySubscribers(valueToWrite); + } + } + } + }); + + result(target()); + + return result; + }; + + function AppViewModel(one, two) { + this.myNumberOne = ko.observable(one).extend({ numeric: 0 }); + this.myNumberTwo = ko.observable(two).extend({ numeric: 2 }); + } + + ko.applyBindings(new AppViewModel(221.2234, 123.4525)); + + ko.extenders.required = function (target, overrideMessage) { + + target.hasError = ko.observable(); + target.validationMessage = ko.observable(); + + function validate(newValue) { + target.hasError(newValue ? false : true); + target.validationMessage(newValue ? "" : overrideMessage || "This field is required"); + } + + validate(target()); + + target.subscribe(validate); + + return target; + }; + + function AppViewModel(first, last) { + this.firstName = ko.observable(first).extend({ required: "Please enter a first name" }); + this.lastName = ko.observable(last).extend({ required: "" }); + } + + ko.applyBindings(new AppViewModel("Bob", "Smith")); + + var first; + this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" }); + + var upperCaseName = ko.computed(function () { + return name.toUpperCase(); + }).extend({ throttle: 500 }); + + function AppViewModel() { + this.instantaneousValue = ko.observable(); + this.throttledValue = ko.computed(this.instantaneousValue) + .extend({ throttle: 400 }); + + this.loggedValues = ko.observableArray([]); + this.throttledValue.subscribe(function (val) { + if (val !== '') + this.loggedValues.push(val); + }, this); + } + + function GridViewModel() { + this.pageSize = ko.observable(20); + this.pageIndex = ko.observable(1); + this.currentPageData = ko.observableArray(); + + ko.computed(function () { + var params = { page: this.pageIndex(), size: this.pageSize() }; + $.getJSON('/Some/Json/Service', params, this.currentPageData); + }, this); + } + this.setPageSize = function (newPageSize) { + this.pageSize(newPageSize); + this.pageIndex(1); + } + + ko.computed(function () { + var params = { page: this.pageIndex(), size: this.pageSize() }; + $.getJSON('/Some/Json/Service', params, this.currentPageData); + }, this).extend({ throttle: 1 }); + + $(".remove").click(function () { + viewModel.pets.remove(ko.dataFor(this)); + }); + $(".remove").live("click", function () { + viewModel.pets.remove(ko.dataFor(this)); + }); + + $("#people").delegate(".remove", "click", function () { + + var context = ko.contextFor(this), + parentArray = context.$parent.people || context.$parent.children; + + parentArray.remove(context.$data); + + return false; + }); + $("#people").delegate(".add", "click", function () { + var context = ko.contextFor(this), + childName = context.$data.name() + " child", + parentArray = context.$data.people || context.$data.children; + + context.$root.addChild(childName, parentArray); + + return false; + }); + ko.observableArray.fn.filterByProperty = function (propName, matchValue) { + return ko.computed(function () { + var allItems = this(), matchingItems = []; + for (var i = 0; i < allItems.length; i++) { + var current = allItems[i]; + if (ko.utils.unwrapObservable(current[propName]) === matchValue) + matchingItems.push(current); + } + return matchingItems; + }, this); + } + function Task(title, done) { + this.title = ko.observable(title); + this.done = ko.observable(done); + } + + function AppViewModel() { + this.tasks = ko.observableArray([ + new Task('Find new desktop background', true), + new Task('Put shiny stickers on laptop', false), + new Task('Request more reggae music in the office', true) + ]); + + this.doneTasks = this.tasks.filterByProperty("done", true); + } + + ko.applyBindings(new AppViewModel()); + this.doneTasks = ko.computed(function () { + var all = this.tasks(), done = []; + for (var i = 0; i < all.length; i++) + if (all[i].done()) + done.push(all[i]); + return done; + }, this); +} + +function test_mappingplugin() { + var viewModel = { + serverTime: ko.observable(), + numUsers: ko.observable() + } + var data = { + serverTime: '2010-01-07', + numUsers: 3 + }; + viewModel.serverTime(data.serverTime); + viewModel.numUsers(data.numUsers); + + var viewModel = ko.mapping.fromJS(data); + ko.mapping.fromJS(data, viewModel); + var unmapped = ko.mapping.toJS(viewModel); + + var viewModel = ko.mapping.fromJS(data); + ko.mapping.fromJS(data, viewModel); + + var myChildModel = function (data) { + ko.mapping.fromJS(data, {}, this); + + this.nameLength = ko.computed(function () { + return this.name().length; + }, this); + } + + var oldOptions = ko.mapping.defaultOptions().include; + ko.mapping.defaultOptions().include = ["alwaysIncludeThis"]; + + var oldOptions = ko.mapping.defaultOptions().copy; + ko.mapping.defaultOptions().copy = ["alwaysCopyThis"]; + + var someObject; + ko.mapping.fromJS(data, {}, someObject); + ko.mapping.fromJS(data, {}, this); + + var alice, aliceMappingOptions, bob, bobMappingOptions; + var viewModel = ko.mapping.fromJS(alice, aliceMappingOptions); + ko.mapping.fromJS(bob, bobMappingOptions, viewModel); + + var obj; + var result = ko.mapping.fromJS(obj, { + key: function (item) { + return ko.utils.unwrapObservable(item.id); + } + }); + + result.mappedRemove({ id: 2 }); + var newItem = result.mappedCreate({ id: 3 }); +} + +// Define your own functions +interface KnockoutSubscribableFunctions { + publishOn(topic: string): any; + subscribeTo(topic: string): any; +} + +interface KnockoutBindingHandlers { + isolatedOptions: KnockoutBindingHandler; +} + +function test_misc() { + // define dummy vars + var callback: any; + var target: any; + var topic: any; + var vm: any; + var value: any; + + var postbox = new ko.subscribable(); + postbox.subscribe(callback, target, topic); + + postbox.subscribe(function (newValue) { + this.latestTopic(newValue); + }, vm, "mytopic"); + postbox.notifySubscribers(value, "mytopic"); + + ko.subscribable.fn.publishOn = function (topic) { + this.subscribe(function (newValue) { + postbox.notifySubscribers(newValue, topic); + }); + + return this; + }; + + this.myObservable = ko.observable("myValue").publishOn("myTopic"); + + ko.subscribable.fn.subscribeTo = function (topic) { + postbox.subscribe(this, null, topic); + + return this; + }; + + this.observableFromAnotherVM = ko.observable().subscribeTo("myTopic"); + + postbox.subscribe(function (newValue) { + this(newValue); + }, this, topic); + + ko.bindingHandlers.isolatedOptions = { + init: function (element, valueAccessor) { + var args = arguments; + ko.computed({ + read: function () { + ko.utils.unwrapObservable(valueAccessor()); + ko.bindingHandlers.options.update.apply(this, args); + }, + owner: this, + disposeWhenNodeIsRemoved: element + }); + } + }; + + ko.subscribable.fn.publishOn = function (topic) { + this.subscribe(function (newValue) { + postbox.notifySubscribers(newValue, topic); + }); + + return this; + }; + + this.myObservable = ko.observable("myValue").publishOn("myTopic"); + + var x = ko.observableArray([1, 2, 3]); + + var element; + ko.utils.domNodeDisposal.addDisposeCallback(element, function () { + $(element).datepicker("destroy"); + }); } \ No newline at end of file