jsPlumb之流程圖項目總結及實例


在使用jsPlumb過程中,所遇到的問題,以及解決方案,文中引用了《數據結構與算法JavaScript描述》的相關圖片和一部分代碼.截圖是有點多,有時比較懶,沒有太多的時間去詳細的編輯.

前言

首先是UML類圖
3

然后是流程圖
4

使用了jsPlumb的相關功能,初版是可以看到雛形了,差不多用了兩個月的時間,中間斷斷續續的又有其它工作穿插,但還是把基本功能做出來了.

其實做完了之后,才發現jsPlumb的功能,只用到了很少的一部分,更多的是對於內部數據結構的理解和實現,只能說做到了數據同步更新,距離數據驅動仍然有一定的距離.

這里會總結和記錄一下項目中遇到的問題,和解決的方法,如果有更好的方法,歡迎指出.

對於連線上的多個標簽的處理

如上圖所示,一開始是認為是否是要在連線時,配置兩個overlays,

    var j = jsPlumb.getInstance();

    j.connect({
        source:source,
        target:target,
        overlays:[
            "Arrow",
            ["label",{label:"foo1",location:0.25,id:"m1"}],
            ["label",{label:"foo2",location:0.75,id:"m2"}]
        ]
    })

當然,這里也有坑,如果id重復,那么會使用最后一個,而不會重合,包括jsPlumb內部緩存的數據都只會剩下最后的那個.

后面發現,其實也可以通過importDefaults函數來動態修改配置項.

    j.importDefaults({
        ConnectionOverlays: [
            ["Arrow", { location: 1, id: "arrow", length: 10, foldback: 0, width: 10 }],
            ["Label", { label: "n", id: "label-n", location: 0.25, cssClass: "jspl-label" }],
            ["Label", { label: "1", id: "label-1", location: 0.75, cssClass: "jspl-label" }]
        ]
    })

只不過這樣,只會在運行了函數之后的連線里,才能有兩個標簽顯示,而之前的則無法一起變化.
所以為了方便,直接在初始化里將其給修改了.

Groups的使用

在做流程圖時,Group確實是個問題,如上圖的無限嵌套層級中,就無法使用jsPlumb提供的Groups功能.
按照文檔中來說,如果標識一個元素為組,則該組中的元素則會跟隨組的移動而移動,連線也是,但問題就是一旦一個元素成為組了,那就不能接受其它組元素了,換句話說,它所提供的的Groups方法只有一層,自然無法滿足要求.
先把總結的組的用法貼出來:

    j.addGroup({
        el:el,
        id:"one"
        constrain:true, // 子元素僅限在元素內拖動
        droppable:true, // 子元素是否可以放置其他元素
        draggable:true, // 默認為true,組是否可以拖動
        dropOverride:true ,// 組中的元素是否可以拓展到其他組,為true時表示否,這里的拓展會對dom結構進行修改,而非單純的位置移動
        ghost:true, // 是否創建一個子元素的副本元素
        revert:true, // 元素是否可以拖到只有邊框可以重合
    })

后面采用了新的方式,在節點移動時,動態刷新連線

    j.repaintEverything();

而為了不阻塞頁面,需要用到函數節流throttle()

    function throttle(fn,interval){
        var canRun = true;

        return function(){
            if(!canRun) return;
            canRun = false;
            setTimeout(function(){
                fn.apply(this,arguments);
                canRun = true;
            },interval ? interval : 300);
        };
    };

這是一個簡單的實現方式,主要就是為了減少dom中事件移動時重復調用的事件,同時達到執行事件的目的(只允許一個函數在x毫秒內執行一次);
當然,也可以使用underscore.js中自帶的_.throttle()函數,同樣可以達到目的.

這里的html結構就使用了嵌套的層級,將父級和子級使用這種層級保存到內部的數據源里

多層or一層 數據結構解析

5

