Angular.js指令格式化程序只调用一次 - 或者如何告诉ngModel表现得像watchCollection

时间:2015-03-25 14:05:10

标签: javascript angularjs angularjs-directive

我试图构建一个可以解析和格式化二维数组作为逗号和行分隔文本的指令。 (一行中的所有项目都以逗号分隔,行分别为'\ n')

解析部分正在运行。 格式化部分只能工作一次。

我在formatter中有一个debug console.log console.log("formatter called. value:", value); 我看到这只打印了一次。

app.directive('mycsvparser', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attrs, ngModelCtrl) {

            // View --> Model 
            ngModelCtrl.$parsers.push(function(viewValue) {
                if (viewValue) {
                    // parse input text:
                    var resultArray = [];

                    // first split into lines:
                    var arrayLines = viewValue.split("\n");

                    // parse every line
                    for (var line_index in arrayLines) {
                        .....
                        resultArray.push(lineArray);
                    }

                    return resultArray;
                } else {
                    // console.log("invalid");
                    return ngModelCtrl.$modelValue;
                }
            });

            // Model --> View
            ngModelCtrl.$formatters.push( function formatter(value) {
                console.log("formatter called. value:", value);
                if (value) {
                    var resultString = "";
                    var arrayLines = value;

                    for (var line_index in arrayLines) {
                        var arrayItems = arrayLines[line_index];
                        resultString += arrayItems.join(", ");
                        resultString += "\n";
                    }

                    return resultString;
                }
            });

            /**/
            scope.$watchCollection(attrs.ngModel, function(newData) {
                console.log('mycsvparser newData', newData);
                ngModelCtrl.$render();
            });
            /**/

        }
    };
});

我认为问题在于我使用的数据是(2D)数组。 但我怎么能解决这个问题? 我找到scope.$watchCollection(),我可以验证每次更新ngModel时都会触发。 但我怎样才能触发“渲染”过程/手动更新视图?

如果iam以错误的方式请求给我一个暗示要寻找的东西!

我制作了Plunker并将脚本示例添加为片段:
打开控制台
然后更改两个textareas中的一个中的数字 第一个是指令
第二个是在MainController中使用bind和manual手表的textarea 在右侧,您可以看到原始数据(采用JSON格式)

如果您更改csv区域中的数字,所有内容都会获得更新 如果更改较低文本区域中的数字,则csv区域不会更新 在控制台中,您可以看到$watchCollection触发。

/**************************************************************************************************/
/**   angularjs                                                                                  **/



var app = angular.module('myTestApp', []);

// quick JSON formating
app.filter('prettyprint', function() {
    // sames as json filter...but with 4 spaces...
    return function(input) {
        return JSON.stringify(input, null, 4);
    };
});

/** mycsvparser
  * this directive can parse and format a 2d array
  * this directive only updates the model if a valid value is entered!!
  **/
app.directive('mycsvparser', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attrs, ngModelCtrl) {
            // console.log("directive date");
             console.log("scope", scope);
            // console.log("element", element);
             console.log("attrs", attrs);
             console.log("ngModelngModelCtrl", ngModelCtrl);
            
            // View --> Model 
            ngModelCtrl.$parsers.push(function(viewValue) {
                if (viewValue) {
                    // console.log("valid");
                    // console.log("ngModelCtrl.$modelValue", ngModelCtrl.$modelValue);
                    // console.log("viewValue", viewValue);
                    
                    // parse input text:
                    
                    var resultArray = [];
                    
                    // first split into lines:
                    var arrayLines = viewValue.split("\n");
                    
                    // parse every line
                    for (var line_index in arrayLines) {
                        var line = arrayLines[line_index];
                        // split line into items
                        var arrayItems = line.split(",");
                        
                        var lineArray = [];
                        for (var item_index in arrayItems) {
                            var item = arrayItems[item_index];
                            // trim string to bare content
                            item = item.trim();
                            // here you can check for a special item type.. 
                            // convert to number
                            var itemAsNumber = Number(item);
                            // if (!isNaN(itemAsNumber)) {
                                // item = "";
                            // }
                            // add item to lineArray
                            lineArray.push(itemAsNumber);
                        }
                        // add content of line (in Array form) to resultArray
                        resultArray.push(lineArray);
                    }
                    
                    console.log("resultArray", resultArray);
                    ngModelCtrl.$render();
                    return resultArray;
                } else {
                    // console.log("invalid");
                    return ngModelCtrl.$modelValue;
                }
            });
            
            // Model --> View
            ngModelCtrl.$formatters.push( function formatter(value) {
                console.log("formatter called. value:", value);
                if (value) {
                    var resultString = "";
                    var arrayLines = value;
                    
                    for (var line_index in arrayLines) {
                        var arrayItems = arrayLines[line_index];
                        resultString += arrayItems.join(", ");
                        resultString += "\n";
                    }
                    
                    return resultString;
                }
            });
            
            /**/
            scope.$watchCollection(attrs.ngModel, function(newData) {
                console.log('mycsvparser newData', newData);
                ngModelCtrl.$render();
            });
            /**/
            
        }
    };
});
/***/


//http://stackoverflow.com/questions/15310935/angularjs-extend-recursive
// only needed for AngularJS < v1.4
// https://docs.angularjs.org/api/ng/function/angular.merge
function extendDeep(dst) {
    angular.forEach(arguments, function(obj) {
        if (obj !== dst) {
            angular.forEach(obj, function(value, key) {
                if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
                    extendDeep(dst[key], value);
                } else {
                    dst[key] = value;
                }     
            });   
        }
    });
    return dst;
}

// special variant that overwrites arrays.
function extendDeepArrayOverwrite(dst) {
    angular.forEach(arguments, function(obj) {
        if (obj !== dst) {
            angular.forEach(obj, function(value, key) {
                /*
                if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
                  extendDeep(dst[key], value);
                } else if (dst[key] && dst[key].constructor && dst[key].constructor === Array) {
                  dst[key].concat(value);
                } else if(!angular.isFunction(dst[key])) {
                  dst[key] = value;
                }
                */
                if (dst[key] && angular.isObject(dst[key])) {
                  extendDeep(dst[key], value);
                } else if (dst[key] && angular.isArray(dst[key])) {
                  // we want to overwrite the old array content.
                  // reset array
                  dst[key].length = 0;
                  // combine new content in old empty array
                  dst[key].concat(value);
                } else if(!angular.isFunction(dst[key])) {
                  dst[key] = value;
                }        
            });   
        }
    });
    return dst;
}



// MAIN Controller
app.controller('MainController', ['$scope', '$filter', function($scope, $filter) {

    //////////////////////////////////////////////////
    // internal data structure 

    $scope.systemdata = {
        name : "sun",
        lastsaved : new Date().toISOString(),
        patch : [
            [1],
            [4, 20,],
        ],
    };
    
    $scope.systemdata_text = '{\n\
    "name": "sun",\n\
    "lastsaved": "1",\n\
    "patch": [\n\
        [\n\
            1\n\
        ],\n\
        [\n\
            4,\n\
            20\n\
        ]\n\
    ]\n}';
    
    //////////////////////////////////////////
    // functions
    
        $scope.saveConfig = function() {
            console.log();
        }
    
    // watch example/info http://stackoverflow.com/a/15113029/574981
	$scope.$watch('systemdata_text', function() {
	    if ($scope.systemdata_text.length >0) {
    		// convert to JS-Object
            var tempData = JSON.parse($scope.systemdata_text);
            // normally you should now verify that the resulting object
            // is of the right structure.
            // we skip this and just believe in good will ;-)
            
            // merge data
            // angular.merge does not work with the arrays as we need it.
            //angular.merge($scope.systemdata, tempData);
            extendDeepArrayOverwrite($scope.systemdata, tempData);
	    }
	});
	
	$scope.$watchCollection('systemdata', function() {
    	$scope.systemdata_text = JSON.stringify($scope.systemdata, null, 4);
	});
	
    
}]);










/**************************************************************************************************/
/**   helper                                                                                     **/

function handleToggle(event) {
    this.parentNode.children[1].classList.toggle('hide');
}

function initSystem(){
    console.groupCollapsed("system init:");
    
    // toggle script
    console.log("add click event to 'div.toggle':");
    var toggleContainers = document.querySelectorAll("div.toggle .caption")
    for (var i = 0; i < toggleContainers.length; ++i) {
        var item = toggleContainers[i];  // Calling datasets.item(i) isn't necessary in JavaScript
        item.addEventListener('click', handleToggle, false);
    }
    console.log("\t found " + toggleContainers.length + " toggleContainers");
    
    console.log("finished.");
    console.groupEnd();
}

/* * pure JS - newer browsers...* */
document.addEventListener('DOMContentLoaded', initSystem, false);
/* Styles go here */

.raw {
    float:right; 
    padding:				0.5em;
    background-color:       rgb(100, 100, 100);
    background-color:       rgba(0, 0, 0, 0.1);
    border:					none;
	border-radius:			0.5em;
	box-shadow:				0px 0px 10px rgba(0,0,0,0.5) inset;
	color:					inherit;
}



.toggle .caption {
	cursor:					pointer;
}

div .hide{
	height:					0;
	margin:					0;
	padding:				0;
	overflow:				hidden;
}



input, textarea, select {
	padding:				0.2em;
	background-color:		rgba(0, 0, 0, 0.1);
	border:					none;
	border-radius:			0.5em;
	box-shadow:				0px 0px 10px rgba(0,0,0,0.5) inset;
	color:					inherit;
	font-size:				inherit;
	line-height:			inherit;
	font-family:			inherit;
	text-align:				right;
	text-shadow:			inherit;
	white-space:			pre;
}

textarea {
	display:				block;
	margin:					1em;
	padding:				0.5em;
	background-color:		rgba(0, 0, 0, 0.2);
	border-radius:			1em;
	box-shadow:				0px 0px 10px rgba(0,0,0,0.5) inset;
	text-align:				left;
	white-space:			pre;
}
<!DOCTYPE html>
<html ng-app="myTestApp">

  <head>
    <script src="https://code.angularjs.org/1.4.0-beta.6/angular.js" data-semver="1.4.0-beta.6" data-require="angular.js@*"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <div ng-controller="MainController" class="app">
        <div style="" class="toggle raw">
            <h4 class="caption">raw:</h4>
            <pre class="">{{systemdata | prettyprint}}</pre>
        </div>
        <div>
            Hello {{systemdata.name}}<br><br>
            
            <label>CSV List:</label>
            <textarea
				id="patch_text" 
				name="patch_text" 
				class="output_text" 
				cols="25" 
				rows="4" 
				wrap="off"
				type="text"
				ng-model="systemdata.patch"
				ngTrim="false"
				mycsvparser
			></textarea>
			<label>raw json</label>
			<textarea
				id="output_text" 
				name="output_text" 
				class="output_text" 
				cols="50" 
				rows="15" 
				wrap="off"
				type="text"
				ng-model="systemdata_text"
				ngTrim="false"
			></textarea>	
        </div>
    </div>
  </body>

</html>


标题:Angular.js指令格式化程序只调用一次 -
或者如何告诉ngModel表现得像watchCollection
修改
我又遇到了原来的问题...... 但是有一个不同的例子..
我想我已经理解了user3906922的答案 在这个plunker你可以尝试一下 -
我有一个textarea显示$ scope变量的json($ scope.showdata) 这是由控制器中的两个监视功能手动绑定的

$scope.$watch('testdata_text', function() {
    if ($scope.testdata_text.length >0) {
        try {
            var tempData = JSON.parse($scope.testdata_text);
            extendDeepArrayOverwrite($scope.showdata, tempData);
        }
        catch(error) {
            console.error("testdata_text no valid JSON:  ", error.message)
        }
    }
});

$scope.$watchCollection('showdata', function() {
    $scope.testdata_text = JSON.stringify($scope.showdata, null, 4);
});

然后我试图将此行为转换为指令 - 这是第二个textarea:

app.directive('rawjson', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attrs, ngModelCtrl) {
            // View --> Model 
            ngModelCtrl.$parsers.push(function(viewValue) {
                if (viewValue) {
                    try {
                        var tempData = JSON.parse(viewValue);
                        extendDeepArrayOverwrite(ngModelCtrl.$modelValue, tempData);
                        ngModelCtrl.$setValidity("isJson", true);
                    }
                    catch(error) {
                        ngModelCtrl.$setValidity("isJson", false);
                    }
                    return ngModelCtrl.$modelValue;
                } else {
                    return ngModelCtrl.$modelValue;
                }
            });

            // Model --> View
            ngModelCtrl.$formatters.push( function formatter(value) {
                if (value) {
                    var resultString = JSON.stringify(value, null, 4);
                    return resultString;
                }
            });
        }
    };
});

我已经在链接函数(它在plunker中)中使用了watchCollection进行了测试,并且每次更改时都会触发。

那么可以告诉ngModel更新/或者它使用watchCollection吗?

或者这是错误的思考方式吗?

1 个答案:

答案 0 :(得分:0)

因为您的extendedDeep函数替换了数组的内容,因此angular不知道模型中有变化,并且没有调用格式化程序。

我不确定你对extendDeepArrayOverwrite函数的意图是什么,但你可以在isObject和isArray之间切换if-else的顺序(因为数组也是一个对象) ,并将concat函数结果赋回dst[key](没有它对语句没有意义):

    angular.forEach(obj, function(value, key) {
            /*
            if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
              extendDeep(dst[key], value);
            } else if (dst[key] && dst[key].constructor && dst[key].constructor === Array) {
              dst[key].concat(value);
            } else if(!angular.isFunction(dst[key])) {
              dst[key] = value;
            }
            */
            if (dst[key] && angular.isArray(dst[key])) {
              // we want to overwrite the old array content.
              // reset array
              dst[key].length = 0;
              // combine new content in old empty array
              dst[key] = dst[key].concat(value);
            }
            else if (dst[key] && angular.isObject(dst[key])) {
              extendDeep(dst[key], value);
            } else if(!angular.isFunction(dst[key])) {
              dst[key] = value;
            }         
        });

选中此plunker