问:
Adding a loader/spinner to yii2 interface
I have a view in which I have two buttons. While clicking on any of the two buttons, a certain request is made.

View

```<code><?php Pjax::begin(); ?>
        <?=Html::beginForm(['process'],'post');?>
        <?=Html::submitButton('Disconnect', ['id'=>'d','name'=>'dco','class' => 'btn btn-primary']);?>
        <?=Html::submitButton('Connect', ['id'=>'r','name'=>'rco','class' => 'btn btn-info']);?>
        <br><br>
        <div class="pre-scrollable">
        <?= GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [

        ['class' => 'yii\grid\CheckboxColumn', 'checkboxOptions' => function($d) {
            return ['value' => $d['msn']];
        }],

        'customer_id',
       // 'dept_code:ntext',
        'dept_name:ntext',
        'sub_div_name',
        [
            'label' => 'Sub Division Name',
            'value' => function ($d) {
                return $d->subdiv->name;
            },
            'filter' => Html::activeDropDownList($searchModel, 'sub_div_code', \common\models\SurveyHescoSubdivision::toArrayList(), ['prompt' => "Sub-Div", 'class' => 'form-control']),

        ],
        'division_name',
        'allowed_units',
        'msn',

        'units_consumed',
        [
            'label' => 'Disconnected',
            'attribute' => 'disconnected',
            'format'=>'raw',
            'contentOptions' => ['style'=>'text-align:center'],
            'value' => function($model){
                return $model->disconnected == 1 ? '<span class="glyphicon glyphicon-ok text-success"></span>' : '<span class="glyphicon glyphicon-remove text-danger"></span>';
            },
            'filter' => Html::activeDropDownList($searchModel, 'disconnected', [''=>'All','1'=>'Yes','0'=>'No'], ['class' => 'form-control']),
        ],

        'active_energy_total_m',

        ['class' => 'yii\grid\ActionColumn'],
    ],
]); ?>
        </div>
        <?= Html::endForm();?>
        <?php Pjax::end(); ?>
</code>```

Controller

```<code> public function actionProcess()
{

 $soapUrl = "http://ip:port/HES/services/Request";
    $userName = "user";
    $password = "123456";

 if (isset($_POST['dco']))
    {
        if(Yii::$app->request->isPost)
        {
            $data = Yii::$app->request->post('selection'); //checkbox (array)

            foreach($data as $value)
            {

                $msn = $value;

                $xml_post_string = /** @lang text */
                    '//soap request';

                $headers = array(
                    "Content-type: text/xml;charset=\"utf-8\"",
                    "Accept: text/xml",
                    "Cache-Control: no-cache",
                    "Pragma: no-cache",
                    "Content-length: ".strlen($xml_post_string),
                ); //SOAPAction: your op URL

                $url = $soapUrl;

                // PHP cURL  for https connection

                $ch = curl_init();
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
                curl_setopt($ch, CURLOPT_URL, $url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

                curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
                curl_setopt($ch, CURLOPT_TIMEOUT, 10);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_post_string); // the SOAP request
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

                // converting
                $response = curl_exec($ch);
                curl_close($ch);
                $domd=new DOMDocument();

                var_dump($response);
                if(!$domd->loadXML($response)){
                    throw new \RuntimeException("failed to parse XML!");
                }
                $inner_xml=$domd->getElementsByTagName("return")->item(0)->textContent;
                if(!($domd2=@DOMDocument::loadXML($inner_xml))){
                    throw new \RuntimeException("failed to parse inner_xml!");
                }
                $AsyncReplyFlag=$domd2->getElementsByTagName("AsyncReplyFlag")->item(0)->textContent;

                if ($AsyncReplyFlag =='true')
                {
                    $ds = 1;

                    $disconnected_at = date('Y-m-d H:i:s');

                    try {
                        Yii::$app->db->createCommand(/** @lang text */
                            "update
  `accurate_mam`.`daily_log`
set
  `disconnected` = '$ds',
  `diconnected_at` = '$disconnected_at',
  `reconnected_at` = NULL
where `msn` = '$msn' ;

")->execute(); //update master table

                        Yii::$app->db->createCommand(/** @lang text */
                            "update
                        `accurate_mam`.`log_disconnected`
                         set

                        `disconnected_at` = '$disconnected_at'
                         where `msn` = '$msn'")->execute();// update log disconnected table
                    } catch (Exception $e) {
                    } // updating the master table
                }

            }

        }
    }
    }
</code>```

What I want to do?

On button click, I want to show a spinner/loader that will start when the request is made and stop when the request is completed.

Note

I don't want to add a default time for spinner/loader. It should starts when the request is sent and stop when it's completed i.e. when the process is completed

I have searched many articles but can't find the complete implementation details.

Update 1

I have found a solution here and tried to implement it by doing the following

```<code><style>
#loader {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    background: rgba(0, 0, 0, 0.05) url("<?php \yii\helpers\Url::to('@web/images/25.png', true);  ?>") no-repeat center center;
    z-index: 10000;
}
</style>
</code>```

JS

```<code>$(document).ready(function () {

      $('form').submit(function(e) {
       e.preventDefault();

        spinner.show();
        $.ajax({
            url:'$url',
            data: $(this).serialize()
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            spinner.hide();
          //alert(resp.status);
        });

  });

});
</code>```

Problem Facing

Now I am able to see a spinner but the process is not completed. After further debugging, I observed that I am getting bool(false) in var_dump(isset($_POST['dco']));
        die(); while submitting the request

Update 2

I have found one more solution and tried to do the same

Updating both buttons

```<code><?= Html::submitButton(Yii::t('app', '<i class="fa fa-times"></i> Disconnect'), ['class' => 'btn red', 'name' => 'dco', 'value' => '0']) ?>
<?= Html::submitButton(Yii::t('app', '<i class="fa fa-check"></i> Connect'), ['class' => 'btn blue', 'name' => 'rco', 'value' => '1']) ?>
</code>```

JS

```<code> $('form').submit(function(e) {
       e.preventDefault();
        var strValue = "";
    $('input[name="selection[]"]:checked').each(function() {

    if(strValue!=="")
        {
        strValue = strValue + " , " + this.value;

        }
    else
       strValue = this.value;

});

        spinner.show();
        $.ajax({
            url:'$url',
            data: {data :strValue},
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            spinner.hide();
          //alert(resp.status);
        });

  });
</code>```

Controller

```<code>if (Yii::$app->request->post()) {

   if (Yii::$app->request->post('dco') == 0) {
    //Code for value 0
 }

   if (Yii::$app->request->post('rco') == 1) {
    //Code for value 1
  }

 }
</code>```

Problem Facing

When I click any of the two buttons it always hit the dco check i.e. if (Yii::$app->request->post('dco') == 0) it's not hitting the rco condition

There must be something that I am missing.

Any help would be highly appreciated.

答:
My Understanding

Looking at your current implementation you are using normal form submission although wrapped inside the Pjax container but you haven't enabled form submission via Pjax for your form and want to show a spinner or loader that would show until the request to the action where you are sending the curl request to some API completes.

Limitations

Something you need to understand is that if you are making normal form submission and trying to show a spinner for some process that you are doing inside your controller/action won't work as the view is not rendered yet and your spinner related any javascript or CSS isn't rendered in the browser yet and won't show.

Solution

On the other hand, if you use ajax or pjax for your form submissions as you already have wrapped your form inside the Pjax container you can use the Pjax events with enabling a spinner from the AdminBSBTheme which uses CSS only to show the spinner.

That won't just work for your ajax forms but you can use it overall the site for normal page loads leaving a nice effect. It automatically hides once the DOM is ready, in case of slow internet connections it makes things look more aligned.

Enable Pjax on Form

You have to add data-pjax="1" attribute in the form tag to enable Pjax form submission it won't work by default, so go ahead and change the following line to enable Pjax submission for the form.

```<code><?php echo Html::beginForm(['process'],'post', ['data' => ['pjax' => 1]);?>
</code>```

Enable Timeout for Pjax

Then you need to specify the timeout parameter property if your curl request takes a bit of time it's better to add high value for the timeout property so that it won't throw loading request failed or timeout error and waits for the response. Let us give it 10 seconds so if the request takes up to 10 secs to complete it should keep waiting otherwise continue if the response received

```<code><?php Pjax::begin(['timeout'=>10000]); ?>
</code>```

Spinner Integration

Now comes the part of the spinner integration you can see it running in the demo below it is pure CSS based.

Copy the Html from the demo below in your views\layouts\main.php file after $this->beginBody() and
add the css inside your site.css file or, create a separate file and include in the AppAssets.php file.

/ Page Loader ================================= /

.page-loader-wrapper {
  z-index: 99999999;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 100%;
  height: 100%;
  background: #eee;
  overflow: hidden;
  text-align: center;
}

.page-loader-wrapper p {
  font-size: 13px;
  margin-top: 10px;
  font-weight: bold;
  color: #444;
}

.page-loader-wrapper .loader {
  position: relative;
  top: calc(50% - 30px);
}

/ Preloaders ================================== /

.md-preloader .pl-red {
  stroke: #F44336;
}

.md-preloader .pl-pink {
  stroke: #E91E63;
}

.md-preloader .pl-purple {
  stroke: #9C27B0;
}

.md-preloader .pl-deep-purple {
  stroke: #673AB7;
}

.md-preloader .pl-indigo {
  stroke: #3F51B5;
}

.md-preloader .pl-blue {
  stroke: #2196F3;
}

.md-preloader .pl-light-blue {
  stroke: #03A9F4;
}

.md-preloader .pl-cyan {
  stroke: #00BCD4;
}

.md-preloader .pl-teal {
  stroke: #009688;
}

.md-preloader .pl-green {
  stroke: #4CAF50;
}

.md-preloader .pl-light-green {
  stroke: #8BC34A;
}

.md-preloader .pl-lime {
  stroke: #CDDC39;
}

.md-preloader .pl-yellow {
  stroke: #ffe821;
}

.md-preloader .pl-amber {
  stroke: #FFC107;
}

.md-preloader .pl-orange {
  stroke: #FF9800;
}

.md-preloader .pl-deep-orange {
  stroke: #FF5722;
}

.md-preloader .pl-brown {
  stroke: #795548;
}

.md-preloader .pl-grey {
  stroke: #9E9E9E;
}

.md-preloader .pl-blue-grey {
  stroke: #607D8B;
}

.md-preloader .pl-black {
  stroke: #000000;
}

.md-preloader .pl-white {
  stroke: #ffffff;
}

