Propagating view's dirty state to the containi

2019-08-06 12:47发布

问题:

I have a main form with the following markup

 <tabset vertical="true" type="pills">
                                    <tab ng-repeat="tab in tabsViews" select="selectView(tab.name, tab.index)"
                                         ng-show="tab.isVisible"
                                         class=" {{tabsViews[tab.index-1]['invalid'] ? 'invalid-tab': 'valid-tab' }}">
                                        <tab-heading>{{tab.title}}</tab-heading>
                                    </tab>
                                </tabset>

and the selectView method in my controller is the following:

$scope.previousIndex = null;

            $scope.selectView = function (viewName, viewIndex) {
                $scope.selections.showInvoicesDetailView = false;
                $scope.selections.showInvoicesView = false;
                $scope.selections.showPassesView = false;
                if (viewIndex) {

                    if ($scope.previousIndex != null && $scope.form) {
                        $scope.tabsViews[$scope.previousIndex - 1]["invalid"] = $scope.form.$invalid;
                    }

                    $scope.previousIndex = viewIndex;
                }

                if (viewName.substring(0, 9) != 'invoices.')
                    $scope.selections.lastSelectedView = viewName;
                else
                    $scope.selections.showInvoicesDetailView = true;

                if (viewName == 'guestPasses')
                    $scope.selections.showPassesView = true;

                if (viewName == 'invoices')
                    $scope.selections.showInvoicesView = true;

                if ($scope.selections.isNew) {
                    window.console && console.log('SelectView called with the new.' + viewName + ' view...');
                    $state.go('new.' + viewName);
                }
                else {
                    window.console && console.log('SelectView called with the edit.' + viewName + ' view...');
                    $state.go('edit.' + viewName);
                }
            };

The main form has a directive to detect its dirty state and ask for saving the changes. The problem is that when I change anything in my current view, that form's dirty state is not propagated into that main form. Is there a way to set main form dirty state based on the particular tab (defined as the view) dirty state?

To understand the problem better, here is the main form markup (the one that has tabs):

div ng-class="{'col-md-7': $parent.showSearch, 'col-md-11': !$parent.showSearch}">
    @Html.Partial("_EditFormHeader")
    <div class="fourpanel">
        <div data-sm:collapse="$parent.showForm" id="hideFrm" class="pull-left col-sm-3 sm-search-list">
            <form name="guestMainForm" novalidate role="form" data-sm:dirty-check data-server:error
                  ng-show="$parent.showForm && !selections.justDeleted" class="ng-cloak">
                <div id="guestEditForm" class="widget" data-resize:container>
                    <div class="widget-head">
                        <div class="row">
                            <div class="clearfix"></div>
                            @Labels.guest: {{currentGuest.contactPerson.firstName + ' ' + currentGuest.contactPerson.lastName}} {{ !isNew ? '(' + currentGuest.guestNo + ')' : '' }}
                            <div class="pull-right text-right col-lg-1" style="padding-right:5px">
                                <i class="fa fa-angle-double-left sm-slider-button" ng-click="toggleFormVisibility()"
                                   alt="@String.Format(Labels.hideX, Labels.account)" id="angle-left"></i>
                            </div>
                        </div>
                    </div>
                    <div class="widget-content">
                        <div class="scrollable widget-resize">
                            <div class="padd">
                                @Html.Partial("_EditFormAlerts")
                            </div>
                            <div class="col-lg-2 col-md-2 panel-container">
                                <tabset vertical="true" type="pills">
                                    <tab ng-repeat="tab in tabsViews" select="selectView(tab.name, tab.index)"
                                         ng-show="tab.isVisible"
                                         class=" {{tabsViews[tab.index-1]['invalid'] ? 'invalid-tab': 'valid-tab' }}">
                                        <tab-heading>{{tab.title}}</tab-heading>
                                    </tab>
                                </tabset>
                            </div>

                            <div class="col-lg-8 col-md-4 panel-container">
                                <div data-ui-view data-autoscroll="false"></div>
                                <div data-ui-view="guestPasses" ng-show="selections.showPassesView"></div>
                                <div data-ui-view="invoices" data-autoscroll="false" ng-show="selections.showInvoicesView"></div>
                            </div>
                        </div>
                    </div>

                    <div class="widget-foot">
                        <div ng-show="!isNew">
                            <button class="btn btn-primary" ng-click="save(currentGuest)"
                                    ng-disabled="form.$invalid || disableAction">
                                @Labels.save
                            </button>
                            <data-delete:button title="{{ '@Labels.delete: ' + currentGuest.contactPerson.firstName.trim() + ' ' + currentGuest.contactPerson.lastName.trim() + ' (' + currentGuest.guestNo +')' }}"
                                                message="@String.Format(Messages.confirmDelete, Labels.guest)"
                                                disable-action="disableAction"
                                                delete="delete()">
                            </data-delete:button>
                            <data-cancel:button title="@Labels.unsavedChanges"
                                                message="@Messages.unsavedChanges"
                                                cancel="cancel()"
                                                disable-action="disableAction"
                                                dirty="form.$dirty">
                            </data-cancel:button>
                        </div>
                        <div ng-show="isNew">
                            <button id="btnAdd" class="btn btn-primary" ng-click="new(currentGuest)"
                                    ng-disabled="form.$invalid || disableAction">
                                @Labels.add
                                </button>
                                <data-cancel:button title="@Labels.unsavedChanges"
                                                    message="@Messages.unsavedChanges"
                                                    cancel="cancel()"
                                                    disable-action="disableAction"
                                                    dirty="form.$dirty">
                                </data-cancel:button>
                            </div>
                        </div>
                    </div>
                </form>
        </div>
        <div id="showFrm" class="sm-form-expand-button text-center col-sm-1"
             ng-show="!$parent.showForm"
             ng-click="toggleFormVisibility()">
            <i class="fa fa-angle-double-right"></i>
            <div class="panel2Label">@Labels.guest: {{ currentGuest.contact.firstName.trim() + ' ' + currentGuest.contact.lastName.trim() }} {{ !isNew ? '(' + currentGuest.guestNo + ')' : '' }}</div>
        </div>

        <div class="col-sm-5 panel-container">
            <div data-ui-view="detail" data-autoscroll="true" ng-show="selections.showInvoicesDetailView"></div>
            <div data-ui-view="passDetail"></div>
        </div>
    </div>
</div>

I created a new directive and added it to my view forms:

function ($modal, $rootScope, $location, $state) {
            return {
                restrict: 'A',
                require: ['^form'],
                //scope: {
                //    onOk: '&',
                //    onCancel: '&'
                //},
                link: function (scope, element, attrs, controllers) {

                    var form = controllers[0];

                    window.console && console.log(form);

                    window.onbeforeunload = function () {
                        if ((form && form.$dirty) || element.hasClass('ng-dirty')) {
                            //         return resourceFactory.getResource('Messages', 'unsavedChanges');

                            if (scope.form)
                            {
                                scope.form.$setDirty();
                            }
                        }
                    };
                   
                }
            };

I am debugging and I can see that form is correctly set to my view's form and I can access the parent form using form.$$parentForm property. However, I don't know to which event should I hook to set form.$$parentForm.$setDirty when my form becomes dirty. If you can help me figure this out, then it will work for me, I guess.

回答1:

Quote from Angular documentation on the form directive:

In Angular, forms can be nested. This means that the outer form is valid when all of the child forms are valid as well. However, browsers do not allow nesting of elements, so Angular provides the ngForm directive which behaves identically to but can be nested. This allows you to have nested forms, which is very useful when using Angular validation directives in forms that are dynamically generated using the ngRepeat directive. Since you cannot dynamically generate the name attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an ngForm directive and nest these in an outer form element.

Maybe this also works for the $dirty state so that if a child form is $dirty the parent form will also be $dirty. I'm not sure that in your case you'll be able to nest the forms. I don't have enough context to visualise what you want to achieve.

Alternatively, you can manually set the main form to dirty when one of those other forms becomes dirty. Because you added the code from your main form, I can see you're not using the built in dirty checker from angular. Maybe you have a good reason for this, but perhaps you didn't know of it's existence. You'll have to use the angular form directive then. The FormController has the following method: $setDirty();.

FormController documentation

Form Directive documentation