├── .github
└── dependabot.yml
├── .gitignore
├── .jshintrc
├── .npmignore
├── LICENSE
├── README.md
├── config-sample.js
├── gulpfile.js
├── index.html
├── package-lock.json
├── package.json
└── src
├── ajax.js
├── client.js
├── index.css
├── index.js
├── jquery.js
├── search-box.js
└── util.js
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: weekly
7 | time: '04:35'
8 | timezone: America/Los_Angeles
9 | open-pull-requests-limit: 10
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # When adding new items here, don't forget to add them to .npmignore as well!
2 |
3 | .DS_Store
4 | *.sublime-project
5 | *.sublime-workspace
6 | node_modules
7 | npm-debug.log
8 | config.js
9 | dist/
10 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "browser": true,
3 | "browserify": true,
4 | "globalstrict": true,
5 | "globals": {
6 | "domainr": true,
7 | "module": true,
8 | "require": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # This should be identical to .gitignore except we don't want to ignore the dist directory for npm.
2 | # Also npm automatically ignores node_modules and npm-debug.log.
3 |
4 | *.sublime-project
5 | *.sublime-workspace
6 | config.js
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 nb.io, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Domainr Search Box
2 |
3 | Drop-in [instant domain search](https://domainr.com/) for your site.
4 |
5 | ## Installation
6 |
7 | Make sure you have [Node.js](https://nodejs.org/) and [NPM](https://www.npmjs.com/) installed.
8 |
9 | Run `npm install domainr-search-box` to create a local copy of the module.
10 |
11 | ## Usage
12 |
13 | From the `dist/` directory, include `domainr-search-box.min.js` and `domainr-search-box.css` on your page.
14 |
15 | Add an empty `div` underneath your `input`; the Domainr Search Box will fill it with results.
16 |
17 | To authenticate, use the API [for free via RapidAPI](https://rapidapi.com/domainr/api/Domainr) with your RapidAPI API key, or [contact us](mailto:sales@domainr.com) regarding high-volume usage.
18 |
19 | ### Plain JavaScript
20 |
21 | ```javascript
22 | var box = new domainr.SearchBox({
23 | mashapeKey: yourRapidAPIKey, // your RapidAPI API key
24 | // clientId: yourClientId, // your high-volume clientId; not needed if using RapidAPI
25 | observe: yourInputElement,
26 | renderTo: yourResultsElement,
27 | onSelect: function(result) { ... }
28 | });
29 | ```
30 |
31 | ### jQuery
32 |
33 | ```javascript
34 | $('input#search')
35 | .domainrSearchBox({
36 | mashapeKey: yourRapidAPIKey, // your RapidAPI API key
37 | clientId: yourClientId, // or alternatively your Client ID; not needed if using RapidAPI
38 | renderTo: yourResultsElementOrSelector,
39 | onSelect: function(result) { ... }
40 | });
41 | ```
42 |
43 | ## Reference
44 |
45 | ### Plain JavaScript
46 |
47 | #### SearchBox
48 |
49 | Create a `domainr.SearchBox` with `new domainr.SearchBox(options)`. The `options` argument is an object with the following possible properties:
50 |
51 | * `clientId`: Either this or `mashapeKey` are required.
52 | * `mashapeKey`: Either this or `clientId` are required.
53 | * `observe`: The DOM element to observe.
54 | * `renderTo`: The DOM element to contain the rendered autocomplete.
55 | * `renderWith`: A function that will render the autocomplete. It will receive a state object.
56 | * `limit`: A number for the max number of results to display. Optional; default = 20.
57 | * `registrar`: Registrar or Registry domain name, to limit search results to domains supported by a registrar or registry. e.g. `namecheap.com` or `donuts.co`
58 | * `defaults`: Include the given default zones (an array of strings) in your search, e.g. `["coffee", "cafe", "organic"]`. Results will be sorted according to the order of this array.
59 | * `searchDelay`: How many milliseconds after keyboard entry before the search is made (to avoid redundant network traffic). Optional; default = `250`.
60 | * `onSelect`: An optional function to be called when the user selects a domain (if omitted, a new window will open with a recommended registrar for that result). Receives a single object with these properties:
61 | * `domain`
62 | * `host`
63 | * `path`
64 | * `registerURL`
65 | * `status`
66 | * `subdomain`
67 | * `zone`
68 |
69 | #### Client
70 |
71 | Create a `domainr.Client` with `new domainr.Client(options)`. The `options` argument is an object with the following possible properties:
72 |
73 | * `clientId`: Either this or `mashapeKey` are required.
74 | * `mashapeKey`: Either this or clientId are required.
75 | * `baseURL`: Optional.
76 |
77 | The `domainr.Client` has the following methods:
78 |
79 | * `search(params, callback)`: Search with the given params and call the callback with the results upon completion. The `params` argument is an object with the following possible properties:
80 | * `defaults`: Comma-delimited string, e.g. `coffee,cafe,organic`
81 | * `location`: String, e.g. `de`
82 | * `query`: String.
83 | * `registrar`: String, e.g. `namecheap.com` or `donuts.co`
84 | * `status(domains, callback)`: Call the `status` API with the given domains (an array) and call the callback with the results upon completion.
85 | * `options(domain, callback)`: Call the `options` API with the given domain (a string) and call the callback with the results upon completion.
86 | * `zones(callback)`: Call the `zones` API and call the callback with the results upon completion.
87 | * `registerURL(domain, options)`: Returns a URL for registering the given domain (a string). The options parameter is an object; currently one property is accepted, `registrar`, to specify a specific registrar.
88 |
89 | ### jQuery
90 |
91 | Select the element you want to observe and call `.domainrSearchBox(options)` on it. Possible options include:
92 |
93 | * `clientId`: Either this or `mashapeKey` are required.
94 | * `mashapeKey`: Either this or `clientId` are required.
95 | * `renderTo`: Specification for which element to contain the rendered autocomplete. Can be anything jQuery will accept (selector, element, jQuery object).
96 | * `renderWith`: A function that will render the autocomplete. It will receive a state object.
97 | * `limit`: A number for the max number of results to display. Optional; default = 20.
98 | * `registrar`: Registrar or Registry domain name, to limit search results to domains supported by a registrar or registry. e.g. `namecheap.com` or `donuts.co`
99 | * `defaults`: Include the given default zones (an array of strings) in your search, e.g. `["coffee", "cafe", "organic"]`. Results will be sorted according to the order of this array.
100 | * `searchDelay`: How many milliseconds after keyboard entry before the search is made (to avoid redundant network traffic). Optional; default = 250.
101 | * `onSelect`: An optional function to be called when the user selects a domain (if omitted, a new window will open with a recommended registrar for that result). Receives a single object with these properties:
102 | * `domain`
103 | * `host`
104 | * `path`
105 | * `registerURL`
106 | * `status`
107 | * `subdomain`
108 | * `zone`
109 |
110 | ## Development
111 |
112 | You'll need to have [Node](https://nodejs.org/) and [Gulp](http://gulpjs.com/) installed.
113 |
114 | * `npm install -g gulp` to install Gulp.
115 | * `npm install` to install dependencies
116 | * `gulp build` to build the code.
117 | * `gulp watch` to watch the code, building on changes.
118 | * note: console output will appear when files are changed
119 | * `gulp` builds + watch, and runs the demo in a webpage.
120 |
121 | ### Publishing new versions
122 |
123 | - Always increment the version
124 | - `npm whoami` to see your current npm user
125 | - `npm publish` to publish it
126 |
127 | Note that the demo requires the code to be built. Also, you'll need to get a `mashapeKey` or `clientId` and set up a `config.js`; instructions are in `index.html`.
128 |
129 | © 2018 nb.io, LLC
130 |
--------------------------------------------------------------------------------
/config-sample.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | mashapeKey: 'YOUR RapidAPI KEY GOES HERE', // comment out, if using clientId
3 | // clientId: 'YOUR HIGH-VOLUME CLIENT KEY GOES HERE' // comment out, if using RapidAPI
4 | };
5 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* globals process, console */
2 | 'use strict';
3 |
4 | var browserify = require('browserify');
5 | var gulp = require('gulp');
6 | var source = require('vinyl-source-stream');
7 | var buffer = require('vinyl-buffer');
8 | var rename = require('gulp-rename');
9 | var uglify = require('gulp-uglify');
10 | var httpServer = require('http-server');
11 | var openBrowser = require('opener');
12 | var minifyCss = require('gulp-clean-css');
13 |
14 | var dest = './dist/';
15 | var basename = 'domainr-search-box';
16 | var jsName = basename + '.js';
17 |
18 | // ----------
19 | gulp.task('js', function () {
20 | return browserify('./src/index.js', { standalone: 'domainr' })
21 | .bundle()
22 | .pipe(source(jsName))
23 | .pipe(buffer())
24 | .pipe(gulp.dest(dest))
25 | .pipe(uglify())
26 | .pipe(rename({ extname: '.min.js' }))
27 | .pipe(gulp.dest(dest));
28 | });
29 |
30 | // ----------
31 | gulp.task('css', function () {
32 | return gulp.src('./src/index.css')
33 | .pipe(rename({ basename: basename }))
34 | .pipe(minifyCss({ compatibility: 'ie8' }))
35 | .pipe(gulp.dest(dest));
36 | });
37 |
38 | // ----------
39 | gulp.task('build', gulp.series(gulp.parallel('js', 'css')));
40 |
41 | // ----------
42 | gulp.task('watch', function() {
43 | var watcher = gulp.watch('src/*.*', gulp.parallel('build'));
44 | watcher.on('all', function (event, path, stats) {
45 | console.log('File ' + path + ' was ' + event + ', running tasks...');
46 | });
47 | })
48 |
49 | // ----------
50 | gulp.task('serve', function () {
51 | var port = process.env.PORT || 3100;
52 | var server = httpServer.createServer();
53 | return server.listen(port, 'localhost', function () {
54 | console.log('Server listening at http://localhost:' + port);
55 | openBrowser('http://localhost:' + port);
56 | });
57 | });
58 |
59 | gulp.task('default', gulp.parallel('serve', 'watch'));
60 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Domainr Search Demo
6 |
7 |
8 |
9 |
10 |
19 |
20 |
21 |
22 |
Simple Domainr Search Demo
23 |
24 |
25 |
To make this demo work, make a config.js with your clientId or mashapeKey and put it in the same directory. See config-sample.js for an example.
26 |
27 |
28 |
29 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "domainr-search-box",
3 | "version": "0.0.31",
4 | "description": "Domainr instant search box for your site",
5 | "homepage": "https://github.com/domainr/domainr-search-box",
6 | "repository": "https://github.com/domainr/domainr-search-box.git",
7 | "bugs": {
8 | "url": "https://github.com/domainr/domainr-search-box/issues",
9 | "email": "bugs+search-box@domainr.com"
10 | },
11 | "licenses": [
12 | {
13 | "type": "MIT",
14 | "url": "https://github.com/domainr/domainr-search-box/blob/main/LICENSE"
15 | }
16 | ],
17 | "contributors": [
18 | {
19 | "name": "Randy Reddig"
20 | },
21 | {
22 | "name": "Ian Gilman"
23 | },
24 | {
25 | "name": "Eric Case"
26 | },
27 | {
28 | "name": "Cameron Walters"
29 | }
30 | ],
31 | "keywords": [
32 | "jquery-plugin",
33 | "ecosystem:jquery",
34 | "domainr",
35 | "domain",
36 | "domains",
37 | "search",
38 | "domain names",
39 | "tld"
40 | ],
41 | "devDependencies": {
42 | "browserify": "^17.0.0",
43 | "graceful-fs": "^4.2.4",
44 | "gulp": "^4.0.2",
45 | "gulp-clean-css": "^4.2.0",
46 | "gulp-rename": "^2.0.0",
47 | "gulp-uglify": "^3.0.0",
48 | "http-server": "^14.0.0",
49 | "lodash": "^4.17.15",
50 | "minimatch": "^9.0.0",
51 | "natives": "^1.1.6",
52 | "opener": "^1.4.1",
53 | "vinyl-buffer": "^1.0.1",
54 | "vinyl-source-stream": "^2.0.0"
55 | },
56 | "scripts": {
57 | "prepare": "gulp build"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/ajax.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('./util');
4 |
5 | var sequence = 0;
6 |
7 | // Detect CORS support
8 | var cors = false;
9 | if ('withCredentials' in xhr()) {
10 | cors = true;
11 | }
12 |
13 | // ----------
14 | function getJSON(url, callback, failure) {
15 | if (!callback) {
16 | throw new Error('[domainr] Missing callback');
17 | }
18 |
19 | if (cors) {
20 | getCORS(url, callback, failure);
21 | return;
22 | }
23 |
24 | getJSONP(url, callback, failure);
25 | }
26 |
27 | // ----------
28 | // Minimal fallback for IE
29 | // http://toddmotto.com/writing-a-standalone-ajax-xhr-javascript-micro-library/
30 | function xhr() {
31 | try {
32 | var XHR = window.XMLHttpRequest || window.ActiveXObject;
33 | return new XHR('MSXML2.XMLHTTP.3.0');
34 | } catch (e) {
35 | return {};
36 | }
37 | }
38 |
39 | // ----------
40 | function getCORS(url, callback, failure) {
41 | failure = failure || function() {};
42 | var x = xhr();
43 |
44 | x.onreadystatechange = function() {
45 | var message;
46 |
47 | if (x.readyState != 4) {
48 | return;
49 | }
50 |
51 | if (x.status != 200) {
52 | message = 'Error fetching data: ' + x.responseText;
53 | util.error(message);
54 | failure({ message: message });
55 | return;
56 | }
57 |
58 | var result;
59 | try {
60 | result = JSON.parse(x.responseText);
61 | } catch (e) {
62 | message = 'Unable to parse data: ' + x.responseText + '; ' + e;
63 | util.error(message);
64 | failure({ message: message });
65 | return;
66 | }
67 |
68 | callback(result);
69 | };
70 |
71 | x.open('GET', url, true);
72 | x.send();
73 | }
74 |
75 | // ----------
76 | // You must provide a success callback, but the failure callback is optional.
77 | // For each call to getJSONP, you'll get at most 1 callback (either success or failure) call. If you
78 | // don't provide a failure callback, you might not receive a call at all (if the function doesn't
79 | // succeed). If you do provide the failure callback, you'll get exactly 1 call, whichever one is
80 | // appropriate to the result.
81 | function getJSONP(url, success, failure) {
82 | var script = document.createElement('script');
83 | script.async = true;
84 | var id = '_jsonp' + sequence++;
85 | var failureSent = false;
86 |
87 | var timeout = setTimeout(function() {
88 | var message = 'Timeout trying to retrieve ' + url;
89 | util.error(message);
90 | if (failure) {
91 | failure({ message: message });
92 |
93 | // Here we set a flag so we won't end up sending a success callback later if the result comes
94 | // through after the timeout.
95 | failureSent = true;
96 | }
97 | }, 5000);
98 |
99 | window[id] = function(data) {
100 | clearTimeout(timeout);
101 |
102 | setTimeout(function() {
103 | var scr = document.getElementById(id);
104 | scr.parentNode.removeChild(scr);
105 | window[id] = undefined;
106 | try {
107 | delete window[id];
108 | } catch (e) {}
109 | }, 0);
110 |
111 | if (!failureSent) {
112 | success(data);
113 | }
114 | };
115 |
116 | var c = url.indexOf('?') >= 0 ? '&' : '?';
117 | script.setAttribute('src', url + c + 'callback=' + id);
118 | script.id = id;
119 | var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
120 | head.insertBefore(script, head.firstChild);
121 | }
122 |
123 | // ----------
124 | module.exports = {
125 | getJSON: getJSON
126 | };
127 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var ajax = require('./ajax');
4 | var util = require('./util');
5 |
6 | function Client(options) {
7 | options = options || {};
8 | this.clientId = options.clientId;
9 | this.mashapeKey = options.mashapeKey;
10 | if (!this.clientId && !this.mashapeKey) {
11 | throw new Error('[domainr] Missing mashapeKey or clientId');
12 | }
13 | this.baseURL = options.baseURL || (this.clientId ? 'https://api.domainr.com/v2' : 'https://domainr.p.rapidapi.com/v2');
14 | }
15 |
16 | Client.prototype = {
17 | search: function(params, callback) {
18 | this._get('/search', this.searchParams(params), callback);
19 | },
20 |
21 | searchParams: function(p) {
22 | return util.extract(p, ['query', 'registrar', 'location', 'defaults']);
23 | },
24 |
25 | status: function(domains, callback) {
26 | var self = this;
27 |
28 | if (!domains) {
29 | throw new Error('[domainr] domains array is required');
30 | }
31 |
32 | util.uniq(domains);
33 |
34 | var output = {
35 | status: []
36 | };
37 |
38 | var completed = 0;
39 |
40 | var incrementCompleted = function() {
41 | completed++;
42 | if (completed === domains.length) {
43 | callback(output);
44 | }
45 | };
46 |
47 | var doOne = function(domain) {
48 | var params = {
49 | domain: domain
50 | };
51 |
52 | self._get('/status', params, function(result) {
53 | if (result && result.status && result.status[0]) {
54 | output.status.push(result.status[0]);
55 | } else {
56 | util.error('Empty status result', result);
57 | }
58 |
59 | incrementCompleted();
60 | }, incrementCompleted);
61 | };
62 |
63 | for (var i = 0; i < domains.length; i++) {
64 | doOne(domains[i]);
65 | }
66 | },
67 |
68 | options: function(domain, callback) {
69 | this._get('/options', {
70 | domain: domain,
71 | }, callback);
72 | },
73 |
74 | zones: function(callback) {
75 | this._get('/zones', {}, callback);
76 | },
77 |
78 | registerURL: function(domain, options) {
79 | var params = util.extract(options, ['registrar']);
80 | params.domain = domain;
81 | return this._url('/register', params);
82 | },
83 |
84 | _get: function(path, params, callback, failure) {
85 | var url = this.baseURL + path + '?' + util.qs(params || {}, this._key());
86 | ajax.getJSON(url, callback, failure);
87 | },
88 |
89 | _url: function (path, params) {
90 | return this.baseURL + path + '?' + util.qs(params || {}, this._key());
91 | },
92 |
93 | _key: function() {
94 | if (this.clientId) {
95 | return {
96 | client_id: this.clientId
97 | };
98 | }
99 |
100 | return {
101 | 'mashape-key': this.mashapeKey
102 | };
103 | }
104 | };
105 |
106 | module.exports = Client;
107 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | .domainr-results-container {
2 | position: relative;
3 | width: 100%;
4 | text-align: left;
5 | text-shadow: none;
6 | z-index: 99999;
7 | }
8 |
9 | .domainr-results {
10 | position: absolute;
11 | box-sizing: border-box;
12 | width: 100%;
13 | z-index: 2;
14 | border: 1px solid #ccc;
15 | padding: 0 1px;
16 | background: white;
17 | color: #666;
18 | box-shadow: 0 2px 4px 0 rgba(0,0,0,0.25);
19 | line-height: 1.25em;
20 | }
21 |
22 | .domainr-result {
23 | position: relative;
24 | cursor: pointer;
25 | margin: 1px 0;
26 | border-radius: 1px;
27 | padding: 2px 8px;
28 | }
29 |
30 | .domainr-result:after {
31 | float: right;
32 | opacity: 0.5;
33 | font-weight: normal;
34 | font-size: 60%;
35 | line-height: inherit;
36 | }
37 |
38 | .domainr-result:hover,
39 | .domainr-result.selected {
40 | background-color: #eee;
41 | }
42 |
43 | .domainr-results .active,
44 | .domainr-results .claimed,
45 | .domainr-results .reserved {
46 | color: #006699;
47 | }
48 |
49 | .domainr-results .reserved:after {
50 | content: "reserved";
51 | }
52 |
53 | .domainr-results .inactive:not(.reserved) {
54 | color: #009900;
55 | background: #eeffee;
56 | }
57 |
58 | .domainr-results .inactive:not(.reserved):hover,
59 | .domainr-results .inactive:not(.reserved).selected {
60 | background: #ddeedd;
61 | }
62 |
63 | .domainr-results .inactive:not(.reserved):not(.premium):after {
64 | content: "available";
65 | }
66 |
67 | .domainr-results .pending:not(.disallowed):not(.invalid):not(.unregistrable):not(.zone):not(.suffix):after {
68 | content: "new";
69 | }
70 |
71 | .domainr-results .priced,
72 | .domainr-results .transferable,
73 | .domainr-results .marketed {
74 | color: #009900;
75 | }
76 |
77 | .domainr-results .priced:after,
78 | .domainr-results .transferable:after,
79 | .domainr-results .marketed:after {
80 | content: "for sale";
81 | }
82 |
83 | .domainr-results .priced,
84 | .domainr-results .transferable {
85 | background: #eeffee;
86 | }
87 |
88 | .domainr-results .priced:hover,
89 | .domainr-results .transferable:hover,
90 | .domainr-results .priced.selected,
91 | .domainr-results .transferable.selected {
92 | background: #ddeedd;
93 | }
94 |
95 | .domainr-results .expiring,
96 | .domainr-results .deleting {
97 | color: #f79a0f;
98 | background-color: #fef4e7;
99 | }
100 |
101 | .domainr-results .expiring:hover,
102 | .domainr-results .deleting:hover,
103 | .domainr-results .expiring.selected,
104 | .domainr-results .deleting.selected {
105 | background-color: #f2e8dc;
106 | }
107 |
108 | .domainr-results .expiring:after,
109 | .domainr-results .deleting:after {
110 | content: "expiring";
111 | }
112 |
113 | .domainr-results .premium {
114 | color: #009900;
115 | background: #eeffee;
116 | }
117 |
118 | .domainr-results .premium:hover,
119 | .domainr-results .premium.selected {
120 | background: #ddeedd;
121 | }
122 |
123 | .domainr-results .premium:after {
124 | content: "premium";
125 | }
126 |
127 | .domainr-results .parked:not(.marketed):after {
128 | content: "parked";
129 | }
130 |
131 | .domainr-results .unregistrable,
132 | .domainr-results .disallowed,
133 | .domainr-results .invalid,
134 | .domainr-results .unknown {
135 | color: rgba(0, 0, 0, 0.3);
136 | }
137 |
138 | .domainr-results .tld,
139 | .domainr-results .zone,
140 | .domainr-results .registry,
141 | .domainr-results .registrar {
142 | font-weight: bold;
143 | color: #333333;
144 | }
145 |
146 | .domainr-results .zone span.domainr-result-domain:before {
147 | content: ".";
148 | display: inline-block;
149 | position: relative;
150 | left: -0.3em;
151 | width: 0;
152 | }
153 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var Client = require('./client');
4 | var SearchBox = require('./search-box');
5 | require('./jquery');
6 |
7 | module.exports = {
8 | Client: Client,
9 | SearchBox: SearchBox
10 | };
11 |
--------------------------------------------------------------------------------
/src/jquery.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var SearchBox = require('./search-box');
4 | var util = require('./util');
5 |
6 | if (window.jQuery) {
7 | var $ = window.jQuery;
8 |
9 | $.fn.domainrSearchBox = function(config) {
10 | this.each(function(i, el) {
11 | var searchBoxConfig = util.extract(config, ['clientId', 'mashapeKey',
12 | 'renderWith', 'limit', 'registrar', 'defaults', 'onSelect']);
13 |
14 | searchBoxConfig.observe = el;
15 |
16 | if (config.renderTo) {
17 | var $el = $(config.renderTo);
18 | if ($el.length > 0) {
19 | searchBoxConfig.renderTo = $el[0];
20 | }
21 | }
22 |
23 | var searchBox = new domainr.SearchBox(searchBoxConfig);
24 | });
25 |
26 | return this;
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/search-box.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var Client = require('./client');
4 | var util = require('./util');
5 |
6 | var SearchBox = function(options) {
7 | var self = this;
8 |
9 | options = options || {};
10 |
11 | if (!options.observe) {
12 | throw new Error('[domainr] "observe" is required');
13 | }
14 |
15 | this._client = new Client(util.extract(options, ['clientId', 'mashapeKey']));
16 | this._state = {
17 | query: '',
18 | results: [],
19 | limit: 20,
20 | selection: -1
21 | };
22 |
23 | this._seq = 0;
24 | this._last = 0;
25 | this._cache = {};
26 | this._listeners = {};
27 | this._searchTimeout = null;
28 | this._searchDelay = 250;
29 | this._renderer = defaultRenderer;
30 |
31 | this._in = options.observe;
32 | on(this._in, 'keyup', this._input, this);
33 | on(this._in, 'input', this._input, this);
34 | on(this._in, 'change', this._input, this);
35 | on(this._in, 'keydown', this._keydown, this);
36 |
37 | if (options.renderTo !== undefined) {
38 | this._out = options.renderTo;
39 | addClass(this._out, 'domainr-results-container');
40 | on(this._out, 'click', this._click, this);
41 | }
42 |
43 | if (options.renderWith !== undefined) {
44 | this._renderer = options.renderWith;
45 | }
46 |
47 | if (options.limit !== undefined) {
48 | this._state.limit = options.limit;
49 | }
50 |
51 | if (options.registrar !== undefined) {
52 | this._state.registrar = options.registrar;
53 | }
54 |
55 | if (options.defaults !== undefined) {
56 | this._state.defaults = options.defaults.join(',');
57 | }
58 |
59 | if (options.searchDelay !== undefined) {
60 | this._searchDelay = options.searchDelay;
61 | }
62 |
63 | if (options.onSelect !== undefined) {
64 | this._onSelect = options.onSelect;
65 | } else {
66 | this._onSelect = function(result) {
67 | self._in.value = result.domain;
68 | window.open(self._client.registerURL(result.domain));
69 | };
70 | }
71 |
72 | this._input(); // In case there's something already in the input
73 | };
74 |
75 | SearchBox.prototype = {
76 | _input: function() {
77 | if (this._state.query != this._in.value) {
78 | if (this._in.value === '') {
79 | this._state.results = [];
80 | this._update();
81 | return;
82 | }
83 | this._state.query = this._in.value;
84 | window.clearTimeout(this._searchTimeout);
85 | this._searchTimeout = window.setTimeout(this._search.bind(this), this._searchDelay);
86 | }
87 | },
88 |
89 | _keydown: function(event) {
90 | event = event || window.event;
91 | var handled = false;
92 |
93 | if (event.keyCode === 38) { // Up arrow
94 | handled = true;
95 | this._state.selection--;
96 | if (this._state.selection < 0) {
97 | this._state.selection = this._state.results.length - 1;
98 | }
99 |
100 | this._update();
101 | } else if (event.keyCode === 40) { // Down arrow
102 | handled = true;
103 | this._state.selection++;
104 | if (this._state.selection >= this._state.results.length) {
105 | this._state.selection = 0;
106 | }
107 |
108 | this._update();
109 | } else if (event.keyCode === 13) { // Enter key
110 | if (this._state.selection !== -1) {
111 | handled = true;
112 | this._choose(this._state.results[this._state.selection]);
113 | }
114 | }
115 |
116 | if (handled && event.preventDefault) {
117 | event.preventDefault();
118 | }
119 | },
120 |
121 | _click: function(event) {
122 | event = event || window.event;
123 | var rs = this._state.results;
124 | for (var e = event.target || event.srcElement; e && e != document; e = e.parentNode) {
125 | var d = e.getAttribute('data-domain');
126 | if (d) {
127 | for (var i = 0; i < rs.length; i++) {
128 | var r = rs[i];
129 | if (r.domain == d) {
130 | this._choose(r);
131 | return;
132 | }
133 | }
134 | }
135 | }
136 | },
137 |
138 | _render: function() {
139 | if (!this._out) {
140 | return;
141 | }
142 | this._out.innerHTML = this._renderer(this._state);
143 | return this;
144 | },
145 |
146 | _search: function() {
147 | var self = this;
148 | this._state.selection = -1;
149 |
150 | // Try cache first
151 | var key = util.qs(this._client.searchParams(this._state));
152 | var res = this._cache[key];
153 | if (res !== undefined) {
154 | this._state.results = res.results;
155 | this._update();
156 | return;
157 | }
158 |
159 | // Make network request
160 | var seq = this._seq++;
161 | this._client.search(this._state, function(res) {
162 | self._cache[key] = res;
163 | if (self._last > seq) {
164 | return;
165 | }
166 | self._last = seq;
167 | self._state.results = res.results;
168 | self._update();
169 | });
170 | },
171 |
172 | _update: function() {
173 | this._sort();
174 | this._limit();
175 | this._status();
176 | this._render();
177 | },
178 |
179 | _sort: function() {
180 | if (!this._state.defaults || !this._state.results) {
181 | return;
182 | }
183 |
184 | var defaults = this._state.defaults.split(',');
185 | this._state.results.sort(function(a, b) {
186 | var aIndex = util.indexOf(defaults, a.zone);
187 | if (aIndex === -1) {
188 | aIndex = defaults.length;
189 | }
190 |
191 | var bIndex = util.indexOf(defaults, b.zone);
192 | if (bIndex === -1) {
193 | bIndex = defaults.length;
194 | }
195 |
196 | if (aIndex !== bIndex) {
197 | return aIndex - bIndex;
198 | }
199 |
200 | return a.domain - b.domain;
201 | });
202 | },
203 |
204 | _limit: function() {
205 | if (this._state.limit >= 0 && this._state.results.length > this._state.limit) {
206 | this._state.results.length = this._state.limit;
207 | }
208 | },
209 |
210 | _status: function() {
211 | var self = this;
212 |
213 | // Extract domains without status
214 | var i;
215 | var d = [];
216 | var MAX_STATUS_DOMAINS = 10;
217 | var rs = this._state.results;
218 | for (i = 0; i < rs.length && d.length < MAX_STATUS_DOMAINS; i++) {
219 | var r = rs[i];
220 | r.status = this._cache[r.domain + ':status'] || r.status;
221 | if (!r.status) {
222 | r.status = 'unknown';
223 | d.push(r.domain);
224 | }
225 | }
226 |
227 | if (d.length === 0) {
228 | return;
229 | }
230 |
231 | util.uniq(d);
232 |
233 | var doOne = function(domain) {
234 | self._client.status([domain], function(res) {
235 | var s = res.status[0];
236 | if (s) {
237 | self._cache[s.domain + ':status'] = s.status;
238 | self._update();
239 | }
240 | });
241 | };
242 |
243 | for (i = 0; i < d.length; i++) {
244 | doOne(d[i]);
245 | }
246 | },
247 |
248 | _choose: function(result) {
249 | if (this._onSelect) {
250 | this._onSelect(result);
251 | }
252 | }
253 | };
254 |
255 | function defaultRenderer(state) {
256 | var rs = state.results;
257 | var l = rs.length;
258 | if (l === 0) {
259 | return '';
260 | }
261 | var h = [''];
262 | for (var i = 0; i < l; i++) {
263 | var r = rs[i];
264 |
265 | var classNames = [
266 | 'domainr-result',
267 | r.status
268 | ];
269 |
270 | if (state.selection === i) {
271 | classNames.push('selected');
272 | }
273 |
274 | h.push(
275 | '
' +
276 | '' +
277 | '' + r.host + '' +
278 | '' + r.subdomain + '' +
279 | '' + r.zone + '' +
280 | '' +
281 | '' + r.path + '' +
282 | '
'
283 | );
284 | }
285 | h.push('
');
286 | return h.join('');
287 | }
288 |
289 | function on(e, ev, cb, obj) {
290 | if (obj) {
291 | var original = cb;
292 | cb = function() {
293 | return original.apply(obj, arguments);
294 | };
295 | }
296 | if (e.addEventListener) {
297 | e.addEventListener(ev, cb, false);
298 | } else if (e.attachEvent) {
299 | e.attachEvent('on' + ev, cb);
300 | } else {
301 | e['on' + ev] = cb;
302 | }
303 | }
304 |
305 | function addClass(e, className) {
306 | if (e.classList) {
307 | e.classList.add(className);
308 | } else {
309 | e.className += ' ' + className;
310 | }
311 | }
312 |
313 | module.exports = SearchBox;
314 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var euc = encodeURIComponent;
4 |
5 | function extract(p, keys) {
6 | var x = {};
7 | if (p) {
8 | for (var i = 0; i < keys.length; i++) {
9 | var k = keys[i];
10 | if (p[k] !== undefined) {
11 | x[k] = p[k];
12 | }
13 | }
14 | }
15 | return x;
16 | }
17 |
18 | function qs() {
19 | var q = [];
20 | for (var i = 0; i < arguments.length; i++) {
21 | var p = arguments[i];
22 | for (var k in p) {
23 | q.push(euc(k) + '=' + euc(p[k]));
24 | }
25 | }
26 | return q.sort().join('&');
27 | }
28 |
29 | function error(message) {
30 | if (window.console && window.console.error) {
31 | window.console.error('[domainr] ' + message);
32 | }
33 | }
34 |
35 | function uniq(a) {
36 | if (!a) {
37 | return;
38 | }
39 |
40 | var i, j;
41 | for (i = 0; i < a.length; i++) {
42 | for (j = i + 1; j < a.length; j++) {
43 | if (a[i] === a[j]) {
44 | a.splice(j, 1);
45 | j--;
46 | }
47 | }
48 | }
49 | }
50 |
51 | function indexOf(a, v) {
52 | if (!a) {
53 | return -1;
54 | }
55 |
56 | var i;
57 | for (i = 0; i < a.length; i++) {
58 | if (a[i] === v) {
59 | return i;
60 | }
61 | }
62 |
63 | return -1;
64 | }
65 |
66 | module.exports = {
67 | euc: euc,
68 | extract: extract,
69 | qs: qs,
70 | error: error,
71 | uniq: uniq,
72 | indexOf: indexOf
73 | };
74 |
--------------------------------------------------------------------------------