類似這種實際存在嵌套關系的數據體,有兩種方式可以進行管理,

  • 多層級嵌套:類似
        [
            {
                id:"1",
                child:{
                    id:"2",
                    child:{
                        id:"3",
                        child:{}
                    }
                }
            }
        ]
    

用來進行管理的話,優點是直觀,能根據層級就知道整體結構大概是多少,轉換成xml或者html也很方便.
但缺點就是進行查找和修改,並不是那么方便.

  • 一層展示所有節點:類似
        [
            {
                id:"1",
                child:[{
                    id:"2"
                }]
            },
            {
                id:"2",
                parentId:"1",
                child:[{
                    id:"3"
                }]
            },
            {
                id:"3",
                parentId:"2",
                child:[]
            }
        ]
    

這種結構好處就是全部在一個層級中,查找起來和修改數據非常方便,而如果想要解析成多層級的結構,只需要運用遞歸,來生成新結構:

    function mt(){
        var OBJ;
        this.root = null;
        this.Node = function(e) {
            this.id = e.id;
            this.name = e.name;
            this.parentId = e.parentId;
            this.children = [];
        };

        this.insert=function(e,key){
            function add(obj,e){
                if(obj.id == e.parentId){
                    obj.children.push(e);
                } else {
                    for (var i = 0; i < obj.children.length; i++) {
                        add(obj.children[i], e);
                    }
                }
            }

            if (e != undefined) {
                e = new this.Node(e);
            } else {
                return;
            }

            if (this.root == null) {
                this.root = e;
            } else {
                OBJ = this.root;
                add(OBJ, e);
            }
        }

        this.init = function(data){
            var _this = this;
            for(var i = 0;i<data.length;i++){
                _this.insert(data[i]);
            }

            return OBJ;
        }
    }

將一層的數組通過初始化函數init,就可以轉為多層級
6

如果想轉成html結構,只需要稍微改下函數,就可以實現了.

校驗流程是否存在死路(是否存在不能到達圖的終點的路徑的點)

這個就完全得靠算法來實現了.首先,對於圖的理解是重點
7

我也懶得打字了,直接用圖表示一下,基本的圖大致是這樣,而具體的表現形式則是
8

可以看到,基礎的圖的表現形式,可以用一個鄰接表來表示;

而實現,則可以看到下列的代碼:

function Graph1(v) {
  this.vertices = v; // 總頂點
  this.edges = 0; // 圖的邊數
  this.adj = [];

  // 通過 for 循環為數組中的每個元素添加一個子數組來存儲所有的相鄰頂點,[並將所有元素初始化為空字符串。]?
  for (var i = 0; i < this.vertices; ++i) {
    this.adj[i] = [];
  }

  /**
   * 當調用這個函數並傳入頂點 v 和 w 時,函數會先查找頂點 v 的鄰接表,將頂點 w 添加到列表中
   * 然后再查找頂點 w 的鄰接表,將頂點 v 加入列表。最后,這個函數會將邊數加 1。
   * @param {[type]} v [第一個頂點]
   * @param {[type]} w [第二個頂點]
   */
  this.addEdge = function(v, w) {
    this.adj[v].push(w);
    this.adj[w].push(v);
    this.edges++;
  }

  /**
   * 打印所有頂點的關系簡單表現形式
   * @return {[type]} [description]
   */
  this.showGraph = function() {
    for (var i = 0; i < this.vertices; ++i) {
      var str = i + " ->";
      for (var j = 0; j < this.vertices; ++j) {
        if (this.adj[i][j] != undefined) {
          str += this.adj[i][j] + ' '
        }
      }

      console.log("表現形式為:" + str);
    }

    console.log(this.adj);
  }
}

而光構建是不夠的,所以來看下基礎的搜索方法:
深度優先搜索和廣度優先搜索;

深度優先搜索

