├── .github
├── ISSUE_TEMPLATE
│ ├── bug.md
│ └── feature.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── comment-issue.yml
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── History.md
├── LICENSE
├── README.md
├── autocomplete-client.coffee
├── autocomplete-server.coffee
├── autocomplete.css
├── docs
├── mention1.png
└── mention2.png
├── examples
└── pubsublocal
│ ├── .gitignore
│ ├── .meteor
│ ├── .finished-upgraders
│ ├── .gitignore
│ ├── .id
│ ├── identifier
│ ├── packages
│ ├── platforms
│ ├── release
│ └── versions
│ ├── README.md
│ ├── client
│ ├── client.js
│ ├── options.html
│ ├── options.js
│ ├── pubsublocal.html
│ ├── single.html
│ └── single.js
│ ├── deploy.sh
│ ├── lib
│ └── collections.js
│ ├── package-lock.json
│ ├── package.json
│ ├── packages
│ └── autocomplete
│ ├── server
│ └── server.js
│ └── upload-db.sh
├── inputs.html
├── package.js
├── templates.coffee
└── tests
├── param_tests.coffee
├── regex_tests.coffee
├── rule_tests.coffee
└── security_tests.coffee
/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug
3 | about: Reporting a bug or a problem
4 | title: ''
5 | labels: bug
6 | assignee: ''
7 | ---
8 |
9 | ## What
10 |
11 |
12 | ## Reproduction
13 |
14 | - [ ] Go to the sample application
15 | - [ ] Type...
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You would like to see something added to the project.
4 | title: ''
5 | labels: enhancement
6 | assignee: ''
7 | ---
8 |
9 | ## What
10 |
11 |
12 | ## Use case
13 |
14 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 | ## What
13 |
14 |
15 | ## Why
16 |
17 |
18 | ## Checklist
19 |
20 | - [ ] Agreed upon solution from issue #
21 | - [ ] Stakeholders have approved the changes
--------------------------------------------------------------------------------
/.github/workflows/comment-issue.yml:
--------------------------------------------------------------------------------
1 | name: Add immediate comment on new issues
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 |
7 | jobs:
8 | createComment:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Create Comment
12 | uses: peter-evans/create-or-update-comment@v1.4.2
13 | with:
14 | issue-number: ${{ github.event.issue.number }}
15 | body: |
16 | Thank you for submitting this issue!
17 |
18 | We, the Members of Meteor Community Packages take every issue seriously.
19 | Our goal is to provide long-term lifecycles for packages and keep up
20 | with the newest changes in Meteor and the overall NodeJs/JavaScript ecosystem.
21 |
22 | However, we contribute to these packages mostly in our free time.
23 | Therefore, we can't guarantee your issues to be solved within certain time.
24 |
25 | If you think this issue is trivial to solve, don't hesitate to submit
26 | a pull request, too! We will accompany you in the process with reviews and hints
27 | on how to get development set up.
28 |
29 | Please also consider sponsoring the maintainers of the package.
30 | If you don't know who is currently maintaining this package, just leave a comment
31 | and we'll let you know
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .versions
2 |
3 | lib-cov
4 | *.seed
5 | *.log
6 | *.csv
7 | *.dat
8 | *.out
9 | *.pid
10 | *.gz
11 |
12 | pids
13 | logs
14 | results
15 |
16 | npm-debug.log
17 | .build*
18 |
19 | .idea/
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | dist: focal
3 | language: node_js
4 | node_js:
5 | - "14"
6 | - "12"
7 | before_install:
8 | - "curl -L http://git.io/ejPSng | /bin/sh"
9 | before_script:
10 | - "export PATH=$HOME/.meteor:$PATH"
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Meteor Community Code of Conduct
2 |
3 | ### Community
4 |
5 | We want to build a productive, happy and agile community that welcomes new ideas, constantly looks for areas to improve, and fosters collaboration.
6 |
7 | The project gains strength from a diversity of backgrounds and perspectives in our contributor community, and we actively seek participation from those who enhance it. This code of conduct exists to lay some ground rules that ensure we can collaborate and communicate effectively, despite our diversity. The code applies equally to founders, team members and those seeking help and guidance.
8 |
9 | ### Using This Code
10 |
11 | This isn’t an exhaustive list of things that you can’t do. Rather, it’s a guide for participation in the community that outlines how each of us can work to keep Meteor a positive, successful, and growing project.
12 |
13 | This code of conduct applies to all spaces managed by the Meteor Community project. This includes GitHub issues, and any other forums created by the team which the community uses for communication. We expect it to be honored by everyone who represents or participates in the project, whether officially or informally.
14 |
15 | #### When Something Happens
16 |
17 | If you see a Code of Conduct violation, follow these steps:
18 |
19 | * Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s).
20 | * That person should immediately stop the behavior and correct the issue.
21 | * If this doesn’t happen, or if you’re uncomfortable speaking up, contact admins.
22 | * As soon as available, an admin will join, identify themselves, and take further action (see below), starting with a warning, then temporary deactivation, then long-term deactivation.
23 |
24 | When reporting, please include any relevant details, links, screenshots, context, or other information that may be used to better understand and resolve the situation.
25 |
26 | The Admin team will prioritize the well-being and comfort of the recipients of the violation over the comfort of the violator.
27 |
28 | If you believe someone is violating the code of conduct, please report it to any member of the governing team.
29 |
30 | ### We Strive To:
31 |
32 | - **Be open, patient, and welcoming**
33 |
34 | Members of this community are open to collaboration, whether it's on PRs, issues, or problems. We're receptive to constructive comment and criticism, as we value what the experiences and skill sets of contributors bring to the project. We're accepting of all who wish to get involved, and find ways for anyone to participate in a way that best matches their strengths.
35 |
36 | - **Be considerate**
37 |
38 | We are considerate of our peers: other Meteor users and contributors. We’re thoughtful when addressing others’ efforts, keeping in mind that work is often undertaken for the benefit of the community. We also value others’ time and appreciate that not every issue or comment will be responded to immediately. We strive to be mindful in our communications, whether in person or online, and we're tactful when approaching views that are different from our own.
39 |
40 | - **Be respectful**
41 |
42 | As a community of professionals, we are professional in our handling of disagreements, and don’t allow frustration to turn into a personal attack. We work together to resolve conflict, assume good intentions and do our best to act in an empathic fashion.
43 |
44 | We do not tolerate harassment or exclusionary behavior. This includes, but is not limited to:
45 | - Violent threats or language directed against another person.
46 | - Discriminatory jokes and language.
47 | - Posting sexually explicit or sexualized content.
48 | - Posting content depicting or encouraging violence.
49 | - Posting (or threatening to post) other people's personally identifying information ("doxing").
50 | - Personal insults, especially those using racist or sexist terms.
51 | - Unwelcome sexual attention.
52 | - Advocating for, or encouraging, any of the above behavior.
53 | - Repeated harassment of others. In general, if someone asks you to stop, then stop.
54 |
55 | - **Take responsibility for our words and our actions**
56 |
57 | We can all make mistakes; when we do, we take responsibility for them. If someone has been harmed or offended, we listen carefully and respectfully. We are also considerate of others’ attempts to amend their mistakes.
58 |
59 | - **Be collaborative**
60 |
61 | The work we produce is (and is part of) an ecosystem containing several parallel efforts working towards a similar goal. Collaboration between teams and individuals that each have their own goal and vision is essential to reduce redundancy and improve the quality of our work.
62 |
63 | Internally and externally, we celebrate good collaboration. Wherever possible, we work closely with upstream projects and others in the free software community to coordinate our efforts. We prefer to work transparently and involve interested parties as early as possible.
64 |
65 | - **Ask for help when in doubt**
66 |
67 | Nobody is expected to be perfect in this community. Asking questions early avoids many problems later, so questions are encouraged, though they may be directed to the appropriate forum. Those who are asked should be responsive and helpful.
68 |
69 | - **Take initiative**
70 |
71 | We encourage new participants to feel empowered to lead, to take action, and to experiment when they feel innovation could improve the project. If we have an idea for a new tool, or how an existing tool can be improved, we speak up and take ownership of that work when possible.
72 |
73 | ### Attribution
74 |
75 | This Code of Conduct was inspired by [Meteor CoC](https://github.com/meteor/meteor/blob/devel/CODE_OF_CONDUCT.md).
76 |
77 | Sections of this Code of Conduct were inspired in by the following Codes from other open source projects and resources we admire:
78 |
79 | - [The Contributor Covenant](http://contributor-covenant.org/version/1/4/)
80 | - [Python](https://www.python.org/psf/codeofconduct/)
81 | - [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct)
82 | - [Django](https://www.djangoproject.com/conduct/)
83 |
84 | *This Code of Conduct is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) license. This Code was last updated on March 12, 2019.*
85 |
86 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 | ## vNEXT
2 |
3 | * Add proper package dependencies for newer versions of Meteor.
4 | * Fix an issue where an extraneous `collection` field was required for custom server-side subscriptions. (#40)
5 | * Make compatible with CoffeeScript 2 (#148)
6 | * `autocomplete` html attribute is now set to `off` in order to prevent browser displaying its own autocomplete dropdown (#99).
7 | * Updated example to the latest Meteor version
8 | * Support all utf-8 symbols in regex (#151)
9 |
10 | ## v0.5.1
11 |
12 | * Allow either top or bottom positioning in both normal and whole-field modes. (#75)
13 |
14 | ## v0.5.0
15 |
16 | * Switch to jQuery events instead of callbacks; you can now detect autocomplete selections using a template's event map. **Callbacks are no longer supported.** See the demo for a use example. (#48, #56)
17 |
18 | ## v0.4.10
19 |
20 | * Make the `Autocomplete.publishCursor(cursor, subscription)` function available on the server, which greatly simplifies the process of returning results for an autocomplete query over a publication.
21 | * Update the usage of the Mongo Collection API changed in Meteor 0.9.1 and later.
22 |
23 | ## v0.4.9
24 |
25 | * Update usage of template helpers for Meteor 0.9.4. (#66, #67)
26 | * Don't follow the cursor in whole-field autocompletion mode (#55, #63 -thanks @cretep).
27 | * Better compatibility of whole-field mode when using `TAB` and `Shift+TAB` after selections. (#64)
28 |
29 | ## v0.4.8
30 |
31 | * Updates for Meteor 0.9.1 APIs, since we use a lot of weird stuff. This is just to get things working; expect some general cleanup in the future as Meteor's API stabilizes for 1.0.
32 |
33 | ## v0.4.7
34 |
35 | * **Updated for Meteor 0.9.**
36 | * Made pre-sorting the autocomplete list an option that is off by default, for better performance on searches over large collections, especially on the client.
37 | * Fix errors resulting from trying to select nonexistent items.
38 |
39 | ## v0.4.6
40 |
41 | * Refactor UI components using the new Blaze API on Meteor 0.8.3, with Blaze Views.
42 | * Restore textarea block helper content.
43 |
44 | ## v0.4.5
45 |
46 | * Temporarily disable textarea block helper content until the Blaze API is updated.
47 |
48 | ## v0.4.4
49 |
50 | * Simulate pre-Blaze rendering behavior to properly deal with changing data contexts, until an updated Blaze Component API is released.
51 | * Support a custom specified template when no match is found. (#25)
52 |
53 | ## v0.4.3
54 |
55 | * Fix an issue where caret position was incorrect on a focus.
56 |
57 | ## v0.4.2
58 |
59 | * Use the Meteor caret-position package instead of the `jquery-caretposition` and `jquery-migrate` packages.
60 | * Added some validation for specifying rules, and tests for regular expressions.
61 | * Improve behavior of whole-field (tokenless) autocompletion.
62 | * Pressing the escape key while autocompleting now blurs the field.
63 |
64 | ## v0.4.1
65 |
66 | * Allow for creating any custom selector from an autocomplete match, in addition to the standard `$regex` behavior.
67 |
68 | ## v0.4.0
69 |
70 | * Revamped the behavior of token-less autocompletion. (See #4, #27, and #33)
71 | * The selection callback now passes the input element as the second argument. (#31)
72 |
73 | ## v0.3.0
74 |
75 | * Update for Meteor 0.8.0 (Blaze). **NOTE: You will need to update your app to use this version.** (#22)
76 |
77 | ## v0.2.4
78 |
79 | This is the last version of autocomplete that will support Meteor <0.8.0 (Blaze).
80 |
81 | * Add an optional `filter` field to allow additional static filters on a collection search. (#21)
82 | * Only insecure collections can be searched by default on the server side. **If you are using the default implementation, you will need to write your own publish function**. (#20)
83 | * Add automated testing infrastructure.
84 |
85 | ## v0.2.3
86 |
87 | * Support nested values for `field`, i.e. `'profile.foo'`. (#19)
88 |
89 | ## v0.2.2
90 |
91 | * Provided an option for callbacks when an item is selected and inserted (#18).
92 |
93 | ## v0.2.1
94 |
95 | * Fixed a bug with CSS positioning of the autocomplete popup.
96 | * Provided more control over regex options. Default option is case-insensitive `'i'`.
97 |
98 | ## v0.2.0
99 |
100 | * Added server-side (pub/sub) autocompletion (#6) - many thanks to @dandv; see #17 for implementation discussion.
101 |
102 | ## v0.1.1
103 |
104 | * Increased z-index on autocomplete container (#8).
105 | * Added jquery-migrate package to temporarily support caret position operations on Meteor 0.7.1.2 (this will be fixed in the future).
106 |
107 | ## v0.1.0
108 |
109 | * First release.
110 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Andrew Mao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | meteor-autocomplete [](https://travis-ci.org/mizzao/meteor-autocomplete)
2 | ===================
3 |
4 | Client/server autocompletion designed for Meteor's collections and reactivity.
5 |
6 | Check out a demo app at http://autocomplete.meteor.com or the [source](examples/pubsublocal).
7 |
8 | Help keep your favorite Meteor packages alive! If you depend on this package in your app and find it useful, consider a donation at [Gittip](https://www.gittip.com/mizzao/) for me (or other Meteor package maintainers).
9 |
10 | ## What's this do?
11 |
12 | Auto-completes typing in text `input`s or `textarea`s from different local or remote Meteor collections when triggered by certain symbols. You've probably seen this when referring to users or issues in a GitHub conversation. For example, you may want to ping a user:
13 |
14 | 
15 |
16 | ...and ask them to look at a certain item:
17 |
18 | 
19 |
20 | Features:
21 | - Multiple collection matching with different trigger tokens and fields
22 | - Fully live and reactive Meteor template rendering of drop-down list items
23 | - Drop-down can be positioned above or below the text
24 | - Mouse or keyboard interaction with autocomplete menu
25 | - Simple token-less autocompletion in an `` element, just like Bootstrap typeahead
26 |
27 | Meteor's client-side data availability makes this dynamic, full-fledged autocomplete widget possible. Use it in chat rooms, comments, other messaging systems, or wherever strikes your fancy.
28 |
29 | ## Usage
30 |
31 | Use Meteor to install the package:
32 |
33 | ```
34 | meteor add mizzao:autocomplete
35 | ```
36 |
37 | Add a text `input` or `textarea` to a template in one of the following ways, as a Spacebars template or block helper. Pass in any HTML parameters as other arguments to the template:
38 |
39 | ```
40 |
41 | ... stuff
42 | {{> inputAutocomplete settings=settings id="msg" class="input-xlarge" placeholder="Chat..."}}
43 | ... more stuff
44 |
45 |
46 |
47 | ... stuff
48 | {{#textareaAutocomplete settings=settings id="msg"}}
49 | {{myStartingText}}
50 | {{/textareaAutocomplete}}
51 | ... more stuff
52 |
53 | ```
54 |
55 | Define a helper for the first argument, like the following example:
56 |
57 | ```javascript
58 | Template.foo.helpers({
59 | settings: function() {
60 | return {
61 | position: "top",
62 | limit: 5,
63 | rules: [
64 | {
65 | token: '@',
66 | collection: Meteor.users,
67 | field: "username",
68 | template: Template.userPill
69 | },
70 | {
71 | token: '!',
72 | collection: Dataset,
73 | field: "_id",
74 | options: '',
75 | matchAll: true,
76 | filter: { type: "autocomplete" },
77 | template: Template.dataPiece
78 | }
79 | ]
80 | };
81 | }
82 | });
83 | ```
84 |
85 | ##### Top Level Options
86 |
87 | - `position` (= `top` or `bottom`) specifies if the autocomplete menu should render above or below the cursor. Select based on the placement of your `input`/`textarea` relative to other elements on the page.
88 | - `limit`: Controls how big the autocomplete menu should get.
89 | - `rules`: An array of matching rules for the autocomplete widget, which will be checked in order.
90 |
91 | ##### Rule Specific Options
92 |
93 | - `token`: (optional) What character should trigger this rule. Leave blank for whole-field behavior (see below).
94 | - `collection`: What collection should be used to match for this rule. Must be a `Mongo.Collection` for client-side collections, or a string for remote collections (available in `global` on the server.)
95 | - `subscription`: A custom subscription for server-side search; see below.
96 | - `template`: The template that should be used to render each list item.
97 | - `filter`: (optional) An object that will be merged with the autocomplete selector to limit the results to more specific documents in the collection.
98 | - `sort`: (default `false`) Whether to sort the results before applying the limit. For good performance on large collections, this should be turned on only for server-side searches where an index can be used.
99 | - `noMatchTemplate`: (optional) A template to display when nothing matches. This template can use the [reactive functions on the AutoComplete object](autocomplete-client.coffee) to display a specific message, or be [assigned mouse/keyboard events](http://docs.meteor.com/#eventmaps) for user interaction.
100 |
101 | Default matcher arguments: the default behavior is to create a regex against the field to be matched, which will be constructed using the arguments below.
102 |
103 | - `field`: The field of the collection that the rule will match against. Can be nested, i.e. `'profile.foo'`.
104 | - `options`: `'i'` (default) to specify the regex matching options.
105 | - `matchAll`: `false` (default) to match only fields starting with the matched string. (see below)
106 |
107 | Custom matcher: if this is specified, the *default* matcher arguments will be ignored. (Note that you should still specify `field`.)
108 |
109 | - `selector`: a one argument `function(match)` that takes the currently matched token suffix and returns the selector that should be added to the argument to `collection.find` to filter the autocomplete results. (**NOTE**: if you are using `$where`, the selector cannot be serialized to the server).
110 |
111 | ##### Detecting Selections
112 |
113 | Autocomplete triggers jQuery events that can be listened to using Meteor's event maps. The only currently supported event is `autocompleteselect`, which notifies of a selected element. For example:
114 |
115 | ```
116 | Template.foo.events({
117 | "autocompleteselect input": function(event, template, doc) {
118 | console.log("selected ", doc);
119 | }
120 | });
121 | ```
122 |
123 | See the example app for more details.
124 |
125 | ##### Regex Specification and Options
126 |
127 | Note that [regular expression searches](http://docs.mongodb.org/manual/reference/operator/query/regex/) can only use an index efficiently when the regular expression has an anchor for the beginning (i.e. `^`) of a string and is a case-sensitive match. Hence, when using case-sensitive matches and string start anchors (i.e. `matchAll: false`) searches can take advantage of server indices in Mongo.
128 |
129 | This behavior is demonstrated in the example app.
130 |
131 | ##### Whole-field (Tokenless) Autocompletion
132 |
133 | If you only need to autocomplete over a single collection and want to match the entire field, specify a `rules` array with a single object and omit the `token` argument. The behavior for this is a little different than with tokens; see the [demo](http://autocomplete.meteor.com).
134 |
135 | Mixing tokens with tokenless autocompletion is unsupported and will probably result in unexpected behavior.
136 |
137 | ##### Server-side Autocompletion and Text Search Engines
138 |
139 | For security purposes, a default implementation of server-side autocomplete is only provided for insecure collections, to be used while prototyping. In all other applications, write your own publish function with the same arguments as in the [autocomplete-recordset](autocomplete-server.coffee) publication and secure it properly, given that malicious clients can subscribe to this function in ways other than the autocomplete client code would.
140 |
141 | Make sure to push documents to the `autocompleteRecords` client-side collection. A convenience function, `Autocomplete.publishCursor`, is provided as an easy way to do this. See the default implementation for an example.
142 |
143 | Use of a custom publish function also allows you to:
144 |
145 | * use full-text search services outside of Meteor, such as [ElasticSearch](http://www.elasticsearch.org/)
146 | * use [preferential matching](https://github.com/mizzao/meteor-autocomplete/blob/a437c7b464ad9e779da2ca15566a5b91cf603902/autocomplete-server.coffee) for record fields that start with the autocomplete text, rather than contain it anywhere
147 |
148 | ##### Autocomplete Templates
149 |
150 | An autocomplete template is just a normal Meteor template that is passed in the matched document. The template will be passed the entire matched document as a data context, so render list items as fancily as you would like. For example, it's usually helpful to see metadata for matches as in the pictures above.
151 |
152 | Records that match the filter text typed after the token render a list of the `template` sorted in ascending order by `field`. For example, if you were matching on `Meteor.users` and you just wanted to display the username, you can do something very simple, and display the same field:
153 |
154 | ```
155 |
156 | {{username}}
157 |
158 | ```
159 |
160 | However, you might want to do something a little more fancy and show not only the user, but whether they are online or not (with something like the [user-status](https://github.com/mizzao/meteor-user-status) package. In that case you could do something like the following:
161 |
162 | ```
163 |
164 | {{username}}
165 |
166 | ```
167 |
168 | and accompanying code:
169 |
170 | ```javascript
171 | Template.userPill.labelClass = function() {
172 | if this._id === Meteor.userId()
173 | return "label-warning"
174 | else if this.profile.online === true
175 | return "label-success"
176 | else
177 | return ""
178 | }
179 | ```
180 |
181 | This (using normal Bootstrap classes) will cause the user to show up in orange for him/herself, in green for other users that are online, and in grey otherwise. See [CrowdMapper's templates](https://github.com/mizzao/CrowdMapper/blob/master/client/views/common.html) for other interesting things you may want to do.
182 |
183 | ##### Examples
184 |
185 | For example settings see one of the following:
186 |
187 | - [Multi-field example](examples/pubsublocal/client/client.js) (from the app above)
188 | - [Single-field example](examples/pubsublocal/client/single.js) (also from the app above)
189 | - [Autocompleting chatroom example](https://github.com/mizzao/CrowdMapper/blob/master/client/views/chat.coffee)
190 |
191 | ### Future Work (a.k.a. send pull requests)
192 |
193 | - To reduce latency, we could additionally support using `Meteor.methods` to return an array of documents, instead of a subscription, if the client's cache of the collection is assumed to be read-only or if changes don't matter.
194 | - The widget can keep track of a list of ordered document ids for matched items instead of just spitting out the fields (which currently should be unique)
195 | - Could potentially support rendering DOM elements instead of just text. However, this can currently be managed in post-processing code for chat/post functions (like how GitHub does it).
196 |
197 | ### Credits/Notes
198 |
199 | - If you are not using Meteor, you may want to check out [jquery sew](https://github.com/tactivos/jquery-sew), from which this was heavily modified.
200 | - If you need tag autocompletion only (from one collection, and no text), try either the [x-editable smart package](https://github.com/nate-strauser/meteor-x-editable-bootstrap) with Select2 or [jquery-tokenInput](http://loopj.com/jquery-tokeninput/). Those support rendering DOM elements in the input field.
201 |
202 | ### Main Contributors
203 |
204 | - Andrew Mao ([mizzao](https://github.com/mizzao))
205 | - Dan Dascalescu ([dandv](https://github.com/dandv))
206 | - Adam Love ([Neobii](https://github.com/Neobii))
207 |
208 |
--------------------------------------------------------------------------------
/autocomplete-client.coffee:
--------------------------------------------------------------------------------
1 | AutoCompleteRecords = new Mongo.Collection("autocompleteRecords")
2 |
3 | isServerSearch = (rule) -> rule.subscription? || _.isString(rule.collection)
4 |
5 | validateRule = (rule) ->
6 | if rule.subscription? and rule.collection?
7 | throw new Error("Rule cannot specify both a server-side subscription and a client/server collection to search simultaneously")
8 |
9 | unless rule.subscription? or Match.test(rule.collection, Match.OneOf(String, Mongo.Collection))
10 | throw new Error("Collection to search must be either a Mongo collection or server-side name")
11 |
12 | # XXX back-compat message, to be removed
13 | if rule.callback?
14 | console.warn("autocomplete no longer supports callbacks; use event listeners instead.")
15 |
16 | isWholeField = (rule) ->
17 | # either '' or null both count as whole field.
18 | return !rule.token
19 |
20 | getRegExp = (rule) ->
21 | unless isWholeField(rule)
22 | # Expressions for the range from the last word break to the current cursor position
23 | new RegExp('(^|\\b|\\s)' + rule.token + '([\\\\w.a-zA-Z0-9\u0080-\u9fff]*)$')
24 | else
25 | # Whole-field behavior - word characters or spaces
26 | new RegExp('(^)(.*)$')
27 |
28 | getFindParams = (rule, filter, limit) ->
29 | # This is a different 'filter' - the selector from the settings
30 | # We need to extend so that we don't copy over rule.filter
31 | selector = _.extend({}, rule.filter || {})
32 | options = { limit: limit }
33 |
34 | # Match anything, no sort, limit X
35 | return [ selector, options ] unless filter
36 |
37 | if rule.sort and rule.field
38 | sortspec = {}
39 | # Only sort if there is a filter, for faster performance on a match of anything
40 | sortspec[rule.field] = 1
41 | options.sort = sortspec
42 |
43 | if _.isFunction(rule.selector)
44 | # Custom selector
45 | _.extend(selector, rule.selector(filter))
46 | else
47 | selector[rule.field] = {
48 | $regex: if rule.matchAll then filter else "^" + filter
49 | # default is case insensitive search - empty string is not the same as undefined!
50 | $options: if (typeof rule.options is 'undefined') then 'i' else rule.options
51 | }
52 |
53 | return [ selector, options ]
54 |
55 | getField = (obj, str) ->
56 | obj = obj[key] for key in str.split(".")
57 | return obj
58 |
59 | class @AutoComplete
60 |
61 | @KEYS: [
62 | 40, # DOWN
63 | 38, # UP
64 | 13, # ENTER
65 | 27, # ESCAPE
66 | 9 # TAB
67 | ]
68 |
69 | constructor: (settings) ->
70 | @limit = settings.limit || 5
71 | @position = settings.position || "bottom"
72 |
73 | @rules = settings.rules
74 | validateRule(rule) for rule in @rules
75 |
76 | @expressions = (getRegExp(rule) for rule in @rules)
77 |
78 | @matched = -1
79 | @loaded = true
80 |
81 | # Reactive dependencies for current matching rule and filter
82 | @ruleDep = new Deps.Dependency
83 | @filterDep = new Deps.Dependency
84 | @loadingDep = new Deps.Dependency
85 |
86 | # autosubscribe to the record set published by the server based on the filter
87 | # This will tear down server subscriptions when they are no longer being used.
88 | @sub = null
89 | @comp = Deps.autorun =>
90 | # Stop any existing sub immediately, don't wait
91 | @sub?.stop()
92 |
93 | return unless (rule = @matchedRule()) and (filter = @getFilter()) isnt null
94 |
95 | # subscribe only for server-side collections
96 | unless isServerSearch(rule)
97 | @setLoaded(true) # Immediately loaded
98 | return
99 |
100 | [ selector, options ] = getFindParams(rule, filter, @limit)
101 |
102 | # console.debug 'Subscribing to <%s> in <%s>.<%s>', filter, rule.collection, rule.field
103 | @setLoaded(false)
104 | subName = rule.subscription || "autocomplete-recordset"
105 | @sub = Meteor.subscribe(subName,
106 | selector, options, rule.collection, => @setLoaded(true))
107 |
108 | teardown: ->
109 | # Stop the reactive computation we started for this autocomplete instance
110 | @comp.stop()
111 |
112 | # reactive getters and setters for @filter and the currently matched rule
113 | matchedRule: ->
114 | @ruleDep.depend()
115 | if @matched >= 0 then @rules[@matched] else null
116 |
117 | setMatchedRule: (i) ->
118 | @matched = i
119 | @ruleDep.changed()
120 |
121 | getFilter: ->
122 | @filterDep.depend()
123 | return @filter
124 |
125 | setFilter: (x) ->
126 | @filter = x
127 | @filterDep.changed()
128 | return @filter
129 |
130 | isLoaded: ->
131 | @loadingDep.depend()
132 | return @loaded
133 |
134 | setLoaded: (val) ->
135 | return if val is @loaded # Don't cause redraws unnecessarily
136 | @loaded = val
137 | @loadingDep.changed()
138 |
139 | onKeyUp: ->
140 | return unless @$element # Don't try to do this while loading
141 | startpos = @element.selectionStart
142 | val = @getText().substring(0, startpos)
143 |
144 | ###
145 | Matching on multiple expressions.
146 | We always go from a matched state to an unmatched one
147 | before going to a different matched one.
148 | ###
149 | i = 0
150 | breakLoop = false
151 | while i < @expressions.length
152 | matches = val.match(@expressions[i])
153 |
154 | # matching -> not matching
155 | if not matches and @matched is i
156 | @setMatchedRule(-1)
157 | breakLoop = true
158 |
159 | # not matching -> matching
160 | if matches and @matched is -1
161 | @setMatchedRule(i)
162 | breakLoop = true
163 |
164 | # Did filter change?
165 | if matches and @filter isnt matches[2]
166 | @setFilter(matches[2])
167 | breakLoop = true
168 |
169 | break if breakLoop
170 | i++
171 |
172 | onKeyDown: (e) ->
173 | return if @matched is -1 or (@constructor.KEYS.indexOf(e.keyCode) < 0)
174 |
175 | switch e.keyCode
176 | when 9, 13 # TAB, ENTER
177 | if @select() # Don't jump fields or submit if select successful
178 | e.preventDefault()
179 | e.stopPropagation()
180 | # preventDefault needed below to avoid moving cursor when selecting
181 | when 40 # DOWN
182 | e.preventDefault()
183 | @next()
184 | when 38 # UP
185 | e.preventDefault()
186 | @prev()
187 | when 27 # ESCAPE
188 | @$element.blur()
189 | @hideList()
190 |
191 | return
192 |
193 | onFocus: ->
194 | # We need to run onKeyUp after the focus resolves,
195 | # or the caret position (selectionStart) will not be correct
196 | Meteor.defer => @onKeyUp()
197 |
198 | onBlur: ->
199 | # We need to delay this so click events work
200 | # TODO this is a bit of a hack; see if we can't be smarter
201 | Meteor.setTimeout =>
202 | @hideList()
203 | , 500
204 |
205 | onItemClick: (doc, e) => @processSelection(doc, @rules[@matched])
206 |
207 | onItemHover: (doc, e) ->
208 | @tmplInst.$(".-autocomplete-item").removeClass("selected")
209 | $(e.target).closest(".-autocomplete-item").addClass("selected")
210 |
211 | filteredList: ->
212 | # @ruleDep.depend() # optional as long as we use depend on filter, because list will always get re-rendered
213 | filter = @getFilter() # Reactively depend on the filter
214 | return null if @matched is -1
215 |
216 | rule = @rules[@matched]
217 | # Don't display list unless we have a token or a filter (or both)
218 | # Single field: nothing displayed until something is typed
219 | return null unless rule.token or filter
220 |
221 | [ selector, options ] = getFindParams(rule, filter, @limit)
222 |
223 | Meteor.defer => @ensureSelection()
224 |
225 | # if server collection, the server has already done the filtering work
226 | return AutoCompleteRecords.find({}, options) if isServerSearch(rule)
227 |
228 | # Otherwise, search on client
229 | return rule.collection.find(selector, options)
230 |
231 | isShowing: ->
232 | rule = @matchedRule()
233 | # Same rules as above
234 | showing = rule? and (rule.token or @getFilter())
235 |
236 | # Do this after the render
237 | if showing
238 | Meteor.defer =>
239 | @positionContainer()
240 | @ensureSelection()
241 |
242 | return showing
243 |
244 | # Replace text with currently selected item
245 | select: ->
246 | node = @tmplInst.find(".-autocomplete-item.selected")
247 | return false unless node?
248 | doc = Blaze.getData(node)
249 | return false unless doc # Don't select if nothing matched
250 |
251 | @processSelection(doc, @rules[@matched])
252 | return true
253 |
254 | processSelection: (doc, rule) ->
255 | replacement = getField(doc, rule.field)
256 |
257 | unless isWholeField(rule)
258 | @replace(replacement, rule)
259 | @hideList()
260 |
261 | else
262 | # Empty string or doesn't exist?
263 | # Single-field replacement: replace whole field
264 | @setText(replacement)
265 |
266 | # Field retains focus, but list is hidden unless another key is pressed
267 | # Must be deferred or onKeyUp will trigger and match again
268 | # TODO this is a hack; see above
269 | @onBlur()
270 |
271 | @$element.trigger("autocompleteselect", doc)
272 | return
273 |
274 | # Replace the appropriate region
275 | replace: (replacement) ->
276 | startpos = @element.selectionStart
277 | fullStuff = @getText()
278 | val = fullStuff.substring(0, startpos)
279 | val = val.replace(@expressions[@matched], "$1" + @rules[@matched].token + replacement)
280 | posfix = fullStuff.substring(startpos, fullStuff.length)
281 | separator = (if posfix.match(/^\s/) then "" else " ")
282 | finalFight = val + separator + posfix
283 | @setText finalFight
284 |
285 | newPosition = val.length + 1
286 | @element.setSelectionRange(newPosition, newPosition)
287 | return
288 |
289 | hideList: ->
290 | @setMatchedRule(-1)
291 | @setFilter(null)
292 |
293 | getText: ->
294 | return @$element.val() || @$element.text()
295 |
296 | setText: (text) ->
297 | if @$element.is("input,textarea")
298 | @$element.val(text).change()
299 | else
300 | @$element.html(text)
301 |
302 | ###
303 | Rendering functions
304 | ###
305 | positionContainer: ->
306 | # First render; Pick the first item and set css whenever list gets shown
307 | position = @$element.position()
308 |
309 | rule = @matchedRule()
310 |
311 | offset = getCaretCoordinates(@element, @element.selectionStart)
312 |
313 | # In whole-field positioning, we don't move the container and make it the
314 | # full width of the field.
315 | if rule? and isWholeField(rule)
316 | pos =
317 | left: position.left
318 | width: @$element.outerWidth() # position.offsetWidth
319 | else # Normal positioning, at token word
320 | pos =
321 | left: position.left + offset.left
322 |
323 | # Position menu from top (above) or from bottom of caret (below, default)
324 | if @position is "top"
325 | pos.bottom = @$element.offsetParent().height() - position.top - offset.top
326 | else
327 | pos.top = position.top + offset.top + parseInt(@$element.css('font-size'))
328 |
329 | @tmplInst.$(".-autocomplete-container").css(pos)
330 |
331 | ensureSelection : ->
332 | # Re-render; make sure selected item is something in the list or none if list empty
333 | selectedItem = @tmplInst.$(".-autocomplete-item.selected")
334 |
335 | unless selectedItem.length
336 | # Select anything
337 | @tmplInst.$(".-autocomplete-item:first-child").addClass("selected")
338 |
339 | # Select next item in list
340 | next: ->
341 | currentItem = @tmplInst.$(".-autocomplete-item.selected")
342 | return unless currentItem.length # Don't try to iterate an empty list
343 | currentItem.removeClass("selected")
344 |
345 | next = currentItem.next()
346 | if next.length
347 | next.addClass("selected")
348 | else # End of list or lost selection; Go back to first item
349 | @tmplInst.$(".-autocomplete-item:first-child").addClass("selected")
350 |
351 | # Select previous item in list
352 | prev: ->
353 | currentItem = @tmplInst.$(".-autocomplete-item.selected")
354 | return unless currentItem.length # Don't try to iterate an empty list
355 | currentItem.removeClass("selected")
356 |
357 | prev = currentItem.prev()
358 | if prev.length
359 | prev.addClass("selected")
360 | else # Beginning of list or lost selection; Go to end of list
361 | @tmplInst.$(".-autocomplete-item:last-child").addClass("selected")
362 |
363 | # This doesn't need to be reactive because list already changes reactively
364 | # and will cause all of the items to re-render anyway
365 | currentTemplate: -> @rules[@matched].template
366 |
367 | AutocompleteTest =
368 | records: AutoCompleteRecords
369 | isServerSearch: isServerSearch
370 | getRegExp: getRegExp
371 | getFindParams: getFindParams
372 |
--------------------------------------------------------------------------------
/autocomplete-server.coffee:
--------------------------------------------------------------------------------
1 | class Autocomplete
2 | @publishCursor: (cursor, sub) ->
3 | # This also attaches an onStop callback to sub, so we don't need to worry about that.
4 | # https://github.com/meteor/meteor/blob/devel/packages/mongo/collection.js
5 | Mongo.Collection._publishCursor(cursor, sub, "autocompleteRecords")
6 |
7 | Meteor.publish 'autocomplete-recordset', (selector, options, collName) ->
8 | collection = global[collName]
9 | unless collection
10 | throw new Error(collName + ' is not defined on the global namespace of the server.')
11 |
12 | # This is a semi-documented Meteor feature:
13 | # https://github.com/meteor/meteor/blob/devel/packages/mongo-livedata/collection.js
14 | unless collection._isInsecure()
15 | Meteor._debug(collName + ' is a secure collection, therefore no data was returned because the client could compromise security by subscribing to arbitrary server collections via the browser console. Please write your own publish function.')
16 | return [] # We need this for the subscription to be marked ready
17 |
18 | # guard against client-side DOS: hard limit to 50
19 | options.limit = Math.min(50, Math.abs(options.limit)) if options.limit
20 |
21 | # Push this into our own collection on the client so they don't interfere with other publications of the named collection.
22 | # This also stops the observer automatically when the subscription is stopped.
23 | Autocomplete.publishCursor( collection.find(selector, options), this)
24 |
25 | # Mark the subscription ready after the initial addition of documents.
26 | this.ready()
27 |
28 |
--------------------------------------------------------------------------------
/autocomplete.css:
--------------------------------------------------------------------------------
1 | .-autocomplete-container {
2 | position: absolute;
3 | background: white;
4 | border: 1px solid #DDD;
5 | border-radius: 3px;
6 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
7 | min-width: 180px;
8 | z-index: 1000;
9 | }
10 |
11 | .-autocomplete-list {
12 | list-style: none;
13 | margin: 0;
14 | padding: 0;
15 | }
16 |
17 | .-autocomplete-item {
18 | display: block;
19 | padding: 5px 10px;
20 | border-bottom: 1px solid #DDD;
21 | }
22 |
23 | .-autocomplete-item.selected {
24 | color: white;
25 | background: #4183C4;
26 | text-decoration: none;
27 | }
28 |
--------------------------------------------------------------------------------
/docs/mention1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-autocomplete/a77c3cf3200ea31ab0c06eca5a193d33be08e4ed/docs/mention1.png
--------------------------------------------------------------------------------
/docs/mention2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-autocomplete/a77c3cf3200ea31ab0c06eca5a193d33be08e4ed/docs/mention2.png
--------------------------------------------------------------------------------
/examples/pubsublocal/.gitignore:
--------------------------------------------------------------------------------
1 | .meteor/local
2 | .meteor/meteorite
3 | settings.json
4 | node_modules/
5 |
6 | # the mongodb dump
7 | dump/*
8 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 | 1.3.5-remove-old-dev-bundle-link
15 | 1.4.0-remove-old-dev-bundle-link
16 | 1.4.1-add-shell-server-package
17 | 1.4.3-split-account-service-packages
18 | 1.5-add-dynamic-import-package
19 | 1.7-split-underscore-from-meteor-base
20 | 1.8.3-split-jquery-from-blaze
21 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | dev_bundle
2 | local
3 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 136t8nc1inuy9x1nm4ry3
8 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/identifier:
--------------------------------------------------------------------------------
1 | 1uiyyydkjmd1653fcdp
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | standard-app-packages
7 | coffeescript
8 | insecure@1.0.7
9 | mizzao:autocomplete
10 | twbs:bootstrap
11 | standard-minifier-css@1.6.1
12 | standard-minifier-js@2.6.0
13 | shell-server
14 | dynamic-import
15 | ecmascript
16 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.11.1
2 |
--------------------------------------------------------------------------------
/examples/pubsublocal/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.1.0
2 | autoupdate@1.6.0
3 | babel-compiler@7.5.3
4 | babel-runtime@1.5.0
5 | base64@1.0.12
6 | binary-heap@1.0.11
7 | blaze@2.3.4
8 | blaze-tools@1.0.10
9 | boilerplate-generator@1.7.1
10 | caching-compiler@1.2.2
11 | caching-html-compiler@1.1.3
12 | callback-hook@1.3.0
13 | check@1.3.1
14 | coffeescript@1.0.17
15 | dandv:caret-position@2.1.1
16 | ddp@1.4.0
17 | ddp-client@2.3.3
18 | ddp-common@1.4.0
19 | ddp-server@2.3.2
20 | deps@1.0.12
21 | diff-sequence@1.1.1
22 | dynamic-import@0.5.3
23 | ecmascript@0.14.3
24 | ecmascript-runtime@0.7.0
25 | ecmascript-runtime-client@0.11.0
26 | ecmascript-runtime-server@0.10.0
27 | ejson@1.1.1
28 | fastclick@1.0.13
29 | fetch@0.1.1
30 | geojson-utils@1.0.10
31 | html-tools@1.0.11
32 | htmljs@1.0.11
33 | id-map@1.1.0
34 | insecure@1.0.7
35 | inter-process-messaging@0.1.1
36 | jquery@1.11.11
37 | launch-screen@1.2.0
38 | livedata@1.0.18
39 | logging@1.1.20
40 | meteor@1.9.3
41 | meteor-platform@1.2.6
42 | minifier-css@1.5.3
43 | minifier-js@2.6.0
44 | minimongo@1.6.0
45 | mizzao:autocomplete@0.5.1
46 | mobile-status-bar@1.1.0
47 | modern-browsers@0.1.5
48 | modules@0.15.0
49 | modules-runtime@0.12.0
50 | mongo@1.10.0
51 | mongo-decimal@0.1.1
52 | mongo-dev-server@1.1.0
53 | mongo-id@1.0.7
54 | npm-mongo@3.8.1
55 | observe-sequence@1.0.16
56 | ordered-dict@1.1.0
57 | promise@0.11.2
58 | random@1.2.0
59 | reactive-dict@1.3.0
60 | reactive-var@1.0.11
61 | reload@1.3.0
62 | retry@1.1.0
63 | routepolicy@1.1.0
64 | session@1.2.0
65 | shell-server@0.5.0
66 | socket-stream-client@0.3.1
67 | spacebars@1.0.15
68 | spacebars-compiler@1.1.3
69 | standard-app-packages@1.0.9
70 | standard-minifier-css@1.6.1
71 | standard-minifier-js@2.6.0
72 | templating@1.3.2
73 | templating-compiler@1.3.3
74 | templating-runtime@1.3.2
75 | templating-tools@1.1.2
76 | tracker@1.2.0
77 | twbs:bootstrap@3.3.6
78 | ui@1.0.13
79 | underscore@1.0.10
80 | webapp@1.9.1
81 | webapp-hashing@1.0.9
82 |
--------------------------------------------------------------------------------
/examples/pubsublocal/README.md:
--------------------------------------------------------------------------------
1 | # Demo/testing app for autocomplete
2 |
3 | To run:
4 |
5 | ```shell script
6 | npm run start
7 | ```
8 | or
9 | ```shell script
10 | meteor
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/pubsublocal/client/client.js:
--------------------------------------------------------------------------------
1 | // client-only collection to demo interoperability with server-side one
2 | Fruits = new Mongo.Collection(null);
3 |
4 | ['Apple', 'Banana', 'Cherry', 'Date', 'Fig', 'Lemon', 'Melon', 'Prune', 'Raspberry', 'Strawberry', 'Blueberry', 'Blackberry', 'Boysenberry', 'Licorice', 'Watermelon', 'Tomato', 'Jackfruit', 'Kiwi', 'Lime', 'Clementine', 'Tangerine', 'Orange', 'Grape'].forEach(function (fruit) {
5 | Fruits.insert({type: fruit})
6 | });
7 |
8 | Template.pubsub.helpers({
9 | settings: function() {
10 | return {
11 | position: Session.get("position"),
12 | limit: 30, // more than 20, to emphasize matches outside strings *starting* with the filter
13 | rules: [
14 | {
15 | token: '@',
16 | // string means a server-side collection; otherwise, assume a client-side collection
17 | collection: 'BigCollection',
18 | field: 'name',
19 | options: '', // Use case-sensitive match to take advantage of server index.
20 | template: Template.serverCollectionPill,
21 | noMatchTemplate: Template.serverNoMatch
22 | },
23 | {
24 | token: '!',
25 | collection: Fruits, // Mongo.Collection object means client-side collection
26 | field: 'type',
27 | // set to true to search anywhere in the field, which cannot use an index.
28 | matchAll: true, // 'ba' will match 'bar' and 'baz' first, then 'abacus'
29 | template: Template.clientCollectionPill
30 | }
31 | ]
32 | }
33 | }
34 | });
35 |
36 | Template.pubsub.events({
37 | "autocompleteselect textarea": function(e, t, doc) {
38 | console.log("selected ", doc);
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/examples/pubsublocal/client/options.html:
--------------------------------------------------------------------------------
1 |
2 | Options
3 |
4 |
5 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/pubsublocal/client/options.js:
--------------------------------------------------------------------------------
1 | Session.setDefault("position", "top");
2 |
3 | Template.options.helpers({
4 | position: function(arg) {
5 | return Session.equals("position", arg);
6 | }
7 | });
8 |
9 | Template.options.events({
10 | "change input": function(e, t) {
11 | Session.set(e.target.name, e.target.value);
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/examples/pubsublocal/client/pubsublocal.html:
--------------------------------------------------------------------------------
1 |
The server contains 160,000 records - all the 4-letter words that can be typed using the letters 'a' through 't'
26 |That is, from 'aaaa' to 'tttt'. Letters in the 'uvwxyz' set will produce an autocomplete miss.
27 |The trigger character is '@'. Matches start from the beginning of the string and are case sensitive to take advantage of a server index.
28 | 29 |There's also a client-only collection. Type '!' then some fruit names.
31 | 32 |
38 |
39 | @a => aaaa, aaab, ..., aaat, aaba, aabb, ..., aabj
40 | @j => jaaa, jaab, ..., jaat, jaba, jabb, ..., jabj
41 | @z => [nothing]
42 | @aaa => aaaa, aaab, ..., aaat
43 | @ttt => ttta, tttb, ..., tttt
44 | @mete => mete
45 | @meteor => [nothing]
46 |
47 |
48 |
49 |
50 |
51 | {{name}} ({{_id}})
52 |
53 |
54 |
55 | {{type}} ({{_id}})
56 |
57 |
58 |
59 | Nothing found on the server for {{getFilter}}!
60 |
61 |
62 |
--------------------------------------------------------------------------------
/examples/pubsublocal/client/single.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Autocompletion using a single collection has slightly different behavior than using multiple tokens; as it's designed to replace a dropdown or select:
6 | 7 |Type something from the following list:
19 | 20 |{{#each legends}}{{legend}} 21 | {{/each}}22 | 23 | 24 | 25 | {{year}} L{{code}} {{legend}} 26 | 27 | -------------------------------------------------------------------------------- /examples/pubsublocal/client/single.js: -------------------------------------------------------------------------------- 1 | StandardLegends = new Mongo.Collection(null); 2 | 3 | Template.single.helpers({ 4 | settings: function() { 5 | return { 6 | position: Session.get("position"), 7 | limit: 10, 8 | rules: [ 9 | { 10 | // token: '', 11 | collection: StandardLegends, 12 | field: 'legend', 13 | matchAll: true, 14 | template: Template.standardLegends 15 | } 16 | ] 17 | }; 18 | }, 19 | legends: function() { 20 | return StandardLegends.find(); 21 | } 22 | }); 23 | 24 | [ 25 | { 26 | legend: '110° HOT WATER RETURN', 27 | code: '355', 28 | year: '2007', 29 | color: 'White', 30 | bg: 'Green' 31 | }, 32 | { 33 | legend: '110° HOT WATER RETURN', 34 | code: '360', 35 | year: '1996', 36 | color: 'Black', 37 | bg: 'Yellow' 38 | }, 39 | { 40 | legend: '110° HOT WATER SUPPLY', 41 | code: '361', 42 | year: '2007', 43 | color: 'White', 44 | bg: 'Green' 45 | }, 46 | { 47 | legend: '110° HOT WATER SUPPLY', 48 | code: '356', 49 | year: '1996', 50 | color: 'Black', 51 | bg: 'Yellow' 52 | }, 53 | { 54 | legend: '140° HOT WATER RETURN', 55 | code: '357', 56 | year: '2007', 57 | color: 'White', 58 | bg: 'Green' 59 | }, 60 | { 61 | legend: '140° HOT WATER RETURN', 62 | code: '362', 63 | year: '1996', 64 | color: 'Black', 65 | bg: 'Yellow' 66 | }, 67 | { 68 | legend: '140° HOT WATER SUPPLY', 69 | code: '364', 70 | year: '2007', 71 | color: 'White', 72 | bg: 'Green' 73 | }, 74 | { 75 | legend: '140° HOT WATER SUPPLY', 76 | code: '358', 77 | year: '1996', 78 | color: 'Black', 79 | bg: 'Yellow' 80 | }, 81 | { 82 | legend: 'ACID', 83 | code: '100', 84 | year: '2007', 85 | color: 'Black', 86 | bg: 'Orange' 87 | }, 88 | { 89 | legend: 'ACID', 90 | code: '108', 91 | year: '1996', 92 | color: 'Black', 93 | bg: 'Yellow' 94 | }, 95 | { 96 | legend: 'ACID VENT', 97 | code: '102', 98 | year: '2007', 99 | color: 'Black', 100 | bg: 'Orange' 101 | }, 102 | { 103 | legend: 'ACID VENT', 104 | code: '106', 105 | year: '1996', 106 | color: 'Black', 107 | bg: 'Yellow' 108 | }, 109 | { 110 | legend: 'ACID WASTE', 111 | code: '105', 112 | year: '2007', 113 | color: 'Black', 114 | bg: 'Orange' 115 | }, 116 | { 117 | legend: 'ACID WASTE', 118 | code: '107', 119 | year: '1996', 120 | color: 'Black', 121 | bg: 'Yellow' 122 | }, 123 | { 124 | legend: 'AIR', 125 | code: '111', 126 | year: '2007', 127 | color: 'White', 128 | bg: 'Blue' 129 | }, 130 | { 131 | legend: 'AMMONIA', 132 | code: '115', 133 | year: '2007', 134 | color: 'Black', 135 | bg: 'Orange' 136 | }, 137 | { 138 | legend: 'AMMONIA', 139 | code: '117', 140 | year: '1996', 141 | color: 'Black', 142 | bg: 'Yellow' 143 | }, 144 | { 145 | legend: 'ARGON', 146 | code: '118', 147 | year: '2007', 148 | color: 'White', 149 | bg: 'Green' 150 | }, 151 | { 152 | legend: 'ASBESTOS FREE', 153 | code: '119', 154 | year: '2007', 155 | color: 'White', 156 | bg: 'Blue' 157 | }, 158 | { 159 | legend: 'BOILER BLOW DOWN', 160 | code: '120', 161 | year: '2007', 162 | color: 'White', 163 | bg: 'Green' 164 | }, 165 | { 166 | legend: 'BOILER FEED WATER', 167 | code: '121', 168 | year: '2007', 169 | color: 'White', 170 | bg: 'Green' 171 | }, 172 | { 173 | legend: 'CARBON DIOXIDE', 174 | code: '122', 175 | year: '2007', 176 | color: 'Black', 177 | bg: 'Yellow' 178 | }, 179 | { 180 | legend: 'CARBON DIOXIDE', 181 | code: '124', 182 | year: '2007', 183 | color: 'White', 184 | bg: 'Silver' 185 | }, 186 | { 187 | legend: 'FREE FOOD', 188 | code: '42', 189 | year: '2014', 190 | color: 'Red', 191 | bg: 'White' 192 | }, 193 | { 194 | legend: '', 195 | code: '', 196 | year: '', 197 | color: '', 198 | bg: '' 199 | } 200 | ].forEach(function (obj) { 201 | StandardLegends.insert(obj); 202 | }); 203 | -------------------------------------------------------------------------------- /examples/pubsublocal/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEPLOY_HOSTNAME=galaxy.meteor.com meteor deploy autocomplete.meteorapp.com --settings settings.json 3 | -------------------------------------------------------------------------------- /examples/pubsublocal/lib/collections.js: -------------------------------------------------------------------------------- 1 | BigCollection = new Mongo.Collection('bigcollection'); 2 | -------------------------------------------------------------------------------- /examples/pubsublocal/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-autocomplete-example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime": { 8 | "version": "7.11.2", 9 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", 10 | "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", 11 | "requires": { 12 | "regenerator-runtime": "^0.13.4" 13 | } 14 | }, 15 | "jquery": { 16 | "version": "3.5.1", 17 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", 18 | "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" 19 | }, 20 | "meteor-node-stubs": { 21 | "version": "1.0.1", 22 | "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.0.1.tgz", 23 | "integrity": "sha512-I4PE/z7eAl45XEsebHA4pcQbgjqEdK3EBGgiUoIZBi3bMQcMq6blLWZo+WdtK4Or9X4NJOiYWw4GmHiubr3egA==", 24 | "requires": { 25 | "assert": "^1.4.1", 26 | "browserify-zlib": "^0.2.0", 27 | "buffer": "^5.2.1", 28 | "console-browserify": "^1.1.0", 29 | "constants-browserify": "^1.0.0", 30 | "crypto-browserify": "^3.12.0", 31 | "domain-browser": "^1.2.0", 32 | "events": "^3.0.0", 33 | "https-browserify": "^1.0.0", 34 | "os-browserify": "^0.3.0", 35 | "path-browserify": "^1.0.0", 36 | "process": "^0.11.10", 37 | "punycode": "^2.1.1", 38 | "querystring-es3": "^0.2.1", 39 | "readable-stream": "^3.3.0", 40 | "stream-browserify": "^2.0.2", 41 | "stream-http": "^3.0.0", 42 | "string_decoder": "^1.2.0", 43 | "timers-browserify": "^2.0.10", 44 | "tty-browserify": "0.0.1", 45 | "url": "^0.11.0", 46 | "util": "^0.11.1", 47 | "vm-browserify": "^1.1.0" 48 | }, 49 | "dependencies": { 50 | "asn1.js": { 51 | "version": "4.10.1", 52 | "bundled": true, 53 | "requires": { 54 | "bn.js": "^4.0.0", 55 | "inherits": "^2.0.1", 56 | "minimalistic-assert": "^1.0.0" 57 | } 58 | }, 59 | "assert": { 60 | "version": "1.4.1", 61 | "bundled": true, 62 | "requires": { 63 | "util": "0.10.3" 64 | }, 65 | "dependencies": { 66 | "util": { 67 | "version": "0.10.3", 68 | "bundled": true, 69 | "requires": { 70 | "inherits": "2.0.1" 71 | } 72 | } 73 | } 74 | }, 75 | "base64-js": { 76 | "version": "1.3.0", 77 | "bundled": true 78 | }, 79 | "bn.js": { 80 | "version": "4.11.8", 81 | "bundled": true 82 | }, 83 | "brorand": { 84 | "version": "1.1.0", 85 | "bundled": true 86 | }, 87 | "browserify-aes": { 88 | "version": "1.2.0", 89 | "bundled": true, 90 | "requires": { 91 | "buffer-xor": "^1.0.3", 92 | "cipher-base": "^1.0.0", 93 | "create-hash": "^1.1.0", 94 | "evp_bytestokey": "^1.0.3", 95 | "inherits": "^2.0.1", 96 | "safe-buffer": "^5.0.1" 97 | } 98 | }, 99 | "browserify-cipher": { 100 | "version": "1.0.1", 101 | "bundled": true, 102 | "requires": { 103 | "browserify-aes": "^1.0.4", 104 | "browserify-des": "^1.0.0", 105 | "evp_bytestokey": "^1.0.0" 106 | } 107 | }, 108 | "browserify-des": { 109 | "version": "1.0.2", 110 | "bundled": true, 111 | "requires": { 112 | "cipher-base": "^1.0.1", 113 | "des.js": "^1.0.0", 114 | "inherits": "^2.0.1", 115 | "safe-buffer": "^5.1.2" 116 | } 117 | }, 118 | "browserify-rsa": { 119 | "version": "4.0.1", 120 | "bundled": true, 121 | "requires": { 122 | "bn.js": "^4.1.0", 123 | "randombytes": "^2.0.1" 124 | } 125 | }, 126 | "browserify-sign": { 127 | "version": "4.0.4", 128 | "bundled": true, 129 | "requires": { 130 | "bn.js": "^4.1.1", 131 | "browserify-rsa": "^4.0.0", 132 | "create-hash": "^1.1.0", 133 | "create-hmac": "^1.1.2", 134 | "elliptic": "^6.0.0", 135 | "inherits": "^2.0.1", 136 | "parse-asn1": "^5.0.0" 137 | } 138 | }, 139 | "browserify-zlib": { 140 | "version": "0.2.0", 141 | "bundled": true, 142 | "requires": { 143 | "pako": "~1.0.5" 144 | } 145 | }, 146 | "buffer": { 147 | "version": "5.2.1", 148 | "bundled": true, 149 | "requires": { 150 | "base64-js": "^1.0.2", 151 | "ieee754": "^1.1.4" 152 | } 153 | }, 154 | "buffer-xor": { 155 | "version": "1.0.3", 156 | "bundled": true 157 | }, 158 | "builtin-status-codes": { 159 | "version": "3.0.0", 160 | "bundled": true 161 | }, 162 | "cipher-base": { 163 | "version": "1.0.4", 164 | "bundled": true, 165 | "requires": { 166 | "inherits": "^2.0.1", 167 | "safe-buffer": "^5.0.1" 168 | } 169 | }, 170 | "console-browserify": { 171 | "version": "1.1.0", 172 | "bundled": true, 173 | "requires": { 174 | "date-now": "^0.1.4" 175 | } 176 | }, 177 | "constants-browserify": { 178 | "version": "1.0.0", 179 | "bundled": true 180 | }, 181 | "core-util-is": { 182 | "version": "1.0.2", 183 | "bundled": true 184 | }, 185 | "create-ecdh": { 186 | "version": "4.0.3", 187 | "bundled": true, 188 | "requires": { 189 | "bn.js": "^4.1.0", 190 | "elliptic": "^6.0.0" 191 | } 192 | }, 193 | "create-hash": { 194 | "version": "1.2.0", 195 | "bundled": true, 196 | "requires": { 197 | "cipher-base": "^1.0.1", 198 | "inherits": "^2.0.1", 199 | "md5.js": "^1.3.4", 200 | "ripemd160": "^2.0.1", 201 | "sha.js": "^2.4.0" 202 | } 203 | }, 204 | "create-hmac": { 205 | "version": "1.1.7", 206 | "bundled": true, 207 | "requires": { 208 | "cipher-base": "^1.0.3", 209 | "create-hash": "^1.1.0", 210 | "inherits": "^2.0.1", 211 | "ripemd160": "^2.0.0", 212 | "safe-buffer": "^5.0.1", 213 | "sha.js": "^2.4.8" 214 | } 215 | }, 216 | "crypto-browserify": { 217 | "version": "3.12.0", 218 | "bundled": true, 219 | "requires": { 220 | "browserify-cipher": "^1.0.0", 221 | "browserify-sign": "^4.0.0", 222 | "create-ecdh": "^4.0.0", 223 | "create-hash": "^1.1.0", 224 | "create-hmac": "^1.1.0", 225 | "diffie-hellman": "^5.0.0", 226 | "inherits": "^2.0.1", 227 | "pbkdf2": "^3.0.3", 228 | "public-encrypt": "^4.0.0", 229 | "randombytes": "^2.0.0", 230 | "randomfill": "^1.0.3" 231 | } 232 | }, 233 | "date-now": { 234 | "version": "0.1.4", 235 | "bundled": true 236 | }, 237 | "des.js": { 238 | "version": "1.0.0", 239 | "bundled": true, 240 | "requires": { 241 | "inherits": "^2.0.1", 242 | "minimalistic-assert": "^1.0.0" 243 | } 244 | }, 245 | "diffie-hellman": { 246 | "version": "5.0.3", 247 | "bundled": true, 248 | "requires": { 249 | "bn.js": "^4.1.0", 250 | "miller-rabin": "^4.0.0", 251 | "randombytes": "^2.0.0" 252 | } 253 | }, 254 | "domain-browser": { 255 | "version": "1.2.0", 256 | "bundled": true 257 | }, 258 | "elliptic": { 259 | "version": "6.5.3", 260 | "bundled": true, 261 | "requires": { 262 | "bn.js": "^4.4.0", 263 | "brorand": "^1.0.1", 264 | "hash.js": "^1.0.0", 265 | "hmac-drbg": "^1.0.0", 266 | "inherits": "^2.0.1", 267 | "minimalistic-assert": "^1.0.0", 268 | "minimalistic-crypto-utils": "^1.0.0" 269 | } 270 | }, 271 | "events": { 272 | "version": "3.0.0", 273 | "bundled": true 274 | }, 275 | "evp_bytestokey": { 276 | "version": "1.0.3", 277 | "bundled": true, 278 | "requires": { 279 | "md5.js": "^1.3.4", 280 | "safe-buffer": "^5.1.1" 281 | } 282 | }, 283 | "hash-base": { 284 | "version": "3.0.4", 285 | "bundled": true, 286 | "requires": { 287 | "inherits": "^2.0.1", 288 | "safe-buffer": "^5.0.1" 289 | } 290 | }, 291 | "hash.js": { 292 | "version": "1.1.7", 293 | "bundled": true, 294 | "requires": { 295 | "inherits": "^2.0.3", 296 | "minimalistic-assert": "^1.0.1" 297 | }, 298 | "dependencies": { 299 | "inherits": { 300 | "version": "2.0.3", 301 | "bundled": true 302 | } 303 | } 304 | }, 305 | "hmac-drbg": { 306 | "version": "1.0.1", 307 | "bundled": true, 308 | "requires": { 309 | "hash.js": "^1.0.3", 310 | "minimalistic-assert": "^1.0.0", 311 | "minimalistic-crypto-utils": "^1.0.1" 312 | } 313 | }, 314 | "https-browserify": { 315 | "version": "1.0.0", 316 | "bundled": true 317 | }, 318 | "ieee754": { 319 | "version": "1.1.13", 320 | "bundled": true 321 | }, 322 | "inherits": { 323 | "version": "2.0.1", 324 | "bundled": true 325 | }, 326 | "isarray": { 327 | "version": "1.0.0", 328 | "bundled": true 329 | }, 330 | "md5.js": { 331 | "version": "1.3.5", 332 | "bundled": true, 333 | "requires": { 334 | "hash-base": "^3.0.0", 335 | "inherits": "^2.0.1", 336 | "safe-buffer": "^5.1.2" 337 | } 338 | }, 339 | "miller-rabin": { 340 | "version": "4.0.1", 341 | "bundled": true, 342 | "requires": { 343 | "bn.js": "^4.0.0", 344 | "brorand": "^1.0.1" 345 | } 346 | }, 347 | "minimalistic-assert": { 348 | "version": "1.0.1", 349 | "bundled": true 350 | }, 351 | "minimalistic-crypto-utils": { 352 | "version": "1.0.1", 353 | "bundled": true 354 | }, 355 | "os-browserify": { 356 | "version": "0.3.0", 357 | "bundled": true 358 | }, 359 | "pako": { 360 | "version": "1.0.10", 361 | "bundled": true 362 | }, 363 | "parse-asn1": { 364 | "version": "5.1.4", 365 | "bundled": true, 366 | "requires": { 367 | "asn1.js": "^4.0.0", 368 | "browserify-aes": "^1.0.0", 369 | "create-hash": "^1.1.0", 370 | "evp_bytestokey": "^1.0.0", 371 | "pbkdf2": "^3.0.3", 372 | "safe-buffer": "^5.1.1" 373 | } 374 | }, 375 | "path-browserify": { 376 | "version": "1.0.0", 377 | "bundled": true 378 | }, 379 | "pbkdf2": { 380 | "version": "3.0.17", 381 | "bundled": true, 382 | "requires": { 383 | "create-hash": "^1.1.2", 384 | "create-hmac": "^1.1.4", 385 | "ripemd160": "^2.0.1", 386 | "safe-buffer": "^5.0.1", 387 | "sha.js": "^2.4.8" 388 | } 389 | }, 390 | "process": { 391 | "version": "0.11.10", 392 | "bundled": true 393 | }, 394 | "process-nextick-args": { 395 | "version": "2.0.0", 396 | "bundled": true 397 | }, 398 | "public-encrypt": { 399 | "version": "4.0.3", 400 | "bundled": true, 401 | "requires": { 402 | "bn.js": "^4.1.0", 403 | "browserify-rsa": "^4.0.0", 404 | "create-hash": "^1.1.0", 405 | "parse-asn1": "^5.0.0", 406 | "randombytes": "^2.0.1", 407 | "safe-buffer": "^5.1.2" 408 | } 409 | }, 410 | "punycode": { 411 | "version": "2.1.1", 412 | "bundled": true 413 | }, 414 | "querystring": { 415 | "version": "0.2.0", 416 | "bundled": true 417 | }, 418 | "querystring-es3": { 419 | "version": "0.2.1", 420 | "bundled": true 421 | }, 422 | "randombytes": { 423 | "version": "2.1.0", 424 | "bundled": true, 425 | "requires": { 426 | "safe-buffer": "^5.1.0" 427 | } 428 | }, 429 | "randomfill": { 430 | "version": "1.0.4", 431 | "bundled": true, 432 | "requires": { 433 | "randombytes": "^2.0.5", 434 | "safe-buffer": "^5.1.0" 435 | } 436 | }, 437 | "readable-stream": { 438 | "version": "3.3.0", 439 | "bundled": true, 440 | "requires": { 441 | "inherits": "^2.0.3", 442 | "string_decoder": "^1.1.1", 443 | "util-deprecate": "^1.0.1" 444 | }, 445 | "dependencies": { 446 | "inherits": { 447 | "version": "2.0.3", 448 | "bundled": true 449 | } 450 | } 451 | }, 452 | "ripemd160": { 453 | "version": "2.0.2", 454 | "bundled": true, 455 | "requires": { 456 | "hash-base": "^3.0.0", 457 | "inherits": "^2.0.1" 458 | } 459 | }, 460 | "safe-buffer": { 461 | "version": "5.1.2", 462 | "bundled": true 463 | }, 464 | "setimmediate": { 465 | "version": "1.0.5", 466 | "bundled": true 467 | }, 468 | "sha.js": { 469 | "version": "2.4.11", 470 | "bundled": true, 471 | "requires": { 472 | "inherits": "^2.0.1", 473 | "safe-buffer": "^5.0.1" 474 | } 475 | }, 476 | "stream-browserify": { 477 | "version": "2.0.2", 478 | "bundled": true, 479 | "requires": { 480 | "inherits": "~2.0.1", 481 | "readable-stream": "^2.0.2" 482 | }, 483 | "dependencies": { 484 | "readable-stream": { 485 | "version": "2.3.6", 486 | "bundled": true, 487 | "requires": { 488 | "core-util-is": "~1.0.0", 489 | "inherits": "~2.0.3", 490 | "isarray": "~1.0.0", 491 | "process-nextick-args": "~2.0.0", 492 | "safe-buffer": "~5.1.1", 493 | "string_decoder": "~1.1.1", 494 | "util-deprecate": "~1.0.1" 495 | }, 496 | "dependencies": { 497 | "inherits": { 498 | "version": "2.0.3", 499 | "bundled": true 500 | } 501 | } 502 | }, 503 | "string_decoder": { 504 | "version": "1.1.1", 505 | "bundled": true, 506 | "requires": { 507 | "safe-buffer": "~5.1.0" 508 | } 509 | } 510 | } 511 | }, 512 | "stream-http": { 513 | "version": "3.0.0", 514 | "bundled": true, 515 | "requires": { 516 | "builtin-status-codes": "^3.0.0", 517 | "inherits": "^2.0.1", 518 | "readable-stream": "^3.0.6", 519 | "xtend": "^4.0.0" 520 | } 521 | }, 522 | "string_decoder": { 523 | "version": "1.2.0", 524 | "bundled": true, 525 | "requires": { 526 | "safe-buffer": "~5.1.0" 527 | } 528 | }, 529 | "timers-browserify": { 530 | "version": "2.0.10", 531 | "bundled": true, 532 | "requires": { 533 | "setimmediate": "^1.0.4" 534 | } 535 | }, 536 | "tty-browserify": { 537 | "version": "0.0.1", 538 | "bundled": true 539 | }, 540 | "url": { 541 | "version": "0.11.0", 542 | "bundled": true, 543 | "requires": { 544 | "punycode": "1.3.2", 545 | "querystring": "0.2.0" 546 | }, 547 | "dependencies": { 548 | "punycode": { 549 | "version": "1.3.2", 550 | "bundled": true 551 | } 552 | } 553 | }, 554 | "util": { 555 | "version": "0.11.1", 556 | "bundled": true, 557 | "requires": { 558 | "inherits": "2.0.3" 559 | }, 560 | "dependencies": { 561 | "inherits": { 562 | "version": "2.0.3", 563 | "bundled": true 564 | } 565 | } 566 | }, 567 | "util-deprecate": { 568 | "version": "1.0.2", 569 | "bundled": true 570 | }, 571 | "vm-browserify": { 572 | "version": "1.1.0", 573 | "bundled": true 574 | }, 575 | "xtend": { 576 | "version": "4.0.1", 577 | "bundled": true 578 | } 579 | } 580 | }, 581 | "regenerator-runtime": { 582 | "version": "0.13.7", 583 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", 584 | "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" 585 | } 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /examples/pubsublocal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-autocomplete-example", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@babel/runtime": "^7.12.1", 6 | "jquery": "^3.5.1", 7 | "meteor-node-stubs": "^1.0.1" 8 | }, 9 | "scripts": { 10 | "start": "meteor run" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/pubsublocal/packages/autocomplete: -------------------------------------------------------------------------------- 1 | ../../.. -------------------------------------------------------------------------------- /examples/pubsublocal/server/server.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function () { 2 | if (!BigCollection.find().count()) { 3 | // Create a "large" collection with a series of records that area easy to 4 | // predict by a human, but not continuous, so that only some searches will 5 | // match. For example, all 4-letter words that can be typed with the 20 6 | // letters from 'a' to 't'. Furthermore, stuff them in the database in a 7 | // non-alphabetical order, to test how sorting works. 8 | var someLetters = 'tsrqponmlkjihgfedcba'.split(''); 9 | for (var i1 = 0; i1 < someLetters.length; i1++) { 10 | for (var i2 = 0; i2 < someLetters.length; i2++) { 11 | for (var i3 = 0; i3 < someLetters.length; i3++) { 12 | for (var i4 = 0; i4 < someLetters.length; i4++) { 13 | BigCollection.insert({ 14 | _id: i1.toString() + '-' + i2.toString() + '-' + i3.toString() + '-' + i4.toString(), 15 | name: someLetters[i1]+someLetters[i2]+someLetters[i3]+someLetters[i4] 16 | }) 17 | } 18 | } 19 | } 20 | } 21 | } 22 | 23 | // Create an index on the name field of BigCollection 24 | BigCollection._ensureIndex({name: 1}); 25 | }); 26 | 27 | // don't publish anything - the out-of-the-box server code will take care of that 28 | -------------------------------------------------------------------------------- /examples/pubsublocal/upload-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # from http://stackoverflow.com/q/18883103/586086 3 | CMD=`meteor mongo -U autocomplete.meteor.com | tail -1 | sed 's_mongodb://\([a-z0-9\-]*\):\([a-f0-9\-]*\)@\(.*\)/\(.*\)_mongorestore -u \1 -p \2 -h \3 -d \4_'` 4 | echo $CMD 5 | -------------------------------------------------------------------------------- /inputs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{> autocompleteContainer}} 4 | 5 | 6 | 7 | 8 | {{> autocompleteContainer}} 9 | 10 | 11 | 12 | {{#if isShowing}} 13 |