목차
머리말
연결시 다중 라벨 처리에 관해서
Groups的使用
多层or一层 数据结构解析
먼저 그룹의 요약된 사용법을 게시하세요.
redo和undo
放大缩小
요약
웹 프론트엔드 JS 튜토리얼 jsPlumb 순서도 경험 요약

jsPlumb 순서도 경험 요약

May 17, 2018 pm 02:03 PM
javascript 흐름도

在使用jsPlumb过程中,所遇到的问题,以及解决方案,文中引用了《数据结构与算法JavaScript描述》的相关图片和一部分代
码.截图是有点多,有时比较懒,没有太多的时间去详细的编辑.
로그인 후 복사

머리말

먼저 UML 클래스 다이어그램입니다
jsPlumb 순서도 경험 요약

그 다음 흐름도
jsPlumb 순서도 경험 요약

jsPlumb의 관련 기능을 사용하면 첫 번째 버전의 프로토타입을 간헐적으로 볼 수 있습니다. 중간에 다른 작업이 산재해 있었지만 그래도 기본 기능은 완성했습니다.

사실 작업을 마친 후에는 jsPlumb의 기능 중 일부만 사용되었고 이해가 더 많이 됐다는 것을 알게 되었습니다. 내부 데이터 구조의 구현은 데이터가 동기적으로 업데이트되었다고 할 수 있지만 여전히 데이터 중심과는 일정한 거리가 있습니다.

여기서 프로젝트에서 발생한 문제와 솔루션을 요약하고 기록하겠습니다. . 더 좋은 방법이 있다면 지적해주세요

연결시 다중 라벨 처리에 관해서

위 사진처럼 처음에는 연결시 오버레이를 2개 구성할까 고민했는데,

    var j = jsPlumb.getInstance();

    j.connect({
        source:source,
        target:target,
        overlays:[
            "Arrow",
            ["label",{label:"foo1",location:0.2jsPlumb 순서도 경험 요약,id:"m1"}],
            ["label",{label:"foo2",location:0.jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약,id:"m2"}]
        ]
    })
로그인 후 복사

의 물론 여기에 함정이 있습니다. ID가 반복되면 사용됩니다. 마지막 항목은 jsPlumb 내부에 캐시된 데이터를 포함하여 중복되지 않으며 마지막 항목만 남게 됩니다.

나중에 구성 항목이 importDefaults 함수를 통해서도 동적으로 수정할 수 있습니다.importDefaults函数来动态修改配置项.

    j.importDefaults({
        ConnectionOverlays: [
            ["Arrow", { location: 1, id: "arrow", length: jsPlumb 순서도 경험 요약, foldback: 0, width: jsPlumb 순서도 경험 요약 }],
            ["Label", { label: "n", id: "label-n", location: 0.2jsPlumb 순서도 경험 요약, cssClass: "jspl-label" }],
            ["Label", { label: "1", id: "label-1", location: 0.jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약, 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 : jsPlumb 순서도 경험 요약00);
        };
    };
로그인 후 복사

这是一个简单的实现方式,主要就是为了减少dom中事件移动时重复调用的事件,同时达到执行事件的目的(只允许一个函数在x毫秒内执行一次);
当然,也可以使用underscore.js中自带的_.throttle()函数,同样可以达到目的.

这里的html结构就使用了嵌套的层级,将父级和子级使用这种层级保存到内部的数据源里

多层or一层 数据结构解析

jsPlumb 순서도 경험 요약

类似这种实际存在嵌套关系的数据体,有两种方式可以进行管理,

  • 多层级嵌套:类似

        [
            {
                id:"1",
                child:{
                    id:"2",
                    child:{
                        id:"jsPlumb 순서도 경험 요약",
                        child:{}
                    }
                }
            }
        ]
    로그인 후 복사

    用来进行管理的话,优点是直观,能根据层级就知道整体结构大概是多少,转换成xml或者html也很方便.
    但缺点就是进行查找和修改,并不是那么方便.

  • 一层展示所有节点:类似

        [
            {
                id:"1",
                child:[{
                    id:"2"
                }]
            },
            {
                id:"2",
                parentId:"1",
                child:[{
                    id:"jsPlumb 순서도 경험 요약"
                }]
            },
            {
                id:"jsPlumb 순서도 경험 요약",
                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

    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] + &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약; &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;
            }
          }
    
          console.log("表现形式为:" + str);
        }
    
        console.log(this.adj);
      }
    }
    로그인 후 복사

    단지 함수 실행 후 연결에서만 두 개의 레이블이 표시될 수 있으며, 이전 레이블은 함께 변경할 수 없습니다 jsPlumb 순서도 경험 요약. 그래서 편의상 초기화시 바로 수정합니다.

  • Groups

Group을 사용할 경우 위와 같이 무한 중첩 수준에서는 Groups를 사용할 수 없습니다.

문서에 따르면 요소를 그룹으로 표시하면 그룹의 요소가 연결과 마찬가지로 그룹의 움직임에 따라 이동합니다. 하지만 문제는 요소가 그룹이 되면 됩니다. , 다른 그룹 요소를 수용할 수 없습니다. 즉, 그룹스 방식은 레이어가 하나뿐이므로 당연히 요구 사항을 충족할 수 없습니다.

먼저 그룹의 요약된 사용법을 게시하세요.

  /**
   * 深度优先搜索算法
   * 这里不需要顶点,也就是邻接表的初始点
   */
    this.dfs = (v) {
        this.marked[v] = true;
        for (var w of this.adj[v]) {
            if (!this.marked[w]) {
                this.dfs(w);
            }
        }
    }
로그인 후 복사

새로운 방식이 나중에 채택됩니다. .노드가 이동하면 연결이 동적으로 새로 고쳐집니다

  /**
   * 广度优先搜索算法
   * @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);
        }
      }
    }
  }
로그인 후 복사
로그인 후 복사
jsPlumb 순서도 경험 요약페이지를 차단하지 않으려면 throttling throttle()

    /**
     * 深度搜索,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;
        };
    }
로그인 후 복사
로그인 후 복사

함수를 사용해야 합니다. 이는 주로 다음과 같은 간단한 구현 방법입니다. DOM 내의 이벤트 이동 시 반복적으로 호출되는 이벤트 수를 줄이고, 동시에 이벤트 실행 목적을 달성합니다. (허용됨 1번만 함수가 실행됩니다. 중첩된 레벨은 이 레벨을 사용하여 내부 데이터 소스에 저장됩니다.
jsPlumb 순서도 경험 요약다층 또는 단층 데이터 구조 분석

jsPlumb 순서도 경험 요약

실제로 중첩 관계가 있는 이와 같은 데이터 본문을 관리하는 방법에는 두 가지가 있습니다.

  • 다단계 중첩:

    jsPlumb.connect({
        source:"foo",
        target:"bar",
        parameters:{
            "p1":jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약,
            "p2":new Date(),
            "pjsPlumb 순서도 경험 요약":function() { console.log("i am pjsPlumb 순서도 경험 요약"); }
        }
    });
    로그인 후 복사
    로그인 후 복사

    와 유사합니다. 관리가 직관적이라는 장점이 있고, 레벨에 따라 대략적인 전체 구조를 알 수 있고, xml이나 html로 변환하는 것도 매우 편리합니다. 단, 검색과 수정이 그리 편리하지 않다는 것이 단점입니다.


  • 모든 노드를 하나의 레이어에 표시: jsPlumb 순서도 경험 요약
        var defaults = {
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;name&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;: "mutation",
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;afterAddServe&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;:$.noop,
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;afterUndo&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;:$.noop,
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;afterRedo&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;:$.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;
    로그인 후 복사
    로그인 후 복사

    와 유사합니다. 이 구조의 장점은 모든 것이 하나의 레벨에 있다는 점이며, 데이터를 구문 분석하려는 경우 매우 편리합니다. 다중 레벨 구조로 전환하려면 재귀를 사용하여 새 구조를 생성하기만 하면 됩니다.

    rrreee

    단일 레벨 배열을 초기화 함수 init를 통해 다중 레벨 배열로 변환하세요

    jsPlumb 순서도 경험 요약

    🎜HTML 구조로 변환하려면 함수를 약간만 변경하면 됩니다. 🎜🎜과정에 막다른 골목이 있는지(경로에 막다른 지점이 있는지) 확인하세요. 그래프 끝에 도달할 수 없습니다)🎜🎜이것은 전적으로 알고리즘에 달려 있습니다. 그것을 달성하려면 우선 그림에 대한 이해가 핵심입니다🎜🎜🎜🎜타자하기가 너무 귀찮아서 직접적으로 표현하겠습니다. 그림 기본 그림은 대략 이렇고 구체적인 표현은 🎜🎜🎜🎜보실 수 있습니다. 기본 그래프 표현은 인접 목록으로 표현될 수 있습니다. 🎜🎜그리고 구현을 위해 다음 코드를 볼 수 있습니다. 🎜만드는 것만으로는 충분하지 않으므로 기본 검색 방법을 살펴보겠습니다. 🎜깊이 우선 검색 및 너비 우선 검색 🎜🎜깊이 우선 검색🎜🎜초기 노드에서 방문을 시작하고 방문한 것으로 표시한 다음 재귀적으로 방문합니다. 초기 노드의 인접 목록에서 다른 방문하지 않은 노드를 검색한 다음 모든 노드를 방문할 수 있습니다🎜🎜🎜rrreee🎜그림과 위 코드에 따르면 딥 서치는 실제로 다른 많은 확장을 할 수 있음을 알 수 있습니다🎜🎜Breadth -첫 번째 검색🎜🎜🎜🎜
      /**
       * 广度优先搜索算法
       * @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描述》这本书,有兴趣的可以去实现下查找最短路径拓扑排序;

    两点之间所有路径

    这算是找到的比较能理解的方式来计算
    jsPlumb 순서도 경험 요약

    以上图为例,这是一个简单的流程图,可以很简单的看出,右边的流程实际上是未完成的,因为无法到达终点,所以是一个非法点,而通过上面的深度搜索,可以看出,只要对深度优先搜索算法进行一定的修改,那么就可以找到从开始到结束的所有的路径,再通过对比,就可以知道哪些点无法到达终点,从而确定非法点.
    上代码:

        /**
         * 深度搜索,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()函数,将邻接表中的双向记录改为单向记录,可以有效避免下图的错误计算:
    jsPlumb 순서도 경험 요약

    只计算起点到终点的所有连线有时并不客观,如果出现
    1jsPlumb 순서도 경험 요약

    这种情况的话,实际上深度遍历并不能计算出最右边的节点是合法的,那么就需要重新修改起点和终点,来推导是否能够到达终点.从而判定该点是否合法.至于其他的,只是多了个返回值,存储了一下计算出来的所有路径.
    而在dfs函数中,当满足能够从起点走到终点的,则记录下当前的path中的值,保存到lines中去,而每一次对于path的推入或者推出,保证了只有满足条件的点,才能被返回;
    this.marked[v] = false,则确保了,在每一次重新计算路径时,都会验证每个点是否存在不同的相对于终点能够到达的路径是否存在.
    当然,一定会有更加简单的方法,我这里只是稍微修改了下基础的代码!

    redo和undo

    这是我觉得最简单却耗时最久的功能,思路都知道:创建一个队列,记录每一次创建一个流程节点,删除一个流程节点,建立一个新的关联关系,删除一个新的关联关系等,都需要记录下来,再通过统一的接口来访问队列,执行操作.
    但在具体实现上,jsPlumb的remove确实需要注意一下:
    首先,如果需要删除连线,那么使用jsPlumb提供的detach()方法,就可以删除连线,注意,传入的数据应该是connection对象.
    当然,也可以使用remove()方法,参数为选择器或者element对象都可以,这个方法删除的是一个节点,包括节点上所有的线.
    而jsPlumb中会内部缓存所有的数据,用于刷新,和重连.
    那么当我移除一个多层级且内部有连线的情况时,如果只删除最外层的元素,那么内部的连线实际上并没有清除,所以当redo或者移动时,会出现连线的端点有一端会跑到坐标原点,也就是p上(0,0)的地方去.所以清除时,需要注意,要把内部的所有节点依次清除,才不会发生一些莫名其妙的bug.

    而在删除和连接连线上,我使用了jsPlumb提供的事件bind(&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;connection&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;)bind("connectionDetached"),用于判断一条连线被连接或者删除.而在记录这里的redo和undo事件时,尤其要注意,需要首先确定删除和连接时的连线的类型,否则会产生额外的队列事件.
    因此,在使用连接事件时,就可以使用

    jsPlumb.connect({
        source:"foo",
        target:"bar",
        parameters:{
            "p1":jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약,
            "p2":new Date(),
            "pjsPlumb 순서도 경험 요약":function() { console.log("i am pjsPlumb 순서도 경험 요약"); }
        }
    });
    로그인 후 복사
    로그인 후 복사

    来进行类型的传参,这样事件触发时就可以分类处理.
    也可以使用connection.setData()事件,参数可以指定任意的值,通过connection.getData()方法,就可以拿到相应的数据了.
    而redo和undo本身确实没有什么东西

        var defaults = {
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;name&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;: "mutation",
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;afterAddServe&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;:$.noop,
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;afterUndo&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;:$.noop,
            &#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;afterRedo&#jsPlumb 순서도 경험 요약jsPlumb 순서도 경험 요약;:$.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,让元素在节流中动态的变化大小,就可以了,
    1jsPlumb 순서도 경험 요약

    只需要用一个节点,在点击元素时,根据元素的大小来确定该辅助节点四个点的位置,就可以了,只要监听了这四个点的位置,再同步给该定位元素,就能实现这一效果,方法就不贴了,没有太多东西

    요약

    저는 개인적으로 이 프로젝트가 매우 흥미롭다고 생각합니다. 새로운 알고리즘을 배우고, 디자인 패턴을 포함한 새로운 데이터 구조를 이해하고, 이를 통합하여 사용된 코드, 미들웨어 패턴 및 릴리스를 통합할 수 있습니다. js에 대한 이해. 모듈을 관리하기 위해 require를 사용했지만 구조는 여전히 결합도가 높으며 여전히 제한적이어야 합니다.
    퇴사 전 마지막 프로젝트로, 실제로 코딩 능력은 예전과 크게 다르지 않은 것 같습니다. 이제 편안한 환경을 떠나 다시 시작해야 할 때인 것 같습니다.

    위 내용은 jsPlumb 순서도 경험 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

WPS 문서에서 흐름도를 그리는 방법 WPS 문서에서 흐름도를 그리는 방법 Mar 20, 2024 pm 10:20 PM

우리는 업무 회의에서 순서도를 자주 사용합니다. 순서도를 사용하면 설명이 더욱 직관적이고 편리해집니다. 사실 순서도를 만드는 것은 어렵지 않고 wps 문서만 만들면 됩니다. 아래 편집기는 WPS 문서에 흐름도를 그리는 방법에 대한 구체적인 단계를 설명합니다. 1. wps 소프트웨어를 열고 메뉴 표시줄에서 삽입 버튼을 선택합니다. 2. 흐름도의 둥근 직사각형 프레임을 선택하고 페이지에 그립니다. 내부에는 다양한 흐름도와 화살표 패턴이 있습니다. 그 중 하나를 선택하고 문서에서 끌어낸 다음 마우스 오른쪽 버튼을 클릭하여 텍스트를 입력하면 됩니다. 3. 이때 그려진 직사각형 프레임이 단색임을 확인하고 그리기 도구에서 재설정하여 투명한 색상으로 채워줍니다. 4. 이 직사각형 상자를 클릭하고 마우스를 사용해 보겠습니다.

WebSocket과 JavaScript를 사용하여 온라인 음성 인식 시스템을 구현하는 방법 WebSocket과 JavaScript를 사용하여 온라인 음성 인식 시스템을 구현하는 방법 Dec 17, 2023 pm 02:54 PM

WebSocket 및 JavaScript를 사용하여 온라인 음성 인식 시스템을 구현하는 방법 소개: 지속적인 기술 개발로 음성 인식 기술은 인공 지능 분야의 중요한 부분이 되었습니다. WebSocket과 JavaScript를 기반으로 한 온라인 음성 인식 시스템은 낮은 대기 시간, 실시간, 크로스 플랫폼이라는 특징을 갖고 있으며 널리 사용되는 솔루션이 되었습니다. 이 기사에서는 WebSocket과 JavaScript를 사용하여 온라인 음성 인식 시스템을 구현하는 방법을 소개합니다.

WebSocket 및 JavaScript: 실시간 모니터링 시스템 구현을 위한 핵심 기술 WebSocket 및 JavaScript: 실시간 모니터링 시스템 구현을 위한 핵심 기술 Dec 17, 2023 pm 05:30 PM

WebSocket과 JavaScript: 실시간 모니터링 시스템 구현을 위한 핵심 기술 서론: 인터넷 기술의 급속한 발전과 함께 실시간 모니터링 시스템이 다양한 분야에서 널리 활용되고 있다. 실시간 모니터링을 구현하는 핵심 기술 중 하나는 WebSocket과 JavaScript의 조합입니다. 이 기사에서는 실시간 모니터링 시스템에서 WebSocket 및 JavaScript의 적용을 소개하고 코드 예제를 제공하며 구현 원칙을 자세히 설명합니다. 1. 웹소켓 기술

JavaScript 및 WebSocket을 사용하여 실시간 온라인 주문 시스템을 구현하는 방법 JavaScript 및 WebSocket을 사용하여 실시간 온라인 주문 시스템을 구현하는 방법 Dec 17, 2023 pm 12:09 PM

JavaScript 및 WebSocket을 사용하여 실시간 온라인 주문 시스템을 구현하는 방법 소개: 인터넷의 대중화와 기술의 발전으로 점점 더 많은 레스토랑에서 온라인 주문 서비스를 제공하기 시작했습니다. 실시간 온라인 주문 시스템을 구현하기 위해 JavaScript 및 WebSocket 기술을 사용할 수 있습니다. WebSocket은 TCP 프로토콜을 기반으로 하는 전이중 통신 프로토콜로 클라이언트와 서버 간의 실시간 양방향 통신을 실현할 수 있습니다. 실시간 온라인 주문 시스템에서는 사용자가 요리를 선택하고 주문을 하면

WebSocket과 JavaScript를 사용하여 온라인 예약 시스템을 구현하는 방법 WebSocket과 JavaScript를 사용하여 온라인 예약 시스템을 구현하는 방법 Dec 17, 2023 am 09:39 AM

WebSocket과 JavaScript를 사용하여 온라인 예약 시스템을 구현하는 방법 오늘날의 디지털 시대에는 점점 더 많은 기업과 서비스에서 온라인 예약 기능을 제공해야 합니다. 효율적인 실시간 온라인 예약 시스템을 구현하는 것이 중요합니다. 이 기사에서는 WebSocket과 JavaScript를 사용하여 온라인 예약 시스템을 구현하는 방법을 소개하고 구체적인 코드 예제를 제공합니다. 1. WebSocket이란 무엇입니까? WebSocket은 단일 TCP 연결의 전이중 방식입니다.

JavaScript와 WebSocket: 효율적인 실시간 일기예보 시스템 구축 JavaScript와 WebSocket: 효율적인 실시간 일기예보 시스템 구축 Dec 17, 2023 pm 05:13 PM

JavaScript 및 WebSocket: 효율적인 실시간 일기 예보 시스템 구축 소개: 오늘날 일기 예보의 정확성은 일상 생활과 의사 결정에 매우 중요합니다. 기술이 발전함에 따라 우리는 날씨 데이터를 실시간으로 획득함으로써 보다 정확하고 신뢰할 수 있는 일기예보를 제공할 수 있습니다. 이 기사에서는 JavaScript 및 WebSocket 기술을 사용하여 효율적인 실시간 일기 예보 시스템을 구축하는 방법을 알아봅니다. 이 문서에서는 특정 코드 예제를 통해 구현 프로세스를 보여줍니다. 우리

간단한 JavaScript 튜토리얼: HTTP 상태 코드를 얻는 방법 간단한 JavaScript 튜토리얼: HTTP 상태 코드를 얻는 방법 Jan 05, 2024 pm 06:08 PM

JavaScript 튜토리얼: HTTP 상태 코드를 얻는 방법, 특정 코드 예제가 필요합니다. 서문: 웹 개발에서는 서버와의 데이터 상호 작용이 종종 포함됩니다. 서버와 통신할 때 반환된 HTTP 상태 코드를 가져와서 작업의 성공 여부를 확인하고 다양한 상태 코드에 따라 해당 처리를 수행해야 하는 경우가 많습니다. 이 기사에서는 JavaScript를 사용하여 HTTP 상태 코드를 얻는 방법과 몇 가지 실용적인 코드 예제를 제공합니다. XMLHttpRequest 사용

자바스크립트에서 insertBefore를 사용하는 방법 자바스크립트에서 insertBefore를 사용하는 방법 Nov 24, 2023 am 11:56 AM

사용법: JavaScript에서 insertBefore() 메서드는 DOM 트리에 새 노드를 삽입하는 데 사용됩니다. 이 방법에는 삽입할 새 노드와 참조 노드(즉, 새 노드가 삽입될 노드)라는 두 가지 매개 변수가 필요합니다.

See all articles