先從初始節點開始訪問,並標記為已訪問過的狀態,再遞歸的去訪問在初始節點的鄰接表中其他沒有訪問過的節點,依次之后,就能訪問過所有的節點了
9

  /**
   * 深度優先搜索算法
   * 這里不需要頂點,也就是鄰接表的初始點
   */
    this.dfs = (v) {
        this.marked[v] = true;
        for (var w of this.adj[v]) {
            if (!this.marked[w]) {
                this.dfs(w);
            }
        }
    }

根據圖片和上述的代碼,可以看出深度搜索其實可以做很多其他的擴展

廣度優先搜索

10

  /**
   * 廣度優先搜索算法
   * @param  {[type]} s [description]
   */
  this.bfs = function(s) {
    var queue = [];
    this.marked[s] = true;
    queue.push(s); // 添加到隊尾
    while (queue.length > 0) {
      var v = queue.shift(); // 從隊首移除
      console.log("Visisted vertex: " + v);
      for (var w of this.adj[v]) {
        if (!this.marked[w]) {
          this.edgeTo[w] = v;
          this.marked[w] = true;
          queue.push(w);
        }
      }
    }
  }

而如果看了《數據結構與算法JavaScript描述》這本書,有興趣的可以去實現下查找最短路徑拓撲排序;

兩點之間所有路徑

這算是找到的比較能理解的方式來計算
11

以上圖為例,這是一個簡單的流程圖,可以很簡單的看出,右邊的流程實際上是未完成的,因為無法到達終點,所以是一個非法點,而通過上面的深度搜索,可以看出,只要對深度優先搜索算法進行一定的修改,那么就可以找到從開始到結束的所有的路徑,再通過對比,就可以知道哪些點無法到達終點,從而確定非法點.
上代碼:

    /**
     * 深度搜索,dfs,解兩點之間所有路徑
     * @param  {[type]} v [description]
     * @return {[type]}   [description]
     */
    function Graph2(v) {
        var _this = this;

        this.vertices = v; // 總頂點
        this.edges = 0; //圖的起始邊數
        this.adj = []; //內部鄰接表表現形式
        this.marked = []; // 內部頂點訪問狀態,與鄰接表對應
        this.path = []; // 路徑表示
        this.lines = []; // 所有路徑匯總

        for (var i = 0; i < this.vertices; ++i) {
            _this.adj[i] = [];
        }

        /**
         * 初始化訪問狀態
         * @return {[type]} [description]
         */
        this.initMarked = function() {
            for (var i = 0; i < _this.vertices; ++i) {
                _this.marked[i] = false;
            }
        };

        /**
         * 在鄰接表中增加節點
         * @param {[type]} v [description]
         * @param {[type]} w [description]
         */
        this.addEdge = function(v, w) {
            this.adj[v].push(w);
            this.edges++;
        };

        /**
         * 返回生成的鄰接表
         * @return {[type]} [description]
         */
        this.showGraph = function() {
            return this.adj;
        };

        /**
         * 深度搜索算法
         * @param  {[type]} v    [起點]
         * @param  {[type]} d    [終點]
         * @param  {[type]} path [路徑]
         * @return {[type]}      [description]
         */
        this.dfs = function(v, d, path) {
            var _this = this;

            this.marked[v] = true;
            path.push(v);

            if (v == d) {
                var arr = [];
                for (var i = 0; i < path.length; i++) {
                    arr.push(path[i]);
                }

                _this.lines.push(arr);
            } else {
                for (var w of this.adj[v]) {
                    if (!this.marked[w]) {
                        this.dfs(w, d, path);
                    }
                }
            }

            path.pop();
            this.marked[v] = false;
        };

        this.verify = function(arr, start, end) {

            this.initMarked();

            for (var i = 0; i < arr.length; i++) {
                _this.addEdge(arr[i].from, arr[i].to);
            }

            this.dfs(start, end, this.path);
            return this.lines;
        };
    }

