1 /**
  2  *  @fileOverview menu where items can be filtered based on user keyboard input
  3  *  @author yiminghe@gmail.com
  4  */
  5 KISSY.add("menu/filtermenu", function (S, Menu, FilterMenuRender) {
  6 
  7     var HIT_CLS = "ks-menuitem-hit";
  8 
  9     // 转义正则特殊字符,返回字符串用来构建正则表达式
 10     function regExpEscape(s) {
 11         return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
 12             replace(/\x08/g, '\\x08');
 13     }
 14 
 15     /**
 16      * @class
 17      * Filter Menu for KISSY.
 18      * xclass: 'filtermenu'.
 19      * @extends Menu
 20      * @memberOf Menu
 21      * @name FilterMenu
 22      */
 23     var FilterMenu = Menu.extend(
 24         /**
 25          * @lends Menu.FilterMenu#
 26          */
 27         {
 28             bindUI:function () {
 29                 var self = this,
 30                     view = self.get("view"),
 31                     filterInput = view.get("filterInput");
 32                 /*监控键盘事件*/
 33                 filterInput.on("keyup", self.handleFilterEvent, self);
 34             },
 35 
 36             handleMouseEnter:function () {
 37                 var self = this;
 38                 FilterMenu.superclass.handleMouseEnter.apply(self, arguments);
 39                 // 权益解决,filter input focus 后会滚动到牌聚焦处,select 则不会
 40                 // 如果 filtermenu 的菜单项被滚轮滚到后面,点击触发不了,会向前滚动到 filter input
 41                 self.getKeyEventTarget()[0].select();
 42             },
 43 
 44             handleFilterEvent:function () {
 45                 var self = this,
 46                     view = self.get("view"),
 47                     filterInput = view.get("filterInput"),
 48                     highlightedItem = self.get("highlightedItem");
 49                 /* 根据用户输入过滤 */
 50                 self.set("filterStr", filterInput.val());
 51 
 52                 // 如果没有高亮项或者高亮项因为过滤被隐藏了
 53                 // 默认选择符合条件的第一项
 54                 if (!highlightedItem || !highlightedItem.get("visible")) {
 55                     self.set("highlightedItem",
 56                         self._getNextEnabledHighlighted(0, 1));
 57                 }
 58             },
 59 
 60             _uiSetFilterStr:function (v) {
 61                 // 过滤条件变了立即过滤
 62                 this.filterItems(v);
 63             },
 64 
 65             /**
 66              * For override. Specify how to filter items.
 67              * @param {String} str User input.
 68              */
 69             filterItems:function (str) {
 70                 var self = this,
 71                     view = self.get("view"),
 72                     _labelEl = view.get("labelEl"),
 73                     filterInput = view.get("filterInput");
 74 
 75                 // 有过滤条件提示隐藏,否则提示显示
 76                 _labelEl[str ? "hide" : "show"]();
 77 
 78                 if (self.get("allowMultiple")) {
 79                     var enteredItems = [],
 80                         lastWord;
 81                     // \uff0c => ,
 82                     var match = str.match(/(.+)[,\uff0c]\s*([^\uff0c,]*)/);
 83                     // 已经确认的项
 84                     // , 号之前的项必定确认
 85 
 86                     var items = [];
 87 
 88                     if (match) {
 89                         items = match[1].split(/[,\uff0c]/);
 90                     }
 91 
 92                     // 逗号结尾
 93                     // 如果可以补全,那么补全最后一项为第一个高亮项
 94                     if (/[,\uff0c]$/.test(str)) {
 95                         enteredItems = [];
 96                         if (match) {
 97                             enteredItems = items;
 98                             //待补全的项
 99                             lastWord = items[items.length - 1];
100                             var item = self.get("highlightedItem"),
101                                 content = item && item.get("content");
102                             // 有高亮而且最后一项不为空补全
103                             if (content && content.indexOf(lastWord) > -1
104                                 && lastWord) {
105                                 enteredItems[enteredItems.length - 1] = content;
106                             }
107                             filterInput.val(enteredItems.join(",") + ",");
108                         }
109                         str = '';
110                     } else {
111                         // 需要菜单过滤的过滤词,在最后一个 , 后面
112                         if (match) {
113                             str = match[2] || "";
114                         }
115                         // 没有 , 则就是当前输入的
116                         // else{ str=str}
117 
118                         //记录下
119                         enteredItems = items;
120                     }
121                     var oldEnteredItems = self.get("enteredItems");
122                     // 发生变化,长度变化和内容变化等同
123                     if (oldEnteredItems.length != enteredItems.length) {
124                         S.log("enteredItems : ");
125                         S.log(enteredItems);
126                         self.set("enteredItems", enteredItems);
127                     }
128                 }
129 
130                 var children = self.get("children"),
131                     strExp = str && new RegExp(regExpEscape(str), "ig");
132 
133                 // 过滤所有子组件
134                 S.each(children, function (c) {
135                     var content = c.get("content");
136                     if (!str) {
137                         // 没有过滤条件
138                         // 恢复原有内容
139                         // 显示出来
140                         c.get("el").html(content);
141                         c.set("visible", true);
142                     } else {
143                         if (content.indexOf(str) > -1) {
144                             // 如果符合过滤项
145                             // 显示
146                             c.set("visible", true);
147                             // 匹配子串着重 wrap
148                             c.get("el").html(content.replace(strExp, function (m) {
149                                 return "<span class='" + HIT_CLS + "'>" + m + "<" + "/span>";
150                             }));
151                         } else {
152                             // 不符合
153                             // 隐藏
154                             c.set("visible", false);
155                         }
156                     }
157                 });
158             },
159 
160             decorateInternal:function (el) {
161                 var self = this;
162                 self.set("el", el);
163                 var menuContent = el.one("." + "ks-menu-content");
164                 self.decorateChildren(menuContent);
165             },
166 
167             /**
168              * Reset user input.
169              */
170             reset:function () {
171                 var self = this,
172                     view = self.get("view");
173                 self.set("filterStr", "");
174                 self.set("enteredItems", []);
175                 var filterInput = view && view.get("filterInput");
176                 filterInput && filterInput.val("");
177             },
178 
179             destructor:function () {
180                 var view = this.get("view");
181                 var filterInput = view && view.get("filterInput");
182                 filterInput && filterInput.detach();
183             }
184 
185         },
186         {
187             ATTRS:/**
188              * @lends Menu.FilterMenu#
189              */
190             {
191                 /**
192                  * Hit info string
193                  * @type String
194                  */
195                 label:{
196                     view:1
197                 },
198 
199                 /**
200                  * Filter string
201                  * @type String
202                  */
203                 filterStr:{
204                 },
205 
206                 /**
207                  * user entered string list when allowMultiple.
208                  * @type String[]
209                  */
210                 enteredItems:{
211                     value:[]
212                 },
213 
214                 /**
215                  * Whether to allow input multiple.
216                  * @type Boolean
217                  */
218                 allowMultiple:{
219                     value:false
220                 },
221 
222                 xrender:{
223                     value:FilterMenuRender
224                 }
225             }
226         }, {
227             xclass:'filtermenu',
228             priority:20
229         });
230 
231     return FilterMenu;
232 }, {
233     requires:['./base', './filtermenuRender']
234 });