需求

应老罗的要求,做一个可拖动的长条形列表菜单。

这是原来的样子

进入编辑界面,它的编辑添加都不能实时更新,而且操作起来比较麻烦。

所以我估计一下应该有这些需求:

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的值应该比包含它的数组+1correctV = 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的值应该比包含它的数组+1correctV = tV[0] + ( parseInt(tV[1]) + 1 );// 利用JSON.stringify将要改动的json变换成字符串,利用replace替换v的值,再反向回jsontarInfo[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'];//找到最后一个litarget = 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:关闭编辑功能//全局thisvar _this = this;//基本var zdx_v0 = $('zdx_v0');var allEle = zdx_v0.getElementsByTagName('li');var allSpan = zdx_v0.getElementsByTagName('span');//鼠标hovervar 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;

转载于:https://www.cnblogs.com/mflnhg/p/9744943.html

可拖动菜单 【总结】相关推荐

  1. 有趣的自由拖动菜单形式

    <html> <head> <meta http-equiv="Content-Type" content="text/html; char ...

  2. Android自定义View之仿QQ侧滑菜单实现

    最近,由于正在做的一个应用中要用到侧滑菜单,所以通过查资料看视频,学习了一下自定义View,实现一个类似于QQ的侧滑菜单,顺便还将其封装为自定义组件,可以实现类似QQ的侧滑菜单和抽屉式侧滑菜单两种菜单 ...

  3. 自定义Windows 10开始菜单的10种方法

    Windows 10 finally brought back the Start menu, and it's more customizable than ever. Here's a quick ...

  4. PE启动菜单修改工具 MsgDiyer(GfxMenu Message制作工具) V2.0.3官方版

    MsgDiyer基本功能  1.新建message文件.修改现有message文件: 2.自定义背景图片: 3.制作message字体,包括行高.大小等(目前不兼容WIN7): 4.自定义字库: 5. ...

  5. 手势魅力-设置一个触摸菜单

    虽互不曾谋面,但希望能和你成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击右上方,可关注本刊 (后台回复该标题,可下载源码,阅读文字就好,口头表达能力 ...

  6. 分享112个JS菜单导航,总有一款适合您

    分享112个JS菜单导航,总有一款适合您 112个JS菜单导航下载链接:https://pan.baidu.com/s/1Dm73d2snbu15hZErJjTXxg?pwd=fz1c  提取码:fz ...

  7. 8个超棒的使用javascript开发的视觉特效网站

    日期:2012-8-17  来源:GBin1.com 随着flash的落寞及其HTML5和CSS3的发展,更多的网站开始使用javascript,CSS3和HTML5来开发炫酷的特效.今天我们挑选了8 ...

  8. linux mac docky,Ubuntu 7.10中安装酷酷的MAC风格 dock(图)

    看我的Ubuntu 7.10下的MAC风格 dock效果图,喜欢的话看下面跟我学! 全新安装Ubuntu 7.10已经好长时间了,默认的界面我不太喜欢,在Linux公社的帮助下,我也要给我的Ubunt ...

  9. Android开发— 2016_最流行的Android组件、工具、框架大全(二)

    2019独角兽企业重金招聘Python工程师标准>>> 泡在网上的日子 首页 代码 话题 问答 标签 关于 登录注册 首页 › 安卓开发 › android开发 Android开发- ...

最新文章

  1. 初探JavaScript魅力1
  2. Eclipse非常有用的快捷键
  3. [macOS]一些基础控件
  4. jvm 崩溃日志设置_JVM致命错误日志(hs_err_pid.log)分析(转载)
  5. cURL 原作者收到死亡恐吓邮件!
  6. SQL Server 网络协议和 TDS 端点
  7. shell脚本中数组的使用_Shell脚本中的数组
  8. 【基础】深入浅出神经网络基础
  9. android自动签约续费功能,微信代扣 Android 开发流程
  10. ProxySQL 配置详解及读写分离(+GTID)等功能说明 (完整篇)
  11. 等保合规2022系列 | 一个中心+三重防护,助力企业等级保护建设更科学
  12. 2018年个人所得税Excel计算公式
  13. Maven的作用到底是什么?
  14. JAVA版微信支付V3—JSAPI支付
  15. 我的世界服务器无限背包,我的世界无限背包存档
  16. 远程桌面连接不上,三种方法教你重启服务器
  17. 赠与今年的大学毕业生-----------胡适
  18. Coolpad 大神F2 全网通版root尝试过程
  19. 机器人参数化建模与仿真,软体机器人
  20. 阿里云MVP携手Gopher China,寻找下一个MVP!申请就送100元代金券!

热门文章

  1. 天猫某个商品秒杀js精简
  2. Java第四十八天,Jsp之Taglib,自定义标签详解
  3. Angular4 - 启动过程
  4. 05. HAXM is not installed
  5. 【收藏】UltraISO制作U盘启动安装CentOS 7.4
  6. 每日IN语(2009-02-04)好马不吃回头草,因为...
  7. 博途(TIA)软件安装教程;博途软件安装常见问题解决办法
  8. 微信小程序开发之——个人中心-介绍(1)
  9. java常见面试题(3-4年工作经验)整理
  10. Nginx实现内网服务唯一端口外网映射