Обрабатывать проверку клиента и сервера с помощью Flex 3? - PullRequest
1 голос
/ 05 мая 2011

Я новый выпускник, так что будьте добры. Я работаю над проверкой ввода в ячейку Flex DataGrid, которую пользователь может редактировать. Строки в DataGrid поддерживаются mx.collections.ArrayCollection, который включает в себя [Bindable]Model, который я написал. Я хочу проверить по пользовательскому клиентскому валидатору , если и только если это пройдет, я хочу проверить входные данные на сервере. Если проверка на стороне клиента не удалась, я хочу отобразить обычную ошибку проверки (см. Изображение ниже). Если проверка на стороне сервера не удалась, я хочу использовать компоненты пользовательского интерфейса такого же типа для уведомления пользователя. Решение не должно включать какие-либо внешние рамки ( Cairngorm или PureMVC ).

Standard Validator usage

Моя реализация DataGrid:

<mx:DataGrid id="myPageGrid" dataProvider="{myModelList}" editable="true"
             itemEditEnd="verifyInputIsValid(event)">
    <mx:columns>
        <mx:DataGridColumn dataField="name" headerText="Name"
                           editable="false" />

        <mx:DataGridColumn dataField="fieldNeedingValidation" editable="true" 
                           id="fnv" headerText="Field Needing Validation" />

    </mx:columns>
</mx:DataGrid>

Когда пользователь редактирует ячейку, эта функция вызывается:

private function verifyInputIsValid(event:DataGridEvent):void
{
    // Check the reason for the event.
    if (event.reason == DataGridEventReason.CANCELLED)
    {
        return; // Do not update cell.
    }            

    // For the fieldNeedingValidation only
    if(event.dataField == "fieldNeedingValidation") {
        // Get the new data value from the editor.
        var newValue:String = TextInput(event.currentTarget.itemEditorInstance).text;
        var validatorResult:ValidationResultEvent = myValidator.validate(newValue);

        if(validatorResult.type==ValidationResultEvent.INVALID){
            // Prevent the user from removing focus,  and leave the cell editor open.  
            // Also, the edit will not continue and store the blank value
            event.preventDefault();
            // Write a message to the errorString property. 
            // This message appears when the user mouses over the editor.
            TextInput(myPageGrid.itemEditorInstance).errorString = validatorResult.message;
            return;                     
        }
        else if(validatorResult.type==ValidationResultEvent.VALID){
            // Assuming the data is valid on the Server, this is fine
            TextInput(myPageGrid.itemEditorInstance).errorString = "";
            TextInput(myPageGrid.itemEditorInstance).text = newValue;
            return;


            // I'd rather do this
            remoteObjectValidationService.validate(newValue);
            // Get a String result back from the call to the RemoteObject
            // Mark this "edit" (of the cell) as invalid, just as the client-side validator would

        }
    }
}

Конечно, чтобы это работало, resultHandler из remoteObjectValidationService необходимо будет вызвать (и запустить), прежде чем мы выйдем из функции verifyInputIsValid. «Синхронно». Я знаю «Все операции ввода-вывода во Flex являются асинхронными» , но должен быть стандартный способ сделать что-то подобное, верно? Я уже реализовал свой пользовательский Validator, и он отлично работает.

Как программисты Flex проверяют на сервере сразу после прохождения действительной проверки на стороне клиента?

Я понимаю, что глупо искать этот «синхронный» дизайн, и я надеюсь, что у кого-то есть ответ, чтобы решить мою проблему с помощью лучших практик. В свою защиту я хочу проверить на сервере сразу после проверки на стороне клиента, так как я использую среду проверки Flex. Если я получаю неправильный ответ от сервера, я хочу использовать встроенные компоненты пользовательского интерфейса, которые Flex должен сообщить пользователю, что что-то не так с его / ее вводом.

Есть идеи?

Ответы [ 3 ]

1 голос
/ 05 мая 2011

Вы можете создать собственный валидатор, который вызывает удаленный сервис. Просто сделайте, чтобы валидатор выполнил любые внутренние проверки перед выполнением вызова.

Я написал что-то вроде этого давно. Конечно, это можно было бы сделать лучше, но вы можете извлечь из него несколько идей.