.preloader {
  display: inline-block;
  position: relative;
  width: 50px;
  height: 50px;
  -webkit-animation: container-rotate 1568ms linear infinite;
  -moz-animation: container-rotate 1568ms linear infinite;
  -o-animation: container-rotate 1568ms linear infinite;
  animation: container-rotate 1568ms linear infinite;
}

.preloader.pl-size-xl {
  width: 75px;
  height: 75px;
}

.preloader.pl-size-l {
  width: 60px;
  height: 60px;
}

.preloader.pl-size-md {
  width: 50px;
  height: 50px;
}

.preloader.pl-size-sm {
  width: 40px;
  height: 40px;
}

.preloader.pl-size-xs {
  width: 25px;
  height: 25px;
}

.spinner-layer {
  position: absolute;
  width: 100%;
  height: 100%;
  border-color: #F44336;
  -ms-opacity: 1;
  opacity: 1;
  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -moz-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -o-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}

.spinner-layer.pl-red {
  border-color: #F44336;
}

.spinner-layer.pl-pink {
  border-color: #E91E63;
}

.spinner-layer.pl-purple {
  border-color: #9C27B0;
}

.spinner-layer.pl-deep-purple {
  border-color: #673AB7;
}

.spinner-layer.pl-indigo {
  border-color: #3F51B5;
}

.spinner-layer.pl-blue {
  border-color: #2196F3;
}

.spinner-layer.pl-light-blue {
  border-color: #03A9F4;
}

.spinner-layer.pl-cyan {
  border-color: #00BCD4;
}

.spinner-layer.pl-teal {
  border-color: #009688;
}

.spinner-layer.pl-green {
  border-color: #4CAF50;
}

.spinner-layer.pl-light-green {
  border-color: #8BC34A;
}

.spinner-layer.pl-lime {
  border-color: #CDDC39;
}

.spinner-layer.pl-yellow {
  border-color: #ffe821;
}

.spinner-layer.pl-amber {
  border-color: #FFC107;
}

.spinner-layer.pl-orange {
  border-color: #FF9800;
}

.spinner-layer.pl-deep-orange {
  border-color: #FF5722;
}

.spinner-layer.pl-brown {
  border-color: #795548;
}

.spinner-layer.pl-grey {
  border-color: #9E9E9E;
}

.spinner-layer.pl-blue-grey {
  border-color: #607D8B;
}

.spinner-layer.pl-black {
  border-color: #000000;
}

.spinner-layer.pl-white {
  border-color: #ffffff;
}

.right {
  float: right !important;
}

.gap-patch {
  position: absolute;
  top: 0;
  left: 45%;
  width: 10%;
  height: 100%;
  overflow: hidden;
  border-color: inherit;
}

.gap-patch.circle {
  width: 1000%;
  left: -450%;
}

.circle-clipper {
  display: inline-block;
  position: relative;
  width: 50%;
  height: 100%;
  overflow: hidden;
  border-color: inherit;
}

.circle-clipper .circle {
  width: 200%;
  height: 100%;
  border-width: 3px;
  border-style: solid;
  border-color: inherit;
  border-bottom-color: transparent !important;
  -ms-border-radius: 50%;
  border-radius: 50%;
  -webkit-animation: none;
  animation: none;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
}

.circle-clipper.left .circle {
  left: 0;
  border-right-color: transparent !important;
  -webkit-transform: rotate(129deg);
  -moz-transform: rotate(129deg);
  -ms-transform: rotate(129deg);
  -o-transform: rotate(129deg);
  transform: rotate(129deg);
  -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -moz-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -o-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}

.circle-clipper.right .circle {
  left: -100%;
  border-left-color: transparent !important;
  -webkit-transform: rotate(-129deg);
  -moz-transform: rotate(-129deg);
  -ms-transform: rotate(-129deg);
  -o-transform: rotate(-129deg);
  transform: rotate(-129deg);
  -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -moz-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -o-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}

@-webkit-keyframes container-rotate {
  to {
    -webkit-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@keyframes container-rotate {
  to {
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@-webkit-keyframes fill-unfill-rotate {
  12.5% {
    -webkit-transform: rotate(135deg);
    transform: rotate(135deg);
  }
  25% {
    -webkit-transform: rotate(270deg);
    transform: rotate(270deg);
  }
  37.5% {
    -webkit-transform: rotate(405deg);
    transform: rotate(405deg);
  }
  50% {
    -webkit-transform: rotate(540deg);
    transform: rotate(540deg);
  }
  62.5% {
    -webkit-transform: rotate(675deg);
    transform: rotate(675deg);
  }
  75% {
    -webkit-transform: rotate(810deg);
    transform: rotate(810deg);
  }
  87.5% {
    -webkit-transform: rotate(945deg);
    transform: rotate(945deg);
  }
  to {
    -webkit-transform: rotate(1080deg);
    transform: rotate(1080deg);
  }
}

@keyframes fill-unfill-rotate {
  12.5% {
    transform: rotate(135deg);
  }
  25% {
    transform: rotate(270deg);
  }
  37.5% {
    transform: rotate(405deg);
  }
  50% {
    transform: rotate(540deg);
  }
  62.5% {
    transform: rotate(675deg);
  }
  75% {
    transform: rotate(810deg);
  }
  87.5% {
    transform: rotate(945deg);
  }
  to {
    transform: rotate(1080deg);
  }
}

@-webkit-keyframes left-spin {
  from {
    -webkit-transform: rotate(130deg);
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    transform: rotate(130deg);
  }
  50% {
    -webkit-transform: rotate(-5deg);
    -moz-transform: rotate(-5deg);
    -ms-transform: rotate(-5deg);
    -o-transform: rotate(-5deg);
    transform: rotate(-5deg);
  }
  to {
    -webkit-transform: rotate(130deg);
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    transform: rotate(130deg);
  }
}

@keyframes left-spin {
  from {
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    -webkit-transform: rotate(130deg);
    transform: rotate(130deg);
  }
  50% {
    -moz-transform: rotate(-5deg);
    -ms-transform: rotate(-5deg);
    -o-transform: rotate(-5deg);
    -webkit-transform: rotate(-5deg);
    transform: rotate(-5deg);
  }
  to {
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    -webkit-transform: rotate(130deg);
    transform: rotate(130deg);
  }
}

@-webkit-keyframes right-spin {
  from {
    -webkit-transform: rotate(-130deg);
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
  50% {
    -webkit-transform: rotate(5deg);
    -moz-transform: rotate(5deg);
    -ms-transform: rotate(5deg);
    -o-transform: rotate(5deg);
    transform: rotate(5deg);
  }
  to {
    -webkit-transform: rotate(-130deg);
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
}

@-moz-keyframes right-spin {
  from {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
  50% {
    -moz-transform: rotate(5deg);
    -ms-transform: rotate(5deg);
    -o-transform: rotate(5deg);
    -webkit-transform: rotate(5deg);
    transform: rotate(5deg);
  }
  to {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
}

@keyframes right-spin {
  from {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
  50% {
    -moz-transform: rotate(5deg);
    -ms-transform: rotate(5deg);
    -o-transform: rotate(5deg);
    -webkit-transform: rotate(5deg);
    transform: rotate(5deg);
  }
  to {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
}```
<pre class="snippet-code-html lang-html prettyprint-override"><code><!-- Page Loader -->
<div class="page-loader-wrapper">
  <div class="loader">
    <div class="preloader">
      <div class="spinner-layer pl-red">
        <div class="circle-clipper left">
          <div class="circle"></div>
        </div>
        <div class="circle-clipper right">
          <div class="circle"></div>
        </div>
      </div>
    </div>
    <p>Please wait...</p>
  </div>
</div>
<!-- #END# Page Loader --></code>```

Then add the following on top of the views\layouts\main.php file

```<code>$js = <<<JS
     setTimeout(function () {
        $('.page-loader-wrapper').fadeOut();
    }, 50);
JS;
$this->registerJs($js, \yii\web\View::POS_READY);
</code>```

Note: Ideally you can wrap everything related to the spinner in a separate asset file and call it in the main layout, but I will leave that part to you.

Until this point, if you try to click any of the links on your application you will see the spinner working for your normal page loads like the home page about or any other.

Enabling for Pjax requests

We need to enable it for the Pjax too, so go to your view file and add the following piece of javascript on top which uses the pjax:beforeSend event to show the spinner

```<code>$js = <<<JS
$(document).on("pjax:beforeSend",function(){
    $('.page-loader-wrapper').fadeIn();
});
JS;
$this->registerJs($js, \yii\web\View::POS_READY);
</code>```

You don't need to worry about closing the spinner it would be closed once the page is loaded after the curl response via our main Layout code.

Update

The reason why the submit button value isn't submitted when using pjax is a known issue, see here. What you can do about it is to add 2 hidden fields for the 2 submit buttons and updated the value of the respective hidden field associated with the button when clicked and submit them along with the form and check the value for those new hidden inputs, so that you can identify which button was clicked.

So, first we will add hidden inputs, note the options used for the original submitButton rco and dco the id should be there.

```<code><?php echo Html::beginForm(['about'], 'post', ['data' => ['pjax' => 1]]); ?>
<?php echo Html::submitButton('Disconnect', ['class' => 'btn btn-success', 'id' => 'dco']) ?>
<?php echo Html::submitButton('Connect', ['class' => 'btn btn-info', 'id' => 'rco']) ?>
<?php echo Html::hiddenInput('dco', '', ['id' => 'dco_input']); ?>
<?php echo Html::hiddenInput('rco', '', ['id' => 'rco_input']); ?>
</code>```

Now we will replace the javascript that we added before in our view wiith the following

```<code>$js = <<<JS

    function loader(){
        $('.page-loader-wrapper').fadeOut();
        //reset the input values
        $("#dco_input,#rco_input").val('');

        //assign respective values to the associated hidden fields
        $("#rco,#dco").on('click',function(e){
            var buttonId = $(this).attr('id');
            $("#"+buttonId+"_input").val(1);
        });
    }

    $(document).on("pjax:beforeSend",function(e){
        $('.page-loader-wrapper').fadeIn();
    }).on("pjax:end",function(){
        loader();
    });
    loader();
JS;
    $this->registerJs($js, View::POS_READY);
?>
</code>```

Now whenever we will click the dco submit button the hidden input dco_input will be populated with 1 and when the rco submit button is clicked rco_input will be updated with value 1 before the for is submitted and you will receive the value in post for both the buttons.

The last thing you need to change are the checks in your controller/action  process(), which are checking isset($_POST['dco']) and isset($_POST['rco']) which will not work and need to be updated

```<code>if(isset($_POST['dco']))
</code>```

with

```<code>if(isset($_POST["dco"]) && $_POST['dco']==1)
</code>```

and

```<code>if(isset($_POST['rco']))
</code>```

with

```<code>if(isset($_POST['rco']) && $_POST['rco']==1)
</code>```

Body:
I have a view in which I have two buttons. While clicking on any of the two buttons, a certain request is made.

View

<?php Pjax::begin(); ?>
        <?=Html::beginForm(['process'],'post');?>
        <?=Html::submitButton('Disconnect', ['id'=>'d','name'=>'dco','class' => 'btn btn-primary']);?>
        <?=Html::submitButton('Connect', ['id'=>'r','name'=>'rco','class' => 'btn btn-info']);?>

        <?= GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [

        ['class' => 'yii\grid\CheckboxColumn', 'checkboxOptions' => function($d) {
            return ['value' => $d['msn']];
        }],

        'customer_id',
       // 'dept_code:ntext',
        'dept_name:ntext',
        'sub_div_name',
        [
            'label' => 'Sub Division Name',
            'value' => function ($d) {
                return $d->subdiv->name;
            },
            'filter' => Html::activeDropDownList($searchModel, 'sub_div_code', \common\models\SurveyHescoSubdivision::toArrayList(), ['prompt' => "Sub-Div", 'class' => 'form-control']),

        ],
        'division_name',
        'allowed_units',
        'msn',

        'units_consumed',
        [
            'label' => 'Disconnected',
            'attribute' => 'disconnected',
            'format'=>'raw',
            'contentOptions' => ['style'=>'text-align:center'],
            'value' => function($model){
                return $model->disconnected == 1 ? '' : '';
            },
            'filter' => Html::activeDropDownList($searchModel, 'disconnected', [''=>'All','1'=>'Yes','0'=>'No'], ['class' => 'form-control']),
        ],

        'active_energy_total_m',

        ['class' => 'yii\grid\ActionColumn'],
    ],
]); ?>

        <?= Html::endForm();?>
        <?php Pjax::end(); ?>

Controller

 public function actionProcess()
{

 $soapUrl = "http://ip:port/HES/services/Request";
    $userName = "user";
    $password = "123456";

 if (isset($_POST['dco']))
    {
        if(Yii::$app->request->isPost)
        {
            $data = Yii::$app->request->post('selection'); //checkbox (array)

            foreach($data as $value)
            {

                $msn = $value;

                $xml_post_string = /** @lang text */
                    '//soap request';

                $headers = array(
                    "Content-type: text/xml;charset=\"utf-8\"",
                    "Accept: text/xml",
                    "Cache-Control: no-cache",
                    "Pragma: no-cache",
                    "Content-length: ".strlen($xml_post_string),
                ); //SOAPAction: your op URL

                $url = $soapUrl;

                // PHP cURL  for https connection

                $ch = curl_init();
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
                curl_setopt($ch, CURLOPT_URL, $url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

                curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
                curl_setopt($ch, CURLOPT_TIMEOUT, 10);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_post_string); // the SOAP request
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

                // converting
                $response = curl_exec($ch);
                curl_close($ch);
                $domd=new DOMDocument();

                var_dump($response);
                if(!$domd->loadXML($response)){
                    throw new \RuntimeException("failed to parse XML!");
                }
                $inner_xml=$domd->getElementsByTagName("return")->item(0)->textContent;
                if(!($domd2=@DOMDocument::loadXML($inner_xml))){
                    throw new \RuntimeException("failed to parse inner_xml!");
                }
                $AsyncReplyFlag=$domd2->getElementsByTagName("AsyncReplyFlag")->item(0)->textContent;

                if ($AsyncReplyFlag =='true')
                {
                    $ds = 1;

                    $disconnected_at = date('Y-m-d H:i:s');

                    try {
                        Yii::$app->db->createCommand(/** @lang text */
                            "update
  `accurate_mam`.`daily_log`
set
  `disconnected` = '$ds',
  `diconnected_at` = '$disconnected_at',
  `reconnected_at` = NULL
where `msn` = '$msn' ;

")->execute(); //update master table

                        Yii::$app->db->createCommand(/** @lang text */
                            "update
                        `accurate_mam`.`log_disconnected`
                         set

                        `disconnected_at` = '$disconnected_at'
                         where `msn` = '$msn'")->execute();// update log disconnected table
                    } catch (Exception $e) {
                    } // updating the master table
                }

            }

        }
    }
    }

What I want to do?

On button click, I want to show a spinner/loader that will start when the request is made and stop when the request is completed.

Note

I don't want to add a default time for spinner/loader. It should starts when the request is sent and stop when it's completed i.e. when the process is completed

I have searched many articles but can't find the complete implementation details.

Update 1

I have found a solution here and tried to implement it by doing the following

JS

$(document).ready(function () {

      $('form').submit(function(e) {
       e.preventDefault();

        spinner.show();
        $.ajax({
            url:'$url',
            data: $(this).serialize()
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            spinner.hide();
          //alert(resp.status);
        });

  });

});

Problem Facing

Now I am able to see a spinner but the process is not completed. After further debugging, I observed that I am getting bool(false) in var_dump(isset($_POST['dco']));
        die(); while submitting the request

Update 2

I have found one more solution and tried to do the same

Updating both buttons

<?= Html::submitButton(Yii::t('app', ' Disconnect'), ['class' => 'btn red', 'name' => 'dco', 'value' => '0']) ?>
<?= Html::submitButton(Yii::t('app', ' Connect'), ['class' => 'btn blue', 'name' => 'rco', 'value' => '1']) ?>

JS

 $('form').submit(function(e) {
       e.preventDefault();
        var strValue = "";
    $('input[name="selection[]"]:checked').each(function() {

    if(strValue!=="")
        {
        strValue = strValue + " , " + this.value;

        }
    else
       strValue = this.value;

});

        spinner.show();
        $.ajax({
            url:'$url',
            data: {data :strValue},
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            spinner.hide();
          //alert(resp.status);
        });

  });

Controller

if (Yii::$app->request->post()) {

   if (Yii::$app->request->post('dco') == 0) {
    //Code for value 0
 }

   if (Yii::$app->request->post('rco') == 1) {
    //Code for value 1
  }

 }

Problem Facing

When I click any of the two buttons it always hit the dco check i.e. if (Yii::$app->request->post('dco') == 0) it's not hitting the rco condition

There must be something that I am missing.

Any help would be highly appreciated.

Body:
My Understanding

Looking at your current implementation you are using normal form submission although wrapped inside the Pjax container but you haven't enabled form submission via Pjax for your form and want to show a spinner or loader that would show until the request to the action where you are sending the curl request to some API completes.

Limitations

Something you need to understand is that if you are making normal form submission and trying to show a spinner for some process that you are doing inside your controller/action won't work as the view is not rendered yet and your spinner related any javascript or CSS isn't rendered in the browser yet and won't show.

Solution

On the other hand, if you use ajax or pjax for your form submissions as you already have wrapped your form inside the Pjax container you can use the Pjax events with enabling a spinner from the AdminBSBTheme which uses CSS only to show the spinner.

That won't just work for your ajax forms but you can use it overall the site for normal page loads leaving a nice effect. It automatically hides once the DOM is ready, in case of slow internet connections it makes things look more aligned.

Enable Pjax on Form

You have to add data-pjax="1" attribute in the form tag to enable Pjax form submission it won't work by default, so go ahead and change the following line to enable Pjax submission for the form.

<?php echo Html::beginForm(['process'],'post', ['data' => ['pjax' => 1]);?>

Enable Timeout for Pjax

Then you need to specify the timeout parameter property if your curl request takes a bit of time it's better to add high value for the timeout property so that it won't throw loading request failed or timeout error and waits for the response. Let us give it 10 seconds so if the request takes up to 10 secs to complete it should keep waiting otherwise continue if the response received

<?php Pjax::begin(['timeout'=>10000]); ?>

Spinner Integration

Now comes the part of the spinner integration you can see it running in the demo below it is pure CSS based.

Copy the Html from the demo below in your views\layouts\main.php file after $this->beginBody() and
add the css inside your site.css file or, create a separate file and include in the AppAssets.php file.

/ Page Loader ================================= /

.page-loader-wrapper {
  z-index: 99999999;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 100%;
  height: 100%;
  background: #eee;
  overflow: hidden;
  text-align: center;
}

.page-loader-wrapper p {
  font-size: 13px;
  margin-top: 10px;
  font-weight: bold;
  color: #444;
}

.page-loader-wrapper .loader {
  position: relative;
  top: calc(50% - 30px);
}

/ Preloaders ================================== /

.md-preloader .pl-red {
  stroke: #F44336;
}

.md-preloader .pl-pink {
  stroke: #E91E63;
}

.md-preloader .pl-purple {
  stroke: #9C27B0;
}

.md-preloader .pl-deep-purple {
  stroke: #673AB7;
}

.md-preloader .pl-indigo {
  stroke: #3F51B5;
}

.md-preloader .pl-blue {
  stroke: #2196F3;
}

.md-preloader .pl-light-blue {
  stroke: #03A9F4;
}

.md-preloader .pl-cyan {
  stroke: #00BCD4;
}

.md-preloader .pl-teal {
  stroke: #009688;
}

.md-preloader .pl-green {
  stroke: #4CAF50;
}

.md-preloader .pl-light-green {
  stroke: #8BC34A;
}

.md-preloader .pl-lime {
  stroke: #CDDC39;
}

.md-preloader .pl-yellow {
  stroke: #ffe821;
}

.md-preloader .pl-amber {
  stroke: #FFC107;
}

.md-preloader .pl-orange {
  stroke: #FF9800;
}

.md-preloader .pl-deep-orange {
  stroke: #FF5722;
}

.md-preloader .pl-brown {
  stroke: #795548;
}

.md-preloader .pl-grey {
  stroke: #9E9E9E;
}

.md-preloader .pl-blue-grey {
  stroke: #607D8B;
}

.md-preloader .pl-black {
  stroke: #000000;
}

.md-preloader .pl-white {
  stroke: #ffffff;
}

.preloader {
  display: inline-block;
  position: relative;
  width: 50px;
  height: 50px;
  -webkit-animation: container-rotate 1568ms linear infinite;
  -moz-animation: container-rotate 1568ms linear infinite;
  -o-animation: container-rotate 1568ms linear infinite;
  animation: container-rotate 1568ms linear infinite;
}

.preloader.pl-size-xl {
  width: 75px;
  height: 75px;
}

.preloader.pl-size-l {
  width: 60px;
  height: 60px;
}

.preloader.pl-size-md {
  width: 50px;
  height: 50px;
}

.preloader.pl-size-sm {
  width: 40px;
  height: 40px;
}

.preloader.pl-size-xs {
  width: 25px;
  height: 25px;
}

.spinner-layer {
  position: absolute;
  width: 100%;
  height: 100%;
  border-color: #F44336;
  -ms-opacity: 1;
  opacity: 1;
  -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -moz-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -o-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}

.spinner-layer.pl-red {
  border-color: #F44336;
}

.spinner-layer.pl-pink {
  border-color: #E91E63;
}

.spinner-layer.pl-purple {
  border-color: #9C27B0;
}

.spinner-layer.pl-deep-purple {
  border-color: #673AB7;
}

.spinner-layer.pl-indigo {
  border-color: #3F51B5;
}

.spinner-layer.pl-blue {
  border-color: #2196F3;
}

.spinner-layer.pl-light-blue {
  border-color: #03A9F4;
}

.spinner-layer.pl-cyan {
  border-color: #00BCD4;
}

.spinner-layer.pl-teal {
  border-color: #009688;
}

.spinner-layer.pl-green {
  border-color: #4CAF50;
}

.spinner-layer.pl-light-green {
  border-color: #8BC34A;
}

.spinner-layer.pl-lime {
  border-color: #CDDC39;
}

.spinner-layer.pl-yellow {
  border-color: #ffe821;
}

.spinner-layer.pl-amber {
  border-color: #FFC107;
}

.spinner-layer.pl-orange {
  border-color: #FF9800;
}

.spinner-layer.pl-deep-orange {
  border-color: #FF5722;
}

.spinner-layer.pl-brown {
  border-color: #795548;
}

.spinner-layer.pl-grey {
  border-color: #9E9E9E;
}

.spinner-layer.pl-blue-grey {
  border-color: #607D8B;
}

.spinner-layer.pl-black {
  border-color: #000000;
}

.spinner-layer.pl-white {
  border-color: #ffffff;
}

.right {
  float: right !important;
}

.gap-patch {
  position: absolute;
  top: 0;
  left: 45%;
  width: 10%;
  height: 100%;
  overflow: hidden;
  border-color: inherit;
}

.gap-patch.circle {
  width: 1000%;
  left: -450%;
}

.circle-clipper {
  display: inline-block;
  position: relative;
  width: 50%;
  height: 100%;
  overflow: hidden;
  border-color: inherit;
}

.circle-clipper .circle {
  width: 200%;
  height: 100%;
  border-width: 3px;
  border-style: solid;
  border-color: inherit;
  border-bottom-color: transparent !important;
  -ms-border-radius: 50%;
  border-radius: 50%;
  -webkit-animation: none;
  animation: none;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
}

.circle-clipper.left .circle {
  left: 0;
  border-right-color: transparent !important;
  -webkit-transform: rotate(129deg);
  -moz-transform: rotate(129deg);
  -ms-transform: rotate(129deg);
  -o-transform: rotate(129deg);
  transform: rotate(129deg);
  -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -moz-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -o-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}

.circle-clipper.right .circle {
  left: -100%;
  border-left-color: transparent !important;
  -webkit-transform: rotate(-129deg);
  -moz-transform: rotate(-129deg);
  -ms-transform: rotate(-129deg);
  -o-transform: rotate(-129deg);
  transform: rotate(-129deg);
  -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -moz-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  -o-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
  animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}

@-webkit-keyframes container-rotate {
  to {
    -webkit-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@keyframes container-rotate {
  to {
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@-webkit-keyframes fill-unfill-rotate {
  12.5% {
    -webkit-transform: rotate(135deg);
    transform: rotate(135deg);
  }
  25% {
    -webkit-transform: rotate(270deg);
    transform: rotate(270deg);
  }
  37.5% {
    -webkit-transform: rotate(405deg);
    transform: rotate(405deg);
  }
  50% {
    -webkit-transform: rotate(540deg);
    transform: rotate(540deg);
  }
  62.5% {
    -webkit-transform: rotate(675deg);
    transform: rotate(675deg);
  }
  75% {
    -webkit-transform: rotate(810deg);
    transform: rotate(810deg);
  }
  87.5% {
    -webkit-transform: rotate(945deg);
    transform: rotate(945deg);
  }
  to {
    -webkit-transform: rotate(1080deg);
    transform: rotate(1080deg);
  }
}

@keyframes fill-unfill-rotate {
  12.5% {
    transform: rotate(135deg);
  }
  25% {
    transform: rotate(270deg);
  }
  37.5% {
    transform: rotate(405deg);
  }
  50% {
    transform: rotate(540deg);
  }
  62.5% {
    transform: rotate(675deg);
  }
  75% {
    transform: rotate(810deg);
  }
  87.5% {
    transform: rotate(945deg);
  }
  to {
    transform: rotate(1080deg);
  }
}

@-webkit-keyframes left-spin {
  from {
    -webkit-transform: rotate(130deg);
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    transform: rotate(130deg);
  }
  50% {
    -webkit-transform: rotate(-5deg);
    -moz-transform: rotate(-5deg);
    -ms-transform: rotate(-5deg);
    -o-transform: rotate(-5deg);
    transform: rotate(-5deg);
  }
  to {
    -webkit-transform: rotate(130deg);
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    transform: rotate(130deg);
  }
}

@keyframes left-spin {
  from {
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    -webkit-transform: rotate(130deg);
    transform: rotate(130deg);
  }
  50% {
    -moz-transform: rotate(-5deg);
    -ms-transform: rotate(-5deg);
    -o-transform: rotate(-5deg);
    -webkit-transform: rotate(-5deg);
    transform: rotate(-5deg);
  }
  to {
    -moz-transform: rotate(130deg);
    -ms-transform: rotate(130deg);
    -o-transform: rotate(130deg);
    -webkit-transform: rotate(130deg);
    transform: rotate(130deg);
  }
}

@-webkit-keyframes right-spin {
  from {
    -webkit-transform: rotate(-130deg);
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
  50% {
    -webkit-transform: rotate(5deg);
    -moz-transform: rotate(5deg);
    -ms-transform: rotate(5deg);
    -o-transform: rotate(5deg);
    transform: rotate(5deg);
  }
  to {
    -webkit-transform: rotate(-130deg);
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
}

@-moz-keyframes right-spin {
  from {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
  50% {
    -moz-transform: rotate(5deg);
    -ms-transform: rotate(5deg);
    -o-transform: rotate(5deg);
    -webkit-transform: rotate(5deg);
    transform: rotate(5deg);
  }
  to {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
}

@keyframes right-spin {
  from {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
  50% {
    -moz-transform: rotate(5deg);
    -ms-transform: rotate(5deg);
    -o-transform: rotate(5deg);
    -webkit-transform: rotate(5deg);
    transform: rotate(5deg);
  }
  to {
    -moz-transform: rotate(-130deg);
    -ms-transform: rotate(-130deg);
    -o-transform: rotate(-130deg);
    -webkit-transform: rotate(-130deg);
    transform: rotate(-130deg);
  }
}

<!-- Page Loader -->
<div class="page-loader-wrapper">
  <div class="loader">
    <div class="preloader">
      <div class="spinner-layer pl-red">
        <div class="circle-clipper left">
          <div class="circle"></div>
        </div>
        <div class="circle-clipper right">
          <div class="circle"></div>
        </div>
      </div>
    </div>
    <p>Please wait...</p>
  </div>
</div>
<!-- #END# Page Loader -->

Then add the following on top of the views\layouts\main.php file

$js = <<<JS
     setTimeout(function () {
        $('.page-loader-wrapper').fadeOut();
    }, 50);
JS;
$this->registerJs($js, \yii\web\View::POS_READY);

Note: Ideally you can wrap everything related to the spinner in a separate asset file and call it in the main layout, but I will leave that part to you.

Until this point, if you try to click any of the links on your application you will see the spinner working for your normal page loads like the home page about or any other.

Enabling for Pjax requests

We need to enable it for the Pjax too, so go to your view file and add the following piece of javascript on top which uses the pjax:beforeSend event to show the spinner

$js = <<<JS
$(document).on("pjax:beforeSend",function(){
    $('.page-loader-wrapper').fadeIn();
});
JS;
$this->registerJs($js, \yii\web\View::POS_READY);

You don't need to worry about closing the spinner it would be closed once the page is loaded after the curl response via our main Layout code.

Update

The reason why the submit button value isn't submitted when using pjax is a known issue, see here. What you can do about it is to add 2 hidden fields for the 2 submit buttons and updated the value of the respective hidden field associated with the button when clicked and submit them along with the form and check the value for those new hidden inputs, so that you can identify which button was clicked.

So, first we will add hidden inputs, note the options used for the original submitButton rco and dco the id should be there.

<?php echo Html::beginForm(['about'], 'post', ['data' => ['pjax' => 1]]); ?>
<?php echo Html::submitButton('Disconnect', ['class' => 'btn btn-success', 'id' => 'dco']) ?>
<?php echo Html::submitButton('Connect', ['class' => 'btn btn-info', 'id' => 'rco']) ?>
<?php echo Html::hiddenInput('dco', '', ['id' => 'dco_input']); ?>
<?php echo Html::hiddenInput('rco', '', ['id' => 'rco_input']); ?>

Now we will replace the javascript that we added before in our view wiith the following

$js = <<<JS

    function loader(){
        $('.page-loader-wrapper').fadeOut();
        //reset the input values
        $("#dco_input,#rco_input").val('');

        //assign respective values to the associated hidden fields
        $("#rco,#dco").on('click',function(e){
            var buttonId = $(this).attr('id');
            $("#"+buttonId+"_input").val(1);
        });
    }

    $(document).on("pjax:beforeSend",function(e){
        $('.page-loader-wrapper').fadeIn();
    }).on("pjax:end",function(){
        loader();
    });
    loader();
JS;
    $this->registerJs($js, View::POS_READY);
?>

Now whenever we will click the dco submit button the hidden input dco_input will be populated with 1 and when the rco submit button is clicked rco_input will be updated with value 1 before the for is submitted and you will receive the value in post for both the buttons.

The last thing you need to change are the checks in your controller/action  process(), which are checking isset($_POST['dco']) and isset($_POST['rco']) which will not work and need to be updated

if(isset($_POST['dco']))

with

if(isset($_POST["dco"]) && $_POST['dco']==1)

and

if(isset($_POST['rco']))

with

if(isset($_POST['rco']) && $_POST['rco']==1)


来源: stackexchange

问:
Flutter how to detect PageView onTapUp
I used PageView to write a list in the vertical direction. Now when I slide the list up or down (my finger doesn't leave the screen), onPageChanged will execute. Is there a way to detect that the onPageChanged method will only be executed when the finger leaves the screen.Part of the code below：

    SizedBox(
            width: 200,
            height: 200,
            child: PageView(
                controller: pageController,
                scrollDirection: Axis.vertical,
                onPageChanged: (index) {
                  setState(() {
                    pageIndex = index;
                  });
                },
                children: _buildCardList()),
          )

```

like below:

答:
you can do it like that;</p>

```<code>   int currentPage;
    .
    .
    .
    onPageChanged(int page){
      if(currentPage != page){
        setState((){
          currentPage = page;
        });
      }
    }
</code>```

ContentLicense: CC BY-SA 4.0

Title: Flutter how to detect PageView onTapUp

Body:
I used PageView to write a list in the vertical direction. Now when I slide the list up or down (my finger doesn't leave the screen), onPageChanged will execute. Is there a way to detect that the onPageChanged method will only be executed when the finger leaves the screen.Part of the code below：

    SizedBox(
            width: 200,
            height: 200,
            child: PageView(
                controller: pageController,
                scrollDirection: Axis.vertical,
                onPageChanged: (index) {
                  setState(() {
                    pageIndex = index;
                  });
                },
                children: _buildCardList()),
          )

like below:

Body:
you can do it like that;

   int currentPage;
    .
    .
    .
    onPageChanged(int page){
      if(currentPage != page){
        setState((){
          currentPage = page;
        });
      }
    }


问:
Angular 7 Table with Pagination, Sort, and Filter
Hi does anyone knows how to make a table with pagination, sort and filter in angular. I'm using Creative Tim's Argon Dashboard for Angular and I'm confused how I am going to integrate it to my project

Here's the documentation of the template click me

Here's a sample code of the table

```<code><div class="table-responsive">
    <table class="table align-items-center table-flush">
      <thead class="thead-light">
        <tr>
          <th scope="col">Project</th>
          <th scope="col">Budget</th>
          <th scope="col">Status</th>
          <th scope="col">Users</th>
          <th scope="col">Completion</th>
          <th scope="col"></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">
            <div class="media align-items-center">
              <a href="#" class="avatar rounded-circle mr-3">
                <img alt="Image placeholder" src="assets/img/theme/bootstrap.jpg">
              </a>
              <div class="media-body">
                <span class="mb-0 text-sm">Argon Design System</span>
              </div>
            </div>
          </th>
          <td>
            $2,500 USD
          </td>
          <td>
            <span class="badge badge-dot mr-4">
              <i class="bg-warning"></i> pending
            </span>
          </td>
          <td>
            <div class="avatar-group">
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Ryan Tompson">
                <img alt="Image placeholder" src="assets/img/theme/team-1-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Romina Hadid">
                <img alt="Image placeholder" src="assets/img/theme/team-2-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Alexander Smith">
                <img alt="Image placeholder" src="assets/img/theme/team-3-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Jessica Doe">
                <img alt="Image placeholder" src="assets/img/theme/team-4-800x800.jpg" class="rounded-circle">
              </a>
            </div>
          </td>
          <td>
            <div class="d-flex align-items-center">
              <span class="mr-2">60%</span>
              <div>
                <div class="progress">
                  <div class="progress-bar bg-warning" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 60%;"></div>
                </div>
              </div>
            </div>
          </td>
          <td class="text-right">
            <div ngbDropdown placement="bottom-right">
              <a class="btn btn-sm btn-icon-only text-light"  ngbDropdownToggle>
                <i class="fas fa-ellipsis-v"></i>
              </a>
              <div ngbDropdownMenu class=" dropdown-menu-right dropdown-menu-arrow">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
        </tr>
        <tr>
          <th scope="row">
            <div class="media align-items-center">
              <a href="#" class="avatar rounded-circle mr-3">
                <img alt="Image placeholder" src="assets/img/theme/angular.jpg">
              </a>
              <div class="media-body">
                <span class="mb-0 text-sm">Angular Now UI Kit PRO</span>
              </div>
            </div>
          </th>
          <td>
            $1,800 USD
          </td>
          <td>
            <span class="badge badge-dot">
              <i class="bg-success"></i> completed
            </span>
          </td>
          <td>
            <div class="avatar-group">
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Ryan Tompson">
                <img alt="Image placeholder" src="assets/img/theme/team-1-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Romina Hadid">
                <img alt="Image placeholder" src="assets/img/theme/team-2-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Alexander Smith">
                <img alt="Image placeholder" src="assets/img/theme/team-3-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Jessica Doe">
                <img alt="Image placeholder" src="assets/img/theme/team-4-800x800.jpg" class="rounded-circle">
              </a>
            </div>
          </td>
          <td>
            <div class="d-flex align-items-center">
              <span class="mr-2">100%</span>
              <div>
                <div class="progress">
                  <div class="progress-bar bg-success" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%;"></div>
                </div>
              </div>
            </div>
          </td>
          <td class="text-right">
            <div ngbDropdown  placement="bottom-right">
              <a class="btn btn-sm btn-icon-only text-light"  ngbDropdownToggle>
                <i class="fas fa-ellipsis-v"></i>
              </a>
              <div ngbDropdownMenu class=" dropdown-menu-right dropdown-menu-arrow">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
        </tr>
        <tr>
          <th scope="row">
            <div class="media align-items-center">
              <a href="#" class="avatar rounded-circle mr-3">
                <img alt="Image placeholder" src="assets/img/theme/sketch.jpg">
              </a>
              <div class="media-body">
                <span class="mb-0 text-sm">Black Dashboard</span>
              </div>
            </div>
          </th>
          <td>
            $3,150 USD
          </td>
          <td>
            <span class="badge badge-dot mr-4">
              <i class="bg-danger"></i> delayed
            </span>
          </td>
          <td>
            <div class="avatar-group">
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Ryan Tompson">
                <img alt="Image placeholder" src="assets/img/theme/team-1-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Romina Hadid">
                <img alt="Image placeholder" src="assets/img/theme/team-2-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Alexander Smith">
                <img alt="Image placeholder" src="assets/img/theme/team-3-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Jessica Doe">
                <img alt="Image placeholder" src="assets/img/theme/team-4-800x800.jpg" class="rounded-circle">
              </a>
            </div>
          </td>
          <td>
            <div class="d-flex align-items-center">
              <span class="mr-2">72%</span>
              <div>
                <div class="progress">
                  <div class="progress-bar bg-danger" role="progressbar" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100" style="width: 72%;"></div>
                </div>
              </div>
            </div>
          </td>
          <td class="text-right">
            <div ngbDropdown  placement="top-right">
              <a class="btn btn-sm btn-icon-only text-light"  ngbDropdownToggle>
                <i class="fas fa-ellipsis-v"></i>
              </a>
              <div ngbDropdownMenu class=" dropdown-menu-right dropdown-menu-arrow">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
        </tr>
        <tr>
          <th scope="row">
            <div class="media align-items-center">
              <a href="#" class="avatar rounded-circle mr-3">
                <img alt="Image placeholder" src="assets/img/theme/react.jpg">
              </a>
              <div class="media-body">
                <span class="mb-0 text-sm">React Material Dashboard</span>
              </div>
            </div>
          </th>
          <td>
            $4,400 USD
          </td>
          <td>
            <span class="badge badge-dot">
              <i class="bg-info"></i> on schedule
            </span>
          </td>
          <td>
            <div class="avatar-group">
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Ryan Tompson">
                <img alt="Image placeholder" src="assets/img/theme/team-1-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Romina Hadid">
                <img alt="Image placeholder" src="assets/img/theme/team-2-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Alexander Smith">
                <img alt="Image placeholder" src="assets/img/theme/team-3-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Jessica Doe">
                <img alt="Image placeholder" src="assets/img/theme/team-4-800x800.jpg" class="rounded-circle">
              </a>
            </div>
          </td>
          <td>
            <div class="d-flex align-items-center">
              <span class="mr-2">90%</span>
              <div>
                <div class="progress">
                  <div class="progress-bar bg-info" role="progressbar" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100" style="width: 90%;"></div>
                </div>
              </div>
            </div>
          </td>
          <td class="text-right">
            <div ngbDropdown   placement="top-right">
              <a class="btn btn-sm btn-icon-only text-light"  ngbDropdownToggle>
                <i class="fas fa-ellipsis-v"></i>
              </a>
              <div ngbDropdownMenu class=" dropdown-menu-right dropdown-menu-arrow">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
        </tr>
        <tr>
          <th scope="row">
            <div class="media align-items-center">
              <a href="#" class="avatar rounded-circle mr-3">
                <img alt="Image placeholder" src="assets/img/theme/vue.jpg">
              </a>
              <div class="media-body">
                <span class="mb-0 text-sm">Vue Paper UI Kit PRO</span>
              </div>
            </div>
          </th>
          <td>
            $2,200 USD
          </td>
          <td>
            <span class="badge badge-dot mr-4">
              <i class="bg-success"></i> completed
            </span>
          </td>
          <td>
            <div class="avatar-group">
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Ryan Tompson">
                <img alt="Image placeholder" src="assets/img/theme/team-1-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Romina Hadid">
                <img alt="Image placeholder" src="assets/img/theme/team-2-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Alexander Smith">
                <img alt="Image placeholder" src="assets/img/theme/team-3-800x800.jpg" class="rounded-circle">
              </a>
              <a href="#" class="avatar avatar-sm" data-toggle="tooltip" data-original-title="Jessica Doe">
                <img alt="Image placeholder" src="assets/img/theme/team-4-800x800.jpg" class="rounded-circle">
              </a>
            </div>
          </td>
          <td>
            <div class="d-flex align-items-center">
              <span class="mr-2">100%</span>
              <div>
                <div class="progress">
                  <div class="progress-bar bg-success" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%;"></div>
                </div>
              </div>
            </div>
          </td>
          <td class="text-right">
            <div ngbDropdown   placement="top-right">
              <a class="btn btn-sm btn-icon-only text-light"  ngbDropdownToggle>
                <i class="fas fa-ellipsis-v"></i>
              </a>
              <div ngbDropdownMenu class=" dropdown-menu-right dropdown-menu-arrow">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</code>```

Here's a sample code of the pagination used

```<code><nav aria-label="...">
<ul class="pagination pagination-sm">
  <li class="page-item disabled">
    <a class="page-link" href="#" tabindex="-1">1</a>
  </li>
  <li class="page-item"><a class="page-link" href="#">2</a></li>
  <li class="page-item active"><a class="page-link" href="#">3</a></li>
</ul>
</code>```

I don't know about the filter. I really don't know how to start I'm kinda new to this. I came from AngularJS 1.6.x

edit: I would like to know I can still use the table design from the template if I integrated some of your suggestions

答:
You can use Angular-Datatable. It is very simple to integrate.

HTML:

```<code><table datatable >
</table
</code>```

app.module:

import { DataTablesModule } from 'angular-datatables';

```<code>  imports: [
    DataTablesModule
  ]
</code>```

Body:
Hi does anyone knows how to make a table with pagination, sort and filter in angular. I'm using Creative Tim's Argon Dashboard for Angular and I'm confused how I am going to integrate it to my project

Here's the documentation of the template click me

Here's a sample code of the table

          Project
          Budget
          Status
          Users
          Completion

                Argon Design System

            $2,500 USD

               pending

              60%

                Action
                Another action
                Something else here

                Angular Now UI Kit PRO

            $1,800 USD

               completed

              100%

                Action
                Another action
                Something else here

                Black Dashboard

            $3,150 USD

               delayed

              72%

                Action
                Another action
                Something else here

                React Material Dashboard

            $4,400 USD

               on schedule

              90%

                Action
                Another action
                Something else here

                Vue Paper UI Kit PRO

            $2,200 USD

               completed

              100%

                Action
                Another action
                Something else here

Here's a sample code of the pagination used

    1

2

3

I don't know about the filter. I really don't know how to start I'm kinda new to this. I came from AngularJS 1.6.x

edit: I would like to know I can still use the table design from the template if I integrated some of your suggestions

Body:
You can use Angular-Datatable. It is very simple to integrate.

HTML:

<table datatable >
</table

app.module:

import { DataTablesModule } from 'angular-datatables';

  imports: [
    DataTablesModule
  ]


问:
Input Output vs ngrx Dispatch Oftype
Tell me please what is the best way to communicate data between components in angular if you use Ngrx Store.

Use Input Output

Use dispatch(action) ofType(action)

For example Component1 -> send data to -> Component2. After some time business tell me add new service, so I decide to create a new Component3 and add it between them.

So now I have to modify data flow. Component1 -> Component3 -> Component2.
So in which way it would be more code clean and simple to understand, with input outpt or despatch(action) ofType(action)?

答:
Generally speaking, if you're already using NgRx, you should stick to its patterns and pass all application state through the Store.

Inputs and Outputs can quickly become annoying to track, especially as an app becomes more complex. What if you want to add a feature which uses the same data? If you're already using the Store, you simply need to subscribe to the bit you need.

There are undoubtedly situations in which you'll want to use custom properties and events, but if you've chosen to use NgRx, you'll almost always want to use the Store for state management. Otherwise, why install it?

Title: Input Output vs ngrx Dispatch Oftype


问:
How To Mount Directory Of Conainter Into Another After containers are run?
I have angular app, which is built when container is run as shown in Dockerfile, that's built from two images.

```<code>FROM node:10.15.3-alpine as builder

RUN mkdir -p /usr/src/banax_education_platform
WORKDIR /usr/src/banax_education_platform

RUN apk add git
COPY package*.json /usr/src/banax_education_platform/
RUN npm i

COPY . /usr/src/banax_education_platform

RUN npm i -g @angular/cli && npm run-script build

FROM nginx:1.13.12-alpine

RUN rm -rf /usr/share/nginx/html/*

COPY /nginx/default.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /usr/src/banax_education_platform/dist/edu-app /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
</code>```

So first of all, it fetches node image and build the app with all requirments. and then it fetches another image of nginx and creates server block to show the app. Now I want to split both images, so one container holds angular app and second does nginx reverse proxy.

Came up with this docker-compose.yml

```<code>version: "2"
services:
 nginx:
  build: ./nginx
  container_name: angular-nginx
  ports:
   - "80:80"
  volumes:
   - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
  depends_on:
   - web
 web:
  build: ./angular
  container_name: angular-app
  ports:
   - "8000"
</code>```

But the issue is, I want this task:
COPY --from=builder /usr/src/banax_education_platform/dist/edu-app /usr/share/nginx/html to be done via docker-compose, cause now I have 2 Dockerfiles, one for angular, and one for nginx, so I don't know how to link that angular container's directory with nginx container. Someone has any idea?

答:
I think there is a conceptual misunderstanding. I'm not familiar with Angular, but here's how I would approach this (not tested).

The reverse proxy itself does not need the compiled files that you generate in your Dockerfile. This container (web in your case) is just there to pass HTTP(S) requests to the correct endpoint (which is another webserver, let's call it app). app itself is an Nginx webserver that serves your Angular application.

So, what you would have to do is something like this:

```<code>version: "3.7"

services:

  web:
    image: nginx
    volumes:
      - ./config/with/proxy/rules:/etc/nginx/conf.d/default.conf
    ports:
      - 80:80
      - 443:443

  app:
    build: ./path/to/your/dockerfile
    expose:
      - "8000"
    volumes:
      - ./path/to/nice/config:/etc/nginx/conf.d/default.conf
    depends_on:
      - web
</code>```

The config file of the first volume would have to include all the proxy rules that you need. Basically, something like this should occur somewhere:

```<code>location / {
      proxy_pass http://app:8000;
}
</code>```

That's all your reverse proxy has to know.

Body:
I have angular app, which is built when container is run as shown in Dockerfile, that's built from two images.

FROM node:10.15.3-alpine as builder

RUN mkdir -p /usr/src/banax_education_platform
WORKDIR /usr/src/banax_education_platform

RUN apk add git
COPY package*.json /usr/src/banax_education_platform/
RUN npm i

COPY . /usr/src/banax_education_platform

RUN npm i -g @angular/cli && npm run-script build

FROM nginx:1.13.12-alpine

RUN rm -rf /usr/share/nginx/html/*

COPY /nginx/default.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /usr/src/banax_education_platform/dist/edu-app /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

So first of all, it fetches node image and build the app with all requirments. and then it fetches another image of nginx and creates server block to show the app. Now I want to split both images, so one container holds angular app and second does nginx reverse proxy.

Came up with this docker-compose.yml

version: "2"
services:
 nginx:
  build: ./nginx
  container_name: angular-nginx
  ports:
   - "80:80"
  volumes:
   - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
  depends_on:
   - web
 web:
  build: ./angular
  container_name: angular-app
  ports:
   - "8000"

But the issue is, I want this task:
COPY --from=builder /usr/src/banax_education_platform/dist/edu-app /usr/share/nginx/html to be done via docker-compose, cause now I have 2 Dockerfiles, one for angular, and one for nginx, so I don't know how to link that angular container's directory with nginx container. Someone has any idea?

Body:
I think there is a conceptual misunderstanding. I'm not familiar with Angular, but here's how I would approach this (not tested).

The reverse proxy itself does not need the compiled files that you generate in your Dockerfile. This container (web in your case) is just there to pass HTTP(S) requests to the correct endpoint (which is another webserver, let's call it app). app itself is an Nginx webserver that serves your Angular application.

So, what you would have to do is something like this:

version: "3.7"

services:

  web:
    image: nginx
    volumes:
      - ./config/with/proxy/rules:/etc/nginx/conf.d/default.conf
    ports:
      - 80:80
      - 443:443

  app:
    build: ./path/to/your/dockerfile
    expose:
      - "8000"
    volumes:
      - ./path/to/nice/config:/etc/nginx/conf.d/default.conf
    depends_on:
      - web

The config file of the first volume would have to include all the proxy rules that you need. Basically, something like this should occur somewhere:

location / {
      proxy_pass http://app:8000;
}

That's all your reverse proxy has to know.


问:
How do I use tryNext() in Gremlin with Node.js?
The following example does not work for me in Node.js using the 'gremlin' 3.4.1 npm package:

```<code>g.V().has('person','name','bill').tryNext().orElseGet{g.addV('person').property('name','bill').next()}
</code>```

I am getting a TypeError saying tryNext() is not a function. What am I doing wrong?

```<code>import {driver, structure} from 'gremlin';
import DriverRemoteConnection = driver.DriverRemoteConnection;
import Graph = structure.Graph;

const g = new Graph().traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin'));

console.log(g.V().toList());    <= working
</code>```

Now using the line from above in that code will not work, but it does work using the Gremlin console.

答:
If tryNext() not supported as Dave mentioned.

You can rewrite your query to do the same with other gremlin steps:

```<code>g.V().has('person','name','bill').fold().coalesce(unfold(),g.addV('person').property('name','bill')).next()

</code>```

Body:
The following example does not work for me in Node.js using the 'gremlin' 3.4.1 npm package:

g.V().has('person','name','bill').tryNext().orElseGet{g.addV('person').property('name','bill').next()}

I am getting a TypeError saying tryNext() is not a function. What am I doing wrong?

import {driver, structure} from 'gremlin';
import DriverRemoteConnection = driver.DriverRemoteConnection;
import Graph = structure.Graph;

const g = new Graph().traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin'));

console.log(g.V().toList());    <= working

Now using the line from above in that code will not work, but it does work using the Gremlin console.

Body:
If tryNext() not supported as Dave mentioned.

You can rewrite your query to do the same with other gremlin steps:

g.V().has('person','name','bill').fold().coalesce(unfold(),g.addV('person').property('name','bill')).next()


问:
SmartPointer : cast between base and derived classes
Say you have a function like this :

```<code>SmartPtr<A> doSomething(SmartPtr<A> a);
</code>```

And classes like this :

```<code>class A { }
class B : public A { }
</code>```

And now I do this :

```<code>SmartPtr<A> foo = new B();
doSomething(foo);
</code>```

Now, I would like to get back a SmartPtr object from doSomething.

```<code>SmartPtr<B> b = doSomething(foo);
</code>```

Is it possible ? What kind of casting do I have to do ?

Right now, I just found something I believe ugly :

```<code>B* b = (B*)doSomething().get()
</code>```

Important notes :  I do not have any access to SmartPtr and doSomething() code.

答:
```<code>SmartPtr<B> b = dynamic_cast<B*>(doSomething().get())
</code>```

or perhaps something like doSomething().dynamic_cast() if your SmartPtr supports it.

ContentLicense: CC BY-SA 3.0

Body:
Say you have a function like this :

SmartPtr doSomething(SmartPtr a);

And classes like this :

class A { }
class B : public A { }

And now I do this :

SmartPtr foo = new B();
doSomething(foo);

Now, I would like to get back a SmartPtr object from doSomething.

SmartPtr b = doSomething(foo);

Is it possible ? What kind of casting do I have to do ?

Right now, I just found something I believe ugly :

B* b = (B*)doSomething().get()

Important notes :  I do not have any access to SmartPtr and doSomething() code.

Body:
SmartPtr<B> b = dynamic_cast<B*>(doSomething().get())

or perhaps something like doSomething().dynamic_cast<B*>() if your SmartPtr supports it.


问:
On add element in queue call a listener to notify queue element is variable
I want when ever we add element in queue , I should call a method to fetch as soon as element added in queue.</p>

```<code>package com.java.listner;

import java.util.LinkedList;
import java.util.Queue;

public class QueueListner {

public static Queue<String> queue=new LinkedList<String>();

    public boolean add(String e) {
        itemAvailableInqueue();
        return false;
    }

    public static void main(String[] args) {
        queue.add("record1");
        System.out.println(queue.poll());
    }

    public void itemAvailableInqueue() {
        int size = queue.size();
        for (int i = 1; i < size; i++) {
            System.out.println(queue.poll());
        }
    }
}
</code>```

答:
To add a listener to a queue, you need to make it "listenable", the simplest way to do this is Decorator pattern. This pattern targets adding features in a class while keeping its base functionality.

In case of Queue, you just extend a AbstractQueue class, overriding its offer method. Other methods just delegate to a backing queue, since they don't need to notify listeners.

```<code>public class ListenableQueue<E> extends AbstractQueue<E> {

    interface Listener<E> {
        void onElementAdded(E element);
    }

    private final Queue<E> delegate;  // backing queue
    private final List<Listener<E>> listeners = new ArrayList<>();

    public ListenableQueue(Queue<E> delegate) {
        this.delegate = delegate;
    }

    public ListenableQueue<E> registerListener(Listener<E> listener) {
        listeners.add(listener);
        return this;
    }

    @Override
    public boolean offer(E e) {
        // here, we put an element in the backing queue,
        // then notify listeners
        if (delegate.offer(e)) {
            listeners.forEach(listener -> listener.onElementAdded(e));
            return true;
        } else {
            return false;
        }
    }

    // following methods just delegate to backing instance
    @Override public E poll() { return delegate.poll(); }
    @Override public E peek() { return delegate.peek(); }
    @Override public int size() { return delegate.size(); }
    @Override public Iterator<E> iterator() { return delegate.iterator(); }

}
</code>```

This ListenableQueue implements Queue interface thus has all Queue functionality considering properties of backing delegate queue (i.e. capacity constraints, blocking behavior, etc), so can be used like any other Queue.

Usage example:

```<code>// we create new `LinkedList` as a backing queue and decorate it
ListenableQueue<String> q = new ListenableQueue<>(new LinkedList<>());

// register a listener which polls a queue and prints an element
q.registerListener(e -> System.out.println(q.poll()));

// voila!
q.add("record1");
</code>```

Body:
I want when ever we add element in queue , I should call a method to fetch as soon as element added in queue.

package com.java.listner;

import java.util.LinkedList;
import java.util.Queue;

public class QueueListner {

public static Queue<String> queue=new LinkedList<String>();

    public boolean add(String e) {
        itemAvailableInqueue();
        return false;
    }

    public static void main(String[] args) {
        queue.add("record1");
        System.out.println(queue.poll());
    }

    public void itemAvailableInqueue() {
        int size = queue.size();
        for (int i = 1; i < size; i++) {
            System.out.println(queue.poll());
        }
    }
}

Body:
To add a listener to a queue, you need to make it "listenable", the simplest way to do this is Decorator pattern. This pattern targets adding features in a class while keeping its base functionality.

In case of Queue, you just extend a AbstractQueue class, overriding its offer method. Other methods just delegate to a backing queue, since they don't need to notify listeners.

public class ListenableQueue<E> extends AbstractQueue<E> {

    interface Listener<E> {
        void onElementAdded(E element);
    }

    private final Queue<E> delegate;  // backing queue
    private final List<Listener<E>> listeners = new ArrayList<>();

    public ListenableQueue(Queue<E> delegate) {
        this.delegate = delegate;
    }

    public ListenableQueue<E> registerListener(Listener<E> listener) {
        listeners.add(listener);
        return this;
    }

    @Override
    public boolean offer(E e) {
        // here, we put an element in the backing queue,
        // then notify listeners
        if (delegate.offer(e)) {
            listeners.forEach(listener -> listener.onElementAdded(e));
            return true;
        } else {
            return false;
        }
    }

    // following methods just delegate to backing instance
    @Override public E poll() { return delegate.poll(); }
    @Override public E peek() { return delegate.peek(); }
    @Override public int size() { return delegate.size(); }
    @Override public Iterator<E> iterator() { return delegate.iterator(); }

}

This ListenableQueue implements Queue interface thus has all Queue functionality considering properties of backing delegate queue (i.e. capacity constraints, blocking behavior, etc), so can be used like any other Queue.

Usage example:

// we create new `LinkedList` as a backing queue and decorate it
ListenableQueue<String> q = new ListenableQueue<>(new LinkedList<>());

// register a listener which polls a queue and prints an element
q.registerListener(e -> System.out.println(q.poll()));

// voila!
q.add("record1");


问:
Group multidimensional array by key and sum values in PHP
There are similar questions on Stack Overflow but nothing quite like mine, and I also want to double check that I am doing this the most efficient way (resource wise).

I have surveys that are submitted and I want to tally the results. Each survey is saved in a multidimensional array like so:

Array ( [name] => Clark Kent [rating] => 5 )

These are coming from a loop as they are separate database entries.

So I am beginning by creating a new array with all these combined:

```<code>$mods = array();
$index = -1;
foreach($fields as $field) {
  $index++;
  $mods[$index]['name'] = $field['name'];
  $mods[$index]['rating'] = $field['rating'];
}
</code>```

Then I am grouping these so that all the ratings for the same name are together, so I can sum them later.

```<code>$groups = array();
foreach ($mods as $value) {
  $groups[$value['name']][] = $value;
}
</code>```

This produces the following:

```<code>Array (
  [Clark Kent] => Array (
    [0] => Array (
        [name] => Clark Kent
        [rating] => 5
        )
    [1] => Array (
        [name] => Clark Kent
        [rating] => 5
        )
    )
[Peter Parker] => Array (
    [0] => Array (
        [name] => Peter Parker
        [rating] => 5
        )
    [1] => Array (
        [name] => Peter Parker
        [rating] => 5
        )
    )
[Bruce Wayne] => Array (
    [0] => Array (
        [name] => Bruce Wayne
        [rating] => 5
        )
    [1] => Array (
        [name] => Bruce Wayne
        [rating] => 5
        )
    )
[Bruce Banner] => Array (
    [0] => Array (
        [name] => Bruce Banner
        [rating] => 5
        )
    [1] => Array (
        [name] => Bruce Banner
        [rating] => 5
        )
    )
)
</code>```

What I am trying to accomplish would be something like this:

```<code><table>
  <tr>
    <td>Clark Kent</td>
    <td>{average of all ratings}</td>
  </tr>
</table>
</code>```

I'm most of the way there, but I am stuck! I'm not sure how to get the grouped name that doesn't have any type of index or key so I can use that value for my table. Then I need to sum each grouped values.

答:
Try to use the code below. Eliminating an extra loop to preparing group array.

```<code>$mods = array();
foreach($fields as $field) {
  $mods[$field['name']][] = $field['rating'];
}

<table>
  <tr>

<?php
if($mods) {
    foreach($mods as $key=>$value) {
?>
    <td><?php echo $key; ?></td>
    <td><?php echo (array_sum($value)/count($value)); ?></td>
<?php
    }
}
?>
</code>```

Body:
There are similar questions on Stack Overflow but nothing quite like mine, and I also want to double check that I am doing this the most efficient way (resource wise).

I have surveys that are submitted and I want to tally the results. Each survey is saved in a multidimensional array like so:

Array ( [name] => Clark Kent [rating] => 5 )

These are coming from a loop as they are separate database entries.

So I am beginning by creating a new array with all these combined:

$mods = array();
$index = -1;
foreach($fields as $field) {
  $index++;
  $mods[$index]['name'] = $field['name'];
  $mods[$index]['rating'] = $field['rating'];
}

Then I am grouping these so that all the ratings for the same name are together, so I can sum them later.

$groups = array();
foreach ($mods as $value) {
  $groups[$value['name']][] = $value;
}

This produces the following:

Array (
  [Clark Kent] => Array (
    [0] => Array (
        [name] => Clark Kent
        [rating] => 5
        )
    [1] => Array (
        [name] => Clark Kent
        [rating] => 5
        )
    )
[Peter Parker] => Array (
    [0] => Array (
        [name] => Peter Parker
        [rating] => 5
        )
    [1] => Array (
        [name] => Peter Parker
        [rating] => 5
        )
    )
[Bruce Wayne] => Array (
    [0] => Array (
        [name] => Bruce Wayne
        [rating] => 5
        )
    [1] => Array (
        [name] => Bruce Wayne
        [rating] => 5
        )
    )
[Bruce Banner] => Array (
    [0] => Array (
        [name] => Bruce Banner
        [rating] => 5
        )
    [1] => Array (
        [name] => Bruce Banner
        [rating] => 5
        )
    )
)

What I am trying to accomplish would be something like this:

    Clark Kent
    {average of all ratings}

I'm most of the way there, but I am stuck! I'm not sure how to get the grouped name that doesn't have any type of index or key so I can use that value for my table. Then I need to sum each grouped values.

Body:
Try to use the code below. Eliminating an extra loop to preparing group array.

$mods = array();
foreach($fields as $field) {
  $mods[$field['name']][] = $field['rating'];
}

<table>
  <tr>

<?php
if($mods) {
    foreach($mods as $key=>$value) {
?>
    <td><?php echo $key; ?></td>
    <td><?php echo (array_sum($value)/count($value)); ?></td>
<?php
    }
}
?>


问:
Forcing suspended function to work on one thread for mapping Realm objects in background
Let's say I periodically (each 3 seconds) need to read a list of some RealmObjects and I want to map those objects to simple data objects that will be used for filling the content of lists .

For those who wonder, there are three reasons for that:

big complexity of calculating the values and behaviors (visibility, i.e.) of views in ViewHolders in my case

I want to use ListAdapter which needs to have provided DiffUtilCallback which would be executed on some other thread than UI, so doing needed computations in its methods is not possible, because they would be used from the thread in which they are not created.

removing view layer's dependency from Realm

I can not simply use coroutines in basic way which jump from one available thread in its execution to another, since I will(and I did) eventually receive "Realm is already closed" exception.

The only solution that I come up with is using dispatcher that uses only one thread newSingleThreadContext("RealmSingleThreadContext") for example:

```<code>class RealmSingleThreadContext {
    val ctx = NewSingleThreadedContext("RealmSingleThreadContext")

    lateinit var realm : Realm

    init {
        // opening Realm connection for thread "RealmSingleThreadContext"
        GlobalScope.launch(ctx) { realm = Realm.getDefaultInstance() }
    }

    fun close() {
        GlobalScope.launch(ctx) { if (!realm.isClosed()) realm.close() }
    }

}
</code>```

This class is provided as Singleton through Dagger and eventually at the end, I will have one thread to do the mapping for whole app in the background and my "main" viewModel, will call in its onClosed method close() method of this class.

This is how I use it:

```<code>class HomeViewModel @Inject constructor(/* other */var realmSingleThreadContext: RealmSingleThreadContext) {

    var topLiveEventsUI: MutableLiveData<List<EventPreviewUI>> = MutableLiveData(listOf())

    fun useRealmInBckg() {
       viewModeScope.launch(realmSingleThreadContext.ctx) {
           topLiveEventsUI.postValue(eventsReadUseCase.getTopLiveEvents()
               .map{ mapper.map(it) }
        }
    }

}
</code>```

The only thing is that the function newSingleThreadContext(name: String) has @ObsoleteCoroutineApi annotation, so it means that it would be removed soon.

My question is: can anyone propose another solution than using this obsoleted Dispatcher?

答:
It's very easy to make your own single-threaded dispatcher from a Java executor:</p>

```<code>val singleThreadContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
</code>```

Body:
Let's say I periodically (each 3 seconds) need to read a list of some RealmObjects and I want to map those objects to simple data objects that will be used for filling the content of lists .

For those who wonder, there are three reasons for that:

big complexity of calculating the values and behaviors (visibility, i.e.) of views in ViewHolders in my case

I want to use ListAdapter which needs to have provided DiffUtilCallback which would be executed on some other thread than UI, so doing needed computations in its methods is not possible, because they would be used from the thread in which they are not created.

removing view layer's dependency from Realm

I can not simply use coroutines in basic way which jump from one available thread in its execution to another, since I will(and I did) eventually receive "Realm is already closed" exception.

The only solution that I come up with is using dispatcher that uses only one thread newSingleThreadContext("RealmSingleThreadContext") for example:

class RealmSingleThreadContext {
    val ctx = NewSingleThreadedContext("RealmSingleThreadContext")

    lateinit var realm : Realm

    init {
        // opening Realm connection for thread "RealmSingleThreadContext"
        GlobalScope.launch(ctx) { realm = Realm.getDefaultInstance() }
    }

    fun close() {
        GlobalScope.launch(ctx) { if (!realm.isClosed()) realm.close() }
    }

}

This class is provided as Singleton through Dagger and eventually at the end, I will have one thread to do the mapping for whole app in the background and my "main" viewModel, will call in its onClosed method close() method of this class.

This is how I use it:

class HomeViewModel @Inject constructor(/* other */var realmSingleThreadContext: RealmSingleThreadContext) {

    var topLiveEventsUI: MutableLiveData<List<EventPreviewUI>> = MutableLiveData(listOf())

    fun useRealmInBckg() {
       viewModeScope.launch(realmSingleThreadContext.ctx) {
           topLiveEventsUI.postValue(eventsReadUseCase.getTopLiveEvents()
               .map{ mapper.map(it) }
        }
    }

}

The only thing is that the function newSingleThreadContext(name: String) has @ObsoleteCoroutineApi annotation, so it means that it would be removed soon.

My question is: can anyone propose another solution than using this obsoleted Dispatcher?

Body:
It's very easy to make your own single-threaded dispatcher from a Java executor:

val singleThreadContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()


问:
Asynchronous Task without recall
I have some difficulties with asynchronous Tasks in different Applications and Languages.

The typical usage, as far as I understood it is something like:

```<code>asyncTask(x,y){
    //do something
}

alwaysRunWhenAsyncIsFinished(){
    //continue with the app has to
    //happen here
}

...

mainProgramFunction(){
    asyncTask(5,6);
}
</code>```

Well in this Case you have to write your Program until the async task happens, and when you have to call the asyncTask, you have to write the continuing Code in the Recall Function, which is the Function that always happens last after the async task.

Is there a possibility to write something like this:

```<code>asyncTask(x,y){
    //do something
}

...

mainProgramFunction(){
    asyncTask(5,6);

    continuingCode();//But happens AFTER asyncTask is done.
}
</code>```

I don't exactly know if this is possible or not. And maybe this is just possible with a specific sdk, which provides this kind of async task?

The last option could be that I didn't really understand async tasks, and how to use them.

答:
mainProgramFunction(){
     asyncTask(5,6);

continuingCode();//But happens AFTER asyncTask is done.
  }

Well, if the continuingCode() must run after asyncTask(), you leave the asynchonous world (-:

But if you need to do something asynchrously, and then wait all the tasks to continue, you can use a semaphore for exemple :

```<code>mainProgramFunction(){
    bool taskDone = false;
    asyncTask(5,6);
    doSometingElse();

    while(taskDone != true){
      // wait
    }

    continuingCode();//But happens AFTER asyncTask is done.
}
asyncTask(a,b){
   // compute a and b
   taskDone = true;
}
</code>```

(Note that is pseudo code, and each language have good practice on how to to the 'wait' part !).

An other (surely better way) is to use event driven logic...

Title: Asynchronous Task without recall

Body:
I have some difficulties with asynchronous Tasks in different Applications and Languages.

The typical usage, as far as I understood it is something like:

asyncTask(x,y){
    //do something
}

alwaysRunWhenAsyncIsFinished(){
    //continue with the app has to
    //happen here
}

...

mainProgramFunction(){
    asyncTask(5,6);
}

Well in this Case you have to write your Program until the async task happens, and when you have to call the asyncTask, you have to write the continuing Code in the Recall Function, which is the Function that always happens last after the async task.

Is there a possibility to write something like this:

asyncTask(x,y){
    //do something
}

...

mainProgramFunction(){
    asyncTask(5,6);

    continuingCode();//But happens AFTER asyncTask is done.
}

I don't exactly know if this is possible or not. And maybe this is just possible with a specific sdk, which provides this kind of async task?

The last option could be that I didn't really understand async tasks, and how to use them.

Body:
mainProgramFunction(){
     asyncTask(5,6);

continuingCode();//But happens AFTER asyncTask is done.
  }

Well, if the continuingCode() must run after asyncTask(), you leave the asynchonous world (-:

But if you need to do something asynchrously, and then wait all the tasks to continue, you can use a semaphore for exemple :

mainProgramFunction(){
    bool taskDone = false;
    asyncTask(5,6);
    doSometingElse();

    while(taskDone != true){
      // wait
    }

    continuingCode();//But happens AFTER asyncTask is done.
}
asyncTask(a,b){
   // compute a and b
   taskDone = true;
}

(Note that is pseudo code, and each language have good practice on how to to the 'wait' part !).

An other (surely better way) is to use event driven logic...


问:
Matlab Serial buffer issue
I am attempting to communicate from my Simblee Rfduino to Matlab, with the following Arduino code:

```<code>char testing[] = {1,2,3,4,5,6,7,8,'\0'};

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(6,OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(6,LOW);
  Serial.flush();
  testing(EMG);
  Serial.flush();
  digitalWrite(6,HIGH);
}
</code>```

In Python I am able to correctly read in the 1-8 in the correct order consistently.

However in Matlab it continuously changes order with no consistency, with the following code:

```<code>function serial()
global ser
ser = serial('COM5', 'BaudRate', 9600, 'FlowControl', 'hardware');
fopen(ser);

end
function serial_callback(~, ~)
    global ser
    time = tic;
    fread(ser,1) % pull in data from serial port
    toc(time);
end
</code>```

I think there could be some issue in the Serial buffer.
Could you please provide some guidance as to how to consistently get Matlab to read in the data in order? Have others been able to get Matlab to reliably read from a serial port?
Thank you!

答:
You could set the FlowControl to software (xOn and xOff flags). Hardware is only possible if you have the "hardware ressources".

The input buffer of matlab usually works with fifo principle.

After  fopen() you have to wait for one second. Because some microcontrollers (like arduino uno ...) restarts after initialization of the uart interface.

--> pause(1);

Title: Matlab Serial buffer issue

Body:
I am attempting to communicate from my Simblee Rfduino to Matlab, with the following Arduino code:

char testing[] = {1,2,3,4,5,6,7,8,'\0'};

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(6,OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(6,LOW);
  Serial.flush();
  testing(EMG);
  Serial.flush();
  digitalWrite(6,HIGH);
}

In Python I am able to correctly read in the 1-8 in the correct order consistently.

However in Matlab it continuously changes order with no consistency, with the following code:

function serial()
global ser
ser = serial('COM5', 'BaudRate', 9600, 'FlowControl', 'hardware');
fopen(ser);

end
function serial_callback(~, ~)
    global ser
    time = tic;
    fread(ser,1) % pull in data from serial port
    toc(time);
end

I think there could be some issue in the Serial buffer.
Could you please provide some guidance as to how to consistently get Matlab to read in the data in order? Have others been able to get Matlab to reliably read from a serial port?
Thank you!


问:
Updating a kubernetes job: what happens?
I'm looking for a definitive answer for k8s' response to a job being updated - specifically, if I update the container spec (image / args).

If the containers are starting up, will it stop & restart them?

If the job's pod is all running, will it stop & restart?

If it's Completed, will it run it again with the new setup?

If it failed, will it run it again with the new setup?

I've not been able to find documentation on this point, but if there is some I'd be very happy to get some signposting.

答:
The .spec.template field can not be updated in a Job, the field is immutable. The Job would need to be deleted and recreated which covers all of your questions.

The reasoning behind the changes aren't available in the github commit or pr, but these changes were soon after Jobs were originally added. Your stated questions are likely part of that reasoning as making it immutable removes ambiguity.