可以看出修改了addEdge()函數,將鄰接表中的雙向記錄改為單向記錄,可以有效避免下圖的錯誤計算:
12

只計算起點到終點的所有連線有時並不客觀,如果出現
13

這種情況的話,實際上深度遍歷並不能計算出最右邊的節點是合法的,那么就需要重新修改起點和終點,來推導是否能夠到達終點.從而判定該點是否合法.至於其他的,只是多了個返回值,存儲了一下計算出來的所有路徑.
而在dfs函數中,當滿足能夠從起點走到終點的,則記錄下當前的path中的值,保存到lines中去,而每一次對於path的推入或者推出,保證了只有滿足條件的點,才能被返回;
this.marked[v] = false,則確保了,在每一次重新計算路徑時,都會驗證每個點是否存在不同的相對於終點能夠到達的路徑是否存在.
當然,一定會有更加簡單的方法,我這里只是稍微修改了下基礎的代碼!

redo和undo

這是我覺得最簡單卻耗時最久的功能,思路都知道:創建一個隊列,記錄每一次創建一個流程節點,刪除一個流程節點,建立一個新的關聯關系,刪除一個新的關聯關系等,都需要記錄下來,再通過統一的接口來訪問隊列,執行操作.
但在具體實現上,jsPlumb的remove確實需要注意一下:
首先,如果需要刪除連線,那么使用jsPlumb提供的detach()方法,就可以刪除連線,注意,傳入的數據應該是connection對象.
當然,也可以使用remove()方法,參數為選擇器或者element對象都可以,這個方法刪除的是一個節點,包括節點上所有的線.
而jsPlumb中會內部緩存所有的數據,用於刷新,和重連.
那么當我移除一個多層級且內部有連線的情況時,如果只刪除最外層的元素,那么內部的連線實際上並沒有清除,所以當redo或者移動時,會出現連線的端點有一端會跑到坐標原點,也就是div上(0,0)的地方去.所以清除時,需要注意,要把內部的所有節點依次清除,才不會發生一些莫名其妙的bug.

而在刪除和連接連線上,我使用了jsPlumb提供的事件bind('connection')bind("connectionDetached"),用於判斷一條連線被連接或者刪除.而在記錄這里的redo和undo事件時,尤其要注意,需要首先確定刪除和連接時的連線的類型,否則會產生額外的隊列事件.
因此,在使用連接事件時,就可以使用

jsPlumb.connect({
    source:"foo",
    target:"bar",
    parameters:{
        "p1":34,
        "p2":new Date(),
        "p3":function() { console.log("i am p3"); }
    }
});

來進行類型的傳參,這樣事件觸發時就可以分類處理.
也可以使用connection.setData()事件,參數可以指定任意的值,通過connection.getData()方法,就可以拿到相應的數據了.
而redo和undo本身確實沒有什么東西

    var defaults = {
        'name': "mutation",
        'afterAddServe':$.noop,
        'afterUndo':$.noop,
        'afterRedo':$.noop
    }

    var mutation = function(options){
        this.options = $.extend(true,{},defaults,options);

        this.list = [];
        this.index = 0;
    };

    mutation.prototype = {
        addServe:function(undo,redo){
            if(!_.isFunction(undo) || !_.isFunction(redo)) return false;

            // 說明是在有后續操作時,更新了隊列
            if(this.canRedo){
                this.splice(this.index+1);
            };
            this.list.push({
                undo:undo,
                redo:redo
            });

            console.log(this.list);

            this.index = this.list.length - 1;

            _.isFunction(this.options.afterAddServe) && this.options.afterAddServe(this.canUndo(),this.canRedo());
        },
        /**
         * 相當於保存之后清空之前的所有保存的操作
         * @return {[type]} [description]
         */
        reset:function(){
            this.list = [];
            this.index = 0;
        },
        /**
         * 當破壞原來隊列時,需要對隊列進行修改,
         * index開始的所有存儲值都沒有用了
         * @param  {[type]} index [description]
         * @return {[type]}       [description]
         */
        splice:function(index){
            this.list.splice(index);
        },
        /**
         * 撤銷操作
         * @return {[type]} [description]
         */
        undo:function(){
            if(this.canUndo()){
                this.list[this.index].undo();
                this.index--;

                _.isFunction(this.options.afterUndo) && this.options.afterUndo(this.canUndo(),this.canRedo());
            }
        },
        /**
         * 重做操作
         * @return {[type]} [description]
         */
        redo:function(){
            if(this.canRedo()){
                this.index++;
                this.list[this.index].redo();

                _.isFunction(this.options.afterRedo) && this.options.afterRedo(this.canUndo(),this.canRedo());
            }
        },
        canUndo:function(){
            return this.index !== -1;
        },
        canRedo:function(){
            return this.list.length - 1 !== this.index;
        }
    }

    return mutation;

