Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
S
sifter
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Custom Issue Tracker
Custom Issue Tracker
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
angularjs
sifter
Commits
526723d5
Commit
526723d5
authored
Aug 03, 2018
by
bingchuan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[dev]0.4.x
parents
Pipeline
#75
failed with stages
Changes
2
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
494 additions
and
0 deletions
+494
-0
bower.json
bower.json
+23
-0
sifter.js
sifter.js
+471
-0
No files found.
bower.json
0 → 100644
View file @
526723d5
{
"name"
:
"sifter"
,
"keywords"
:
[
"search"
,
"filter"
,
"sift"
,
"data"
,
"results"
,
"match"
,
"sort"
,
"autocomplete"
],
"description"
:
"A library for textually searching arrays and hashes of objects by property (or multiple properties). Designed specifically for autocomplete."
,
"version"
:
"0.4.5"
,
"license"
:
"Apache License, Version 2.0"
,
"readmeFilename"
:
"README.md"
,
"repository"
:
{
"type"
:
"git"
,
"url"
:
"git://github.com/brianreavis/sifter.js.git"
},
"main"
:
[
"sifter.js"
],
"ignore"
:
[
"Makefile"
,
"test"
,
"lib"
,
"bin"
,
"benchmark"
,
"node_modules"
,
".travis.yml"
],
"dependencies"
:
{}
}
sifter.js
0 → 100644
View file @
526723d5
/**
* sifter.js
* Copyright (c) 2013 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
(
function
(
root
,
factory
)
{
if
(
typeof
define
===
'function'
&&
define
.
amd
)
{
define
(
factory
);
}
else
if
(
typeof
exports
===
'object'
)
{
module
.
exports
=
factory
();
}
else
{
root
.
Sifter
=
factory
();
}
}(
this
,
function
()
{
/**
* Textually searches arrays and hashes of objects
* by property (or multiple properties). Designed
* specifically for autocomplete.
*
* @constructor
* @param {array|object} items
* @param {object} items
*/
var
Sifter
=
function
(
items
,
settings
)
{
this
.
items
=
items
;
this
.
settings
=
settings
||
{
diacritics
:
true
};
};
/**
* Splits a search string into an array of individual
* regexps to be used to match results.
*
* @param {string} query
* @returns {array}
*/
Sifter
.
prototype
.
tokenize
=
function
(
query
)
{
query
=
trim
(
String
(
query
||
''
).
toLowerCase
());
if
(
!
query
||
!
query
.
length
)
return
[];
var
i
,
n
,
regex
,
letter
;
var
tokens
=
[];
var
words
=
query
.
split
(
/ +/
);
for
(
i
=
0
,
n
=
words
.
length
;
i
<
n
;
i
++
)
{
regex
=
escape_regex
(
words
[
i
]);
if
(
this
.
settings
.
diacritics
)
{
for
(
letter
in
DIACRITICS
)
{
if
(
DIACRITICS
.
hasOwnProperty
(
letter
))
{
regex
=
regex
.
replace
(
new
RegExp
(
letter
,
'g'
),
DIACRITICS
[
letter
]);
}
}
}
tokens
.
push
({
string
:
words
[
i
],
regex
:
new
RegExp
(
regex
,
'i'
)
});
}
return
tokens
;
};
/**
* Iterates over arrays and hashes.
*
* ```
* this.iterator(this.items, function(item, id) {
* // invoked for each item
* });
* ```
*
* @param {array|object} object
*/
Sifter
.
prototype
.
iterator
=
function
(
object
,
callback
)
{
var
iterator
;
if
(
is_array
(
object
))
{
iterator
=
Array
.
prototype
.
forEach
||
function
(
callback
)
{
for
(
var
i
=
0
,
n
=
this
.
length
;
i
<
n
;
i
++
)
{
callback
(
this
[
i
],
i
,
this
);
}
};
}
else
{
iterator
=
function
(
callback
)
{
for
(
var
key
in
this
)
{
if
(
this
.
hasOwnProperty
(
key
))
{
callback
(
this
[
key
],
key
,
this
);
}
}
};
}
iterator
.
apply
(
object
,
[
callback
]);
};
/**
* Returns a function to be used to score individual results.
*
* Good matches will have a higher score than poor matches.
* If an item is not a match, 0 will be returned by the function.
*
* @param {object|string} search
* @param {object} options (optional)
* @returns {function}
*/
Sifter
.
prototype
.
getScoreFunction
=
function
(
search
,
options
)
{
var
self
,
fields
,
tokens
,
token_count
;
self
=
this
;
search
=
self
.
prepareSearch
(
search
,
options
);
tokens
=
search
.
tokens
;
fields
=
search
.
options
.
fields
;
token_count
=
tokens
.
length
;
/**
* Calculates how close of a match the
* given value is against a search token.
*
* @param {mixed} value
* @param {object} token
* @return {number}
*/
var
scoreValue
=
function
(
value
,
token
)
{
var
score
,
pos
;
if
(
!
value
)
return
0
;
value
=
String
(
value
||
''
);
pos
=
value
.
search
(
token
.
regex
);
if
(
pos
===
-
1
)
return
0
;
score
=
token
.
string
.
length
/
value
.
length
;
if
(
pos
===
0
)
score
+=
0.5
;
return
score
;
};
/**
* Calculates the score of an object
* against the search query.
*
* @param {object} token
* @param {object} data
* @return {number}
*/
var
scoreObject
=
(
function
()
{
var
field_count
=
fields
.
length
;
if
(
!
field_count
)
{
return
function
()
{
return
0
;
};
}
if
(
field_count
===
1
)
{
return
function
(
token
,
data
)
{
return
scoreValue
(
data
[
fields
[
0
]],
token
);
};
}
return
function
(
token
,
data
)
{
for
(
var
i
=
0
,
sum
=
0
;
i
<
field_count
;
i
++
)
{
sum
+=
scoreValue
(
data
[
fields
[
i
]],
token
);
}
return
sum
/
field_count
;
};
})();
if
(
!
token_count
)
{
return
function
()
{
return
0
;
};
}
if
(
token_count
===
1
)
{
return
function
(
data
)
{
return
scoreObject
(
tokens
[
0
],
data
);
};
}
if
(
search
.
options
.
conjunction
===
'and'
)
{
return
function
(
data
)
{
var
score
;
for
(
var
i
=
0
,
sum
=
0
;
i
<
token_count
;
i
++
)
{
score
=
scoreObject
(
tokens
[
i
],
data
);
if
(
score
<=
0
)
return
0
;
sum
+=
score
;
}
return
sum
/
token_count
;
};
}
else
{
return
function
(
data
)
{
for
(
var
i
=
0
,
sum
=
0
;
i
<
token_count
;
i
++
)
{
sum
+=
scoreObject
(
tokens
[
i
],
data
);
}
return
sum
/
token_count
;
};
}
};
/**
* Returns a function that can be used to compare two
* results, for sorting purposes. If no sorting should
* be performed, `null` will be returned.
*
* @param {string|object} search
* @param {object} options
* @return function(a,b)
*/
Sifter
.
prototype
.
getSortFunction
=
function
(
search
,
options
)
{
var
i
,
n
,
self
,
field
,
fields
,
fields_count
,
multiplier
,
multipliers
,
get_field
,
implicit_score
,
sort
;
self
=
this
;
search
=
self
.
prepareSearch
(
search
,
options
);
sort
=
(
!
search
.
query
&&
options
.
sort_empty
)
||
options
.
sort
;
/**
* Fetches the specified sort field value
* from a search result item.
*
* @param {string} name
* @param {object} result
* @return {mixed}
*/
get_field
=
function
(
name
,
result
)
{
if
(
name
===
'$score'
)
return
result
.
score
;
return
self
.
items
[
result
.
id
][
name
];
};
// parse options
fields
=
[];
if
(
sort
)
{
for
(
i
=
0
,
n
=
sort
.
length
;
i
<
n
;
i
++
)
{
if
(
search
.
query
||
sort
[
i
].
field
!==
'$score'
)
{
fields
.
push
(
sort
[
i
]);
}
}
}
// the "$score" field is implied to be the primary
// sort field, unless it's manually specified
if
(
search
.
query
)
{
implicit_score
=
true
;
for
(
i
=
0
,
n
=
fields
.
length
;
i
<
n
;
i
++
)
{
if
(
fields
[
i
].
field
===
'$score'
)
{
implicit_score
=
false
;
break
;
}
}
if
(
implicit_score
)
{
fields
.
unshift
({
field
:
'$score'
,
direction
:
'desc'
});
}
}
else
{
for
(
i
=
0
,
n
=
fields
.
length
;
i
<
n
;
i
++
)
{
if
(
fields
[
i
].
field
===
'$score'
)
{
fields
.
splice
(
i
,
1
);
break
;
}
}
}
multipliers
=
[];
for
(
i
=
0
,
n
=
fields
.
length
;
i
<
n
;
i
++
)
{
multipliers
.
push
(
fields
[
i
].
direction
===
'desc'
?
-
1
:
1
);
}
// build function
fields_count
=
fields
.
length
;
if
(
!
fields_count
)
{
return
null
;
}
else
if
(
fields_count
===
1
)
{
field
=
fields
[
0
].
field
;
multiplier
=
multipliers
[
0
];
return
function
(
a
,
b
)
{
return
multiplier
*
cmp
(
get_field
(
field
,
a
),
get_field
(
field
,
b
)
);
};
}
else
{
return
function
(
a
,
b
)
{
var
i
,
result
,
a_value
,
b_value
,
field
;
for
(
i
=
0
;
i
<
fields_count
;
i
++
)
{
field
=
fields
[
i
].
field
;
result
=
multipliers
[
i
]
*
cmp
(
get_field
(
field
,
a
),
get_field
(
field
,
b
)
);
if
(
result
)
return
result
;
}
return
0
;
};
}
};
/**
* Parses a search query and returns an object
* with tokens and fields ready to be populated
* with results.
*
* @param {string} query
* @param {object} options
* @returns {object}
*/
Sifter
.
prototype
.
prepareSearch
=
function
(
query
,
options
)
{
if
(
typeof
query
===
'object'
)
return
query
;
options
=
extend
({},
options
);
var
option_fields
=
options
.
fields
;
var
option_sort
=
options
.
sort
;
var
option_sort_empty
=
options
.
sort_empty
;
if
(
option_fields
&&
!
is_array
(
option_fields
))
options
.
fields
=
[
option_fields
];
if
(
option_sort
&&
!
is_array
(
option_sort
))
options
.
sort
=
[
option_sort
];
if
(
option_sort_empty
&&
!
is_array
(
option_sort_empty
))
options
.
sort_empty
=
[
option_sort_empty
];
return
{
options
:
options
,
query
:
String
(
query
||
''
).
toLowerCase
(),
tokens
:
this
.
tokenize
(
query
),
total
:
0
,
items
:
[]
};
};
/**
* Searches through all items and returns a sorted array of matches.
*
* The `options` parameter can contain:
*
* - fields {string|array}
* - sort {array}
* - score {function}
* - filter {bool}
* - limit {integer}
*
* Returns an object containing:
*
* - options {object}
* - query {string}
* - tokens {array}
* - total {int}
* - items {array}
*
* @param {string} query
* @param {object} options
* @returns {object}
*/
Sifter
.
prototype
.
search
=
function
(
query
,
options
)
{
var
self
=
this
,
value
,
score
,
search
,
calculateScore
;
var
fn_sort
;
var
fn_score
;
search
=
this
.
prepareSearch
(
query
,
options
);
options
=
search
.
options
;
query
=
search
.
query
;
// generate result scoring function
fn_score
=
options
.
score
||
self
.
getScoreFunction
(
search
);
// perform search and sort
if
(
query
.
length
)
{
self
.
iterator
(
self
.
items
,
function
(
item
,
id
)
{
score
=
fn_score
(
item
);
if
(
options
.
filter
===
false
||
score
>
0
)
{
search
.
items
.
push
({
'score'
:
score
,
'id'
:
id
});
}
});
}
else
{
self
.
iterator
(
self
.
items
,
function
(
item
,
id
)
{
search
.
items
.
push
({
'score'
:
1
,
'id'
:
id
});
});
}
fn_sort
=
self
.
getSortFunction
(
search
,
options
);
if
(
fn_sort
)
search
.
items
.
sort
(
fn_sort
);
// apply limits
search
.
total
=
search
.
items
.
length
;
if
(
typeof
options
.
limit
===
'number'
)
{
search
.
items
=
search
.
items
.
slice
(
0
,
options
.
limit
);
}
return
search
;
};
// utilities
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var
cmp
=
function
(
a
,
b
)
{
if
(
typeof
a
===
'number'
&&
typeof
b
===
'number'
)
{
return
a
>
b
?
1
:
(
a
<
b
?
-
1
:
0
);
}
a
=
asciifold
(
String
(
a
||
''
));
b
=
asciifold
(
String
(
b
||
''
));
if
(
a
>
b
)
return
1
;
if
(
b
>
a
)
return
-
1
;
return
0
;
};
var
extend
=
function
(
a
,
b
)
{
var
i
,
n
,
k
,
object
;
for
(
i
=
1
,
n
=
arguments
.
length
;
i
<
n
;
i
++
)
{
object
=
arguments
[
i
];
if
(
!
object
)
continue
;
for
(
k
in
object
)
{
if
(
object
.
hasOwnProperty
(
k
))
{
a
[
k
]
=
object
[
k
];
}
}
}
return
a
;
};
var
trim
=
function
(
str
)
{
return
(
str
+
''
).
replace
(
/^
\s
+|
\s
+$|/g
,
''
);
};
var
escape_regex
=
function
(
str
)
{
return
(
str
+
''
).
replace
(
/
([
.?*+^$[
\]\\
(){}|-
])
/g
,
'
\\
$1'
);
};
var
is_array
=
Array
.
isArray
||
(
typeof
$
!==
'undefined'
&&
$
.
isArray
)
||
function
(
object
)
{
return
Object
.
prototype
.
toString
.
call
(
object
)
===
'[object Array]'
;
};
var
DIACRITICS
=
{
'a'
:
'[aÀÁÂÃÄÅàáâãäåĀāąĄ]'
,
'c'
:
'[cÇçćĆčČ]'
,
'd'
:
'[dđĐďĎð]'
,
'e'
:
'[eÈÉÊËèéêëěĚĒēęĘ]'
,
'i'
:
'[iÌÍÎÏìíîïĪī]'
,
'l'
:
'[lłŁ]'
,
'n'
:
'[nÑñňŇńŃ]'
,
'o'
:
'[oÒÓÔÕÕÖØòóôõöøŌō]'
,
'r'
:
'[rřŘ]'
,
's'
:
'[sŠšśŚ]'
,
't'
:
'[tťŤ]'
,
'u'
:
'[uÙÚÛÜùúûüůŮŪū]'
,
'y'
:
'[yŸÿýÝ]'
,
'z'
:
'[zŽžżŻźŹ]'
};
var
asciifold
=
(
function
()
{
var
i
,
n
,
k
,
chunk
;
var
foreignletters
=
''
;
var
lookup
=
{};
for
(
k
in
DIACRITICS
)
{
if
(
DIACRITICS
.
hasOwnProperty
(
k
))
{
chunk
=
DIACRITICS
[
k
].
substring
(
2
,
DIACRITICS
[
k
].
length
-
1
);
foreignletters
+=
chunk
;
for
(
i
=
0
,
n
=
chunk
.
length
;
i
<
n
;
i
++
)
{
lookup
[
chunk
.
charAt
(
i
)]
=
k
;
}
}
}
var
regexp
=
new
RegExp
(
'['
+
foreignletters
+
']'
,
'g'
);
return
function
(
str
)
{
return
str
.
replace
(
regexp
,
function
(
foreignletter
)
{
return
lookup
[
foreignletter
];
}).
toLowerCase
();
};
})();
// export
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
return
Sifter
;
}));
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment