選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

483 行
12 KiB

  1. /*
  2. * jQuery UI Autocomplete 1.8
  3. *
  4. * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
  5. * Dual licensed under the MIT (MIT-LICENSE.txt)
  6. * and GPL (GPL-LICENSE.txt) licenses.
  7. *
  8. * http://docs.jquery.com/UI/Autocomplete
  9. *
  10. * Depends:
  11. * jquery.ui.core.js
  12. * jquery.ui.widget.js
  13. * jquery.ui.position.js
  14. */
  15. (function( $ ) {
  16. $.widget( "ui.autocomplete", {
  17. options: {
  18. minLength: 1,
  19. delay: 300
  20. },
  21. _create: function() {
  22. var self = this,
  23. doc = this.element[ 0 ].ownerDocument;
  24. this.element
  25. .addClass( "ui-autocomplete-input" )
  26. .attr( "autocomplete", "off" )
  27. // TODO verify these actually work as intended
  28. .attr({
  29. role: "textbox",
  30. "aria-autocomplete": "list",
  31. "aria-haspopup": "true"
  32. })
  33. .bind( "keydown.autocomplete", function( event ) {
  34. var keyCode = $.ui.keyCode;
  35. switch( event.keyCode ) {
  36. case keyCode.PAGE_UP:
  37. self._move( "previousPage", event );
  38. break;
  39. case keyCode.PAGE_DOWN:
  40. self._move( "nextPage", event );
  41. break;
  42. case keyCode.UP:
  43. self._move( "previous", event );
  44. // prevent moving cursor to beginning of text field in some browsers
  45. event.preventDefault();
  46. break;
  47. case keyCode.DOWN:
  48. self._move( "next", event );
  49. // prevent moving cursor to end of text field in some browsers
  50. event.preventDefault();
  51. break;
  52. case keyCode.ENTER:
  53. // when menu is open or has focus
  54. if ( self.menu.active ) {
  55. event.preventDefault();
  56. }
  57. //passthrough - ENTER and TAB both select the current element
  58. case keyCode.TAB:
  59. if ( !self.menu.active ) {
  60. return;
  61. }
  62. self.menu.select();
  63. break;
  64. case keyCode.ESCAPE:
  65. self.element.val( self.term );
  66. self.close( event );
  67. break;
  68. case keyCode.SHIFT:
  69. case keyCode.CONTROL:
  70. case 18:
  71. // ignore metakeys (shift, ctrl, alt)
  72. break;
  73. default:
  74. // keypress is triggered before the input value is changed
  75. clearTimeout( self.searching );
  76. self.searching = setTimeout(function() {
  77. self.search( null, event );
  78. }, self.options.delay );
  79. break;
  80. }
  81. })
  82. .bind( "focus.autocomplete", function() {
  83. self.previous = self.element.val();
  84. })
  85. .bind( "blur.autocomplete", function( event ) {
  86. clearTimeout( self.searching );
  87. // clicks on the menu (or a button to trigger a search) will cause a blur event
  88. // TODO try to implement this without a timeout, see clearTimeout in search()
  89. self.closing = setTimeout(function() {
  90. self.close( event );
  91. }, 150 );
  92. });
  93. this._initSource();
  94. this.response = function() {
  95. return self._response.apply( self, arguments );
  96. };
  97. this.menu = $( "<ul></ul>" )
  98. .addClass( "ui-autocomplete" )
  99. .appendTo( "body", doc )
  100. .menu({
  101. focus: function( event, ui ) {
  102. var item = ui.item.data( "item.autocomplete" );
  103. if ( false !== self._trigger( "focus", null, { item: item } ) ) {
  104. // use value to match what will end up in the input
  105. self.element.val( item.value );
  106. }
  107. },
  108. selected: function( event, ui ) {
  109. var item = ui.item.data( "item.autocomplete" );
  110. if ( false !== self._trigger( "select", event, { item: item } ) ) {
  111. self.element.val( item.value );
  112. }
  113. self.close( event );
  114. self.previous = self.element.val();
  115. // only trigger when focus was lost (click on menu)
  116. if ( self.element[0] !== doc.activeElement ) {
  117. self.element.focus();
  118. }
  119. },
  120. blur: function( event, ui ) {
  121. if ( self.menu.element.is(":visible") ) {
  122. self.element.val( self.term );
  123. }
  124. }
  125. })
  126. .zIndex( this.element.zIndex() + 1 )
  127. // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
  128. .css({ top: 0, left: 0 })
  129. .hide()
  130. .data( "menu" );
  131. if ( $.fn.bgiframe ) {
  132. this.menu.element.bgiframe();
  133. }
  134. },
  135. destroy: function() {
  136. this.element
  137. .removeClass( "ui-autocomplete-input ui-widget ui-widget-content" )
  138. .removeAttr( "autocomplete" )
  139. .removeAttr( "role" )
  140. .removeAttr( "aria-autocomplete" )
  141. .removeAttr( "aria-haspopup" );
  142. this.menu.element.remove();
  143. $.Widget.prototype.destroy.call( this );
  144. },
  145. _setOption: function( key ) {
  146. $.Widget.prototype._setOption.apply( this, arguments );
  147. if ( key === "source" ) {
  148. this._initSource();
  149. }
  150. },
  151. _initSource: function() {
  152. var array,
  153. url;
  154. if ( $.isArray(this.options.source) ) {
  155. array = this.options.source;
  156. this.source = function( request, response ) {
  157. // escape regex characters
  158. var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
  159. response( $.grep( array, function(value) {
  160. return matcher.test( value.label || value.value || value );
  161. }) );
  162. };
  163. } else if ( typeof this.options.source === "string" ) {
  164. url = this.options.source;
  165. this.source = function( request, response ) {
  166. $.getJSON( url, request, response );
  167. };
  168. } else {
  169. this.source = this.options.source;
  170. }
  171. },
  172. search: function( value, event ) {
  173. value = value != null ? value : this.element.val();
  174. if ( value.length < this.options.minLength ) {
  175. return this.close( event );
  176. }
  177. clearTimeout( this.closing );
  178. if ( this._trigger("search") === false ) {
  179. return;
  180. }
  181. return this._search( value );
  182. },
  183. _search: function( value ) {
  184. this.term = this.element
  185. .addClass( "ui-autocomplete-loading" )
  186. // always save the actual value, not the one passed as an argument
  187. .val();
  188. this.source( { term: value }, this.response );
  189. },
  190. _response: function( content ) {
  191. if ( content.length ) {
  192. content = this._normalize( content );
  193. this._suggest( content );
  194. this._trigger( "open" );
  195. } else {
  196. this.close();
  197. }
  198. this.element.removeClass( "ui-autocomplete-loading" );
  199. },
  200. close: function( event ) {
  201. clearTimeout( this.closing );
  202. if ( this.menu.element.is(":visible") ) {
  203. this._trigger( "close", event );
  204. this.menu.element.hide();
  205. this.menu.deactivate();
  206. }
  207. if ( this.previous !== this.element.val() ) {
  208. this._trigger( "change", event );
  209. }
  210. },
  211. _normalize: function( items ) {
  212. // assume all items have the right format when the first item is complete
  213. if ( items.length && items[0].label && items[0].value ) {
  214. return items;
  215. }
  216. return $.map( items, function(item) {
  217. if ( typeof item === "string" ) {
  218. return {
  219. label: item,
  220. value: item
  221. };
  222. }
  223. return $.extend({
  224. label: item.label || item.value,
  225. value: item.value || item.label
  226. }, item );
  227. });
  228. },
  229. _suggest: function( items ) {
  230. var ul = this.menu.element
  231. .empty()
  232. .zIndex( this.element.zIndex() + 1 ),
  233. menuWidth,
  234. textWidth;
  235. this._renderMenu( ul, items );
  236. // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
  237. this.menu.deactivate();
  238. this.menu.refresh();
  239. this.menu.element.show().position({
  240. my: "left top",
  241. at: "left bottom",
  242. of: this.element,
  243. collision: "none"
  244. });
  245. menuWidth = ul.width( "" ).width();
  246. textWidth = this.element.width();
  247. ul.width( Math.max( menuWidth, textWidth ) );
  248. },
  249. _renderMenu: function( ul, items ) {
  250. var self = this;
  251. $.each( items, function( index, item ) {
  252. self._renderItem( ul, item );
  253. });
  254. },
  255. _renderItem: function( ul, item) {
  256. return $( "<li></li>" )
  257. .data( "item.autocomplete", item )
  258. .append( "<a>" + item.label + "</a>" )
  259. .appendTo( ul );
  260. },
  261. _move: function( direction, event ) {
  262. if ( !this.menu.element.is(":visible") ) {
  263. this.search( null, event );
  264. return;
  265. }
  266. if ( this.menu.first() && /^previous/.test(direction) ||
  267. this.menu.last() && /^next/.test(direction) ) {
  268. this.element.val( this.term );
  269. this.menu.deactivate();
  270. return;
  271. }
  272. this.menu[ direction ]();
  273. },
  274. widget: function() {
  275. return this.menu.element;
  276. }
  277. });
  278. $.extend( $.ui.autocomplete, {
  279. escapeRegex: function( value ) {
  280. return value.replace( /([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1" );
  281. }
  282. });
  283. }( jQuery ));
  284. /*
  285. * jQuery UI Menu (not officially released)
  286. *
  287. * This widget isn't yet finished and the API is subject to change. We plan to finish
  288. * it for the next release. You're welcome to give it a try anyway and give us feedback,
  289. * as long as you're okay with migrating your code later on. We can help with that, too.
  290. *
  291. * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
  292. * Dual licensed under the MIT (MIT-LICENSE.txt)
  293. * and GPL (GPL-LICENSE.txt) licenses.
  294. *
  295. * http://docs.jquery.com/UI/Menu
  296. *
  297. * Depends:
  298. * jquery.ui.core.js
  299. * jquery.ui.widget.js
  300. */
  301. (function($) {
  302. $.widget("ui.menu", {
  303. _create: function() {
  304. var self = this;
  305. this.element
  306. .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
  307. .attr({
  308. role: "listbox",
  309. "aria-activedescendant": "ui-active-menuitem"
  310. })
  311. .click(function(e) {
  312. // temporary
  313. e.preventDefault();
  314. self.select();
  315. });
  316. this.refresh();
  317. },
  318. refresh: function() {
  319. var self = this;
  320. // don't refresh list items that are already adapted
  321. var items = this.element.children("li:not(.ui-menu-item):has(a)")
  322. .addClass("ui-menu-item")
  323. .attr("role", "menuitem");
  324. items.children("a")
  325. .addClass("ui-corner-all")
  326. .attr("tabindex", -1)
  327. // mouseenter doesn't work with event delegation
  328. .mouseenter(function() {
  329. self.activate($(this).parent());
  330. })
  331. .mouseleave(function() {
  332. self.deactivate();
  333. });
  334. },
  335. activate: function(item) {
  336. this.deactivate();
  337. if (this.hasScroll()) {
  338. var offset = item.offset().top - this.element.offset().top,
  339. scroll = this.element.attr("scrollTop"),
  340. elementHeight = this.element.height();
  341. if (offset < 0) {
  342. this.element.attr("scrollTop", scroll + offset);
  343. } else if (offset > elementHeight) {
  344. this.element.attr("scrollTop", scroll + offset - elementHeight + item.height());
  345. }
  346. }
  347. this.active = item.eq(0)
  348. .children("a")
  349. .addClass("ui-state-hover")
  350. .attr("id", "ui-active-menuitem")
  351. .end();
  352. this._trigger("focus", null, { item: item });
  353. },
  354. deactivate: function() {
  355. if (!this.active) { return; }
  356. this.active.children("a")
  357. .removeClass("ui-state-hover")
  358. .removeAttr("id");
  359. this._trigger("blur");
  360. this.active = null;
  361. },
  362. next: function() {
  363. this.move("next", "li:first");
  364. },
  365. previous: function() {
  366. this.move("prev", "li:last");
  367. },
  368. first: function() {
  369. return this.active && !this.active.prev().length;
  370. },
  371. last: function() {
  372. return this.active && !this.active.next().length;
  373. },
  374. move: function(direction, edge) {
  375. if (!this.active) {
  376. this.activate(this.element.children(edge));
  377. return;
  378. }
  379. var next = this.active[direction]();
  380. if (next.length) {
  381. this.activate(next);
  382. } else {
  383. this.activate(this.element.children(edge));
  384. }
  385. },
  386. // TODO merge with previousPage
  387. nextPage: function() {
  388. if (this.hasScroll()) {
  389. // TODO merge with no-scroll-else
  390. if (!this.active || this.last()) {
  391. this.activate(this.element.children(":first"));
  392. return;
  393. }
  394. var base = this.active.offset().top,
  395. height = this.element.height(),
  396. result = this.element.children("li").filter(function() {
  397. var close = $(this).offset().top - base - height + $(this).height();
  398. // TODO improve approximation
  399. return close < 10 && close > -10;
  400. });
  401. // TODO try to catch this earlier when scrollTop indicates the last page anyway
  402. if (!result.length) {
  403. result = this.element.children(":last");
  404. }
  405. this.activate(result);
  406. } else {
  407. this.activate(this.element.children(!this.active || this.last() ? ":first" : ":last"));
  408. }
  409. },
  410. // TODO merge with nextPage
  411. previousPage: function() {
  412. if (this.hasScroll()) {
  413. // TODO merge with no-scroll-else
  414. if (!this.active || this.first()) {
  415. this.activate(this.element.children(":last"));
  416. return;
  417. }
  418. var base = this.active.offset().top,
  419. height = this.element.height();
  420. result = this.element.children("li").filter(function() {
  421. var close = $(this).offset().top - base + height - $(this).height();
  422. // TODO improve approximation
  423. return close < 10 && close > -10;
  424. });
  425. // TODO try to catch this earlier when scrollTop indicates the last page anyway
  426. if (!result.length) {
  427. result = this.element.children(":first");
  428. }
  429. this.activate(result);
  430. } else {
  431. this.activate(this.element.children(!this.active || this.first() ? ":last" : ":first"));
  432. }
  433. },
  434. hasScroll: function() {
  435. return this.element.height() < this.element.attr("scrollHeight");
  436. },
  437. select: function() {
  438. this._trigger("selected", null, { item: this.active });
  439. }
  440. });
  441. }(jQuery));