typeahead.js-0.11.1/000077500000000000000000000000001251733230600141535ustar00rootroot00000000000000typeahead.js-0.11.1/.gitignore000066400000000000000000000001751251733230600161460ustar00rootroot00000000000000*.swp .DS_Store .grunt _SpecRunner.html test/coverage dist_temp node_modules npm-debug.log bower_components *.iml .idea typeahead.js-0.11.1/.jshintrc000066400000000000000000000003451251733230600160020ustar00rootroot00000000000000{ "curly": true, "newcap": true, "noarg": true, "quotmark": "single", "regexp": true, "trailing": true, "boss": true, "eqnull": true, "expr": true, "validthis": true, "browser": true, "jquery": true } typeahead.js-0.11.1/.travis.yml000066400000000000000000000022611251733230600162650ustar00rootroot00000000000000language: node_js env: matrix: - TEST_SUITE=unit - TEST_SUITE=integration BROWSER='firefox' - TEST_SUITE=integration BROWSER='firefox:3.5' - TEST_SUITE=integration BROWSER='firefox:3.6' - TEST_SUITE=integration BROWSER='safari:5' - TEST_SUITE=integration BROWSER='safari:6' - TEST_SUITE=integration BROWSER='safari:7' - TEST_SUITE=integration BROWSER='internet explorer:8' - TEST_SUITE=integration BROWSER='internet explorer:9' - TEST_SUITE=integration BROWSER='internet explorer:10' - TEST_SUITE=integration BROWSER='internet explorer:11' - TEST_SUITE=integration BROWSER='chrome' global: - secure: VY4J2ERfrMEin++f4+UDDtTMWLuE3jaYAVchRxfO2c6PQUYgR+SW4SMekz855U/BuptMtiVMR2UUoNGMgOSKIFkIXpPfHhx47G5a541v0WNjXfQ2qzivXAWaXNK3l3C58z4dKxgPWsFY9JtMVCddJd2vQieAILto8D8G09p7bpo= - secure: kehbNCoYUG2gLnhmCH/oKhlJG6LoxgcOPMCtY7KOI4ropG8qlypb+O2b/19+BWeO3aIuMB0JajNh3p2NL0UKgLmUK7EYBA9fQz+vesFReRk0V/KqMTSxHJuseM4aLOWA2Wr9US843VGltfODVvDN5sNrfY7RcoRx2cTK/k1CXa8= node_js: - 0.11.13 before_script: - npm install -g grunt-cli@0.1.13 - npm install -g node-static@0.7.3 - npm install -g bower@1.3.8 - bower install - grunt build script: test/ci addons: sauce_connect: true typeahead.js-0.11.1/CHANGELOG.md000066400000000000000000000264531251733230600157760ustar00rootroot00000000000000Changelog ========= For transparency and insight into our release cycle, releases will be numbered with the follow format: `..` And constructed with the following guidelines: * Breaking backwards compatibility bumps the major * New additions without breaking backwards compatibility bumps the minor * Bug fixes and misc changes bump the patch For more information on semantic versioning, please visit http://semver.org/. --- ### 0.11.1 April 26, 2015 * Add prepare option to prefetch. [#1181] * Handle QuotaExceededError. [#1110] * Escape HTML entities from suggestion display value when rendering with default template. [#964] * List jquery as a dependency in package.json. [#1143] ### 0.11.0 April 25, 2015 An overhaul of typeahead.js – consider this a release candidate for v1. There are bunch of API changes with this release so don't expect backwards compatibility with previous versions. There are also many new undocumented features that have been introduced. Documentation for those features will be added before v1 ships. Beware that since this release is pretty much a rewrite, there are bound to be some bugs. To be safe, you should consider this release beta software and throughly test your integration of it before using it in production environments. This caveat only applies to this release as subsequent releases will address any issues that come up. ### 0.10.5 August 7, 2014 * Increase supported version range for jQuery dependency. [#917] ### 0.10.4 July 13, 2014 **Hotfix** * Fix regression that breaks Bloodhound instances when more than 1 instance is relying on remote data. [#899] ### 0.10.3 July 10, 2014 **Bug fixes** * `Bloodhound#clearPrefetchCache` now works with cache keys that contain regex characters. [#771] * Prevent outdated network requests from being sent. [#809] * Add support to object tokenizers for multiple property tokenization. [#811] * Fix broken `jQuery#typeahead('val')` method. [#815] * Remove `disabled` attribute from the hint input control. [#839] * Add `tt-highlight` class to highlighted text. [#833] * Handle non-string types that are passed to `jQuery#typeahead('val', val)`. [#881] ### 0.10.2 March 10, 2014 * Prevent flickering of dropdown menu when requesting remote suggestions. [#718] * Reduce hint flickering. [#754] * Added `Bloodhound#{clear, clearPrefetchCache, clearRemoteCache}` and made it possible to reinitialize Bloodhound instances. [#703] * Invoke `local` function during initialization. [#687] * In addition to HTML strings, templates can now return DOM nodes. [#742] * Prevent `jQuery#typeahead('val', val)` from opening dropdown menus of non-active typeaheads. [#646] * Fix bug in IE that resulted in dropdown menus with overflow being closed when clicking on the scrollbar. [#705] * Only show dropdown menu if `minLength` is satisfied. [#710] ### 0.10.1 February 9, 2014 **Hotfix** * Fixed bug that prevented some ajax configs from being respected. [#630] * Event delegation on suggestion clicks is no longer broken. [#118] * Ensure dataset names are valid class name suffixes. [#610] * Added support for `displayKey` to be a function. [#633] * `jQuery#typeahead('val')` now mirrors `jQuery#val()`. [#659] * Datasets can now be passed to jQuery plugin as an array. [#664] * Added a `noConflict` method to the jQuery plugin. [#612] * Bloodhound's `local` property can now be a function. [#485] ### 0.10.0 February 2, 2014 **Introducting Bloodhound** This release was almost a complete rewrite of typeahead.js and will hopefully lay the foundation for the 1.0.0 release. It's impossible to enumerate all of the issues that were fixed. If you want to get an idea of what issues 0.10.0 resolved, take a look at the closed issues in the [0.10.0 milestone]. The most important change in 0.10.0 is that typeahead.js was broken up into 2 individual components: Bloodhound and jQuery#typeahead. Bloodhound is an feature-rich suggestion engine. jQuery#typeahead is a jQuery plugin that turns input controls into typeaheads. It's impossible to write a typeahead library that supports every use-case out of the box – that was the main motivation behind decomposing typeahead.js. Previously, some prospective typeahead.js users were unable to use the library because either the suggestion engine or the typeahead UI did not meet their requirements. In those cases, they were either forced to fork typeahead.js and make the necessary modifications or they had to give up on using typeahead.js entirely. Now they have the option of swapping out the component that doesn't work for them with a custom implementation. ### 0.9.3 June 24, 2013 * Ensure cursor visibility in menus with overflow. [#209] * Fixed bug that led to the menu staying open when it should have been closed. [#260] * Private browsing in Safari no longer breaks prefetch. [#270] * Pressing tab while a suggestion is highlighted now results in a selection. [#266] * Dataset name is now passed as an argument for typeahead:selected event. [#207] ### 0.9.2 April 14, 2013 * Prefetch usage no longer breaks when cookies are disabled. [#190] * Precompiled templates are now wrapped in the appropriate DOM element. [#172] ### 0.9.1 April 1, 2013 * Multiple requests no longer get sent for a query when datasets share a remote source. [#152] * Datasets now support precompiled templates. [#137] * Cached remote suggestions now get rendered immediately. [#156] * Added typeahead:autocompleted event. [#132] * Added a plugin method for programmatically setting the query. Experimental. [#159] * Added minLength option for datasets. Experimental. [#131] * Prefetch objects now support thumbprint option. Experimental. [#157] ### 0.9.0 March 24, 2013 **Custom events, no more typeahead.css, and an improved API** * Implemented the triggering of custom events. [#106] * Got rid of typeahead.css and now apply styling through JavaScript. [#15] * Made the API more flexible and addressed a handful of remote issues by rewriting the transport component. [#25] * Added support for dataset headers and footers. [#81] * No longer cache unnamed datasets. [#116] * Made the key name of the value property configurable. [#115] * Input values set before initialization of typeaheads are now respected. [#109] * Fixed an input value/hint casing bug. [#108] ### 0.8.2 March 04, 2013 * Fixed bug causing error to be thrown when initializing a typeahead on multiple elements. [#51] * Tokens with falsy values are now filtered out – was causing wonky behavior. [#75] * No longer making remote requests for blank queries. [#74] * Datums with regex characters in their value no longer cause errors. [#77] * Now compatible with the Closure Compiler. [#48] * Reference to jQuery is now obtained through window.jQuery, not window.$. [#47] * Added a plugin method for destroying typeaheads. Won't be documented until v0.9 and might change before then. [#59] ### 0.8.1 February 25, 2013 * Fixed bug preventing local and prefetch from being used together. [#39] * No longer prevent default browser behavior when up or down arrow is pressed with a modifier. [#6] * Hint is hidden when user entered query is wider than the input. [#26] * Data stored in localStorage now expires properly. [#34] * Normalized search tokens and fixed query tokenization. [#38] * Remote suggestions now are appended, not prepended to suggestions list. [#40] * Fixed some typos through the codebase. [#3] ### 0.8.0 February 19, 2013 **Initial public release** * Prefetch and search data locally insanely fast. * Search hard-coded, prefetched, and/or remote data. * Hinting. * RTL/IME/international support. * Search multiple datasets. * Share datasets (and caching) between multiple inputs. * And much, much more... [0.10.0 milestone]: https://github.com/twitter/typeahead.js/issues?milestone=8&page=1&state=closed [#1181]: https://github.com/twitter/typeahead.js/pull/1181 [#1143]: https://github.com/twitter/typeahead.js/pull/1143 [#1110]: https://github.com/twitter/typeahead.js/pull/1110 [#964]: https://github.com/twitter/typeahead.js/pull/964 [#917]: https://github.com/twitter/typeahead.js/pull/917 [#899]: https://github.com/twitter/typeahead.js/pull/899 [#881]: https://github.com/twitter/typeahead.js/pull/881 [#839]: https://github.com/twitter/typeahead.js/pull/839 [#833]: https://github.com/twitter/typeahead.js/pull/833 [#815]: https://github.com/twitter/typeahead.js/pull/815 [#811]: https://github.com/twitter/typeahead.js/pull/811 [#809]: https://github.com/twitter/typeahead.js/pull/809 [#771]: https://github.com/twitter/typeahead.js/pull/771 [#754]: https://github.com/twitter/typeahead.js/pull/754 [#742]: https://github.com/twitter/typeahead.js/pull/742 [#718]: https://github.com/twitter/typeahead.js/pull/718 [#710]: https://github.com/twitter/typeahead.js/pull/710 [#705]: https://github.com/twitter/typeahead.js/pull/705 [#703]: https://github.com/twitter/typeahead.js/pull/703 [#687]: https://github.com/twitter/typeahead.js/pull/687 [#664]: https://github.com/twitter/typeahead.js/pull/664 [#659]: https://github.com/twitter/typeahead.js/pull/659 [#646]: https://github.com/twitter/typeahead.js/pull/646 [#633]: https://github.com/twitter/typeahead.js/pull/633 [#630]: https://github.com/twitter/typeahead.js/pull/630 [#612]: https://github.com/twitter/typeahead.js/pull/612 [#610]: https://github.com/twitter/typeahead.js/pull/610 [#485]: https://github.com/twitter/typeahead.js/pull/485 [#270]: https://github.com/twitter/typeahead.js/pull/270 [#266]: https://github.com/twitter/typeahead.js/pull/266 [#260]: https://github.com/twitter/typeahead.js/pull/260 [#209]: https://github.com/twitter/typeahead.js/pull/209 [#207]: https://github.com/twitter/typeahead.js/pull/207 [#190]: https://github.com/twitter/typeahead.js/pull/190 [#172]: https://github.com/twitter/typeahead.js/pull/172 [#159]: https://github.com/twitter/typeahead.js/pull/159 [#157]: https://github.com/twitter/typeahead.js/pull/157 [#156]: https://github.com/twitter/typeahead.js/pull/156 [#152]: https://github.com/twitter/typeahead.js/pull/152 [#137]: https://github.com/twitter/typeahead.js/pull/137 [#132]: https://github.com/twitter/typeahead.js/pull/132 [#131]: https://github.com/twitter/typeahead.js/pull/131 [#118]: https://github.com/twitter/typeahead.js/pull/118 [#116]: https://github.com/twitter/typeahead.js/pull/116 [#115]: https://github.com/twitter/typeahead.js/pull/115 [#109]: https://github.com/twitter/typeahead.js/pull/109 [#108]: https://github.com/twitter/typeahead.js/pull/108 [#106]: https://github.com/twitter/typeahead.js/pull/106 [#81]: https://github.com/twitter/typeahead.js/pull/81 [#77]: https://github.com/twitter/typeahead.js/pull/77 [#75]: https://github.com/twitter/typeahead.js/pull/75 [#74]: https://github.com/twitter/typeahead.js/pull/74 [#59]: https://github.com/twitter/typeahead.js/pull/59 [#51]: https://github.com/twitter/typeahead.js/pull/51 [#48]: https://github.com/twitter/typeahead.js/pull/48 [#47]: https://github.com/twitter/typeahead.js/pull/47 [#40]: https://github.com/twitter/typeahead.js/pull/40 [#39]: https://github.com/twitter/typeahead.js/pull/39 [#38]: https://github.com/twitter/typeahead.js/pull/38 [#34]: https://github.com/twitter/typeahead.js/pull/34 [#26]: https://github.com/twitter/typeahead.js/pull/26 [#25]: https://github.com/twitter/typeahead.js/pull/25 [#15]: https://github.com/twitter/typeahead.js/pull/15 [#6]: https://github.com/twitter/typeahead.js/pull/6 [#3]: https://github.com/twitter/typeahead.js/pull/3 typeahead.js-0.11.1/CONTRIBUTING.md000066400000000000000000000077751251733230600164240ustar00rootroot00000000000000Contributing to typeahead.js ============================ *These contributing guidelines were proudly stolen from the [Flight](https://github.com/flightjs/flight) project* Looking to contribute something to typeahead.js? Here's how you can help. Bugs Reports ------------ A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful – thank you! Guidelines for bug reports: 1. **Use the GitHub issue search** — check if the issue has already been reported. 2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or integration branch in the repository. 3. **Isolate the problem** — ideally create a reduced test case and a live example. 4. Please try to be as detailed as possible in your report. Include specific information about the environment – operating system and version, browser and version, version of typeahead.js – and steps required to reproduce the issue. Feature Requests & Contribution Enquiries ----------------------------------------- Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case for the inclusion of your feature. Please provide as much detail and context as possible. Contribution enquiries should take place before any significant pull request, otherwise you risk spending a lot of time working on something that we might have good reasons for rejecting. Pull Requests ------------- Good pull requests – patches, improvements, new features – are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. Make sure to adhere to the coding conventions used throughout the codebase (indentation, accurate comments, etc.) and any other requirements (such as test coverage). Please follow this process; it's the best way to get your work included in the project: 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes: ```bash # Clone your fork of the repo into the current directory git clone https://github.com//typeahead.js # Navigate to the newly cloned directory cd # Assign the original repo to a remote called "upstream" git remote add upstream git://github.com/twitter/typeahead.js ``` 2. If you cloned a while ago, get the latest changes from upstream: ```bash git checkout master git pull upstream master ``` 3. Install the dependencies (you must have Node.js and [Bower](http://bower.io) installed), and create a new topic branch (off the main project development branch) to contain your feature, change, or fix: ```bash npm install bower install git checkout -b ``` 4. Make sure to update, or add to the tests when appropriate. Patches and features will not be accepted without tests. Run `npm test` to check that all tests pass after you've made changes. 5. Commit your changes in logical chunks. Provide clear and explanatory commit messages. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public. 6. Locally merge (or rebase) the upstream development branch into your topic branch: ```bash git pull [--rebase] upstream master ``` 7. Push your topic branch up to your fork: ```bash git push origin ``` 8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description. 9. If you are asked to amend your changes before they can be merged in, please use `git commit --amend` (or rebasing for multi-commit Pull Requests) and force push to your remote feature branch. You may also be asked to squash commits. License ------- By contributing your code, You agree to license your contribution under the terms of the MIT License https://github.com/twitter/typeahead.js/blob/master/LICENSE typeahead.js-0.11.1/Gruntfile.js000066400000000000000000000206261251733230600164560ustar00rootroot00000000000000var semver = require('semver'), f = require('util').format, files = { common: [ 'src/common/utils.js' ], bloodhound: [ 'src/bloodhound/version.js', 'src/bloodhound/tokenizers.js', 'src/bloodhound/lru_cache.js', 'src/bloodhound/persistent_storage.js', 'src/bloodhound/transport.js', 'src/bloodhound/search_index.js', 'src/bloodhound/prefetch.js', 'src/bloodhound/remote.js', 'src/bloodhound/options_parser.js', 'src/bloodhound/bloodhound.js' ], typeahead: [ 'src/typeahead/www.js', 'src/typeahead/event_bus.js', 'src/typeahead/event_emitter.js', 'src/typeahead/highlight.js', 'src/typeahead/input.js', 'src/typeahead/dataset.js', 'src/typeahead/menu.js', 'src/typeahead/default_menu.js', 'src/typeahead/typeahead.js', 'src/typeahead/plugin.js' ] }; module.exports = function(grunt) { grunt.initConfig({ version: grunt.file.readJSON('package.json').version, tempDir: 'dist_temp', buildDir: 'dist', banner: [ '/*!', ' * typeahead.js <%= version %>', ' * https://github.com/twitter/typeahead.js', ' * Copyright 2013-<%= grunt.template.today("yyyy") %> Twitter, Inc. and other contributors; Licensed MIT', ' */\n\n' ].join('\n'), uglify: { options: { banner: '<%= banner %>' }, concatBloodhound: { options: { mangle: false, beautify: true, compress: false, banner: '' }, src: files.common.concat(files.bloodhound), dest: '<%= tempDir %>/bloodhound.js' }, concatTypeahead: { options: { mangle: false, beautify: true, compress: false, banner: '' }, src: files.common.concat(files.typeahead), dest: '<%= tempDir %>/typeahead.jquery.js' }, bloodhound: { options: { mangle: false, beautify: true, compress: false }, src: '<%= tempDir %>/bloodhound.js', dest: '<%= buildDir %>/bloodhound.js' }, bloodhoundMin: { options: { mangle: true, compress: {} }, src: '<%= tempDir %>/bloodhound.js', dest: '<%= buildDir %>/bloodhound.min.js' }, typeahead: { options: { mangle: false, beautify: true, compress: false }, src: '<%= tempDir %>/typeahead.jquery.js', dest: '<%= buildDir %>/typeahead.jquery.js' }, typeaheadMin: { options: { mangle: true, compress: {} }, src: '<%= tempDir %>/typeahead.jquery.js', dest: '<%= buildDir %>/typeahead.jquery.min.js' }, bundle: { options: { mangle: false, beautify: true, compress: false }, src: [ '<%= tempDir %>/bloodhound.js', '<%= tempDir %>/typeahead.jquery.js' ], dest: '<%= buildDir %>/typeahead.bundle.js' }, bundleMin: { options: { mangle: true, compress: {} }, src: [ '<%= tempDir %>/bloodhound.js', '<%= tempDir %>/typeahead.jquery.js' ], dest: '<%= buildDir %>/typeahead.bundle.min.js' } }, umd: { bloodhound: { src: '<%= tempDir %>/bloodhound.js', objectToExport: 'Bloodhound', amdModuleId: 'bloodhound', deps: { default: ['$'], amd: ['jquery'], cjs: ['jquery'], global: ['jQuery'] } }, typeahead: { src: '<%= tempDir %>/typeahead.jquery.js', amdModuleId: 'typeahead.js', deps: { default: ['$'], amd: ['jquery'], cjs: ['jquery'], global: ['jQuery'] } } }, sed: { version: { pattern: '%VERSION%', replacement: '<%= version %>', recursive: true, path: '<%= buildDir %>' } }, jshint: { options: { jshintrc: '.jshintrc' }, src: 'src/**/*.js', test: ['test/**/*_spec.js', 'test/integration/test.js'], gruntfile: ['Gruntfile.js'] }, watch: { js: { files: 'src/**/*', tasks: 'build' } }, exec: { npm_publish: 'npm publish', git_is_clean: 'test -z "$(git status --porcelain)"', git_on_master: 'test $(git symbolic-ref --short -q HEAD) = master', git_add: 'git add .', git_push: 'git push && git push --tags', git_commit: { cmd: function(m) { return f('git commit -m "%s"', m); } }, git_tag: { cmd: function(v) { return f('git tag v%s -am "%s"', v, v); } }, publish_assets: [ 'cp -r <%= buildDir %> typeahead.js', 'zip -r typeahead.js/typeahead.js.zip typeahead.js', 'git checkout gh-pages', 'rm -rf releases/latest', 'cp -r typeahead.js releases/<%= version %>', 'cp -r typeahead.js releases/latest', 'git add releases/<%= version %> releases/latest', 'sed -E -i "" \'s/v[0-9]+\\.[0-9]+\\.[0-9]+/v<%= version %>/\' index.html', 'git add index.html', 'git commit -m "Add assets for <%= version %>."', 'git push', 'git checkout -', 'rm -rf typeahead.js' ].join(' && ') }, clean: { dist: 'dist' }, connect: { server: { options: { port: 8888, keepalive: true } } }, concurrent: { options: { logConcurrentOutput: true }, dev: ['server', 'watch'] }, step: { options: { option: false } } }); grunt.registerTask('release', '#shipit', function(version) { var curVersion = grunt.config.get('version'); version = semver.inc(curVersion, version) || version; if (!semver.valid(version) || semver.lte(version, curVersion)) { grunt.fatal('hey dummy, that version is no good!'); } grunt.config.set('version', version); grunt.task.run([ 'exec:git_on_master', 'exec:git_is_clean', f('step:Update to version %s?', version), f('manifests:%s', version), 'build', 'exec:git_add', f('exec:git_commit:%s', version), f('exec:git_tag:%s', version), 'step:Push changes?', 'exec:git_push', 'step:Publish to npm?', 'exec:npm_publish', 'step:Publish assets?', 'exec:publish_assets' ]); }); grunt.registerTask('manifests', 'Update manifests.', function(version) { var _ = grunt.util._, pkg = grunt.file.readJSON('package.json'), bower = grunt.file.readJSON('bower.json'), jqueryPlugin = grunt.file.readJSON('typeahead.js.jquery.json'); bower = JSON.stringify(_.extend(bower, { name: pkg.name, version: version }), null, 2); jqueryPlugin = JSON.stringify(_.extend(jqueryPlugin, { name: pkg.name, title: pkg.name, version: version, author: pkg.author, description: pkg.description, keywords: pkg.keywords, homepage: pkg.homepage, bugs: pkg.bugs, maintainers: pkg.contributors }), null, 2); pkg = JSON.stringify(_.extend(pkg, { version: version }), null, 2); grunt.file.write('package.json', pkg); grunt.file.write('bower.json', bower); grunt.file.write('typeahead.js.jquery.json', jqueryPlugin); }); // aliases // ------- grunt.registerTask('default', 'build'); grunt.registerTask('server', 'connect:server'); grunt.registerTask('lint', 'jshint'); grunt.registerTask('dev', ['build', 'concurrent:dev']); grunt.registerTask('build', [ 'uglify:concatBloodhound', 'uglify:concatTypeahead', 'umd:bloodhound', 'umd:typeahead', 'uglify:bloodhound', 'uglify:bloodhoundMin', 'uglify:typeahead', 'uglify:typeaheadMin', 'uglify:bundle', 'uglify:bundleMin', 'sed:version' ]); // load tasks // ---------- grunt.loadNpmTasks('grunt-umd'); grunt.loadNpmTasks('grunt-sed'); grunt.loadNpmTasks('grunt-exec'); grunt.loadNpmTasks('grunt-step'); grunt.loadNpmTasks('grunt-concurrent'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-connect'); }; typeahead.js-0.11.1/LICENSE000066400000000000000000000020451251733230600151610ustar00rootroot00000000000000Copyright (c) 2013-2014 Twitter, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. typeahead.js-0.11.1/README.md000066400000000000000000000134331251733230600154360ustar00rootroot00000000000000[![build status](https://secure.travis-ci.org/twitter/typeahead.js.svg?branch=master)](http://travis-ci.org/twitter/typeahead.js) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) [typeahead.js][gh-page] ======================= Inspired by [twitter.com]'s autocomplete search functionality, typeahead.js is a flexible JavaScript library that provides a strong foundation for building robust typeaheads. The typeahead.js library consists of 2 components: the suggestion engine, [Bloodhound], and the UI view, [Typeahead]. The suggestion engine is responsible for computing suggestions for a given query. The UI view is responsible for rendering suggestions and handling DOM interactions. Both components can be used separately, but when used together, they can provide a rich typeahead experience. [gh-page]: http://twitter.github.io/typeahead.js/ [twitter.com]: https://twitter.com [Bloodhound]: https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md [Typeahead]: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md Getting Started --------------- How you acquire typeahead.js is up to you. Preferred method: * Install with [Bower]: `$ bower install typeahead.js` Other methods: * [Download zipball of latest release][zipball]. * Download the latest dist files individually: * *[bloodhound.js]* (standalone suggestion engine) * *[typeahead.jquery.js]* (standalone UI view) * *[typeahead.bundle.js]* (*bloodhound.js* + *typeahead.jquery.js*) * *[typeahead.bundle.min.js]* **Note:** both *bloodhound.js* and *typeahead.jquery.js* have a dependency on [jQuery] 1.9+. [Bower]: http://bower.io/ [zipball]: http://twitter.github.com/typeahead.js/releases/latest/typeahead.js.zip [bloodhound.js]: http://twitter.github.com/typeahead.js/releases/latest/bloodhound.js [typeahead.jquery.js]: http://twitter.github.com/typeahead.js/releases/latest/typeahead.jquery.js [typeahead.bundle.js]: http://twitter.github.com/typeahead.js/releases/latest/typeahead.bundle.js [typeahead.bundle.min.js]: http://twitter.github.com/typeahead.js/releases/latest/typeahead.bundle.min.js [jQuery]: http://jquery.com/ Documentation ------------- * [Typeahead Docs] * [Bloodhound Docs] [Typeahead Docs]: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md [Bloodhound Docs]: https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md Examples -------- For some working examples of typeahead.js, visit the [examples page]. [examples page]: http://twitter.github.io/typeahead.js/examples Browser Support --------------- * Chrome * Firefox 3.5+ * Safari 4+ * Internet Explorer 8+ * Opera 11+ **NOTE:** typeahead.js is not tested on mobile browsers. Customer Support ---------------- For general questions about typeahead.js, tweet at [@typeahead]. For technical questions, you should post a question on [Stack Overflow] and tag it with [typeahead.js][so tag]. [Stack Overflow]: http://stackoverflow.com/ [@typeahead]: https://twitter.com/typeahead [so tag]: http://stackoverflow.com/questions/tagged/typeahead.js Issues ------ Discovered a bug? Please create an issue here on GitHub! https://github.com/twitter/typeahead.js/issues Versioning ---------- For transparency and insight into our release cycle, releases will be numbered with the following format: `..` And constructed with the following guidelines: * Breaking backwards compatibility bumps the major * New additions without breaking backwards compatibility bumps the minor * Bug fixes and misc changes bump the patch For more information on semantic versioning, please visit http://semver.org/. Testing ------- Tests are written using [Jasmine] and ran with [Karma]. To run the test suite with PhantomJS, run `$ npm test`. [Jasmine]: http://jasmine.github.io/ [Karma]: http://karma-runner.github.io/ Developers ---------- If you plan on contributing to typeahead.js, be sure to read the [contributing guidelines]. A good starting place for new contributors are issues labeled with [entry-level]. Entry-level issues tend to require minor changes and provide developers a chance to get more familiar with typeahead.js before taking on more challenging work. In order to build and test typeahead.js, you'll need to install its dev dependencies (`$ npm install`) and have [grunt-cli] installed (`$ npm install -g grunt-cli`). Below is an overview of the available Grunt tasks that'll be useful in development. * `grunt build` – Builds *typeahead.js* from source. * `grunt lint` – Runs source and test files through JSHint. * `grunt watch` – Rebuilds *typeahead.js* whenever a source file is modified. * `grunt server` – Serves files from the root of typeahead.js on localhost:8888. Useful for using *test/playground.html* for debugging/testing. * `grunt dev` – Runs `grunt watch` and `grunt server` in parallel. [contributing guidelines]: https://github.com/twitter/typeahead.js/blob/master/CONTRIBUTING.md [entry-level]: https://github.com/twitter/typeahead.js/issues?&labels=entry-level&state=open [grunt-cli]: https://github.com/gruntjs/grunt-cli Maintainers ----------- * **Jake Harding** * [@JakeHarding](https://twitter.com/JakeHarding) * [GitHub](https://github.com/jharding) * **You?** Authors ------- * **Jake Harding** * [@JakeHarding](https://twitter.com/JakeHarding) * [GitHub](https://github.com/jharding) * **Veljko Skarich** * [@vskarich](https://twitter.com/vskarich) * [GitHub](https://github.com/vskarich) * **Tim Trueman** * [@timtrueman](https://twitter.com/timtrueman) * [GitHub](https://github.com/timtrueman) License ------- Copyright 2013 Twitter, Inc. Licensed under the MIT License typeahead.js-0.11.1/bower.json000066400000000000000000000003671251733230600161720ustar00rootroot00000000000000{ "name": "typeahead.js", "version": "0.11.1", "main": "dist/typeahead.bundle.js", "dependencies": { "jquery": ">=1.7" }, "devDependencies": { "jquery": "~1.7", "jasmine-ajax": "~1.3.1", "jasmine-jquery": "~1.5.2" } }typeahead.js-0.11.1/composer.json000066400000000000000000000007531251733230600167020ustar00rootroot00000000000000{ "name": "twitter/typeahead.js", "description": "fast and fully-featured autocomplete library", "keywords": ["typeahead", "autocomplete"], "homepage": "http://twitter.github.com/typeahead.js", "authors": [ { "name": "Twitter Inc.", "homepage": "https://twitter.com/twitteross" } ], "support": { "issues": "https://github.com/twitter/typeahead.js/issues" }, "author": "Twitter Inc.", "license": "MIT" } typeahead.js-0.11.1/doc/000077500000000000000000000000001251733230600147205ustar00rootroot00000000000000typeahead.js-0.11.1/doc/bloodhound.md000066400000000000000000000230331251733230600174000ustar00rootroot00000000000000Bloodhound ========== Bloodhound is the typeahead.js suggestion engine. Bloodhound is robust, flexible, and offers advanced functionalities such as prefetching, intelligent caching, fast lookups, and backfilling with remote data. Table of Contents ----------------- * [Features](#features) * [Usage](#usage) * [API](#api) * [Options](#options) * [Prefetch](#prefetch) * [Remote](#remote) Features -------- * Works with hardcoded data * Prefetches data on initialization to reduce suggestion latency * Uses local storage intelligently to cut down on network requests * Backfills suggestions from a remote source * Rate-limits and caches network requests to remote sources to lighten the load Usage ----- ### API * [`new Bloodhound(options)`](#new-bloodhoundoptions) * [`Bloodhound.noConflict()`](#bloodhoundnoconflict) * [`Bloodhound#initialize(reinitialize)`](#bloodhoundinitializereinitialize) * [`Bloodhound#add(data)`](#bloodhoundadddata) * [`Bloodhound#get(ids)`](#bloodhoundgetids) * [`Bloodhound#search(query, sync, async)`](#bloodhoundsearchquery-sync-async) * [`Bloodhound#clear()`](#bloodhoundclear) #### new Bloodhound(options) The constructor function. It takes an [options hash](#options) as its only argument. ```javascript var engine = new Bloodhound({ local: ['dog', 'pig', 'moose'], queryTokenizer: Bloodhound.tokenizers.whitespace, datumTokenizer: Bloodhound.tokenizers.whitespace }); ``` #### Bloodhound.noConflict() Returns a reference to `Bloodhound` and reverts `window.Bloodhound` to its previous value. Can be used to avoid naming collisions. ```javascript var Dachshund = Bloodhound.noConflict(); ``` #### Bloodhound#initialize(reinitialize) Kicks off the initialization of the suggestion engine. Initialization entails adding the data provided by `local` and `prefetch` to the internal search index as well as setting up transport mechanism used by `remote`. Before `#initialize` is called, the `#get` and `#search` methods will effectively be no-ops. Note, unless the `initialize` option is `false`, this method is implicitly called by the constructor. ```javascript var engine = new Bloodhound({ initialize: false, local: ['dog', 'pig', 'moose'], queryTokenizer: Bloodhound.tokenizers.whitespace, datumTokenizer: Bloodhound.tokenizers.whitespace }); var promise = engine.initialize(); promise .done(function() { console.log('ready to go!'); }) .fail(function() { console.log('err, something went wrong :('); }); ``` After initialization, how subsequent invocations of `#initialize` behave depends on the `reinitialize` argument. If `reinitialize` is falsy, the method will not execute the initialization logic and will just return the same jQuery promise returned by the initial invocation. If `reinitialize` is truthy, the method will behave as if it were being called for the first time. ```javascript var promise1 = engine.initialize(); var promise2 = engine.initialize(); var promise3 = engine.initialize(true); assert(promise1 === promise2); assert(promise3 !== promise1 && promise3 !== promise2); ``` [jQuery promise]: http://api.jquery.com/Types/#Promise #### Bloodhound#add(data) Takes one argument, `data`, which is expected to be an array. The data passed in will get added to the internal search index. ```javascript engine.add([{ val: 'one' }, { val: 'two' }]); ``` #### Bloodhound#get(ids) Returns the data in the local search index corresponding to `ids`. ```javascript var engine = new Bloodhound({ local: [{ id: 1, name: 'dog' }, { id: 2, name: 'pig' }], identify: function(obj) { return obj.id; }, queryTokenizer: Bloodhound.tokenizers.whitespace, datumTokenizer: Bloodhound.tokenizers.whitespace }); engine.get([1, 3]); // [{ id: 1, name: 'dog' }, null] ``` #### Bloodhound#search(query, sync, async) Returns the data that matches `query`. Matches found in the local search index will be passed to the `sync` callback. If the data passed to `sync` doesn't contain at least `sufficient` number of datums, `remote` data will be requested and then passed to the `async` callback. ```javascript bloodhound.get(myQuery, sync, async); function sync(datums) { console.log('datums from `local`, `prefetch`, and `#add`'); console.log(datums); } function async(datums) { console.log('datums from `remote`'); console.log(datums); } ``` #### Bloodhound#clear() Clears the internal search index that's powered by `local`, `prefetch`, and `#add`. ```javascript engine.clear(); ``` ### Options When instantiating a Bloodhound suggestion engine, there are a number of options you can configure. * `datumTokenizer` – A function with the signature `(datum)` that transforms a datum into an array of string tokens. **Required**. * `queryTokenizer` – A function with the signature `(query)` that transforms a query into an array of string tokens. **Required**. * `initialize` – If set to `false`, the Bloodhound instance will not be implicitly initialized by the constructor function. Defaults to `true`. * `identify` – Given a datum, this function is expected to return a unique id for it. Defaults to `JSON.stringify`. Note that it is **highly recommended** to override this option. * `sufficient` – If the number of datums provided from the internal search index is less than `sufficient`, `remote` will be used to backfill search requests triggered by calling `#search`. Defaults to `5`. * `sorter` – A [compare function] used to sort data returned from the internal search index. * `local` – An array of data or a function that returns an array of data. The data will be added to the internal search index when `#initialize` is called. * `prefetch` – Can be a URL to a JSON file containing an array of data or, if more configurability is needed, a [prefetch options hash](#prefetch). * `remote` – Can be a URL to fetch data from when the data provided by the internal search index is insufficient or, if more configurability is needed, a [remote options hash](#remote). [compare function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort ### Prefetch Prefetched data is fetched and processed on initialization. If the browser supports local storage, the processed data will be cached there to prevent additional network requests on subsequent page loads. **WARNING:** While it's possible to get away with it for smaller data sets, prefetched data isn't meant to contain entire sets of data. Rather, it should act as a first-level cache. Ignoring this warning means you'll run the risk of hitting [local storage limits]. When configuring `prefetch`, the following options are available. * `url` – The URL prefetch data should be loaded from. **Required.** * `cache` – If `false`, will not attempt to read or write to local storage and will always load prefetch data from `url` on initialization. Defaults to `true`. * `ttl` – The time (in milliseconds) the prefetched data should be cached in local storage. Defaults to `86400000` (1 day). * `cacheKey` – The key that data will be stored in local storage under. Defaults to value of `url`. * `thumbprint` – A string used for thumbprinting prefetched data. If this doesn't match what's stored in local storage, the data will be refetched. * `prepare` – A function that provides a hook to allow you to prepare the settings object passed to `transport` when a request is about to be made. The function signature should be `prepare(settings)` where `settings` is the default settings object created internally by the Bloodhound instance. The `prepare` function should return a settings object. Defaults to the [identity function]. * `transform` – A function with the signature `transform(response)` that allows you to transform the prefetch response before the Bloodhound instance operates on it. Defaults to the [identity function]. [local storage limits]: http://stackoverflow.com/a/2989317 [identity function]: http://en.wikipedia.org/wiki/Identity_function ### Remote Bloodhound only goes to the network when the internal search engine cannot provide a sufficient number of results. In order to prevent an obscene number of requests being made to the remote endpoint, requests are rate-limited. When configuring `remote`, the following options are available. * `url` – The URL remote data should be loaded from. **Required.** * `prepare` – A function that provides a hook to allow you to prepare the settings object passed to `transport` when a request is about to be made. The function signature should be `prepare(query, settings)`, where `query` is the query `#search` was called with and `settings` is the default settings object created internally by the Bloodhound instance. The `prepare` function should return a settings object. Defaults to the [identity function]. * `wildcard` – A convenience option for `prepare`. If set, `prepare` will be a function that replaces the value of this option in `url` with the URI encoded query. * `rateLimitBy` – The method used to rate-limit network requests. Can be either `debounce` or `throttle`. Defaults to `debounce`. * `rateLimitWait` – The time interval in milliseconds that will be used by `rateLimitBy`. Defaults to `300`. * `transform` – A function with the signature `transform(response)` that allows you to transform the remote response before the Bloodhound instance operates on it. Defaults to the [identity function]. [identity function]: http://en.wikipedia.org/wiki/Identity_function typeahead.js-0.11.1/doc/jquery_typeahead.md000066400000000000000000000240641251733230600206130ustar00rootroot00000000000000jQuery#typeahead ---------------- The UI component of typeahead.js is available as a jQuery plugin. It's responsible for rendering suggestions and handling DOM interactions. Table of Contents ----------------- * [Features](#features) * [Usage](#usage) * [API](#api) * [Options](#options) * [Datasets](#datasets) * [Custom Events](#custom-events) * [Class Names](#class-names) Features -------- * Displays suggestions to end-users as they type * Shows top suggestion as a hint (i.e. background text) * Supports custom templates to allow for UI flexibility * Works well with RTL languages and input method editors * Highlights query matches within the suggestion * Triggers custom events to encourage extensibility Usage ----- ### API * [`jQuery#typeahead(options, [*datasets])`](#jquerytypeaheadoptions-datasets) * [`jQuery#typeahead('val')`](#jquerytypeaheadval) * [`jQuery#typeahead('val', val)`](#jquerytypeaheadval-val) * [`jQuery#typeahead('destroy')`](#jquerytypeaheaddestroy) * [`jQuery.fn.typeahead.noConflict()`](#jqueryfntypeaheadnoconflict) #### jQuery#typeahead(options, [\*datasets]) For a given `input[type="text"]`, enables typeahead functionality. `options` is an options hash that's used for configuration. Refer to [Options](#options) for more info regarding the available configs. Subsequent arguments (`*datasets`), are individual option hashes for datasets. For more details regarding datasets, refer to [Datasets](#datasets). ```javascript $('.typeahead').typeahead({ minLength: 3, highlight: true }, { name: 'my-dataset', source: mySource }); ``` #### jQuery#typeahead('val') Returns the current value of the typeahead. The value is the text the user has entered into the `input` element. ```javascript var myVal = $('.typeahead').typeahead('val'); ``` #### jQuery#typeahead('val', val) Sets the value of the typeahead. This should be used in place of `jQuery#val`. ```javascript $('.typeahead').typeahead('val', myVal); ``` #### jQuery#typeahead('open') Opens the suggestion menu. ```javascript $('.typeahead').typeahead('open'); ``` #### jQuery#typeahead('close') Closes the suggestion menu. ```javascript $('.typeahead').typeahead('close'); ``` #### jQuery#typeahead('destroy') Removes typeahead functionality and reverts the `input` element back to its original state. ```javascript $('.typeahead').typeahead('destroy'); ``` #### jQuery.fn.typeahead.noConflict() Returns a reference to the typeahead plugin and reverts `jQuery.fn.typeahead` to its previous value. Can be used to avoid naming collisions. ```javascript var typeahead = jQuery.fn.typeahead.noConflict(); jQuery.fn._typeahead = typeahead; ``` ### Options When initializing a typeahead, there are a number of options you can configure. * `highlight` – If `true`, when suggestions are rendered, pattern matches for the current query in text nodes will be wrapped in a `strong` element with its class set to `{{classNames.highlight}}`. Defaults to `false`. * `hint` – If `false`, the typeahead will not show a hint. Defaults to `true`. * `minLength` – The minimum character length needed before suggestions start getting rendered. Defaults to `1`. * `classNames` – For overriding the default class names used. See [Class Names](#class-names) for more details. ### Datasets A typeahead is composed of one or more datasets. When an end-user modifies the value of a typeahead, each dataset will attempt to render suggestions for the new value. For most use cases, one dataset should suffice. It's only in the scenario where you want rendered suggestions to be grouped based on some sort of categorical relationship that you'd need to use multiple datasets. For example, on twitter.com, the search typeahead groups results into recent searches, trends, and accounts – that would be a great use case for using multiple datasets. Datasets can be configured using the following options. * `source` – The backing data source for suggestions. Expected to be a function with the signature `(query, syncResults, asyncResults)`. `syncResults` should be called with suggestions computed synchronously and `asyncResults` should be called with suggestions computed asynchronously (e.g. suggestions that come for an AJAX request). `source` can also be a Bloodhound instance. **Required**. * `async` – Lets the dataset know if async suggestions should be expected. If not set, this information is inferred from the signature of `source` i.e. if the `source` function expects 3 arguments, `async` will be set to `true`. * `name` – The name of the dataset. This will be appended to `{{classNames.dataset}}-` to form the class name of the containing DOM element. Must only consist of underscores, dashes, letters (`a-z`), and numbers. Defaults to a random number. * `limit` – The max number of suggestions to be displayed. Defaults to `5`. * `display` – For a given suggestion, determines the string representation of it. This will be used when setting the value of the input control after a suggestion is selected. Can be either a key string or a function that transforms a suggestion object into a string. Defaults to stringifying the suggestion. * `templates` – A hash of templates to be used when rendering the dataset. Note a precompiled template is a function that takes a JavaScript object as its first argument and returns a HTML string. * `notFound` – Rendered when `0` suggestions are available for the given query. Can be either a HTML string or a precompiled template. If it's a precompiled template, the passed in context will contain `query`. * `pending` - Rendered when `0` synchronous suggestions are available but asynchronous suggestions are expected. Can be either a HTML string or a precompiled template. If it's a precompiled template, the passed in context will contain `query`. * `header`– Rendered at the top of the dataset when suggestions are present. Can be either a HTML string or a precompiled template. If it's a precompiled template, the passed in context will contain `query` and `suggestions`. * `footer`– Rendered at the bottom of the dataset when suggestions are present. Can be either a HTML string or a precompiled template. If it's a precompiled template, the passed in context will contain `query` and `suggestions`. * `suggestion` – Used to render a single suggestion. If set, this has to be a precompiled template. The associated suggestion object will serve as the context. Defaults to the value of `display` wrapped in a `div` tag i.e. `
{{value}}
`. ### Custom Events The following events get triggered on the input element during the life-cycle of a typeahead. * `typeahead:active` – Fired when the typeahead moves to active state. * `typeahead:idle` – Fired when the typeahead moves to idle state. * `typeahead:open` – Fired when the results container is opened. * `typeahead:close` – Fired when the results container is closed. * `typeahead:change` – Normalized version of the native [`change` event]. Fired when input loses focus and the value has changed since it originally received focus. * `typeahead:render` – Fired when suggestions are rendered for a dataset. The event handler will be invoked with 4 arguments: the jQuery event object, the suggestions that were rendered, a flag indicating whether the suggestions were fetched asynchronously, and the name of the dataset the rendering occurred in. * `typeahead:select` – Fired when a suggestion is selected. The event handler will be invoked with 2 arguments: the jQuery event object and the suggestion object that was selected. * `typeahead:autocomplete` – Fired when a autocompletion occurs. The event handler will be invoked with 2 arguments: the jQuery event object and the suggestion object that was used for autocompletion. * `typeahead:cursorchange` – Fired when the results container cursor moves. The event handler will be invoked with 2 arguments: the jQuery event object and the suggestion object that was moved to. * `typeahead:asyncrequest` – Fired when an async request for suggestions is sent. The event handler will be invoked with 3 arguments: the jQuery event object, the current query, and the name of the dataset the async request belongs to. * `typeahead:asynccancel` – Fired when an async request is cancelled. The event handler will be invoked with 3 arguments: the jQuery event object, the current query, and the name of the dataset the async request belonged to. * `typeahead:asyncreceive` – Fired when an async request completes. The event handler will be invoked with 3 arguments: the jQuery event object, the current query, and the name of the dataset the async request belongs to. Example usage: ``` $('.typeahead').bind('typeahead:select', function(ev, suggestion) { console.log('Selection: ' + suggestion); }); ``` **NOTE**: Every event does not supply the same arguments. See the event descriptions above for details on each event's argument list. [`change` event]: https://developer.mozilla.org/en-US/docs/Web/Events/change ### Class Names * `input` - Added to input that's initialized into a typeahead. Defaults to `tt-input`. * `hint` - Added to hint input. Defaults to `tt-hint`. * `menu` - Added to menu element. Defaults to `tt-menu`. * `dataset` - Added to dataset elements. to Defaults to `tt-dataset`. * `suggestion` - Added to suggestion elements. Defaults to `tt-suggestion`. * `empty` - Added to menu element when it contains no content. Defaults to `tt-empty`. * `open` - Added to menu element when it is opened. Defaults to `tt-open`. * `cursor` - Added to suggestion element when menu cursor moves to said suggestion. Defaults to `tt-cursor`. * `highlight` - Added to the element that wraps highlighted text. Defaults to `tt-highlight`. To override any of these defaults, you can use the `classNames` option: ```javascript $('.typeahead').typeahead({ classNames: { input: 'Typeahead-input', hint: 'Typeahead-hint', selectable: 'Typeahead-selectable' } }); ``` typeahead.js-0.11.1/doc/migration/000077500000000000000000000000001251733230600167115ustar00rootroot00000000000000typeahead.js-0.11.1/doc/migration/0.10.0.md000066400000000000000000000167661251733230600177670ustar00rootroot00000000000000Migrating to typeahead.js v0.10.0 ================================= Preamble -------- v0.10.0 of typeahead.js ended up being almost a complete rewrite. Many things stayed the same, but there were a handful of changes you need to be aware of if you plan on upgrading from an older version. This document aims to call out those changes and explain what you need to do in order to have an painless upgrade. Notable Changes ---------------- ### First Argument to the jQuery Plugin In v0.10.0, the first argument to `jQuery#typeahead` is an options hash that can be used to configure the behavior of the typeahead. This is in contrast to previous versions where `jQuery#typeahead` expected just a series of datasets to be passed to it: ```javascript // pre-v0.10.0 $('.typeahead').typeahead(myDataset); // v0.10.0 $('.typeahead').typeahead({ highlight: true, hint: false }, myDataset); ``` If you're fine with the default configuration, you can just pass `null` as the first argument: ```javascript $('.typeahead').typeahead(null, myDataset); ``` ### Bloodhound Suggestion Engine The most notable change in v0.10.0 is that typeahead.js has been decomposed into a suggestion engine and a UI view. As part of this change, the way you configure datasets has changed. Previously, a dataset config would have looked like: ```javascript { valueKey: 'num', local: [{ num: 'one' }, { num: 'two' }, { num: 'three' }], prefetch: '/prefetch', remote: '/remote?q=%QUERY' } ``` In v0.10.0, an equivalent dataset config would look like: ```javascript { displayKey: 'num', source: mySource } ``` As you can see, `local`, `prefetch`, and `remote` are no longer defined at the dataset level. Instead, all you set in a dataset config is `source`. `source` is expected to be a function with the signature `function(query, callback)`. When a typeahead's query changes, suggestions will be requested from `source`. It's expected `source` will compute the suggestion set and invoke `callback` with an array of suggestion objects. The typeahead will then go on to render those suggestions. If you're wondering if you can still configure `local`, `prefetch`, and `remote`, don't worry, that's where the Bloodhound suggestion engine comes in. Here's how you would define `mySource` which was referenced in the previous code snippet: ``` var mySource = new Bloodhound({ datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.num); }, queryTokenizer: Bloodhound.tokenizers.whitespace, local: [{ num: 'one' }, { num: 'two' }, { num: 'three' }], prefetch: '/prefetch', remote: '/remote?q=%QUERY' }); // this kicks off the loading and processing of local and prefetch data // the suggestion engine will be useless until it is initialized mySource.initialize(); ``` In the above snippet, a Bloodhound suggestion engine is initialized and that's what will be used as the source of your dataset. There's still one last thing that needs to be done before you can use a Bloodhound suggestion engine as the source of a dataset. Because datasets expect `source` to be function, the Bloodhound instance needs to be wrapped in an adapter so it can meet that expectation. ``` mySource = mySource.ttAdapter(); ``` Put it all together: ```javascript var mySource = new Bloodhound({ datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.num); }, queryTokenizer: Bloodhound.tokenizers.whitespace, local: [{ num: 'one' }, { num: 'two' }, { num: 'three' }], prefetch: '/prefetch', remote: '/remote?q=%QUERY' }); mySource.initialize(); $('.typeahead').typeahead(null, { displayKey: 'num', source: mySource.ttAdapter() }); ``` ### Tokenization Methods Must Be Provided The Bloodhound suggestion engine is token-based, so how datums and queries are tokenized plays a vital role in the quality of search results. Pre-v0.10.0, it was not possible to configure the tokenization method. Starting in v0.10.0, you **must** specify how you want datums and queries tokenized. The most common tokenization methods split a given string on whitespace or non-word characters. Bloodhound provides implementations for those methods out of the box: ```javascript // returns ['one', 'two', 'twenty-five'] Bloodhound.tokenizers.whitespace(' one two twenty-five'); // returns ['one', 'two', 'twenty', 'five'] Bloodhound.tokenizers.nonword(' one two twenty-five'); ``` For query tokenization, you'll probably want to use one of the above methods. For datum tokenization, this is where you may want to do something a tad bit more advanced. For datums, sometimes you want tokens to be dervied from more than one property. For example, if you were building a search engine for GitHub repositories, it'd probably be wise to have tokens derived from the repo's name, owner, and primary language: ```javascript var repos = [ { name: 'example', owner: 'John Doe', language: 'JavaScript' }, { name: 'another example', owner: 'Joe Doe', language: 'Scala' } ]; function customTokenizer(datum) { var nameTokens = Bloodhound.tokenizers.whitespace(datum.name); var ownerTokens = Bloodhound.tokenizers.whitespace(datum.owner); var languageTokens = Bloodhound.tokenizers.whitespace(datum.language); return nameTokens.concat(ownerTokens).concat(languageTokens); } ``` There may also be the scenario where you want datum tokenization to be performed on the backend. The best way to do that is to just add a property to your datums that contains those tokens. You can then provide a tokenizer that just returns the already existing tokens: ```javascript var sports = [ { value: 'football', tokens: ['football', 'pigskin'] }, { value: 'basketball', tokens: ['basketball', 'bball'] } ]; function customTokenizer(datum) { return datum.tokens; } ``` There are plenty of other ways you could go about tokenizing datums, it really just depends on what you are trying to accomplish. ### String Datums Are No Longer Supported Dropping support for string datums was a difficult choice, but in the end it made sense for a number of reasons. If you still want to hydrate the suggestion engine with string datums, you'll need to use the `filter` function: ```javascript var engine = new Bloodhound({ prefetch: { url: '/data', filter: function(data) { // assume data is an array of strings e.g. ['one', 'two', 'three'] return $.map(data, function(str) { return { value: str }; }); }, datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.value); }, queryTokenizer: Bloodhound.tokenizers.whitespace } }); ``` ### Precompiled Templates Are Now Required In previous versions of typeahead.js, you could specify a string template along with the templating engine that should be used to compile/render it. In v0.10.0, you can no longer specify templating engines; instead you must provide precompiled templates. Precompiled templates are functions that take one argument: the context the template should be rendered with. Most of the popular templating engines allow for the creation of precompiled templates. For example, you can generate one using Handlebars by doing the following: ```javascript var precompiledTemplate = Handlebars.compile('

