【发布时间】:2015-05-29 06:13:14
【问题描述】:
我正在尝试构建一个指令,该指令可以将二维数组解析并格式化为逗号和行分隔的文本。 (一行中的所有项目以逗号分隔,行以'\n'分隔)
解析部分正在工作。 格式化部分只工作一次。
我在格式化程序中有一个调试 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 时都会触发。
但是我怎样才能触发“渲染”过程/手动更新视图?
如果我走错了路,请给我提示要寻找什么!
我创建了一个Plunker 并将脚本示例添加为 sn-p:
打开控制台
然后更改两个文本区域之一中的数字。
第一个是指令
第二个只是一个文本区域,在 MainController
中有绑定和手动监视
在右侧您可以看到原始数据(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 你可以试试 -
我有一个显示 $scope 变量 ($scope.showdata) 的 json 的 textarea
这是由控制器中的两个手表函数手动绑定的
$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);
});
然后我尝试将此行为转换为指令 - 即第二个文本区域:
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?
或者这是对这种情况的错误思考方式?
【问题讨论】:
标签: javascript angularjs angularjs-directive