每次在使用redo或者undo時,只需要判斷當前是否是隊列的尾端或者起始端,再確定是否redo或者undo就可以了.
調用時的undo()redo()通過傳參,將不同的函數封裝進隊列里,就可以減少耦合度.

放大縮小

這里想了想還是記錄一下,方法采用了最簡單的mousedownmousemove,讓元素在節流中動態的變化大小,就可以了,
14

只需要用一個節點,在點擊元素時,根據元素的大小來確定該輔助節點四個點的位置,就可以了,只要監聽了這四個點的位置,再同步給該定位元素,就能實現這一效果

define([
    'text!textPath/tpl.flow.control.html',
], function(flowControl) {
    var defaults = {
        stage: document, //舞台
        root: null,
        refresh: null,
        dragStop: null
    }

    var resize = function(el, options) {
        this.options = $.extend(true, {}, defaults, options);
        this.target = el instanceof jQuery ? el : $(el);
        this.init();
    };

    resize.prototype = {
        init: function() {
            this.initResizeBox();
        },
        renderTpl: function(tpl, data) {
            if (!_.isFunction(tpl)) tpl = _.template(tpl);
            return tpl(data)
        },
        initResizeBox: function() {
            var _this = this;

            this.ox = 0; // 初始位置x
            this.oy = 0; // 初始位置y
            this.ow = 0; // 初始寬度
            this.oh = 0; // 初始高度

            this.oLeft = 0; // 初始元素left定位
            this.oTop = 0; // 初始元素top定位
            this.helperLeft = 0; // 初始助手left定位
            this.helperTop = 0; // 初始助手top定位
            this.org = null; // 映射元素
            this.parent = ''; // 父元素
            this.orgItem = null; // 映射子元素,用於計算范圍
            this.minWidth = 0; // 映射元素最小寬度
            this.minHeight = 0; // 映射元素最小高度
            this.maxWidth = 0; // 映射元素最大寬度
            this.maxHeight = 0; // 映射元素最大高度

            this.helper = $(this.renderTpl(flowControl)).appendTo(this.target); // 縮放助手
            this.bindResizeEvent(this.helper);
        },
        offset: function(curEle) {
            var totalLeft = null,
                totalTop = null,
                par = curEle.offsetParent;
            //首先加自己本身的左偏移和上偏移
            totalLeft += curEle.offsetLeft;
            totalTop += curEle.offsetTop
            //只要沒有找到body,我們就把父級參照物的邊框和偏移也進行累加
            while (par) {
                if (navigator.userAgent.indexOf("MSIE 8.0") === -1) {
                    //累加父級參照物的邊框
                    totalLeft += par.clientLeft;
                    totalTop += par.clientTop
                }

                //累加父級參照物本身的偏移
                totalLeft += par.offsetLeft;
                totalTop += par.offsetTop
                par = par.offsetParent;
            }

            return {
                left: totalLeft,
                top: totalTop
            }
        },
        scrollArtboard: function(pos, el) {
            var _this = this;

            var artboardWidth = $(".artboard.flow").outerWidth(),
                artboardHeight = parseFloat($(".artboard.flow").outerHeight()) - 42,
                elWidth = el.outerWidth(),
                elHeight = el.outerHeight(),
                isConcurrenceChild = el.parent('.symbol_flow-concurrence').length > 0 ? true : false;

            if (isConcurrenceChild) {
                if (_this.offset(el.get(0)).left + elWidth > artboardWidth) {
                    console.log("並發體越界");
                    $(".artboard.flow").scrollLeft(_this.offset(el.get(0)).left + elWidth);
                }

                if (_this.offset(el.get(0)).top + elHeight > artboardHeight) {
                    console.log("並發體越界");
                    $(".artboard.flow").scrollTop(_this.offset(el.get(0)).top + elHeight);
                }
            } else {
                // 長度長於畫布
                if (pos.left + elWidth > artboardWidth) {
                    $(".artboard.flow").scrollLeft(pos.left + elWidth);
                }

                if (pos.top + elHeight > artboardHeight) {
                    $(".artboard.flow").scrollTop(pos.top + elHeight);
                }
            }
        },
        hasBeyond: function(el,master) {
            var _this = this;

            if (_this.isConcurrenceChild) {
                var parentOffset = _this.offset(_this.parent.get(0));
                parentOffset.height = parentOffset.top + _this.parent.outerHeight();
                parentOffset.width = parentOffset.left + _this.parent.outerWidth();
                var elOffset = _this.offset(el.get(0));
                elOffset.height = elOffset.top + el.outerHeight();
                elOffset.width = elOffset.left + el.outerWidth();

                if (master.left < 0 || master.top < 0) {
                    $(_this.options.stage).trigger('mouseup');
                }

                if (parentOffset.height < elOffset.height || parentOffset.width < elOffset.width) {
                    $(_this.options.stage).trigger('mouseup');
                }
            }
        },
        /**
         * 根據傳入的操作節點進行定位
         * 新增根據parentId來判斷並發體中的定位校准
         * @param  {[type]} target [description]
         * @return {[type]}        [description]
         */
        position: function(target, parentId) {
            var _this = this;

            this.org = target;
            this.parent = $("#" + parentId);
            this.orgItem = target.children('.symbol_flow-concurrence');
            this.oLeft = (this.org.offset().left - this.options.root.offset().left) < parseFloat(this.org.css('left')) ? parseFloat(this.org.css('left')) : (this.org.offset().left - this.options.root.offset().left);
            this.oTop = (this.org.offset().top - this.options.root.offset().top) < parseFloat(this.org.css('top')) ? parseFloat(this.org.css('top')) : (this.org.offset().top - this.options.root.offset().top);
            this.minWidth = parseFloat(this.orgItem.css('minWidth').replace('px', ''));
            this.minHeight = parseFloat(this.orgItem.css('minHeight').replace('px', ''));
            this.maxHeight = parseFloat(this.org.closest('.symbol_flow-concurrence').outerHeight());
            this.maxWidth = parseFloat(this.org.closest('.symbol_flow-concurrence').outerWidth());

            this.helperLeft = parseFloat(this.offset(target.get(0)).left);
            this.helperTop = parseFloat(this.offset(target.get(0)).top) - 42; // 頂部偏移

            this.isConcurrenceChild = parentId == "artboard" ? false : true;

            this.helper.css({
                width: _this.orgItem.outerWidth(),
                height: _this.orgItem.outerHeight(),
                left: _this.helperLeft,
                top: _this.helperTop
            })
            _this.show();
        },
        show: function() {
            this.helper.css("display", "block");
        },
        hide: function() {
            this.helper.css("display", "none");
        },
        bindResizeEvent: function(el) {
            var _this = this;

            var nwMove = false;
            el.on('mousedown', '.nw', function(e) {
                _this.ox = e.pageX;
                _this.oy = e.pageY;
                _this.ow = el.width();
                _this.oh = el.height();
                _this.oLeft = _this.isConcurrenceChild ? _this.org.offset().left - _this.parent.offset().left : _this.offset(_this.org.get(0)).left;
                _this.oTop = _this.isConcurrenceChild ? _this.org.offset().top - _this.parent.offset().top : parseFloat(_this.offset(_this.org.get(0)).top) - 42;

                _this.helperLeft = parseFloat(_this.offset(_this.org.get(0)).left);
                _this.helperTop = parseFloat(_this.offset(_this.org.get(0)).top) - 42;

                nwMove = true;

                $(_this.options.stage).on('mousemove', _.throttle(function(e) {
                    if (nwMove) {
                        var x = e.pageX - _this.ox;
                        var y = e.pageY - _this.oy;
                        var master = {
                            height: (_this.oh - y) < _this.minHeight ? _this.minHeight : (_this.oh - y) > _this.maxHeight ? _this.maxHeight : (_this.oh - y),
                            top: _this.oTop + y,
                            width: (_this.ow - x) < _this.minWidth ? _this.minWidth : (_this.ow - x) > _this.maxWidth ? _this.maxWidth : (_this.ow - x),
                            left: _this.oLeft + x
                        };

                        var master2 = {
                            height: master.height,
                            top: _this.helperTop + y,
                            width: master.width,
                            left: _this.helperLeft + x
                        }

                        el.css(master2);
                        _this.org.css(master);
                        _this.scrollArtboard(master2, el);
                        _this.hasBeyond(el,master);
                    }

                    _.isFunction(_this.options.refresh) && _this.options.refresh();
                }, 50)).on('mouseup', function() {
                    nwMove = false;
                    $(this).off('mousemove');
                    $(this).off('mouseup');
                    _.isFunction(_this.options.dragStop) && _this.options.dragStop(_this.org);
                });
            });

            var neMove = false;
            el.on('mousedown', '.ne', function(e) {
                _this.ox = e.pageX;
                _this.oy = e.pageY;
                _this.ow = el.width();
                _this.oh = el.height();
                _this.oTop = _this.isConcurrenceChild ? _this.org.offset().top - _this.parent.offset().top : parseFloat(_this.offset(_this.org.get(0)).top) - 42;

                _this.helperTop = parseFloat(_this.offset(_this.org.get(0)).top) - 42;

                neMove = true;
                console.log("ne???");

                $(_this.options.stage).on('mousemove', _.throttle(function(e) {
                    if (neMove) {
                        var x = e.pageX - _this.ox;
                        var y = e.pageY - _this.oy;
                        var master = {
                            height: (_this.oh - y) < _this.minHeight ? _this.minHeight : (_this.oh - y) > _this.maxHeight ? _this.maxHeight : (_this.oh - y),
                            top: _this.oTop + y,
                            width: (_this.ow + x) < _this.minWidth ? _this.minWidth : (_this.ow + x) > _this.maxWidth ? _this.maxWidth : (_this.ow + x)
                        };

                        var master2 = {
                            height: master.height,
                            top: _this.helperTop + y,
                            width: master.width
                        }

                        el.css(master2);
                        _this.org.css(master);
                        _this.scrollArtboard(master2, el);
                        _this.hasBeyond(el,master);
                    }

                    _.isFunction(_this.options.refresh) && _this.options.refresh();
                }, 50)).on('mouseup', function() {
                    neMove = false;
                    $(this).off('mousemove');
                    $(this).off('mouseup');
                    _.isFunction(_this.options.dragStop) && _this.options.dragStop(_this.org);
                });
            });

            var swMove = false;
            el.on('mousedown', '.sw', function(e) {
                _this.ox = e.pageX;
                _this.oy = e.pageY;
                _this.ow = el.width();
                _this.oh = el.height();
                _this.oLeft = _this.isConcurrenceChild ? _this.org.offset().left - _this.parent.offset().left : _this.offset(_this.org.get(0)).left;

                _this.helperLeft = parseFloat(_this.offset(_this.org.get(0)).left);

                swMove = true;
                console.log("sw???");

                $(_this.options.stage).on('mousemove', _.throttle(function(e) {
                    if (swMove) {
                        var x = e.pageX - _this.ox;
                        var y = e.pageY - _this.oy;
                        var master = {
                            height: (_this.oh + y) < _this.minHeight ? _this.minHeight : (_this.oh + y) > _this.maxHeight ? _this.maxHeight : (_this.oh + y),
                            width: (_this.ow - x) < _this.minWidth ? _this.minWidth : (_this.ow - x) > _this.maxWidth ? _this.maxWidth : (_this.ow - x),
                            left: _this.oLeft + x
                        };

                        var master2 = {
                            height: master.height,
                            width: master.width,
                            left: _this.helperLeft + x
                        }

                        el.css(master2);
                        _this.org.css(master);
                        _this.scrollArtboard(master2, el);
                        _this.hasBeyond(el,master);
                    }

                    _.isFunction(_this.options.refresh) && _this.options.refresh();
                }, 50)).on('mouseup', function() {
                    swMove = false;
                    $(this).off('mousemove');
                    $(this).off('mouseup');
                    _.isFunction(_this.options.dragStop) && _this.options.dragStop(_this.org);
                });
            });

            var seMove = false;
            el.on('mousedown', '.se', function(e) {
                _this.ox = e.pageX;
                _this.oy = e.pageY;
                _this.ow = el.width();
                _this.oh = el.height();

                seMove = true;
                console.log("se???");

                $(_this.options.stage).on('mousemove', _.throttle(function(e) {
                    if (seMove) {
                        var x = e.pageX - _this.ox;
                        var y = e.pageY - _this.oy;
                        var master = {
                            height: (_this.oh + y) < _this.minHeight ? _this.minHeight : (_this.oh + y) > _this.maxHeight ? _this.maxHeight : (_this.oh + y),
                            width: (_this.ow + x) < _this.minWidth ? _this.minWidth : (_this.ow + x) > _this.maxWidth ? _this.maxWidth : (_this.ow + x)
                        };
                        el.css(master);
                        _this.org.css(master);
                        _this.scrollArtboard(master, el);
                        _this.hasBeyond(el,master);
                    }

                    _.isFunction(_this.options.refresh) && _this.options.refresh();
                }, 50)).on('mouseup', function() {
                    seMove = false;
                    $(this).off('mousemove');
                    $(this).off('mouseup');
                    _.isFunction(_this.options.dragStop) && _this.options.dragStop(_this.org);
                });
            });
        }
    }

    return resize;
});

這里的tpl只是一個簡單的遮罩層

<div class="symbol_control">
    <div class="symbol_control-shape nw"></div>
    <div class="symbol_control-shape ne"></div>
    <div class="symbol_control-shape sw"></div>
    <div class="symbol_control-shape se"></div>
</div>

overlays沖突

在運行項目時,發現如果instance時,傳入了ConnectionOverlays,會與之后連線上的overlays沖突,因此,這里可以去除默認配置中的overlays,給全局加上importDefault方法,解決這一問題.

demo,分組及各種事件監聽

小結

這次的項目我個人還是覺得蠻有意思的,可以學習新的算法,了解新的數據結構,包括設計模式,也代入了其中,進行代碼的整合,所用到的中間件模式和發布訂閱者模式都讓我對於js有了一個新的理解.雖然已經用require來管理模塊,但結構仍然存在高度耦合的情況,應該還是被限制住了.
作為離職前的最后一次的項目來說,其實我感覺我的代碼能力仍然與年初沒有什么太大的改變,也許是時候脫離安逸的環境,重新開始了.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM