transfer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /**
  2. @Name:layui.transfer 穿梭框
  3. @Author:贤心
  4. @License:MIT
  5. */
  6. layui.define(['laytpl', 'form'], function(exports){
  7. "use strict";
  8. var $ = layui.$
  9. ,laytpl = layui.laytpl
  10. ,form = layui.form
  11. //模块名
  12. ,MOD_NAME = 'transfer'
  13. //外部接口
  14. ,transfer = {
  15. config: {}
  16. ,index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0
  17. //设置全局项
  18. ,set: function(options){
  19. var that = this;
  20. that.config = $.extend({}, that.config, options);
  21. return that;
  22. }
  23. //事件监听
  24. ,on: function(events, callback){
  25. return layui.onevent.call(this, MOD_NAME, events, callback);
  26. }
  27. }
  28. //操作当前实例
  29. ,thisModule = function(){
  30. var that = this
  31. ,options = that.config
  32. ,id = options.id || that.index;
  33. thisModule.that[id] = that; //记录当前实例对象
  34. thisModule.config[id] = options; //记录当前实例配置项
  35. return {
  36. config: options
  37. //重置实例
  38. ,reload: function(options){
  39. that.reload.call(that, options);
  40. }
  41. //获取右侧数据
  42. ,getData: function(){
  43. return that.getData.call(that);
  44. }
  45. }
  46. }
  47. //获取当前实例配置项
  48. ,getThisModuleConfig = function(id){
  49. var config = thisModule.config[id];
  50. if(!config) hint.error('The ID option was not found in the '+ MOD_NAME +' instance');
  51. return config || null;
  52. }
  53. //字符常量
  54. ,ELEM = 'layui-transfer', HIDE = 'layui-hide', DISABLED = 'layui-btn-disabled', NONE = 'layui-none'
  55. ,ELEM_BOX = 'layui-transfer-box', ELEM_HEADER = 'layui-transfer-header', ELEM_SEARCH = 'layui-transfer-search', ELEM_ACTIVE = 'layui-transfer-active', ELEM_DATA = 'layui-transfer-data'
  56. //穿梭框模板
  57. ,TPL_BOX = function(obj){
  58. obj = obj || {};
  59. return ['<div class="layui-transfer-box" data-index="'+ obj.index +'">'
  60. ,'<div class="layui-transfer-header">'
  61. ,'<input type="checkbox" name="'+ obj.checkAllName +'" lay-filter="layTransferCheckbox" lay-type="all" lay-skin="primary" title="{{ d.data.title['+ obj.index +'] || \'list'+ (obj.index + 1) +'\' }}">'
  62. ,'</div>'
  63. ,'{{# if(d.data.showSearch){ }}'
  64. ,'<div class="layui-transfer-search">'
  65. ,'<i class="layui-icon layui-icon-search"></i>'
  66. ,'<input type="input" class="layui-input" placeholder="关键词搜索">'
  67. ,'</div>'
  68. ,'{{# } }}'
  69. ,'<ul class="layui-transfer-data"></ul>'
  70. ,'</div>'].join('');
  71. }
  72. //主模板
  73. ,TPL_MAIN = ['<div class="layui-transfer layui-form layui-border-box" lay-filter="LAY-transfer-{{ d.index }}">'
  74. ,TPL_BOX({
  75. index: 0
  76. ,checkAllName: 'layTransferLeftCheckAll'
  77. })
  78. ,'<div class="layui-transfer-active">'
  79. ,'<button type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-btn-disabled" data-index="0">'
  80. ,'<i class="layui-icon layui-icon-next"></i>'
  81. ,'</button>'
  82. ,'<button type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-btn-disabled" data-index="1">'
  83. ,'<i class="layui-icon layui-icon-prev"></i>'
  84. ,'</button>'
  85. ,'</div>'
  86. ,TPL_BOX({
  87. index: 1
  88. ,checkAllName: 'layTransferRightCheckAll'
  89. })
  90. ,'</div>'].join('')
  91. //构造器
  92. ,Class = function(options){
  93. var that = this;
  94. that.index = ++transfer.index;
  95. that.config = $.extend({}, that.config, transfer.config, options);
  96. that.render();
  97. };
  98. //默认配置
  99. Class.prototype.config = {
  100. title: ['列表一', '列表二']
  101. ,width: 200
  102. ,height: 360
  103. ,data: [] //数据源
  104. ,value: [] //选中的数据
  105. ,showSearch: false //是否开启搜索
  106. ,id: '' //唯一索引,默认自增 index
  107. ,text: {
  108. none: '无数据'
  109. ,searchNone: '无匹配数据'
  110. }
  111. };
  112. //重载实例
  113. Class.prototype.reload = function(options){
  114. var that = this;
  115. layui.each(options, function(key, item){
  116. if(item.constructor === Array) delete that.config[key];
  117. });
  118. that.config = $.extend(true, {}, that.config, options);
  119. that.render();
  120. };
  121. //渲染
  122. Class.prototype.render = function(){
  123. var that = this
  124. ,options = that.config;
  125. //解析模板
  126. var thisElem = that.elem = $(laytpl(TPL_MAIN).render({
  127. data: options
  128. ,index: that.index //索引
  129. }));
  130. var othis = options.elem = $(options.elem);
  131. if(!othis[0]) return;
  132. //初始化属性
  133. options.data = options.data || [];
  134. options.value = options.value || [];
  135. //索引
  136. that.key = options.id || that.index;
  137. //插入组件结构
  138. othis.html(that.elem);
  139. //各级容器
  140. that.layBox = that.elem.find('.'+ ELEM_BOX)
  141. that.layHeader = that.elem.find('.'+ ELEM_HEADER)
  142. that.laySearch = that.elem.find('.'+ ELEM_SEARCH)
  143. that.layData = thisElem.find('.'+ ELEM_DATA);
  144. that.layBtn = thisElem.find('.'+ ELEM_ACTIVE + ' .layui-btn');
  145. //初始化尺寸
  146. that.layBox.css({
  147. width: options.width
  148. ,height: options.height
  149. });
  150. that.layData.css({
  151. height: function(){
  152. return options.height - that.layHeader.outerHeight() - that.laySearch.outerHeight() - 2
  153. }()
  154. });
  155. that.renderData(); //渲染数据
  156. that.events(); //事件
  157. };
  158. //渲染数据
  159. Class.prototype.renderData = function(){
  160. var that = this
  161. ,options = that.config;
  162. //左右穿梭框差异数据
  163. var arr = [{
  164. checkName: 'layTransferLeftCheck'
  165. ,views: []
  166. }, {
  167. checkName: 'layTransferRightCheck'
  168. ,views: []
  169. }];
  170. //解析格式
  171. that.parseData(function(item){
  172. //标注为 selected 的为右边的数据
  173. var _index = item.selected ? 1 : 0
  174. ,listElem = ['<li>'
  175. ,'<input type="checkbox" name="'+ arr[_index].checkName +'" lay-skin="primary" lay-filter="layTransferCheckbox" title="'+ item.title +'"'+ (item.disabled ? ' disabled' : '') + (item.checked ? ' checked' : '') +' value="'+ item.value +'">'
  176. ,'</li>'].join('');
  177. arr[_index].views.push(listElem);
  178. delete item.selected;
  179. });
  180. that.layData.eq(0).html(arr[0].views.join(''));
  181. that.layData.eq(1).html(arr[1].views.join(''));
  182. that.renderCheckBtn();
  183. }
  184. //渲染表单
  185. Class.prototype.renderForm = function(type){
  186. form.render(type, 'LAY-transfer-'+ this.index);
  187. };
  188. //同步复选框和按钮状态
  189. Class.prototype.renderCheckBtn = function(obj){
  190. var that = this
  191. ,options = that.config;
  192. obj = obj || {};
  193. that.layBox.each(function(_index){
  194. var othis = $(this)
  195. ,thisDataElem = othis.find('.'+ ELEM_DATA)
  196. ,allElemCheckbox = othis.find('.'+ ELEM_HEADER).find('input[type="checkbox"]')
  197. ,listElemCheckbox = thisDataElem.find('input[type="checkbox"]');
  198. //同步复选框和按钮状态
  199. var nums = 0
  200. ,haveChecked = false;
  201. listElemCheckbox.each(function(){
  202. var isHide = $(this).data('hide');
  203. if(this.checked || this.disabled || isHide){
  204. nums++;
  205. }
  206. if(this.checked && !isHide){
  207. haveChecked = true;
  208. }
  209. });
  210. allElemCheckbox.prop('checked', haveChecked && nums === listElemCheckbox.length); //全选复选框状态
  211. that.layBtn.eq(_index)[haveChecked ? 'removeClass' : 'addClass'](DISABLED); //对应的按钮状态
  212. //无数据视图
  213. if(!obj.stopNone){
  214. var isNone = thisDataElem.children('li:not(.'+ HIDE +')').length
  215. that.noneView(thisDataElem, isNone ? '' : options.text.none);
  216. }
  217. });
  218. that.renderForm('checkbox');
  219. };
  220. //无数据视图
  221. Class.prototype.noneView = function(thisDataElem, text){
  222. var createNoneElem = $('<p class="layui-none">'+ (text || '') +'</p>');
  223. if(thisDataElem.find('.'+ NONE)[0]){
  224. thisDataElem.find('.'+ NONE).remove();
  225. }
  226. text.replace(/\s/g, '') && thisDataElem.append(createNoneElem);
  227. };
  228. //同步 value 属性值
  229. Class.prototype.setValue = function(){
  230. var that = this
  231. ,options = that.config
  232. ,arr = [];
  233. that.layBox.eq(1).find('.'+ ELEM_DATA +' input[type="checkbox"]').each(function(){
  234. var isHide = $(this).data('hide');
  235. isHide || arr.push(this.value);
  236. });
  237. options.value = arr;
  238. return that;
  239. };
  240. //解析数据
  241. Class.prototype.parseData = function(callback){
  242. var that = this
  243. ,options = that.config
  244. ,newData = [];
  245. layui.each(options.data, function(index, item){
  246. //解析格式
  247. item = (typeof options.parseData === 'function'
  248. ? options.parseData(item)
  249. : item) || item;
  250. newData.push(item = $.extend({}, item))
  251. layui.each(options.value, function(index2, item2){
  252. if(item2 == item.value){
  253. item.selected = true;
  254. }
  255. });
  256. callback && callback(item);
  257. });
  258. options.data = newData;
  259. return that;
  260. };
  261. //获得右侧面板数据
  262. Class.prototype.getData = function(value){
  263. var that = this
  264. ,options = that.config
  265. ,selectedData = [];
  266. layui.each(value || options.value, function(index, item){
  267. layui.each(options.data, function(index2, item2){
  268. delete item2.selected;
  269. if(item == item2.value){
  270. selectedData.push(item2);
  271. };
  272. });
  273. });
  274. return selectedData;
  275. };
  276. //事件
  277. Class.prototype.events = function(){
  278. var that = this
  279. ,options = that.config;
  280. //左右复选框
  281. that.elem.on('click', 'input[lay-filter="layTransferCheckbox"]+', function(){
  282. var thisElemCheckbox = $(this).prev()
  283. ,checked = thisElemCheckbox[0].checked
  284. ,thisDataElem = thisElemCheckbox.parents('.'+ ELEM_BOX).eq(0).find('.'+ ELEM_DATA);
  285. if(thisElemCheckbox[0].disabled) return;
  286. //判断是否全选
  287. if(thisElemCheckbox.attr('lay-type') === 'all'){
  288. thisDataElem.find('input[type="checkbox"]').each(function(){
  289. if(this.disabled) return;
  290. this.checked = checked;
  291. });
  292. }
  293. that.renderCheckBtn({stopNone: true});
  294. });
  295. //按钮事件
  296. that.layBtn.on('click', function(){
  297. var othis = $(this)
  298. ,_index = othis.data('index')
  299. ,thisBoxElem = that.layBox.eq(_index)
  300. ,arr = [];
  301. if(othis.hasClass(DISABLED)) return;
  302. that.layBox.eq(_index).each(function(_index){
  303. var othis = $(this)
  304. ,thisDataElem = othis.find('.'+ ELEM_DATA);
  305. thisDataElem.children('li').each(function(){
  306. var thisList = $(this)
  307. ,thisElemCheckbox = thisList.find('input[type="checkbox"]')
  308. ,isHide = thisElemCheckbox.data('hide');
  309. if(thisElemCheckbox[0].checked && !isHide){
  310. thisElemCheckbox[0].checked = false;
  311. thisBoxElem.siblings('.'+ ELEM_BOX).find('.'+ ELEM_DATA).append(thisList.clone());
  312. thisList.remove();
  313. //记录当前穿梭的数据
  314. arr.push(thisElemCheckbox[0].value);
  315. }
  316. that.setValue();
  317. });
  318. });
  319. that.renderCheckBtn();
  320. //穿梭时,如果另外一个框正在搜索,则触发匹配
  321. var siblingInput = thisBoxElem.siblings('.'+ ELEM_BOX).find('.'+ ELEM_SEARCH +' input')
  322. siblingInput.val() === '' || siblingInput.trigger('keyup');
  323. //穿梭时的回调
  324. options.onchange && options.onchange(that.getData(arr), _index);
  325. });
  326. //搜索
  327. that.laySearch.find('input').on('keyup', function(){
  328. var value = this.value
  329. ,thisDataElem = $(this).parents('.'+ ELEM_SEARCH).eq(0).siblings('.'+ ELEM_DATA)
  330. ,thisListElem = thisDataElem.children('li');
  331. thisListElem.each(function(){
  332. var thisList = $(this)
  333. ,thisElemCheckbox = thisList.find('input[type="checkbox"]')
  334. ,isMatch = thisElemCheckbox[0].title.indexOf(value) !== -1;
  335. thisList[isMatch ? 'removeClass': 'addClass'](HIDE);
  336. thisElemCheckbox.data('hide', isMatch ? false : true);
  337. });
  338. that.renderCheckBtn();
  339. //无匹配数据视图
  340. var isNone = thisListElem.length === thisDataElem.children('li.'+ HIDE).length;
  341. that.noneView(thisDataElem, isNone ? options.text.searchNone : '');
  342. });
  343. };
  344. //记录所有实例
  345. thisModule.that = {}; //记录所有实例对象
  346. thisModule.config = {}; //记录所有实例配置项
  347. //重载实例
  348. transfer.reload = function(id, options){
  349. var that = thisModule.that[id];
  350. that.reload(options);
  351. return thisModule.call(that);
  352. };
  353. //获得选中的数据(右侧面板)
  354. transfer.getData = function(id){
  355. var that = thisModule.that[id];
  356. return that.getData();
  357. };
  358. //核心入口
  359. transfer.render = function(options){
  360. var inst = new Class(options);
  361. return thisModule.call(inst);
  362. };
  363. exports(MOD_NAME, transfer);
  364. });