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
4cb1a264
Commit
4cb1a264
authored
Oct 30, 2013
by
resurtm
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixes #1104. Model generator table names auto complete.
parent
6559b06e
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1247 additions
and
1 deletion
+1247
-1
Generator.php
framework/yii/gii/Generator.php
+11
-0
GiiAsset.php
framework/yii/gii/GiiAsset.php
+2
-0
main.css
framework/yii/gii/assets/main.css
+8
-0
typeahead.js
framework/yii/gii/assets/typeahead.js
+1140
-0
typeahead.js-bootstrap.css
framework/yii/gii/assets/typeahead.js-bootstrap.css
+51
-0
ActiveField.php
framework/yii/gii/components/ActiveField.php
+23
-1
Generator.php
framework/yii/gii/generators/model/Generator.php
+12
-0
No files found.
framework/yii/gii/Generator.php
View file @
4cb1a264
...
...
@@ -111,6 +111,17 @@ abstract class Generator extends Model
}
/**
* Returns the list of auto complete values.
* The array keys are the attribute names, and the array values are the corresponding auto complete values.
* Auto complete values can also be callable typed in order one want to make postponed data generation.
* @return array the list of auto complete values
*/
public
function
autoCompleteData
()
{
return
[];
}
/**
* Returns the message to be displayed when the newly generated code is saved successfully.
* Child classes may override this method to customize the message.
* @return string the message to be displayed when the newly generated code is saved successfully.
...
...
framework/yii/gii/GiiAsset.php
View file @
4cb1a264
...
...
@@ -26,12 +26,14 @@ class GiiAsset extends AssetBundle
*/
public
$css
=
[
'main.css'
,
'typeahead.js-bootstrap.css'
,
];
/**
* @inheritdoc
*/
public
$js
=
[
'gii.js'
,
'typeahead.js'
,
];
/**
* @inheritdoc
...
...
framework/yii/gii/assets/main.css
View file @
4cb1a264
...
...
@@ -201,3 +201,11 @@ body {
.DifferencesInline
.ChangeReplace
del
{
background
:
#e99
;
}
/* additional styles for typeahead.js-bootstrap.css */
.twitter-typeahead
{
display
:
block
!important
;
}
.twitter-typeahead
.tt-hint
{
padding
:
6px
12px
!important
;
}
framework/yii/gii/assets/typeahead.js
0 → 100644
View file @
4cb1a264
/*!
* typeahead.js 0.9.3
* https://github.com/twitter/typeahead
* Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
*/
(
function
(
$
)
{
var
VERSION
=
"0.9.3"
;
var
utils
=
{
isMsie
:
function
()
{
var
match
=
/
(
msie
)
([\w
.
]
+
)
/i
.
exec
(
navigator
.
userAgent
);
return
match
?
parseInt
(
match
[
2
],
10
)
:
false
;
},
isBlankString
:
function
(
str
)
{
return
!
str
||
/^
\s
*$/
.
test
(
str
);
},
escapeRegExChars
:
function
(
str
)
{
return
str
.
replace
(
/
[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]
/g
,
"
\\
$&"
);
},
isString
:
function
(
obj
)
{
return
typeof
obj
===
"string"
;
},
isNumber
:
function
(
obj
)
{
return
typeof
obj
===
"number"
;
},
isArray
:
$
.
isArray
,
isFunction
:
$
.
isFunction
,
isObject
:
$
.
isPlainObject
,
isUndefined
:
function
(
obj
)
{
return
typeof
obj
===
"undefined"
;
},
bind
:
$
.
proxy
,
bindAll
:
function
(
obj
)
{
var
val
;
for
(
var
key
in
obj
)
{
$
.
isFunction
(
val
=
obj
[
key
])
&&
(
obj
[
key
]
=
$
.
proxy
(
val
,
obj
));
}
},
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
)
{
var
result
=
true
;
if
(
!
obj
)
{
return
result
;
}
$
.
each
(
obj
,
function
(
key
,
val
)
{
if
(
!
(
result
=
test
.
call
(
null
,
val
,
key
,
obj
)))
{
return
false
;
}
});
return
!!
result
;
},
some
:
function
(
obj
,
test
)
{
var
result
=
false
;
if
(
!
obj
)
{
return
result
;
}
$
.
each
(
obj
,
function
(
key
,
val
)
{
if
(
result
=
test
.
call
(
null
,
val
,
key
,
obj
))
{
return
false
;
}
});
return
!!
result
;
},
mixin
:
$
.
extend
,
getUniqueId
:
function
()
{
var
counter
=
0
;
return
function
()
{
return
counter
++
;
};
}(),
defer
:
function
(
fn
)
{
setTimeout
(
fn
,
0
);
},
debounce
:
function
(
func
,
wait
,
immediate
)
{
var
timeout
,
result
;
return
function
()
{
var
context
=
this
,
args
=
arguments
,
later
,
callNow
;
later
=
function
()
{
timeout
=
null
;
if
(
!
immediate
)
{
result
=
func
.
apply
(
context
,
args
);
}
};
callNow
=
immediate
&&
!
timeout
;
clearTimeout
(
timeout
);
timeout
=
setTimeout
(
later
,
wait
);
if
(
callNow
)
{
result
=
func
.
apply
(
context
,
args
);
}
return
result
;
};
},
throttle
:
function
(
func
,
wait
)
{
var
context
,
args
,
timeout
,
result
,
previous
,
later
;
previous
=
0
;
later
=
function
()
{
previous
=
new
Date
();
timeout
=
null
;
result
=
func
.
apply
(
context
,
args
);
};
return
function
()
{
var
now
=
new
Date
(),
remaining
=
wait
-
(
now
-
previous
);
context
=
this
;
args
=
arguments
;
if
(
remaining
<=
0
)
{
clearTimeout
(
timeout
);
timeout
=
null
;
previous
=
now
;
result
=
func
.
apply
(
context
,
args
);
}
else
if
(
!
timeout
)
{
timeout
=
setTimeout
(
later
,
remaining
);
}
return
result
;
};
},
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
;
}
this
.
_callbacks
=
this
.
_callbacks
||
{};
events
=
events
.
split
(
eventSplitter
);
while
(
event
=
events
.
shift
())
{
this
.
_callbacks
[
event
]
=
this
.
_callbacks
[
event
]
||
[];
this
.
_callbacks
[
event
].
push
(
callback
);
}
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
});
}
}
}
return
this
;
}
};
}();
var
EventBus
=
function
()
{
var
namespace
=
"typeahead:"
;
function
EventBus
(
o
)
{
if
(
!
o
||
!
o
.
el
)
{
$
.
error
(
"EventBus initialized without el"
);
}
this
.
$el
=
$
(
o
.
el
);
}
utils
.
mixin
(
EventBus
.
prototype
,
{
trigger
:
function
(
type
)
{
var
args
=
[].
slice
.
call
(
arguments
,
1
);
this
.
$el
.
trigger
(
namespace
+
type
,
args
);
}
});
return
EventBus
;
}();
var
PersistentStorage
=
function
()
{
var
ls
,
methods
;
try
{
ls
=
window
.
localStorage
;
ls
.
setItem
(
"~~~"
,
"!"
);
ls
.
removeItem
(
"~~~"
);
}
catch
(
err
)
{
ls
=
null
;
}
function
PersistentStorage
(
namespace
)
{
this
.
prefix
=
[
"__"
,
namespace
,
"__"
].
join
(
""
);
this
.
ttlKey
=
"__ttl__"
;
this
.
keyMatcher
=
new
RegExp
(
"^"
+
this
.
prefix
);
}
if
(
ls
&&
window
.
JSON
)
{
methods
=
{
_prefix
:
function
(
key
)
{
return
this
.
prefix
+
key
;
},
_ttlKey
:
function
(
key
)
{
return
this
.
_prefix
(
key
)
+
this
.
ttlKey
;
},
get
:
function
(
key
)
{
if
(
this
.
isExpired
(
key
))
{
this
.
remove
(
key
);
}
return
decode
(
ls
.
getItem
(
this
.
_prefix
(
key
)));
},
set
:
function
(
key
,
val
,
ttl
)
{
if
(
utils
.
isNumber
(
ttl
))
{
ls
.
setItem
(
this
.
_ttlKey
(
key
),
encode
(
now
()
+
ttl
));
}
else
{
ls
.
removeItem
(
this
.
_ttlKey
(
key
));
}
return
ls
.
setItem
(
this
.
_prefix
(
key
),
encode
(
val
));
},
remove
:
function
(
key
)
{
ls
.
removeItem
(
this
.
_ttlKey
(
key
));
ls
.
removeItem
(
this
.
_prefix
(
key
));
return
this
;
},
clear
:
function
()
{
var
i
,
key
,
keys
=
[],
len
=
ls
.
length
;
for
(
i
=
0
;
i
<
len
;
i
++
)
{
if
((
key
=
ls
.
key
(
i
)).
match
(
this
.
keyMatcher
))
{
keys
.
push
(
key
.
replace
(
this
.
keyMatcher
,
""
));
}
}
for
(
i
=
keys
.
length
;
i
--
;
)
{
this
.
remove
(
keys
[
i
]);
}
return
this
;
},
isExpired
:
function
(
key
)
{
var
ttl
=
decode
(
ls
.
getItem
(
this
.
_ttlKey
(
key
)));
return
utils
.
isNumber
(
ttl
)
&&
now
()
>
ttl
?
true
:
false
;
}
};
}
else
{
methods
=
{
get
:
utils
.
noop
,
set
:
utils
.
noop
,
remove
:
utils
.
noop
,
clear
:
utils
.
noop
,
isExpired
:
utils
.
noop
};
}
utils
.
mixin
(
PersistentStorage
.
prototype
,
methods
);
return
PersistentStorage
;
function
now
()
{
return
new
Date
().
getTime
();
}
function
encode
(
val
)
{
return
JSON
.
stringify
(
utils
.
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
;
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
);
}
utils
.
mixin
(
Transport
.
prototype
,
{
_get
:
function
(
url
,
cb
)
{
var
that
=
this
;
if
(
belowPendingRequestsThreshold
())
{
this
.
_sendRequest
(
url
).
done
(
done
);
}
else
{
this
.
onDeckRequestArgs
=
[].
slice
.
call
(
arguments
,
0
);
}
function
done
(
resp
)
{
var
data
=
that
.
filter
?
that
.
filter
(
resp
)
:
resp
;
cb
&&
cb
(
data
);
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
;
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
);
if
(
resp
=
requestCache
.
get
(
url
))
{
utils
.
defer
(
function
()
{
cb
(
that
.
filter
?
that
.
filter
(
resp
)
:
resp
);
});
}
else
{
this
.
_get
(
url
,
cb
);
}
return
!!
resp
;
}
});
return
Transport
;
function
incrementPendingRequests
()
{
pendingRequestsCount
++
;
}
function
decrementPendingRequests
()
{
pendingRequestsCount
--
;
}
function
belowPendingRequestsThreshold
()
{
return
pendingRequestsCount
<
maxPendingRequests
;
}
}();
var
Dataset
=
function
()
{
var
keys
=
{
thumbprint
:
"thumbprint"
,
protocol
:
"protocol"
,
itemHash
:
"itemHash"
,
adjacencyList
:
"adjacencyList"
};
function
Dataset
(
o
)
{
utils
.
bindAll
(
this
);
if
(
utils
.
isString
(
o
.
template
)
&&
!
o
.
engine
)
{
$
.
error
(
"no template engine specified"
);
}
if
(
!
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
.
local
;
this
.
prefetch
=
o
.
prefetch
;
this
.
remote
=
o
.
remote
;
this
.
itemHash
=
{};
this
.
adjacencyList
=
{};
this
.
storage
=
o
.
name
?
new
PersistentStorage
(
o
.
name
)
:
null
;
}
utils
.
mixin
(
Dataset
.
prototype
,
{
_processLocalData
:
function
(
data
)
{
this
.
_mergeProcessedData
(
this
.
_processData
(
data
));
},
_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
});
deferred
=
$
.
Deferred
().
resolve
();
}
else
{
deferred
=
$
.
getJSON
(
o
.
url
).
done
(
processPrefetchData
);
}
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
);
}
},
_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
;
}
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
;
});
},
_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
[];
}
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
;
},
initialize
:
function
()
{
var
deferred
;
this
.
local
&&
this
.
_processLocalData
(
this
.
local
);
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
;
};
return
deferred
;
},
getSuggestions
:
function
(
query
,
cb
)
{
var
that
=
this
,
terms
,
suggestions
,
cacheHit
=
false
;
if
(
query
.
length
<
this
.
minLength
)
{
return
;
}
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
;
});
!
isDuplicate
&&
suggestions
.
push
(
item
);
return
suggestions
.
length
<
that
.
limit
;
});
cb
&&
cb
(
suggestions
);
}
}
});
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
);
}
else
{
renderFn
=
function
(
context
)
{
return
"<p>"
+
context
[
valueKey
]
+
"</p>"
;
};
}
return
renderFn
;
}
}();
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"
};
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
);
}
else
{
this
.
$input
.
on
(
"keydown.tt keypress.tt cut.tt paste.tt"
,
function
(
$e
)
{
if
(
that
.
specialKeyCodeMap
[
$e
.
which
||
$e
.
keyCode
])
{
return
;
}
utils
.
defer
(
that
.
_compareQueryToInputValue
);
});
}
this
.
query
=
this
.
$input
.
val
();
this
.
$overflowHelper
=
buildOverflowHelper
(
this
.
$input
);
}
utils
.
mixin
(
InputView
.
prototype
,
EventTarget
,
{
_handleFocus
:
function
()
{
this
.
trigger
(
"focused"
);
},
_handleBlur
:
function
()
{
this
.
trigger
(
"blured"
);
},
_handleSpecialKeyEvent
:
function
(
$e
)
{
var
keyName
=
this
.
specialKeyCodeMap
[
$e
.
which
||
$e
.
keyCode
];
keyName
&&
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
});
}
},
destroy
:
function
()
{
this
.
$hint
.
off
(
".tt"
);
this
.
$input
.
off
(
".tt"
);
this
.
$hint
=
this
.
$input
=
this
.
$overflowHelper
=
null
;
},
focus
:
function
()
{
this
.
$input
.
focus
();
},
blur
:
function
()
{
this
.
$input
.
blur
();
},
getQuery
:
function
()
{
return
this
.
query
;
},
setQuery
:
function
(
query
)
{
this
.
query
=
query
;
},
getInputValue
:
function
()
{
return
this
.
$input
.
val
();
},
setInputValue
:
function
(
value
,
silent
)
{
this
.
$input
.
val
(
value
);
!
silent
&&
this
.
_compareQueryToInputValue
();
},
getHintValue
:
function
()
{
return
this
.
$hint
.
val
();
},
setHintValue
:
function
(
value
)
{
this
.
$hint
.
val
(
value
);
},
getLanguageDirection
:
function
()
{
return
(
this
.
$input
.
css
(
"direction"
)
||
"ltr"
).
toLowerCase
();
},
isOverflow
:
function
()
{
this
.
$overflowHelper
.
text
(
this
.
getInputValue
());
return
this
.
$overflowHelper
.
width
()
>
this
.
$input
.
width
();
},
isCursorAtEnd
:
function
()
{
var
valueLength
=
this
.
$input
.
val
().
length
,
selectionStart
=
this
.
$input
[
0
].
selectionStart
,
range
;
if
(
utils
.
isNumber
(
selectionStart
))
{
return
selectionStart
===
valueLength
;
}
else
if
(
document
.
selection
)
{
range
=
document
.
selection
.
createRange
();
range
.
moveStart
(
"character"
,
-
valueLength
);
return
valueLength
===
range
.
text
.
length
;
}
return
true
;
}
});
return
InputView
;
function
buildOverflowHelper
(
$input
)
{
return
$
(
"<span></span>"
).
css
({
position
:
"absolute"
,
left
:
"-9999px"
,
visibility
:
"hidden"
,
whiteSpace
:
"nowrap"
,
fontFamily
:
$input
.
css
(
"font-family"
),
fontSize
:
$input
.
css
(
"font-size"
),
fontStyle
:
$input
.
css
(
"font-style"
),
fontVariant
:
$input
.
css
(
"font-variant"
),
fontWeight
:
$input
.
css
(
"font-weight"
),
wordSpacing
:
$input
.
css
(
"word-spacing"
),
letterSpacing
:
$input
.
css
(
"letter-spacing"
),
textIndent
:
$input
.
css
(
"text-indent"
),
textRendering
:
$input
.
css
(
"text-rendering"
),
textTransform
:
$input
.
css
(
"text-transform"
)
}).
insertAfter
(
$input
);
}
function
compareQueries
(
a
,
b
)
{
a
=
(
a
||
""
).
replace
(
/^
\s
*/g
,
""
).
replace
(
/
\s{2,}
/g
,
" "
);
b
=
(
b
||
""
).
replace
(
/^
\s
*/g
,
""
).
replace
(
/
\s{2,}
/g
,
" "
);
return
a
===
b
;
}
}();
var
DropdownView
=
function
()
{
var
html
=
{
suggestionsList
:
'<span class="tt-suggestions"></span>'
},
css
=
{
suggestionsList
:
{
display
:
"block"
},
suggestion
:
{
whiteSpace
:
"nowrap"
,
cursor
:
"pointer"
},
suggestionChild
:
{
whiteSpace
:
"normal"
}
};
function
DropdownView
(
o
)
{
utils
.
bindAll
(
this
);
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
);
}
utils
.
mixin
(
DropdownView
.
prototype
,
EventTarget
,
{
_handleMouseenter
:
function
()
{
this
.
isMouseOverDropdown
=
true
;
},
_handleMouseleave
:
function
()
{
this
.
isMouseOverDropdown
=
false
;
},
_handleMouseover
:
function
(
$e
)
{
var
$suggestion
=
$
(
$e
.
currentTarget
);
this
.
_getSuggestions
().
removeClass
(
"tt-is-under-cursor"
);
$suggestion
.
addClass
(
"tt-is-under-cursor"
);
},
_handleSelection
:
function
(
$e
)
{
var
$suggestion
=
$
(
$e
.
currentTarget
);
this
.
trigger
(
"suggestionSelected"
,
extractSuggestion
(
$suggestion
));
},
_show
:
function
()
{
this
.
$menu
.
css
(
"display"
,
"block"
);
},
_hide
:
function
()
{
this
.
$menu
.
hide
();
},
_moveCursor
:
function
(
increment
)
{
var
$suggestions
,
$cur
,
nextIndex
,
$underCursor
;
if
(
!
this
.
isVisible
())
{
return
;
}
$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
.
trigger
(
"cursorRemoved"
);
return
;
}
else
if
(
nextIndex
<
-
1
)
{
nextIndex
=
$suggestions
.
length
-
1
;
}
$underCursor
=
$suggestions
.
eq
(
nextIndex
).
addClass
(
"tt-is-under-cursor"
);
this
.
_ensureVisibility
(
$underCursor
);
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
);
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
()
{
if
(
this
.
isOpen
)
{
this
.
isOpen
=
false
;
this
.
isMouseOverDropdown
=
false
;
this
.
_hide
();
this
.
$menu
.
find
(
".tt-suggestions > .tt-suggestion"
).
removeClass
(
"tt-is-under-cursor"
);
this
.
trigger
(
"closed"
);
}
},
open
:
function
()
{
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
);
},
moveCursorUp
:
function
()
{
this
.
_moveCursor
(
-
1
);
},
moveCursorDown
:
function
()
{
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
);
}
this
.
trigger
(
"suggestionsRendered"
);
},
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
();
}
}
});
return
DropdownView
;
function
extractSuggestion
(
$el
)
{
return
$el
.
data
(
"suggestion"
);
}
}();
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"
}
};
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
;
$menu
=
this
.
$node
.
find
(
".tt-dropdown-menu"
);
$input
=
this
.
$node
.
find
(
".tt-query"
);
$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
({
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
);
}
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
;
}
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
);
}
},
_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
]
:
""
));
}
},
_clearHint
:
function
()
{
this
.
inputView
.
setHintValue
(
""
);
},
_clearSuggestions
:
function
()
{
this
.
dropdownView
.
clearSuggestions
();
},
_setInputValueToQuery
:
function
()
{
this
.
inputView
.
setInputValue
(
this
.
inputView
.
getQuery
());
},
_setInputValueToSuggestionUnderCursor
:
function
(
e
)
{
var
suggestion
=
e
.
data
;
this
.
inputView
.
setInputValue
(
suggestion
.
value
,
true
);
},
_openDropdown
:
function
()
{
this
.
dropdownView
.
open
();
},
_closeDropdown
:
function
(
e
)
{
this
.
dropdownView
[
e
.
type
===
"blured"
?
"closeUnlessMouseIsOverDropdown"
:
"close"
]();
},
_moveDropdownCursor
:
function
(
e
)
{
var
$e
=
e
.
data
;
if
(
!
$e
.
shiftKey
&&
!
$e
.
ctrlKey
&&
!
$e
.
metaKey
)
{
this
.
dropdownView
[
e
.
type
===
"upKeyed"
?
"moveCursorUp"
:
"moveCursorDown"
]();
}
},
_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
);
}
},
_getSuggestions
:
function
()
{
var
that
=
this
,
query
=
this
.
inputView
.
getQuery
();
if
(
utils
.
isBlankString
(
query
))
{
return
;
}
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
);
}
},
_propagateEvent
:
function
(
e
)
{
this
.
eventBus
.
trigger
(
e
.
type
);
},
destroy
:
function
()
{
this
.
inputView
.
destroy
();
this
.
dropdownView
.
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"
)
});
$input
.
data
(
"ttAttrs"
,
{
dir
:
$input
.
attr
(
"dir"
),
autocomplete
:
$input
.
attr
(
"autocomplete"
),
spellcheck
:
$input
.
attr
(
"spellcheck"
),
style
:
$input
.
attr
(
"style"
)
});
$input
.
addClass
(
"tt-query"
).
attr
({
autocomplete
:
"off"
,
spellcheck
:
false
}).
css
(
css
.
query
);
try
{
!
$input
.
attr
(
"dir"
)
&&
$input
.
attr
(
"dir"
,
"auto"
);
}
catch
(
e
)
{}
return
$input
.
wrap
(
$wrapper
).
parent
().
prepend
(
$hint
).
append
(
$dropdown
);
}
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
);
});
$input
.
detach
().
removeData
(
"ttAttrs"
).
removeClass
(
"tt-query"
).
insertAfter
(
$node
);
$node
.
remove
();
}
}();
(
function
()
{
var
cache
=
{},
viewKey
=
"ttView"
,
methods
;
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
();
});
$input
.
data
(
viewKey
,
new
TypeaheadView
({
input
:
$input
,
eventBus
:
eventBus
=
new
EventBus
({
el
:
$input
}),
datasets
:
datasets
}));
$
.
when
.
apply
(
$
,
deferreds
).
always
(
function
()
{
utils
.
defer
(
function
()
{
eventBus
.
trigger
(
"initialized"
);
});
});
}
},
destroy
:
function
()
{
return
this
.
each
(
destroy
);
function
destroy
()
{
var
$this
=
$
(
this
),
view
=
$this
.
data
(
viewKey
);
if
(
view
)
{
view
.
destroy
();
$this
.
removeData
(
viewKey
);
}
}
},
setQuery
:
function
(
query
)
{
return
this
.
each
(
setQuery
);
function
setQuery
()
{
var
view
=
$
(
this
).
data
(
viewKey
);
view
&&
view
.
setQuery
(
query
);
}
}
};
jQuery
.
fn
.
typeahead
=
function
(
method
)
{
if
(
methods
[
method
])
{
return
methods
[
method
].
apply
(
this
,
[].
slice
.
call
(
arguments
,
1
));
}
else
{
return
methods
.
initialize
.
apply
(
this
,
arguments
);
}
};
})();
})(
window
.
jQuery
);
\ No newline at end of file
framework/yii/gii/assets/typeahead.js-bootstrap.css
0 → 100644
View file @
4cb1a264
/* always keep this link here when updating this file: https://github.com/jharding/typeahead.js-bootstrap.css */
.twitter-typeahead
.tt-query
,
.twitter-typeahead
.tt-hint
{
margin-bottom
:
0
;
}
.tt-dropdown-menu
{
min-width
:
160px
;
margin-top
:
2px
;
padding
:
5px
0
;
background-color
:
#fff
;
border
:
1px
solid
#ccc
;
border
:
1px
solid
rgba
(
0
,
0
,
0
,
.2
);
*
border-right-width
:
2px
;
*
border-bottom-width
:
2px
;
-webkit-border-radius
:
6px
;
-moz-border-radius
:
6px
;
border-radius
:
6px
;
-webkit-box-shadow
:
0
5px
10px
rgba
(
0
,
0
,
0
,
.2
);
-moz-box-shadow
:
0
5px
10px
rgba
(
0
,
0
,
0
,
.2
);
box-shadow
:
0
5px
10px
rgba
(
0
,
0
,
0
,
.2
);
-webkit-background-clip
:
padding-box
;
-moz-background-clip
:
padding
;
background-clip
:
padding-box
;
}
.tt-suggestion
{
display
:
block
;
padding
:
3px
20px
;
}
.tt-suggestion.tt-is-under-cursor
{
color
:
#fff
;
background-color
:
#0081c2
;
background-image
:
-moz-linear-gradient
(
top
,
#0088cc
,
#0077b3
);
background-image
:
-webkit-gradient
(
linear
,
0
0
,
0
100%
,
from
(
#0088cc
),
to
(
#0077b3
));
background-image
:
-webkit-linear-gradient
(
top
,
#0088cc
,
#0077b3
);
background-image
:
-o-linear-gradient
(
top
,
#0088cc
,
#0077b3
);
background-image
:
linear-gradient
(
to
bottom
,
#0088cc
,
#0077b3
);
background-repeat
:
repeat-x
;
filter
:
progid
:
DXImageTransform
.
Microsoft
.
gradient
(
startColorstr
=
'#ff0088cc'
,
endColorstr
=
'#ff0077b3'
,
GradientType
=
0
)
}
.tt-suggestion.tt-is-under-cursor
a
{
color
:
#fff
;
}
.tt-suggestion
p
{
margin
:
0
;
}
framework/yii/gii/components/ActiveField.php
View file @
4cb1a264
...
...
@@ -8,6 +8,7 @@
namespace
yii\gii\components
;
use
yii\gii\Generator
;
use
yii\helpers\Json
;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
...
...
@@ -30,10 +31,18 @@ class ActiveField extends \yii\widgets\ActiveField
if
(
isset
(
$hints
[
$this
->
attribute
]))
{
$this
->
hint
(
$hints
[
$this
->
attribute
]);
}
$autoCompleteData
=
$this
->
model
->
autoCompleteData
();
if
(
isset
(
$autoCompleteData
[
$this
->
attribute
]))
{
if
(
is_callable
(
$autoCompleteData
[
$this
->
attribute
]))
{
$this
->
autoComplete
(
call_user_func
(
$autoCompleteData
[
$this
->
attribute
]));
}
else
{
$this
->
autoComplete
(
$autoCompleteData
[
$this
->
attribute
]);
}
}
}
/**
* Makes fi
le
d remember its value between page reloads
* Makes fi
el
d remember its value between page reloads
* @return static the field object itself
*/
public
function
sticky
()
...
...
@@ -41,4 +50,17 @@ class ActiveField extends \yii\widgets\ActiveField
$this
->
options
[
'class'
]
.=
' sticky'
;
return
$this
;
}
/**
* Makes field auto completable
* @param array $data auto complete data (array of callables or scalars)
* @return static the field object itself
*/
public
function
autoComplete
(
$data
)
{
static
$counter
=
0
;
$this
->
inputOptions
[
'class'
]
.=
' typeahead-'
.
(
++
$counter
);
$this
->
form
->
getView
()
->
registerJs
(
"jQuery('.typeahead-
{
$counter
}
').typeahead({local: "
.
Json
::
encode
(
$data
)
.
"});"
);
return
$this
;
}
}
framework/yii/gii/generators/model/Generator.php
View file @
4cb1a264
...
...
@@ -113,6 +113,18 @@ class Generator extends \yii\gii\Generator
/**
* @inheritdoc
*/
public
function
autoCompleteData
()
{
return
[
'tableName'
=>
function
()
{
return
$this
->
getDbConnection
()
->
getSchema
()
->
getTableNames
();
},
];
}
/**
* @inheritdoc
*/
public
function
requiredTemplates
()
{
return
[
'model.php'
];
...
...
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