Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
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
PSDI Army
yii2
Commits
69f0e5be
Commit
69f0e5be
authored
Feb 06, 2014
by
Alexander Makarov
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2329 from tonydspaniard/1094-modal-generator-autocomplete-fix
1904 modal generator autocomplete fix
parents
d438913e
333630ae
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
1219 additions
and
705 deletions
+1219
-705
CHANGELOG.md
extensions/gii/CHANGELOG.md
+1
-0
gii.js
extensions/gii/assets/gii.js
+9
-0
typeahead.js
extensions/gii/assets/typeahead.js
+1205
-704
ActiveField.php
extensions/gii/components/ActiveField.php
+4
-1
No files found.
extensions/gii/CHANGELOG.md
View file @
69f0e5be
...
...
@@ -5,6 +5,7 @@ Yii Framework 2 gii extension Change Log
----------------------------
-
Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue)
-
Bug #1904: Fixed autocomplete to work with underscore inputs "_" (tonydspaniard)
-
Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
-
Bug: fixed controller in crud template to avoid returning query in findModel() (cebe)
-
Enh #1624: generate rules for unique indexes (lucianobaraglia)
...
...
extensions/gii/assets/gii.js
View file @
69f0e5be
...
...
@@ -71,6 +71,15 @@ yii.gii = (function ($) {
};
return
{
autocomplete
:
function
(
counter
,
data
)
{
var
datum
=
new
Bloodhound
({
datumTokenizer
:
function
(
d
){
return
Bloodhound
.
tokenizers
.
whitespace
(
d
.
word
);},
queryTokenizer
:
Bloodhound
.
tokenizers
.
whitespace
,
local
:
data
});
datum
.
initialize
();
jQuery
(
'.typeahead-'
+
counter
).
typeahead
(
null
,{
displayKey
:
'word'
,
source
:
datum
.
ttAdapter
()});
},
init
:
function
()
{
initHintBlocks
();
initStickyInputs
();
...
...
extensions/gii/assets/typeahead.js
View file @
69f0e5be
/*!
* typeahead.js 0.
9.3
* https://github.com/twitter/typeahead
* typeahead.js 0.
10.0
* https://github.com/twitter/typeahead
.js
* Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
*/
(
function
(
$
)
{
var
VERSION
=
"0.9.3"
;
var
utils
=
{
var
_
=
{
isMsie
:
function
()
{
var
match
=
/
(
msie
)
([\w
.
]
+
)
/i
.
exec
(
navigator
.
userAgent
);
return
match
?
parseInt
(
match
[
2
],
10
)
:
false
;
return
/
(
msie|trident
)
/i
.
test
(
navigator
.
userAgent
)
?
navigator
.
userAgent
.
match
(
/
(
msie |rv:
)(\d
+
(
.
\d
+
)?)
/i
)[
2
]
:
false
;
},
isBlankString
:
function
(
str
)
{
return
!
str
||
/^
\s
*$/
.
test
(
str
);
...
...
@@ -30,21 +28,12 @@
return
typeof
obj
===
"undefined"
;
},
bind
:
$
.
proxy
,
bindAll
:
function
(
obj
)
{
var
val
;
f
or
(
var
key
in
obj
)
{
$
.
isFunction
(
val
=
obj
[
key
])
&&
(
obj
[
key
]
=
$
.
proxy
(
val
,
obj
)
);
each
:
function
(
collection
,
cb
)
{
$
.
each
(
collection
,
reverseArgs
)
;
f
unction
reverseArgs
(
index
,
value
)
{
return
cb
(
value
,
index
);
}
},
indexOf
:
function
(
haystack
,
needle
)
{
for
(
var
i
=
0
;
i
<
haystack
.
length
;
i
++
)
{
if
(
haystack
[
i
]
===
needle
)
{
return
i
;
}
}
return
-
1
;
},
each
:
$
.
each
,
map
:
$
.
map
,
filter
:
$
.
grep
,
every
:
function
(
obj
,
test
)
{
...
...
@@ -78,6 +67,12 @@
return
counter
++
;
};
}(),
templatify
:
function
templatify
(
obj
)
{
return
$
.
isFunction
(
obj
)
?
obj
:
template
;
function
template
()
{
return
String
(
obj
);
}
},
defer
:
function
(
fn
)
{
setTimeout
(
fn
,
0
);
},
...
...
@@ -123,69 +118,69 @@
return
result
;
};
},
tokenizeQuery
:
function
(
str
)
{
return
$
.
trim
(
str
).
toLowerCase
().
split
(
/
[\s]
+/
);
},
tokenizeText
:
function
(
str
)
{
return
$
.
trim
(
str
).
toLowerCase
().
split
(
/
[\s\-
_
]
+/
);
},
getProtocol
:
function
()
{
return
location
.
protocol
;
},
noop
:
function
()
{}
};
var
EventTarget
=
function
()
{
var
eventSplitter
=
/
\s
+/
;
return
{
on
:
function
(
events
,
callback
)
{
var
event
;
if
(
!
callback
)
{
return
this
;
var
VERSION
=
"0.10.0"
;
var
LruCache
=
function
(
root
,
undefined
)
{
function
LruCache
(
maxSize
)
{
this
.
maxSize
=
maxSize
||
100
;
this
.
size
=
0
;
this
.
hash
=
{};
this
.
list
=
new
List
();
}
_
.
mixin
(
LruCache
.
prototype
,
{
set
:
function
set
(
key
,
val
)
{
var
tailItem
=
this
.
list
.
tail
,
node
;
if
(
this
.
size
>=
this
.
maxSize
)
{
this
.
list
.
remove
(
tailItem
);
delete
this
.
hash
[
tailItem
.
key
];
}
this
.
_callbacks
=
this
.
_callbacks
||
{};
events
=
events
.
split
(
eventSplitter
);
while
(
event
=
events
.
shift
())
{
this
.
_callbacks
[
event
]
=
this
.
_callbacks
[
event
]
||
[];
this
.
_callbacks
[
event
].
push
(
callback
);
if
(
node
=
this
.
hash
[
key
])
{
node
.
val
=
val
;
this
.
list
.
moveToFront
(
node
);
}
else
{
node
=
new
Node
(
key
,
val
);
this
.
list
.
add
(
node
);
this
.
hash
[
key
]
=
node
;
this
.
size
++
;
}
return
this
;
},
trigger
:
function
(
events
,
data
)
{
var
event
,
callbacks
;
if
(
!
this
.
_callbacks
)
{
return
this
;
}
events
=
events
.
split
(
eventSplitter
);
while
(
event
=
events
.
shift
())
{
if
(
callbacks
=
this
.
_callbacks
[
event
])
{
for
(
var
i
=
0
;
i
<
callbacks
.
length
;
i
+=
1
)
{
callbacks
[
i
].
call
(
this
,
{
type
:
event
,
data
:
data
});
}
}
get
:
function
get
(
key
)
{
var
node
=
this
.
hash
[
key
];
if
(
node
)
{
this
.
list
.
moveToFront
(
node
);
return
node
.
val
;
}
return
this
;
}
};
}();
var
EventBus
=
function
()
{
var
namespace
=
"typeahead:"
;
function
EventBus
(
o
)
{
if
(
!
o
||
!
o
.
el
)
{
$
.
error
(
"EventBus initialized without el"
);
}
this
.
$el
=
$
(
o
.
el
);
});
function
List
()
{
this
.
head
=
this
.
tail
=
null
;
}
utils
.
mixin
(
EventBus
.
prototype
,
{
trigger
:
function
(
type
)
{
var
args
=
[].
slice
.
call
(
arguments
,
1
);
this
.
$el
.
trigger
(
namespace
+
type
,
args
);
_
.
mixin
(
List
.
prototype
,
{
add
:
function
add
(
node
)
{
if
(
this
.
head
)
{
node
.
next
=
this
.
head
;
this
.
head
.
prev
=
node
;
}
this
.
head
=
node
;
this
.
tail
=
this
.
tail
||
node
;
},
remove
:
function
remove
(
node
)
{
node
.
prev
?
node
.
prev
.
next
=
node
.
next
:
this
.
head
=
node
.
next
;
node
.
next
?
node
.
next
.
prev
=
node
.
prev
:
this
.
tail
=
node
.
prev
;
},
moveToFront
:
function
(
node
)
{
this
.
remove
(
node
);
this
.
add
(
node
);
}
});
return
EventBus
;
}();
function
Node
(
key
,
val
)
{
this
.
key
=
key
;
this
.
val
=
val
;
this
.
prev
=
this
.
next
=
null
;
}
return
LruCache
;
}(
this
);
var
PersistentStorage
=
function
()
{
var
ls
,
methods
;
try
{
...
...
@@ -215,7 +210,7 @@
return
decode
(
ls
.
getItem
(
this
.
_prefix
(
key
)));
},
set
:
function
(
key
,
val
,
ttl
)
{
if
(
utils
.
isNumber
(
ttl
))
{
if
(
_
.
isNumber
(
ttl
))
{
ls
.
setItem
(
this
.
_ttlKey
(
key
),
encode
(
now
()
+
ttl
));
}
else
{
ls
.
removeItem
(
this
.
_ttlKey
(
key
));
...
...
@@ -241,416 +236,773 @@
},
isExpired
:
function
(
key
)
{
var
ttl
=
decode
(
ls
.
getItem
(
this
.
_ttlKey
(
key
)));
return
utils
.
isNumber
(
ttl
)
&&
now
()
>
ttl
?
true
:
false
;
return
_
.
isNumber
(
ttl
)
&&
now
()
>
ttl
?
true
:
false
;
}
};
}
else
{
methods
=
{
get
:
utils
.
noop
,
set
:
utils
.
noop
,
remove
:
utils
.
noop
,
clear
:
utils
.
noop
,
isExpired
:
utils
.
noop
get
:
_
.
noop
,
set
:
_
.
noop
,
remove
:
_
.
noop
,
clear
:
_
.
noop
,
isExpired
:
_
.
noop
};
}
utils
.
mixin
(
PersistentStorage
.
prototype
,
methods
);
_
.
mixin
(
PersistentStorage
.
prototype
,
methods
);
return
PersistentStorage
;
function
now
()
{
return
new
Date
().
getTime
();
}
function
encode
(
val
)
{
return
JSON
.
stringify
(
utils
.
isUndefined
(
val
)
?
null
:
val
);
return
JSON
.
stringify
(
_
.
isUndefined
(
val
)
?
null
:
val
);
}
function
decode
(
val
)
{
return
JSON
.
parse
(
val
);
}
}();
var
RequestCache
=
function
()
{
function
RequestCache
(
o
)
{
utils
.
bindAll
(
this
);
o
=
o
||
{};
this
.
sizeLimit
=
o
.
sizeLimit
||
10
;
this
.
cache
=
{};
this
.
cachedKeysByAge
=
[];
}
utils
.
mixin
(
RequestCache
.
prototype
,
{
get
:
function
(
url
)
{
return
this
.
cache
[
url
];
},
set
:
function
(
url
,
resp
)
{
var
requestToEvict
;
if
(
this
.
cachedKeysByAge
.
length
===
this
.
sizeLimit
)
{
requestToEvict
=
this
.
cachedKeysByAge
.
shift
();
delete
this
.
cache
[
requestToEvict
];
}
this
.
cache
[
url
]
=
resp
;
this
.
cachedKeysByAge
.
push
(
url
);
}
});
return
RequestCache
;
}();
var
Transport
=
function
()
{
var
pendingRequestsCount
=
0
,
pendingRequests
=
{},
maxPendingRequests
,
requestCache
;
var
pendingRequestsCount
=
0
,
pendingRequests
=
{},
maxPendingRequests
=
6
,
requestCache
=
new
LruCache
(
10
)
;
function
Transport
(
o
)
{
utils
.
bindAll
(
this
);
o
=
utils
.
isString
(
o
)
?
{
url
:
o
}
:
o
;
requestCache
=
requestCache
||
new
RequestCache
();
maxPendingRequests
=
utils
.
isNumber
(
o
.
maxParallelRequests
)
?
o
.
maxParallelRequests
:
maxPendingRequests
||
6
;
this
.
url
=
o
.
url
;
this
.
wildcard
=
o
.
wildcard
||
"%QUERY"
;
this
.
filter
=
o
.
filter
;
this
.
replace
=
o
.
replace
;
this
.
ajaxSettings
=
{
type
:
"get"
,
cache
:
o
.
cache
,
timeout
:
o
.
timeout
,
dataType
:
o
.
dataType
||
"json"
,
beforeSend
:
o
.
beforeSend
};
this
.
_get
=
(
/^throttle$/i
.
test
(
o
.
rateLimitFn
)
?
utils
.
throttle
:
utils
.
debounce
)(
this
.
_get
,
o
.
rateLimitWait
||
300
);
o
=
o
||
{};
this
.
_send
=
o
.
send
?
callbackToDeferred
(
o
.
send
)
:
$
.
ajax
;
this
.
_get
=
o
.
rateLimiter
?
o
.
rateLimiter
(
this
.
_get
)
:
this
.
_get
;
}
utils
.
mixin
(
Transport
.
prototype
,
{
_get
:
function
(
url
,
cb
)
{
var
that
=
this
;
if
(
belowPendingRequestsThreshold
())
{
this
.
_sendRequest
(
url
).
done
(
done
);
Transport
.
setMaxPendingRequests
=
function
setMaxPendingRequests
(
num
)
{
maxPendingRequests
=
num
;
};
Transport
.
resetCache
=
function
clearCache
()
{
requestCache
=
new
LruCache
(
10
);
};
_
.
mixin
(
Transport
.
prototype
,
{
_get
:
function
(
url
,
o
,
cb
)
{
var
that
=
this
,
jqXhr
;
if
(
jqXhr
=
pendingRequests
[
url
])
{
jqXhr
.
done
(
done
);
}
else
if
(
pendingRequestsCount
<
maxPendingRequests
)
{
pendingRequestsCount
++
;
pendingRequests
[
url
]
=
this
.
_send
(
url
,
o
).
done
(
done
).
always
(
always
);
}
else
{
this
.
onDeckRequestArgs
=
[].
slice
.
call
(
arguments
,
0
);
}
function
done
(
resp
)
{
var
data
=
that
.
filter
?
that
.
filter
(
resp
)
:
resp
;
cb
&&
cb
(
data
);
cb
&&
cb
(
resp
);
requestCache
.
set
(
url
,
resp
);
}
},
_sendRequest
:
function
(
url
)
{
var
that
=
this
,
jqXhr
=
pendingRequests
[
url
];
if
(
!
jqXhr
)
{
incrementPendingRequests
();
jqXhr
=
pendingRequests
[
url
]
=
$
.
ajax
(
url
,
this
.
ajaxSettings
).
always
(
always
);
}
return
jqXhr
;
function
always
()
{
decrementPendingRequests
()
;
pendingRequests
[
url
]
=
null
;
pendingRequestsCount
--
;
delete
pendingRequests
[
url
]
;
if
(
that
.
onDeckRequestArgs
)
{
that
.
_get
.
apply
(
that
,
that
.
onDeckRequestArgs
);
that
.
onDeckRequestArgs
=
null
;
}
}
},
get
:
function
(
query
,
cb
)
{
var
that
=
this
,
encodedQuery
=
encodeURIComponent
(
query
||
""
),
url
,
resp
;
cb
=
cb
||
utils
.
noop
;
url
=
this
.
replace
?
this
.
replace
(
this
.
url
,
encodedQuery
)
:
this
.
url
.
replace
(
this
.
wildcard
,
encodedQuery
);
get
:
function
(
url
,
o
,
cb
)
{
var
that
=
this
,
resp
;
if
(
_
.
isFunction
(
o
))
{
cb
=
o
;
o
=
{};
}
if
(
resp
=
requestCache
.
get
(
url
))
{
utils
.
defer
(
function
()
{
cb
(
that
.
filter
?
that
.
filter
(
resp
)
:
resp
);
_
.
defer
(
function
()
{
cb
&&
cb
(
resp
);
});
}
else
{
this
.
_get
(
url
,
cb
);
this
.
_get
(
url
,
o
,
cb
);
}
return
!!
resp
;
}
});
return
Transport
;
function
incrementPendingRequests
()
{
pendingRequestsCount
++
;
function
callbackToDeferred
(
fn
)
{
return
function
customSendWrapper
(
url
,
o
)
{
var
deferred
=
$
.
Deferred
();
fn
(
url
,
o
,
onSuccess
,
onError
);
return
deferred
;
function
onSuccess
(
resp
)
{
_
.
defer
(
function
()
{
deferred
.
resolve
(
resp
);
});
}
function
onError
(
err
)
{
_
.
defer
(
function
()
{
deferred
.
reject
(
err
);
});
}
};
}
}();
var
SearchIndex
=
function
()
{
function
SearchIndex
(
o
)
{
o
=
o
||
{};
if
(
!
o
.
datumTokenizer
||
!
o
.
queryTokenizer
)
{
$
.
error
(
"datumTokenizer and queryTokenizer are both required"
);
}
this
.
datumTokenizer
=
o
.
datumTokenizer
;
this
.
queryTokenizer
=
o
.
queryTokenizer
;
this
.
datums
=
[];
this
.
trie
=
newNode
();
}
_
.
mixin
(
SearchIndex
.
prototype
,
{
bootstrap
:
function
bootstrap
(
o
)
{
this
.
datums
=
o
.
datums
;
this
.
trie
=
o
.
trie
;
},
add
:
function
(
data
)
{
var
that
=
this
;
data
=
_
.
isArray
(
data
)
?
data
:
[
data
];
_
.
each
(
data
,
function
(
datum
)
{
var
id
,
tokens
;
id
=
that
.
datums
.
push
(
datum
)
-
1
;
tokens
=
normalizeTokens
(
that
.
datumTokenizer
(
datum
));
_
.
each
(
tokens
,
function
(
token
)
{
var
node
,
chars
,
ch
,
ids
;
node
=
that
.
trie
;
chars
=
token
.
split
(
""
);
while
(
ch
=
chars
.
shift
())
{
node
=
node
.
children
[
ch
]
||
(
node
.
children
[
ch
]
=
newNode
());
node
.
ids
.
push
(
id
);
}
});
});
},
get
:
function
get
(
query
)
{
var
that
=
this
,
tokens
,
matches
;
tokens
=
normalizeTokens
(
this
.
queryTokenizer
(
query
));
_
.
each
(
tokens
,
function
(
token
)
{
var
node
,
chars
,
ch
,
ids
;
if
(
matches
&&
matches
.
length
===
0
)
{
return
false
;
}
node
=
that
.
trie
;
chars
=
token
.
split
(
""
);
while
(
node
&&
(
ch
=
chars
.
shift
()))
{
node
=
node
.
children
[
ch
];
}
if
(
node
&&
chars
.
length
===
0
)
{
ids
=
node
.
ids
.
slice
(
0
);
matches
=
matches
?
getIntersection
(
matches
,
ids
)
:
ids
;
}
else
{
matches
=
[];
return
false
;
}
});
return
matches
?
_
.
map
(
unique
(
matches
),
function
(
id
)
{
return
that
.
datums
[
id
];
})
:
[];
},
serialize
:
function
serialize
()
{
return
{
datums
:
this
.
datums
,
trie
:
this
.
trie
};
}
});
return
SearchIndex
;
function
normalizeTokens
(
tokens
)
{
tokens
=
_
.
filter
(
tokens
,
function
(
token
)
{
return
!!
token
;
});
tokens
=
_
.
map
(
tokens
,
function
(
token
)
{
return
token
.
toLowerCase
();
});
return
tokens
;
}
function
decrementPendingRequests
()
{
pendingRequestsCount
--
;
function
newNode
()
{
return
{
ids
:
[],
children
:
{}
};
}
function
belowPendingRequestsThreshold
()
{
return
pendingRequestsCount
<
maxPendingRequests
;
function
unique
(
array
)
{
var
seen
=
{},
uniques
=
[];
for
(
var
i
=
0
;
i
<
array
.
length
;
i
++
)
{
if
(
!
seen
[
array
[
i
]])
{
seen
[
array
[
i
]]
=
true
;
uniques
.
push
(
array
[
i
]);
}
}
return
uniques
;
}
function
getIntersection
(
arrayA
,
arrayB
)
{
var
ai
=
0
,
bi
=
0
,
intersection
=
[];
arrayA
=
arrayA
.
sort
(
compare
);
arrayB
=
arrayB
.
sort
(
compare
);
while
(
ai
<
arrayA
.
length
&&
bi
<
arrayB
.
length
)
{
if
(
arrayA
[
ai
]
<
arrayB
[
bi
])
{
ai
++
;
}
else
if
(
arrayA
[
ai
]
>
arrayB
[
bi
])
{
bi
++
;
}
else
{
intersection
.
push
(
arrayA
[
ai
]);
ai
++
;
bi
++
;
}
}
return
intersection
;
function
compare
(
a
,
b
)
{
return
a
-
b
;
}
}
}();
var
Dataset
=
function
()
{
var
keys
=
{
thumbprint
:
"thumbprint"
,
protocol
:
"protocol"
,
itemHash
:
"itemHash"
,
adjacencyList
:
"adjacencyList"
var
oParser
=
function
()
{
return
{
local
:
getLocal
,
prefetch
:
getPrefetch
,
remote
:
getRemote
};
function
Dataset
(
o
)
{
utils
.
bindAll
(
this
);
if
(
utils
.
isString
(
o
.
template
)
&&
!
o
.
engine
)
{
$
.
error
(
"no template engine specified"
);
function
getLocal
(
o
)
{
return
o
.
local
||
null
;
}
function
getPrefetch
(
o
)
{
var
prefetch
,
defaults
;
defaults
=
{
url
:
null
,
thumbprint
:
""
,
ttl
:
24
*
60
*
60
*
1
e3
,
filter
:
null
,
ajax
:
{}
};
if
(
prefetch
=
o
.
prefetch
||
null
)
{
prefetch
=
_
.
isString
(
prefetch
)
?
{
url
:
prefetch
}
:
prefetch
;
prefetch
=
_
.
mixin
(
defaults
,
prefetch
);
prefetch
.
thumbprint
=
VERSION
+
prefetch
.
thumbprint
;
prefetch
.
ajax
.
method
=
prefetch
.
ajax
.
method
||
"get"
;
prefetch
.
ajax
.
dataType
=
prefetch
.
ajax
.
dataType
||
"json"
;
!
prefetch
.
url
&&
$
.
error
(
"prefetch requires url to be set"
);
}
return
prefetch
;
}
function
getRemote
(
o
)
{
var
remote
,
defaults
;
defaults
=
{
url
:
null
,
wildcard
:
"%QUERY"
,
replace
:
null
,
rateLimitBy
:
"debounce"
,
rateLimitWait
:
300
,
send
:
null
,
filter
:
null
,
ajax
:
{}
};
if
(
remote
=
o
.
remote
||
null
)
{
remote
=
_
.
isString
(
remote
)
?
{
url
:
remote
}
:
remote
;
remote
=
_
.
mixin
(
defaults
,
remote
);
remote
.
rateLimiter
=
/^throttle$/i
.
test
(
remote
.
rateLimitBy
)
?
byThrottle
(
remote
.
rateLimitWait
)
:
byDebounce
(
remote
.
rateLimitWait
);
remote
.
ajax
.
method
=
remote
.
ajax
.
method
||
"get"
;
remote
.
ajax
.
dataType
=
remote
.
ajax
.
dataType
||
"json"
;
delete
remote
.
rateLimitBy
;
delete
remote
.
rateLimitWait
;
!
remote
.
url
&&
$
.
error
(
"remote requires url to be set"
);
}
return
remote
;
function
byDebounce
(
wait
)
{
return
function
(
fn
)
{
return
_
.
debounce
(
fn
,
wait
);
};
}
if
(
!
o
.
local
&&
!
o
.
prefetch
&&
!
o
.
remote
)
{
function
byThrottle
(
wait
)
{
return
function
(
fn
)
{
return
_
.
throttle
(
fn
,
wait
);
};
}
}
}();
var
Bloodhound
=
window
.
Bloodhound
=
function
()
{
var
keys
;
keys
=
{
data
:
"data"
,
protocol
:
"protocol"
,
thumbprint
:
"thumbprint"
};
function
Bloodhound
(
o
)
{
if
(
!
o
||
!
o
.
local
&&
!
o
.
prefetch
&&
!
o
.
remote
)
{
$
.
error
(
"one of local, prefetch, or remote is required"
);
}
this
.
name
=
o
.
name
||
utils
.
getUniqueId
();
this
.
limit
=
o
.
limit
||
5
;
this
.
minLength
=
o
.
minLength
||
1
;
this
.
header
=
o
.
header
;
this
.
footer
=
o
.
footer
;
this
.
valueKey
=
o
.
valueKey
||
"value"
;
this
.
template
=
compileTemplate
(
o
.
template
,
o
.
engine
,
this
.
valueKey
);
this
.
local
=
o
.
loca
l
;
this
.
prefetch
=
o
.
prefetch
;
this
.
remote
=
o
.
remote
;
this
.
itemHash
=
{};
this
.
adjacencyList
=
{}
;
this
.
storage
=
o
.
name
?
new
PersistentStorage
(
o
.
name
)
:
null
;
this
.
sorter
=
o
.
sorter
||
noSort
;
this
.
dupDetector
=
o
.
dupDetector
||
ignoreDuplicates
;
this
.
local
=
oParser
.
local
(
o
)
;
this
.
prefetch
=
oParser
.
prefetch
(
o
)
;
this
.
remote
=
oParser
.
remote
(
o
);
this
.
cacheKey
=
this
.
prefetch
?
this
.
prefetch
.
cacheKey
||
this
.
prefetch
.
url
:
nul
l
;
this
.
index
=
new
SearchIndex
({
datumTokenizer
:
o
.
datumTokenizer
,
queryTokenizer
:
o
.
queryTokenizer
})
;
this
.
storage
=
this
.
cacheKey
?
new
PersistentStorage
(
this
.
cacheKey
)
:
null
;
}
utils
.
mixin
(
Dataset
.
prototype
,
{
_processLocalData
:
function
(
data
)
{
this
.
_mergeProcessedData
(
this
.
_processData
(
data
)
);
Bloodhound
.
tokenizers
=
{
whitespace
:
function
whitespaceTokenizer
(
s
)
{
return
s
.
split
(
/
\s
+/
);
},
_loadPrefetchData
:
function
(
o
)
{
var
that
=
this
,
thumbprint
=
VERSION
+
(
o
.
thumbprint
||
""
),
storedThumbprint
,
storedProtocol
,
storedItemHash
,
storedAdjacencyList
,
isExpired
,
deferred
;
if
(
this
.
storage
)
{
storedThumbprint
=
this
.
storage
.
get
(
keys
.
thumbprint
);
storedProtocol
=
this
.
storage
.
get
(
keys
.
protocol
);
storedItemHash
=
this
.
storage
.
get
(
keys
.
itemHash
);
storedAdjacencyList
=
this
.
storage
.
get
(
keys
.
adjacencyList
);
}
isExpired
=
storedThumbprint
!==
thumbprint
||
storedProtocol
!==
utils
.
getProtocol
();
o
=
utils
.
isString
(
o
)
?
{
url
:
o
}
:
o
;
o
.
ttl
=
utils
.
isNumber
(
o
.
ttl
)
?
o
.
ttl
:
24
*
60
*
60
*
1
e3
;
if
(
storedItemHash
&&
storedAdjacencyList
&&
!
isExpired
)
{
this
.
_mergeProcessedData
({
itemHash
:
storedItemHash
,
adjacencyList
:
storedAdjacencyList
});
nonword
:
function
nonwordTokenizer
(
s
)
{
return
s
.
split
(
/
\W
+/
);
}
};
_
.
mixin
(
Bloodhound
.
prototype
,
{
_loadPrefetch
:
function
loadPrefetch
(
o
)
{
var
that
=
this
,
serialized
,
deferred
;
if
(
serialized
=
this
.
_readFromStorage
(
o
.
thumbprint
))
{
this
.
index
.
bootstrap
(
serialized
);
deferred
=
$
.
Deferred
().
resolve
();
}
else
{
deferred
=
$
.
getJSON
(
o
.
url
).
done
(
processPrefetchData
);
deferred
=
$
.
ajax
(
o
.
url
,
o
.
ajax
).
done
(
handlePrefetchResponse
);
}
return
deferred
;
function
processPrefetchData
(
data
)
{
var
filteredData
=
o
.
filter
?
o
.
filter
(
data
)
:
data
,
processedData
=
that
.
_processData
(
filteredData
),
itemHash
=
processedData
.
itemHash
,
adjacencyList
=
processedData
.
adjacencyList
;
if
(
that
.
storage
)
{
that
.
storage
.
set
(
keys
.
itemHash
,
itemHash
,
o
.
ttl
);
that
.
storage
.
set
(
keys
.
adjacencyList
,
adjacencyList
,
o
.
ttl
);
that
.
storage
.
set
(
keys
.
thumbprint
,
thumbprint
,
o
.
ttl
);
that
.
storage
.
set
(
keys
.
protocol
,
utils
.
getProtocol
(),
o
.
ttl
);
}
that
.
_mergeProcessedData
(
processedData
);
function
handlePrefetchResponse
(
resp
)
{
var
filtered
;
filtered
=
o
.
filter
?
o
.
filter
(
resp
)
:
resp
;
that
.
add
(
filtered
);
that
.
_saveToStorage
(
that
.
index
.
serialize
(),
o
.
thumbprint
,
o
.
ttl
);
}
},
_transformDatum
:
function
(
datum
)
{
var
value
=
utils
.
isString
(
datum
)
?
datum
:
datum
[
this
.
valueKey
],
tokens
=
datum
.
tokens
||
utils
.
tokenizeText
(
value
),
item
=
{
value
:
value
,
tokens
:
tokens
};
if
(
utils
.
isString
(
datum
))
{
item
.
datum
=
{};
item
.
datum
[
this
.
valueKey
]
=
datum
;
}
else
{
item
.
datum
=
datum
;
_getFromRemote
:
function
getFromRemote
(
query
,
cb
)
{
var
that
=
this
,
url
,
uriEncodedQuery
;
query
=
query
||
""
;
uriEncodedQuery
=
encodeURIComponent
(
query
);
url
=
this
.
remote
.
replace
?
this
.
remote
.
replace
(
this
.
remote
.
url
,
query
)
:
this
.
remote
.
url
.
replace
(
this
.
remote
.
wildcard
,
uriEncodedQuery
);
return
this
.
transport
.
get
(
url
,
this
.
remote
.
ajax
,
handleRemoteResponse
);
function
handleRemoteResponse
(
resp
)
{
var
filtered
=
that
.
remote
.
filter
?
that
.
remote
.
filter
(
resp
)
:
resp
;
cb
(
filtered
);
}
item
.
tokens
=
utils
.
filter
(
item
.
tokens
,
function
(
token
)
{
return
!
utils
.
isBlankString
(
token
);
});
item
.
tokens
=
utils
.
map
(
item
.
tokens
,
function
(
token
)
{
return
token
.
toLowerCase
();
});
return
item
;
},
_processData
:
function
(
data
)
{
var
that
=
this
,
itemHash
=
{},
adjacencyList
=
{};
utils
.
each
(
data
,
function
(
i
,
datum
)
{
var
item
=
that
.
_transformDatum
(
datum
),
id
=
utils
.
getUniqueId
(
item
.
value
);
itemHash
[
id
]
=
item
;
utils
.
each
(
item
.
tokens
,
function
(
i
,
token
)
{
var
character
=
token
.
charAt
(
0
),
adjacency
=
adjacencyList
[
character
]
||
(
adjacencyList
[
character
]
=
[
id
]);
!~
utils
.
indexOf
(
adjacency
,
id
)
&&
adjacency
.
push
(
id
);
});
});
return
{
itemHash
:
itemHash
,
adjacencyList
:
adjacencyList
};
},
_mergeProcessedData
:
function
(
processedData
)
{
var
that
=
this
;
utils
.
mixin
(
this
.
itemHash
,
processedData
.
itemHash
);
utils
.
each
(
processedData
.
adjacencyList
,
function
(
character
,
adjacency
)
{
var
masterAdjacency
=
that
.
adjacencyList
[
character
];
that
.
adjacencyList
[
character
]
=
masterAdjacency
?
masterAdjacency
.
concat
(
adjacency
)
:
adjacency
;
});
_saveToStorage
:
function
saveToStorage
(
data
,
thumbprint
,
ttl
)
{
if
(
this
.
storage
)
{
this
.
storage
.
set
(
keys
.
data
,
data
,
ttl
);
this
.
storage
.
set
(
keys
.
protocol
,
location
.
protocol
,
ttl
);
this
.
storage
.
set
(
keys
.
thumbprint
,
thumbprint
,
ttl
);
}
},
_getLocalSuggestions
:
function
(
terms
)
{
var
that
=
this
,
firstChars
=
[],
lists
=
[],
shortestList
,
suggestions
=
[];
utils
.
each
(
terms
,
function
(
i
,
term
)
{
var
firstChar
=
term
.
charAt
(
0
);
!~
utils
.
indexOf
(
firstChars
,
firstChar
)
&&
firstChars
.
push
(
firstChar
);
});
utils
.
each
(
firstChars
,
function
(
i
,
firstChar
)
{
var
list
=
that
.
adjacencyList
[
firstChar
];
if
(
!
list
)
{
return
false
;
}
lists
.
push
(
list
);
if
(
!
shortestList
||
list
.
length
<
shortestList
.
length
)
{
shortestList
=
list
;
}
});
if
(
lists
.
length
<
firstChars
.
length
)
{
return
[];
_readFromStorage
:
function
readFromStorage
(
thumbprint
)
{
var
stored
=
{};
if
(
this
.
storage
)
{
stored
.
data
=
this
.
storage
.
get
(
keys
.
data
);
stored
.
protocol
=
this
.
storage
.
get
(
keys
.
protocol
);
stored
.
thumbprint
=
this
.
storage
.
get
(
keys
.
thumbprint
);
}
utils
.
each
(
shortestList
,
function
(
i
,
id
)
{
var
item
=
that
.
itemHash
[
id
],
isCandidate
,
isMatch
;
isCandidate
=
utils
.
every
(
lists
,
function
(
list
)
{
return
~
utils
.
indexOf
(
list
,
id
);
});
isMatch
=
isCandidate
&&
utils
.
every
(
terms
,
function
(
term
)
{
return
utils
.
some
(
item
.
tokens
,
function
(
token
)
{
return
token
.
indexOf
(
term
)
===
0
;
});
});
isMatch
&&
suggestions
.
push
(
item
);
});
return
suggestions
;
isExpired
=
stored
.
thumbprint
!==
thumbprint
||
stored
.
protocol
!==
location
.
protocol
;
return
stored
.
data
&&
!
isExpired
?
stored
.
data
:
null
;
},
initialize
:
function
()
{
var
deferred
;
this
.
local
&&
this
.
_processLocalData
(
this
.
local
);
initialize
:
function
initialize
()
{
var
that
=
this
,
deferred
;
deferred
=
this
.
prefetch
?
this
.
_loadPrefetch
(
this
.
prefetch
)
:
$
.
Deferred
().
resolve
();
this
.
local
&&
deferred
.
done
(
addLocalToIndex
);
this
.
transport
=
this
.
remote
?
new
Transport
(
this
.
remote
)
:
null
;
deferred
=
this
.
prefetch
?
this
.
_loadPrefetchData
(
this
.
prefetch
)
:
$
.
Deferred
().
resolve
();
this
.
local
=
this
.
prefetch
=
this
.
remote
=
null
;
this
.
initialize
=
function
()
{
return
deferred
;
this
.
initialize
=
function
initialize
()
{
return
deferred
.
promise
();
};
return
deferred
;
return
deferred
.
promise
();
function
addLocalToIndex
()
{
that
.
add
(
that
.
local
);
}
},
getSuggestions
:
function
(
query
,
cb
)
{
var
that
=
this
,
terms
,
suggestions
,
cacheHit
=
false
;
if
(
query
.
length
<
this
.
minLength
)
{
return
;
add
:
function
add
(
data
)
{
this
.
index
.
add
(
data
);
},
get
:
function
get
(
query
,
cb
)
{
var
that
=
this
,
matches
,
cacheHit
=
false
;
matches
=
this
.
index
.
get
(
query
).
sort
(
this
.
sorter
).
slice
(
0
,
this
.
limit
);
if
(
matches
.
length
<
this
.
limit
&&
this
.
transport
)
{
cacheHit
=
this
.
_getFromRemote
(
query
,
returnRemoteMatches
);
}
terms
=
utils
.
tokenizeQuery
(
query
);
suggestions
=
this
.
_getLocalSuggestions
(
terms
).
slice
(
0
,
this
.
limit
);
if
(
suggestions
.
length
<
this
.
limit
&&
this
.
transport
)
{
cacheHit
=
this
.
transport
.
get
(
query
,
processRemoteData
);
}
!
cacheHit
&&
cb
&&
cb
(
suggestions
);
function
processRemoteData
(
data
)
{
suggestions
=
suggestions
.
slice
(
0
);
utils
.
each
(
data
,
function
(
i
,
datum
)
{
var
item
=
that
.
_transformDatum
(
datum
),
isDuplicate
;
isDuplicate
=
utils
.
some
(
suggestions
,
function
(
suggestion
)
{
return
item
.
value
===
suggestion
.
value
;
!
cacheHit
&&
cb
&&
cb
(
matches
);
function
returnRemoteMatches
(
remoteMatches
)
{
var
matchesWithBackfill
=
matches
.
slice
(
0
);
_
.
each
(
remoteMatches
,
function
(
remoteMatch
)
{
var
isDuplicate
;
isDuplicate
=
_
.
some
(
matchesWithBackfill
,
function
(
match
)
{
return
that
.
dupDetector
(
remoteMatch
,
match
);
});
!
isDuplicate
&&
suggestions
.
push
(
item
);
return
suggestions
.
length
<
that
.
limit
;
!
isDuplicate
&&
matchesWithBackfill
.
push
(
remoteMatch
);
return
matchesWithBackfill
.
length
<
that
.
limit
;
});
cb
&&
cb
(
suggestions
);
cb
&&
cb
(
matchesWithBackfill
.
sort
(
that
.
sorter
)
);
}
},
ttAdapter
:
function
ttAdapter
()
{
return
_
.
bind
(
this
.
get
,
this
);
}
});
return
Dataset
;
function
compileTemplate
(
template
,
engine
,
valueKey
)
{
var
renderFn
,
compiledTemplate
;
if
(
utils
.
isFunction
(
template
))
{
renderFn
=
template
;
}
else
if
(
utils
.
isString
(
template
))
{
compiledTemplate
=
engine
.
compile
(
template
);
renderFn
=
utils
.
bind
(
compiledTemplate
.
render
,
compiledTemplate
);
return
Bloodhound
;
function
noSort
()
{
return
0
;
}
function
ignoreDuplicates
()
{
return
false
;
}
}();
var
html
=
{
wrapper
:
'<span class="twitter-typeahead"></span>'
,
dropdown
:
'<span class="tt-dropdown-menu"></span>'
,
dataset
:
'<div class="tt-dataset-%CLASS%"></div>'
,
suggestions
:
'<span class="tt-suggestions"></span>'
,
suggestion
:
'<div class="tt-suggestion">%BODY%</div>'
};
var
css
=
{
wrapper
:
{
position
:
"relative"
,
display
:
"inline-block"
},
hint
:
{
position
:
"absolute"
,
top
:
"0"
,
left
:
"0"
,
borderColor
:
"transparent"
,
boxShadow
:
"none"
},
input
:
{
position
:
"relative"
,
verticalAlign
:
"top"
,
backgroundColor
:
"transparent"
},
inputWithNoHint
:
{
position
:
"relative"
,
verticalAlign
:
"top"
},
dropdown
:
{
position
:
"absolute"
,
top
:
"100%"
,
left
:
"0"
,
zIndex
:
"100"
,
display
:
"none"
},
suggestions
:
{
display
:
"block"
},
suggestion
:
{
whiteSpace
:
"nowrap"
,
cursor
:
"pointer"
},
suggestionChild
:
{
whiteSpace
:
"normal"
},
ltr
:
{
left
:
"0"
,
right
:
"auto"
},
rtl
:
{
left
:
"auto"
,
right
:
" 0"
}
};
if
(
_
.
isMsie
())
{
_
.
mixin
(
css
.
input
,
{
backgroundImage
:
"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
});
}
if
(
_
.
isMsie
()
&&
_
.
isMsie
()
<=
7
)
{
_
.
mixin
(
css
.
input
,
{
marginTop
:
"-1px"
});
}
var
EventBus
=
function
()
{
var
namespace
=
"typeahead:"
;
function
EventBus
(
o
)
{
if
(
!
o
||
!
o
.
el
)
{
$
.
error
(
"EventBus initialized without el"
);
}
this
.
$el
=
$
(
o
.
el
);
}
_
.
mixin
(
EventBus
.
prototype
,
{
trigger
:
function
(
type
)
{
var
args
=
[].
slice
.
call
(
arguments
,
1
);
this
.
$el
.
trigger
(
namespace
+
type
,
args
);
}
});
return
EventBus
;
}();
var
EventEmitter
=
function
()
{
var
splitter
=
/
\s
+/
,
nextTick
=
getNextTick
();
return
{
onSync
:
onSync
,
onAsync
:
onAsync
,
off
:
off
,
trigger
:
trigger
};
function
on
(
method
,
types
,
cb
,
context
)
{
var
type
;
if
(
!
cb
)
{
return
this
;
}
types
=
types
.
split
(
splitter
);
cb
=
context
?
bindContext
(
cb
,
context
)
:
cb
;
this
.
_callbacks
=
this
.
_callbacks
||
{};
while
(
type
=
types
.
shift
())
{
this
.
_callbacks
[
type
]
=
this
.
_callbacks
[
type
]
||
{
sync
:
[],
async
:
[]
};
this
.
_callbacks
[
type
][
method
].
push
(
cb
);
}
return
this
;
}
function
onAsync
(
types
,
cb
,
context
)
{
return
on
.
call
(
this
,
"async"
,
types
,
cb
,
context
);
}
function
onSync
(
types
,
cb
,
context
)
{
return
on
.
call
(
this
,
"sync"
,
types
,
cb
,
context
);
}
function
off
(
types
)
{
var
type
;
if
(
!
this
.
_callbacks
)
{
return
this
;
}
types
=
types
.
split
(
splitter
);
while
(
type
=
types
.
shift
())
{
delete
this
.
_callbacks
[
type
];
}
return
this
;
}
function
trigger
(
types
)
{
var
that
=
this
,
type
,
callbacks
,
args
,
syncFlush
,
asyncFlush
;
if
(
!
this
.
_callbacks
)
{
return
this
;
}
types
=
types
.
split
(
splitter
);
args
=
[].
slice
.
call
(
arguments
,
1
);
while
((
type
=
types
.
shift
())
&&
(
callbacks
=
this
.
_callbacks
[
type
]))
{
syncFlush
=
getFlush
(
callbacks
.
sync
,
this
,
[
type
].
concat
(
args
));
asyncFlush
=
getFlush
(
callbacks
.
async
,
this
,
[
type
].
concat
(
args
));
syncFlush
()
&&
nextTick
(
asyncFlush
);
}
return
this
;
}
function
getFlush
(
callbacks
,
context
,
args
)
{
return
flush
;
function
flush
()
{
var
cancelled
;
for
(
var
i
=
0
;
!
cancelled
&&
i
<
callbacks
.
length
;
i
+=
1
)
{
cancelled
=
callbacks
[
i
].
apply
(
context
,
args
)
===
false
;
}
return
!
cancelled
;
}
}
function
getNextTick
()
{
var
nextTickFn
,
messageChannel
;
if
(
window
.
setImmediate
)
{
nextTickFn
=
function
nextTickSetImmediate
(
fn
)
{
setImmediate
(
function
()
{
fn
();
});
};
}
else
{
renderFn
=
function
(
context
)
{
return
"<p>"
+
context
[
valueKey
]
+
"</p>"
;
nextTickFn
=
function
nextTickSetTimeout
(
fn
)
{
setTimeout
(
function
()
{
fn
();
},
0
);
};
}
return
render
Fn
;
return
nextTick
Fn
;
}
}();
var
InputView
=
function
()
{
function
InputView
(
o
)
{
var
that
=
this
;
utils
.
bindAll
(
this
);
this
.
specialKeyCodeMap
=
{
9
:
"tab"
,
27
:
"esc"
,
37
:
"left"
,
39
:
"right"
,
13
:
"enter"
,
38
:
"up"
,
40
:
"down"
function
bindContext
(
fn
,
context
)
{
return
fn
.
bind
?
fn
.
bind
(
context
)
:
function
()
{
fn
.
apply
(
context
,
[].
slice
.
call
(
arguments
,
0
));
};
}
}();
var
highlight
=
function
(
doc
)
{
var
defaults
=
{
node
:
null
,
pattern
:
null
,
tagName
:
"strong"
,
className
:
null
,
wordsOnly
:
false
,
caseSensitive
:
false
};
return
function
hightlight
(
o
)
{
var
regex
;
o
=
_
.
mixin
({},
defaults
,
o
);
if
(
!
o
.
node
||
!
o
.
pattern
)
{
return
;
}
o
.
pattern
=
_
.
isArray
(
o
.
pattern
)
?
o
.
pattern
:
[
o
.
pattern
];
regex
=
getRegex
(
o
.
pattern
,
o
.
caseSensitive
,
o
.
wordsOnly
);
traverse
(
o
.
node
,
hightlightTextNode
);
function
hightlightTextNode
(
textNode
)
{
var
match
,
patternNode
;
if
(
match
=
regex
.
exec
(
textNode
.
data
))
{
wrapperNode
=
doc
.
createElement
(
o
.
tagName
);
o
.
className
&&
(
wrapperNode
.
className
=
o
.
className
);
patternNode
=
textNode
.
splitText
(
match
.
index
);
patternNode
.
splitText
(
match
[
0
].
length
);
wrapperNode
.
appendChild
(
patternNode
.
cloneNode
(
true
));
textNode
.
parentNode
.
replaceChild
(
wrapperNode
,
patternNode
);
}
return
!!
match
;
}
function
traverse
(
el
,
hightlightTextNode
)
{
var
childNode
,
TEXT_NODE_TYPE
=
3
;
for
(
var
i
=
0
;
i
<
el
.
childNodes
.
length
;
i
++
)
{
childNode
=
el
.
childNodes
[
i
];
if
(
childNode
.
nodeType
===
TEXT_NODE_TYPE
)
{
i
+=
hightlightTextNode
(
childNode
)
?
1
:
0
;
}
else
{
traverse
(
childNode
,
hightlightTextNode
);
}
}
}
};
function
getRegex
(
patterns
,
caseSensitive
,
wordsOnly
)
{
var
escapedPatterns
=
[],
regexStr
;
for
(
var
i
=
0
;
i
<
patterns
.
length
;
i
++
)
{
escapedPatterns
.
push
(
_
.
escapeRegExChars
(
patterns
[
i
]));
}
regexStr
=
wordsOnly
?
"
\\
b("
+
escapedPatterns
.
join
(
"|"
)
+
")
\\
b"
:
"("
+
escapedPatterns
.
join
(
"|"
)
+
")"
;
return
caseSensitive
?
new
RegExp
(
regexStr
)
:
new
RegExp
(
regexStr
,
"i"
);
}
}(
window
.
document
);
var
Input
=
function
()
{
var
specialKeyCodeMap
;
specialKeyCodeMap
=
{
9
:
"tab"
,
27
:
"esc"
,
37
:
"left"
,
39
:
"right"
,
13
:
"enter"
,
38
:
"up"
,
40
:
"down"
};
function
Input
(
o
)
{
var
that
=
this
,
onBlur
,
onFocus
,
onKeydown
,
onInput
;
o
=
o
||
{};
if
(
!
o
.
input
)
{
$
.
error
(
"input is missing"
);
}
onBlur
=
_
.
bind
(
this
.
_onBlur
,
this
);
onFocus
=
_
.
bind
(
this
.
_onFocus
,
this
);
onKeydown
=
_
.
bind
(
this
.
_onKeydown
,
this
);
onInput
=
_
.
bind
(
this
.
_onInput
,
this
);
this
.
$hint
=
$
(
o
.
hint
);
this
.
$input
=
$
(
o
.
input
).
on
(
"blur.tt"
,
this
.
_handleBlur
).
on
(
"focus.tt"
,
this
.
_handleFocus
).
on
(
"keydown.tt"
,
this
.
_handleSpecialKeyEvent
);
if
(
!
utils
.
isMsie
())
{
this
.
$input
.
on
(
"input.tt"
,
this
.
_compareQueryToInputValue
);
this
.
$input
=
$
(
o
.
input
).
on
(
"blur.tt"
,
onBlur
).
on
(
"focus.tt"
,
onFocus
).
on
(
"keydown.tt"
,
onKeydown
);
if
(
this
.
$hint
.
length
===
0
)
{
this
.
setHintValue
=
this
.
getHintValue
=
this
.
clearHint
=
_
.
noop
;
}
if
(
!
_
.
isMsie
())
{
this
.
$input
.
on
(
"input.tt"
,
onInput
);
}
else
{
this
.
$input
.
on
(
"keydown.tt keypress.tt cut.tt paste.tt"
,
function
(
$e
)
{
if
(
that
.
specialKeyCodeMap
[
$e
.
which
||
$e
.
keyCode
])
{
if
(
specialKeyCodeMap
[
$e
.
which
||
$e
.
keyCode
])
{
return
;
}
utils
.
defer
(
that
.
_compareQueryToInputValue
);
_
.
defer
(
_
.
bind
(
that
.
_onInput
,
that
,
$e
)
);
});
}
this
.
query
=
this
.
$input
.
val
();
this
.
$overflowHelper
=
buildOverflowHelper
(
this
.
$input
);
}
utils
.
mixin
(
InputView
.
prototype
,
EventTarget
,
{
_handleFocus
:
function
()
{
this
.
trigger
(
"focused"
);
Input
.
normalizeQuery
=
function
(
str
)
{
return
(
str
||
""
).
replace
(
/^
\s
*/g
,
""
).
replace
(
/
\s{2,}
/g
,
" "
);
};
_
.
mixin
(
Input
.
prototype
,
EventEmitter
,
{
_onBlur
:
function
onBlur
(
$e
)
{
this
.
resetInputValue
();
this
.
trigger
(
"blurred"
);
},
_
handleBlur
:
function
(
)
{
this
.
trigger
(
"
blur
ed"
);
_
onFocus
:
function
onFocus
(
$e
)
{
this
.
trigger
(
"
focus
ed"
);
},
_handleSpecialKeyEvent
:
function
(
$e
)
{
var
keyName
=
this
.
specialKeyCodeMap
[
$e
.
which
||
$e
.
keyCode
];
keyName
&&
this
.
trigger
(
keyName
+
"Keyed"
,
$e
);
_onKeydown
:
function
onKeydown
(
$e
)
{
var
keyName
=
specialKeyCodeMap
[
$e
.
which
||
$e
.
keyCode
];
this
.
_managePreventDefault
(
keyName
,
$e
);
if
(
keyName
&&
this
.
_shouldTrigger
(
keyName
,
$e
))
{
this
.
trigger
(
keyName
+
"Keyed"
,
$e
);
}
},
_compareQueryToInputValue
:
function
()
{
var
inputValue
=
this
.
getInputValue
(),
isSameQuery
=
compareQueries
(
this
.
query
,
inputValue
),
isSameQueryExceptWhitespace
=
isSameQuery
?
this
.
query
.
length
!==
inputValue
.
length
:
false
;
if
(
isSameQueryExceptWhitespace
)
{
this
.
trigger
(
"whitespaceChanged"
,
{
value
:
this
.
query
});
}
else
if
(
!
isSameQuery
)
{
this
.
trigger
(
"queryChanged"
,
{
value
:
this
.
query
=
inputValue
});
_onInput
:
function
onInput
(
$e
)
{
this
.
_checkInputValue
();
},
_managePreventDefault
:
function
managePreventDefault
(
keyName
,
$e
)
{
var
preventDefault
,
hintValue
,
inputValue
;
switch
(
keyName
)
{
case
"tab"
:
hintValue
=
this
.
getHintValue
();
inputValue
=
this
.
getInputValue
();
preventDefault
=
hintValue
&&
hintValue
!==
inputValue
&&
!
withModifier
(
$e
);
break
;
case
"up"
:
case
"down"
:
preventDefault
=
!
withModifier
(
$e
);
break
;
default
:
preventDefault
=
false
;
}
preventDefault
&&
$e
.
preventDefault
();
},
destroy
:
function
()
{
this
.
$hint
.
off
(
".tt"
);
this
.
$input
.
off
(
".tt"
);
this
.
$hint
=
this
.
$input
=
this
.
$overflowHelper
=
null
;
_shouldTrigger
:
function
shouldTrigger
(
keyName
,
$e
)
{
var
trigger
;
switch
(
keyName
)
{
case
"tab"
:
trigger
=
!
withModifier
(
$e
);
break
;
default
:
trigger
=
true
;
}
return
trigger
;
},
_checkInputValue
:
function
checkInputValue
()
{
var
inputValue
,
areEquivalent
,
hasDifferentWhitespace
;
inputValue
=
this
.
getInputValue
();
areEquivalent
=
areQueriesEquivalent
(
inputValue
,
this
.
query
);
hasDifferentWhitespace
=
areEquivalent
?
this
.
query
.
length
!==
inputValue
.
length
:
false
;
if
(
!
areEquivalent
)
{
this
.
trigger
(
"queryChanged"
,
this
.
query
=
inputValue
);
}
else
if
(
hasDifferentWhitespace
)
{
this
.
trigger
(
"whitespaceChanged"
,
this
.
query
);
}
},
focus
:
function
()
{
focus
:
function
focus
()
{
this
.
$input
.
focus
();
},
blur
:
function
()
{
blur
:
function
blur
()
{
this
.
$input
.
blur
();
},
getQuery
:
function
()
{
getQuery
:
function
getQuery
()
{
return
this
.
query
;
},
setQuery
:
function
(
query
)
{
setQuery
:
function
setQuery
(
query
)
{
this
.
query
=
query
;
},
getInputValue
:
function
()
{
getInputValue
:
function
getInputValue
()
{
return
this
.
$input
.
val
();
},
setInputValue
:
function
(
value
,
silent
)
{
setInputValue
:
function
setInputValue
(
value
,
silent
)
{
this
.
$input
.
val
(
value
);
!
silent
&&
this
.
_c
ompareQueryTo
InputValue
();
!
silent
&&
this
.
_c
heck
InputValue
();
},
getHintValue
:
function
()
{
getHintValue
:
function
getHintValue
()
{
return
this
.
$hint
.
val
();
},
setHintValue
:
function
(
value
)
{
setHintValue
:
function
setHintValue
(
value
)
{
this
.
$hint
.
val
(
value
);
},
getLanguageDirection
:
function
()
{
resetInputValue
:
function
resetInputValue
()
{
this
.
$input
.
val
(
this
.
query
);
},
clearHint
:
function
clearHint
()
{
this
.
$hint
.
val
(
""
);
},
getLanguageDirection
:
function
getLanguageDirection
()
{
return
(
this
.
$input
.
css
(
"direction"
)
||
"ltr"
).
toLowerCase
();
},
isOverflow
:
function
()
{
hasOverflow
:
function
hasOverflow
()
{
var
constraint
=
this
.
$input
.
width
()
-
2
;
this
.
$overflowHelper
.
text
(
this
.
getInputValue
());
return
this
.
$overflowHelper
.
width
()
>
this
.
$input
.
width
()
;
return
this
.
$overflowHelper
.
width
()
>
=
constraint
;
},
isCursorAtEnd
:
function
()
{
var
valueLength
=
this
.
$input
.
val
().
length
,
selectionStart
=
this
.
$input
[
0
].
selectionStart
,
range
;
if
(
utils
.
isNumber
(
selectionStart
))
{
var
valueLength
,
selectionStart
,
range
;
valueLength
=
this
.
$input
.
val
().
length
;
selectionStart
=
this
.
$input
[
0
].
selectionStart
;
if
(
_
.
isNumber
(
selectionStart
))
{
return
selectionStart
===
valueLength
;
}
else
if
(
document
.
selection
)
{
range
=
document
.
selection
.
createRange
();
...
...
@@ -658,13 +1010,17 @@
return
valueLength
===
range
.
text
.
length
;
}
return
true
;
},
destroy
:
function
destroy
()
{
this
.
$hint
.
off
(
".tt"
);
this
.
$input
.
off
(
".tt"
);
this
.
$hint
=
this
.
$input
=
this
.
$overflowHelper
=
null
;
}
});
return
Input
View
;
return
Input
;
function
buildOverflowHelper
(
$input
)
{
return
$
(
"<span></span>"
).
css
({
return
$
(
'<pre aria-hidden="true"></pre>'
).
css
({
position
:
"absolute"
,
left
:
"-9999px"
,
visibility
:
"hidden"
,
whiteSpace
:
"nowrap"
,
fontFamily
:
$input
.
css
(
"font-family"
),
...
...
@@ -679,452 +1035,597 @@
textTransform
:
$input
.
css
(
"text-transform"
)
}).
insertAfter
(
$input
);
}
function
compareQueries
(
a
,
b
)
{
a
=
(
a
||
""
).
replace
(
/^
\s
*/g
,
""
).
replace
(
/
\s{2,}
/g
,
" "
);
b
=
(
b
||
""
).
replace
(
/^
\s
*/g
,
""
).
replace
(
/
\s{2,}
/g
,
" "
);
return
a
===
b
;
function
areQueriesEquivalent
(
a
,
b
)
{
return
Input
.
normalizeQuery
(
a
)
===
Input
.
normalizeQuery
(
b
);
}
function
withModifier
(
$e
)
{
return
$e
.
altKey
||
$e
.
ctrlKey
||
$e
.
metaKey
||
$e
.
shiftKey
;
}
}();
var
DropdownView
=
function
()
{
var
html
=
{
suggestionsList
:
'<span class="tt-suggestions"></span>'
},
css
=
{
suggestionsList
:
{
display
:
"block"
},
suggestion
:
{
whiteSpace
:
"nowrap"
,
cursor
:
"pointer"
},
suggestionChild
:
{
whiteSpace
:
"normal"
var
Dataset
=
function
()
{
var
datasetKey
=
"ttDataset"
,
valueKey
=
"ttValue"
,
datumKey
=
"ttDatum"
;
function
Dataset
(
o
)
{
o
=
o
||
{};
o
.
templates
=
o
.
templates
||
{};
if
(
!
o
.
source
)
{
$
.
error
(
"missing source"
);
}
this
.
query
=
null
;
this
.
highlight
=
!!
o
.
highlight
;
this
.
name
=
o
.
name
||
_
.
getUniqueId
();
this
.
source
=
o
.
source
;
this
.
valueKey
=
o
.
displayKey
||
"value"
;
this
.
templates
=
getTemplates
(
o
.
templates
,
this
.
valueKey
);
this
.
$el
=
$
(
html
.
dataset
.
replace
(
"%CLASS%"
,
this
.
name
));
}
Dataset
.
extractDatasetName
=
function
extractDatasetName
(
el
)
{
return
$
(
el
).
data
(
datasetKey
);
};
Dataset
.
extractValue
=
function
extractDatum
(
el
)
{
return
$
(
el
).
data
(
valueKey
);
};
function
DropdownView
(
o
)
{
utils
.
bindAll
(
this
);
Dataset
.
extractDatum
=
function
extractDatum
(
el
)
{
return
$
(
el
).
data
(
datumKey
);
};
_
.
mixin
(
Dataset
.
prototype
,
EventEmitter
,
{
_render
:
function
render
(
query
,
suggestions
)
{
if
(
!
this
.
$el
)
{
return
;
}
var
that
=
this
,
hasSuggestions
;
this
.
$el
.
empty
();
hasSuggestions
=
suggestions
&&
suggestions
.
length
;
if
(
!
hasSuggestions
&&
this
.
templates
.
empty
)
{
this
.
$el
.
html
(
getEmptyHtml
()).
prepend
(
that
.
templates
.
header
?
getHeaderHtml
()
:
null
).
append
(
that
.
templates
.
footer
?
getFooterHtml
()
:
null
);
}
else
if
(
hasSuggestions
)
{
this
.
$el
.
html
(
getSuggestionsHtml
()).
prepend
(
that
.
templates
.
header
?
getHeaderHtml
()
:
null
).
append
(
that
.
templates
.
footer
?
getFooterHtml
()
:
null
);
}
this
.
trigger
(
"rendered"
);
function
getEmptyHtml
()
{
return
that
.
templates
.
empty
({
query
:
query
});
}
function
getSuggestionsHtml
()
{
var
$suggestions
;
$suggestions
=
$
(
html
.
suggestions
).
css
(
css
.
suggestions
).
append
(
_
.
map
(
suggestions
,
getSuggestionNode
));
that
.
highlight
&&
highlight
({
node
:
$suggestions
[
0
],
pattern
:
query
});
return
$suggestions
;
function
getSuggestionNode
(
suggestion
)
{
var
$el
,
innerHtml
,
outerHtml
;
innerHtml
=
that
.
templates
.
suggestion
(
suggestion
);
outerHtml
=
html
.
suggestion
.
replace
(
"%BODY%"
,
innerHtml
);
$el
=
$
(
outerHtml
).
data
(
datasetKey
,
that
.
name
).
data
(
valueKey
,
suggestion
[
that
.
valueKey
]).
data
(
datumKey
,
suggestion
);
$el
.
children
().
each
(
function
()
{
$
(
this
).
css
(
css
.
suggestionChild
);
});
return
$el
;
}
}
function
getHeaderHtml
()
{
return
that
.
templates
.
header
({
query
:
query
,
isEmpty
:
!
hasSuggestions
});
}
function
getFooterHtml
()
{
return
that
.
templates
.
footer
({
query
:
query
,
isEmpty
:
!
hasSuggestions
});
}
},
getRoot
:
function
getRoot
()
{
return
this
.
$el
;
},
update
:
function
update
(
query
)
{
var
that
=
this
;
this
.
query
=
query
;
this
.
source
(
query
,
renderIfQueryIsSame
);
function
renderIfQueryIsSame
(
suggestions
)
{
query
===
that
.
query
&&
that
.
_render
(
query
,
suggestions
);
}
},
clear
:
function
clear
()
{
this
.
_render
(
this
.
query
||
""
);
},
isEmpty
:
function
isEmpty
()
{
return
this
.
$el
.
is
(
":empty"
);
},
destroy
:
function
destroy
()
{
this
.
$el
=
null
;
}
});
return
Dataset
;
function
getTemplates
(
templates
,
valueKey
)
{
return
{
empty
:
templates
.
empty
&&
_
.
templatify
(
templates
.
empty
),
header
:
templates
.
header
&&
_
.
templatify
(
templates
.
header
),
footer
:
templates
.
footer
&&
_
.
templatify
(
templates
.
footer
),
suggestion
:
templates
.
suggestion
||
suggestionTemplate
};
function
suggestionTemplate
(
context
)
{
return
"<p>"
+
context
[
valueKey
]
+
"</p>"
;
}
}
}();
var
Dropdown
=
function
()
{
function
Dropdown
(
o
)
{
var
that
=
this
,
onMouseEnter
,
onMouseLeave
,
onSuggestionClick
,
onSuggestionMouseEnter
,
onSuggestionMouseLeave
;
o
=
o
||
{};
if
(
!
o
.
menu
)
{
$
.
error
(
"menu is required"
);
}
this
.
isOpen
=
false
;
this
.
isEmpty
=
true
;
this
.
isMouseOverDropdown
=
false
;
this
.
$menu
=
$
(
o
.
menu
).
on
(
"mouseenter.tt"
,
this
.
_handleMouseenter
).
on
(
"mouseleave.tt"
,
this
.
_handleMouseleave
).
on
(
"click.tt"
,
".tt-suggestion"
,
this
.
_handleSelection
).
on
(
"mouseover.tt"
,
".tt-suggestion"
,
this
.
_handleMouseover
);
this
.
datasets
=
_
.
map
(
o
.
datasets
,
initializeDataset
);
onMouseEnter
=
_
.
bind
(
this
.
_onMouseEnter
,
this
);
onMouseLeave
=
_
.
bind
(
this
.
_onMouseLeave
,
this
);
onSuggestionClick
=
_
.
bind
(
this
.
_onSuggestionClick
,
this
);
onSuggestionMouseEnter
=
_
.
bind
(
this
.
_onSuggestionMouseEnter
,
this
);
onSuggestionMouseLeave
=
_
.
bind
(
this
.
_onSuggestionMouseLeave
,
this
);
this
.
$menu
=
$
(
o
.
menu
).
on
(
"mouseenter.tt"
,
onMouseEnter
).
on
(
"mouseleave.tt"
,
onMouseLeave
).
on
(
"click.tt"
,
".tt-suggestion"
,
onSuggestionClick
).
on
(
"mouseenter.tt"
,
".tt-suggestion"
,
onSuggestionMouseEnter
).
on
(
"mouseleave.tt"
,
".tt-suggestion"
,
onSuggestionMouseLeave
);
_
.
each
(
this
.
datasets
,
function
(
dataset
)
{
that
.
$menu
.
append
(
dataset
.
getRoot
());
dataset
.
onSync
(
"rendered"
,
that
.
_onRendered
,
that
);
});
}
utils
.
mixin
(
DropdownView
.
prototype
,
EventTarget
,
{
_
handleMouseenter
:
function
(
)
{
_
.
mixin
(
Dropdown
.
prototype
,
EventEmitter
,
{
_
onMouseEnter
:
function
onMouseEnter
(
$e
)
{
this
.
isMouseOverDropdown
=
true
;
},
_
handleMouseleave
:
function
(
)
{
_
onMouseLeave
:
function
onMouseLeave
(
$e
)
{
this
.
isMouseOverDropdown
=
false
;
},
_handleMouseover
:
function
(
$e
)
{
var
$suggestion
=
$
(
$e
.
currentTarget
);
this
.
_getSuggestions
().
removeClass
(
"tt-is-under-cursor"
);
$suggestion
.
addClass
(
"tt-is-under-cursor"
);
_onSuggestionClick
:
function
onSuggestionClick
(
$e
)
{
this
.
trigger
(
"suggestionClicked"
,
$
(
$e
.
currentTarget
));
},
_
handleSelection
:
function
(
$e
)
{
var
$suggestion
=
$
(
$e
.
currentTarget
);
this
.
trigger
(
"suggestionSelected"
,
extractSuggestion
(
$suggestion
)
);
_
onSuggestionMouseEnter
:
function
onSuggestionMouseEnter
(
$e
)
{
this
.
_removeCursor
(
);
this
.
_setCursor
(
$
(
$e
.
currentTarget
),
true
);
},
_show
:
function
()
{
this
.
$menu
.
css
(
"display"
,
"block"
);
_onSuggestionMouseLeave
:
function
onSuggestionMouseLeave
(
$e
)
{
this
.
_removeCursor
();
},
_onRendered
:
function
onRendered
()
{
this
.
isEmpty
=
_
.
every
(
this
.
datasets
,
isDatasetEmpty
);
this
.
isEmpty
?
this
.
_hide
()
:
this
.
isOpen
&&
this
.
_show
();
this
.
trigger
(
"datasetRendered"
);
function
isDatasetEmpty
(
dataset
)
{
return
dataset
.
isEmpty
();
}
},
_hide
:
function
()
{
this
.
$menu
.
hide
();
},
_moveCursor
:
function
(
increment
)
{
var
$suggestions
,
$cur
,
nextIndex
,
$underCursor
;
if
(
!
this
.
isVisible
())
{
_show
:
function
()
{
this
.
$menu
.
css
(
"display"
,
"block"
);
},
_getSuggestions
:
function
getSuggestions
()
{
return
this
.
$menu
.
find
(
".tt-suggestion"
);
},
_getCursor
:
function
getCursor
()
{
return
this
.
$menu
.
find
(
".tt-cursor"
).
first
();
},
_setCursor
:
function
setCursor
(
$el
,
silent
)
{
$el
.
first
().
addClass
(
"tt-cursor"
);
!
silent
&&
this
.
trigger
(
"cursorMoved"
);
},
_removeCursor
:
function
removeCursor
()
{
this
.
_getCursor
().
removeClass
(
"tt-cursor"
);
},
_moveCursor
:
function
moveCursor
(
increment
)
{
var
$suggestions
,
$oldCursor
,
newCursorIndex
,
$newCursor
;
if
(
!
this
.
isOpen
)
{
return
;
}
$oldCursor
=
this
.
_getCursor
();
$suggestions
=
this
.
_getSuggestions
();
$cur
=
$suggestions
.
filter
(
".tt-is-under-cursor"
);
$cur
.
removeClass
(
"tt-is-under-cursor"
);
nextIndex
=
$suggestions
.
index
(
$cur
)
+
increment
;
nextIndex
=
(
nextIndex
+
1
)
%
(
$suggestions
.
length
+
1
)
-
1
;
if
(
nextIndex
===
-
1
)
{
this
.
_removeCursor
();
newCursorIndex
=
$suggestions
.
index
(
$oldCursor
)
+
increment
;
newCursorIndex
=
(
newCursorIndex
+
1
)
%
(
$suggestions
.
length
+
1
)
-
1
;
if
(
newCursorIndex
===
-
1
)
{
this
.
trigger
(
"cursorRemoved"
);
return
;
}
else
if
(
ne
xt
Index
<
-
1
)
{
ne
xt
Index
=
$suggestions
.
length
-
1
;
}
else
if
(
ne
wCursor
Index
<
-
1
)
{
ne
wCursor
Index
=
$suggestions
.
length
-
1
;
}
$underCursor
=
$suggestions
.
eq
(
nextIndex
).
addClass
(
"tt-is-under-cursor"
);
this
.
_ensureVisib
ility
(
$under
Cursor
);
this
.
trigger
(
"cursorMoved"
,
extractSuggestion
(
$underCursor
));
},
_getSuggestions
:
function
()
{
return
this
.
$menu
.
find
(
".tt-suggestions > .tt-suggestion"
)
;
},
_ensureVisibility
:
function
(
$el
)
{
var
menuHeight
=
this
.
$menu
.
height
()
+
parseInt
(
this
.
$menu
.
css
(
"paddingTop"
),
10
)
+
parseInt
(
this
.
$menu
.
css
(
"paddingBottom"
),
10
),
menuScrollTop
=
this
.
$menu
.
scrollTop
(),
elTop
=
$el
.
position
().
top
,
elBottom
=
elTop
+
$el
.
outerHeight
(
true
);
this
.
_setCursor
(
$newCursor
=
$suggestions
.
eq
(
newCursorIndex
)
);
this
.
_ensureVisib
le
(
$new
Cursor
);
},
_ensureVisible
:
function
ensureVisible
(
$el
)
{
var
elTop
,
elBottom
,
menuScrollTop
,
menuHeight
;
elTop
=
$el
.
position
().
top
;
elBottom
=
elTop
+
$el
.
outerHeight
(
true
);
menuScrollTop
=
this
.
$menu
.
scrollTop
();
menuHeight
=
this
.
$menu
.
height
()
+
parseInt
(
this
.
$menu
.
css
(
"paddingTop"
),
10
)
+
parseInt
(
this
.
$menu
.
css
(
"paddingBottom"
),
10
);
if
(
elTop
<
0
)
{
this
.
$menu
.
scrollTop
(
menuScrollTop
+
elTop
);
}
else
if
(
menuHeight
<
elBottom
)
{
this
.
$menu
.
scrollTop
(
menuScrollTop
+
(
elBottom
-
menuHeight
));
}
},
destroy
:
function
()
{
this
.
$menu
.
off
(
".tt"
);
this
.
$menu
=
null
;
},
isVisible
:
function
()
{
return
this
.
isOpen
&&
!
this
.
isEmpty
;
},
closeUnlessMouseIsOverDropdown
:
function
()
{
if
(
!
this
.
isMouseOverDropdown
)
{
this
.
close
();
}
},
close
:
function
()
{
close
:
function
close
()
{
if
(
this
.
isOpen
)
{
this
.
isOpen
=
false
;
this
.
isMouseOverDropdown
=
false
;
this
.
isOpen
=
this
.
isMouseOverDropdown
=
false
;
this
.
_removeCursor
()
;
this
.
_hide
();
this
.
$menu
.
find
(
".tt-suggestions > .tt-suggestion"
).
removeClass
(
"tt-is-under-cursor"
);
this
.
trigger
(
"closed"
);
}
},
open
:
function
()
{
open
:
function
open
()
{
if
(
!
this
.
isOpen
)
{
this
.
isOpen
=
true
;
!
this
.
isEmpty
&&
this
.
_show
();
this
.
trigger
(
"opened"
);
}
},
setLanguageDirection
:
function
(
dir
)
{
var
ltrCss
=
{
left
:
"0"
,
right
:
"auto"
},
rtlCss
=
{
left
:
"auto"
,
right
:
" 0"
};
dir
===
"ltr"
?
this
.
$menu
.
css
(
ltrCss
)
:
this
.
$menu
.
css
(
rtlCss
);
setLanguageDirection
:
function
setLanguageDirection
(
dir
)
{
this
.
$menu
.
css
(
dir
===
"ltr"
?
css
.
ltr
:
css
.
rtl
);
},
moveCursorUp
:
function
()
{
moveCursorUp
:
function
moveCursorUp
()
{
this
.
_moveCursor
(
-
1
);
},
moveCursorDown
:
function
()
{
moveCursorDown
:
function
moveCursorDown
()
{
this
.
_moveCursor
(
+
1
);
},
getSuggestionUnderCursor
:
function
()
{
var
$suggestion
=
this
.
_getSuggestions
().
filter
(
".tt-is-under-cursor"
).
first
();
return
$suggestion
.
length
>
0
?
extractSuggestion
(
$suggestion
)
:
null
;
},
getFirstSuggestion
:
function
()
{
var
$suggestion
=
this
.
_getSuggestions
().
first
();
return
$suggestion
.
length
>
0
?
extractSuggestion
(
$suggestion
)
:
null
;
},
renderSuggestions
:
function
(
dataset
,
suggestions
)
{
var
datasetClassName
=
"tt-dataset-"
+
dataset
.
name
,
wrapper
=
'<div class="tt-suggestion">%body</div>'
,
compiledHtml
,
$suggestionsList
,
$dataset
=
this
.
$menu
.
find
(
"."
+
datasetClassName
),
elBuilder
,
fragment
,
$el
;
if
(
$dataset
.
length
===
0
)
{
$suggestionsList
=
$
(
html
.
suggestionsList
).
css
(
css
.
suggestionsList
);
$dataset
=
$
(
"<div></div>"
).
addClass
(
datasetClassName
).
append
(
dataset
.
header
).
append
(
$suggestionsList
).
append
(
dataset
.
footer
).
appendTo
(
this
.
$menu
);
}
if
(
suggestions
.
length
>
0
)
{
this
.
isEmpty
=
false
;
this
.
isOpen
&&
this
.
_show
();
elBuilder
=
document
.
createElement
(
"div"
);
fragment
=
document
.
createDocumentFragment
();
utils
.
each
(
suggestions
,
function
(
i
,
suggestion
)
{
suggestion
.
dataset
=
dataset
.
name
;
compiledHtml
=
dataset
.
template
(
suggestion
.
datum
);
elBuilder
.
innerHTML
=
wrapper
.
replace
(
"%body"
,
compiledHtml
);
$el
=
$
(
elBuilder
.
firstChild
).
css
(
css
.
suggestion
).
data
(
"suggestion"
,
suggestion
);
$el
.
children
().
each
(
function
()
{
$
(
this
).
css
(
css
.
suggestionChild
);
});
fragment
.
appendChild
(
$el
[
0
]);
});
$dataset
.
show
().
find
(
".tt-suggestions"
).
html
(
fragment
);
}
else
{
this
.
clearSuggestions
(
dataset
.
name
);
getDatumForSuggestion
:
function
getDatumForSuggestion
(
$el
)
{
var
datum
=
null
;
if
(
$el
.
length
)
{
datum
=
{
raw
:
Dataset
.
extractDatum
(
$el
),
value
:
Dataset
.
extractValue
(
$el
),
datasetName
:
Dataset
.
extractDatasetName
(
$el
)
};
}
this
.
trigger
(
"suggestionsRendered"
)
;
return
datum
;
},
clearSuggestions
:
function
(
datasetName
)
{
var
$datasets
=
datasetName
?
this
.
$menu
.
find
(
".tt-dataset-"
+
datasetName
)
:
this
.
$menu
.
find
(
'[class^="tt-dataset-"]'
),
$suggestions
=
$datasets
.
find
(
".tt-suggestions"
);
$datasets
.
hide
();
$suggestions
.
empty
();
if
(
this
.
_getSuggestions
().
length
===
0
)
{
this
.
isEmpty
=
true
;
this
.
_hide
();
getDatumForCursor
:
function
getDatumForCursor
()
{
return
this
.
getDatumForSuggestion
(
this
.
_getCursor
().
first
());
},
getDatumForTopSuggestion
:
function
getDatumForTopSuggestion
()
{
return
this
.
getDatumForSuggestion
(
this
.
_getSuggestions
().
first
());
},
update
:
function
update
(
query
)
{
_
.
each
(
this
.
datasets
,
updateDataset
);
function
updateDataset
(
dataset
)
{
dataset
.
update
(
query
);
}
},
empty
:
function
empty
()
{
_
.
each
(
this
.
datasets
,
clearDataset
);
function
clearDataset
(
dataset
)
{
dataset
.
clear
();
}
},
isVisible
:
function
isVisible
()
{
return
this
.
isOpen
&&
!
this
.
isEmpty
;
},
destroy
:
function
destroy
()
{
this
.
$menu
.
off
(
".tt"
);
this
.
$menu
=
null
;
_
.
each
(
this
.
datasets
,
destroyDataset
);
function
destroyDataset
(
dataset
)
{
dataset
.
destroy
();
}
}
});
return
Dropdown
View
;
function
extractSuggestion
(
$el
)
{
return
$el
.
data
(
"suggestion"
);
return
Dropdown
;
function
initializeDataset
(
oDataset
)
{
return
new
Dataset
(
oDataset
);
}
}();
var
TypeaheadView
=
function
()
{
var
html
=
{
wrapper
:
'<span class="twitter-typeahead"></span>'
,
hint
:
'<input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled>'
,
dropdown
:
'<span class="tt-dropdown-menu"></span>'
},
css
=
{
wrapper
:
{
position
:
"relative"
,
display
:
"inline-block"
},
hint
:
{
position
:
"absolute"
,
top
:
"0"
,
left
:
"0"
,
borderColor
:
"transparent"
,
boxShadow
:
"none"
},
query
:
{
position
:
"relative"
,
verticalAlign
:
"top"
,
backgroundColor
:
"transparent"
},
dropdown
:
{
position
:
"absolute"
,
top
:
"100%"
,
left
:
"0"
,
zIndex
:
"100"
,
display
:
"none"
var
Typeahead
=
function
()
{
var
attrsKey
=
"ttAttrs"
;
function
Typeahead
(
o
)
{
var
$menu
,
$input
,
$hint
,
datasets
;
o
=
o
||
{};
if
(
!
o
.
input
)
{
$
.
error
(
"missing input"
);
}
};
if
(
utils
.
isMsie
())
{
utils
.
mixin
(
css
.
query
,
{
backgroundImage
:
"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
});
}
if
(
utils
.
isMsie
()
&&
utils
.
isMsie
()
<=
7
)
{
utils
.
mixin
(
css
.
wrapper
,
{
display
:
"inline"
,
zoom
:
"1"
});
utils
.
mixin
(
css
.
query
,
{
marginTop
:
"-1px"
});
}
function
TypeaheadView
(
o
)
{
var
$menu
,
$input
,
$hint
;
utils
.
bindAll
(
this
);
this
.
$node
=
buildDomStructure
(
o
.
input
);
this
.
datasets
=
o
.
datasets
;
this
.
dir
=
null
;
this
.
eventBus
=
o
.
eventBus
;
this
.
autoselect
=
!!
o
.
autoselect
;
this
.
minLength
=
_
.
isNumber
(
o
.
minLength
)
?
o
.
minLength
:
1
;
this
.
$node
=
buildDomStructure
(
o
.
input
,
o
.
withHint
);
$menu
=
this
.
$node
.
find
(
".tt-dropdown-menu"
);
$input
=
this
.
$node
.
find
(
".tt-
query
"
);
$input
=
this
.
$node
.
find
(
".tt-
input
"
);
$hint
=
this
.
$node
.
find
(
".tt-hint"
);
this
.
dropdownView
=
new
DropdownView
({
menu
:
$menu
}).
on
(
"suggestionSelected"
,
this
.
_handleSelection
).
on
(
"cursorMoved"
,
this
.
_clearHint
).
on
(
"cursorMoved"
,
this
.
_setInputValueToSuggestionUnderCursor
).
on
(
"cursorRemoved"
,
this
.
_setInputValueToQuery
).
on
(
"cursorRemoved"
,
this
.
_updateHint
).
on
(
"suggestionsRendered"
,
this
.
_updateHint
).
on
(
"opened"
,
this
.
_updateHint
).
on
(
"closed"
,
this
.
_clearHint
).
on
(
"opened closed"
,
this
.
_propagateEvent
);
this
.
inputView
=
new
InputView
({
this
.
eventBus
=
o
.
eventBus
||
new
EventBus
({
el
:
$input
});
this
.
dropdown
=
new
Dropdown
({
menu
:
$menu
,
datasets
:
o
.
datasets
}).
onSync
(
"suggestionClicked"
,
this
.
_onSuggestionClicked
,
this
).
onSync
(
"cursorMoved"
,
this
.
_onCursorMoved
,
this
).
onSync
(
"cursorRemoved"
,
this
.
_onCursorRemoved
,
this
).
onSync
(
"opened"
,
this
.
_onOpened
,
this
).
onSync
(
"closed"
,
this
.
_onClosed
,
this
).
onAsync
(
"datasetRendered"
,
this
.
_onDatasetRendered
,
this
);
this
.
input
=
new
Input
({
input
:
$input
,
hint
:
$hint
}).
on
(
"focused"
,
this
.
_openDropdown
).
on
(
"blured"
,
this
.
_closeDropdown
).
on
(
"blured"
,
this
.
_setInputValueToQuery
).
on
(
"enterKeyed tabKeyed"
,
this
.
_handleSelection
).
on
(
"queryChanged"
,
this
.
_clearHint
).
on
(
"queryChanged"
,
this
.
_clearSuggestions
).
on
(
"queryChanged"
,
this
.
_getSuggestions
).
on
(
"whitespaceChanged"
,
this
.
_updateHint
).
on
(
"queryChanged whitespaceChanged"
,
this
.
_openDropdown
).
on
(
"queryChanged whitespaceChanged"
,
this
.
_setLanguageDirection
).
on
(
"escKeyed"
,
this
.
_closeDropdown
).
on
(
"escKeyed"
,
this
.
_setInputValueToQuery
).
on
(
"tabKeyed upKeyed downKeyed"
,
this
.
_managePreventDefault
).
on
(
"upKeyed downKeyed"
,
this
.
_moveDropdownCursor
).
on
(
"upKeyed downKeyed"
,
this
.
_openDropdown
).
on
(
"tabKeyed leftKeyed rightKeyed"
,
this
.
_autocomplete
);
}).
onSync
(
"focused"
,
this
.
_onFocused
,
this
).
onSync
(
"blurred"
,
this
.
_onBlurred
,
this
).
onSync
(
"enterKeyed"
,
this
.
_onEnterKeyed
,
this
).
onSync
(
"tabKeyed"
,
this
.
_onTabKeyed
,
this
).
onSync
(
"escKeyed"
,
this
.
_onEscKeyed
,
this
).
onSync
(
"upKeyed"
,
this
.
_onUpKeyed
,
this
).
onSync
(
"downKeyed"
,
this
.
_onDownKeyed
,
this
).
onSync
(
"leftKeyed"
,
this
.
_onLeftKeyed
,
this
).
onSync
(
"rightKeyed"
,
this
.
_onRightKeyed
,
this
).
onSync
(
"queryChanged"
,
this
.
_onQueryChanged
,
this
).
onSync
(
"whitespaceChanged"
,
this
.
_onWhitespaceChanged
,
this
);
$menu
.
on
(
"mousedown.tt"
,
function
(
$e
)
{
if
(
_
.
isMsie
()
&&
_
.
isMsie
()
<
9
)
{
$input
[
0
].
onbeforedeactivate
=
function
()
{
window
.
event
.
returnValue
=
false
;
$input
[
0
].
onbeforedeactivate
=
null
;
};
}
$e
.
preventDefault
();
});
}
utils
.
mixin
(
TypeaheadView
.
prototype
,
EventTarget
,
{
_managePreventDefault
:
function
(
e
)
{
var
$e
=
e
.
data
,
hint
,
inputValue
,
preventDefault
=
false
;
switch
(
e
.
type
)
{
case
"tabKeyed"
:
hint
=
this
.
inputView
.
getHintValue
();
inputValue
=
this
.
inputView
.
getInputValue
();
preventDefault
=
hint
&&
hint
!==
inputValue
;
break
;
case
"upKeyed"
:
case
"downKeyed"
:
preventDefault
=
!
$e
.
shiftKey
&&
!
$e
.
ctrlKey
&&
!
$e
.
metaKey
;
break
;
_
.
mixin
(
Typeahead
.
prototype
,
{
_onSuggestionClicked
:
function
onSuggestionClicked
(
type
,
$el
)
{
var
datum
;
if
(
datum
=
this
.
dropdown
.
getDatumForSuggestion
(
$el
))
{
this
.
_select
(
datum
);
}
preventDefault
&&
$e
.
preventDefault
();
},
_setLanguageDirection
:
function
()
{
var
dir
=
this
.
inputView
.
getLanguageDirection
();
if
(
dir
!==
this
.
dir
)
{
this
.
dir
=
dir
;
this
.
$node
.
css
(
"direction"
,
dir
);
this
.
dropdownView
.
setLanguageDirection
(
dir
);
_onCursorMoved
:
function
onCursorMoved
()
{
var
datum
=
this
.
dropdown
.
getDatumForCursor
();
this
.
input
.
clearHint
();
this
.
input
.
setInputValue
(
datum
.
value
,
true
);
this
.
eventBus
.
trigger
(
"cursorchanged"
,
datum
.
raw
,
datum
.
datasetName
);
},
_onCursorRemoved
:
function
onCursorRemoved
()
{
this
.
input
.
resetInputValue
();
this
.
_updateHint
();
},
_onDatasetRendered
:
function
onDatasetRendered
()
{
this
.
_updateHint
();
},
_onOpened
:
function
onOpened
()
{
this
.
_updateHint
();
this
.
eventBus
.
trigger
(
"opened"
);
},
_onClosed
:
function
onClosed
()
{
this
.
input
.
clearHint
();
this
.
eventBus
.
trigger
(
"closed"
);
},
_onFocused
:
function
onFocused
()
{
this
.
dropdown
.
open
();
},
_onBlurred
:
function
onBlurred
()
{
!
this
.
dropdown
.
isMouseOverDropdown
&&
this
.
dropdown
.
close
();
},
_onEnterKeyed
:
function
onEnterKeyed
(
type
,
$e
)
{
var
cursorDatum
,
topSuggestionDatum
;
cursorDatum
=
this
.
dropdown
.
getDatumForCursor
();
topSuggestionDatum
=
this
.
dropdown
.
getDatumForTopSuggestion
();
if
(
cursorDatum
)
{
this
.
_select
(
cursorDatum
);
$e
.
preventDefault
();
}
else
if
(
this
.
autoselect
&&
topSuggestionDatum
)
{
this
.
_select
(
topSuggestionDatum
);
$e
.
preventDefault
();
}
},
_updateHint
:
function
()
{
var
suggestion
=
this
.
dropdownView
.
getFirstSuggestion
(),
hint
=
suggestion
?
suggestion
.
value
:
null
,
dropdownIsVisible
=
this
.
dropdownView
.
isVisible
(),
inputHasOverflow
=
this
.
inputView
.
isOverflow
(),
inputValue
,
query
,
escapedQuery
,
beginsWithQuery
,
match
;
if
(
hint
&&
dropdownIsVisible
&&
!
inputHasOverflow
)
{
inputValue
=
this
.
inputView
.
getInputValue
();
query
=
inputValue
.
replace
(
/
\s{2,}
/g
,
" "
).
replace
(
/^
\s
+/g
,
""
);
escapedQuery
=
utils
.
escapeRegExChars
(
query
);
beginsWithQuery
=
new
RegExp
(
"^(?:"
+
escapedQuery
+
")(.*$)"
,
"i"
);
match
=
beginsWithQuery
.
exec
(
hint
);
this
.
inputView
.
setHintValue
(
inputValue
+
(
match
?
match
[
1
]
:
""
));
_onTabKeyed
:
function
onTabKeyed
(
type
,
$e
)
{
var
datum
;
if
(
datum
=
this
.
dropdown
.
getDatumForCursor
())
{
this
.
_select
(
datum
);
$e
.
preventDefault
();
}
else
{
this
.
_autocomplete
();
}
},
_clearHint
:
function
()
{
this
.
inputView
.
setHintValue
(
""
);
_onEscKeyed
:
function
onEscKeyed
()
{
this
.
dropdown
.
close
();
this
.
input
.
resetInputValue
();
},
_clearSuggestions
:
function
()
{
this
.
dropdownView
.
clearSuggestions
();
_onUpKeyed
:
function
onUpKeyed
()
{
var
query
=
this
.
input
.
getQuery
();
if
(
!
this
.
dropdown
.
isOpen
&&
query
.
length
>=
this
.
minLength
)
{
this
.
dropdown
.
update
(
query
);
}
this
.
dropdown
.
open
();
this
.
dropdown
.
moveCursorUp
();
},
_setInputValueToQuery
:
function
()
{
this
.
inputView
.
setInputValue
(
this
.
inputView
.
getQuery
());
_onDownKeyed
:
function
onDownKeyed
()
{
var
query
=
this
.
input
.
getQuery
();
if
(
!
this
.
dropdown
.
isOpen
&&
query
.
length
>=
this
.
minLength
)
{
this
.
dropdown
.
update
(
query
);
}
this
.
dropdown
.
open
();
this
.
dropdown
.
moveCursorDown
();
},
_setInputValueToSuggestionUnderCursor
:
function
(
e
)
{
var
suggestion
=
e
.
data
;
this
.
inputView
.
setInputValue
(
suggestion
.
value
,
true
);
_onLeftKeyed
:
function
onLeftKeyed
()
{
this
.
dir
===
"rtl"
&&
this
.
_autocomplete
();
},
_o
penDropdown
:
function
()
{
this
.
d
ropdownView
.
open
();
_o
nRightKeyed
:
function
onRightKeyed
()
{
this
.
d
ir
===
"ltr"
&&
this
.
_autocomplete
();
},
_closeDropdown
:
function
(
e
)
{
this
.
dropdownView
[
e
.
type
===
"blured"
?
"closeUnlessMouseIsOverDropdown"
:
"close"
]();
_onQueryChanged
:
function
onQueryChanged
(
e
,
query
)
{
this
.
input
.
clearHint
();
this
.
dropdown
.
empty
();
query
.
length
>=
this
.
minLength
&&
this
.
dropdown
.
update
(
query
);
this
.
dropdown
.
open
();
this
.
_setLanguageDirection
();
},
_moveDropdownCursor
:
function
(
e
)
{
var
$e
=
e
.
data
;
if
(
!
$e
.
shiftKey
&&
!
$e
.
ctrlKey
&&
!
$e
.
metaKey
)
{
this
.
dropdownView
[
e
.
type
===
"upKeyed"
?
"moveCursorUp"
:
"moveCursorDown"
]();
}
_onWhitespaceChanged
:
function
onWhitespaceChanged
()
{
this
.
_updateHint
();
this
.
dropdown
.
open
();
},
_handleSelection
:
function
(
e
)
{
var
byClick
=
e
.
type
===
"suggestionSelected"
,
suggestion
=
byClick
?
e
.
data
:
this
.
dropdownView
.
getSuggestionUnderCursor
();
if
(
suggestion
)
{
this
.
inputView
.
setInputValue
(
suggestion
.
value
);
byClick
?
this
.
inputView
.
focus
()
:
e
.
data
.
preventDefault
();
byClick
&&
utils
.
isMsie
()
?
utils
.
defer
(
this
.
dropdownView
.
close
)
:
this
.
dropdownView
.
close
();
this
.
eventBus
.
trigger
(
"selected"
,
suggestion
.
datum
,
suggestion
.
dataset
);
_setLanguageDirection
:
function
setLanguageDirection
()
{
var
dir
;
if
(
this
.
dir
!==
(
dir
=
this
.
input
.
getLanguageDirection
()))
{
this
.
dir
=
dir
;
this
.
$node
.
css
(
"direction"
,
dir
);
this
.
dropdown
.
setLanguageDirection
(
dir
);
}
},
_getSuggestions
:
function
()
{
var
that
=
this
,
query
=
this
.
inputView
.
getQuery
();
if
(
utils
.
isBlankString
(
query
))
{
return
;
_updateHint
:
function
updateHint
()
{
var
datum
,
inputValue
,
query
,
escapedQuery
,
frontMatchRegEx
,
match
;
datum
=
this
.
dropdown
.
getDatumForTopSuggestion
();
if
(
datum
&&
this
.
dropdown
.
isVisible
()
&&
!
this
.
input
.
hasOverflow
())
{
inputValue
=
this
.
input
.
getInputValue
();
query
=
Input
.
normalizeQuery
(
inputValue
);
escapedQuery
=
_
.
escapeRegExChars
(
query
);
frontMatchRegEx
=
new
RegExp
(
"^(?:"
+
escapedQuery
+
")(.*$)"
,
"i"
);
match
=
frontMatchRegEx
.
exec
(
datum
.
value
);
this
.
input
.
setHintValue
(
inputValue
+
(
match
?
match
[
1
]
:
""
));
}
utils
.
each
(
this
.
datasets
,
function
(
i
,
dataset
)
{
dataset
.
getSuggestions
(
query
,
function
(
suggestions
)
{
if
(
query
===
that
.
inputView
.
getQuery
())
{
that
.
dropdownView
.
renderSuggestions
(
dataset
,
suggestions
);
}
});
});
},
_autocomplete
:
function
(
e
)
{
var
isCursorAtEnd
,
ignoreEvent
,
query
,
hint
,
suggestion
;
if
(
e
.
type
===
"rightKeyed"
||
e
.
type
===
"leftKeyed"
)
{
isCursorAtEnd
=
this
.
inputView
.
isCursorAtEnd
();
ignoreEvent
=
this
.
inputView
.
getLanguageDirection
()
===
"ltr"
?
e
.
type
===
"leftKeyed"
:
e
.
type
===
"rightKeyed"
;
if
(
!
isCursorAtEnd
||
ignoreEvent
)
{
return
;
}
}
query
=
this
.
inputView
.
getQuery
();
hint
=
this
.
inputView
.
getHintValue
();
if
(
hint
!==
""
&&
query
!==
hint
)
{
suggestion
=
this
.
dropdownView
.
getFirstSuggestion
();
this
.
inputView
.
setInputValue
(
suggestion
.
value
);
this
.
eventBus
.
trigger
(
"autocompleted"
,
suggestion
.
datum
,
suggestion
.
dataset
);
_autocomplete
:
function
autocomplete
()
{
var
hint
,
query
,
datum
;
hint
=
this
.
input
.
getHintValue
();
query
=
this
.
input
.
getQuery
();
if
(
hint
&&
query
!==
hint
&&
this
.
input
.
isCursorAtEnd
())
{
datum
=
this
.
dropdown
.
getDatumForTopSuggestion
();
datum
&&
this
.
input
.
setInputValue
(
datum
.
value
);
this
.
eventBus
.
trigger
(
"autocompleted"
,
datum
.
raw
,
datum
.
datasetName
);
}
},
_propagateEvent
:
function
(
e
)
{
this
.
eventBus
.
trigger
(
e
.
type
);
_select
:
function
select
(
datum
)
{
this
.
input
.
clearHint
();
this
.
input
.
setQuery
(
datum
.
value
);
this
.
input
.
setInputValue
(
datum
.
value
,
true
);
this
.
dropdown
.
empty
();
this
.
_setLanguageDirection
();
_
.
defer
(
_
.
bind
(
this
.
dropdown
.
close
,
this
.
dropdown
));
this
.
eventBus
.
trigger
(
"selected"
,
datum
.
raw
,
datum
.
datasetName
);
},
destroy
:
function
()
{
this
.
inputView
.
destroy
();
this
.
dropdownView
.
destroy
();
open
:
function
open
()
{
this
.
dropdown
.
open
();
},
close
:
function
close
()
{
this
.
dropdown
.
close
();
},
getQuery
:
function
getQuery
()
{
return
this
.
input
.
getQuery
();
},
setQuery
:
function
setQuery
(
val
)
{
this
.
input
.
setInputValue
(
val
);
},
destroy
:
function
destroy
()
{
this
.
input
.
destroy
();
this
.
dropdown
.
destroy
();
destroyDomStructure
(
this
.
$node
);
this
.
$node
=
null
;
},
setQuery
:
function
(
query
)
{
this
.
inputView
.
setQuery
(
query
);
this
.
inputView
.
setInputValue
(
query
);
this
.
_clearHint
();
this
.
_clearSuggestions
();
this
.
_getSuggestions
();
}
});
return
TypeaheadView
;
function
buildDomStructure
(
input
)
{
var
$wrapper
=
$
(
html
.
wrapper
),
$dropdown
=
$
(
html
.
dropdown
),
$input
=
$
(
input
),
$hint
=
$
(
html
.
hint
);
$wrapper
=
$wrapper
.
css
(
css
.
wrapper
);
$dropdown
=
$dropdown
.
css
(
css
.
dropdown
);
$hint
.
css
(
css
.
hint
).
css
({
backgroundAttachment
:
$input
.
css
(
"background-attachment"
),
backgroundClip
:
$input
.
css
(
"background-clip"
),
backgroundColor
:
$input
.
css
(
"background-color"
),
backgroundImage
:
$input
.
css
(
"background-image"
),
backgroundOrigin
:
$input
.
css
(
"background-origin"
),
backgroundPosition
:
$input
.
css
(
"background-position"
),
backgroundRepeat
:
$input
.
css
(
"background-repeat"
),
backgroundSize
:
$input
.
css
(
"background-size"
)
return
Typeahead
;
function
buildDomStructure
(
input
,
withHint
)
{
var
$input
,
$wrapper
,
$dropdown
,
$hint
;
$input
=
$
(
input
);
$wrapper
=
$
(
html
.
wrapper
).
css
(
css
.
wrapper
);
$dropdown
=
$
(
html
.
dropdown
).
css
(
css
.
dropdown
);
$hint
=
$input
.
clone
().
css
(
css
.
hint
).
css
(
getBackgroundStyles
(
$input
));
$hint
.
removeData
().
addClass
(
"tt-hint"
).
removeAttr
(
"id name placeholder"
).
prop
(
"disabled"
,
true
).
attr
({
autocomplete
:
"off"
,
spellcheck
:
"false"
});
$input
.
data
(
"ttAttrs"
,
{
$input
.
data
(
attrsKey
,
{
dir
:
$input
.
attr
(
"dir"
),
autocomplete
:
$input
.
attr
(
"autocomplete"
),
spellcheck
:
$input
.
attr
(
"spellcheck"
),
style
:
$input
.
attr
(
"style"
)
});
$input
.
addClass
(
"tt-
query
"
).
attr
({
$input
.
addClass
(
"tt-
input
"
).
attr
({
autocomplete
:
"off"
,
spellcheck
:
false
}).
css
(
css
.
query
);
}).
css
(
withHint
?
css
.
input
:
css
.
inputWithNoHint
);
try
{
!
$input
.
attr
(
"dir"
)
&&
$input
.
attr
(
"dir"
,
"auto"
);
}
catch
(
e
)
{}
return
$input
.
wrap
(
$wrapper
).
parent
().
prepend
(
$hint
).
append
(
$dropdown
);
return
$input
.
wrap
(
$wrapper
).
parent
().
prepend
(
withHint
?
$hint
:
null
).
append
(
$dropdown
);
}
function
getBackgroundStyles
(
$el
)
{
return
{
backgroundAttachment
:
$el
.
css
(
"background-attachment"
),
backgroundClip
:
$el
.
css
(
"background-clip"
),
backgroundColor
:
$el
.
css
(
"background-color"
),
backgroundImage
:
$el
.
css
(
"background-image"
),
backgroundOrigin
:
$el
.
css
(
"background-origin"
),
backgroundPosition
:
$el
.
css
(
"background-position"
),
backgroundRepeat
:
$el
.
css
(
"background-repeat"
),
backgroundSize
:
$el
.
css
(
"background-size"
)
};
}
function
destroyDomStructure
(
$node
)
{
var
$input
=
$node
.
find
(
".tt-
query
"
);
utils
.
each
(
$input
.
data
(
"ttAttrs"
),
function
(
key
,
val
)
{
utils
.
isUndefined
(
val
)
?
$input
.
removeAttr
(
key
)
:
$input
.
attr
(
key
,
val
);
var
$input
=
$node
.
find
(
".tt-
input
"
);
_
.
each
(
$input
.
data
(
attrsKey
),
function
(
val
,
key
)
{
_
.
isUndefined
(
val
)
?
$input
.
removeAttr
(
key
)
:
$input
.
attr
(
key
,
val
);
});
$input
.
detach
().
removeData
(
"ttAttrs"
).
removeClass
(
"tt-query
"
).
insertAfter
(
$node
);
$input
.
detach
().
removeData
(
attrsKey
).
removeClass
(
"tt-input
"
).
insertAfter
(
$node
);
$node
.
remove
();
}
}();
(
function
()
{
var
cache
=
{},
viewKey
=
"ttView"
,
methods
;
var
typeaheadKey
,
methods
;
typeaheadKey
=
"ttTypeahead"
;
methods
=
{
initialize
:
function
(
datasetDefs
)
{
var
datasets
;
datasetDefs
=
utils
.
isArray
(
datasetDefs
)
?
datasetDefs
:
[
datasetDefs
];
if
(
datasetDefs
.
length
===
0
)
{
$
.
error
(
"no datasets provided"
);
}
datasets
=
utils
.
map
(
datasetDefs
,
function
(
o
)
{
var
dataset
=
cache
[
o
.
name
]
?
cache
[
o
.
name
]
:
new
Dataset
(
o
);
if
(
o
.
name
)
{
cache
[
o
.
name
]
=
dataset
;
}
return
dataset
;
});
return
this
.
each
(
initialize
);
function
initialize
()
{
var
$input
=
$
(
this
),
deferreds
,
eventBus
=
new
EventBus
({
el
:
$input
});
deferreds
=
utils
.
map
(
datasets
,
function
(
dataset
)
{
return
dataset
.
initialize
();
initialize
:
function
initialize
(
o
)
{
var
datasets
=
[].
slice
.
call
(
arguments
,
1
);
o
=
o
||
{};
return
this
.
each
(
attach
);
function
attach
()
{
var
$input
=
$
(
this
),
eventBus
,
typeahead
;
_
.
each
(
datasets
,
function
(
d
)
{
d
.
highlight
=
!!
o
.
highlight
;
});
$input
.
data
(
viewKey
,
new
TypeaheadView
({
typeahead
=
new
Typeahead
({
input
:
$input
,
eventBus
:
eventBus
=
new
EventBus
({
el
:
$input
}),
withHint
:
_
.
isUndefined
(
o
.
hint
)
?
true
:
!!
o
.
hint
,
minLength
:
o
.
minLength
,
autoselect
:
o
.
autoselect
,
datasets
:
datasets
}));
$
.
when
.
apply
(
$
,
deferreds
).
always
(
function
()
{
utils
.
defer
(
function
()
{
eventBus
.
trigger
(
"initialized"
);
});
});
$input
.
data
(
typeaheadKey
,
typeahead
);
function
trigger
(
eventName
)
{
return
function
()
{
_
.
defer
(
function
()
{
eventBus
.
trigger
(
eventName
);
});
};
}
}
},
open
:
function
open
()
{
return
this
.
each
(
openTypeahead
);
function
openTypeahead
()
{
var
$input
=
$
(
this
),
typeahead
;
if
(
typeahead
=
$input
.
data
(
typeaheadKey
))
{
typeahead
.
open
();
}
}
},
destroy
:
function
()
{
return
this
.
each
(
destroy
);
function
destroy
()
{
var
$this
=
$
(
this
),
view
=
$this
.
data
(
viewKey
);
if
(
view
)
{
view
.
destroy
();
$this
.
removeData
(
viewKey
);
close
:
function
close
()
{
return
this
.
each
(
closeTypeahead
);
function
closeTypeahead
()
{
var
$input
=
$
(
this
),
typeahead
;
if
(
typeahead
=
$input
.
data
(
typeaheadKey
))
{
typeahead
.
close
();
}
}
},
setQuery
:
function
(
query
)
{
return
this
.
each
(
setQuery
);
val
:
function
val
(
newVal
)
{
return
_
.
isString
(
newVal
)
?
this
.
each
(
setQuery
)
:
this
.
map
(
getQuery
).
get
(
);
function
setQuery
()
{
var
view
=
$
(
this
).
data
(
viewKey
);
view
&&
view
.
setQuery
(
query
);
var
$input
=
$
(
this
),
typeahead
;
if
(
typeahead
=
$input
.
data
(
typeaheadKey
))
{
typeahead
.
setQuery
(
newVal
);
}
}
function
getQuery
()
{
var
$input
=
$
(
this
),
typeahead
,
query
;
if
(
typeahead
=
$input
.
data
(
typeaheadKey
))
{
query
=
typeahead
.
getQuery
();
}
return
query
;
}
},
destroy
:
function
destroy
()
{
return
this
.
each
(
unattach
);
function
unattach
()
{
var
$input
=
$
(
this
),
typeahead
;
if
(
typeahead
=
$input
.
data
(
typeaheadKey
))
{
typeahead
.
destroy
();
$input
.
removeData
(
typeaheadKey
);
}
}
}
};
...
...
extensions/gii/components/ActiveField.php
View file @
69f0e5be
...
...
@@ -63,7 +63,10 @@ class ActiveField extends \yii\widgets\ActiveField
{
static
$counter
=
0
;
$this
->
inputOptions
[
'class'
]
.=
' typeahead-'
.
(
++
$counter
);
$this
->
form
->
getView
()
->
registerJs
(
"jQuery('.typeahead-
{
$counter
}
').typeahead({local: "
.
Json
::
encode
(
$data
)
.
"});"
);
foreach
(
$data
as
&
$item
)
{
$item
=
array
(
'word'
=>
$item
);
}
$this
->
form
->
getView
()
->
registerJs
(
"yii.gii.autocomplete(
$counter
, "
.
Json
::
encode
(
$data
)
.
");"
);
return
$this
;
}
}
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