Package muntjac :: Package ui :: Module select
[hide private]
[frames] | no frames]

Source Code for Module muntjac.ui.select

  1  # Copyright (C) 2012 Vaadin Ltd.  
  2  # Copyright (C) 2012 Richard Lincoln 
  3  #  
  4  # Licensed under the Apache License, Version 2.0 (the "License");  
  5  # you may not use this file except in compliance with the License.  
  6  # You may obtain a copy of the License at  
  7  #  
  8  #     http://www.apache.org/licenses/LICENSE-2.0  
  9  #  
 10  # Unless required by applicable law or agreed to in writing, software  
 11  # distributed under the License is distributed on an "AS IS" BASIS,  
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
 13  # See the License for the specific language governing permissions and  
 14  # limitations under the License. 
 15   
 16  """Defines a class representing a selection of items the user has selected 
 17  in a UI.""" 
 18   
 19  from warnings import warn 
 20   
 21  from muntjac.event import field_events 
 22  from muntjac.data.util.filter.simple_string_filter import SimpleStringFilter 
 23  from muntjac.data.container import IContainer, IFilterable, IIndexed 
 24  from muntjac.ui import abstract_select 
 25   
 26  from muntjac.event.field_events import \ 
 27      FocusEvent, BlurEvent, IBlurListener, IFocusListener 
 28   
 29   