{{value}}

'); ``` [Handlebars]: http://handlebarsjs.com/ ### CSS Class Changes `tt-is-under-cursor` is now `tt-cursor` - Applied to a hovered-on suggestion (either via cursor or arrow key). `tt-query` is now `tt-input` - Applied to the typeahead input field. Something Missing? ------------------ If something is missing from this migration guide, pull requests are accepted :) typeahead.js-0.11.1/karma.conf.js000066400000000000000000000024741251733230600165370ustar00rootroot00000000000000module.exports = function(config) { config.set({ basePath: '', preprocessors: { 'src/**/*.js': 'coverage' }, reporters: ['progress', 'coverage'], browsers: ['Chrome'], frameworks: ['jasmine'], coverageReporter: { type: 'html', dir: 'test/coverage/' }, files: [ 'bower_components/jquery/jquery.js', 'src/common/utils.js', 'src/bloodhound/version.js', 'src/bloodhound/tokenizers.js', 'src/bloodhound/lru_cache.js', 'src/bloodhound/persistent_storage.js', 'src/bloodhound/transport.js', 'src/bloodhound/remote.js', 'src/bloodhound/prefetch.js', 'src/bloodhound/search_index.js', 'src/bloodhound/options_parser.js', 'src/bloodhound/bloodhound.js', 'src/typeahead/www.js', 'src/typeahead/event_bus.js', 'src/typeahead/event_emitter.js', 'src/typeahead/highlight.js', 'src/typeahead/input.js', 'src/typeahead/dataset.js', 'src/typeahead/menu.js', 'src/typeahead/default_menu.js', 'src/typeahead/typeahead.js', 'src/typeahead/plugin.js', 'test/fixtures/**/*', 'bower_components/jasmine-jquery/lib/jasmine-jquery.js', 'bower_components/jasmine-ajax/lib/mock-ajax.js', 'test/helpers/**/*', 'test/**/*_spec.js' ] }); }; typeahead.js-0.11.1/package.json000066400000000000000000000034111251733230600164400ustar00rootroot00000000000000{ "name": "typeahead.js", "description": "fast and fully-featured autocomplete library", "keywords": [ "typeahead", "autocomplete" ], "homepage": "http://twitter.github.com/typeahead.js", "bugs": "https://github.com/twitter/typeahead.js/issues", "repository": { "type": "git", "url": "https://github.com/twitter/typeahead.js.git" }, "author": { "name": "Twitter, Inc.", "url": "https://twitter.com/twitteross" }, "contributors": [ { "name": "Jake Harding", "url": "https://twitter.com/JakeHarding" }, { "name": "Tim Trueman", "url": "https://twitter.com/timtrueman" }, { "name": "Veljko Skarich", "url": "https://twitter.com/vskarich" } ], "dependencies": { "jquery": ">=1.7" }, "devDependencies": { "chai": "^1.9.1", "colors": "^0.6.2", "grunt": "~0.4", "grunt-concurrent": "^0.5.0", "grunt-contrib-clean": "~0.4.0", "grunt-contrib-concat": "~0.1", "grunt-contrib-connect": "~0.1", "grunt-contrib-jshint": "~0.8", "grunt-contrib-uglify": "~0.2.6", "grunt-contrib-watch": "~0.2", "grunt-exec": "~0.4.5", "grunt-sed": "~0.1", "grunt-step": "~0.2.0", "grunt-umd": "^2.3.3", "karma": "^0.12.22", "karma-chrome-launcher": "^0.1.4", "karma-coverage": "^0.2.6", "karma-firefox-launcher": "^0.1.3", "karma-jasmine": "^0.1.5", "karma-opera-launcher": "^0.1.0", "karma-phantomjs-launcher": "^0.1.4", "karma-safari-launcher": "^0.1.1", "mocha": "^1.20.1", "semver": "~1.1.3", "underscore": "^1.6.0", "yiewd": "^0.5.0" }, "scripts": { "test": "./node_modules/karma/bin/karma start --single-run --browsers PhantomJS" }, "version": "0.11.1", "main": "dist/typeahead.bundle.js" }typeahead.js-0.11.1/src/000077500000000000000000000000001251733230600147425ustar00rootroot00000000000000typeahead.js-0.11.1/src/bloodhound/000077500000000000000000000000001251733230600170775ustar00rootroot00000000000000typeahead.js-0.11.1/src/bloodhound/bloodhound.js000066400000000000000000000106621251733230600215770ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Bloodhound = (function() { 'use strict'; var old; old = window && window.Bloodhound; // constructor // ----------- function Bloodhound(o) { o = oParser(o); this.sorter = o.sorter; this.identify = o.identify; this.sufficient = o.sufficient; this.local = o.local; this.remote = o.remote ? new Remote(o.remote) : null; this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null; // the backing data structure used for fast pattern matching this.index = new SearchIndex({ identify: this.identify, datumTokenizer: o.datumTokenizer, queryTokenizer: o.queryTokenizer }); // hold off on intialization if the intialize option was explicitly false o.initialize !== false && this.initialize(); } // static methods // -------------- Bloodhound.noConflict = function noConflict() { window && (window.Bloodhound = old); return Bloodhound; }; Bloodhound.tokenizers = tokenizers; // instance methods // ---------------- _.mixin(Bloodhound.prototype, { // ### super secret stuff used for integration with jquery plugin __ttAdapter: function ttAdapter() { var that = this; return this.remote ? withAsync : withoutAsync; function withAsync(query, sync, async) { return that.search(query, sync, async); } function withoutAsync(query, sync) { return that.search(query, sync); } }, // ### private _loadPrefetch: function loadPrefetch() { var that = this, deferred, serialized; deferred = $.Deferred(); if (!this.prefetch) { deferred.resolve(); } else if (serialized = this.prefetch.fromCache()) { this.index.bootstrap(serialized); deferred.resolve(); } else { this.prefetch.fromNetwork(done); } return deferred.promise(); function done(err, data) { if (err) { return deferred.reject(); } that.add(data); that.prefetch.store(that.index.serialize()); deferred.resolve(); } }, _initialize: function initialize() { var that = this, deferred; // in case this is a reinitialization, clear previous data this.clear(); (this.initPromise = this._loadPrefetch()) .done(addLocalToIndex); // local must be added to index after prefetch return this.initPromise; function addLocalToIndex() { that.add(that.local); } }, // ### public initialize: function initialize(force) { return !this.initPromise || force ? this._initialize() : this.initPromise; }, // TODO: before initialize what happens? add: function add(data) { this.index.add(data); return this; }, get: function get(ids) { ids = _.isArray(ids) ? ids : [].slice.call(arguments); return this.index.get(ids); }, search: function search(query, sync, async) { var that = this, local; local = this.sorter(this.index.search(query)); // return a copy to guarantee no changes within this scope // as this array will get used when processing the remote results sync(this.remote ? local.slice() : local); if (this.remote && local.length < this.sufficient) { this.remote.get(query, processRemote); } else if (this.remote) { // #149: prevents outdated rate-limited requests from being sent this.remote.cancelLastRequest(); } return this; function processRemote(remote) { var nonDuplicates = []; // exclude duplicates _.each(remote, function(r) { !_.some(local, function(l) { return that.identify(r) === that.identify(l); }) && nonDuplicates.push(r); }); async && async(nonDuplicates); } }, all: function all() { return this.index.all(); }, clear: function clear() { this.index.reset(); return this; }, clearPrefetchCache: function clearPrefetchCache() { this.prefetch && this.prefetch.clear(); return this; }, clearRemoteCache: function clearRemoteCache() { Transport.resetCache(); return this; }, // DEPRECATED: will be removed in v1 ttAdapter: function ttAdapter() { return this.__ttAdapter(); } }); return Bloodhound; })(); typeahead.js-0.11.1/src/bloodhound/lru_cache.js000066400000000000000000000037521251733230600213710ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ // inspired by https://github.com/jharding/lru-cache var LruCache = (function() { 'use strict'; function LruCache(maxSize) { this.maxSize = _.isNumber(maxSize) ? maxSize : 100; this.reset(); // if max size is less than 0, provide a noop cache if (this.maxSize <= 0) { this.set = this.get = $.noop; } } _.mixin(LruCache.prototype, { set: function set(key, val) { var tailItem = this.list.tail, node; // at capacity if (this.size >= this.maxSize) { this.list.remove(tailItem); delete this.hash[tailItem.key]; this.size--; } // writing over existing key if (node = this.hash[key]) { node.val = val; this.list.moveToFront(node); } // new key else { node = new Node(key, val); this.list.add(node); this.hash[key] = node; this.size++; } }, get: function get(key) { var node = this.hash[key]; if (node) { this.list.moveToFront(node); return node.val; } }, reset: function reset() { this.size = 0; this.hash = {}; this.list = new List(); } }); function List() { this.head = this.tail = null; } _.mixin(List.prototype, { add: function add(node) { if (this.head) { node.next = this.head; this.head.prev = node; } this.head = node; this.tail = this.tail || node; }, remove: function remove(node) { node.prev ? node.prev.next = node.next : this.head = node.next; node.next ? node.next.prev = node.prev : this.tail = node.prev; }, moveToFront: function(node) { this.remove(node); this.add(node); } }); function Node(key, val) { this.key = key; this.val = val; this.prev = this.next = null; } return LruCache; })(); typeahead.js-0.11.1/src/bloodhound/options_parser.js000066400000000000000000000107171251733230600225120ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var oParser = (function() { 'use strict'; return function parse(o) { var defaults, sorter; defaults = { initialize: true, identify: _.stringify, datumTokenizer: null, queryTokenizer: null, sufficient: 5, sorter: null, local: [], prefetch: null, remote: null }; o = _.mixin(defaults, o || {}); // throw error if required options are not set !o.datumTokenizer && $.error('datumTokenizer is required'); !o.queryTokenizer && $.error('queryTokenizer is required'); sorter = o.sorter; o.sorter = sorter ? function(x) { return x.sort(sorter); } : _.identity; o.local = _.isFunction(o.local) ? o.local() : o.local; o.prefetch = parsePrefetch(o.prefetch); o.remote = parseRemote(o.remote); return o; }; function parsePrefetch(o) { var defaults; if (!o) { return null; } defaults = { url: null, ttl: 24 * 60 * 60 * 1000, // 1 day cache: true, cacheKey: null, thumbprint: '', prepare: _.identity, transform: _.identity, transport: null }; // support basic (url) and advanced configuration o = _.isString(o) ? { url: o } : o; o = _.mixin(defaults, o); // throw error if required options are not set !o.url && $.error('prefetch requires url to be set'); // DEPRECATED: filter will be dropped in v1 o.transform = o.filter || o.transform; o.cacheKey = o.cacheKey || o.url; o.thumbprint = VERSION + o.thumbprint; o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; return o; } function parseRemote(o) { var defaults; if (!o) { return; } defaults = { url: null, cache: true, // leave undocumented prepare: null, replace: null, wildcard: null, limiter: null, rateLimitBy: 'debounce', rateLimitWait: 300, transform: _.identity, transport: null }; // support basic (url) and advanced configuration o = _.isString(o) ? { url: o } : o; o = _.mixin(defaults, o); // throw error if required options are not set !o.url && $.error('remote requires url to be set'); // DEPRECATED: filter will be dropped in v1 o.transform = o.filter || o.transform; o.prepare = toRemotePrepare(o); o.limiter = toLimiter(o); o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; delete o.replace; delete o.wildcard; delete o.rateLimitBy; delete o.rateLimitWait; return o; } function toRemotePrepare(o) { var prepare, replace, wildcard; prepare = o.prepare; replace = o.replace; wildcard = o.wildcard; if (prepare) { return prepare; } if (replace) { prepare = prepareByReplace; } else if (o.wildcard) { prepare = prepareByWildcard; } else { prepare = idenityPrepare; } return prepare; function prepareByReplace(query, settings) { settings.url = replace(settings.url, query); return settings; } function prepareByWildcard(query, settings) { settings.url = settings.url.replace(wildcard, encodeURIComponent(query)); return settings; } function idenityPrepare(query, settings) { return settings; } } function toLimiter(o) { var limiter, method, wait; limiter = o.limiter; method = o.rateLimitBy; wait = o.rateLimitWait; if (!limiter) { limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait); } return limiter; function debounce(wait) { return function debounce(fn) { return _.debounce(fn, wait); }; } function throttle(wait) { return function throttle(fn) { return _.throttle(fn, wait); }; } } function callbackToDeferred(fn) { return function wrapper(o) { var deferred = $.Deferred(); fn(o, onSuccess, onError); return deferred; function onSuccess(resp) { // defer in case fn is synchronous, otherwise done // and always handlers will be attached after the resolution _.defer(function() { deferred.resolve(resp); }); } function onError(err) { // defer in case fn is synchronous, otherwise done // and always handlers will be attached after the resolution _.defer(function() { deferred.reject(err); }); } }; } })(); typeahead.js-0.11.1/src/bloodhound/persistent_storage.js000066400000000000000000000061711251733230600233660ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var PersistentStorage = (function() { 'use strict'; var LOCAL_STORAGE; try { LOCAL_STORAGE = window.localStorage; // while in private browsing mode, some browsers make // localStorage available, but throw an error when used LOCAL_STORAGE.setItem('~~~', '!'); LOCAL_STORAGE.removeItem('~~~'); } catch (err) { LOCAL_STORAGE = null; } // constructor // ----------- function PersistentStorage(namespace, override) { this.prefix = ['__', namespace, '__'].join(''); this.ttlKey = '__ttl__'; this.keyMatcher = new RegExp('^' + _.escapeRegExChars(this.prefix)); // for testing purpose this.ls = override || LOCAL_STORAGE; // if local storage isn't available, everything becomes a noop !this.ls && this._noop(); } // instance methods // ---------------- _.mixin(PersistentStorage.prototype, { // ### private _prefix: function(key) { return this.prefix + key; }, _ttlKey: function(key) { return this._prefix(key) + this.ttlKey; }, _noop: function() { this.get = this.set = this.remove = this.clear = this.isExpired = _.noop; }, _safeSet: function(key, val) { try { this.ls.setItem(key, val); } catch (err) { // hit the localstorage limit so clean up and better luck next time if (err.name === 'QuotaExceededError') { this.clear(); this._noop(); } } }, // ### public get: function(key) { if (this.isExpired(key)) { this.remove(key); } return decode(this.ls.getItem(this._prefix(key))); }, set: function(key, val, ttl) { if (_.isNumber(ttl)) { this._safeSet(this._ttlKey(key), encode(now() + ttl)); } else { this.ls.removeItem(this._ttlKey(key)); } return this._safeSet(this._prefix(key), encode(val)); }, remove: function(key) { this.ls.removeItem(this._ttlKey(key)); this.ls.removeItem(this._prefix(key)); return this; }, clear: function() { var i, keys = gatherMatchingKeys(this.keyMatcher); for (i = keys.length; i--;) { this.remove(keys[i]); } return this; }, isExpired: function(key) { var ttl = decode(this.ls.getItem(this._ttlKey(key))); return _.isNumber(ttl) && now() > ttl ? true : false; } }); return PersistentStorage; // helper functions // ---------------- function now() { return new Date().getTime(); } function encode(val) { // convert undefined to null to avoid issues with JSON.parse return JSON.stringify(_.isUndefined(val) ? null : val); } function decode(val) { return $.parseJSON(val); } function gatherMatchingKeys(keyMatcher) { var i, key, keys = [], len = LOCAL_STORAGE.length; for (i = 0; i < len; i++) { if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) { keys.push(key.replace(keyMatcher, '')); } } return keys; } })(); typeahead.js-0.11.1/src/bloodhound/prefetch.js000066400000000000000000000043251251733230600212410ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Prefetch = (function() { 'use strict'; var keys; keys = { data: 'data', protocol: 'protocol', thumbprint: 'thumbprint' }; // constructor // ----------- // defaults for options are handled in options_parser function Prefetch(o) { this.url = o.url; this.ttl = o.ttl; this.cache = o.cache; this.prepare = o.prepare; this.transform = o.transform; this.transport = o.transport; this.thumbprint = o.thumbprint; this.storage = new PersistentStorage(o.cacheKey); } // instance methods // ---------------- _.mixin(Prefetch.prototype, { // ### private _settings: function settings() { return { url: this.url, type: 'GET', dataType: 'json' }; }, // ### public store: function store(data) { if (!this.cache) { return; } this.storage.set(keys.data, data, this.ttl); this.storage.set(keys.protocol, location.protocol, this.ttl); this.storage.set(keys.thumbprint, this.thumbprint, this.ttl); }, fromCache: function fromCache() { var stored = {}, isExpired; if (!this.cache) { return null; } stored.data = this.storage.get(keys.data); stored.protocol = this.storage.get(keys.protocol); stored.thumbprint = this.storage.get(keys.thumbprint); // the stored data is considered expired if the thumbprints // don't match or if the protocol it was originally stored under // has changed isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol; // TODO: if expired, remove from local storage return stored.data && !isExpired ? stored.data : null; }, fromNetwork: function(cb) { var that = this, settings; if (!cb) { return; } settings = this.prepare(this._settings()); this.transport(settings).fail(onError).done(onResponse); function onError() { cb(true); } function onResponse(resp) { cb(null, that.transform(resp)); } }, clear: function clear() { this.storage.clear(); return this; } }); return Prefetch; })(); typeahead.js-0.11.1/src/bloodhound/remote.js000066400000000000000000000021651251733230600207340ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Remote = (function() { 'use strict'; // constructor // ----------- function Remote(o) { this.url = o.url; this.prepare = o.prepare; this.transform = o.transform; this.transport = new Transport({ cache: o.cache, limiter: o.limiter, transport: o.transport }); } // instance methods // ---------------- _.mixin(Remote.prototype, { // ### private _settings: function settings() { return { url: this.url, type: 'GET', dataType: 'json' }; }, // ### public get: function get(query, cb) { var that = this, settings; if (!cb) { return; } query = query || ''; settings = this.prepare(query, this._settings()); return this.transport.get(settings, onResponse); function onResponse(err, resp) { err ? cb([]) : cb(that.transform(resp)); } }, cancelLastRequest: function cancelLastRequest() { this.transport.cancel(); } }); return Remote; })(); typeahead.js-0.11.1/src/bloodhound/search_index.js000066400000000000000000000076741251733230600221070ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var SearchIndex = window.SearchIndex = (function() { 'use strict'; var CHILDREN = 'c', IDS = 'i'; // constructor // ----------- function SearchIndex(o) { o = o || {}; if (!o.datumTokenizer || !o.queryTokenizer) { $.error('datumTokenizer and queryTokenizer are both required'); } this.identify = o.identify || _.stringify; this.datumTokenizer = o.datumTokenizer; this.queryTokenizer = o.queryTokenizer; this.reset(); } // instance methods // ---------------- _.mixin(SearchIndex.prototype, { // ### public bootstrap: function bootstrap(o) { this.datums = o.datums; this.trie = o.trie; }, add: function(data) { var that = this; data = _.isArray(data) ? data : [data]; _.each(data, function(datum) { var id, tokens; that.datums[id = that.identify(datum)] = datum; tokens = normalizeTokens(that.datumTokenizer(datum)); _.each(tokens, function(token) { var node, chars, ch; node = that.trie; chars = token.split(''); while (ch = chars.shift()) { node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode()); node[IDS].push(id); } }); }); }, get: function get(ids) { var that = this; return _.map(ids, function(id) { return that.datums[id]; }); }, search: function search(query) { var that = this, tokens, matches; tokens = normalizeTokens(this.queryTokenizer(query)); _.each(tokens, function(token) { var node, chars, ch, ids; // previous tokens didn't share any matches if (matches && matches.length === 0) { return false; } node = that.trie; chars = token.split(''); while (node && (ch = chars.shift())) { node = node[CHILDREN][ch]; } if (node && chars.length === 0) { ids = node[IDS].slice(0); matches = matches ? getIntersection(matches, ids) : ids; } // break early if we find out there are no possible matches else { matches = []; return false; } }); return matches ? _.map(unique(matches), function(id) { return that.datums[id]; }) : []; }, all: function all() { var values = []; for (var key in this.datums) { values.push(this.datums[key]); } return values; }, reset: function reset() { this.datums = {}; this.trie = newNode(); }, serialize: function serialize() { return { datums: this.datums, trie: this.trie }; } }); return SearchIndex; // helper functions // ---------------- function normalizeTokens(tokens) { // filter out falsy tokens tokens = _.filter(tokens, function(token) { return !!token; }); // normalize tokens tokens = _.map(tokens, function(token) { return token.toLowerCase(); }); return tokens; } function newNode() { var node = {}; node[IDS] = []; node[CHILDREN] = {}; return node; } function unique(array) { var seen = {}, uniques = []; for (var i = 0, len = array.length; i < len; i++) { if (!seen[array[i]]) { seen[array[i]] = true; uniques.push(array[i]); } } return uniques; } function getIntersection(arrayA, arrayB) { var ai = 0, bi = 0, intersection = []; arrayA = arrayA.sort(); arrayB = arrayB.sort(); var lenArrayA = arrayA.length, lenArrayB = arrayB.length; while (ai < lenArrayA && bi < lenArrayB) { if (arrayA[ai] < arrayB[bi]) { ai++; } else if (arrayA[ai] > arrayB[bi]) { bi++; } else { intersection.push(arrayA[ai]); ai++; bi++; } } return intersection; } })(); typeahead.js-0.11.1/src/bloodhound/tokenizers.js000066400000000000000000000016231251733230600216340ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var tokenizers = (function() { 'use strict'; return { nonword: nonword, whitespace: whitespace, obj: { nonword: getObjTokenizer(nonword), whitespace: getObjTokenizer(whitespace) } }; function whitespace(str) { str = _.toStr(str); return str ? str.split(/\s+/) : []; } function nonword(str) { str = _.toStr(str); return str ? str.split(/\W+/) : []; } function getObjTokenizer(tokenizer) { return function setKey(keys) { keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0); return function tokenize(o) { var tokens = []; _.each(keys, function(k) { tokens = tokens.concat(tokenizer(_.toStr(o[k]))); }); return tokens; }; }; } })(); typeahead.js-0.11.1/src/bloodhound/transport.js000066400000000000000000000057261251733230600215030ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Transport = (function() { 'use strict'; var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10); // constructor // ----------- function Transport(o) { o = o || {}; this.cancelled = false; this.lastReq = null; this._send = o.transport; this._get = o.limiter ? o.limiter(this._get) : this._get; this._cache = o.cache === false ? new LruCache(0) : sharedCache; } // static methods // -------------- Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { maxPendingRequests = num; }; Transport.resetCache = function resetCache() { sharedCache.reset(); }; // instance methods // ---------------- _.mixin(Transport.prototype, { // ### private _fingerprint: function fingerprint(o) { o = o || {}; return o.url + o.type + $.param(o.data || {}); }, _get: function(o, cb) { var that = this, fingerprint, jqXhr; fingerprint = this._fingerprint(o); // #149: don't make a network request if there has been a cancellation // or if the url doesn't match the last url Transport#get was invoked with if (this.cancelled || fingerprint !== this.lastReq) { return; } // a request is already in progress, piggyback off of it if (jqXhr = pendingRequests[fingerprint]) { jqXhr.done(done).fail(fail); } // under the pending request threshold, so fire off a request else if (pendingRequestsCount < maxPendingRequests) { pendingRequestsCount++; pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always); } // at the pending request threshold, so hang out in the on deck circle else { this.onDeckRequestArgs = [].slice.call(arguments, 0); } function done(resp) { cb(null, resp); that._cache.set(fingerprint, resp); } function fail() { cb(true); } function always() { pendingRequestsCount--; delete pendingRequests[fingerprint]; // ensures request is always made for the last query if (that.onDeckRequestArgs) { that._get.apply(that, that.onDeckRequestArgs); that.onDeckRequestArgs = null; } } }, // ### public get: function(o, cb) { var resp, fingerprint; cb = cb || $.noop; o = _.isString(o) ? { url: o } : (o || {}); fingerprint = this._fingerprint(o); this.cancelled = false; this.lastReq = fingerprint; // in-memory cache hit if (resp = this._cache.get(fingerprint)) { cb(null, resp); } // go to network else { this._get(o, cb); } }, cancel: function() { this.cancelled = true; } }); return Transport; })(); typeahead.js-0.11.1/src/bloodhound/version.js000066400000000000000000000002501251733230600211170ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var VERSION = '%VERSION%'; typeahead.js-0.11.1/src/common/000077500000000000000000000000001251733230600162325ustar00rootroot00000000000000typeahead.js-0.11.1/src/common/utils.js000066400000000000000000000072771251733230600177450ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var _ = (function() { 'use strict'; return { isMsie: function() { // from https://github.com/ded/bowser/blob/master/bowser.js return (/(msie|trident)/i).test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; }, isBlankString: function(str) { return !str || /^\s*$/.test(str); }, // http://stackoverflow.com/a/6969486 escapeRegExChars: function(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); }, isString: function(obj) { return typeof obj === 'string'; }, isNumber: function(obj) { return typeof obj === 'number'; }, isArray: $.isArray, isFunction: $.isFunction, isObject: $.isPlainObject, isUndefined: function(obj) { return typeof obj === 'undefined'; }, isElement: function(obj) { return !!(obj && obj.nodeType === 1); }, isJQuery: function(obj) { return obj instanceof $; }, toStr: function toStr(s) { return (_.isUndefined(s) || s === null) ? '' : s + ''; }, bind: $.proxy, each: function(collection, cb) { // stupid argument order for jQuery.each $.each(collection, reverseArgs); function reverseArgs(index, value) { return cb(value, index); } }, map: $.map, filter: $.grep, every: function(obj, test) { var result = true; if (!obj) { return result; } $.each(obj, function(key, val) { if (!(result = test.call(null, val, key, obj))) { return false; } }); return !!result; }, some: function(obj, test) { var result = false; if (!obj) { return result; } $.each(obj, function(key, val) { if (result = test.call(null, val, key, obj)) { return false; } }); return !!result; }, mixin: $.extend, identity: function(x) { return x; }, clone: function(obj) { return $.extend(true, {}, obj); }, getIdGenerator: function() { var counter = 0; return function() { return counter++; }; }, templatify: function templatify(obj) { return $.isFunction(obj) ? obj : template; function template() { return String(obj); } }, defer: function(fn) { setTimeout(fn, 0); }, debounce: function(func, wait, immediate) { var timeout, result; return function() { var context = this, args = arguments, later, callNow; later = function() { timeout = null; if (!immediate) { result = func.apply(context, args); } }; callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); } return result; }; }, throttle: function(func, wait) { var context, args, timeout, result, previous, later; previous = 0; later = function() { previous = new Date(); timeout = null; result = func.apply(context, args); }; return function() { var now = new Date(), remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); } else if (!timeout) { timeout = setTimeout(later, remaining); } return result; }; }, stringify: function(val) { return _.isString(val) ? val : JSON.stringify(val); }, noop: function() {} }; })(); typeahead.js-0.11.1/src/typeahead/000077500000000000000000000000001251733230600167065ustar00rootroot00000000000000typeahead.js-0.11.1/src/typeahead/dataset.js000066400000000000000000000211631251733230600206740ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Dataset = (function() { 'use strict'; var keys, nameGenerator; keys = { val: 'tt-selectable-display', obj: 'tt-selectable-object' }; nameGenerator = _.getIdGenerator(); // constructor // ----------- function Dataset(o, www) { o = o || {}; o.templates = o.templates || {}; // DEPRECATED: empty will be dropped in v1 o.templates.notFound = o.templates.notFound || o.templates.empty; if (!o.source) { $.error('missing source'); } if (!o.node) { $.error('missing node'); } if (o.name && !isValidName(o.name)) { $.error('invalid dataset name: ' + o.name); } www.mixin(this); this.highlight = !!o.highlight; this.name = o.name || nameGenerator(); this.limit = o.limit || 5; this.displayFn = getDisplayFn(o.display || o.displayKey); this.templates = getTemplates(o.templates, this.displayFn); // use duck typing to see if source is a bloodhound instance by checking // for the __ttAdapter property; otherwise assume it is a function this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; // if the async option is undefined, inspect the source signature as // a hint to figuring out of the source will return async suggestions this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; this._resetLastSuggestion(); this.$el = $(o.node) .addClass(this.classes.dataset) .addClass(this.classes.dataset + '-' + this.name); } // static methods // -------------- Dataset.extractData = function extractData(el) { var $el = $(el); if ($el.data(keys.obj)) { return { val: $el.data(keys.val) || '', obj: $el.data(keys.obj) || null }; } return null; }; // instance methods // ---------------- _.mixin(Dataset.prototype, EventEmitter, { // ### private _overwrite: function overwrite(query, suggestions) { suggestions = suggestions || []; // got suggestions: overwrite dom with suggestions if (suggestions.length) { this._renderSuggestions(query, suggestions); } // no suggestions, expecting async: overwrite dom with pending else if (this.async && this.templates.pending) { this._renderPending(query); } // no suggestions, not expecting async: overwrite dom with not found else if (!this.async && this.templates.notFound) { this._renderNotFound(query); } // nothing to render: empty dom else { this._empty(); } this.trigger('rendered', this.name, suggestions, false); }, _append: function append(query, suggestions) { suggestions = suggestions || []; // got suggestions, sync suggestions exist: append suggestions to dom if (suggestions.length && this.$lastSuggestion.length) { this._appendSuggestions(query, suggestions); } // got suggestions, no sync suggestions: overwrite dom with suggestions else if (suggestions.length) { this._renderSuggestions(query, suggestions); } // no async/sync suggestions: overwrite dom with not found else if (!this.$lastSuggestion.length && this.templates.notFound) { this._renderNotFound(query); } this.trigger('rendered', this.name, suggestions, true); }, _renderSuggestions: function renderSuggestions(query, suggestions) { var $fragment; $fragment = this._getSuggestionsFragment(query, suggestions); this.$lastSuggestion = $fragment.children().last(); this.$el.html($fragment) .prepend(this._getHeader(query, suggestions)) .append(this._getFooter(query, suggestions)); }, _appendSuggestions: function appendSuggestions(query, suggestions) { var $fragment, $lastSuggestion; $fragment = this._getSuggestionsFragment(query, suggestions); $lastSuggestion = $fragment.children().last(); this.$lastSuggestion.after($fragment); this.$lastSuggestion = $lastSuggestion; }, _renderPending: function renderPending(query) { var template = this.templates.pending; this._resetLastSuggestion(); template && this.$el.html(template({ query: query, dataset: this.name, })); }, _renderNotFound: function renderNotFound(query) { var template = this.templates.notFound; this._resetLastSuggestion(); template && this.$el.html(template({ query: query, dataset: this.name, })); }, _empty: function empty() { this.$el.empty(); this._resetLastSuggestion(); }, _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { var that = this, fragment; fragment = document.createDocumentFragment(); _.each(suggestions, function getSuggestionNode(suggestion) { var $el, context; context = that._injectQuery(query, suggestion); $el = $(that.templates.suggestion(context)) .data(keys.obj, suggestion) .data(keys.val, that.displayFn(suggestion)) .addClass(that.classes.suggestion + ' ' + that.classes.selectable); fragment.appendChild($el[0]); }); this.highlight && highlight({ className: this.classes.highlight, node: fragment, pattern: query }); return $(fragment); }, _getFooter: function getFooter(query, suggestions) { return this.templates.footer ? this.templates.footer({ query: query, suggestions: suggestions, dataset: this.name }) : null; }, _getHeader: function getHeader(query, suggestions) { return this.templates.header ? this.templates.header({ query: query, suggestions: suggestions, dataset: this.name }) : null; }, _resetLastSuggestion: function resetLastSuggestion() { this.$lastSuggestion = $(); }, _injectQuery: function injectQuery(query, obj) { return _.isObject(obj) ? _.mixin({ _query: query }, obj) : obj; }, // ### public update: function update(query) { var that = this, canceled = false, syncCalled = false, rendered = 0; // cancel possible pending update this.cancel(); this.cancel = function cancel() { canceled = true; that.cancel = $.noop; that.async && that.trigger('asyncCanceled', query); }; this.source(query, sync, async); !syncCalled && sync([]); function sync(suggestions) { if (syncCalled) { return; } syncCalled = true; suggestions = (suggestions || []).slice(0, that.limit); rendered = suggestions.length; that._overwrite(query, suggestions); if (rendered < that.limit && that.async) { that.trigger('asyncRequested', query); } } function async(suggestions) { suggestions = suggestions || []; // if the update has been canceled or if the query has changed // do not render the suggestions as they've become outdated if (!canceled && rendered < that.limit) { that.cancel = $.noop; rendered += suggestions.length; that._append(query, suggestions.slice(0, that.limit - rendered)); that.async && that.trigger('asyncReceived', query); } } }, // cancel function gets set in #update cancel: $.noop, clear: function clear() { this._empty(); this.cancel(); this.trigger('cleared'); }, isEmpty: function isEmpty() { return this.$el.is(':empty'); }, destroy: function destroy() { // #970 this.$el = $('
'); } }); return Dataset; // helper functions // ---------------- function getDisplayFn(display) { display = display || _.stringify; return _.isFunction(display) ? display : displayFn; function displayFn(obj) { return obj[display]; } } function getTemplates(templates, displayFn) { return { notFound: templates.notFound && _.templatify(templates.notFound), pending: templates.pending && _.templatify(templates.pending), header: templates.header && _.templatify(templates.header), footer: templates.footer && _.templatify(templates.footer), suggestion: templates.suggestion || suggestionTemplate }; function suggestionTemplate(context) { return $('
').text(displayFn(context)); } } function isValidName(str) { // dashes, underscores, letters, and numbers return (/^[_a-zA-Z0-9-]+$/).test(str); } })(); typeahead.js-0.11.1/src/typeahead/default_menu.js000066400000000000000000000033301251733230600217130ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var DefaultMenu = (function() { 'use strict'; var s = Menu.prototype; function DefaultMenu() { Menu.apply(this, [].slice.call(arguments, 0)); } _.mixin(DefaultMenu.prototype, Menu.prototype, { // overrides // --------- open: function open() { // only display the menu when there's something to be shown !this._allDatasetsEmpty() && this._show(); return s.open.apply(this, [].slice.call(arguments, 0)); }, close: function close() { this._hide(); return s.close.apply(this, [].slice.call(arguments, 0)); }, _onRendered: function onRendered() { if (this._allDatasetsEmpty()) { this._hide(); } else { this.isOpen() && this._show(); } return s._onRendered.apply(this, [].slice.call(arguments, 0)); }, _onCleared: function onCleared() { if (this._allDatasetsEmpty()) { this._hide(); } else { this.isOpen() && this._show(); } return s._onCleared.apply(this, [].slice.call(arguments, 0)); }, setLanguageDirection: function setLanguageDirection(dir) { this.$node.css(dir === 'ltr' ? this.css.ltr : this.css.rtl); return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); }, // private // --------- _hide: function hide() { this.$node.hide(); }, _show: function show() { // can't use jQuery#show because $node is a span element we want // display: block; not dislay: inline; this.$node.css('display', 'block'); } }); return DefaultMenu; })(); typeahead.js-0.11.1/src/typeahead/event_bus.js000066400000000000000000000030321251733230600212340ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var EventBus = (function() { 'use strict'; var namespace, deprecationMap; namespace = 'typeahead:'; // DEPRECATED: will be remove in v1 // // NOTE: there is no deprecation plan for the opened and closed event // as their behavior has changed enough that it wouldn't make sense deprecationMap = { render: 'rendered', cursorchange: 'cursorchanged', select: 'selected', autocomplete: 'autocompleted' }; // constructor // ----------- function EventBus(o) { if (!o || !o.el) { $.error('EventBus initialized without el'); } this.$el = $(o.el); } // instance methods // ---------------- _.mixin(EventBus.prototype, { // ### private _trigger: function(type, args) { var $e; $e = $.Event(namespace + type); (args = args || []).unshift($e); this.$el.trigger.apply(this.$el, args); return $e; }, // ### public before: function(type) { var args, $e; args = [].slice.call(arguments, 1); $e = this._trigger('before' + type, args); return $e.isDefaultPrevented(); }, trigger: function(type) { var deprecatedType; this._trigger(type, [].slice.call(arguments, 1)); // TODO: remove in v1 if (deprecatedType = deprecationMap[type]) { this._trigger(deprecatedType, [].slice.call(arguments, 1)); } } }); return EventBus; })(); typeahead.js-0.11.1/src/typeahead/event_emitter.js000066400000000000000000000051021251733230600221140ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ // inspired by https://github.com/jharding/boomerang var EventEmitter = (function() { 'use strict'; var splitter = /\s+/, nextTick = getNextTick(); return { onSync: onSync, onAsync: onAsync, off: off, trigger: trigger }; function on(method, types, cb, context) { var type; if (!cb) { return this; } types = types.split(splitter); cb = context ? bindContext(cb, context) : cb; this._callbacks = this._callbacks || {}; while (type = types.shift()) { this._callbacks[type] = this._callbacks[type] || { sync: [], async: [] }; this._callbacks[type][method].push(cb); } return this; } function onAsync(types, cb, context) { return on.call(this, 'async', types, cb, context); } function onSync(types, cb, context) { return on.call(this, 'sync', types, cb, context); } function off(types) { var type; if (!this._callbacks) { return this; } types = types.split(splitter); while (type = types.shift()) { delete this._callbacks[type]; } return this; } function trigger(types) { var type, callbacks, args, syncFlush, asyncFlush; if (!this._callbacks) { return this; } types = types.split(splitter); args = [].slice.call(arguments, 1); while ((type = types.shift()) && (callbacks = this._callbacks[type])) { syncFlush = getFlush(callbacks.sync, this, [type].concat(args)); asyncFlush = getFlush(callbacks.async, this, [type].concat(args)); syncFlush() && nextTick(asyncFlush); } return this; } function getFlush(callbacks, context, args) { return flush; function flush() { var cancelled; for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { // only cancel if the callback explicitly returns false cancelled = callbacks[i].apply(context, args) === false; } return !cancelled; } } function getNextTick() { var nextTickFn; // IE10+ if (window.setImmediate) { nextTickFn = function nextTickSetImmediate(fn) { setImmediate(function() { fn(); }); }; } // old browsers else { nextTickFn = function nextTickSetTimeout(fn) { setTimeout(function() { fn(); }, 0); }; } return nextTickFn; } function bindContext(fn, context) { return fn.bind ? fn.bind(context) : function() { fn.apply(context, [].slice.call(arguments, 0)); }; } })(); typeahead.js-0.11.1/src/typeahead/highlight.js000066400000000000000000000041721251733230600212170ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ // inspired by https://github.com/jharding/bearhug var highlight = (function(doc) { 'use strict'; var defaults = { node: null, pattern: null, tagName: 'strong', className: null, wordsOnly: false, caseSensitive: false }; return function hightlight(o) { var regex; o = _.mixin({}, defaults, o); if (!o.node || !o.pattern) { // fail silently return; } // support wrapping multiple patterns o.pattern = _.isArray(o.pattern) ? o.pattern : [o.pattern]; regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); traverse(o.node, hightlightTextNode); function hightlightTextNode(textNode) { var match, patternNode, wrapperNode; if (match = regex.exec(textNode.data)) { wrapperNode = doc.createElement(o.tagName); o.className && (wrapperNode.className = o.className); patternNode = textNode.splitText(match.index); patternNode.splitText(match[0].length); wrapperNode.appendChild(patternNode.cloneNode(true)); textNode.parentNode.replaceChild(wrapperNode, patternNode); } return !!match; } function traverse(el, hightlightTextNode) { var childNode, TEXT_NODE_TYPE = 3; for (var i = 0; i < el.childNodes.length; i++) { childNode = el.childNodes[i]; if (childNode.nodeType === TEXT_NODE_TYPE) { i += hightlightTextNode(childNode) ? 1 : 0; } else { traverse(childNode, hightlightTextNode); } } } }; function getRegex(patterns, caseSensitive, wordsOnly) { var escapedPatterns = [], regexStr; for (var i = 0, len = patterns.length; i < len; i++) { escapedPatterns.push(_.escapeRegExChars(patterns[i])); } regexStr = wordsOnly ? '\\b(' + escapedPatterns.join('|') + ')\\b' : '(' + escapedPatterns.join('|') + ')'; return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, 'i'); } })(window.document); typeahead.js-0.11.1/src/typeahead/input.js000066400000000000000000000206441251733230600204110ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Input = (function() { 'use strict'; var specialKeyCodeMap; specialKeyCodeMap = { 9: 'tab', 27: 'esc', 37: 'left', 39: 'right', 13: 'enter', 38: 'up', 40: 'down' }; // constructor // ----------- function Input(o, www) { o = o || {}; if (!o.input) { $.error('input is missing'); } www.mixin(this); this.$hint = $(o.hint); this.$input = $(o.input); // the query defaults to whatever the value of the input is // on initialization, it'll most likely be an empty string this.query = this.$input.val(); // for tracking when a change event should be triggered this.queryWhenFocused = this.hasFocus() ? this.query : null; // helps with calculating the width of the input's value this.$overflowHelper = buildOverflowHelper(this.$input); // detect the initial lang direction this._checkLanguageDirection(); // if no hint, noop all the hint related functions if (this.$hint.length === 0) { this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; } } // static methods // -------------- Input.normalizeQuery = function(str) { // strips leading whitespace and condenses all whitespace return (_.toStr(str)).replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); }; // instance methods // ---------------- _.mixin(Input.prototype, EventEmitter, { // ### event handlers _onBlur: function onBlur() { this.resetInputValue(); this.trigger('blurred'); }, _onFocus: function onFocus() { this.queryWhenFocused = this.query; this.trigger('focused'); }, _onKeydown: function onKeydown($e) { // which is normalized and consistent (but not for ie) var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; this._managePreventDefault(keyName, $e); if (keyName && this._shouldTrigger(keyName, $e)) { this.trigger(keyName + 'Keyed', $e); } }, _onInput: function onInput() { this._setQuery(this.getInputValue()); this.clearHintIfInvalid(); this._checkLanguageDirection(); }, // ### private _managePreventDefault: function managePreventDefault(keyName, $e) { var preventDefault; switch (keyName) { case 'up': case 'down': preventDefault = !withModifier($e); break; default: preventDefault = false; } preventDefault && $e.preventDefault(); }, _shouldTrigger: function shouldTrigger(keyName, $e) { var trigger; switch (keyName) { case 'tab': trigger = !withModifier($e); break; default: trigger = true; } return trigger; }, _checkLanguageDirection: function checkLanguageDirection() { var dir = (this.$input.css('direction') || 'ltr').toLowerCase(); if (this.dir !== dir) { this.dir = dir; this.$hint.attr('dir', dir); this.trigger('langDirChanged', dir); } }, _setQuery: function setQuery(val, silent) { var areEquivalent, hasDifferentWhitespace; areEquivalent = areQueriesEquivalent(val, this.query); hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; this.query = val; if (!silent && !areEquivalent) { this.trigger('queryChanged', this.query); } else if (!silent && hasDifferentWhitespace) { this.trigger('whitespaceChanged', this.query); } }, // ### public bind: function() { var that = this, onBlur, onFocus, onKeydown, onInput; // bound functions onBlur = _.bind(this._onBlur, this); onFocus = _.bind(this._onFocus, this); onKeydown = _.bind(this._onKeydown, this); onInput = _.bind(this._onInput, this); this.$input .on('blur.tt', onBlur) .on('focus.tt', onFocus) .on('keydown.tt', onKeydown); // ie8 don't support the input event // ie9 doesn't fire the input event when characters are removed if (!_.isMsie() || _.isMsie() > 9) { this.$input.on('input.tt', onInput); } else { this.$input.on('keydown.tt keypress.tt cut.tt paste.tt', function($e) { // if a special key triggered this, ignore it if (specialKeyCodeMap[$e.which || $e.keyCode]) { return; } // give the browser a chance to update the value of the input // before checking to see if the query changed _.defer(_.bind(that._onInput, that, $e)); }); } return this; }, focus: function focus() { this.$input.focus(); }, blur: function blur() { this.$input.blur(); }, getLangDir: function getLangDir() { return this.dir; }, getQuery: function getQuery() { return this.query || ''; }, setQuery: function setQuery(val, silent) { this.setInputValue(val); this._setQuery(val, silent); }, hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { return this.query !== this.queryWhenFocused; }, getInputValue: function getInputValue() { return this.$input.val(); }, setInputValue: function setInputValue(value) { this.$input.val(value); this.clearHintIfInvalid(); this._checkLanguageDirection(); }, resetInputValue: function resetInputValue() { this.setInputValue(this.query); }, getHint: function getHint() { return this.$hint.val(); }, setHint: function setHint(value) { this.$hint.val(value); }, clearHint: function clearHint() { this.setHint(''); }, clearHintIfInvalid: function clearHintIfInvalid() { var val, hint, valIsPrefixOfHint, isValid; val = this.getInputValue(); hint = this.getHint(); valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; isValid = val !== '' && valIsPrefixOfHint && !this.hasOverflow(); !isValid && this.clearHint(); }, hasFocus: function hasFocus() { return this.$input.is(':focus'); }, hasOverflow: function hasOverflow() { // 2 is arbitrary, just picking a small number to handle edge cases var constraint = this.$input.width() - 2; this.$overflowHelper.text(this.getInputValue()); return this.$overflowHelper.width() >= constraint; }, isCursorAtEnd: function() { var valueLength, selectionStart, range; valueLength = this.$input.val().length; selectionStart = this.$input[0].selectionStart; if (_.isNumber(selectionStart)) { return selectionStart === valueLength; } else if (document.selection) { // NOTE: this won't work unless the input has focus, the good news // is this code should only get called when the input has focus range = document.selection.createRange(); range.moveStart('character', -valueLength); return valueLength === range.text.length; } return true; }, destroy: function destroy() { this.$hint.off('.tt'); this.$input.off('.tt'); this.$overflowHelper.remove(); // #970 this.$hint = this.$input = this.$overflowHelper = $('
'); } }); return Input; // helper functions // ---------------- function buildOverflowHelper($input) { return $('') .css({ // position helper off-screen position: 'absolute', visibility: 'hidden', // avoid line breaks and whitespace collapsing whiteSpace: 'pre', // use same font css as input to calculate accurate width fontFamily: $input.css('font-family'), fontSize: $input.css('font-size'), fontStyle: $input.css('font-style'), fontVariant: $input.css('font-variant'), fontWeight: $input.css('font-weight'), wordSpacing: $input.css('word-spacing'), letterSpacing: $input.css('letter-spacing'), textIndent: $input.css('text-indent'), textRendering: $input.css('text-rendering'), textTransform: $input.css('text-transform') }) .insertAfter($input); } function areQueriesEquivalent(a, b) { return Input.normalizeQuery(a) === Input.normalizeQuery(b); } function withModifier($e) { return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; } })(); typeahead.js-0.11.1/src/typeahead/menu.js000066400000000000000000000133631251733230600202160ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Menu = (function() { 'use strict'; // constructor // ----------- function Menu(o, www) { var that = this; o = o || {}; if (!o.node) { $.error('node is required'); } www.mixin(this); this.$node = $(o.node); // the latest query #update was called with this.query = null; this.datasets = _.map(o.datasets, initializeDataset); function initializeDataset(oDataset) { var node = that.$node.find(oDataset.node).first(); oDataset.node = node.length ? node : $('
').appendTo(that.$node); return new Dataset(oDataset, www); } } // instance methods // ---------------- _.mixin(Menu.prototype, EventEmitter, { // ### event handlers _onSelectableClick: function onSelectableClick($e) { this.trigger('selectableClicked', $($e.currentTarget)); }, _onRendered: function onRendered(type, dataset, suggestions, async) { this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); this.trigger('datasetRendered', dataset, suggestions, async); }, _onCleared: function onCleared() { this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); this.trigger('datasetCleared'); }, _propagate: function propagate() { this.trigger.apply(this, arguments); }, // ### private _allDatasetsEmpty: function allDatasetsEmpty() { return _.every(this.datasets, isDatasetEmpty); function isDatasetEmpty(dataset) { return dataset.isEmpty(); } }, _getSelectables: function getSelectables() { return this.$node.find(this.selectors.selectable); }, _removeCursor: function _removeCursor() { var $selectable = this.getActiveSelectable(); $selectable && $selectable.removeClass(this.classes.cursor); }, _ensureVisible: function ensureVisible($el) { var elTop, elBottom, nodeScrollTop, nodeHeight; elTop = $el.position().top; elBottom = elTop + $el.outerHeight(true); nodeScrollTop = this.$node.scrollTop(); nodeHeight = this.$node.height() + parseInt(this.$node.css('paddingTop'), 10) + parseInt(this.$node.css('paddingBottom'), 10); if (elTop < 0) { this.$node.scrollTop(nodeScrollTop + elTop); } else if (nodeHeight < elBottom) { this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); } }, // ### public bind: function() { var that = this, onSelectableClick; onSelectableClick = _.bind(this._onSelectableClick, this); this.$node.on('click.tt', this.selectors.selectable, onSelectableClick); _.each(this.datasets, function(dataset) { dataset .onSync('asyncRequested', that._propagate, that) .onSync('asyncCanceled', that._propagate, that) .onSync('asyncReceived', that._propagate, that) .onSync('rendered', that._onRendered, that) .onSync('cleared', that._onCleared, that); }); return this; }, isOpen: function isOpen() { return this.$node.hasClass(this.classes.open); }, open: function open() { this.$node.addClass(this.classes.open); }, close: function close() { this.$node.removeClass(this.classes.open); this._removeCursor(); }, setLanguageDirection: function setLanguageDirection(dir) { this.$node.attr('dir', dir); }, selectableRelativeToCursor: function selectableRelativeToCursor(delta) { var $selectables, $oldCursor, oldIndex, newIndex; $oldCursor = this.getActiveSelectable(); $selectables = this._getSelectables(); // shifting before and after modulo to deal with -1 index oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; newIndex = oldIndex + delta; newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; // wrap new index if less than -1 newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; return newIndex === -1 ? null : $selectables.eq(newIndex); }, setCursor: function setCursor($selectable) { this._removeCursor(); if ($selectable = $selectable && $selectable.first()) { $selectable.addClass(this.classes.cursor); // in the case of scrollable overflow // make sure the cursor is visible in the node this._ensureVisible($selectable); } }, getSelectableData: function getSelectableData($el) { return ($el && $el.length) ? Dataset.extractData($el) : null; }, getActiveSelectable: function getActiveSelectable() { var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); return $selectable.length ? $selectable : null; }, getTopSelectable: function getTopSelectable() { var $selectable = this._getSelectables().first(); return $selectable.length ? $selectable : null; }, update: function update(query) { var isValidUpdate = query !== this.query; // don't update if the query hasn't changed if (isValidUpdate) { this.query = query; _.each(this.datasets, updateDataset); } return isValidUpdate; function updateDataset(dataset) { dataset.update(query); } }, empty: function empty() { _.each(this.datasets, clearDataset); this.query = null; this.$node.addClass(this.classes.empty); function clearDataset(dataset) { dataset.clear(); } }, destroy: function destroy() { this.$node.off('.tt'); // #970 this.$node = $('
'); _.each(this.datasets, destroyDataset); function destroyDataset(dataset) { dataset.destroy(); } } }); return Menu; })(); typeahead.js-0.11.1/src/typeahead/plugin.js000066400000000000000000000165731251733230600205560ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ (function() { 'use strict'; var old, keys, methods; old = $.fn.typeahead; keys = { www: 'tt-www', attrs: 'tt-attrs', typeahead: 'tt-typeahead' }; methods = { // supported signatures: // function(o, dataset, dataset, ...) // function(o, [dataset, dataset, ...]) initialize: function initialize(o, datasets) { var www; datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); o = o || {}; www = WWW(o.classNames); return this.each(attach); function attach() { var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; // highlight is a top-level config that needs to get inherited // from all of the datasets _.each(datasets, function(d) { d.highlight = !!o.highlight; }); $input = $(this); $wrapper = $(www.html.wrapper); $hint = $elOrNull(o.hint); $menu = $elOrNull(o.menu); defaultHint = o.hint !== false && !$hint; defaultMenu = o.menu !== false && !$menu; defaultHint && ($hint = buildHintFromInput($input, www)); defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); // hint should be empty on init $hint && $hint.val(''); $input = prepInput($input, www); // only apply inline styles and make dom changes if necessary if (defaultHint || defaultMenu) { $wrapper.css(www.css.wrapper); $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); $input .wrap($wrapper) .parent() .prepend(defaultHint ? $hint : null) .append(defaultMenu ? $menu : null); } MenuConstructor = defaultMenu ? DefaultMenu : Menu; eventBus = new EventBus({ el: $input }); input = new Input({ hint: $hint, input: $input, }, www); menu = new MenuConstructor({ node: $menu, datasets: datasets }, www); typeahead = new Typeahead({ input: input, menu: menu, eventBus: eventBus, minLength: o.minLength }, www); $input.data(keys.www, www); $input.data(keys.typeahead, typeahead); } }, isEnabled: function isEnabled() { var enabled; ttEach(this.first(), function(t) { enabled = t.isEnabled(); }); return enabled; }, enable: function enable() { ttEach(this, function(t) { t.enable(); }); return this; }, disable: function disable() { ttEach(this, function(t) { t.disable(); }); return this; }, isActive: function isActive() { var active; ttEach(this.first(), function(t) { active = t.isActive(); }); return active; }, activate: function activate() { ttEach(this, function(t) { t.activate(); }); return this; }, deactivate: function deactivate() { ttEach(this, function(t) { t.deactivate(); }); return this; }, isOpen: function isOpen() { var open; ttEach(this.first(), function(t) { open = t.isOpen(); }); return open; }, open: function open() { ttEach(this, function(t) { t.open(); }); return this; }, close: function close() { ttEach(this, function(t) { t.close(); }); return this; }, select: function select(el) { var success = false, $el = $(el); ttEach(this.first(), function(t) { success = t.select($el); }); return success; }, autocomplete: function autocomplete(el) { var success = false, $el = $(el); ttEach(this.first(), function(t) { success = t.autocomplete($el); }); return success; }, moveCursor: function moveCursoe(delta) { var success = false; ttEach(this.first(), function(t) { success = t.moveCursor(delta); }); return success; }, // mirror jQuery#val functionality: reads opearte on first match, // write operates on all matches val: function val(newVal) { var query; if (!arguments.length) { ttEach(this.first(), function(t) { query = t.getVal(); }); return query; } else { ttEach(this, function(t) { t.setVal(newVal); }); return this; } }, destroy: function destroy() { ttEach(this, function(typeahead, $input) { revert($input); typeahead.destroy(); }); return this; } }; $.fn.typeahead = function(method) { // methods that should only act on intialized typeaheads if (methods[method]) { return methods[method].apply(this, [].slice.call(arguments, 1)); } else { return methods.initialize.apply(this, arguments); } }; $.fn.typeahead.noConflict = function noConflict() { $.fn.typeahead = old; return this; }; // helper methods // -------------- function ttEach($els, fn) { $els.each(function() { var $input = $(this), typeahead; (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); }); } function buildHintFromInput($input, www) { return $input.clone() .addClass(www.classes.hint) .removeData() .css(www.css.hint) .css(getBackgroundStyles($input)) .prop('readonly', true) .removeAttr('id name placeholder required') .attr({ autocomplete: 'off', spellcheck: 'false', tabindex: -1 }); } function prepInput($input, www) { // store the original values of the attrs that get modified // so modifications can be reverted on destroy $input.data(keys.attrs, { dir: $input.attr('dir'), autocomplete: $input.attr('autocomplete'), spellcheck: $input.attr('spellcheck'), style: $input.attr('style') }); $input .addClass(www.classes.input) .attr({ autocomplete: 'off', spellcheck: false }); // ie7 does not like it when dir is set to auto try { !$input.attr('dir') && $input.attr('dir', 'auto'); } catch (e) {} return $input; } function getBackgroundStyles($el) { return { backgroundAttachment: $el.css('background-attachment'), backgroundClip: $el.css('background-clip'), backgroundColor: $el.css('background-color'), backgroundImage: $el.css('background-image'), backgroundOrigin: $el.css('background-origin'), backgroundPosition: $el.css('background-position'), backgroundRepeat: $el.css('background-repeat'), backgroundSize: $el.css('background-size') }; } function revert($input) { var www, $wrapper; www = $input.data(keys.www); $wrapper = $input.parent().filter(www.selectors.wrapper); // need to remove attrs that weren't previously defined and // revert attrs that originally had a value _.each($input.data(keys.attrs), function(val, key) { _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); }); $input .removeData(keys.typeahead) .removeData(keys.www) .removeData(keys.attr) .removeClass(www.classes.input); if ($wrapper.length) { $input.detach().insertAfter($wrapper); $wrapper.remove(); } } function $elOrNull(obj) { var isValid, $el; isValid = _.isJQuery(obj) || _.isElement(obj); $el = isValid ? $(obj).first() : []; return $el.length ? $el : null; } })(); typeahead.js-0.11.1/src/typeahead/typeahead.js000066400000000000000000000274451251733230600212240ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Typeahead = (function() { 'use strict'; // constructor // ----------- function Typeahead(o, www) { var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; o = o || {}; if (!o.input) { $.error('missing input'); } if (!o.menu) { $.error('missing menu'); } if (!o.eventBus) { $.error('missing event bus'); } www.mixin(this); this.eventBus = o.eventBus; this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; this.input = o.input; this.menu = o.menu; this.enabled = true; // activate the typeahead on init if the input has focus this.active = false; this.input.hasFocus() && this.activate(); // detect the initial lang direction this.dir = this.input.getLangDir(); this._hacks(); this.menu.bind() .onSync('selectableClicked', this._onSelectableClicked, this) .onSync('asyncRequested', this._onAsyncRequested, this) .onSync('asyncCanceled', this._onAsyncCanceled, this) .onSync('asyncReceived', this._onAsyncReceived, this) .onSync('datasetRendered', this._onDatasetRendered, this) .onSync('datasetCleared', this._onDatasetCleared, this); // composed event handlers for input onFocused = c(this, 'activate', 'open', '_onFocused'); onBlurred = c(this, 'deactivate', '_onBlurred'); onEnterKeyed = c(this, 'isActive', 'isOpen', '_onEnterKeyed'); onTabKeyed = c(this, 'isActive', 'isOpen', '_onTabKeyed'); onEscKeyed = c(this, 'isActive', '_onEscKeyed'); onUpKeyed = c(this, 'isActive', 'open', '_onUpKeyed'); onDownKeyed = c(this, 'isActive', 'open', '_onDownKeyed'); onLeftKeyed = c(this, 'isActive', 'isOpen', '_onLeftKeyed'); onRightKeyed = c(this, 'isActive', 'isOpen', '_onRightKeyed'); onQueryChanged = c(this, '_openIfActive', '_onQueryChanged'); onWhitespaceChanged = c(this, '_openIfActive', '_onWhitespaceChanged'); this.input.bind() .onSync('focused', onFocused, this) .onSync('blurred', onBlurred, this) .onSync('enterKeyed', onEnterKeyed, this) .onSync('tabKeyed', onTabKeyed, this) .onSync('escKeyed', onEscKeyed, this) .onSync('upKeyed', onUpKeyed, this) .onSync('downKeyed', onDownKeyed, this) .onSync('leftKeyed', onLeftKeyed, this) .onSync('rightKeyed', onRightKeyed, this) .onSync('queryChanged', onQueryChanged, this) .onSync('whitespaceChanged', onWhitespaceChanged, this) .onSync('langDirChanged', this._onLangDirChanged, this); } // instance methods // ---------------- _.mixin(Typeahead.prototype, { // here's where hacks get applied and we don't feel bad about it _hacks: function hacks() { var $input, $menu; // these default values are to make testing easier $input = this.input.$input || $('
'); $menu = this.menu.$node || $('
'); // #705: if there's scrollable overflow, ie doesn't support // blur cancellations when the scrollbar is clicked // // #351: preventDefault won't cancel blurs in ie <= 8 $input.on('blur.tt', function($e) { var active, isActive, hasActive; active = document.activeElement; isActive = $menu.is(active); hasActive = $menu.has(active).length > 0; if (_.isMsie() && (isActive || hasActive)) { $e.preventDefault(); // stop immediate in order to prevent Input#_onBlur from // getting exectued $e.stopImmediatePropagation(); _.defer(function() { $input.focus(); }); } }); // #351: prevents input blur due to clicks within menu $menu.on('mousedown.tt', function($e) { $e.preventDefault(); }); }, // ### event handlers _onSelectableClicked: function onSelectableClicked(type, $el) { this.select($el); }, _onDatasetCleared: function onDatasetCleared() { this._updateHint(); }, _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { this._updateHint(); this.eventBus.trigger('render', suggestions, async, dataset); }, _onAsyncRequested: function onAsyncRequested(type, dataset, query) { this.eventBus.trigger('asyncrequest', query, dataset); }, _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { this.eventBus.trigger('asynccancel', query, dataset); }, _onAsyncReceived: function onAsyncReceived(type, dataset, query) { this.eventBus.trigger('asyncreceive', query, dataset); }, _onFocused: function onFocused() { this._minLengthMet() && this.menu.update(this.input.getQuery()); }, _onBlurred: function onBlurred() { if (this.input.hasQueryChangedSinceLastFocus()) { this.eventBus.trigger('change', this.input.getQuery()); } }, _onEnterKeyed: function onEnterKeyed(type, $e) { var $selectable; if ($selectable = this.menu.getActiveSelectable()) { this.select($selectable) && $e.preventDefault(); } }, _onTabKeyed: function onTabKeyed(type, $e) { var $selectable; if ($selectable = this.menu.getActiveSelectable()) { this.select($selectable) && $e.preventDefault(); } else if ($selectable = this.menu.getTopSelectable()) { this.autocomplete($selectable) && $e.preventDefault(); } }, _onEscKeyed: function onEscKeyed() { this.close(); }, _onUpKeyed: function onUpKeyed() { this.moveCursor(-1); }, _onDownKeyed: function onDownKeyed() { this.moveCursor(+1); }, _onLeftKeyed: function onLeftKeyed() { if (this.dir === 'rtl' && this.input.isCursorAtEnd()) { this.autocomplete(this.menu.getTopSelectable()); } }, _onRightKeyed: function onRightKeyed() { if (this.dir === 'ltr' && this.input.isCursorAtEnd()) { this.autocomplete(this.menu.getTopSelectable()); } }, _onQueryChanged: function onQueryChanged(e, query) { this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); }, _onWhitespaceChanged: function onWhitespaceChanged() { this._updateHint(); }, _onLangDirChanged: function onLangDirChanged(e, dir) { if (this.dir !== dir) { this.dir = dir; this.menu.setLanguageDirection(dir); } }, // ### private _openIfActive: function openIfActive() { this.isActive() && this.open(); }, _minLengthMet: function minLengthMet(query) { query = _.isString(query) ? query : (this.input.getQuery() || ''); return query.length >= this.minLength; }, _updateHint: function updateHint() { var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; $selectable = this.menu.getTopSelectable(); data = this.menu.getSelectableData($selectable); val = this.input.getInputValue(); if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { query = Input.normalizeQuery(val); escapedQuery = _.escapeRegExChars(query); // match input value, then capture trailing text frontMatchRegEx = new RegExp('^(?:' + escapedQuery + ')(.+$)', 'i'); match = frontMatchRegEx.exec(data.val); // clear hint if there's no trailing text match && this.input.setHint(val + match[1]); } else { this.input.clearHint(); } }, // ### public isEnabled: function isEnabled() { return this.enabled; }, enable: function enable() { this.enabled = true; }, disable: function disable() { this.enabled = false; }, isActive: function isActive() { return this.active; }, activate: function activate() { // already active if (this.isActive()) { return true; } // unable to activate either due to the typeahead being disabled // or due to the active event being prevented else if (!this.isEnabled() || this.eventBus.before('active')) { return false; } // activate else { this.active = true; this.eventBus.trigger('active'); return true; } }, deactivate: function deactivate() { // already idle if (!this.isActive()) { return true; } // unable to deactivate due to the idle event being prevented else if (this.eventBus.before('idle')) { return false; } // deactivate else { this.active = false; this.close(); this.eventBus.trigger('idle'); return true; } }, isOpen: function isOpen() { return this.menu.isOpen(); }, open: function open() { if (!this.isOpen() && !this.eventBus.before('open')) { this.menu.open(); this._updateHint(); this.eventBus.trigger('open'); } return this.isOpen(); }, close: function close() { if (this.isOpen() && !this.eventBus.before('close')) { this.menu.close(); this.input.clearHint(); this.input.resetInputValue(); this.eventBus.trigger('close'); } return !this.isOpen(); }, setVal: function setVal(val) { // expect val to be a string, so be safe, and coerce this.input.setQuery(_.toStr(val)); }, getVal: function getVal() { return this.input.getQuery(); }, select: function select($selectable) { var data = this.menu.getSelectableData($selectable); if (data && !this.eventBus.before('select', data.obj)) { this.input.setQuery(data.val, true); this.eventBus.trigger('select', data.obj); this.close(); // return true if selection succeeded return true; } return false; }, autocomplete: function autocomplete($selectable) { var query, data, isValid; query = this.input.getQuery(); data = this.menu.getSelectableData($selectable); isValid = data && query !== data.val; if (isValid && !this.eventBus.before('autocomplete', data.obj)) { this.input.setQuery(data.val); this.eventBus.trigger('autocomplete', data.obj); // return true if autocompletion succeeded return true; } return false; }, moveCursor: function moveCursor(delta) { var query, $candidate, data, payload, cancelMove; query = this.input.getQuery(); $candidate = this.menu.selectableRelativeToCursor(delta); data = this.menu.getSelectableData($candidate); payload = data ? data.obj : null; // update will return true when it's a new query and new suggestions // need to be fetched – in this case we don't want to move the cursor cancelMove = this._minLengthMet() && this.menu.update(query); if (!cancelMove && !this.eventBus.before('cursorchange', payload)) { this.menu.setCursor($candidate); // cursor moved to different selectable if (data) { this.input.setInputValue(data.val); } // cursor moved off of selectables, back to input else { this.input.resetInputValue(); this._updateHint(); } this.eventBus.trigger('cursorchange', payload); // return true if move succeeded return true; } return false; }, destroy: function destroy() { this.input.destroy(); this.menu.destroy(); } }); return Typeahead; // helper functions // ---------------- function c(ctx) { var methods = [].slice.call(arguments, 1); return function() { var args = [].slice.call(arguments); _.each(methods, function(method) { return ctx[method].apply(ctx, args); }); }; } })(); typeahead.js-0.11.1/src/typeahead/www.js000066400000000000000000000046451251733230600201010ustar00rootroot00000000000000/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var WWW = (function() { 'use strict'; var defaultClassNames = { wrapper: 'twitter-typeahead', input: 'tt-input', hint: 'tt-hint', menu: 'tt-menu', dataset: 'tt-dataset', suggestion: 'tt-suggestion', selectable: 'tt-selectable', empty: 'tt-empty', open: 'tt-open', cursor: 'tt-cursor', highlight: 'tt-highlight' }; return build; function build(o) { var www, classes; classes = _.mixin({}, defaultClassNames, o); www = { css: buildCss(), classes: classes, html: buildHtml(classes), selectors: buildSelectors(classes) }; return { css: www.css, html: www.html, classes: www.classes, selectors: www.selectors, mixin: function(o) { _.mixin(o, www); } }; } function buildHtml(c) { return { wrapper: '', menu: '
' }; } function buildSelectors(classes) { var selectors = {}; _.each(classes, function(v, k) { selectors[k] = '.' + v; }); return selectors; } function buildCss() { var css = { wrapper: { position: 'relative', display: 'inline-block' }, hint: { position: 'absolute', top: '0', left: '0', borderColor: 'transparent', boxShadow: 'none', // #741: fix hint opacity issue on iOS opacity: '1' }, input: { position: 'relative', verticalAlign: 'top', backgroundColor: 'transparent' }, inputWithNoHint: { position: 'relative', verticalAlign: 'top' }, menu: { position: 'absolute', top: '100%', left: '0', zIndex: '100', display: 'none' }, ltr: { left: '0', right: 'auto' }, rtl: { left: 'auto', right:' 0' } }; // ie specific styling if (_.isMsie()) { // ie6-8 (and 9?) doesn't fire hover and click events for elements with // transparent backgrounds, for a workaround, use 1x1 transparent gif _.mixin(css.input, { backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)' }); } return css; } })(); typeahead.js-0.11.1/test/000077500000000000000000000000001251733230600151325ustar00rootroot00000000000000typeahead.js-0.11.1/test/bloodhound/000077500000000000000000000000001251733230600172675ustar00rootroot00000000000000typeahead.js-0.11.1/test/bloodhound/bloodhound_spec.js000066400000000000000000000212511251733230600227750ustar00rootroot00000000000000describe('Bloodhound', function() { function build(o) { return new Bloodhound(_.mixin({ datumTokenizer: datumTokenizer, queryTokenizer: queryTokenizer }, o || {})); } beforeEach(function() { jasmine.Remote.useMock(); jasmine.Prefetch.useMock(); jasmine.Transport.useMock(); jasmine.PersistentStorage.useMock(); }); afterEach(function() { clearAjaxRequests(); }); describe('#initialize', function() { beforeEach(function() { this.bloodhound = build({ initialize: false }); spyOn(this.bloodhound, '_initialize').andCallThrough(); }); it('should not initialize if intialize option is false', function() { expect(this.bloodhound._initialize).not.toHaveBeenCalled(); }); it('should not support reinitialization by default', function() { var p1, p2; p1 = this.bloodhound.initialize(); p2 = this.bloodhound.initialize(); expect(p1).toBe(p2); expect(this.bloodhound._initialize.callCount).toBe(1); }); it('should reinitialize if reintialize flag is true', function() { var p1, p2; p1 = this.bloodhound.initialize(); p2 = this.bloodhound.initialize(true); expect(p1).not.toBe(p2); expect(this.bloodhound._initialize.callCount).toBe(2); }); it('should clear the index', function() { this.bloodhound = build({ initialize: false, prefetch: '/prefetch' }); spyOn(this.bloodhound, 'clear'); this.bloodhound.initialize(); expect(this.bloodhound.clear).toHaveBeenCalled(); }); it('should load data from prefetch cache if available', function() { this.bloodhound = build({ initialize: false, prefetch: '/prefetch' }); this.bloodhound.prefetch.fromCache.andReturn(fixtures.serialized.simple); this.bloodhound.initialize(); expect(this.bloodhound.all()).toEqual(fixtures.data.simple); expect(this.bloodhound.prefetch.fromNetwork).not.toHaveBeenCalled(); }); it('should load data from prefetch network as fallback', function() { this.bloodhound = build({ initialize: false, prefetch: '/prefetch' }); this.bloodhound.prefetch.fromCache.andReturn(null); this.bloodhound.prefetch.fromNetwork.andCallFake(fakeFromNetwork); this.bloodhound.initialize(); expect(this.bloodhound.all()).toEqual(fixtures.data.simple); function fakeFromNetwork(cb) { cb(null, fixtures.data.simple); } }); it('should store prefetch network data in the prefetch cache', function() { this.bloodhound = build({ initialize: false, prefetch: '/prefetch' }); this.bloodhound.prefetch.fromCache.andReturn(null); this.bloodhound.prefetch.fromNetwork.andCallFake(fakeFromNetwork); this.bloodhound.initialize(); expect(this.bloodhound.prefetch.store) .toHaveBeenCalledWith(fixtures.serialized.simple); function fakeFromNetwork(cb) { cb(null, fixtures.data.simple); } }); it('should add local after prefetch is loaded', function() { this.bloodhound = build({ initialize: false, local: [{ foo: 'bar' }], prefetch: '/prefetch' }); this.bloodhound.prefetch.fromNetwork.andCallFake(fakeFromNetwork); expect(this.bloodhound.all()).toEqual([]); this.bloodhound.initialize(); expect(this.bloodhound.all()).toEqual([{ foo: 'bar' }]); function fakeFromNetwork(cb) { cb(null, []); } }); }); describe('#add', function() { it('should add datums to search index', function() { var spy = jasmine.createSpy(); this.bloodhound = build().add(fixtures.data.simple); this.bloodhound.search('big', spy); expect(spy).toHaveBeenCalledWith([ { value: 'big' }, { value: 'bigger' }, { value: 'biggest' } ]); }); }); describe('#get', function() { beforeEach(function() { this.bloodhound = build({ identify: function(d) { return d.value; }, local: fixtures.data.simple }); }); it('should support array signature', function() { expect(this.bloodhound.get(['big', 'bigger'])).toEqual([ { value: 'big' }, { value: 'bigger' } ]); }); it('should support splat signature', function() { expect(this.bloodhound.get('big', 'bigger')).toEqual([ { value: 'big' }, { value: 'bigger' } ]); }); it('should return nothing for unknown ids', function() { expect(this.bloodhound.get('big', 'foo', 'bigger')).toEqual([ { value: 'big' }, { value: 'bigger' } ]); }); }); describe('#clear', function() { it('should remove all datums to search index', function() { var spy = jasmine.createSpy(); this.bloodhound = build({ local: fixtures.data.simple }).clear(); this.bloodhound.search('big', spy); expect(spy).toHaveBeenCalledWith([]); }); }); describe('#clearPrefetchCache', function() { it('should clear persistent storage', function() { this.bloodhound = build({ prefetch: '/prefetch' }).clearPrefetchCache(); expect(this.bloodhound.prefetch.clear).toHaveBeenCalled(); }); }); describe('#clearRemoteCache', function() { it('should clear remote request cache', function() { spyOn(Transport, 'resetCache'); this.bloodhound = build({ remote: '/remote' }).clearRemoteCache(); expect(Transport.resetCache).toHaveBeenCalled(); }); }); describe('#all', function() { it('should return all local results', function() { this.bloodhound = build({ local: fixtures.data.simple }); expect(this.bloodhound.all()).toEqual(fixtures.data.simple); }); }); describe('#search – local', function() { it('should return sync matches', function() { var spy = jasmine.createSpy(); this.bloodhound = build({ local: fixtures.data.simple }); this.bloodhound.search('big', spy); expect(spy).toHaveBeenCalledWith([ { value: 'big' }, { value: 'bigger' }, { value: 'biggest' } ]); }); }); describe('#search – prefetch', function() { it('should return sync matches', function() { var spy = jasmine.createSpy(); this.bloodhound = build({ initialize: false, prefetch: '/prefetch' }); this.bloodhound.prefetch.fromCache.andReturn(fixtures.serialized.simple); this.bloodhound.initialize(); this.bloodhound.search('big', spy); expect(spy).toHaveBeenCalledWith([ { value: 'big' }, { value: 'bigger' }, { value: 'biggest' } ]); }); }); describe('#search – remote', function() { it('should return async matches', function() { var spy = jasmine.createSpy(); this.bloodhound = build({ remote: '/remote' }); this.bloodhound.remote.get.andCallFake(fakeGet); this.bloodhound.search('dog', $.noop, spy); expect(spy.callCount).toBe(1); function fakeGet(o, cb) { cb(fixtures.data.animals); } }); }); describe('#search – integration', function() { it('should backfill when local/prefetch is not sufficient', function() { var syncSpy, asyncSpy; syncSpy = jasmine.createSpy(); asyncSpy = jasmine.createSpy(); this.bloodhound = build({ sufficient: 3, local: fixtures.data.simple, remote: '/remote' }); this.bloodhound.remote.get.andCallFake(fakeGet); this.bloodhound.search('big', syncSpy, asyncSpy); expect(syncSpy).toHaveBeenCalledWith([ { value: 'big' }, { value: 'bigger' }, { value: 'biggest' } ]); expect(asyncSpy).not.toHaveBeenCalled(); this.bloodhound.search('bigg', syncSpy, asyncSpy); expect(syncSpy).toHaveBeenCalledWith([ { value: 'bigger' }, { value: 'biggest' } ]); expect(asyncSpy).toHaveBeenCalledWith(fixtures.data.animals); function fakeGet(o, cb) { cb(fixtures.data.animals); } }); it('should remove duplicates from backfill', function() { var syncSpy, asyncSpy; syncSpy = jasmine.createSpy(); asyncSpy = jasmine.createSpy(); this.bloodhound = build({ identify: function(d) { return d.value; }, local: fixtures.data.animals, remote: '/remote' }); this.bloodhound.remote.get.andCallFake(fakeGet); this.bloodhound.search('dog', syncSpy, asyncSpy); expect(syncSpy).toHaveBeenCalledWith([{ value: 'dog' }]); expect(asyncSpy).toHaveBeenCalledWith([ { value: 'cat' }, { value: 'moose' } ]); function fakeGet(o, cb) { cb(fixtures.data.animals); } }); }); // helper functions // ---------------- function datumTokenizer(d) { return $.trim(d.value).split(/\s+/); } function queryTokenizer(s) { return $.trim(s).split(/\s+/); } }); typeahead.js-0.11.1/test/bloodhound/lru_cache_spec.js000066400000000000000000000023041251733230600225630ustar00rootroot00000000000000describe('LruCache', function() { beforeEach(function() { this.cache = new LruCache(3); }); it('should make entries retrievable by their keys', function() { var key = 'key', val = 42; this.cache.set(key, val); expect(this.cache.get(key)).toBe(val); }); it('should return undefined if key has not been set', function() { expect(this.cache.get('wat?')).toBeUndefined(); }); it('should hold up to maxSize entries', function() { this.cache.set('one', 1); this.cache.set('two', 2); this.cache.set('three', 3); this.cache.set('four', 4); expect(this.cache.get('one')).toBeUndefined(); expect(this.cache.get('two')).toBe(2); expect(this.cache.get('three')).toBe(3); expect(this.cache.get('four')).toBe(4); }); it('should evict lru entry if cache is full', function() { this.cache.set('one', 1); this.cache.set('two', 2); this.cache.set('three', 3); this.cache.get('one'); this.cache.set('four', 4); expect(this.cache.get('one')).toBe(1); expect(this.cache.get('two')).toBeUndefined(); expect(this.cache.get('three')).toBe(3); expect(this.cache.get('four')).toBe(4); expect(this.cache.size).toBe(3); }); }); typeahead.js-0.11.1/test/bloodhound/options_parser_spec.js000066400000000000000000000130651251733230600237130ustar00rootroot00000000000000describe('options parser', function() { function build(o) { return oParser(_.mixin({ datumTokenizer: $.noop, queryTokenizer: $.noop }, o || {})); } function prefetch(o) { return oParser({ datumTokenizer: $.noop, queryTokenizer: $.noop, prefetch: _.mixin({ url: '/example' }, o || {}) }); } function remote(o) { return oParser({ datumTokenizer: $.noop, queryTokenizer: $.noop, remote: _.mixin({ url: '/example' }, o || {}) }); } it('should throw exception if datumTokenizer is not set', function() { expect(parse).toThrow(); function parse() { build({ datumTokenizer: null }); } }); it('should throw exception if queryTokenizer is not set', function() { expect(parse).toThrow(); function parse() { build({ queryTokenizer: null }); } }); it('should wrap sorter', function() { var o = build({ sorter: function(a, b) { return a -b; } }); expect(o.sorter([2, 1, 3])).toEqual([1, 2, 3]); }); it('should default sorter to identity function', function() { var o = build(); expect(o.sorter([2, 1, 3])).toEqual([2, 1, 3]); }); describe('local', function() { it('should default to empty array', function() { var o = build(); expect(o.local).toEqual([]); }); it('should support function', function() { var o = build({ local: function() { return [1]; } }); expect(o.local).toEqual([1]); }); it('should support arrays', function() { var o = build({ local: [1] }); expect(o.local).toEqual([1]); }); }); describe('prefetch', function() { it('should throw exception if url is not set', function() { expect(parse).toThrow(); function parse() { prefetch({ url: null }); } }); it('should support simple string format', function() { expect(build({ prefetch: '/prefetch' }).prefetch).toBeDefined(); }); it('should default ttl to 1 day', function() { var o = prefetch(); expect(o.prefetch.ttl).toBe(86400000); }); it('should default cache to true', function() { var o = prefetch(); expect(o.prefetch.cache).toBe(true); }); it('should default transform to identiy function', function() { var o = prefetch(); expect(o.prefetch.transform('foo')).toBe('foo'); }); it('should default cacheKey to url', function() { var o = prefetch(); expect(o.prefetch.cacheKey).toBe(o.prefetch.url); }); it('should default transport to jQuery.ajax', function() { var o = prefetch(); expect(o.prefetch.transport).toBe($.ajax); }); it('should prepend verison to thumbprint', function() { var o = prefetch(); expect(o.prefetch.thumbprint).toBe('%VERSION%'); o = prefetch({ thumbprint: 'foo' }); expect(o.prefetch.thumbprint).toBe('%VERSION%foo'); }); it('should wrap custom transport to be deferred compatible', function() { var o, errDeferred, successDeferred; o = prefetch({ transport: errTransport }); errDeferred = o.prefetch.transport('q'); o = prefetch({ transport: successTransport }); successDeferred = o.prefetch.transport('q'); waits(0); runs(function() { expect(errDeferred.isRejected()).toBe(true); expect(successDeferred.isResolved()).toBe(true); }); function errTransport(q, success, error) { error(); } function successTransport(q, success, error) { success(); } }); }); describe('remote', function() { it('should throw exception if url is not set', function() { expect(parse).toThrow(); function parse() { remote({ url: null }); } }); it('should support simple string format', function() { expect(build({ remote: '/remote' }).remote).toBeDefined(); }); it('should default transform to identiy function', function() { var o = remote(); expect(o.remote.transform('foo')).toBe('foo'); }); it('should default transport to jQuery.ajax', function() { var o = remote(); expect(o.remote.transport).toBe($.ajax); }); it('should default limiter to debouce', function() { var o = remote(); expect(o.remote.limiter.name).toBe('debounce'); }); it('should default prepare to identity function', function() { var o = remote(); expect(o.remote.prepare('q', { url: '/foo' })).toEqual({ url: '/foo' }); }); it('should support wildcard for prepare', function() { var o = remote({ wildcard: '%FOO' }); expect(o.remote.prepare('=', { url: '/%FOO' })).toEqual({ url: '/%3D' }); }); it('should support replace for prepare', function() { var o = remote({ replace: function() { return '/bar'; } }); expect(o.remote.prepare('q', { url: '/foo' })).toEqual({ url: '/bar' }); }); it('should should rateLimitBy for limiter', function() { var o = remote({ rateLimitBy: 'throttle' }); expect(o.remote.limiter.name).toBe('throttle'); }); it('should wrap custom transport to be deferred compatible', function() { var o, errDeferred, successDeferred; o = remote({ transport: errTransport }); errDeferred = o.remote.transport('q'); o = remote({ transport: successTransport }); successDeferred = o.remote.transport('q'); waits(0); runs(function() { expect(errDeferred.isRejected()).toBe(true); expect(successDeferred.isResolved()).toBe(true); }); function errTransport(q, success, error) { error(); } function successTransport(q, success, error) { success(); } }); }); }); typeahead.js-0.11.1/test/bloodhound/persistent_storage_spec.js000066400000000000000000000125601251733230600245670ustar00rootroot00000000000000describe('PersistentStorage', function() { var engine, ls; // test suite is dependent on localStorage being available if (!window.localStorage) { console.warn('no localStorage support – skipping PersistentStorage suite'); return; } // for good measure! localStorage.clear(); beforeEach(function() { ls = { get length() { return localStorage.length; }, key: spyThrough('key'), clear: spyThrough('clear'), getItem: spyThrough('getItem'), setItem: spyThrough('setItem'), removeItem: spyThrough('removeItem') }; engine = new PersistentStorage('ns', ls); spyOn(Date.prototype, 'getTime').andReturn(0); }); afterEach(function() { localStorage.clear(); }); // public methods // -------------- describe('#get', function() { it('should access localStorage with prefixed key', function() { engine.get('key'); expect(ls.getItem).toHaveBeenCalledWith('__ns__key'); }); it('should return undefined when key does not exist', function() { expect(engine.get('does not exist')).toEqual(undefined); }); it('should return value as correct type', function() { engine.set('string', 'i am a string'); engine.set('number', 42); engine.set('boolean', true); engine.set('null', null); engine.set('object', { obj: true }); expect(engine.get('string')).toEqual('i am a string'); expect(engine.get('number')).toEqual(42); expect(engine.get('boolean')).toEqual(true); expect(engine.get('null')).toBeNull(); expect(engine.get('object')).toEqual({ obj: true }); }); it('should expire stale keys', function() { engine.set('key', 'value', -1); expect(engine.get('key')).toBeNull(); expect(ls.getItem('__ns__key__ttl')).toBeNull(); }); }); describe('#set', function() { it('should access localStorage with prefixed key', function() { engine.set('key', 'val'); expect(ls.setItem.mostRecentCall.args[0]).toEqual('__ns__key'); }); it('should JSON.stringify value before storing', function() { engine.set('key', 'val'); expect(ls.setItem.mostRecentCall.args[1]).toEqual(JSON.stringify('val')); }); it('should store ttl if provided', function() { var ttl = 1; engine.set('key', 'value', ttl); expect(ls.setItem.argsForCall[0]) .toEqual(['__ns__key__ttl__', ttl.toString()]); }); it('should call clear if the localStorage limit has been reached', function() { var spy; ls.setItem.andCallFake(function() { var err = new Error(); err.name = 'QuotaExceededError'; throw err; }); engine.clear = spy = jasmine.createSpy(); engine.set('key', 'value', 1); expect(spy).toHaveBeenCalled(); }); it('should noop if the localStorage limit has been reached', function() { var get, set, remove, clear, isExpired; ls.setItem.andCallFake(function() { var err = new Error(); err.name = 'QuotaExceededError'; throw err; }); get = engine.get; set = engine.set; remove = engine.remove; clear = engine.clear; isExpired = engine.isExpired; engine.set('key', 'value', 1); expect(engine.get).not.toBe(get); expect(engine.set).not.toBe(set); expect(engine.remove).not.toBe(remove); expect(engine.clear).not.toBe(clear); expect(engine.isExpired).not.toBe(isExpired); }); }); describe('#remove', function() { it('should remove key from storage', function() { engine.set('key', 'val'); engine.remove('key'); expect(engine.get('key')).toBeNull(); }); }); describe('#clear', function() { it('should work with namespaces that contain regex characters', function() { engine = new PersistentStorage('ns?()'); engine.set('key1', 'val1'); engine.set('key2', 'val2'); engine.clear(); expect(engine.get('key1')).toEqual(undefined); expect(engine.get('key2')).toEqual(undefined); }); it('should remove all keys that exist in namespace of engine', function() { engine.set('key1', 'val1'); engine.set('key2', 'val2'); engine.set('key3', 'val3'); engine.set('key4', 'val4', 0); engine.clear(); expect(engine.get('key1')).toEqual(undefined); expect(engine.get('key2')).toEqual(undefined); expect(engine.get('key3')).toEqual(undefined); expect(engine.get('key4')).toEqual(undefined); }); it('should not affect keys with different namespace', function() { ls.setItem('diff_namespace', 'val'); engine.clear(); expect(ls.getItem('diff_namespace')).toEqual('val'); }); }); describe('#isExpired', function() { it('should be false for keys without ttl', function() { engine.set('key', 'value'); expect(engine.isExpired('key')).toBe(false); }); it('should be false for fresh keys', function() { engine.set('key', 'value', 1); expect(engine.isExpired('key')).toBe(false); }); it('should be true for stale keys', function() { engine.set('key', 'value', -1); expect(engine.isExpired('key')).toBe(true); }); }); // compatible across browsers function spyThrough(method) { return jasmine.createSpy().andCallFake(fake); function fake() { return localStorage[method].apply(localStorage, arguments); } } }); typeahead.js-0.11.1/test/bloodhound/prefetch_spec.js000066400000000000000000000113451251733230600224430ustar00rootroot00000000000000describe('Prefetch', function() { function build(o) { return new Prefetch(_.mixin({ url: '/prefetch', ttl: 3600, cache: true, thumbprint: '', cacheKey: 'cachekey', prepare: function(x) { return x; }, transform: function(x) { return x; }, transport: $.ajax }, o || {})); } beforeEach(function() { jasmine.PersistentStorage.useMock(); this.prefetch = build(); this.storage = this.prefetch.storage; this.thumbprint = this.prefetch.thumbprint; }); describe('#clear', function() { it('should clear cache storage', function() { this.prefetch.clear(); expect(this.storage.clear).toHaveBeenCalled(); }); }); describe('#store', function() { it('should store data in the storage cache', function() { this.prefetch.store({ foo: 'bar' }); expect(this.storage.set) .toHaveBeenCalledWith('data', { foo: 'bar' }, 3600); }); it('should store thumbprint in the storage cache', function() { this.prefetch.store({ foo: 'bar' }); expect(this.storage.set) .toHaveBeenCalledWith('thumbprint', jasmine.any(String), 3600); }); it('should store protocol in the storage cache', function() { this.prefetch.store({ foo: 'bar' }); expect(this.storage.set) .toHaveBeenCalledWith('protocol', location.protocol, 3600); }); it('should be noop if cache option is false', function() { this.prefetch = build({ cache: false }); this.prefetch.store({ foo: 'bar' }); expect(this.storage.set).not.toHaveBeenCalled(); }); }); describe('#fromCache', function() { it('should return data if available', function() { this.storage.get .andCallFake(fakeStorageGet({ foo: 'bar' }, this.thumbprint)); expect(this.prefetch.fromCache()).toEqual({ foo: 'bar' }); }); it('should return null if data is expired', function() { this.storage.get .andCallFake(fakeStorageGet({ foo: 'bar' }, 'foo')); expect(this.prefetch.fromCache()).toBeNull(); }); it('should return null if data does not exist', function() { this.storage.get .andCallFake(fakeStorageGet(null, this.thumbprint)); expect(this.prefetch.fromCache()).toBeNull(); }); it('should return null if cache option is false', function() { this.prefetch = build({ cache: false }); this.storage.get .andCallFake(fakeStorageGet({ foo: 'bar' }, this.thumbprint)); expect(this.prefetch.fromCache()).toBeNull(); expect(this.storage.get).not.toHaveBeenCalled(); }); }); describe('#fromNetwork', function() { it('should have sensible default request settings', function() { var spy; spy = jasmine.createSpy(); spyOn(this.prefetch, 'transport').andReturn($.Deferred()); this.prefetch.fromNetwork(spy); expect(this.prefetch.transport).toHaveBeenCalledWith({ url: '/prefetch', type: 'GET', dataType: 'json' }); }); it('should transform request settings with prepare', function() { var spy; spy = jasmine.createSpy(); spyOn(this.prefetch, 'prepare').andReturn({ foo: 'bar' }); spyOn(this.prefetch, 'transport').andReturn($.Deferred()); this.prefetch.fromNetwork(spy); expect(this.prefetch.transport).toHaveBeenCalledWith({ foo: 'bar' }); }); it('should transform the response using transform', function() { var spy; this.prefetch = build({ transform: function() { return { bar: 'foo' }; } }); spy = jasmine.createSpy(); spyOn(this.prefetch, 'transport') .andReturn($.Deferred().resolve({ foo: 'bar' })); this.prefetch.fromNetwork(spy); expect(spy).toHaveBeenCalledWith(null, { bar: 'foo' }); }); it('should invoke callback with data if success', function() { var spy; spy = jasmine.createSpy(); spyOn(this.prefetch, 'transport') .andReturn($.Deferred().resolve({ foo: 'bar' })); this.prefetch.fromNetwork(spy); expect(spy).toHaveBeenCalledWith(null, { foo: 'bar' }); }); it('should invoke callback with err argument true if failure', function() { var spy; spy = jasmine.createSpy(); spyOn(this.prefetch, 'transport').andReturn($.Deferred().reject()); this.prefetch.fromNetwork(spy); expect(spy).toHaveBeenCalledWith(true); }); }); function fakeStorageGet(data, thumbprint, protocol) { return function(key) { var val; switch (key) { case 'data': val = data; break; case 'protocol': val = protocol || location.protocol; break; case 'thumbprint': val = thumbprint; break; } return val; }; } }); typeahead.js-0.11.1/test/bloodhound/remote_spec.js000066400000000000000000000034751251733230600221430ustar00rootroot00000000000000describe('Remote', function() { beforeEach(function() { jasmine.Transport.useMock(); this.remote = new Remote({ url: '/test?q=%QUERY', prepare: function(x) { return x; }, transform: function(x) { return x; } }); this.transport = this.remote.transport; }); describe('#cancelLastRequest', function() { it('should cancel last request', function() { this.remote.cancelLastRequest(); expect(this.transport.cancel).toHaveBeenCalled(); }); }); describe('#get', function() { it('should have sensible default request settings', function() { var spy; spy = jasmine.createSpy(); spyOn(this.remote, 'prepare'); this.remote.get('foo', spy); expect(this.remote.prepare).toHaveBeenCalledWith('foo', { url: '/test?q=%QUERY', type: 'GET', dataType: 'json' }); }); it('should transform request settings with prepare', function() { var spy; spy = jasmine.createSpy(); spyOn(this.remote, 'prepare').andReturn([{ foo: 'bar' }]); this.remote.get('foo', spy); expect(this.transport.get) .toHaveBeenCalledWith([{ foo: 'bar' }], jasmine.any(Function)); }); it('should transform response with transform', function() { var spy; spy = jasmine.createSpy(); spyOn(this.remote, 'transform').andReturn([{ foo: 'bar' }]); this.transport.get.andCallFake(function(_, cb) { cb(null, {}); }); this.remote.get('foo', spy); expect(spy).toHaveBeenCalledWith([{ foo: 'bar' }]); }); it('should return empty array on error', function() { var spy; spy = jasmine.createSpy(); this.transport.get.andCallFake(function(_, cb) { cb(true); }); this.remote.get('foo', spy); expect(spy).toHaveBeenCalledWith([]); }); }); }); typeahead.js-0.11.1/test/bloodhound/search_index_spec.js000066400000000000000000000040071251733230600232740ustar00rootroot00000000000000describe('SearchIndex', function() { function build(o) { return new SearchIndex(_.mixin({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), queryTokenizer: Bloodhound.tokenizers.whitespace }, o || {})); } beforeEach(function() { this.index = build(); this.index.add(fixtures.data.simple); }); it('should support serialization/deserialization', function() { var serialized = this.index.serialize(); this.index.bootstrap(serialized); expect(this.index.search('smaller')).toEqual([{ value: 'smaller' }]); }); it('should be able to add data on the fly', function() { this.index.add({ value: 'new' }); expect(this.index.search('new')).toEqual([{ value: 'new' }]); }); it('#get should return datums by id', function() { this.index = build({ identify: function(d) { return d.value; } }); this.index.add(fixtures.data.simple); expect(this.index.get(['big', 'bigger'])).toEqual([ { value: 'big' }, { value: 'bigger' } ]); }); it('#search should return datums that match the given query', function() { expect(this.index.search('big')).toEqual([ { value: 'big' }, { value: 'bigger' }, { value: 'biggest' } ]); expect(this.index.search('small')).toEqual([ { value: 'small' }, { value: 'smaller' }, { value: 'smallest' } ]); }); it('#search should return an empty array of there are no matches', function() { expect(this.index.search('wtf')).toEqual([]); }); it('#serach should handle multi-token queries', function() { this.index.add({ value: 'foo bar' }); expect(this.index.search('foo b')).toEqual([{ value: 'foo bar' }]); }); it('#all should return all datums', function() { expect(this.index.all()).toEqual(fixtures.data.simple); }); it('#reset should empty the search index', function() { this.index.reset(); expect(this.index.datums).toEqual([]); expect(this.index.trie.i).toEqual([]); expect(this.index.trie.c).toEqual({}); }); }); typeahead.js-0.11.1/test/bloodhound/tokenizers_spec.js000066400000000000000000000045261251733230600230430ustar00rootroot00000000000000describe('tokenizers', function() { it('.whitespace should tokenize on whitespace', function() { var tokens = tokenizers.whitespace('big-deal ok'); expect(tokens).toEqual(['big-deal', 'ok']); }); it('.whitespace should treat null as empty string', function() { var tokens = tokenizers.whitespace(null); expect(tokens).toEqual([]); }); it('.whitespace should treat undefined as empty string', function() { var tokens = tokenizers.whitespace(undefined); expect(tokens).toEqual([]); }); it('.nonword should tokenize on non-word characters', function() { var tokens = tokenizers.nonword('big-deal ok'); expect(tokens).toEqual(['big', 'deal', 'ok']); }); it('.nonword should treat null as empty string', function() { var tokens = tokenizers.nonword(null); expect(tokens).toEqual([]); }); it('.nonword should treat undefined as empty string', function() { var tokens = tokenizers.nonword(undefined); expect(tokens).toEqual([]); }); it('.obj.whitespace should tokenize on whitespace', function() { var t = tokenizers.obj.whitespace('val'); var tokens = t({ val: 'big-deal ok' }); expect(tokens).toEqual(['big-deal', 'ok']); }); it('.obj.whitespace should accept multiple properties', function() { var t = tokenizers.obj.whitespace('one', 'two'); var tokens = t({ one: 'big-deal ok', two: 'buzz' }); expect(tokens).toEqual(['big-deal', 'ok', 'buzz']); }); it('.obj.whitespace should accept array', function() { var t = tokenizers.obj.whitespace(['one', 'two']); var tokens = t({ one: 'big-deal ok', two: 'buzz' }); expect(tokens).toEqual(['big-deal', 'ok', 'buzz']); }); it('.obj.nonword should tokenize on non-word characters', function() { var t = tokenizers.obj.nonword('val'); var tokens = t({ val: 'big-deal ok' }); expect(tokens).toEqual(['big', 'deal', 'ok']); }); it('.obj.nonword should accept multiple properties', function() { var t = tokenizers.obj.nonword('one', 'two'); var tokens = t({ one: 'big-deal ok', two: 'buzz' }); expect(tokens).toEqual(['big', 'deal', 'ok', 'buzz']); }); it('.obj.nonword should accept array', function() { var t = tokenizers.obj.nonword(['one', 'two']); var tokens = t({ one: 'big-deal ok', two: 'buzz' }); expect(tokens).toEqual(['big', 'deal', 'ok', 'buzz']); }); }); typeahead.js-0.11.1/test/bloodhound/transport_spec.js000066400000000000000000000117241251733230600227000ustar00rootroot00000000000000describe('Transport', function() { beforeEach(function() { jasmine.Ajax.useMock(); jasmine.Clock.useMock(); this.transport = new Transport({ transport: $.ajax }); }); afterEach(function() { // run twice to flush out on-deck requests $.each(ajaxRequests, drop); $.each(ajaxRequests, drop); clearAjaxRequests(); Transport.resetCache(); function drop(i, req) { req.readyState !== 4 && req.response(fixtures.ajaxResps.ok); } }); it('should use jQuery.ajax as the default transport mechanism', function() { var req, resp = fixtures.ajaxResps.ok, spy = jasmine.createSpy(); this.transport.get('/test', spy); req = mostRecentAjaxRequest(); req.response(resp); expect(req.url).toBe('/test'); expect(spy).toHaveBeenCalledWith(null, resp.parsed); }); it('should respect maxPendingRequests configuration', function() { for (var i = 0; i < 10; i++) { this.transport.get('/test' + i, $.noop); } expect(ajaxRequests.length).toBe(6); }); it('should support rate limiting', function() { this.transport = new Transport({ transport: $.ajax, limiter: limiter }); for (var i = 0; i < 5; i++) { this.transport.get('/test' + i, $.noop); } jasmine.Clock.tick(100); expect(ajaxRequests.length).toBe(1); function limiter(fn) { return _.debounce(fn, 20); } }); it('should cache most recent requests', function() { var spy1 = jasmine.createSpy(), spy2 = jasmine.createSpy(); this.transport.get('/test1', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); this.transport.get('/test2', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok1); expect(ajaxRequests.length).toBe(2); this.transport.get('/test1', spy1); this.transport.get('/test2', spy2); jasmine.Clock.tick(0); // no ajax requests were made on subsequent requests expect(ajaxRequests.length).toBe(2); expect(spy1).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok.parsed); expect(spy2).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok1.parsed); }); it('should not cache requests if cache option is false', function() { this.transport = new Transport({ transport: $.ajax, cache: false }); this.transport.get('/test1', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); this.transport.get('/test1', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(ajaxRequests.length).toBe(2); }); it('should prevent dog pile', function() { var spy1 = jasmine.createSpy(), spy2 = jasmine.createSpy(); this.transport.get('/test1', spy1); this.transport.get('/test1', spy2); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(ajaxRequests.length).toBe(1); waitsFor(function() { return spy1.callCount && spy2.callCount; }); runs(function() { expect(spy1).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok.parsed); expect(spy2).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok.parsed); }); }); it('should always make a request for the last call to #get', function() { var spy = jasmine.createSpy(); for (var i = 0; i < 6; i++) { this.transport.get('/test' + i, $.noop); } this.transport.get('/test' + i, spy); expect(ajaxRequests.length).toBe(6); _.each(ajaxRequests, function(req) { req.response(fixtures.ajaxResps.ok); }); expect(ajaxRequests.length).toBe(7); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(spy).toHaveBeenCalled(); }); it('should invoke the callback with err set to true on failure', function() { var req, resp = fixtures.ajaxResps.err, spy = jasmine.createSpy(); this.transport.get('/test', spy); req = mostRecentAjaxRequest(); req.response(resp); expect(req.url).toBe('/test'); expect(spy).toHaveBeenCalledWith(true); }); it('should not send cancelled requests', function() { this.transport = new Transport({ transport: $.ajax, limiter: limiter }); this.transport.get('/test', $.noop); this.transport.cancel(); jasmine.Clock.tick(100); expect(ajaxRequests.length).toBe(0); function limiter(fn) { return _.debounce(fn, 20); } }); it('should not send outdated requests', function() { this.transport = new Transport({ transport: $.ajax, limiter: limiter }); // warm cache this.transport.get('/test1', $.noop); jasmine.Clock.tick(100); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(mostRecentAjaxRequest().url).toBe('/test1'); expect(ajaxRequests.length).toBe(1); // within the same rate-limit cycle, request test2 and test1. test2 becomes // outdated after test1 is requested and no request is sent for test1 // because it's a cache hit this.transport.get('/test2', $.noop); this.transport.get('/test1', $.noop); jasmine.Clock.tick(100); expect(ajaxRequests.length).toBe(1); function limiter(fn) { return _.debounce(fn, 20); } }); }); typeahead.js-0.11.1/test/ci000077500000000000000000000006541251733230600154600ustar00rootroot00000000000000#!/bin/bash -x if [ "$TEST_SUITE" == "unit" ]; then ./node_modules/karma/bin/karma start --single-run --browsers PhantomJS elif [ "$TRAVIS_SECURE_ENV_VARS" == "true" -a "$TEST_SUITE" == "integration" ]; then static -p 8888 & sleep 3 # integration tests are flaky, don't let them fail the build ./node_modules/mocha/bin/mocha --harmony -R spec ./test/integration/test.js || true else echo "Not running any tests" fi typeahead.js-0.11.1/test/fixtures/000077500000000000000000000000001251733230600170035ustar00rootroot00000000000000typeahead.js-0.11.1/test/fixtures/ajax_responses.js000066400000000000000000000007321251733230600223670ustar00rootroot00000000000000var fixtures = fixtures || {}; fixtures.ajaxResps = { ok: { status: 200, responseText: '[{ "value": "big" }, { "value": "bigger" }, { "value": "biggest" }, { "value": "small" }, { "value": "smaller" }, { "value": "smallest" }]' }, ok1: { status: 200, responseText: '["dog", "cat", "moose"]' }, err: { status: 500 } }; $.each(fixtures.ajaxResps, function(i, resp) { resp.responseText && (resp.parsed = $.parseJSON(resp.responseText)); }); typeahead.js-0.11.1/test/fixtures/data.js000066400000000000000000000130021251733230600202460ustar00rootroot00000000000000var fixtures = fixtures || {}; fixtures.data = { simple: [ { value: 'big' }, { value: 'bigger' }, { value: 'biggest' }, { value: 'small' }, { value: 'smaller' }, { value: 'smallest' } ], animals: [ { value: 'dog' }, { value: 'cat' }, { value: 'moose' } ] }; fixtures.serialized = { simple: { "datums": { "{\"value\":\"big\"}": { "value": "big" }, "{\"value\":\"bigger\"}": { "value": "bigger" }, "{\"value\":\"biggest\"}": { "value": "biggest" }, "{\"value\":\"small\"}": { "value": "small" }, "{\"value\":\"smaller\"}": { "value": "smaller" }, "{\"value\":\"smallest\"}": { "value": "smallest" } }, "trie": { "i": [], "c": { "b": { "i": ["{\"value\":\"big\"}", "{\"value\":\"bigger\"}", "{\"value\":\"biggest\"}"], "c": { "i": { "i": ["{\"value\":\"big\"}", "{\"value\":\"bigger\"}", "{\"value\":\"biggest\"}"], "c": { "g": { "i": ["{\"value\":\"big\"}", "{\"value\":\"bigger\"}", "{\"value\":\"biggest\"}"], "c": { "g": { "i": ["{\"value\":\"bigger\"}", "{\"value\":\"biggest\"}"], "c": { "e": { "i": ["{\"value\":\"bigger\"}", "{\"value\":\"biggest\"}"], "c": { "r": { "i": ["{\"value\":\"bigger\"}"], "c": {} }, "s": { "i": ["{\"value\":\"biggest\"}"], "c": { "t": { "i": ["{\"value\":\"biggest\"}"], "c": {} } } } } } } } } } } } } }, "s": { "i": ["{\"value\":\"small\"}", "{\"value\":\"smaller\"}", "{\"value\":\"smallest\"}"], "c": { "m": { "i": ["{\"value\":\"small\"}", "{\"value\":\"smaller\"}", "{\"value\":\"smallest\"}"], "c": { "a": { "i": ["{\"value\":\"small\"}", "{\"value\":\"smaller\"}", "{\"value\":\"smallest\"}"], "c": { "l": { "i": ["{\"value\":\"small\"}", "{\"value\":\"smaller\"}", "{\"value\":\"smallest\"}"], "c": { "l": { "i": ["{\"value\":\"small\"}", "{\"value\":\"smaller\"}", "{\"value\":\"smallest\"}"], "c": { "e": { "i": ["{\"value\":\"smaller\"}", "{\"value\":\"smallest\"}"], "c": { "r": { "i": ["{\"value\":\"smaller\"}"], "c": {} }, "s": { "i": ["{\"value\":\"smallest\"}"], "c": { "t": { "i": ["{\"value\":\"smallest\"}"], "c": {} } } } } } } } } } } } } } } } } } } } typeahead.js-0.11.1/test/fixtures/html.js000066400000000000000000000007331251733230600203100ustar00rootroot00000000000000var fixtures = fixtures || {}; fixtures.html = { input: '', hint: '', dataset: [ '
', '

one

', '

two

', '

three

', '
' ].join('') }; typeahead.js-0.11.1/test/helpers/000077500000000000000000000000001251733230600165745ustar00rootroot00000000000000typeahead.js-0.11.1/test/helpers/typeahead_mocks.js000066400000000000000000000034421251733230600222750ustar00rootroot00000000000000(function(root) { var components; components = [ 'Bloodhound', 'Prefetch', 'Remote', 'PersistentStorage', 'Transport', 'SearchIndex', 'Input', 'Dataset', 'Menu' ]; for (var i = 0; i < components.length; i++) { makeMockable(components[i]); } function makeMockable(component) { var Original, Mock; Original = root[component]; Mock = mock(Original); jasmine[component] = { useMock: useMock, uninstallMock: uninstallMock }; function useMock() { root[component] = Mock; jasmine.getEnv().currentSpec.after(uninstallMock); } function uninstallMock() { root[component] = Original; } } function mock(Constructor) { var constructorSpy; Mock.prototype = Constructor.prototype; constructorSpy = jasmine.createSpy('mock constructor').andCallFake(Mock); // copy instance methods for (var key in Constructor) { if (typeof Constructor[key] === 'function') { constructorSpy[key] = Constructor[key]; } } return constructorSpy; function Mock() { var instance = _.mixin({}, Constructor.prototype); for (var key in instance) { if (typeof instance[key] === 'function') { spyOn(instance, key); // special case for some components if (key === 'bind') { instance[key].andCallFake(function() { return this; }); } } } // have the event emitter methods call through instance.onSync && instance.onSync.andCallThrough(); instance.onAsync && instance.onAsync.andCallThrough(); instance.off && instance.off.andCallThrough(); instance.trigger && instance.trigger.andCallThrough(); instance.constructor = Constructor; return instance; } } })(this); typeahead.js-0.11.1/test/integration/000077500000000000000000000000001251733230600174555ustar00rootroot00000000000000typeahead.js-0.11.1/test/integration/test.html000066400000000000000000000053561251733230600213330ustar00rootroot00000000000000
typeahead.js-0.11.1/test/integration/test.js000066400000000000000000000271321251733230600207770ustar00rootroot00000000000000/* jshint esnext: true, evil: true, sub: true */ var wd = require('yiewd'), colors = require('colors'), expect = require('chai').expect, _ = require('underscore'), f = require('util').format, env = process.env; var browser, caps; browser = (process.env.BROWSER || 'chrome').split(':'); caps = { name: f('[%s] typeahead.js ui', browser.join(' , ')), browserName: browser[0] }; setIf(caps, 'version', browser[1]); setIf(caps, 'platform', browser[2]); setIf(caps, 'tunnel-identifier', env['TRAVIS_JOB_NUMBER']); setIf(caps, 'build', env['TRAVIS_BUILD_NUMBER']); setIf(caps, 'tags', env['CI'] ? ['CI'] : ['local']); function setIf(obj, key, val) { val && (obj[key] = val); } describe('jquery-typeahead.js', function() { var driver, body, input, hint, dropdown, allPassed = true; this.timeout(300000); before(function(done) { var host = 'ondemand.saucelabs.com', port = 80, username, password; if (env['CI']) { host = 'localhost'; port = 4445; username = env['SAUCE_USERNAME']; password = env['SAUCE_ACCESS_KEY']; } driver = wd.remote(host, port, username, password); driver.configureHttp({ timeout: 30000, retries: 5, retryDelay: 200 }); driver.on('status', function(info) { console.log(info.cyan); }); driver.on('command', function(meth, path, data) { console.log(' > ' + meth.yellow, path.grey, data || ''); }); driver.run(function*() { yield this.init(caps); yield this.get('http://localhost:8888/test/integration/test.html'); body = yield this.elementByTagName('body'); input = yield this.elementById('states'); hint = yield this.elementByClassName('tt-hint'); dropdown = yield this.elementByClassName('tt-menu'); done(); }); }); afterEach(function(done) { allPassed = allPassed && (this.currentTest.state === 'passed'); driver.run(function*() { yield body.click(); yield this.execute('window.jQuery("#states").typeahead("val", "")'); done(); }); }); after(function(done) { driver.run(function*() { yield this.quit(); yield driver.sauceJobStatus(allPassed); done(); }); }); describe('on blur', function() { it('should close dropdown', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); expect(yield dropdown.isDisplayed()).to.equal(true); yield body.click(); expect(yield dropdown.isDisplayed()).to.equal(false); done(); }); }); it('should clear hint', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); expect(yield hint.getValue()).to.equal('michigan'); yield body.click(); expect(yield hint.getValue()).to.equal(''); done(); }); }); }); describe('on query change', function() { it('should open dropdown if suggestions', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); expect(yield dropdown.isDisplayed()).to.equal(true); done(); }); }); it('should close dropdown if no suggestions', function(done) { driver.run(function*() { yield input.click(); yield input.type('huh?'); expect(yield dropdown.isDisplayed()).to.equal(false); done(); }); }); it('should render suggestions if suggestions', function(done) { driver.run(function*() { var suggestions; yield input.click(); yield input.type('mi'); suggestions = yield dropdown.elementsByClassName('tt-suggestion'); expect(suggestions).to.have.length('4'); expect(yield suggestions[0].text()).to.equal('Michigan'); expect(yield suggestions[1].text()).to.equal('Minnesota'); expect(yield suggestions[2].text()).to.equal('Mississippi'); expect(yield suggestions[3].text()).to.equal('Missouri'); done(); }); }); it('should show hint if top suggestion is a match', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); expect(yield hint.getValue()).to.equal('michigan'); done(); }); }); it('should match hint to query', function(done) { driver.run(function*() { yield input.click(); yield input.type('NeW JE'); expect(yield hint.getValue()).to.equal('NeW JErsey'); done(); }); }); it('should not show hint if top suggestion is not a match', function(done) { driver.run(function*() { yield input.click(); yield input.type('ham'); expect(yield hint.getValue()).to.equal(''); done(); }); }); it('should not show hint if there is query overflow', function(done) { driver.run(function*() { yield input.click(); yield input.type('this is a very long value so '); expect(yield hint.getValue()).to.equal(''); done(); }); }); }); describe('on up arrow', function() { it('should cycle through suggestions', function(done) { driver.run(function*() { var suggestions; yield input.click(); yield input.type('mi'); suggestions = yield dropdown.elementsByClassName('tt-suggestion'); yield input.type(wd.SPECIAL_KEYS['Up arrow']); expect(yield input.getValue()).to.equal('Missouri'); expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Up arrow']); expect(yield input.getValue()).to.equal('Mississippi'); expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Up arrow']); expect(yield input.getValue()).to.equal('Minnesota'); expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Up arrow']); expect(yield input.getValue()).to.equal('Michigan'); expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Up arrow']); expect(yield input.getValue()).to.equal('mi'); expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); done(); }); }); }); describe('on down arrow', function() { it('should cycle through suggestions', function(done) { driver.run(function*() { var suggestions; yield input.click(); yield input.type('mi'); suggestions = yield dropdown.elementsByClassName('tt-suggestion'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); expect(yield input.getValue()).to.equal('Michigan'); expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); expect(yield input.getValue()).to.equal('Minnesota'); expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); expect(yield input.getValue()).to.equal('Mississippi'); expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); expect(yield input.getValue()).to.equal('Missouri'); expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion tt-selectable tt-cursor'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); expect(yield input.getValue()).to.equal('mi'); expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion tt-selectable'); done(); }); }); }); describe('on escape', function() { it('should close dropdown', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); expect(yield dropdown.isDisplayed()).to.equal(true); yield input.type(wd.SPECIAL_KEYS['Escape']); expect(yield dropdown.isDisplayed()).to.equal(false); done(); }); }); it('should clear hint', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); expect(yield hint.getValue()).to.equal('michigan'); yield input.type(wd.SPECIAL_KEYS['Escape']); expect(yield hint.getValue()).to.equal(''); done(); }); }); }); describe('on tab', function() { it('should autocomplete if hint is present', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); yield input.type(wd.SPECIAL_KEYS['Tab']); expect(yield input.getValue()).to.equal('Michigan'); done(); }); }); it('should select if cursor is on suggestion', function(done) { driver.run(function*() { var suggestions; yield input.click(); yield input.type('mi'); suggestions = yield dropdown.elementsByClassName('tt-suggestion'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); yield input.type(wd.SPECIAL_KEYS['Down arrow']); yield input.type(wd.SPECIAL_KEYS['Tab']); expect(yield dropdown.isDisplayed()).to.equal(false); expect(yield input.getValue()).to.equal('Minnesota'); done(); }); }); }); describe('on right arrow', function() { it('should autocomplete if hint is present', function(done) { driver.run(function*() { yield input.click(); yield input.type('mi'); yield input.type(wd.SPECIAL_KEYS['Right arrow']); expect(yield input.getValue()).to.equal('Michigan'); done(); }); }); }); describe('on suggestion click', function() { it('should select suggestion', function(done) { driver.run(function*() { var suggestions; yield input.click(); yield input.type('mi'); suggestions = yield dropdown.elementsByClassName('tt-suggestion'); yield suggestions[1].click(); expect(yield dropdown.isDisplayed()).to.equal(false); expect(yield input.getValue()).to.equal('Minnesota'); done(); }); }); }); describe('on enter', function() { it('should select if cursor is on suggestion', function(done) { driver.run(function*() { var suggestions; yield input.click(); yield input.type('mi'); suggestions = yield dropdown.elementsByClassName('tt-suggestion'); yield input.type(wd.SPECIAL_KEYS['Down arrow']); yield input.type(wd.SPECIAL_KEYS['Down arrow']); yield input.type(wd.SPECIAL_KEYS['Return']); expect(yield dropdown.isDisplayed()).to.equal(false); expect(yield input.getValue()).to.equal('Minnesota'); done(); }); }); }); }); typeahead.js-0.11.1/test/playground.html000066400000000000000000000203101251733230600202000ustar00rootroot00000000000000
typeahead.js-0.11.1/test/typeahead/000077500000000000000000000000001251733230600170765ustar00rootroot00000000000000typeahead.js-0.11.1/test/typeahead/dataset_spec.js000066400000000000000000000311041251733230600220720ustar00rootroot00000000000000describe('Dataset', function() { var www = WWW(), mockSuggestions, mockSuggestionsDisplayFn; mockSuggestions = [ { value: 'one', raw: { value: 'one' } }, { value: 'two', raw: { value: 'two' } }, { value: 'html', raw: { value: 'html' } } ]; mockSuggestionsDisplayFn = [ { display: '4' }, { display: '5' }, { display: '6' } ]; beforeEach(function() { this.dataset = new Dataset({ name: 'test', node: $('
'), source: this.source = jasmine.createSpy('source') }, www); }); it('should throw an error if source is missing', function() { expect(noSource).toThrow(); function noSource() { new Dataset({}, www); } }); it('should throw an error if the name is not a valid class name', function() { expect(fn).toThrow(); function fn() { var d = new Dataset({ name: 'a space', node: $('
'), source: $.noop }, www); } }); describe('#getRoot', function() { it('should return the root element', function() { var sel = 'div' + www.selectors.dataset + www.selectors.dataset + '-test'; expect(this.dataset.$el).toBe(sel); }); }); describe('#update', function() { it('should render suggestions', function() { this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('one'); expect(this.dataset.$el).toContainText('two'); expect(this.dataset.$el).toContainText('html'); }); it('should escape html chars from display value when using default template', function() { this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('html'); }); it('should respect limit option', function() { this.dataset.limit = 2; this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('one'); expect(this.dataset.$el).toContainText('two'); expect(this.dataset.$el).not.toContainText('three'); }); it('should allow custom display functions', function() { this.dataset = new Dataset({ name: 'test', node: $('
'), display: function(o) { return o.display; }, source: this.source = jasmine.createSpy('source') }, www); this.source.andCallFake(syncMockSuggestionsDisplayFn); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('4'); expect(this.dataset.$el).toContainText('5'); expect(this.dataset.$el).toContainText('6'); }); it('should ignore async invocations of sync', function() { this.source.andCallFake(asyncSync); this.dataset.update('woah'); expect(this.dataset.$el).not.toContainText('one'); }); it('should ignore subesequent invocations of sync', function() { this.source.andCallFake(multipleSync); this.dataset.update('woah'); expect(this.dataset.$el.find('.tt-suggestion')).toHaveLength(3); }); it('should trigger asyncRequested when needing/expecting backfill', function() { var spy = jasmine.createSpy(); this.dataset.async = true; this.dataset.onSync('asyncRequested', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); expect(spy).toHaveBeenCalled(); }); it('should not trigger asyncRequested when not expecting backfill', function() { var spy = jasmine.createSpy(); this.dataset.async = false; this.dataset.onSync('asyncRequested', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); expect(spy).not.toHaveBeenCalled(); }); it('should not trigger asyncRequested when not expecting backfill', function() { var spy = jasmine.createSpy(); this.dataset.limit = 2; this.dataset.async = true; this.dataset.onSync('asyncRequested', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); expect(spy).not.toHaveBeenCalled(); }); it('should trigger asyncCanceled when pending aysnc is canceled', function() { var spy = jasmine.createSpy(); this.dataset.async = true; this.dataset.onSync('asyncCanceled', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); this.dataset.cancel(); waits(100); runs(function() { expect(spy).toHaveBeenCalled(); }); }); it('should not trigger asyncCanceled when cancel happens after update', function() { var spy = jasmine.createSpy(); this.dataset.async = true; this.dataset.onSync('asyncCanceled', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); waits(100); runs(function() { this.dataset.cancel(); expect(spy).not.toHaveBeenCalled(); }); }); it('should trigger asyncReceived when aysnc is received', function() { var spy = jasmine.createSpy(); this.dataset.async = true; this.dataset.onSync('asyncReceived', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); waits(100); runs(function() { expect(spy).toHaveBeenCalled(); }); }); it('should not trigger asyncReceived if canceled', function() { var spy = jasmine.createSpy(); this.dataset.async = true; this.dataset.onSync('asyncReceived', spy); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); this.dataset.cancel(); waits(100); runs(function() { expect(spy).not.toHaveBeenCalled(); }); }); it('should not modify sync when async is added', function() { var $test; this.dataset.async = true; this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); $test = this.dataset.$el.find('.tt-suggestion').first(); $test.addClass('test'); waits(100); runs(function() { expect($test).toHaveClass('test'); }); }); it('should respect limit option in regard to async', function() { this.dataset.async = true; this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); waits(100); runs(function() { expect(this.dataset.$el.find('.tt-suggestion')).toHaveLength(5); }); }); it('should cancel pending async', function() { var spy1 = jasmine.createSpy(), spy2 = jasmine.createSpy(); this.dataset.async = true; this.dataset.onSync('asyncCanceled', spy1); this.dataset.onSync('asyncReceived', spy2); this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); this.dataset.update('woah again'); waits(100); runs(function() { expect(spy1.callCount).toBe(1); expect(spy2.callCount).toBe(1); }); }); it('should render notFound when no suggestions are available', function() { this.dataset = new Dataset({ source: this.source, node: $('
'), templates: { notFound: '

empty

' } }, www); this.source.andCallFake(syncEmptySuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('empty'); }); it('should render pending when no suggestions are available but async is pending', function() { this.dataset = new Dataset({ source: this.source, node: $('
'), async: true, templates: { pending: '

pending

' } }, www); this.source.andCallFake(syncEmptySuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('pending'); }); it('should render header when suggestions are rendered', function() { this.dataset = new Dataset({ source: this.source, node: $('
'), templates: { header: '

header

' } }, www); this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('header'); }); it('should render footer when suggestions are rendered', function() { this.dataset = new Dataset({ source: this.source, node: $('
'), templates: { footer: function(c) { return '

' + c.query + '

'; } } }, www); this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); expect(this.dataset.$el).toContainText('woah'); }); it('should not render header/footer if there is no content', function() { this.dataset = new Dataset({ source: this.source, node: $('
'), templates: { header: '

header

', footer: '

footer

' } }, www); this.source.andCallFake(syncEmptySuggestions); this.dataset.update('woah'); expect(this.dataset.$el).not.toContainText('header'); expect(this.dataset.$el).not.toContainText('footer'); }); it('should not render stale suggestions', function() { this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); this.source.andCallFake(syncMockSuggestions); this.dataset.update('nelly'); waits(100); runs(function() { expect(this.dataset.$el).toContainText('one'); expect(this.dataset.$el).toContainText('two'); expect(this.dataset.$el).toContainText('html'); expect(this.dataset.$el).not.toContainText('four'); expect(this.dataset.$el).not.toContainText('five'); }); }); it('should not render async suggestions if update was canceled', function() { this.source.andCallFake(fakeGetWithAsyncSuggestions); this.dataset.update('woah'); this.dataset.cancel(); waits(100); runs(function() { var rendered = this.dataset.$el.find('.tt-suggestion'); expect(rendered).toHaveLength(3); }); }); it('should trigger rendered after suggestions are rendered', function() { var spy; this.dataset.onSync('rendered', spy = jasmine.createSpy()); this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); waitsFor(function() { return spy.callCount; }); }); }); describe('#clear', function() { it('should clear suggestions', function() { this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); this.dataset.clear(); expect(this.dataset.$el).toBeEmpty(); }); it('should cancel pending updates', function() { var spy; this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); spy = spyOn(this.dataset, 'cancel'); this.dataset.clear(); expect(spy).toHaveBeenCalled(); }); it('should trigger cleared', function() { var spy; this.dataset.onSync('cleared', spy = jasmine.createSpy()); this.dataset.clear(); expect(spy).toHaveBeenCalled(); }); }); describe('#isEmpty', function() { it('should return true when empty', function() { expect(this.dataset.isEmpty()).toBe(true); }); it('should return false when not empty', function() { this.source.andCallFake(syncMockSuggestions); this.dataset.update('woah'); expect(this.dataset.isEmpty()).toBe(false); }); }); describe('#destroy', function() { it('should set dataset element to dummy element', function() { var $prevEl = this.dataset.$el; this.dataset.destroy(); expect(this.dataset.$el).not.toBe($prevEl); }); }); // helper functions // ---------------- function syncEmptySuggestions(q, sync, async) { sync([]); } function syncMockSuggestions(q, sync, async) { sync(mockSuggestions); } function syncMockSuggestionsDisplayFn(q, sync, async) { sync(mockSuggestionsDisplayFn); } function asyncSync(q, sync, async) { setTimeout(function() { sync(mockSuggestions); }, 0); } function multipleSync(q, sync, async) { sync(mockSuggestions); sync(mockSuggestions); } function fakeGetWithAsyncSuggestions(query, sync, async) { sync(mockSuggestions); setTimeout(function() { async([ { value: 'four', raw: { value: 'four' } }, { value: 'five', raw: { value: 'five' } }, { value: 'six', raw: { value: 'six' } }, { value: 'seven', raw: { value: 'seven' } }, { value: 'eight', raw: { value: 'eight' } }, ]); }, 0); } }); typeahead.js-0.11.1/test/typeahead/default_results_spec.js000066400000000000000000000050771251733230600236640ustar00rootroot00000000000000describe('DefaultMenu', function() { var www = WWW(); beforeEach(function() { var $fixture; jasmine.Dataset.useMock(); setFixtures(''); $fixture = $('#jasmine-fixtures'); this.$node = $fixture.find('#menu-fixture'); this.$node.html(fixtures.html.dataset); this.view = new DefaultMenu({ node: this.$node, datasets: [{}] }, www).bind(); this.dataset = this.view.datasets[0]; }); describe('when rendered is triggered on a dataset', function() { it('should hide menu if empty', function() { this.dataset.isEmpty.andReturn(true); this.view._show(); this.dataset.trigger('rendered'); expect(this.$node).not.toBeVisible(); }); it('should not show menu if not open', function() { this.dataset.isEmpty.andReturn(false); this.view._hide(); this.dataset.trigger('rendered'); expect(this.$node).not.toBeVisible(); }); it('should show menu if not empty and open', function() { this.dataset.isEmpty.andReturn(false); this.view._hide(); this.view.open(); this.dataset.trigger('rendered'); expect(this.$node).toBeVisible(); }); }); describe('when cleared is triggered on a dataset', function() { it('should hide menu if empty', function() { this.dataset.isEmpty.andReturn(true); this.view._show(); this.dataset.trigger('cleared'); expect(this.$node).not.toBeVisible(); }); it('should not show menu if not open', function() { this.dataset.isEmpty.andReturn(false); this.view._hide(); this.dataset.trigger('cleared'); expect(this.$node).not.toBeVisible(); }); it('should show menu if not empty and open', function() { this.dataset.isEmpty.andReturn(false); this.view._hide(); this.view.open(); this.dataset.trigger('cleared'); expect(this.$node).toBeVisible(); }); }); describe('#open', function() { it('should show menu if not empty', function() { spyOn(this.view, '_allDatasetsEmpty').andReturn(false); this.view.open(); expect(this.$node[0].getAttribute('style')).toMatch(/display: block/); }); it('should not show menu if empty', function() { spyOn(this.view, '_allDatasetsEmpty').andReturn(true); this.view.open(); expect(this.$node).not.toHaveAttr('style', 'display: block;'); }); }); describe('#close', function() { it('should hide menu', function() { this.view._show(); this.view.close(); expect(this.$node).not.toBeVisible(); }); }); }); typeahead.js-0.11.1/test/typeahead/event_bus_spec.js000066400000000000000000000020261251733230600224400ustar00rootroot00000000000000describe('EventBus', function() { beforeEach(function() { var $fixture; setFixtures(fixtures.html.input); $fixture = $('#jasmine-fixtures'); this.$el = $fixture.find('.tt-input'); this.eventBus = new EventBus({ el: this.$el }); }); it('#trigger should trigger event', function() { var spy = jasmine.createSpy(); this.$el.on('typeahead:fiz', spy); this.eventBus.trigger('fiz'); expect(spy).toHaveBeenCalled(); }); it('#before should return false if default was not prevented', function() { var spy = jasmine.createSpy(); this.$el.on('typeahead:beforefiz', spy); expect(this.eventBus.before('fiz')).toBe(false); expect(spy).toHaveBeenCalled(); }); it('#before should return true if default was prevented', function() { var spy = jasmine.createSpy().andCallFake(prevent); this.$el.on('typeahead:beforefiz', spy); expect(this.eventBus.before('fiz')).toBe(true); expect(spy).toHaveBeenCalled(); function prevent($e) { $e.preventDefault(); } }); }); typeahead.js-0.11.1/test/typeahead/event_emitter_spec.js000066400000000000000000000063531251733230600233270ustar00rootroot00000000000000describe('EventEmitter', function() { beforeEach(function() { this.spy = jasmine.createSpy(); this.target = _.mixin({}, EventEmitter); }); it('methods should be chainable', function() { expect(this.target.onSync()).toEqual(this.target); expect(this.target.onAsync()).toEqual(this.target); expect(this.target.off()).toEqual(this.target); expect(this.target.trigger()).toEqual(this.target); }); it('#on should take the context a callback should be called in', function() { var context = { val: 3 }, cbContext; this.target.onSync('xevent', setCbContext, context).trigger('xevent'); waitsFor(assertCbContext, 'callback was called in the wrong context'); function setCbContext() { cbContext = this; } function assertCbContext() { return cbContext === context; } }); it('#onAsync callbacks should be invoked asynchronously', function() { this.target.onAsync('event', this.spy).trigger('event'); expect(this.spy.callCount).toBe(0); waitsFor(assertCallCount(this.spy, 1), 'the callback was not invoked'); }); it('#onSync callbacks should be invoked synchronously', function() { this.target.onSync('event', this.spy).trigger('event'); expect(this.spy.callCount).toBe(1); }); it('#off should remove callbacks', function() { this.target .onSync('event1 event2', this.spy) .onAsync('event1 event2', this.spy) .off('event1 event2') .trigger('event1 event2'); waits(100); runs(assertCallCount(this.spy, 0)); }); it('methods should accept multiple event types', function() { this.target .onSync('event1 event2', this.spy) .onAsync('event1 event2', this.spy) .trigger('event1 event2'); expect(this.spy.callCount).toBe(2); waitsFor(assertCallCount(this.spy, 4), 'the callback was not invoked'); }); it('the event type should be passed to the callback', function() { this.target .onSync('sync', this.spy) .onAsync('async', this.spy) .trigger('sync async'); waitsFor(assertArgs(this.spy, 0, ['sync']), 'bad args'); waitsFor(assertArgs(this.spy, 1, ['async']), 'bad args'); }); it('arbitrary args should be passed to the callback', function() { this.target .onSync('event', this.spy) .onAsync('event', this.spy) .trigger('event', 1, 2); waitsFor(assertArgs(this.spy, 0, ['event', 1, 2]), 'bad args'); waitsFor(assertArgs(this.spy, 1, ['event', 1, 2]), 'bad args'); }); it('callback execution should be cancellable', function() { var cancelSpy = jasmine.createSpy().andCallFake(cancel); this.target .onSync('one', cancelSpy) .onSync('one', this.spy) .onAsync('two', cancelSpy) .onAsync('two', this.spy) .onSync('three', cancelSpy) .onAsync('three', this.spy) .trigger('one two three'); waitsFor(assertCallCount(cancelSpy, 3)); waitsFor(assertCallCount(this.spy, 0)); function cancel() { return false; } }); function assertCallCount(spy, expected) { return function() { return spy.callCount === expected; }; } function assertArgs(spy, call, expected) { return function() { var env = jasmine.getEnv(), actual = spy.calls[call] ? spy.calls[call].args : undefined; return env.equals_(actual, expected); }; } }); typeahead.js-0.11.1/test/typeahead/highlight_spec.js000066400000000000000000000071431251733230600224220ustar00rootroot00000000000000describe('highlight', function() { it('should allow tagName to be specified', function() { var before = 'abcde', after = 'abcde', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'bcd', tagName: 'span' }); expect(testNode.innerHTML).toEqual(after); }); it('should allow className to be specified', function() { var before = 'abcde', after = 'abcde', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'bcd', className: 'one two' }); expect(testNode.innerHTML).toEqual(after); }); it('should be case insensitive by default', function() { var before = 'ABCDE', after = 'ABCDE', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'bcd' }); expect(testNode.innerHTML).toEqual(after); }); it('should support case sensitivity', function() { var before = 'ABCDE', after = 'ABCDE', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'bcd', caseSensitive: true }); expect(testNode.innerHTML).toEqual(after); }); it('should support words only matching', function() { var before = 'tone one phone', after = 'tone one phone', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'one', wordsOnly: true }); expect(testNode.innerHTML).toEqual(after); }); it('should support matching multiple patterns', function() { var before = 'tone one phone', after = 'tone one phone', testNode = buildTestNode(before); highlight({ node: testNode, pattern: ['tone', 'phone'] }); expect(testNode.innerHTML).toEqual(after); }); it('should support regex chars in the pattern', function() { var before = '*.js when?', after = '*.js when?', testNode = buildTestNode(before); highlight({ node: testNode, pattern: ['*.', '?'] }); expect(testNode.innerHTML).toEqual(after); }); it('should work on complex html structures', function() { var before = [ '
abcde', 'abcde', '

abcde

', '
' ].join(''), after = [ '
abcde', 'abcde', '

abcde

', '
' ].join(''), testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'abc' }); expect(testNode.innerHTML).toEqual(after); }); it('should ignore html tags and attributes', function() { var before = '', after = '', testNode = buildTestNode(before); highlight({ node: testNode, pattern: ['span', 'class'] }); expect(testNode.innerHTML).toEqual(after); }); it('should not match across tags', function() { var before = 'abc', after = 'abc', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'abc' }); expect(testNode.innerHTML).toEqual(after); }); it('should ignore html comments', function() { var before = '', after = '', testNode = buildTestNode(before); highlight({ node: testNode, pattern: 'abc' }); expect(testNode.innerHTML).toEqual(after); }); function buildTestNode(content) { var node = document.createElement('div'); node.innerHTML = content; return node; } }); typeahead.js-0.11.1/test/typeahead/input_spec.js000066400000000000000000000353341251733230600216150ustar00rootroot00000000000000describe('Input', function() { var KEYS, www; KEYS = { enter: 13, esc: 27, tab: 9, left: 37, right: 39, up: 38, down: 40, normal: 65 // "A" key }; www = WWW(); beforeEach(function() { var $fixture; setFixtures(fixtures.html.input + fixtures.html.hint); $fixture = $('#jasmine-fixtures'); this.$input = $fixture.find('.tt-input'); this.$hint = $fixture.find('.tt-hint'); this.view = new Input({ input: this.$input, hint: this.$hint }, www).bind(); }); it('should throw an error if no input is provided', function() { expect(noInput).toThrow(); function noInput() { new Input({}, www); } }); describe('when the blur DOM event is triggered', function() { it('should reset the input value', function() { this.view.setQuery('wine'); this.view.setInputValue('cheese'); this.$input.blur(); expect(this.$input.val()).toBe('wine'); }); it('should trigger blurred', function() { var spy; this.view.onSync('blurred', spy = jasmine.createSpy()); this.$input.blur(); expect(spy).toHaveBeenCalled(); }); }); describe('when the focus DOM event is triggered', function() { it('should update queryWhenFocused', function() { this.view.setQuery('hi'); this.$input.focus(); expect(this.view.hasQueryChangedSinceLastFocus()).toBe(false); this.view.setQuery('bye'); expect(this.view.hasQueryChangedSinceLastFocus()).toBe(true); }); it('should trigger focused', function() { var spy; this.view.onSync('focused', spy = jasmine.createSpy()); this.$input.focus(); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by tab', function() { it('should trigger tabKeyed if no modifiers were pressed', function() { var spy; this.view.onSync('tabKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.tab); expect(spy).toHaveBeenCalled(); }); it('should not trigger tabKeyed if modifiers were pressed', function() { var spy; this.view.onSync('tabKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.tab, true); expect(spy).not.toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by esc', function() { it('should trigger escKeyed', function() { var spy; this.view.onSync('escKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.esc); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by left', function() { it('should trigger leftKeyed', function() { var spy; this.view.onSync('leftKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.left); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by right', function() { it('should trigger rightKeyed', function() { var spy; this.view.onSync('rightKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.right); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by enter', function() { it('should trigger enterKeyed', function() { var spy; this.view.onSync('enterKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.enter); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by up', function() { it('should trigger upKeyed', function() { var spy; this.view.onSync('upKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.up); expect(spy).toHaveBeenCalled(); }); it('should prevent default if no modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.up); expect($e.preventDefault).toHaveBeenCalled(); }); it('should not prevent default if modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.up, true); expect($e.preventDefault).not.toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by down', function() { it('should trigger downKeyed', function() { var spy; this.view.onSync('downKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.down); expect(spy).toHaveBeenCalled(); }); it('should prevent default if no modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.down); expect($e.preventDefault).toHaveBeenCalled(); }); it('should not prevent default if modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.down, true); expect($e.preventDefault).not.toHaveBeenCalled(); }); }); // NOTE: have to treat these as async because the ie polyfill acts // in a async manner describe('when the input DOM event is triggered', function() { it('should update query', function() { this.view.setQuery('wine'); this.view.setInputValue('cheese'); simulateInputEvent(this.$input); waitsFor(function() { return this.view.getQuery() === 'cheese'; }); }); it('should trigger queryChanged if the query changed', function() { var spy; this.view.setQuery('wine'); this.view.setInputValue('cheese'); this.view.onSync('queryChanged', spy = jasmine.createSpy()); simulateInputEvent(this.$input); expect(spy).toHaveBeenCalled(); }); it('should trigger whitespaceChanged if whitespace changed', function() { var spy; this.view.setQuery('wine bar'); this.view.setInputValue('wine bar'); this.view.onSync('whitespaceChanged', spy = jasmine.createSpy()); simulateInputEvent(this.$input); expect(spy).toHaveBeenCalled(); }); it('should clear hint if invalid', function() { spyOn(this.view, 'clearHintIfInvalid'); simulateInputEvent(this.$input); expect(this.view.clearHintIfInvalid).toHaveBeenCalled(); }); it('should check lang direction', function() { var spy; this.$input.css('direction', 'rtl'); this.view.onSync('langDirChanged', spy = jasmine.createSpy()); simulateInputEvent(this.$input); expect(this.view.dir).toBe('rtl'); expect(this.$hint).toHaveAttr('dir', 'rtl'); expect(spy).toHaveBeenCalled(); }); }); describe('.normalizeQuery', function() { it('should strip leading whitespace', function() { expect(Input.normalizeQuery(' foo')).toBe('foo'); }); it('should condense whitespace', function() { expect(Input.normalizeQuery('foo bar')).toBe('foo bar'); }); it('should play nice with non-string values', function() { expect(Input.normalizeQuery(2)).toBe('2'); expect(Input.normalizeQuery([])).toBe(''); expect(Input.normalizeQuery(null)).toBe(''); expect(Input.normalizeQuery(undefined)).toBe(''); expect(Input.normalizeQuery(false)).toBe('false'); }); }); describe('#focus', function() { it('should focus the input', function() { this.$input.blur(); this.view.focus(); expect(this.$input).toBeFocused(); }); }); describe('#blur', function() { it('should blur the input', function() { this.$input.focus(); this.view.blur(); expect(this.$input).not.toBeFocused(); }); }); describe('#getQuery', function() { it('should act as getter to the query property', function() { this.view.setQuery('mouse'); expect(this.view.getQuery()).toBe('mouse'); }); }); describe('#setQuery', function() { it('should act as setter to the query property', function() { this.view.setQuery('mouse'); expect(this.view.getQuery()).toBe('mouse'); }); it('should update input value', function() { this.view.setQuery('mouse'); expect(this.view.getInputValue()).toBe('mouse'); }); it('should trigger queryChanged if the query changed', function() { var spy; this.view.setQuery('wine'); this.view.onSync('queryChanged', spy = jasmine.createSpy()); this.view.setQuery('cheese'); expect(spy).toHaveBeenCalled(); }); it('should trigger whitespaceChanged if whitespace changed', function() { var spy; this.view.setQuery('wine bar'); this.view.onSync('whitespaceChanged', spy = jasmine.createSpy()); this.view.setQuery('wine bar'); expect(spy).toHaveBeenCalled(); }); it('should clear hint if invalid', function() { spyOn(this.view, 'clearHintIfInvalid'); simulateInputEvent(this.$input); expect(this.view.clearHintIfInvalid).toHaveBeenCalled(); }); }); describe('#hasQueryChangedSinceLastFocus', function() { it('should return true if the query has changed since focus', function() { this.view.setQuery('hi'); this.$input.focus(); this.view.setQuery('bye'); expect(this.view.hasQueryChangedSinceLastFocus()).toBe(true); }); it('should return false if the query has not changed since focus', function() { this.view.setQuery('hi'); this.$input.focus(); expect(this.view.hasQueryChangedSinceLastFocus()).toBe(false); }); }); describe('#getInputValue', function() { it('should act as getter to the input value', function() { this.$input.val('cheese'); expect(this.view.getInputValue()).toBe('cheese'); }); }); describe('#setInputValue', function() { it('should act as setter to the input value', function() { this.view.setInputValue('cheese'); expect(this.view.getInputValue()).toBe('cheese'); }); it('should clear hint if invalid', function() { spyOn(this.view, 'clearHintIfInvalid'); this.view.setInputValue('cheese head'); expect(this.view.clearHintIfInvalid).toHaveBeenCalled(); }); it('should check lang direction', function() { var spy; this.$input.css('direction', 'rtl'); this.view.onSync('langDirChanged', spy = jasmine.createSpy()); simulateInputEvent(this.$input); expect(this.view.dir).toBe('rtl'); expect(this.$hint).toHaveAttr('dir', 'rtl'); expect(spy).toHaveBeenCalled(); }); }); describe('#getHint/#setHint', function() { it('should act as getter/setter to value of hint', function() { this.view.setHint('mountain'); expect(this.view.getHint()).toBe('mountain'); }); }); describe('#resetInputValue', function() { it('should reset input value to last query', function() { this.view.setQuery('cheese'); this.view.setInputValue('wine'); this.view.resetInputValue(); expect(this.view.getInputValue()).toBe('cheese'); }); }); describe('#clearHint', function() { it('should set the hint value to the empty string', function() { this.view.setHint('cheese'); this.view.clearHint(); expect(this.view.getHint()).toBe(''); }); }); describe('#clearHintIfInvalid', function() { it('should clear hint if input value is empty string', function() { this.view.setInputValue(''); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe(''); }); it('should clear hint if input value is not prefix of input', function() { this.view.setInputValue('milk'); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe(''); }); it('should clear hint if overflow exists', function() { spyOn(this.view, 'hasOverflow').andReturn(true); this.view.setInputValue('che'); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe(''); }); it('should not clear hint if input value is prefix of input', function() { this.view.setInputValue('che'); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe('cheese'); }); }); describe('#hasOverflow', function() { it('should return true if the input has overflow text', function() { var longStr = new Array(1000).join('a'); this.view.setInputValue(longStr); expect(this.view.hasOverflow()).toBe(true); }); it('should return false if the input has no overflow text', function() { var shortStr = 'aah'; this.view.setInputValue(shortStr); expect(this.view.hasOverflow()).toBe(false); }); }); describe('#isCursorAtEnd', function() { it('should return true if the text cursor is at the end', function() { this.view.setInputValue('boo'); setCursorPosition(this.$input, 3); expect(this.view.isCursorAtEnd()).toBe(true); }); it('should return false if the text cursor is not at the end', function() { this.view.setInputValue('boo'); setCursorPosition(this.$input, 1); expect(this.view.isCursorAtEnd()).toBe(false); }); }); describe('#destroy', function() { it('should remove event handlers', function() { var $input, $hint; $hint = this.view.$hint; $input = this.view.$input; spyOn($hint, 'off'); spyOn($input, 'off'); this.view.destroy(); expect($hint.off).toHaveBeenCalledWith('.tt'); expect($input.off).toHaveBeenCalledWith('.tt'); }); it('should set references to DOM elements to dummy element', function() { var $hint, $input, $overflowHelper; $hint = this.view.$hint; $input = this.view.$input; $overflowHelper = this.view.$overflowHelper; this.view.destroy(); expect(this.view.$hint).not.toBe($hint); expect(this.view.$input).not.toBe($input); expect(this.view.$overflowHelper).not.toBe($overflowHelper); }); }); // helper functions // ---------------- function simulateInputEvent($node) { var $e, type; type = _.isMsie() ? 'keypress' : 'input'; $e = $.Event(type); $node.trigger($e); } function simulateKeyEvent($node, type, key, withModifier) { var $e; $e = $.Event(type, { keyCode: key, altKey: !!withModifier, ctrlKey: !!withModifier, metaKey: !!withModifier, shiftKey: !!withModifier }); spyOn($e, 'preventDefault'); $node.trigger($e); return $e; } function setCursorPosition($input, pos) { var input = $input[0], range; if (input.setSelectionRange) { input.focus(); input.setSelectionRange(pos, pos); } else if (input.createTextRange) { range = input.createTextRange(); range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } } }); typeahead.js-0.11.1/test/typeahead/plugin_spec.js000066400000000000000000000137061251733230600217530ustar00rootroot00000000000000describe('$plugin', function() { beforeEach(function() { var $fixture; setFixtures(''); $fixture = $('#jasmine-fixtures'); this.$input = $fixture.find('.test-input'); this.$input.typeahead(null, { displayKey: 'v', source: function(q, sync) { sync([{ v: '1' }, { v: '2' }, { v: '3' }]); } }); }); it('#enable should enable the typaahead', function() { this.$input.typeahead('disable'); expect(this.$input.typeahead('isEnabled')).toBe(false); this.$input.typeahead('enable'); expect(this.$input.typeahead('isEnabled')).toBe(true); }); it('#disable should disable the typaahead', function() { this.$input.typeahead('enable'); expect(this.$input.typeahead('isEnabled')).toBe(true); this.$input.typeahead('disable'); expect(this.$input.typeahead('isEnabled')).toBe(false); }); it('#activate should activate the typaahead', function() { this.$input.typeahead('deactivate'); expect(this.$input.typeahead('isActive')).toBe(false); this.$input.typeahead('activate'); expect(this.$input.typeahead('isActive')).toBe(true); }); it('#activate should fail to activate the typaahead if disabled', function() { this.$input.typeahead('deactivate'); expect(this.$input.typeahead('isActive')).toBe(false); this.$input.typeahead('disable'); this.$input.typeahead('activate'); expect(this.$input.typeahead('isActive')).toBe(false); }); it('#deactivate should deactivate the typaahead', function() { this.$input.typeahead('activate'); expect(this.$input.typeahead('isActive')).toBe(true); this.$input.typeahead('deactivate'); expect(this.$input.typeahead('isActive')).toBe(false); }); it('#open should open the menu', function() { this.$input.typeahead('close'); expect(this.$input.typeahead('isOpen')).toBe(false); this.$input.typeahead('open'); expect(this.$input.typeahead('isOpen')).toBe(true); }); it('#close should close the menu', function() { this.$input.typeahead('open'); expect(this.$input.typeahead('isOpen')).toBe(true); this.$input.typeahead('close'); expect(this.$input.typeahead('isOpen')).toBe(false); }); it('#select should select selectable', function() { var $el; // activate and set val to render some selectables this.$input.typeahead('activate'); this.$input.typeahead('val', 'o'); $el = $('.tt-selectable').first(); expect(this.$input.typeahead('select', $el)).toBe(true); expect(this.$input.typeahead('val')).toBe('1'); }); it('#select should return false if not valid selectable', function() { var body; // activate and set val to render some selectables this.$input.typeahead('activate'); this.$input.typeahead('val', 'o'); body = document.body; expect(this.$input.typeahead('select', body)).toBe(false); }); it('#autocomplete should autocomplete to selectable', function() { var $el; // activate and set val to render some selectables this.$input.typeahead('activate'); this.$input.typeahead('val', 'o'); $el = $('.tt-selectable').first(); expect(this.$input.typeahead('autocomplete', $el)).toBe(true); expect(this.$input.typeahead('val')).toBe('1'); }); it('#autocomplete should return false if not valid selectable', function() { var body; // activate and set val to render some selectables this.$input.typeahead('activate'); this.$input.typeahead('val', 'o'); body = document.body; expect(this.$input.typeahead('autocomplete', body)).toBe(false); }); it('#moveCursor should move cursor', function() { var $el; // activate and set val to render some selectables this.$input.typeahead('activate'); this.$input.typeahead('val', 'o'); $el = $('.tt-selectable').first(); expect($el).not.toHaveClass('tt-cursor'); expect(this.$input.typeahead('moveCursor', 1)).toBe(true); expect($el).toHaveClass('tt-cursor'); }); it('#select should return false if not valid selectable', function() { var body; // activate and set val to render some selectables this.$input.typeahead('activate'); this.$input.typeahead('val', 'o'); body = document.body; expect(this.$input.typeahead('select', body)).toBe(false); }); it('#val() should typeahead value of element', function() { var $els; this.$input.typeahead('val', 'foo'); $els = this.$input.add('
'); expect($els.typeahead('val')).toBe('foo'); }); it('#val(q) should set query', function() { this.$input.typeahead('val', 'foo'); expect(this.$input.typeahead('val')).toBe('foo'); }); it('#destroy should revert modified attributes', function() { expect(this.$input).toHaveAttr('autocomplete', 'off'); expect(this.$input).toHaveAttr('dir'); expect(this.$input).toHaveAttr('spellcheck'); expect(this.$input).toHaveAttr('style'); this.$input.typeahead('destroy'); expect(this.$input).toHaveAttr('autocomplete', 'on'); expect(this.$input).not.toHaveAttr('dir'); expect(this.$input).not.toHaveAttr('spellcheck'); expect(this.$input).not.toHaveAttr('style'); }); it('#destroy should remove data', function() { expect(this.$input.data('tt-www')).toBeTruthy(); expect(this.$input.data('tt-attrs')).toBeTruthy(); expect(this.$input.data('tt-typeahead')).toBeTruthy(); this.$input.typeahead('destroy'); expect(this.$input.data('tt-www')).toBeFalsy(); expect(this.$input.data('tt-attrs')).toBeFalsy(); expect(this.$input.data('tt-typeahead')).toBeFalsy(); }); it('#destroy should remove add classes', function() { expect(this.$input).toHaveClass('tt-input'); this.$input.typeahead('destroy'); expect(this.$input).not.toHaveClass('tt-input'); }); it('#destroy should revert DOM changes', function() { expect($('.twitter-typeahead')).toExist(); this.$input.typeahead('destroy'); expect($('.twitter-typeahead')).not.toExist(); }); }); typeahead.js-0.11.1/test/typeahead/results_spec.js000066400000000000000000000221711251733230600221520ustar00rootroot00000000000000describe('Menu', function() { var www = WWW(); beforeEach(function() { var $fixture; jasmine.Dataset.useMock(); setFixtures(''); $fixture = $('#jasmine-fixtures'); this.$node = $fixture.find('#menu-fixture'); this.$node.html(fixtures.html.dataset); this.view = new Menu({ node: this.$node, datasets: [{}] }, www).bind(); this.dataset = this.view.datasets[0]; }); it('should throw an error if node is missing', function() { expect(noNode).toThrow(); function noNode() { new Menu({ datasets: [{}] }, www); } }); describe('when click event is triggered on a selectable', function() { it('should trigger selectableClicked', function() { var spy; this.view.onSync('selectableClicked', spy = jasmine.createSpy()); this.$node.find(www.selectors.selectable).first().click(); expect(spy).toHaveBeenCalled(); }); }); describe('when rendered is triggered on a dataset', function() { it('should add empty class to node if empty', function() { this.dataset.isEmpty.andReturn(true); this.$node.removeClass(www.classes.empty); this.dataset.trigger('rendered'); expect(this.$node).toHaveClass(www.classes.empty); }); it('should remove empty class from node if not empty', function() { this.dataset.isEmpty.andReturn(false); this.$node.addClass(www.classes.empty); this.dataset.trigger('rendered'); expect(this.$node).not.toHaveClass(www.classes.empty); }); it('should trigger datasetRendered', function() { var spy; this.view.onSync('datasetRendered', spy = jasmine.createSpy()); this.dataset.trigger('rendered'); expect(spy).toHaveBeenCalled(); }); }); describe('when cleared is triggered on a dataset', function() { it('should add empty class to node if empty', function() { this.dataset.isEmpty.andReturn(true); this.$node.removeClass(www.classes.empty); this.dataset.trigger('cleared'); expect(this.$node).toHaveClass(www.classes.empty); }); it('should remove empty class from node if not empty', function() { this.dataset.isEmpty.andReturn(false); this.$node.addClass(www.classes.empty); this.dataset.trigger('cleared'); expect(this.$node).not.toHaveClass(www.classes.empty); }); it('should trigger datasetCleared', function() { var spy; this.view.onSync('datasetCleared', spy = jasmine.createSpy()); this.dataset.trigger('cleared'); expect(spy).toHaveBeenCalled(); }); }); describe('when asyncRequested is triggered on a dataset', function() { it('should propagate event', function() { var spy = jasmine.createSpy(); this.dataset.onSync('asyncRequested', spy); this.dataset.trigger('asyncRequested'); expect(spy).toHaveBeenCalled(); }); }); describe('when asyncCanceled is triggered on a dataset', function() { it('should propagate event', function() { var spy = jasmine.createSpy(); this.dataset.onSync('asyncCanceled', spy); this.dataset.trigger('asyncCanceled'); expect(spy).toHaveBeenCalled(); }); }); describe('when asyncReceieved is triggered on a dataset', function() { it('should propagate event', function() { var spy = jasmine.createSpy(); this.dataset.onSync('asyncReceived', spy); this.dataset.trigger('asyncReceived'); expect(spy).toHaveBeenCalled(); }); }); describe('#open', function() { it('should add open class to node', function() { this.$node.removeClass(www.classes.open); this.view.open(); expect(this.$node).toHaveClass(www.classes.open); }); }); describe('#close', function() { it('should remove open class to node', function() { this.$node.addClass(www.classes.open); this.view.close(); expect(this.$node).not.toHaveClass(www.classes.open); }); it('should remove cursor', function() { var $selectable; $selectable = this.view._getSelectables().first(); this.view.setCursor($selectable); expect($selectable).toHaveClass(www.classes.cursor); this.view.close(); expect($selectable).not.toHaveClass(www.classes.cursor); }); }); describe('#setLanguageDirection', function() { it('should update css for given language direction', function() { this.view.setLanguageDirection('rtl'); expect(this.$node).toHaveAttr('dir', 'rtl'); this.view.setLanguageDirection('ltr'); expect(this.$node).toHaveAttr('dir', 'ltr'); }); }); describe('#selectableRelativeToCursor', function() { it('should return selectable delta spots away from cursor', function() { var $first, $second; $first = this.view._getSelectables().eq(0); $second = this.view._getSelectables().eq(1); this.view.setCursor($first); expect(this.view.selectableRelativeToCursor(+1)).toBe($second); }); it('should support negative deltas', function() { var $first, $second; $first = this.view._getSelectables().eq(0); $second = this.view._getSelectables().eq(1); this.view.setCursor($second); expect(this.view.selectableRelativeToCursor(-1)).toBe($first); }); it('should wrap', function() { var $expected, $actual; $expected = this.view._getSelectables().eq(-1); $actual = this.view.selectableRelativeToCursor(-1); expect($actual).toBe($expected); }); it('should return null if delta lands on input', function() { var $first; $first = this.view._getSelectables().eq(0); this.view.setCursor($first); expect(this.view.selectableRelativeToCursor(-1)).toBeNull(); }); }); describe('#setCursor', function() { it('should remove cursor if null is passed in', function() { var $selectable; $selectable = this.view._getSelectables().eq(0); this.view.setCursor($selectable); expect(this.view.getActiveSelectable()).toBe($selectable); this.view.setCursor(null); expect(this.view.getActiveSelectable()).toBeNull(); }); it('should move cursor to passed in selectable', function() { var $selectable; $selectable = this.view._getSelectables().eq(0); expect(this.view.getActiveSelectable()).toBeNull(); this.view.setCursor($selectable); expect(this.view.getActiveSelectable()).toBe($selectable); }); }); describe('#getSelectableData', function() { it('should extract the data from the selectable element', function() { var $selectable, datum; $selectable = $('
').data({ 'tt-selectable-display': 'one', 'tt-selectable-object': 'two' }); data = this.view.getSelectableData($selectable); expect(data).toEqual({ val: 'one', obj: 'two' }); }); it('should return null if no element is given', function() { expect(this.view.getSelectableData($('notreal'))).toBeNull(); }); }); describe('#getActiveSelectable', function() { it('should return the selectable the cursor is on', function() { var $first; $first = this.view._getSelectables().eq(0); this.view.setCursor($first); expect(this.view.getActiveSelectable()).toBe($first); }); it('should return null if the cursor is off', function() { expect(this.view.getActiveSelectable()).toBeNull(); }); }); describe('#getTopSelectable', function() { it('should return the selectable at the top of the menu', function() { var $first; $first = this.view._getSelectables().eq(0); expect(this.view.getTopSelectable()).toBe($first); }); }); describe('#update', function() { it('should invoke update on each dataset if valid update', function() { this.view.update('fiz'); expect(this.dataset.update).toHaveBeenCalled(); }); it('should return true when valid update', function() { expect(this.view.update('fiz')).toBe(true); }); it('should return false when invalid update', function() { this.view.update('fiz'); expect(this.view.update('fiz')).toBe(false); }); }); describe('#empty', function() { it('should set query to null', function() { this.view.query = 'fiz'; this.view.empty(); expect(this.view.query).toBeNull(); }); it('should add empty class to node', function() { this.$node.removeClass(www.classes.empty); this.view.empty(); expect(this.$node).toHaveClass(www.classes.empty); }); it('should invoke clear on each dataset', function() { this.view.empty(); expect(this.dataset.clear).toHaveBeenCalled(); }); }); describe('#destroy', function() { it('should remove event handlers', function() { var $node = this.view.$node; spyOn($node, 'off'); this.view.destroy(); expect($node.off).toHaveBeenCalledWith('.tt'); }); it('should destroy its datasets', function() { this.view.destroy(); expect(this.dataset.destroy).toHaveBeenCalled(); }); it('should set node element to dummy element', function() { var $node = this.view.$node; this.view.destroy(); expect(this.view.$node).not.toBe($node); }); }); }); typeahead.js-0.11.1/test/typeahead/typeahead_spec.js000066400000000000000000001126411251733230600224170ustar00rootroot00000000000000describe('Typeahead', function() { var www, testData; www = WWW(); beforeEach(function() { var $fixture, $input; jasmine.Input.useMock(); jasmine.Dataset.useMock(); jasmine.Menu.useMock(); setFixtures(''); $fixture = $('#jasmine-fixtures'); this.$input = $fixture.find('input'); testData = { val: 'foo bar', obj: 'fiz' }; this.view = new Typeahead({ input: new Input(), menu: new Menu(), eventBus: new EventBus({ el: this.$input }) }, www); this.input = this.view.input; this.menu = this.view.menu; }); describe('on selectableClicked', function() { var eventName, payload; beforeEach(function() { eventName = 'selectableClicked'; payload = $(''); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onSelectableClicked'); this.menu.trigger(eventName, payload); expect(this.view._onSelectableClicked).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should select the selectable', function() { spyOn(this.view, 'select'); this.menu.trigger(eventName, payload); expect(this.view.select).toHaveBeenCalledWith(payload); }); }); }); describe('on asyncRequested', function() { var eventName; beforeEach(function() { eventName = 'asyncRequested'; }); it('should trigger typeahead:asyncrequest', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:asyncrequest', spy); this.menu.trigger(eventName); expect(spy).toHaveBeenCalled(); }); }); describe('on asyncCanceled', function() { var eventName; beforeEach(function() { eventName = 'asyncCanceled'; }); it('should trigger typeahead:asynccancel', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:asynccancel', spy); this.menu.trigger(eventName); expect(spy).toHaveBeenCalled(); }); }); describe('on asyncReceived', function() { var eventName; beforeEach(function() { eventName = 'asyncReceived'; }); it('should trigger typeahead:asyncreceive', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:asyncreceive', spy); this.menu.trigger(eventName); expect(spy).toHaveBeenCalled(); }); }); describe('on datasetRendered', function() { var eventName; beforeEach(function() { eventName = 'datasetRendered'; }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onDatasetRendered'); this.menu.trigger(eventName); expect(this.view._onDatasetRendered).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should update the hint', function() { this.input.hasOverflow.andReturn(false); this.menu.getTopSelectable.andReturn($('')); this.menu.getSelectableData.andReturn(testData); this.input.getInputValue.andReturn(testData.val.slice(0, 2)); this.menu.trigger(eventName); expect(this.input.setHint).toHaveBeenCalled(); }); it('should trigger typeahead:render', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:render', spy); this.menu.trigger(eventName); expect(spy).toHaveBeenCalled(); }); }); }); describe('on datasetCleared', function() { var eventName; beforeEach(function() { eventName = 'datasetCleared'; }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onDatasetCleared'); this.menu.trigger(eventName); expect(this.view._onDatasetCleared).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should update the hint', function() { this.input.hasOverflow.andReturn(false); this.menu.getTopSelectable.andReturn($('')); this.menu.getSelectableData.andReturn(testData); this.input.getInputValue.andReturn(testData.val.slice(0, 2)); this.menu.trigger(eventName); expect(this.input.setHint).toHaveBeenCalled(); }); }); }); describe('on focused', function() { var eventName; beforeEach(function() { eventName = 'focused'; }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should activate typeahead', function() { this.input.trigger(eventName); expect(this.view.isActive()).toBe(true); }); it('should open menu', function() { this.input.trigger(eventName); expect(this.menu.open).toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should open menu', function() { this.input.trigger(eventName); expect(this.menu.open).toHaveBeenCalled(); }); it('should update menu for query if minLength met', function() { this.input.getQuery.andReturn('bar'); this.input.trigger(eventName); expect(this.menu.update).toHaveBeenCalledWith('bar'); }); it('should not update menu for query if minLength not met', function() { this.view.minLength = 1; this.input.getQuery.andReturn(''); this.input.trigger(eventName); expect(this.menu.update).not.toHaveBeenCalled(); }); }); }); describe('on blurred', function() { var eventName; beforeEach(function() { eventName = 'blurred'; }); it('should trigger typeahead:change if query changed since focus', function() { var spy = jasmine.createSpy(); this.input.hasQueryChangedSinceLastFocus.andReturn(true); this.$input.on('typeahead:change', spy); this.input.trigger(eventName); expect(spy).toHaveBeenCalled(); }); it('should not trigger typeahead:change if query has not changed since focus', function() { var spy = jasmine.createSpy(); this.input.hasQueryChangedSinceLastFocus.andReturn(false); this.$input.on('typeahead:change', spy); this.input.trigger(eventName); expect(spy).not.toHaveBeenCalled(); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should deactivate typeahead', function() { this.input.trigger(eventName); expect(this.view.isActive()).toBe(false); }); }); }); describe('on enterKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'enterKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onEnterKeyed'); this.input.trigger(eventName, payload); expect(this.view._onEnterKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is closed', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(false); }); it('should do nothing', function() { spyOn(this.view, '_onEnterKeyed'); this.input.trigger(eventName, payload); expect(this.view._onEnterKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is open', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(true); }); it('should select selectable if there is an active one', function() { var $el; $el = $(''); spyOn(this.view, 'select'); this.menu.getActiveSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(this.view.select).toHaveBeenCalledWith($el); }); it('should prevent default if active selectale ', function() { var $el; $el = $(''); spyOn(this.view, 'select').andReturn(true); this.menu.getActiveSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(payload.preventDefault).toHaveBeenCalled(); }); it('should not select selectable if there is no active one', function() { var $el; $el = $(''); spyOn(this.view, 'select'); this.input.trigger(eventName, payload); expect(this.view.select).not.toHaveBeenCalledWith($el); }); it('should not prevent default if no active selectale', function() { var $el; spyOn(this.view, 'select').andReturn(true); $el = $(''); this.input.trigger(eventName, payload); expect(payload.preventDefault).not.toHaveBeenCalled(); }); it('should not prevent default if selection of active selectable fails', function() { var $el; $el = $(''); spyOn(this.view, 'select').andReturn(false); this.menu.getActiveSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(payload.preventDefault).not.toHaveBeenCalled(); }); }); }); describe('on tabKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'tabKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onTabKeyed'); this.input.trigger(eventName, payload); expect(this.view._onTabKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is closed', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(false); }); it('should do nothing', function() { spyOn(this.view, '_onTabKeyed'); this.input.trigger(eventName, payload); expect(this.view._onTabKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is open', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(true); }); it('should select selectable if there is an active one', function() { var $el; $el = $(''); spyOn(this.view, 'select'); this.menu.getActiveSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(this.view.select).toHaveBeenCalledWith($el); }); it('should prevent default if active selectale', function() { var $el; $el = $(''); spyOn(this.view, 'select').andReturn(true); this.menu.getActiveSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(payload.preventDefault).toHaveBeenCalled(); }); it('should not select selectable if there is no active one', function() { var $el; $el = $(''); spyOn(this.view, 'select'); this.input.trigger(eventName, payload); expect(this.view.select).not.toHaveBeenCalledWith($el); }); it('should not prevent default if no active selectale', function() { var $el; $el = $(''); spyOn(this.view, 'select'); this.input.trigger(eventName, payload); expect(payload.preventDefault).not.toHaveBeenCalled(); }); it('should not prevent default if selection of active selectable fails', function() { var $el; $el = $(''); spyOn(this.view, 'select').andReturn(false); this.menu.getActiveSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(payload.preventDefault).not.toHaveBeenCalled(); }); it('should autocomplete to top suggestion', function() { var $el; $el = $(''); spyOn(this.view, 'autocomplete'); this.menu.getTopSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(this.view.autocomplete).toHaveBeenCalledWith($el); }); it('should prevent default behavior of DOM event if autocompletion succeeds', function() { var $el; $el = $(''); spyOn(this.view, 'autocomplete').andReturn(true); this.menu.getTopSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(payload.preventDefault).toHaveBeenCalled(); }); it('should not prevent default behavior of DOM event if autocompletion fails', function() { var $el; $el = $(''); spyOn(this.view, 'autocomplete').andReturn(false); this.menu.getTopSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(payload.preventDefault).not.toHaveBeenCalled(); }); }); }); describe('on escKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'escKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onEscKeyed'); this.input.trigger(eventName, payload); expect(this.view._onEscKeyed).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should close', function() { spyOn(this.view, 'close'); this.input.trigger(eventName, payload); expect(this.view.close).toHaveBeenCalled(); }); }); }); describe('on upKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'upKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onUpKeyed'); this.input.trigger(eventName, payload); expect(this.view._onUpKeyed).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); spyOn(this.view, 'moveCursor'); }); it('should open menu', function() { this.input.trigger(eventName, payload); expect(this.menu.open).toHaveBeenCalled(); }); it('should move cursor -1', function() { this.input.trigger(eventName, payload); expect(this.view.moveCursor).toHaveBeenCalledWith(-1); }); }); }); describe('on downKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'downKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onDownKeyed'); this.input.trigger(eventName, payload); expect(this.view._onDownKeyed).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); spyOn(this.view, 'moveCursor'); }); it('should open menu', function() { this.input.trigger(eventName, payload); expect(this.menu.open).toHaveBeenCalled(); }); it('should move cursor +1', function() { this.input.trigger(eventName, payload); expect(this.view.moveCursor).toHaveBeenCalledWith(1); }); }); }); describe('on leftKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'leftKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onLeftKeyed'); this.input.trigger(eventName, payload); expect(this.view._onLeftKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is closed', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(false); }); it('should do nothing', function() { spyOn(this.view, '_onLeftKeyed'); this.input.trigger(eventName, payload); expect(this.view._onLeftKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is open', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(true); }); it('should autocomplete if language is rtl and text cursor is at end', function() { var $el = $(''); spyOn(this.view, 'autocomplete'); this.view.dir = 'rtl'; this.input.isCursorAtEnd.andReturn(true); this.menu.getTopSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(this.view.autocomplete).toHaveBeenCalledWith($el); }); }); }); describe('on rightKeyed', function() { var eventName, payload; beforeEach(function() { eventName = 'rightKeyed'; payload = jasmine.createSpyObj('event', ['preventDefault']); }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { spyOn(this.view, '_onRightKeyed'); this.input.trigger(eventName, payload); expect(this.view._onRightKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is closed', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(false); }); it('should do nothing', function() { spyOn(this.view, '_onRightKeyed'); this.input.trigger(eventName, payload); expect(this.view._onRightKeyed).not.toHaveBeenCalled(); }); }); describe('when active and menu is open', function() { beforeEach(function() { this.view.activate(); this.menu.isOpen.andReturn(true); }); it('should autocomplete if language is rtl and text cursor is at end', function() { var $el = $(''); spyOn(this.view, 'autocomplete'); this.view.dir = 'ltr'; this.input.isCursorAtEnd.andReturn(true); this.menu.getTopSelectable.andReturn($el); this.input.trigger(eventName, payload); expect(this.view.autocomplete).toHaveBeenCalledWith($el); }); }); }); describe('on queryChanged', function() { var eventName, payload; beforeEach(function() { eventName = 'queryChanged'; payload = ''; }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should not open menu', function() { this.input.trigger(eventName, payload); expect(this.menu.open).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); this.view.open(); }); it('should open menu', function() { this.input.trigger(eventName, payload); expect(this.menu.open).toHaveBeenCalled(); }); it('should empty menu if minLength is not satisfied', function() { this.view.minLength = 100; this.input.trigger(eventName, payload); expect(this.menu.empty).toHaveBeenCalled(); }); it('should update menu if minLength is satisfied', function() { this.input.trigger(eventName, 'fiz'); expect(this.menu.update).toHaveBeenCalledWith('fiz'); }); }); }); describe('on whitespaceChanged', function() { var eventName, payload; beforeEach(function() { eventName = 'whitespaceChanged'; payload = ''; }); describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should not open menu', function() { this.input.trigger(eventName, payload); expect(this.menu.open).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should open menu', function() { this.input.trigger(eventName, payload); expect(this.menu.open).toHaveBeenCalled(); }); it('should update the hint', function() { this.input.hasFocus.andReturn(true); this.input.hasOverflow.andReturn(false); this.menu.getTopSelectable.andReturn($('')); this.menu.getSelectableData.andReturn(testData); this.input.getInputValue.andReturn(testData.val.slice(0, 2)); this.input.trigger(eventName, payload); expect(this.input.setHint).toHaveBeenCalledWith(testData.val); }); }); }); describe('on langDirChanged', function() { var eventName, payload; beforeEach(function() { eventName = 'langDirChanged'; payload = 'rtl'; }); it('should set direction of menu if direction changed', function() { this.view.dir = 'ltr'; this.input.trigger(eventName, payload); expect(this.view.dir).toBe(payload); expect(this.menu.setLanguageDirection).toHaveBeenCalled(); }); it('should do nothing if direction did not changed', function() { this.view.dir = payload; this.input.trigger(eventName, payload); expect(this.view.dir).toBe(payload); expect(this.menu.setLanguageDirection).not.toHaveBeenCalled(); }); }); describe('#isActive', function() { it('should return true if active', function() { this.view.activate(); expect(this.view.isActive()).toBe(true); }); it('should return false if active', function() { this.view.deactivate(); expect(this.view.isActive()).toBe(false); }); }); describe('#isEnabled', function() { it('should returned enabled status', function() { this.view.enable(); expect(this.view.isEnabled()).toBe(true); this.view.disable(); expect(this.view.isEnabled()).toBe(false); }); }); describe('#enable', function() { it('should set enabled to true', function() { this.view.enable(); expect(this.view.isEnabled()).toBe(true); }); }); describe('#disable', function() { it('should set enabled to false', function() { this.view.disable(); expect(this.view.isEnabled()).toBe(false); }); }); describe('#activate', function() { describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should do nothing', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeactive', spy); this.view.activate(); expect(spy).not.toHaveBeenCalled(); }); }); describe('when idle and disabled', function() { beforeEach(function() { this.view.disable(); this.view.activate(); }); it('should do nothing', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeactive', spy); this.view.activate(); expect(spy).not.toHaveBeenCalled(); }); }); describe('when idle and enabled', function() { beforeEach(function() { this.view.enable(); this.view.deactivate(); }); it('should trigger typeahead:beforeactive', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeactive', spy); this.view.activate(); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy1, spy2; spy1 = jasmine.createSpy().andCallFake(prevent); spy2 = jasmine.createSpy(); this.$input.on('typeahead:beforeactive', spy1); this.$input.on('typeahead:active', spy2); this.view.activate(); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); }); it('should change state to active', function() { expect(this.view.isActive()).toBe(false); this.view.activate(); expect(this.view.isActive()).toBe(true); }); it('should trigger typeahead:active if not canceled', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:active', spy); this.view.activate(); expect(spy).toHaveBeenCalled(); }); }); }); describe('#deactivate', function() { describe('when idle', function() { beforeEach(function() { this.view.deactivate(); }); it('should do nothing', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeidle', spy); this.view.deactivate(); expect(spy).not.toHaveBeenCalled(); }); }); describe('when active', function() { beforeEach(function() { this.view.activate(); }); it('should trigger typeahead:beforeidle', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeidle', spy); this.view.deactivate(); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy1, spy2; spy1 = jasmine.createSpy().andCallFake(prevent); spy2 = jasmine.createSpy(); this.$input.on('typeahead:beforeidle', spy1); this.$input.on('typeahead:idle', spy2); this.view.deactivate(); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); }); it('should close', function() { spyOn(this.view, 'close'); this.view.deactivate(); expect(this.view.close).toHaveBeenCalled(); }); it('should change state to idle', function() { expect(this.view.isActive()).toBe(true); this.view.deactivate(); expect(this.view.isActive()).toBe(false); }); it('should trigger typeahead:idle if not canceled', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:idle', spy); this.view.deactivate(); expect(spy).toHaveBeenCalled(); }); }); }); describe('#isOpen', function() { it('should return true if open', function() { this.menu.isOpen.andReturn(true); expect(this.view.isOpen()).toBe(true); }); it('should return false if closed', function() { this.menu.isOpen.andReturn(false); expect(this.view.isOpen()).toBe(false); }); }); describe('#open', function() { describe('when open', function() { beforeEach(function() { spyOn(this.view, 'isOpen').andReturn(true); }); it('should do nothing', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeopen', spy); this.view.open(); expect(spy).not.toHaveBeenCalled(); }); }); describe('when closed', function() { beforeEach(function() { spyOn(this.view, 'isOpen').andReturn(false); }); it('should trigger typeahead:beforeopen', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeopen', spy); this.view.open(); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy1, spy2; spy1 = jasmine.createSpy().andCallFake(prevent); spy2 = jasmine.createSpy(); this.$input.on('typeahead:beforeopen', spy1); this.$input.on('typeahead:open', spy2); this.view.open(); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); }); it('should open menu', function() { this.view.open(); expect(this.menu.open).toHaveBeenCalled(); }); it('should update hint if active', function() { spyOn(this.view, 'isActive').andReturn(true); this.input.hasOverflow.andReturn(false); this.menu.getTopSelectable.andReturn($('')); this.menu.getSelectableData.andReturn(testData); this.input.getInputValue.andReturn(testData.val.slice(0, 2)); this.view.open(); expect(this.input.setHint).toHaveBeenCalled(); }); it('should trigger typeahead:open if not canceled', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:open', spy); this.view.open(); expect(spy).toHaveBeenCalled(); }); }); }); describe('#close', function() { describe('when closed', function() { beforeEach(function() { spyOn(this.view, 'isOpen').andReturn(false); }); it('should do nothing', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeclose', spy); this.view.open(); expect(spy).not.toHaveBeenCalled(); }); }); describe('when open', function() { beforeEach(function() { spyOn(this.view, 'isOpen').andReturn(true); }); it('should trigger typeahead:beforeclose', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforeclose', spy); this.view.close(); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy1, spy2; spy1 = jasmine.createSpy().andCallFake(prevent); spy2 = jasmine.createSpy(); this.$input.on('typeahead:beforeclose', spy1); this.$input.on('typeahead:close', spy2); this.view.close(); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); }); it('should close menu', function() { this.view.close(); expect(this.menu.close).toHaveBeenCalled(); }); it('should clear hint', function() { this.view.close(); expect(this.input.clearHint).toHaveBeenCalled(); }); it('should trigger typeahead:close if not canceled', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:close', spy); this.view.close(); expect(spy).toHaveBeenCalled(); }); }); }); describe('#getVal', function() { it('should return the current query', function() { this.input.getQuery.andReturn('woah'); expect(this.view.getVal()).toBe('woah'); }); }); describe('#setVal', function() { it('should update query', function() { this.input.hasFocus.andReturn(true); this.view.setVal('woah'); expect(this.input.setQuery).toHaveBeenCalledWith('woah'); }); }); describe('#select', function() { it('should do nothing if element is not a selectable', function() { var spy; this.menu.getSelectableData.andReturn(null); this.$input.on('typeahead:beforeselect', spy = jasmine.createSpy()); this.view.select($('')); expect(spy).not.toHaveBeenCalled(); }); it('should trigger typeahead:beforeselect', function() { var spy; this.menu.getSelectableData.andReturn(testData); this.$input.on('typeahead:beforeselect', spy = jasmine.createSpy()); this.view.select($('')); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy1, spy2; spy1 = jasmine.createSpy().andCallFake(prevent); spy2 = jasmine.createSpy(); this.menu.getSelectableData.andReturn(testData); this.$input.on('typeahead:beforeselect', spy1).on('typeahead:select', spy2); this.view.select($('')); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); }); it('should update query', function() { this.menu.getSelectableData.andReturn(testData); this.view.select($('')); expect(this.input.setQuery).toHaveBeenCalledWith(testData.val, true); }); it('should trigger typeahead:select', function() { var spy; this.menu.getSelectableData.andReturn(testData); this.$input.on('typeahead:select', spy = jasmine.createSpy()); this.view.select($('')); expect(spy).toHaveBeenCalled(); }); it('should close', function() { spyOn(this.view, 'close'); this.menu.getSelectableData.andReturn(testData); this.view.select($('')); expect(this.view.close).toHaveBeenCalled(); }); }); describe('#autocomplete', function() { it('should abort if the query matches the top suggestion', function() { var spy; this.input.getQuery.andReturn(testData.val); this.menu.getSelectableData.andReturn(testData); this.$input.on('typeahead:beforeautocomplete', spy = jasmine.createSpy()); this.view.autocomplete($('')); expect(spy).not.toHaveBeenCalled(); }); it('should trigger typeahead:beforeautocomplete', function() { var spy; this.menu.getSelectableData.andReturn(testData); this.$input.on('typeahead:beforeautocomplete', spy = jasmine.createSpy()); this.view.autocomplete($('')); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy1, spy2; spy1 = jasmine.createSpy().andCallFake(prevent); spy2 = jasmine.createSpy(); this.$input.on('typeahead:beforeautocomplete', spy1); this.$input.on('typeahead:autocomplete', spy2); this.menu.getSelectableData.andReturn(testData); this.view.autocomplete($('')); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); }); it('should update the query', function() { this.menu.getSelectableData.andReturn(testData); this.view.autocomplete($('')); expect(this.input.setQuery).toHaveBeenCalledWith(testData.val); }); it('should trigger typeahead:autocomplete', function() { var spy; this.menu.getSelectableData.andReturn(testData); this.$input.on('typeahead:autocomplete', spy = jasmine.createSpy()); this.view.autocomplete($('')); expect(spy).toHaveBeenCalled(); }); }); describe('#moveCursor', function() { beforeEach(function() { this.input.getQuery.andReturn('foo'); }); it('should move cursor if minLength is not satisfied', function() { var spy = jasmine.createSpy(); this.view.minLength = 100; this.menu.update.andReturn(true); this.$input.on('typeahead:beforecursorchange', spy); this.view.moveCursor(1); expect(spy).toHaveBeenCalled(); }); it('should move cursor if invalid update', function() { var spy = jasmine.createSpy(); this.menu.update.andReturn(false); this.$input.on('typeahead:beforecursorchange', spy); this.view.moveCursor(1); expect(spy).toHaveBeenCalled(); }); it('should not move cursor if valid update', function() { var spy = jasmine.createSpy(); this.menu.update.andReturn(true); this.$input.on('typeahead:beforecursorchange', spy); this.view.moveCursor(1); expect(spy).not.toHaveBeenCalled(); }); it('should trigger typeahead:beforecursorchange', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:beforecursorchange', spy); this.view.moveCursor(1); expect(spy).toHaveBeenCalled(); }); it('should support cancellation', function() { var spy = jasmine.createSpy().andCallFake(prevent); this.$input.on('typeahead:beforecursorchange', spy); this.view.moveCursor(1); expect(this.menu.setCursor).not.toHaveBeenCalled(); }); it('should update the input value if moved to selectable', function() { this.menu.getSelectableData.andReturn(testData); this.view.moveCursor(1); expect(this.input.setInputValue).toHaveBeenCalledWith(testData.val); }); it('should reset the input value if moved to input', function() { this.view.moveCursor(1); expect(this.input.resetInputValue).toHaveBeenCalled(); }); it('should update the hint', function() { this.input.hasOverflow.andReturn(false); this.menu.getTopSelectable.andReturn($('')); this.menu.getSelectableData.andCallFake(fake); this.input.getInputValue.andReturn(testData.val.slice(0, 1)); this.view.moveCursor(1); expect(this.input.setHint).toHaveBeenCalledWith(testData.val); function fake($el) { return ($el && $el.prop('tagName') === 'FIZ') ? testData : null; } }); it('should trigger cursorchange after setting cursor', function() { var spy = jasmine.createSpy(); this.$input.on('typeahead:cursorchange', spy); this.view.moveCursor(1); expect(spy).toHaveBeenCalled(); }); }); describe('#destroy', function() { it('should destroy input', function() { this.view.destroy(); expect(this.input.destroy).toHaveBeenCalled(); }); it('should destroy menu', function() { this.view.destroy(); expect(this.menu.destroy).toHaveBeenCalled(); }); }); function prevent($e) { $e.preventDefault(); } }); typeahead.js-0.11.1/typeahead.js.jquery.json000066400000000000000000000017071251733230600207500ustar00rootroot00000000000000{ "licenses": [ { "url": "https://github.com/twitter/typeahead.js/blob/master/LICENSE" } ], "dependencies": { "jquery": ">=1.7" }, "docs": "https://github.com/twitter/typeahead.js", "demo": "http://twitter.github.com/typeahead.js/examples", "name": "typeahead.js", "title": "typeahead.js", "author": { "name": "Twitter, Inc.", "url": "https://twitter.com/twitteross" }, "description": "fast and fully-featured autocomplete library", "keywords": [ "typeahead", "autocomplete" ], "homepage": "http://twitter.github.com/typeahead.js", "bugs": "https://github.com/twitter/typeahead.js/issues", "maintainers": [ { "name": "Jake Harding", "url": "https://twitter.com/JakeHarding" }, { "name": "Tim Trueman", "url": "https://twitter.com/timtrueman" }, { "name": "Veljko Skarich", "url": "https://twitter.com/vskarich" } ], "version": "0.11.1" }