2013-10-04 4 views
8

Ich baue ein neues SPA-Frontend, um das bestehende System eines veralteten Systems von veralteten und zu aktualisierenden Systemen zu ersetzen. Ich bin neu zu eckig und wollte sehen, ob die Gemeinschaft mir eine Perspektive geben könnte. Ich werde mein Problem angeben und dann meine Frage stellen.Pro/Con der Verwendung von Angular-Direktiven für komplexe Formularvalidierung/GUI-Manipulation

muss ich schließen, basierend auf Daten aus einer .js mehrere Serien von Checkboxen erzeugen, mit Daten wie folgt aus:

$scope.fieldMappings.investmentObjectiveMap = [ 
    {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"}, 
    {'id':"STABLE", 'name':"Moderate"}, 
    {'id':"BALANCED", 'name':"Moderate Growth"}, 
    // etc 
    {'id':"NONE", 'name':"None"} 
]; 

Die Kontrollkästchen werden erstellt ein ng-repeat verwenden, wie folgt aus:

<div ng-repeat="investmentObjective in fieldMappings.investmentObjectiveMap"> 
    ... 
    </div> 

Ich benötigte jedoch die Werte, die durch die Kontrollkästchen dargestellt wurden, um sie einem anderen Modell zuzuordnen (nicht nur in Zweiwege-Richtung zum Objekt fieldmappings). Um dies zu erreichen, habe ich eine Direktive erstellt, die ein Zielarray destarray akzeptiert, das schließlich dem Modell zugeordnet wird. Ich weiß auch, dass ich einige sehr spezifische GUI-Steuerelemente behandeln muss, wie zum Beispiel das Deaktivieren von "None", wenn etwas anderes überprüft wird, oder das Aktivieren von "None", wenn alles andere nicht aktiviert ist. Außerdem ist "Keine" in keiner Gruppe von Kontrollkästchen eine Option. Daher muss die Anweisung generisch genug sein, um eine Validierungsfunktion zu akzeptieren, die mit dem checked-Status der Checkbox-Gruppeneingaben basierend auf dem bereits Angeklickten, aber Intelligent umgehen kann genug, um nicht zu brechen, wenn es keine Option gibt, die "NONE" genannt wird. Ich fing an, das zu tun, indem ich einen ng-klick hinzufügte, der eine Funktion in der Steuerung anrief, aber indem ich Stapelüberlauf sah, las ich Leute, die sagen, dass es schlecht ist, DOM Manipulationscode in deinem Controller zu setzen - es sollte in Direktiven gehen. Also brauche ich eine andere Richtlinie?

Bisher: (html):

 <input my-checkbox-group 
       type="checkbox" 
       fieldobj="investmentObjective" 
       ng-click="validationfunc()" 
       validationfunc="clearOnNone()" 
       destarray="investor.investmentObjective" /> 

Richtlinie Code:

.directive("myCheckboxGroup", function() { 
    return { 
    restrict: "A", 
    scope: { 
     destarray:  "=", // the source of all the checkbox values 
     fieldobj:  "=", // the array the values came from 
     validationfunc: "&" // the function to be called for validation (optional) 
    }, 
    link: function (scope, elem, attrs) { 
     if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) { 
     elem[0].checked = true; 
     } 
     elem.bind('click', function() { 
     var index = scope.destarray.indexOf(scope.fieldobj.id); 
     if (elem[0].checked) { 
      if (index === -1) { 
      scope.destarray.push(scope.fieldobj.id); 
      } 
     } 
     else { 
      if (index !== -1) { 
      scope.destarray.splice(index, 1); 
      } 
     } 
     }); 
    } 
    }; 
}) 

Js Controller-Schnipsel:

.controller('SuitabilityCtrl', ['$scope', function ($scope) { 
    $scope.clearOnNone = function() { 
    // naughty jQuery DOM manipulation code that 
    // looks at checkboxes and checks/unchecks as needed 
    }; 

Der obige Code ausgeführt wird und funktioniert gut, außer dem naughty jquery code in clearOnNone(), weshalb ich diese Frage geschrieben habe.

Und hier ist meine Frage: nach all dem, denke ich mir - ich könnte schon getan werden, wenn ich nur all diese GUI-Logik und Validierung Junk mit jQuery in meinem Controller manuell behandelt. An welchem ​​Punkt wird es töricht, diese komplizierten Anweisungen zu schreiben, über die zukünftige Entwickler mehr rätseln müssen, als wenn ich gerade jQuery Code geschrieben hätte, den 99% von uns mit einem flüchtigen Blick verstehen würden? Wie zeichnen andere Entwickler die Grenze?

Ich sehe diesen Over-Stack-Überlauf. Zum Beispiel scheint this question wie es könnte mit einem Dutzend Zeilen von geradlinigen jQuery beantwortet werden, aber er hat sich entschieden, es den eckigen Weg zu tun, mit einer Direktive und einer partiellen ... es scheint wie viel Arbeit für ein einfaches Problem.

ich nicht diese Frage will die Regeln zu verletzen, so speziell, ich nehme ich wissen möchte: Wie SOLL ich den Code schreiben, ob „None“ (ausgewählt wurde überprüft, ob es als vorhanden eine Option in dieser Gruppe von Kontrollkästchen), und aktivieren/deaktivieren Sie dann die anderen Kontrollkästchen entsprechend? Eine komplexere Richtlinie? Ich kann nicht glauben, dass ich der einzige Entwickler bin, der Code implementieren muss, der komplexer ist als nötig, nur um einen rechthaberischen Rahmen zu erfüllen.Gibt es eine andere Util-Bibliothek, die ich verwenden muss?

+0

+1: Meine Güte, was für eine großartige Frage. Leider ist es zu breit für Stackoverflow. Dies wäre viel besser für Programmers.StackExchange.com geeignet. // PS Ich denke, viele AngularJS-Entwickler kämpfen mit ähnlichen Schwierigkeiten/Kompromissen. –

+0

Nur neugierig, ist Ihr Benutzername eine Hommage an den Hersteller von R.B.I. Baseball? http://en.m.wikipedia.org/wiki/R.B.I._Baseball –

+0

Hat Ihr Controller direkten Zugriff auf die Arrays, die das Formular unterstützen? In diesem Fall sollten Sie nur diese direkt manipulieren, anstatt JQuery zu verwenden, um das DOM zu manipulieren. Ich kann nicht sagen, ob es ein Problem für Sie sein wird, aber Funktionen, die an $ scope angehängt sind, können während einer $ digest-Schleife leicht mehrmals ausgeführt werden, und wenn Sie DOM-Sachen in ihnen machen, kann das sehr ernst sein Performance-Hit. – ivarni

Antwort

2

Ich postete dies auf Programmers.StackExchange.com nach Jims Vorschlag. In der Zwischenzeit habe ich mich auf eine Lösung für den Umgang mit all den schwierigen DOM-Manipulationen festgelegt.

Ich habe versucht, es in beiden Richtungen - das DOM-Ereignisses in der Steuerung der Handhabung und es über eine Richtlinie Handhabung:

(Via Controller) - Js Code:

$scope.clearOnNone = function(groupName, $event) { 
    var chkboxArr = $('input[name^=' + groupName + ']'), 
     nonNoneValChecked = false, 
     targetElem = null, 
     labelText = ""; 

    // get the target of the click event by looking at the <label> sibling's text 
    targetElem = event.target.nextElementSibling.textContent.trim(); 

    // if target was the None option, uncheck all others 
    if (targetElem === "None") { 
     chkboxArr.each(function() { 
     labelText = this.nextElementSibling.textContent.trim(); 

     if (labelText !== "None") { 
      this.checked = false; 
     } 
     }); 
    } 
    // if the target was anything BUT the None option, uncheck None 
    else { 
     chkboxArr.each(function() { 
     labelText = this.nextElementSibling.textContent.trim(); 

     if (labelText === "None") { 
      this.checked = false; 
     } 
     }); 
    } 
    }; 

(Via Controller) - hTML-Code:

 <div ng-repeat="investmentObjective in fieldMappings.secondaryInvestmentObjectiveMap"> 
     <input checkbox-group 
       type="checkbox" 
       name="secondaryInvestmentObjective" 
       ng-click="validationfunc('secondaryInvestmentObjective', $event)" 
       validationfunc="clearOnNone('secondaryInvestmentObjective', $event)" 
       fieldobj="investmentObjective" 
       destarray="suitabilityHolder.suitability.secondaryInvestmentObjective" /> 
     <label class="checkbox-label" 
       popover-title="{{investmentObjective.name}}" 
       popover="{{investmentObjective.help}}" 
       popover-trigger="mouseenter">{{investmentObjective.name}} 
     </label> 
     </div> 

(Via Controller) - Richtlinie Code:

.directive("checkboxGroup", function() { 
    return { 
    restrict: "A", 
    scope: { 
     destarray:  "=", // the source of all the checkbox values 
     fieldobj:  "=", // the array the values came from 
     validationfunc: "&" // the function to be called for validation (optional) 
    }, 
    link: function (scope, elem, attrs) { 
     if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) { 
     elem[0].checked = true; 
     } 
     elem.bind('click', function() { 
     var index = scope.destarray.indexOf(scope.fieldobj.id); 
     if (elem[0].checked) { 
      if (index === -1) { 
      scope.destarray.push(scope.fieldobj.id); 
      } 
     } 
     else { 
      if (index !== -1) { 
      scope.destarray.splice(index, 1); 
      } 
     } 
     }); 
    } 
    }; 
}) 

Ich entschied dann, dass ich die event.target.nextElementSibling.textContent.trim() Linien hasste ... Ich fühle mich wie ich sollte überprüfen, dass alle diese Methoden existieren, oder mit einem try/catch. So schrieb ich die Richtlinie die Logik von der Steuerung umfassen:

(via Richtlinie) - HTML-Code:

 <div ng-repeat="otherInvestment in fieldMappings.otherInvestmentsMap"> 
     <input type="checkbox" 
       checkbox-group 
       groupname="otherInvestment" 
       labelvalue="{{otherInvestment.name}}" 
       fieldobj="otherInvestment" 
       destarray="suitabilityHolder.suitability.otherInvestment" /> 
     <label class="checkbox-label" 
       popover-title="{{otherInvestment.name}}" 
       popover="{{otherInvestment.help}}" 
       popover-trigger="mouseenter">{{otherInvestment.name}} 
     </label> 
     </div> 

(via Richtlinie) - Richtlinie Code:

.directive("checkboxGroup", function() { 
    return { 
    restrict: "A", 
    scope: { 
     destarray:  "=", // the source of all the checkbox values 
     fieldobj:  "=", // the array the values came from 
     groupname:  "@", // the logical name of the group of checkboxes 
     labelvalue:  "@" // the value that corresponds to this checkbox 
    }, 
    link: function (scope, elem, attrs) { 
     // Determine initial checked boxes 
     // if the fieldobj.id exists in the destarray, check this checkbox 
     if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) { 
     elem[0].checked = true; 
     } 

     // Update array on click 
     elem.bind('click', function() { 
     // store the index where the fieldobj.id exists in the destarray 
     var index = scope.destarray.indexOf(scope.fieldobj.id), 
      // get the array of checkboxes that form this checkbox group 
      chkboxArr = $('input[groupname^=' + scope.groupname + ']'); 

     // Add if checked 
     if (elem[0].checked) { 
      if (scope.labelvalue === "None") { 
      // loop through checkboxes and uncheck all the ones that are not "None" 
      chkboxArr.each(function() { 
       // have to noodle through the checkbox DOM element to get at its attribute list 
       // - is there a cleaner way? 
       var tmpLabelValue = this.attributes.labelvalue.nodeValue.trim(); 
       if (tmpLabelValue !== "None") { 
       this.checked = false; 
       } 
      }); 
      } 
      // if the target was anything BUT the None option, uncheck None 
      else { 
      chkboxArr.each(function() { 
       var tmpLabelValue = this.attributes.labelvalue.nodeValue.trim(); 

       if (tmpLabelValue === "None") { 
       this.checked = false; 
       } 
      }); 
      } 

      if (index === -1) { 
      // add the id to the end of the dest array 
      // **will not maintain original order if several are unchecked then rechecked** 
      scope.destarray.push(scope.fieldobj.id); 
      } 
     } 

     // Remove if unchecked 
     else { 
      if (index !== -1) { 
      scope.destarray.splice(index, 1); 
      } 
     } 
     }); 
    } 
    }; 
}) 

Im Rückblick Ich nehme an, ich bevorzuge es, den gesamten Code in einer Direktive zu speichern, obwohl ich denke, dass es weniger intuitiv und komplexer ist, als die gesamte Handhabung in der Steuerung über jQuery zu werfen. Es schneidet die clearOnNone() - Funktion vom Controller aus, was bedeutet, dass der gesamte Code, der mit dieser Funktionalität zu tun hat, im HTML-Markup und in der Direktive enthalten ist.

Ich bin kein Fan von Code wie this.attributes.labelvalue.nodeValue.trim(), die ich noch in meiner Richtlinie endete. Für Szenarien wie meine, wo die Geschäftseinheit bestimmte Anforderungen hat, die (anders ausgedrückt) mühsam und umständlich sind, weiß ich nicht, dass es wirklich eine "saubere" Möglichkeit gibt, alles zu codieren.

0

Ich bin noch neu in AngularJS aber ich diesen Fall denke ich, dass ich es, indem Sie entweder einen ng-click Handler oder $scope.$watch lösen würde den Zustand des NONE Modell zu aktualisieren, wenn die anderen Modelle ändern.

Mit ng Sie auf

Ich habe eine jsFiddle peitschte das zeigt, wie es mit ng-click funktionieren könnte:

http://jsfiddle.net/Dzj6K/1/

HTML:

<div ng-controller="myCtrl"> 

    <div ng-repeat="objective in objectives"> 
     <label><input type="checkbox" ng-model="objective.selected" ng-click="click(objective)" /> {{objective.name}}</label> 
    </div> 

</div> 

JavaScript:

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

function myCtrl($scope) { 

    $scope.objectives = [ 
     {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"}, 
     {'id':"STABLE", 'name':"Moderate"}, 
     {'id':"BALANCED", 'name':"Moderate Growth"}, 
     {'id':"NONE", 'name':"None"} 
    ]; 

    $scope.click = function(objective) { 
     if (objective.id === "NONE") { 
      if (objective.selected) { 
       angular.forEach($scope.objectives, function(objective) { 
        if (objective.id !== "NONE") { 
         objective.selected = false; 
        } 
       }); 
      } 
     } else { 
      angular.forEach($scope.objectives, function(objective) { 
       if (objective.id === "NONE") { 
        objective.selected = false; 
       } 
      }); 
     } 
    }; 

} 

$ scope $

Mit beobachten

Und eine Version des jsFiddle das zeigt, wie es mit $scope.$watch arbeiten konnte.

http://jsfiddle.net/Dzj6K/

HTML:

<div ng-controller="myCtrl"> 

    <div ng-repeat="objective in objectives"> 
     <label><input type="checkbox" ng-model="objective.selected" /> {{objective.name}}</label> 
    </div> 

</div> 

JavaScript:

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

function myCtrl($scope) { 

    $scope.objectives = [ 
     {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"}, 
     {'id':"STABLE", 'name':"Moderate"}, 
     {'id':"BALANCED", 'name':"Moderate Growth"}, 
     {'id':"NONE", 'name':"None"} 
    ]; 

    $scope.$watch('objectives', function() { 
     var anySelected = false; 
     var noneModel = null; 

     angular.forEach($scope.objectives, function(objective) { 
      if (objective.id === "NONE") { 
       noneModel = objective; 
      } else { 
       anySelected = anySelected || objective.selected; 
      } 
     }); 

     if (noneModel) { 
      noneModel.selected = !anySelected; 
     } 

    }, true); 

} 
+0

Interessante Idee Karl. Meine Hauptreservierung zu diesem Ansatz für mein Szenario ist, dass ich mehrere Gruppen von Checkboxen habe, und ich weiß nicht, dass ich eine $ Watch für jedes im Controller einrichten möchte. Ich frage mich über die Leistung, da das Aktivieren/Deaktivieren von Boxen zusätzliche $ Watch-Ausführungen auslösen kann. Die Geige funktionierte nicht für mich ("None" würde nie nachsehen). :( – tengen

+0

@tengen Ich habe meine Antwort mit einem 'ng-click'-basierten Ansatz aktualisiert, der viel besser mit dem in der Antwort übereinstimmt. Ich habe dies hauptsächlich aus Gründen der Vollständigkeit getan, da Sie bereits eine Antwort haben das funktioniert für dich, das ist großartig! – Karl

+0

Die Zielkarte mit der Benutzerauswahl zu verzieren ist ziemlich clever. Ich hatte es nicht berücksichtigt - ich dachte nur daran, das endgültige Benutzerobjektmodell zuzuordnen, das ich einreichen möchte dann befrage die Karte und aktualisiere das Benutzermodell entsprechend) aber es könnte in bestimmten Situationen nützlich sein. Danke für die großartige Idee und die Zeit nehmen, um mir zu helfen. – tengen

Verwandte Themen