http://www.actionscript.org/forums/showthread.php3?t=173275

UPDATE

Вот более чистый пример того, как может выглядеть нечто подобное.

package
{
    import flash.events.Event;
    import mx.rpc.AsyncToken;
    import mx.rpc.Responder;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.validators.Validator;

    public class UsernameValidator extends Validator
    {
        /**
         *
         */
        public function UsernameValidator()
        {
            super();
        }

        /**
         * Inject or create some kind of service delegate to process the remote check.
         */
        public var userNameService:IUserNameService;

        /**
         * Store the result of the remote check for the second pass through. 
         */     
        private var _nameCheckResult:int = 0;


        /**
         * Overide this method to start the validation process
         */
        override protected function doValidation(value:Object):Array
        {
            var userName:String = String(value);
            var invalidChars:RegExp = /\W+/;

            // Call base class doValidation().
            var results:Array = super.doValidation(value);

            // Return if there are errors.
            if(results.length > 0)
                return results;

            // If input value is 0, or contains no value, 
            // issue a validation error.
            if(!userName)
            {
                results.push(new ValidationResult(true, null, "required", "No user name was entered. Please select a user name"));
            }
            else if(userName.match(invalidChars))
            {
                results.push(new ValidationResult(true, null, "invalidChars", "This user name contains non alphanumeric characters [a-zA-Z0-9_]. Please select another and try again."));
            }
            else if(_nameCheckResult == 1)
            {
                //well assume that 1 means it's bad
                results.push(new ValidationResult(true, null, "taken", "This user name has already been taken."));
            }
            else
            {
                //all checks have passed so return a special error type indicating a pending operation
                //the string identifier is meaningless, except to indicate that it should be handled
                //differencly by the consumer of the validation
                results.push(new ValidationResult(true, null, "validating", "Checking username availability."));

                //call some kind of remote service

                var token:AsyncToken = this.userNameService.checkAvailability(userName);
                token.addResponder(new Responder(userNameService_resultHandler, userNameService_faultHandler));

                    //...you should also add some logic to handle a change to the input if you want to use "live validation"
            }

            return results;
        }

        /**
         * Dispatch some kind of event indicating an error (preferably with some return codes)
         */
        private function userNameService_faultHandler(event:FaultEvent):void
        {
            trace("UserNameValidator.handleNameCheckError");

            dispatchEvent(new Event("error"));
        }

        /**
         * Check the result and dispatch an event indicating the validation needs to be run again.
         */
        private function userNameService_resultHandler(event:ResultEvent):void
        {
            trace("userNameService_resultHandler(event)");

            _nameCheckResult = event.result as int;

            this.dispatchEvent(new Event("complete"));
        }
    }
}

package
{
    import mx.rpc.AsyncToken;

    /**
     * The interface to a service delegate that checks
     * the username and returns an AsyncToken.
     */
    public interface IUserNameService
    {
        function checkAvailability(userName:String):AsyncToken
    }
}

Идея состоит в том, чтобы запустить валидатор дважды. Один раз, когда начальная проверка завершена, затем снова, когда валидатор так же получил соответствующий код возврата для асинхронной операции. Как вы это сделаете, зависит от того, как вы обрабатываете события проверки в вашем приложении.

1 голос
/ 12 мая 2011

Это сообщение довольно длинное, поэтому я подумал: " отвечает " на мой вопрос с решением, а не " редактирование "Вопрос для показа решения был наиболее уместным.Я буду редактировать вопрос, чтобы точнее отразить параметры проблемы (т. Е. Требование, чтобы решение не включало какую-либо дополнительную среду Flex, такую ​​как Cairngorm или PureMVC , чтобы оставаться простым).

Я также допускаю, что это решение немного слабое.На данный момент у меня есть еще одно событие, которое мне нужно выяснить и удалить - , но оно работает и соответствует бизнес / техническим требованиям.Это также чувствовало, как будто я "заново изобретал колесо", и я предпочел бы не.Так что, если у кого-то есть пример (какой-то шаблон проектирования?), Который включает «проверку клиента и сервера», а также использование инфраструктуры проверки Flex для некоторых изменений в itemEditor (мне нужно редактирование ячейки DataGrid ), Я был бы очень признателен, если бы вы перечислили его здесь в качестве ответа, и, возможно, я могу предложить вам несколько баллов!