30 -class Select(abstract_select.AbstractSelect, abstract_select.IFiltering, 31 field_events.IBlurNotifier, field_events.IFocusNotifier):
32 """A class representing a selection of items the user has selected in a 33 UI. The set of choices is presented as a set of L{IItem}s in a 34 L{IContainer}. 35 36 A C{Select} component may be in single- or multiselect mode. 37 Multiselect mode means that more than one item can be selected 38 simultaneously. 39 40 @author: Vaadin Ltd. 41 @author: Richard Lincoln 42 @version: 1.1.2 43 """ 44 45 CLIENT_WIDGET = None #ClientWidget(VFilterSelect, LoadStyle.LAZY) 46
47 - def __init__(self, *args):
48 #: Holds value of property pageLength. 0 disables paging. 49 self.pageLength = 10 50 51 self._columns = 0 52 53 #: Current page when the user is 'paging' trough options 54 self._currentPage = -1 55 56 self._filteringMode = self.FILTERINGMODE_STARTSWITH 57 58 self._filterstring = None 59 self._prevfilterstring = None 60 61 #: Number of options that pass the filter, excluding the null 62 # item if any. 63 self._filteredSize = None 64 65 #: Cache of filtered options, used only by the in-memory 66 # filtering system. 67 self._filteredOptions = None 68 69 #: Flag to indicate that request repaint is called by filter 70 # request only 71 self._optionRequest = None 72 73 #: True if the container is being filtered temporarily and item 74 # set change notifications should be suppressed. 75 self._filteringContainer = None 76 77 #: Flag to indicate whether to scroll the selected item visible 78 # (select the page on which it is) when opening the popup or not. 79 # Only applies to single select mode. 80 # 81 # This requires finding the index of the item, which can be expensive 82 # in many large lazy loading containers. 83 self._scrollToSelectedItem = True 84 85 nargs = len(args) 86 if nargs == 0: 87 super(Select, self).__init__() 88 elif nargs == 1: 89 caption, = args 90 super(Select, self).__init__(caption) 91 elif nargs == 2: 92 if isinstance(args[1], IContainer): 93 caption, dataSource = args 94 super(Select, self).__init__(caption, dataSource) 95 else: 96 caption, options = args 97 super(Select, self).__init__(caption, options) 98 else: 99 raise ValueError, 'too many arguments'
100 101
102 - def paintContent(self, target):
103 """Paints the content of this component. 104 105 @param target: 106 the Paint Event. 107 @raise PaintException: 108 if the paint operation failed. 109 """ 110 if self.isMultiSelect(): 111 # background compatibility hack. This object shouldn't be used for 112 # multiselect lists anymore (ListSelect instead). This fallbacks to 113 # a simpler paint method in super class. 114 super(Select, self).paintContent(target) 115 # Fix for #4553 116 target.addAttribute('type', 'legacy-multi') 117 return 118 119 # clear caption change listeners 120 self.getCaptionChangeListener().clear() 121 122 # The tab ordering number 123 if self.getTabIndex() != 0: 124 target.addAttribute('tabindex', self.getTabIndex()) 125 126 # If the field is modified, but not committed, set modified attribute 127 if self.isModified(): 128 target.addAttribute('modified', True) 129 130 # Adds the required attribute 131 if self.isRequired(): 132 target.addAttribute('required', True) 133 134 if self.isNewItemsAllowed(): 135 target.addAttribute('allownewitem', True) 136 137 needNullSelectOption = False 138 if self.isNullSelectionAllowed(): 139 target.addAttribute('nullselect', True) 140 needNullSelectOption = self.getNullSelectionItemId() is None 141 if not needNullSelectOption: 142 target.addAttribute('nullselectitem', True) 143 144 # Constructs selected keys array 145 if self.isMultiSelect(): 146 selectedKeys = [None] * len(self.getValue()) 147 elif self.getValue() is None and self.getNullSelectionItemId() is None: 148 selectedKeys = [] 149 else: 150 selectedKeys = [None] 151 152 target.addAttribute('pagelength', self.pageLength) 153 154 target.addAttribute('filteringmode', self.getFilteringMode()) 155 156 # Paints the options and create array of selected id keys 157 keyIndex = 0 158 159 target.startTag('options') 160 161 if self._currentPage < 0: 162 self._optionRequest = False 163 self._currentPage = 0 164 self._filterstring = '' 165 166 nullFilteredOut = ((self._filterstring is not None) 167 and (self._filterstring != '') 168 and (self._filteringMode != self.FILTERINGMODE_OFF)) 169 # null option is needed and not filtered out, even if not on current 170 # page 171 nullOptionVisible = needNullSelectOption and not nullFilteredOut 172 173 # first try if using container filters is possible 174 options = self.getOptionsWithFilter(nullOptionVisible) 175 if None is options: 176 # not able to use container filters, perform explicit 177 # in-memory filtering 178 options = self.getFilteredOptions() 179 self._filteredSize = len(options) 180 options = self.sanitetizeList(options, nullOptionVisible) 181 182 paintNullSelection = (needNullSelectOption and (self._currentPage == 0) 183 and (not nullFilteredOut)) 184 185 if paintNullSelection: 186 target.startTag('so') 187 target.addAttribute('caption', '') 188 target.addAttribute('key', '') 189 target.endTag('so') 190 191 i = iter(options) 192 # Paints the available selection options from data source 193 while True: 194 try: 195 idd = i.next() 196 197 if ((not self.isNullSelectionAllowed()) 198 and (idd is not None) 199 and (idd == self.getNullSelectionItemId()) 200 and (not self.isSelected(idd))): 201 continue 202 203 # Gets the option attribute values 204 key = self.itemIdMapper.key(idd) 205 caption = self.getItemCaption(idd) 206 icon = self.getItemIcon(idd) 207 self.getCaptionChangeListener().addNotifierForItem(idd) 208 209 # Paints the option 210 target.startTag('so') 211 if icon is not None: 212 target.addAttribute('icon', icon) 213 214 target.addAttribute('caption', caption) 215 if (idd is not None) and idd == self.getNullSelectionItemId(): 216 target.addAttribute('nullselection', True) 217 218 target.addAttribute('key', key) 219 if self.isSelected(idd) and (keyIndex < len(selectedKeys)): 220 target.addAttribute('selected', True) 221 selectedKeys[keyIndex] = key 222 keyIndex += 1 223 224 target.endTag('so') 225 except StopIteration: 226 break 227 target.endTag('options') 228 229 target.addAttribute('totalitems', (len(self) 230 + (1 if needNullSelectOption else 0))) 231 232 if (self._filteredSize > 0) or nullOptionVisible: 233 target.addAttribute('totalMatches', (self._filteredSize 234 + (1 if nullOptionVisible else 0))) 235 236 # Paint variables 237 target.addVariable(self, 'selected', selectedKeys) 238 if self.isNewItemsAllowed(): 239 target.addVariable(self, 'newitem', '') 240 241 target.addVariable(self, 'filter', self._filterstring) 242 target.addVariable(self, 'page', self._currentPage) 243 244 self._currentPage = -1 # current page is always set by client 245 246 self._optionRequest = True 247 248 # Hide the error indicator if needed 249 if self.shouldHideErrors(): 250 target.addAttribute('hideErrors', True)
251 252
253 - def getOptionsWithFilter(self, needNullSelectOption):
254 """Returns the filtered options for the current page using a container 255 filter. 256 257 As a size effect, L{filteredSize} is set to the total number of 258 items passing the filter. 259 260 The current container must be L{IFilterable} and L{IIndexed}, 261 and the filtering mode must be suitable for container filtering 262 (tested with L{canUseContainerFilter}). 263 264 Use L{getFilteredOptions} and L{sanitetizeList} if this is not the 265 case. 266 267 @param needNullSelectOption: 268 @return: filtered list of options (may be empty) or null if cannot use 269 container filters 270 """ 271 container = self.getContainerDataSource() 272 273 if self.pageLength == 0: 274 # no paging: return all items 275 self._filteredSize = len(container) 276 return list(container.getItemIds()) 277 278 if ((not isinstance(container, IFilterable)) 279 or (not isinstance(container, IIndexed)) 280 or (self.getItemCaptionMode() != 281 self.ITEM_CAPTION_MODE_PROPERTY)): 282 return None 283 284 filterable = container 285 286 fltr = self.buildFilter(self._filterstring, self._filteringMode) 287 288 # adding and removing filters leads to extraneous item set 289 # change events from the underlying container, but the ComboBox does 290 # not process or propagate them based on the flag filteringContainer 291 if fltr is not None: 292 self._filteringContainer = True 293 filterable.addContainerFilter(fltr) 294 295 indexed = container 296 297 indexToEnsureInView = -1 298 299 # if not an option request (item list when user changes page), go 300 # to page with the selected item after filtering if accepted by 301 # filter 302 selection = self.getValue() 303 if (self.isScrollToSelectedItem() and (not self._optionRequest) 304 and (not self.isMultiSelect()) and (selection is not None)): 305 # ensure proper page 306 indexToEnsureInView = indexed.indexOfId(selection) 307 308 self._filteredSize = len(container) 309 self._currentPage = self.adjustCurrentPage(self._currentPage, 310 needNullSelectOption, indexToEnsureInView, self._filteredSize) 311 first = self.getFirstItemIndexOnCurrentPage(needNullSelectOption, 312 self._filteredSize) 313 last = self.getLastItemIndexOnCurrentPage(needNullSelectOption, 314 self._filteredSize, first) 315 316 options = list() 317 i = first 318 while (i <= last) and (i < self._filteredSize): 319 options.append( indexed.getIdByIndex(i) ) 320 i += 1 321 322 # to the outside, filtering should not be visible 323 if fltr is not None: 324 filterable.removeContainerFilter(fltr) 325 self._filteringContainer = False 326 327 return options
328 329
330 - def buildFilter(self, filterString, filteringMode):
331 """Constructs a filter instance to use when using a IFilterable 332 container in the C{ITEM_CAPTION_MODE_PROPERTY} mode. 333 334 Note that the client side implementation expects the filter string to 335 apply to the item caption string it sees, so changing the behavior of 336 this method can cause problems. 337 """ 338 fltr = None 339 if filterString is not None and filterString != '': 340 test = filteringMode 341 if test == self.FILTERINGMODE_OFF: 342 pass 343 elif test == self.FILTERINGMODE_STARTSWITH: 344 fltr = SimpleStringFilter(self.getItemCaptionPropertyId(), 345 filterString, True, True) 346 elif test == self.FILTERINGMODE_CONTAINS: 347 fltr = SimpleStringFilter(self.getItemCaptionPropertyId(), 348 filterString, True, False) 349 return fltr
350 351
352 - def containerItemSetChange(self, event):
353 if not self._filteringContainer: 354 super(Select, self).containerItemSetChange(event)
355 356
357 - def sanitetizeList(self, options, needNullSelectOption):
358 """Makes correct sublist of given list of options. 359 360 If paint is not an option request (affected by page or filter change), 361 page will be the one where possible selection exists. 362 363 Detects proper first and last item in list to return right page of 364 options. Also, if the current page is beyond the end of the list, it 365 will be adjusted. 366 367 @param options: 368 @param needNullSelectOption: 369 flag to indicate if nullselect option needs to be taken 370 into consideration 371 """ 372 if (self.pageLength != 0) and (len(options) > self.pageLength): 373 374 indexToEnsureInView = -1 375 376 # if not an option request (item list when user changes page), go 377 # to page with the selected item after filtering if accepted by 378 # filter 379 selection = self.getValue() 380 if (self.isScrollToSelectedItem() and (not self._optionRequest) 381 and (not self.isMultiSelect()) 382 and (selection is not None)): 383 # ensure proper page 384 try: 385 indexToEnsureInView = options.index(selection) 386 except ValueError: 387 indexToEnsureInView = -1 388 389 size = len(options) 390 self._currentPage = self.adjustCurrentPage(self._currentPage, 391 needNullSelectOption, indexToEnsureInView, size) 392 first = self.getFirstItemIndexOnCurrentPage(needNullSelectOption, 393 size) 394 last = self.getLastItemIndexOnCurrentPage(needNullSelectOption, 395 size, first) 396 return options[first:last + 1] 397 else: 398 return options
399 400
401 - def getFirstItemIndexOnCurrentPage(self, needNullSelectOption, size):
402 """Returns the index of the first item on the current page. The index 403 is to the underlying (possibly filtered) contents. The null item, if 404 any, does not have an index but takes up a slot on the first page. 405 406 @param needNullSelectOption: 407 true if a null option should be shown before any other 408 options (takes up the first slot on the first page, not 409 counted in index) 410 @param size: 411 number of items after filtering (not including the null 412 item, if any) 413 @return: first item to show on the UI (index to the filtered list of 414 options, not taking the null item into consideration if any) 415 """ 416 # Not all options are visible, find out which ones are on the 417 # current "page". 418 first = self._currentPage * self.pageLength 419 if needNullSelectOption and (self._currentPage > 0): 420 first -= 1 421 return first
422 423
424 - def getLastItemIndexOnCurrentPage(self, needNullSelectOption, size, first):
425 """Returns the index of the last item on the current page. The index 426 is to the underlying (possibly filtered) contents. If 427 needNullSelectOption is true, the null item takes up the first slot 428 on the first page, effectively reducing the first page size by one. 429 430 @param needNullSelectOption: 431 true if a null option should be shown before any other 432 options (takes up the first slot on the first page, not 433 counted in index) 434 @param size: 435 number of items after filtering (not including the null 436 item, if any) 437 @param first: 438 index in the filtered view of the first item of the page 439 @return: index in the filtered view of the last item on the page 440 """ 441 # page length usable for non-null items 442 if needNullSelectOption and (self._currentPage == 0): 443 effectivePageLength = self.pageLength - 1 444 else: 445 effectivePageLength = self.pageLength 446 447 return min(size - 1, (first + effectivePageLength) - 1)
448 449
450 - def adjustCurrentPage(self, page, needNullSelectOption, 451 indexToEnsureInView, size):
452 """Adjusts the index of the current page if necessary: make sure the 453 current page is not after the end of the contents, and optionally go 454 to the page containing a specific item. There are no side effects but 455 the adjusted page index is returned. 456 457 @param page: 458 page number to use as the starting point 459 @param needNullSelectOption: 460 true if a null option should be shown before any other 461 options (takes up the first slot on the first page, not 462 counted in index) 463 @param indexToEnsureInView: 464 index of an item that should be included on the page (in 465 the data set, not counting the null item if any), -1 for 466 none 467 @param size: 468 number of items after filtering (not including the null 469 item, if any) 470 """ 471 if indexToEnsureInView != -1: 472 if needNullSelectOption: 473 newPage = (indexToEnsureInView + 1) / self.pageLength 474 else: 475 newPage = indexToEnsureInView / self.pageLength 476 477 page = newPage 478 479 # adjust the current page if beyond the end of the list 480 if (page * self.pageLength) > size: 481 if needNullSelectOption: 482 page = (size + 1) / self.pageLength 483 else: 484 page = size / self.pageLength 485 486 return page
487 488
489 - def getFilteredOptions(self):
490 """Filters the options in memory and returns the full filtered list. 491 492 This can be less efficient than using container filters, so use 493 L{getOptionsWithFilter} if possible (filterable container and suitable 494 item caption mode etc.). 495 """ 496 if ((self._filterstring is None) or (self._filterstring == '') 497 or (self.FILTERINGMODE_OFF == self._filteringMode)): 498 self._prevfilterstring = None 499 self._filteredOptions = list(self.getItemIds()) 500 return self._filteredOptions 501 502 if self._filterstring == self._prevfilterstring: 503 return self._filteredOptions 504 505 if ((self._prevfilterstring is not None) 506 and self._filterstring.startswith(self._prevfilterstring)): 507 items = self._filteredOptions 508 else: 509 items = self.getItemIds() 510 self._prevfilterstring = self._filterstring 511 512 self._filteredOptions = list() 513 for itemId in items: 514 caption = self.getItemCaption(itemId) 515 if (caption is None) or (caption == ''): 516 continue 517 else: 518 caption = caption.lower() 519 520 test = self._filteringMode 521 if test == self.FILTERINGMODE_CONTAINS: 522 if caption.find(self._filterstring) > -1: 523 self._filteredOptions.append(itemId) 524 elif test == self.FILTERINGMODE_STARTSWITH: 525 pass 526 else: 527 if caption.startswith(self._filterstring): 528 self._filteredOptions.append(itemId) 529 530 return self._filteredOptions
531 532
533 - def changeVariables(self, source, variables):
534 """Invoked when the value of a variable has changed. 535 536 @see: L{AbstractComponent.changeVariables} 537 """ 538 # Not calling super.changeVariables due the history of select 539 # component hierarchy 540 541 # Selection change 542 if 'selected' in variables: 543 ka = variables.get('selected') 544 545 if self.isMultiSelect(): 546 # Multiselect mode 547 548 # TODO: Optimize by adding repaintNotNeeded when applicable 549 550 # Converts the key-array to id-set 551 s = list() 552 for i in range(len(ka)): 553 idd = self.itemIdMapper.get(ka[i]) 554 if (idd is not None) and self.containsId(idd): 555 s.append(idd) 556 557 # Limits the deselection to the set of visible items 558 # (non-visible items can not be deselected) 559 visible = self.getVisibleItemIds() 560 if visible is not None: 561 newsel = self.getValue() 562 563 if newsel is None: 564 newsel = set() 565 else: 566 newsel = set(newsel) 567 568 newsel = newsel.difference(visible) 569 newsel = newsel.union(s) 570 571 self.setValue(newsel, True) 572 else: 573 # Single select mode 574 if len(ka) == 0: 575 # Allows deselection only if the deselected item is visible 576 current = self.getValue() 577 visible = self.getVisibleItemIds() 578 579 if (visible is not None) and (current in visible): 580 self.setValue(None, True) 581 else: 582 idd = self.itemIdMapper.get(ka[0]) 583 584 if ((idd is not None) 585 and (idd == self.getNullSelectionItemId())): 586 self.setValue(None, True) 587 else: 588 self.setValue(idd, True) 589 590 newFilter = variables.get('filter') 591 if newFilter is not None: 592 # this is a filter request 593 self._currentPage = int(variables.get('page')) 594 self._filterstring = newFilter 595 if self._filterstring is not None: 596 self._filterstring = self._filterstring.lower() 597 598 self.optionRepaint() 599 elif self.isNewItemsAllowed(): 600 # New option entered (and it is allowed) 601 newitem = variables.get('newitem') 602 if (newitem is not None) and (len(newitem) > 0): 603 self.getNewItemHandler().addNewItem(newitem) 604 # rebuild list 605 self._filterstring = None 606 self._prevfilterstring = None 607 608 if FocusEvent.EVENT_ID in variables: 609 self.fireEvent( FocusEvent(self) ) 610 611 if BlurEvent.EVENT_ID in variables: 612 self.fireEvent( BlurEvent(self) )
613 614
615 - def requestRepaint(self):
616 super(Select, self).requestRepaint() 617 self._optionRequest = False 618 self._prevfilterstring = self._filterstring 619 self._filterstring = None
620 621
622 - def optionRepaint(self):
623 super(Select, self).requestRepaint()
624 625
626 - def setFilteringMode(self, filteringMode):
627 self._filteringMode = filteringMode
628 629
630 - def getFilteringMode(self):
631 return self._filteringMode
632 633
634 - def setColumns(self, columns):
635 """Note, one should use more generic setWidth(String) method instead 636 of this. This now days actually converts columns to width with em css 637 unit. 638 639 Sets the number of columns in the editor. If the number of columns is 640 set 0, the actual number of displayed columns is determined implicitly 641 by the adapter. 642 643 @deprecated: 644 645 @param columns: 646 the number of columns to set. 647 """ 648 warn('deprecated', DeprecationWarning) 649 650 if columns < 0: 651 columns = 0 652 653 if self._columns != columns: 654 self._columns = columns 655 self.setWidth(columns, Select.UNITS_EM) 656 self.requestRepaint()
657 658
659 - def getColumns(self):
660 """@deprecated: see setter function 661 """ 662 warn('see setter function', DeprecationWarning) 663 return self._columns
664 665
666 - def addListener(self, listener, iface=None):
667 if (isinstance(listener, IBlurListener) and 668 (iface is None or issubclass(iface, IBlurListener))): 669 self.registerListener(BlurEvent.EVENT_ID, 670 BlurEvent, listener, IBlurListener.blurMethod) 671 672 if (isinstance(listener, IFocusListener) and 673 (iface is None or issubclass(iface, IFocusListener))): 674 self.registerListener(FocusEvent.EVENT_ID, 675 FocusEvent, listener, IFocusListener.focusMethod) 676 677 super(Select, self).addListener(listener, iface)
678 679
680 - def addCallback(self, callback, eventType=None, *args):
681 if eventType is None: 682 eventType = callback._eventType 683 684 if issubclass(eventType, BlurEvent): 685 self.registerCallback(BlurEvent, callback, 686 BlurEvent.EVENT_ID, *args) 687 688 elif issubclass(eventType, FocusEvent): 689 self.registerCallback(FocusEvent, callback, 690 FocusEvent.EVENT_ID, *args) 691 else: 692 super(Select, self).addCallback(callback, eventType, *args)
693 694
695 - def removeListener(self, listener, iface=None):
696 if (isinstance(listener, IBlurListener) and 697 (iface is None or issubclass(iface, IBlurListener))): 698 self.withdrawListener(BlurEvent.EVENT_ID, BlurEvent, listener) 699 700 if (isinstance(listener, IFocusListener) and 701 (iface is None or issubclass(iface, IFocusListener))): 702 self.withdrawListener(FocusEvent.EVENT_ID, FocusEvent, listener) 703 704 super(Select, self).removeListener(listener, iface)
705 706
707 - def removeCallback(self, callback, eventType=None):
708 if eventType is None: 709 eventType = callback._eventType 710 711 if issubclass(eventType, BlurEvent): 712 self.withdrawCallback(BlurEvent, callback, BlurEvent.EVENT_ID) 713 714 elif issubclass(eventType, FocusEvent): 715 self.withdrawCallback(FocusEvent, callback, FocusEvent.EVENT_ID) 716 717 else: 718 super(Select, self).removeCallback(callback, eventType)
719 720
721 - def setMultiSelect(self, multiSelect):
722 """@deprecated: use L{ListSelect}, L{OptionGroup} or 723 L{TwinColSelect} instead 724 @see: L{AbstractSelect.setMultiSelect} 725 """ 726 super(Select, self).setMultiSelect(multiSelect)
727 728
729 - def isMultiSelect(self):
730 """@deprecated: use L{ListSelect}, L{OptionGroup} or 731 L{TwinColSelect} instead 732 733 @see: L{AbstractSelect.isMultiSelect} 734 """ 735 return super(Select, self).isMultiSelect()
736 737
738 - def setScrollToSelectedItem(self, scrollToSelectedItem):
739 """Sets whether to scroll the selected item visible (directly open 740 the page on which it is) when opening the combo box popup or not. 741 Only applies to single select mode. 742 743 This requires finding the index of the item, which can be expensive 744 in many large lazy loading containers. 745 746 @param scrollToSelectedItem: 747 true to find the page with the selected item when opening 748 the selection popup 749 """ 750 self._scrollToSelectedItem = scrollToSelectedItem
751 752
753 - def isScrollToSelectedItem(self):
754 """Returns true if the select should find the page with the selected 755 item when opening the popup (single select combo box only). 756 757 @see: L{setScrollToSelectedItem} 758 759 @return: true if the page with the selected item will be shown when 760 opening the popup 761 """ 762 return self._scrollToSelectedItem
763