可拖动菜单 总结
Posted mflnhg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了可拖动菜单 总结相关的知识,希望对你有一定的参考价值。
需求
应老罗的要求,做一个可拖动的长条形列表菜单。
这是原来的样子
进入编辑界面,它的编辑添加都不能实时更新,而且操作起来比较麻烦。
所以我估计一下应该有这些需求:
1.修改,取消,确认按钮
2.点击修改,菜单每一项都可以拖动,每一项都可以修改,每一项都可以删除
3.每一个父级都可添加一个子节点或添加一个子集合
4.点击取消,恢复到点击修改之前的状态
5.点击确认,保存这个状态,更新数据,返回数据给数据库
需求分析:
1.首先,菜单是从无到有的,从数据库获取数据后判断是空还是有的,如果是空,显示在界面上是一个添加按钮,这部分我还没做【未完成】
2.分析数据,数据得是有规定格式的json类型的数据,通过数据生成DOM结构,由于事先不知道有几层,所以用递归。
3.点击修改,修改之前应该保存当前状态,DOM的当前状态,数据的当前状态
4.每一项都有一个编辑、删除按钮,不能给每一项都添加这两个DOM节点,太费资源了,最后就是添加到所以节点的底部,鼠标hover的时候判断,应该修改那一项
a.编辑、删除按钮按下后又分别有取消和确认两个选择
b.整个菜单只有一个编辑按钮,只有一个删除按钮,鼠标hover到某一项,就把这两个按钮“拉”过来
5.拖动的实现,包括按下、按下后移动、鼠标抬起,三个步骤,DOM节点位置的变换,和数据位置的变换应该是在抬起的时候发生的
6.拖动的时候做一条提示线,提示用户当前是在哪里
实现思路:
首先,我希望,用户在使用的时候,其他人在使用我的代码的时候,可以简单的new一个使用就可以,这就意味这我要把这个需求写成OOP,由于我还不是很了解面向对象编程的方式,所以我的代码里只是简单的把功能分开,操作数据的我写在一个继承里,操作DOM的我写在一个继承里。
数据 --> DOM的过程,如下:
createDOMtree:function(DataDOM){
//----------------------分析数据结构,生成节点树-------------------------//
function fn(v,ele){
if( !ele ){
var aUl = document.createElement(‘ul‘);
aUl.setAttribute(‘id‘,‘zdx_v0‘);
}
//---------------------------数组走这里------------------------//
if( Array.isArray(v) ){
//如果是数组
for(var i=0,len=v.length; i<len; i++){
//如果是数组
if( Array.isArray(v[i]) ){
fn(v[i]); //递归
//如果是对象
}else if( typeof v[i] === ‘object‘ ){
var aLi = document.createElement(‘li‘);
aLi.setAttribute(‘onoff‘,‘true‘);
//---------给父级li加个标识------------//
aLi.setAttribute(‘attr‘,‘liWrap‘);
//---------给父级li加个标识------end---//
//---------给最外层的li加个标识--------//
if( v[i].v1 ){
aLi.setAttribute(‘v‘,‘v1‘);
}
//---------给最外层的li加个标识----end--//
ele ? ele.appendChild(aLi) : aUl.appendChild(aLi);
var oUl = document.createElement(‘ul‘);
aLi.appendChild(oUl);
fn(v[i],oUl); //递归
//如果是字符串
}else{
var oLi = document.createElement(‘li‘),
oB = document.createElement(‘b‘),
txt = document.createTextNode(v[i]),
oI = document.createElement(‘i‘);
//-------阻止父级事件触发的开关-------------//
oLi.setAttribute(‘onoff‘,‘true‘);
//-------阻止父级事件触发的开关-------------//
//---------给普通li加个标识------------//
oLi.setAttribute(‘attr‘,‘li‘);
//---------给普通li加个标识------end---//
//--------小圆点-----------//
oI.setAttribute(‘class‘,‘iconP‘);
//--------小圆点-----------//
oLi.appendChild(oI);
oB.appendChild(txt);
oLi.appendChild(oB);
(ele) && ( ele.appendChild(oLi) );
(aUl) && ( aUl.appendChild(oLi) );
}
}
//---------------------------数组走这里-------------end-----------//
//---------------------------对象走这里---------------------------//
}else if( typeof v === ‘object‘ ){
//枚举对象每一项
for(var attr in v){
//如果是数组
if( Array.isArray(v[attr]) ){
fn(v[attr],ele); //递归
//如果算是对象
}else if( typeof v[attr] === ‘object‘ ){
fn(v[attr]); //递归
//如果是字符串
}else{
var oSpan = document.createElement(‘span‘),
oB = document.createElement(‘b‘),
txt = document.createTextNode(v[attr]),
oI = document.createElement(‘i‘);
//--------小圆点-----------//
oI.setAttribute(‘class‘,‘iconP‘);
oSpan.appendChild(oI);
//--------小圆点-----------//
oB.appendChild(txt);
oSpan.appendChild(oB);
ele.setAttribute(‘class‘,attr);
ele.appendChild(oSpan);
}
}
//---------------------------对象走这里-----------end-------------//
//---------------------字符串----------------//
}else{
//do someing
}
//---------------------字符串-------end-------//
return aUl;
}
//----------------------分析数据结构,生成节点树------------end-----------//
//-----生成按钮-----//
var oDiv = document.createElement(‘div‘);
oDiv.setAttribute(‘id‘,‘zdx_menu‘);
var btnWrap = document.createElement(‘div‘);
btnWrap.setAttribute(‘id‘,‘zdx_btnWrap‘)
var idNames = [‘zdx_cancel‘,‘zdx_submit‘,‘zdx_modify‘];
var btnNames = [‘取消‘,‘确定‘,‘修改‘];
var oA = null;
for(var i=0,iLen=idNames.length; i<iLen; i++){
oA = document.createElement(‘a‘);
oA.setAttribute(‘id‘,idNames[i]);
oA.appendChild(document.createTextNode(btnNames[i]));
btnWrap.appendChild(oA);
}
//-----生成按钮-----//
//-----包装节点树----//
oDiv.appendChild(btnWrap);
oDiv.appendChild(fn(DataDOM));
//-----包装节点树----//
//返回节点树
return oDiv;
},
洋洋洒洒一百多行,在创建节点的时候,顺便添加适当的class,id,来设置样式。
拖动的实现:我没有用网上找到的判断鼠标位置实时更新的方式,我通过给每个节点添加onmouseover事件,并把当前onmouseover到的节点实时保存起来,如果这时候释放鼠标,就会判断,判断鼠标位置是否超过hover到的节点高度的1/2,如果小于这个数,选中的节点放到hover前面,否则放在后面,这里简单调用一下insertBefore或insertAfter就可以了。
数据的更新:我采用的是相对的方式,这个节点相对于同一父级的第一个节点以0开始的第几个,以此父级,父级的父级,父级的父级的父级推,最终得到一个位置链数组,
arr = [1,2,3,4,5,3]; 这个链表示,第二章下的第三节下的第四节下的第五节下的第六节下的第四项。
难点:
拖动的实现
数据的更新
继承重用
难点解决方案:
拖动,本来是通过计算鼠标来“精确”拖动的,不过这样的方式在新增节点后就被破坏了,而且代码冗长逻辑复杂难以理解,后来利用onmouseover的方式,虽然给每一项都添加事件有些浪费,但好在灵活,代码量少,好理解,好拓展,所以就用这种方法了。
数据的更新,这个反而没想那么久,用相对的方式,获取数据的准确位置,目前开发阶段使用还未出现bug,如果有机会投入使用,这个数据位置的获取还得做得更好点。
继承重用,由于我刚学面向对象编程,这个继承重用我没有做得很好,在我的代码中出现了很多我觉得可以通过继承来重用的代码,但由于木已成舟,不宜大作改动,所以这个问题留到以后在解决吧
涉及的新知识:
json的字符串化和反字符串为json的两个方法,为了更新json数据而上网查的
JSON.parse( JSON.stringify( tarInfo[0][tarInfo[1]] ).replace(tarV,correctV) );
优化方向:
优化继承,实现更多的重用,将页面中阻止父级事件触发的代码换成【阻止冒泡】(如果可以的话)
用jQ减少代码量
优化我的getId.js
备注:
后期在写代码的时候,如果要和数据库交互的话一定要留点可操作的余地,其他没怎么说的了,写代码要有耐心。
/* //试验数据 var arr = [ { ‘v1‘:‘第一章‘, ‘data‘:[‘一章第1节‘,‘一章第2节‘,‘一章第3节‘,‘一章第4节‘,‘一章第5节‘] }, { ‘v1‘:‘第二章‘, ‘data‘:[{ ‘v2‘:‘二章第1节‘, ‘data‘:[‘第1节-1‘,‘第1节-2‘,‘第1节-3‘,‘第1节-4‘,‘第1节-5‘,‘第1节-6‘] },‘二章第2节‘,{ ‘v2‘:‘二章第3节‘, ‘data‘:[‘第3节-1‘,‘第3节-2‘,{ ‘v3‘:‘第3节-3‘, ‘data‘:[‘第3节-3-1‘,‘第3节-3-2‘,‘第3节-3-3‘,‘第3节-3-4‘,‘第3节-3-5‘] },{ ‘v3‘:‘第3节-4‘, ‘data‘:[‘第4节-3-1‘,‘第4节-3-2‘,‘第4节-3-3‘,‘第4节-3-4‘,{ ‘v4‘:‘第4节-3-5‘, ‘data‘:[‘第4节-3-5-1‘,‘第4节-3-5-2‘,‘第4节-3-5-3‘,‘第4节-3-5-4‘,‘第4节-3-5-5‘,‘第4节-3-5-6‘] }] },‘第3节-5‘] },‘二章第4节‘,‘二章第5节‘] }, { ‘v1‘:‘第三章‘, ‘data‘:[‘三章第1节‘,‘三章第2节‘,‘三章第3节‘,‘三章第4节‘,‘三章第5节‘] }, { ‘v1‘:‘第四章‘, ‘data‘:[‘四章第1节‘,‘四章第2节‘,‘四章第3节‘,‘四章第4节‘,‘四章第5节‘] }, { ‘v1‘:‘第五章‘, ‘data‘:[‘五章第1节‘,‘五章第2节‘,‘五章第3节‘,‘五章第4节‘,{ ‘v2‘:‘五-壹‘, ‘data‘:[‘五-壹-1‘,‘五-壹-2‘,‘五-壹-3‘,‘五-壹-4‘,‘五-壹-5‘,{ ‘v3‘:‘五-壹-壹‘, ‘data‘:[‘五-壹-壹-1‘,‘五-壹-壹-2‘,‘五-壹-壹-3‘,‘五-壹-壹-4‘] }] },‘五章第5节‘,‘五章第6节‘,‘五章第7节‘,‘五章第8节asdfasdfasdfasdfasdfadsfasdf‘] }, ]; */ //实施继承功能的函数 function extendByObj( child , parent , propertys ){ var F = function(){}; F.prototype = parent.prototype; child.prototype = new F(); for(var attr in propertys){ child.prototype[attr] = propertys[attr]; } child.prototype.constructor = child; } //MenuData:操作数据,第一层继承,继承Object function MenuData(data){} MenuData.prototype = { //---------------------------------插入新的数据------------------------------------------------// insertNewData:function(li,t){ var targetArr = this.traceDataByDOM(this.data,t); var tLiData = null; var tV = null; var correctV = null; if( li.getAttribute(‘attr‘) === ‘li‘ ){ tLiData = li.getElementsByTagName(‘b‘)[0].innerhtml; }else if( li.getAttribute(‘attr‘) === ‘liWrap‘ ){ for( var attr in targetArr[2] ){ ( typeof targetArr[2][attr] === ‘string‘ ) && ( tV = attr ); } // v的值应该比包含它的数组+1 correctV = tV[0] + ( parseInt(tV[1]) + 1 ); tLiData = { [correctV] : (li.getElementsByTagName(‘span‘)[0].getElementsByTagName(‘b‘)[0].innerHTML), data:[] }; }else{ tLiData = ‘未能识别数据‘; } targetArr[0].push( tLiData ); }, //---------------------------------插入新的数据------------------------------------------------// //-------------------------------修改数据-----------------------------------------------// modifyData:function(newValue,li){ var targetArr = this.traceDataByDOM( this.data , li ); targetArr[0][targetArr[1]] = newValue; console.log( this.data ); }, //-------------------------------修改数据-----------------------------------------------// //------------------------------删除数据--------------------------------------------------// deleteData:function(li){ var targetArr = this.traceDataByDOM(this.data,li); targetArr[0].splice( targetArr[1] , 1 ); console.log( this.data ); }, //------------------------------删除数据--------------------------------------------------// //------------------------根据DOM修改其在数据中对应的位置-------------------------------// changeOfPositionForData:function( target , t , insertOnOff ){ //target是选中项,t是target在DOM上的新位置 console.log(t); var tarInfo = this.traceDataByDOM( this.data , target ); var tInfo = this.traceDataByDOM( this.data , t ); // 值调整 ( insertOnOff === ‘after‘ ) && ( tInfo[1] = tInfo[1]+1 ); ( tarInfo[1] > tInfo[1] && tarInfo[1] == tInfo[1] ) && ( tarInfo[1] = tarInfo[1]+1 ); var tarV = null; var tV = null; var correctV = null; if( typeof tarInfo[2] === ‘object‘ ){ // tarInfo[0][tarInfo[1]]指向的是选中的对象 // tarInfo[2]指向的是包含这个对象的数组 // tInfo[2]指向的是包含目标项数组 // 通过for in找到v值 for( var attr in tarInfo[0][tarInfo[1]] ){ ( typeof tarInfo[0][tarInfo[1]][attr] === ‘string‘ ) && ( tarV = attr ); } // 通过for in找到v值 for( var attr in tInfo[2] ){ ( typeof tInfo[2][attr] === ‘string‘ ) && ( tV = attr ); } // v的值应该比包含它的数组+1 correctV = tV[0] + ( parseInt(tV[1]) + 1 ); // 利用JSON.stringify将要改动的json变换成字符串,利用replace替换v的值,再反向回json tarInfo[0][tarInfo[1]] = JSON.parse( JSON.stringify( tarInfo[0][tarInfo[1]] ).replace(tarV,correctV) ); } tInfo[0].splice( tInfo[1] , 0 , tarInfo[0][tarInfo[1]] ); tarInfo[0].splice( [tarInfo[1]] , 1 ); console.log(this.data) }, //------------------------根据DOM修改其在数据中对应的位置-------------------------------// //------用于找到tNode在数组中的位置的父级,以及具体位置---------// traceDataByDOM:function(arr,tNode){ var tNodePos = this.generatePosChain(tNode); var targetParent = arr[ tNodePos[0] ]; var targetPos = tNodePos[tNodePos.length-1]; for(var i=1,iLen=tNodePos.length-1; i<iLen; i++){ targetParent = targetParent.data[ tNodePos[i] ]; } return [ targetParent.data , targetPos , targetParent ]; }, //------用于找到tNode在数组中的位置的父级,以及具体位置---------// //----tNode的在数组中的具体位置----// generatePosChain(tNode){ var chain = []; var prePos = tNode; var cnt = 0; var tPreSibling = null; while( prePos ){ tPreSibling = prePos.previousSibling; while( tPreSibling ){ if( tPreSibling.getAttribute(‘attr‘) === ‘li‘ || tPreSibling.getAttribute(‘attr‘) === ‘liWrap‘ ){ cnt++; } tPreSibling = tPreSibling.previousSibling; } chain.push( cnt ); cnt = 0; prePos = prePos.parentNode.parentNode; if( prePos.nodeName === ‘DIV‘ ){ break; } } return chain.reverse(); } //----tNode的在数组中的具体位置----// }; MenuData.prototype.constructor = MenuData; //DataToDOM:根据数据渲染DOM,根据DOM更新数据 function DataToDOM(){} extendByObj( DataToDOM , MenuData , { /* 创建DOM节点树方法 插入DOM节点树到页面的方法 移动节点的方法 删除节点的方法 */ createDOMtree:function(DataDOM){ //----------------------分析数据结构,生成节点树-------------------------// function fn(v,ele){ if( !ele ){ var aUl = document.createElement(‘ul‘); aUl.setAttribute(‘id‘,‘zdx_v0‘); } //---------------------------数组走这里------------------------// if( Array.isArray(v) ){ //如果是数组 for(var i=0,len=v.length; i<len; i++){ //如果是数组 if( Array.isArray(v[i]) ){ fn(v[i]); //递归 //如果是对象 }else if( typeof v[i] === ‘object‘ ){ var aLi = document.createElement(‘li‘); aLi.setAttribute(‘onoff‘,‘true‘); //---------给父级li加个标识------------// aLi.setAttribute(‘attr‘,‘liWrap‘); //---------给父级li加个标识------end---// //---------给最外层的li加个标识--------// if( v[i].v1 ){ aLi.setAttribute(‘v‘,‘v1‘); } //---------给最外层的li加个标识----end--// ele ? ele.appendChild(aLi) : aUl.appendChild(aLi); var oUl = document.createElement(‘ul‘); aLi.appendChild(oUl); fn(v[i],oUl); //递归 //如果是字符串 }else{ var oLi = document.createElement(‘li‘), oB = document.createElement(‘b‘), txt = document.createTextNode(v[i]), oI = document.createElement(‘i‘); //-------阻止父级事件触发的开关-------------// oLi.setAttribute(‘onoff‘,‘true‘); //-------阻止父级事件触发的开关-------------// //---------给普通li加个标识------------// oLi.setAttribute(‘attr‘,‘li‘); //---------给普通li加个标识------end---// //--------小圆点-----------// oI.setAttribute(‘class‘,‘iconP‘); //--------小圆点-----------// oLi.appendChild(oI); oB.appendChild(txt); oLi.appendChild(oB); (ele) && ( ele.appendChild(oLi) ); (aUl) && ( aUl.appendChild(oLi) ); } } //---------------------------数组走这里-------------end-----------// //---------------------------对象走这里---------------------------// }else if( typeof v === ‘object‘ ){ //枚举对象每一项 for(var attr in v){ //如果是数组 if( Array.isArray(v[attr]) ){ fn(v[attr],ele); //递归 //如果算是对象 }else if( typeof v[attr] === ‘object‘ ){ fn(v[attr]); //递归 //如果是字符串 }else{ var oSpan = document.createElement(‘span‘), oB = document.createElement(‘b‘), txt = document.createTextNode(v[attr]), oI = document.createElement(‘i‘); //--------小圆点-----------// oI.setAttribute(‘class‘,‘iconP‘); oSpan.appendChild(oI); //--------小圆点-----------// oB.appendChild(txt); oSpan.appendChild(oB); ele.setAttribute(‘class‘,attr); ele.appendChild(oSpan); } } //---------------------------对象走这里-----------end-------------// //---------------------字符串----------------// }else{ //do someing } //---------------------字符串-------end-------// return aUl; } //----------------------分析数据结构,生成节点树------------end-----------// //-----生成按钮-----// var oDiv = document.createElement(‘div‘); oDiv.setAttribute(‘id‘,‘zdx_menu‘); var btnWrap = document.createElement(‘div‘); btnWrap.setAttribute(‘id‘,‘zdx_btnWrap‘) var idNames = [‘zdx_cancel‘,‘zdx_submit‘,‘zdx_modify‘]; var btnNames = [‘取消‘,‘确定‘,‘修改‘]; var oA = null; for(var i=0,iLen=idNames.length; i<iLen; i++){ oA = document.createElement(‘a‘); oA.setAttribute(‘id‘,idNames[i]); oA.appendChild(document.createTextNode(btnNames[i])); btnWrap.appendChild(oA); } //-----生成按钮-----// //-----包装节点树----// oDiv.appendChild(btnWrap); oDiv.appendChild(fn(DataDOM)); //-----包装节点树----// //返回节点树 return oDiv; }, //插入节点树到界面里 insertDOMtreeTo:function(parent,DOMtree){ parent.appendChild(DOMtree); }, //删除节点 delNode:function(tNode){ tNode.parentNode.removeChild(tNode); }, //根据DOM更新数据 renewAttr:function(tNodeParent,nowNodeParent){ var oriLi = tNodeParent.getElementsByTagName(‘li‘), nowLi = nowNodeParent.getElementsByTagName(‘li‘); var len = oriLi.length > nowLi.length ? oriLi.length : nowLi.length, o = null, n = null; for(var i=0; i<len; i++){ o = oriLi[i]; n = nowLi[i]; if( o ){ o.setAttribute(‘cid‘,i); } if( n ){ n.setAttribute(‘cid‘,i); } } }, value:function(){ return this.data; } }); //Interaction:交互,实现拖拽 function Interaction(parent,data){ this.data = data; this.DOMBackup = null; this.DataBackup = null; this.DOMtree = this.createDOMtree(data,this.colors); this.insertDOMtreeTo(parent,this.DOMtree); this.btn(); this.redact(); this.roll(‘init‘); this.value } extendByObj( Interaction , DataToDOM , { //编辑、删除、确认编辑、取消编辑、确认删除、取消删除按钮 redact:function(){ var zdx_v0 = $(‘zdx_v0‘); var oI = null; var oITxtNode = null; var cssNames = [‘fa fa-pencil-square-o‘,‘fa fa-trash-o‘,‘fa fa-check‘,‘fa fa-times‘,‘‘,‘‘]; var idNames = [‘modifyIcon‘,‘deleteIcon‘,‘checkIcon‘,‘repealIcon‘,‘tDeleteCheck‘,‘tDeleteRepeal‘]; var btnTitles = [‘编辑‘,‘删除‘,‘确认编辑‘,‘取消编辑‘,‘确认删除‘,‘取消删除‘]; var oITxt = [‘‘,‘‘,‘‘,‘‘,‘确认删除‘,‘取消‘]; for(var i=0,iLen=idNames.length; i<iLen; i++){ oI = document.createElement(‘i‘); cssNames[i] && oI.setAttribute(‘class‘,cssNames[i]); btnTitles[i] && oI.setAttribute(‘title‘,btnTitles[i]); true && oI.setAttribute(‘attr‘,‘icon‘); idNames[i] && oI.setAttribute(‘id‘,idNames[i]); oITxt[i] && ( oITxtNode = document.createTextNode(oITxt[i]) ); oITxtNode && oI.appendChild( oITxtNode ); oITxtNode && (oITxtNode = null); zdx_v0 && zdx_v0.appendChild(oI); } }, //修改,取消,确认按钮 btn:function(){ var cancel = $(‘zdx_cancel‘); var submit = $(‘zdx_submit‘); var modify = $(‘zdx_modify‘); var zdx_v0 = null; var liWraps = null; var zdx_menu = $(‘zdx_menu‘); var _this = this; var addBtn = getAttrEle( ‘li‘ , zdx_v0 , ‘attr‘ , ‘btnChild‘ ); //--------------------------------------修改事件-------------------------------------------// modify.onclick = function(){ //编辑前备份当前节点的状态 _this.DOMBackup = $(‘zdx_v0‘).cloneNode(true); _this.DataBackup = copyData( _this.data ); //----隐藏当前按钮和删除按钮----// this.style.display = ‘none‘; cancel.style.display = ‘block‘; submit.style.display = ‘block‘; //----隐藏当前按钮和删除按钮----// //--即时获列表,因为点击取消再次点击修改获得的列表是上一次的,所以...---// zdx_v0 = $(‘zdx_v0‘); //--即时获列表---// //调用drag方法,使所以按li有相关功能 _this.drag(true); //-----------点击的时候新增供添加的按钮---------// liWraps = getAttrEle( ‘li‘ , zdx_v0 , ‘attr‘ , ‘liWrap‘ ); for(var q=0,qLen=liWraps.length; q<qLen; q++){ _this.btnForCreat( liWraps[q] ); } //-----------点击的时候新增供添加的按钮---------// //---重头初始化卷起收起方法----// _this.roll(‘init‘); //---重头初始化卷起收起方法----// } //--------------------------------------修改事件-------------------------------------------// //--------------------------------------取消事件-------------------------------------------// cancel.onclick = function(){ submit.style.display = ‘none‘; cancel.style.display = ‘none‘; modify.style.display = ‘block‘; //暴力取消——删除掉原来的,用备份的 zdx_menu.removeChild(zdx_v0); zdx_menu.appendChild( _this.DOMBackup ); _this.data = _this.DataBackup; //---找到所以的新增按钮并删除---// zdx_v0 = $(‘zdx_v0‘); addBtn = getAttrEle( ‘li‘ , zdx_v0 , ‘class‘ , ‘addNewNode‘ ); for(var i=0,iLen=addBtn.length; i<iLen; i++){ addBtn[i].parentNode.removeChild(addBtn[i]); } //---找到所以的新增按钮并删除---// //---重头初始化卷起收起方法----// _this.roll(‘init‘); //---重头初始化卷起收起方法----// } //--------------------------------------取消事件-------------------------------------------// //--------------------------------------确认事件-------------------------------------------// submit.onclick = function(){ submit.style.display = ‘none‘; cancel.style.display = ‘none‘; modify.style.display = ‘block‘; //调用drag并传入false,使所有的功能失效 _this.drag(false); //---找到所以的新增按钮并删除---// zdx_v0 = $(‘zdx_v0‘); addBtn = getAttrEle( ‘li‘ , zdx_v0 , ‘class‘ , ‘addNewNode‘ ); for(var i=0,iLen=addBtn.length; i<iLen; i++){ addBtn[i].parentNode.removeChild(addBtn[i]); } //---找到所以的新增按钮并删除---// } //--------------------------------------确认事件-------------------------------------------// }, //添加新的父节点,添加新的子节点按钮 //--------------------用于添加两个按钮,接收一个liWrap的li,并添加到其最后一个li后面-----------------// btnForCreat:function(liWrap){ var oLi = null; var oI = null; var oB = null; var oBTxt = null; var oInput = null; var target = null; var oIcon = null; var txt = [‘添加新的父节点‘,‘添加新的子节点‘]; var ids = [‘btn-parent‘,‘btn-child‘]; var attrs = [‘btnParent‘,‘btnChild‘]; //找到最后一个li target = liWrap.getElementsByTagName(‘ul‘)[0].lastElementChild; for(var j=0; j<2; j++){ oLi = document.createElement(‘li‘); oB = document.createElement(‘b‘); oBTxt = document.createTextNode(txt[j]); oInput = document.createElement(‘input‘); oIcon = document.createElement(‘i‘); oLi.setAttribute(‘class‘,‘addNewNode‘); oLi.setAttribute(‘attr‘,attrs[j]); oLi.setAttribute(‘onoff‘,‘true‘); oIcon.setAttribute(‘class‘,‘fa fa-plus oIcon‘); oInput.setAttribute(‘class‘,‘oInput‘); oInput.style.display = ‘none‘; oLi.appendChild(oIcon); oB.appendChild(oBTxt); oLi.appendChild(oB); oLi.appendChild(oInput); //添加到最后一个li后面 insertAfter(oLi,target); } //调用按钮事件绑定方法 this.bindCreateNodeEvent(); //调用drag方法,主要是在drag里面有一些判定,判定这两个按钮不能被拖动,没有mouseover事件 this.drag(true); return; }, //--------------------用于添加两个按钮----------------------------------------------------------// //给添加按钮绑定事件 bindCreateNodeEvent:function(){ var _this = this; var zdx_v0 = $(‘zdx_v0‘); //-----每次调用都找出页面上所有的添加按钮------// var btnChilds = getAttrEle( ‘li‘ , zdx_v0 , ‘attr‘ , ‘btnChild‘ ); var btnParents = getAttrEle( ‘li‘ , zdx_v0 , ‘attr‘ , ‘btnParent‘ ); //-----每次调用都找出页面上所有的添加按钮------// var oLi = null; var oUl = null; var oSpan = null; var oSpanTxt = null; var oI = null; var oB = null; var oBTxt = null; var tGrade = 0; //---把按钮需要绑定的事件封装成函数-----------------------------------------------------// function fn(type,target){ oLi = document.createElement(‘li‘); oI = document.createElement(‘i‘); oB = document.createElement(‘b‘); oI.setAttribute(‘class‘,‘iconP‘); oLi.appendChild(oI); //如果是“小”按钮,只添加一个新的li在其前面 if(type == ‘btnChilds‘){ oBTxt = document.createTextNode(‘未命名子节点‘); oLi.setAttribute(‘onoff‘,‘true‘); oLi.setAttribute(‘attr‘,‘li‘); oB.appendChild(oBTxt); oLi.appendChild(oB); // target是按钮本身,此刻的target是“添加新的节点”的按钮,新建的单个li插入到这个按钮前面 insertBefore(oLi,target); _this.insertNewData( oLi , target.previousSibling ); //如果是“大”按钮,则需要,新增一个liWrap的li,里面也需要两个用来添加节点的按钮 }else if( type == ‘btnParents‘ ){ oUl = document.createElement(‘ul‘); oSpan = document.createElement(‘span‘); oBTxt = document.createTextNode(‘未命名父节点‘); //需要给新的liWrap的li下的ul设一个等级 tGrade = ‘v‘ + ( parseInt( getParent(target,‘UL‘).getAttribute(‘class‘)[1] ) + 1 ); oLi.setAttribute(‘attr‘,‘liWrap‘); oLi.setAttribute(‘onoff‘,‘true‘); oUl.setAttribute(‘class‘,tGrade); oB.appendChild(oBTxt); oSpan.appendChild(oI); oSpan.appendChild(oB); oUl.appendChild(oSpan); oLi.appendChild(oUl); //给新建的liWrap插入两个用于添加节点的按钮 _this.btnForCreat(oLi); //将他新建的liWrap插入到正确位置 insertBefore(oLi,target.previousSibling); _this.insertNewData( oLi , target.previousSibling.previousSibling ); } // 需要将新的节点“纳入”到整体中,调用drag,主要是它会遍历所有的节点 _this.drag(true); } //---把按钮需要绑定的事件封装成函数-----------------------------------------------------// for(var i=0,iLen=btnChilds.length; i<iLen; i++){ btnChilds[i].onclick = function(){ fn(‘btnChilds‘,this); } btnParents[i].onclick = function(){ fn(‘btnParents‘,this); //需要给新增的liWrap里的添加节点的按钮绑定事件,调用一遍当前方法,传入这个新建的liWrap _this.bindCreateNodeEvent(); } } }, //实现,拖动,编辑,删除功能 drag:function(onOff){ //onoff为true:开启编辑功能 //onoff为false:关闭编辑功能 //全局this var _this = this; //基本 var zdx_v0 = $(‘zdx_v0‘); var allEle = zdx_v0.getElementsByTagName(‘li‘); var allSpan = zdx_v0.getElementsByTagName(‘span‘); //鼠标hover var t = null; var markList = []; var tParent = null; var oriH = null; var target = null; //提示线 var aimLine = $(‘aimLine‘); var aimLinePop = null; var aimLinePopTxt = null; var top = null; var aimT = 0; //编辑,删除节点获得 // iconNodes[0] = $(‘modifyIcon‘), // tDelete = $(‘deleteIcon‘), // tCheck = $(‘checkIcon‘), // tRepeal = $(‘repealIcon‘), // tDeleteCheck = $(‘tDeleteCheck‘), // tDeleteRepeal = $(‘tDeleteRepeal‘) var iconNodes = [ $(‘modifyIcon‘), $(‘deleteIcon‘), $(‘checkIcon‘), $(‘repealIcon‘), $(‘tDeleteCheck‘), $(‘tDeleteRepeal‘) ]; //编辑,删除节点位置 var redactPos = null; //编辑输入框 var oInput = null; var oriInput = null; //辅助变量 var insertOnOff = null; var eB = null; var modifyOnOff = false; var moveAble = true; iconNodes[0].onOff = true; iconNodes[1].onOff = true; //把上一次留下的提示线删了 if(aimLine){ aimLine.parentNode.removeChild(aimLine); } for(var i=0,iLen=allEle.length; i<iLen; i++){ allEle[i].onmouseover = function(e){ //是否开启此功能,通过再次给每个li绑定事件的机会,让它们绑定一个空 if(onOff === false){ return; } //-----------------阻止父li的mouseover事件的触发----------后期用监听改进------------------// tParent = getParent(this,‘LI‘); if( this.getAttribute(‘onoff‘) === ‘true‘ ){ //将取得的正确的this保存下来 t = this; if( tParent ){ tParent.setAttribute(‘onoff‘,‘false‘); markList.push( tParent ); } }else{ if( tParent ){ tParent.setAttribute(‘onoff‘,‘false‘); markList.push( tParent ); }else{ //若tParent为空,说明再往上没有li了,for循环将修改过的li属性再恢复 for( var i=0,iLen=markList.length; i<iLen; i++ ){ markList[i].setAttribute(‘onoff‘,‘true‘); } } return; } //-----------------阻止父li的mouseover事件的触发----------end------------------// //b节点,存放字符串的节点 eB = t.getElementsByTagName(‘b‘)[0]; //---------------------设置“编辑”、“删除”两个按钮的位置-------------------------// redactPos = offsetToBODY(t) - offsetToBODY(zdx_v0) + 5; // modifyOnOff:编辑,或删除状态开启,隐藏这两个按钮,否则显示 if( !modifyOnOff ){ if( t.getAttribute(‘class‘) === ‘addNewNode‘ ){ iconNodes[0].style.display = ‘none‘; iconNodes[1].style.display = ‘none‘; }else{ iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; } } if(moveAble){ for(var q=0,qLen=iconNodes.length; q<qLen; q++){ iconNodes[q].style.top = redactPos + ‘px‘; if( t.getAttribute(‘attr‘) === ‘liWrap‘ && t.getAttribute(‘v‘) === ‘v1‘ ){ iconNodes[q].style.top = redactPos + 9 + ‘px‘; } } } //---------------------设置“编辑”、“删除”、“添加”两个按钮的位置-------------------------// iconNodes[1].onclick = function(){ //------点击禁止按钮移动---------// moveAble = false; //------点击禁止按钮移动---------// modifyOnOff = true; iconNodes[1].style.display = ‘none‘; iconNodes[0].style.display = ‘none‘; iconNodes[4].style.display = ‘block‘; iconNodes[5].style.display = ‘block‘; if(eB.parentNode.nodeName === ‘SPAN‘){ eB.style.color = ‘#ffc3ce‘; } if(eB.parentNode.nodeName === ‘LI‘){ eB.style.color = ‘#dcdcdc‘; } } iconNodes[0].onclick = function(){ //------点击禁止按钮移动---------// moveAble = false; //------点击禁止按钮移动---------// //------隐藏按钮----------// iconNodes[0].style.display = ‘none‘; iconNodes[1].style.display = ‘none‘; //------隐藏按钮----------// //-------显示另一组按钮---------// iconNodes[2].style.display = ‘block‘; iconNodes[3].style.display = ‘block‘; //-------显示另一组按钮---------// //编辑状态开启 modifyOnOff = true; //原文本获取 eB.style.opacity = 0; oriInput = eB.innerHTML; //插入输入框 oInput = document.createElement(‘input‘); oInput.setAttribute(‘class‘,‘oInput‘); oInput.value = ‘点击此处输入内容‘; insertAfter( oInput , eB ); } iconNodes[0].onmousedown = function(){ iconNodes[0].onOff = false; } iconNodes[1].onmousedown = function(){ iconNodes[1].onOff = false; } //-----------输入框获取焦点、失去焦点事件-------------// if(oInput){ oInput.onfocus = function(){ if(oInput.value === ‘点击此处输入内容‘){ oInput.value = ‘‘; } } oInput.onblur = function(){ if(oInput.value === ‘‘){ oInput.value = ‘点击此处输入内容‘; } } } //-----------输入框获取焦点、失去焦点事件-------------// //------------确定输入内容-------------------// iconNodes[2].onclick = function(){ iconNodes[2].style.display = ‘none‘; iconNodes[3].style.display = ‘none‘; iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; if( oInput.value != oriInput && oInput.value != ‘‘ && oInput.value != ‘点击此处输入内容‘ ){ eB.innerHTML = oInput.value; // 调用修改数据方法 _this.modifyData( oInput.value , oInput.parentNode ); } oInput.parentNode.removeChild(oInput); eB.style.opacity = 1; modifyOnOff = false; moveAble = true; } //------------确定输入内容-------------------// //------------取消输入内容-------------------// iconNodes[3].onclick = function(){ iconNodes[2].style.display = ‘none‘; iconNodes[3].style.display = ‘none‘; iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; oInput.parentNode.removeChild(oInput); eB.innerHTML = oriInput; eB.style.opacity = 1; oriInput = null; modifyOnOff = false; moveAble = true; } //------------取消输入内容-------------------// //------------确定删除内容-------------------// iconNodes[4].onclick = function(){ iconNodes[4].style.display = ‘none‘; iconNodes[5].style.display = ‘none‘; iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; modifyOnOff = false; moveAble = true; _this.deleteData(t); t.parentNode.removeChild(t); } //------------确定删除内容-------------------// //------------取消删除内容-------------------// iconNodes[5].onclick = function(){ iconNodes[4].style.display = ‘none‘; iconNodes[5].style.display = ‘none‘; iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; if(eB.parentNode.nodeName === ‘SPAN‘){ eB.style.color = ‘‘; } if(eB.parentNode.nodeName === ‘LI‘){ eB.style.color = ‘‘; } modifyOnOff = false; moveAble = true; } //------------取消删除内容-------------------// } //allEle[i].onmouseover结束 allEle[i].onmouseout = function(){ if(!moveAble){ return; } if( onOff === false ){ return; } //-----------防止闪烁-----------------------// iconNodes[0].onmouseover = function(){ iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; } iconNodes[1].onmouseover = function(){ iconNodes[0].style.display = ‘block‘; iconNodes[1].style.display = ‘block‘; } //-----------防止闪烁----------end----------// } //allEle[i].onmouseout结束 allEle[i].onmousedown = function(e){ //---------zdx_modify控制的开关---------------// if(onOff === false){ return; } //---------由zdx_modify控制的开关---------------// //---------由编辑、删除、确定编辑、取消编辑、确定删除、取消删除控制的开关--------// if(!moveAble){ return; } //---------由编辑、删除、确定编辑、取消编辑、确定删除、取消删除控制的开关--------// //------注意事项-----// /* 由于界面上所有的li都有绑上事件, 且这些有事件的li还有嵌套关系, 在chrome里点击的时候是被点击的li 先执行事件再执行父级li,所以,我 写了一个阻止父级li执行事件的判断, 就是利用自定义属性定义的开关,然后 在不允许执行事件的父级li里提前return掉。 这里可能有个隐藏的坑,就是我所有的 变量都定义在最外层,在阻止父级判断里 用到的变量最终的值取决于判断的执行和 最后一个父级li执行完毕后赋予的值,所以 要小心,不要在【阻止父级li的判断】里随意 保存重要的值!!! */ //-----------------阻止父li的mouseover事件的触发----------后期用监听改进------------------// tParent = getParent(this,‘LI‘); if( this.getAttribute(‘onoff‘) === ‘true‘ ){ //将取得的正确的this保存下来 target = this; if(tParent){ tParent.setAttribute(‘onoff‘,‘false‘); markList.push( tParent ); } }else{ if( tParent ){ tParent.setAttribute(‘onoff‘,‘false‘); markList.push( tParent ); }else{ //若tParent为空,说明再往上没有li了,for循环将修改过的li属性再恢复 for( var i=0,iLen=markList.length; i<iLen; i++ ){ markList[i].setAttribute(‘onoff‘,‘true‘); } } return; } //-----------------阻止父li的mouseover事件的触发----------end------------------// //----------------编辑按钮、或删除按钮被点中不允许移动节点,创建提示线-----------// if( !iconNodes[0].onOff || !iconNodes[1].onOff ){ iconNodes[0].onOff = true; iconNodes[1].onOff = true; return; } //----------------编辑按钮、或删除按钮被点中不允许移动节点,创建提示线-----------// //------判断是不是添加节点的按钮--------// if(t.getAttribute(‘class‘) === ‘addNewNode‘){ return; } //------判断是不是添加节点的按钮--------// //-----------------创建一条提示线,作为到className为zdx_v0的最后一个子节点------------// if( !$(‘aimLine‘) ){ aimLine = document.createElement(‘div‘); aimLine.setAttribute(‘id‘,‘aimLine‘); aimLinePop = document.createElement(‘p‘); aimLinePop.setAttribute(‘id‘,‘aimLinePop‘); aimLine.appendChild(aimLinePop); zdx_v0.appendChild(aimLine); aimLine = $(‘aimLine‘); aimLinePop = $(‘aimLinePop‘); } aimLine.style.display = ‘block‘; //-----------------创建一条提示线,作为到className为zdx_v0的最后一个子节点-----end----// //----------------高亮选中项---------------// target.style.background = ‘#ffd4dc‘; //----------------高亮选中项-------end-----// zdx_v0.onmousemove = function(e){ //----------加工一下t,就mouseover获得的t而言,我们只需要单个的li或span-----------------// if( t.getElementsByTagName(‘li‘).length > 0 ){ t = t.getElementsByTagName(‘span‘)[0]; } tH = getStyle(t,‘height‘,true); //----------加工一下t,就mouseover获得的t而言,我们只需要单个的li或span------end--------// //----------top始终相对于li计算-----------------// top = e.clientY - offsetToBODY(t) + (document.documentElement.scrollTop); //----------top始终相对于li计算-----------------// //-----------------------小于元素高度1/2判断----------------------------------// if( top < tH/2 && t.getAttribute(‘class‘) !== ‘addNewNode‘ ){ aimLinePop.innerHTML = ‘插入到【‘+eB.innerHTML+‘】前面‘; aimT = offsetToBODY(t) - offsetToBODY(zdx_v0); aimLine.style.top = aimT + ‘px‘; if( t.nodeName === ‘SPAN‘ ){ t = getParent(t,‘LI‘); } insertOnOff = ‘before‘; } //-----------------------小于元素高度1/2判断---------------end-----------------// //-----------------------大于元素高度1/2判断------------------------------------// if( top > tH/2 && t.getAttribute(‘class‘) !== ‘addNewNode‘ ){ aimLinePop.innerHTML = ‘插入到【‘+eB.innerHTML+‘】后面‘; if( t.nodeName === ‘SPAN‘ ){ console.log( t.onOff ); if( t.onOff ){ aimLinePop.innerHTML = ‘插入到【‘+eB.innerHTML+‘】里面‘; }else{ t = t.parentNode.parentNode; } } aimT = offsetToBODY(t) - offsetToBODY(zdx_v0) + tH; aimLine.style.top = aimT + ‘px‘; insertOnOff = ‘afert‘; } //-----------------------大于元素高度1/2判断-----------------end-----------------// } //zdx_v0.onmousemove结束 } //zdx_v0.onmousedown结束 zdx_v0.onmouseup = function(){ if(onOff == false){ return; } //-----------只有目标节点的等级比选中节点等级高才能插入成功---------// t.grade = parseInt( getParent(t,‘UL‘).className[1] ); target.grade = parseInt( getParent(target,‘UL‘).className[1] ); //-------判断插入到t上面还是下面---------// if( insertOnOff === ‘before‘ && !contains(t,target) ){ //----------边界判断,如果目标t的ul是v1,应该应该作为v1的最后一项非添加按钮的li-------// if( t.previousSibling.getAttribute(‘v‘) === ‘v1‘ ){ t.preUlLast = t.previousSibling.getElementsByTagName(‘ul‘)[0].lastElementChild; t = t.preUlLast.previousSibling.previousSibling; if( target != t ){ _this.changeOfPositionForData( target , t , ‘after‘ ); insertAfter( target , t ); } }else{ if( target != t ){ _this.changeOfPositionForData( target , t ,‘before‘ ); insertBefore( target , t ); } } //----------边界判断,如果目标t的ul是v1,应该应该作为v1的最后一项非添加按钮的li-------// }else if( insertOnOff === ‘afert‘ && !contains(t,target) ){ if( target != t ){ _this.changeOfPositionForData( target , t , ‘after‘ ); insertAfter( target , t ); } }else{ /*do something*/ } //-------判断插入到t上面还是下面----------// //-----------只有目标节点的等级比选中节点等级高才能插入成功---------// //------隐藏提示线,恢复原状------// if( aimLine ){ aimLine.style.display = ‘none‘; aimLine.style.top = ‘‘; target.style.background = ‘‘; } zdx_v0.onmousemove = null; //------隐藏提示线,恢复原状------// } //zdx_v0.onmouseup结束 } //for结束 }, //计算ul的高度 calculateUl:function(liWrap){ var cnt = 0; cnt = getStyle(liWrap,‘height‘,true); return cnt; }, //ul收起,降下方法 roll:function(v){ var zdx_v0 = $(‘zdx_v0‘); var liWraps = getAttrEle( ‘li‘ , zdx_v0 , ‘attr‘ , ‘liWrap‘ ); var t = null; var markList = []; var tV1 = null; var tParent = null; var eUl = null; if( v === ‘init‘ ){ for(var i=0,iLen=liWraps.length; i<iLen; i++){ //----默认只显示一级菜单-----// if( liWraps[i].getAttribute(‘v‘) !== ‘v1‘ ){ liWraps[i].style.height = getStyle( liWraps[i].getElementsByTagName(‘span‘)[0], ‘height‘ ); // liWraps[i].style = ‘transition: height .5s; ‘+ liWraps[i].style.height +‘;‘ //开关为开 liWraps[i].getElementsByTagName(‘span‘)[0].onOff = false; }else{ //开关为关 liWraps[i].getElementsByTagName(‘span‘)[0].onOff = true; } //----默认只显示一级菜单-----// liWraps[i].getElementsByTagName(‘span‘)[0].onclick = function(){ console.log( this ); t = this; if( !this.open ){ this.open = getStyle( this, ‘height‘, true ) + getStyle( this.nextSibling, ‘height‘, true ) * (this.parentNode.getElementsByTagName(‘li‘).length-1); } if(!this.close){ this.close = getStyle( this, ‘height‘, true ); } if(this.onOff){ this.parentNode.parentNode.style.height = this.close + ‘px‘; this.onOff = !this.onOff; }else{ this.parentNode.parentNode.style.height = ‘‘; this.onOff = !this.onOff; } } } } }, }); // Interaction结束 var generateMenu = Interaction;
以上是关于可拖动菜单 总结的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法在 Android 的可穿戴设备中创建类似可拖动菜单的东西?