Я также не совсем уверен в том, как я закрываю / совершаюредактор.Я пытался использовать destroyItemEditor(), но мне это не помогло.

Вот мой исходный код:

MyPage.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
          xmlns:validators="validators.*"
          preinitialize="myPage_preinitializeHandler(event)"
          initialize="myPage_initializeHandler(event)"
          creationComplete="myPage_creationCompleteHandler(event)">

    <mx:Script>
        <![CDATA[
            import entities.MyModel;
            import entities.MyUser;

            import events.MyValidatorEvent;

            import mx.collections.ArrayCollection;
            import mx.controls.TextInput;
            import mx.events.DataGridEvent;
            import mx.events.DataGridEventReason;
            import mx.events.FlexEvent;
            import mx.rpc.events.ResultEvent;
            import mx.rpc.remoting.mxml.RemoteObject;

            import services.UserRemoteObjectService;

            import validators.UserValidator;

            private var _userValidator:UserValidator;

            private var _securedPageService:RemoteObject;
            private var _securedUsersService:RemoteObject;
            private var _userRemoteObjectService:UserRemoteObjectService;

            [Bindable]
            private var _myModelList:ArrayCollection;

            protected function myPage_preinitializeHandler(event:FlexEvent):void
            {
                _userValidator = new UserValidator();
                _myModelList = new ArrayCollection();
            }

            protected function myPage_initializeHandler(event:FlexEvent):void
            {
                _securedPageService = new RemoteObject();
                _securedPageService.destination = "securedPageService";
                _securedPageService.getAllData.addEventListener("result",getAllData_resultHandler);

                _securedUsersService = new RemoteObject();
                _securedUsersService.destination = "securedUsersService";

                // For client-side and server-side validation using a RemoteObject service
                _userRemoteObjectService = new UserRemoteObjectService(_securedUsersService);
                _userValidator.userService = _userRemoteObjectService;
            }

            protected function myPage_creationCompleteHandler(event:FlexEvent):void
            {
                initializeModelList();
            }

            private function initializeModelList():void
            {
                _securedPageService.getAllData();   
            }

            private function getAllData_resultHandler(event:ResultEvent):void
            {
                var untypedList:ArrayCollection = (event.result as ArrayCollection);
                var myModel:MyModel;

                for each(var m:Object in untypedList)
                {
                    myModel = new MyModel(m.auditModelId, m.groupName,
                        m.reviewRequired, m.fieldNeedingValidation, m.lastReview)
                    _myModelList.addItem(myModel);
                }
            }

            private function verifyInputIsValid(dgEvent:DataGridEvent):void
            {               
                if (dgEvent.reason == DataGridEventReason.CANCELLED)
                {
                    return; // Edit is "cancelled", do not update
                }            

                // For the fieldNeedingValidation column only
                if(dgEvent.dataField == "fieldNeedingValidation") {
                    // Get the new data value from the editor.
                    var userID:String = TextInput(dgEvent.currentTarget.itemEditorInstance).text;

                    _userValidator.addEventListener("totallyComplete",userValidator_completeHandler);
                    _userValidator.addEventListener("error",userValidator_errorHandler);

                    _userValidator.validateSystemUser(userID, myPageGrid.itemEditorInstance, dgEvent);
                }
            }

            private function userValidator_completeHandler(event:MyValidatorEvent):void
            {
                TextInput(event.target.itemEditorInstance).errorString = "";
                event.target.dataGridEvent.itemRenderer.data.fieldNeedingValidation = (event.myUser as MyUser).fullName;
                myPageGrid.editedItemPosition = null;
                myPageGrid.selectedIndex = -1;
            }

            private function userValidator_errorHandler(event:MyValidatorEvent):void
            {
                // Prevent the user from removing focus,  and leave the cell editor open.
                // The edit will not continue and store the blank value
                (event.target.dataGridEvent as DataGridEvent).preventDefault();

                // Write a message to the errorString property.
                // This message appears when the user mouses over the editor.
                TextInput(event.target.itemEditorInstance).errorString = event.errorMessage;
                return;
            }
        ]]>
    </mx:Script>
    <mx:Panel title="My Page">
        <mx:DataGrid id="myPageGrid" dataProvider="{_myModelList}"
                     itemEditEnd="verifyInputIsValid(event)" editable="true">
            <mx:columns>
                <mx:DataGridColumn dataField="someField" headerText="Something" editable="false" />
                <mx:DataGridColumn dataField="fieldNeedingValidation" editable="true" headerText="Input User ID"/>
            </mx:columns>
        </mx:DataGrid>
    </mx:Panel>
</mx:Panel>

UserValidator.as

package validators {

    import entities.IMyUser;
    import entities.MyUser;

    import events.MyValidatorEvent;

    import flash.events.Event;

    import mx.controls.TextInput;
    import mx.controls.listClasses.IListItemRenderer;
    import mx.events.DataGridEvent;
    import mx.events.ValidationResultEvent;
    import mx.rpc.AsyncToken;
    import mx.rpc.Responder;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.validators.ValidationResult;
    import mx.validators.Validator;

    import services.IUserService;

    public class UserValidator extends Validator
    {
        public var userService:IUserService; //Service delegate to process the remote validation

        private var _itemEditor:IListItemRenderer;
        private var _dataGridEvent:DataGridEvent;
        private var _inputValue:String = null;

        public function UserValidator()
        {
            super();            
        }

        /**
         * The "Core Method" of this class.  Invokes validation of a userIDToValidate
         * and later takes the appropriate action on UI components as need be.
         */
        public function validateSystemUser(userIDToValidate:String, itemEditor:IListItemRenderer ,dgEvent:DataGridEvent):void
        {
            this._dataGridEvent = dgEvent;
            this._itemEditor = itemEditor;

            var validatorResult:ValidationResultEvent = this.validate(userIDToValidate);

            if(validatorResult.type==ValidationResultEvent.INVALID){
                if(validatorResult.results[0].errorCode == "validating"){
                    // Prevent the user from removing focus,  and leave the cell editor open.  
                    // Also, the edit will not continue and store the blank value
                    dgEvent.preventDefault();
                    // Write a message to the errorString property. 
                    // This message appears when the user mouses over the editor.
                    TextInput(itemEditor).errorString = validatorResult.message;
                    trace("Please wait, server is validating...");
                    return;
                }
                else{
                    // A client-side "invalid", handled the same.  This time the message 
                    // does not include "Please wait" text
                    dgEvent.preventDefault();
                    TextInput(itemEditor).errorString = validatorResult.message;
                    return;
                }
            }
            else if(validatorResult.type==ValidationResultEvent.VALID){
                // Everything was successful, update the UI
                TextInput(itemEditor).errorString = "";
                TextInput(itemEditor).text = userIDToValidate;
                return;
            }
        }

        // Overide this method to start the validation process
        override protected function doValidation(value:Object):Array
        {
            if (_inputValue != String(value)){
                _inputValue = String(value);    
            }

            var results:Array = super.doValidation(value); // Call base class doValidation().
            if(results.length > 0){
                return results; // Return if there are errors.
            }

            //Business rules for client side validation will determine this
            var someErrorCondition:Boolean = false;
            if (someErrorCondition == true)
            {
                results.push(new ValidationResult(true, null, "errorCode", "Error description"));
                return results;
            }
            else{
                trace("All client-side validation has passed");
                /**
                 * Call the remote service, return an 'error' indicating server validation 
                 * is pending. The String identifier is meaningless, except to indicate 
                 * that it should be handled differencly by the consumer of the validation.
                 */
                results.push(new ValidationResult(true, null, "validating", 
                    "Please wait: \nThe server is validating this corpID."));

                var token:AsyncToken = this.userService.service_findByID(_inputValue);

                token.addResponder(new Responder(userValidator_resultHandler,
                    userValidator_faultHandler));

                return results; 
            }
        }

        private function userValidator_resultHandler(re:ResultEvent):void
        {
            if(re.result.errorMessage == null)
            {
                var myUser:IMyUser = new MyUser(re.result.corpID,re.result.fullName,re.result.managerFullName);
                var validatorCompleteEvent:Event = new MyValidatorEvent("totallyComplete", "", myUser);
                this.dispatchEvent(validatorCompleteEvent);
            }
            else
            {
                trace("ERROR: Something went wrong in the userValidator_resultHandler");
            }
        }

        /**
         * This fault handler is invoked because my Server (via BlazeDS) actually 
         * returns/throws a custom Exception.  This will dispatch an error to it's consumer
         * (MyPage.mxml) using the details of that Exception/FaultEvent, used later to populate
         * the same UI component as Flex's standard "Validator" (client-side) would. 
         * @see: http://livedocs.adobe.com/flex/3/html/help.html?content=validators_2.html
         */
        private function userValidator_faultHandler(fe:FaultEvent):void
        {
            var myUser:IMyUser = new MyUser(this._inputValue,null,null);
            var errorEvent:Event = new MyValidatorEvent("error", fe.fault.rootCause.message, myUser);
            dispatchEvent(errorEvent);
        }

        public function get itemEditorInstance():IListItemRenderer
        {
            return _itemEditor;
        }

        public function get dataGridEvent():DataGridEvent
        {
            return _dataGridEvent;
        }
    }
}

