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 });