UserRemoteObjectService.as

package services
{
    import mx.rpc.AsyncResponder;
    import mx.rpc.AsyncToken;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.remoting.mxml.RemoteObject;

    public class UserRemoteObjectService implements IUserService
    {
        private var _userService:RemoteObject;

        public function UserRemoteObjectService(userService:RemoteObject)
        {
            this._userService = userService;
        }

        public function service_findByID(userID:String):AsyncToken
        {
            var token:AsyncToken = _userService.findById(userID);
            token.addResponder(
                new AsyncResponder(findByID_resultHandler, 
                    findByID_faultHandler)
            );
            return token;
        }

        private function findByID_resultHandler(event:ResultEvent, token:AsyncToken=null):void
        { 
            event.token.dispatchEvent(event);
        } 

        private function findByID_faultHandler(event:FaultEvent, token:AsyncToken=null):void
        {
            event.token.dispatchEvent(event);
        }
    }
}

Итак, это текущий код, @drkstr и @Kyle, который мне интересно посмотретьчто вы думаете.

Спасибо StackOverflow, @drkstr, сегодня вы отметили галочкой «Принят», вы вдохновили мое решение.

1 голос
/ 05 мая 2011

«Синхронный» способ сделать это - сначала выполнить проверку на стороне сервера.Создайте удаленный объект и выполните проверку на стороне сервера:

private function verifyInputIsValid(event:DataGridEvent):void
{
    var newValue:String = TextInput(evt.currentTarget.itemEditorInstance).text;
    remoteObjectValidationService.addEventListener("result", function(event:ResultEvent):void{
        resultHandler(event, evt); 
    });
    remoteObjectValidationService.validate(newValue);
}

После завершения проверки сервера выполните проверку на стороне клиента:

private function resultHandler(event:ResultEvent, evt:DataGridEvent):void{
    //Check that the server-side validation is successful
    if((event.result as String).toUpperCase() == "VALID"){

        // Check the reason for the event.
        if (event.reason == DataGridEventReason.CANCELLED)
        {
            return; // Do not update cell.
        }            

        // For the fieldNeedingValidation only
        if(event.dataField == "fieldNeedingValidation") {
            // Get the new data value from the editor.
        var newValue:String = TextInput(event.currentTarget.itemEditorInstance).text;
        var validatorResult:ValidationResultEvent = myValidator.validate(newValue);

        if(validatorResult.type==ValidationResultEvent.INVALID){
        // Prevent the user from removing focus,  and leave the cell editor open.  
        // Also, the edit will not continue and store the blank value
        event.preventDefault();
         // Write a message to the errorString property. 
        // This message appears when the user mouses over the editor.
                TextInput(myPageGrid.itemEditorInstance).errorString = validatorResult.message;
                return;                     
            }
            else if(validatorResult.type==ValidationResultEvent.VALID){
                // Assuming the data is valid on the Server, this is fine
                        TextInput(myPageGrid.itemEditorInstance).errorString = "";
                        TextInput(myPageGrid.itemEditorInstance).text = newValue;
                return;
                    }
                }
            }
        }
...