KoolReport's Forum

Official Support Area, Q&As, Discussions, Suggestions and Bug reports.
Forum's Guidelines

A bit unclear the logic of the views in the FlexView #3305

Open Eugene opened this topic on on May 28 - 14 comments

Eugene commented on May 28

Hi, guys

It's me again and again about Flexview :-)

I encountered a strange effect. I use Flexview with several views. On the first view I load the file and save its name in $this>params('savedFileName')

In the second view, I analyze the downloaded file, so, I access $this->params('savedFileName'). So far everything is working well.

But in the second view I have a button that does $this->widget('FileImportFlexView')->showView('step1') to return to the first view. And this is where the error occurs.

When I press the button, the code related to the second view is executed , and it access $this->params('savedFileName'), which at this moment is already null.

The question is, why is this happening, since I am returning to the first view?

I can avoid this with the help of additional code, but it seems illogical to me to do these checks, since according to my understanding, the code of the second view should not be executed when I switch to the first view.

KoolReport commented on May 28

The flow of FlexView is like this:

  1. Let say the current view is step 2, at client-side it shows you step 2.
  2. You initiate the change view to step 1 by ->showView('step1') from client
  3. The command is transmitted to server.
  4. Now, before execute your command, the FlexView first construct its current view from its state to make sure client-side and server-side are in the same context. So current view is step 2. That's why you see the code in step 2 is executed.
  5. When everything is constructed, it now execute your command to change view. The step 1 view will be executed.

So in FlexView, you should consider the optionViews are set of scenario screen which you can switch. Each screen should be purely a view template. The logic code can be inside the widgets of view but not directly inside the screen itself (which executed everytime the view is called).

I have suggested in previous code that you rearrange a bit, you should check the validity of the files right after it uploaded (in the AllFileHandled event), and when everything is right, you change to step 2.

Eugene commented on May 28

well, I like the idea to put logic inside the widget but it means I have to create my own widget.

Is it correct if I say that generally I need 2 methods in my widget

beforeOnCreated() - with all my logic, and

render() - with widget's output html?

Eugene commented on May 29

Maybe I want too much from the Dashboard and FlexView... sorry for the long read

I remember your suggestion to check the validity of the files right after it uploaded (in the AllFileHandled event) but it is not only just checking - I need to get some data from the file to use it on the second screen. So I decided to create my own widget with the logic inside to put it on the second screen and that will get the information from the file and show it.

The widget generally works until I want to press the button. At this moment I met with the same problem.The FlexView first construct its current view but my widget needs the file name that for unknown reason is not available more but also it is difficult to understand for me why the FlexView want to repeat everything that was already done including the logic in my widget. It is useless at this step as for me...

Below I will show a part of my code (it is not PHP correct because I show you just to explain what I try to do )but I am not sure it is possible to do at all. FlexView looks not so flex... :-(

So my code: FlexView board file:

...
'step1' => function ($params) {
    return [
     ...

        FileUploader::create('SalesSummaryUploader')
         ...
            ->registerEvent('FileHandling', function ($params) {
            $this->params(['savedFileName' => dirname($_SERVER['DOCUMENT_ROOT']) . '/storage/' . $params['file'] ['name']]);
            return true;
         })
 ...
 ];
},

//Now I have my file name in $this->params(['savedFileName'])

 'step2' => function ($params) {

 $bigFileInformationWidget = BigFileInformationWidget::create()
                            ->fileName($this->params('savedFileName'))
                        ;
//Why I do like this - because I want to have access to this object later it also does not work (see below) but it is the second problem

return [
                            Panel::create()->type('primary')->header('<b>Step 2: File Information</b>')->sub([
                                Row::create([
                                    $bigFileInformationWidget, //it works and I can see the content from the file generated by my widget)
                                ]),
                                Html::div()->class('row')->sub([
                                    Html::div()->class('col d-flex justify-content-between')->sub([
                                        Html::span('You can continue only if you see no errors above'),
                                        Html::div()->sub([
                                            Inline::create()->sub([

//The issue happens when I click any of this button. FlexView tries to construct this current view but 'savedFileName' parameter is not set at this moment ( I dont know why because it was set and the wizard could generate the content)

                                                Button::create('BackButton')
                                                    ->type('secondary')
                                                    ->text('Back')
                                                    ->action('submit', function ($request, $response) {
                                                        $this->widget('FileImportFlexView')->showView('step1');
                                                    }),

                                                Button::create('NextButton')
                                                    ->type('primary')
                                                    ->text('Next')
                                                    ->action('submit', function ($request, $response) {
                                                        $this->widget('FileImportFlexView')->showView('step1');
                                                    })->disabled($bigFileInformationWidget->getDataError()), //It also does not work because it looks like this part executed before the real value of dataError will be calculated by widget so it always return the default one

bigFileInformationWidget file:

class BigFileInformationWidget extends Widget
{
    protected array $tableContent;
    protected bool $dataError= false;

    protected function beforeOnCreated()
    {
        parent::beforeOnCreated();
        $this->props([
            'fileName' => null,
        ]);
    }
    protected function onInit()
    {
        $excel = new ExcelProcessing($this->_fileName());

        $result = $excel->getExcel();
...
      
        $this->dataError = ... //I set dataError depends on the result;

        $this->tableContent = [
//content for widgets view
        ];    

    }
    public function getDataError(){

        return $this->dataError;
    }

    protected function render()
    {
        if ($this->_hidden() === true) {
            return null;
        
        $content = '<div id="fileInformationWidget">';

        .... content generation

        return $content;
    }
}

If you have any ideas how to solve my problems please share with me

KoolReport commented on May 29

I have an idea, actually it is a good thing when savedFileName is not available. So we only need to show the BigFIleInformationWidget when the savedFileName is available. Something like this:

Row::create([
       $this->params('savedFileName')?
            BigFileInformationWidget::create()->fileName($this->params('savedFileName'))
            :null
]),
Html::div()->class('row')

So when you change back the view from step 2 to step 1, the savedFileName is not available hence the loading of current view (which is step 2) will not load your BigFileInformationWidget anymore hence avoid unnecessarily data loading.

To answer your question of why FlexView needs to construct the current view. It is because without it, widgets showing in a view will become just a set of bricks. Let say in a view, you have some inputs like Select, Radio button, when user performs an action on any of them, the request will send back to server and then application suddenly could not find those Select or Radio button anymore because FlexView did not reconstruct that view. That will cause failure.

Even in your "step 2" view for example, when you click the Back button, if the FlexView at server-side does not re-built the content of step 2, there will be no Back button available to handle the actionSubmit (to change view).

Eugene commented on May 29

Well, thank you... I think it should work... But how can I resolve getDataError() issue?

DataError is set inside BigFileInformationWidget and depends on it I want to enable or disable the next button but it looks like this part of code with Buttons is executed before DataError had been set.

I see one way – to include buttons to BigFileInformationWidget rendering but how to move this kind of code to the render function

Button::create('BackButton')
 ->type('secondary')
->text('Back')
->action('submit', function ($request, $response) {
$this->widget('FileImportFlexView')->showView('step1');
KoolReport commented on May 29

Those code is not suitable to put in render() function in which only return the pure html.

I am thinking of better way, in your BigFileInformationWidget, you write a processData() function, something like this:

protected $dataError = true; // Assume that dataError is firstly true.
public function processData()
{
    if($this->fileName()===null)
    {
        $this->dataError = true;
          return;
    }

    // Move all the code from current onInit to here and remove the onInit function.
   // Process reading file, do any checking here

    if($nothingWrong)
    {
        $this->dataError = false;
    }
}

public function getDataError()
{
    return $this->dataError;
}

Now in the step2 function, you init the bigFileInformationWidget like this:

$bigFileInformationWidget = BigFileInformationWidget::create()
                            ->fileName($this->params('savedFileName'))

$bigFileInformationWidget->processData();

Now your file is processed (if fileName is null, then the processData did not do anything) thegetDataError() also be available for you to use with disabled function.

Let try it.

Eugene commented on May 29

Yes, Your idea helped me very much.

But suddenly $this->widget('FileImportFlexView')->showView('step1'); stops working :-))

I am confused because now $this points to the Button object not to the Board.like before... but I even dont want to know why :-)))

It looks like $this->sibling('FileImportFlexView')->showView('step1'); I can use

KoolReport commented on May 29

Change it to $this->sibling('FileImportFlexView')->showView('step1');

Eugene commented on May 29

Yes, I have done already... Sometimes this magic a bit frustrating me. before sibling() did not work and I found the solution with widget()...

Eugene commented on May 29

I am sorry. May I ask you one more question.

As you know at the 1st step I uploaded the file using File Uploader. and also I have

->registerEvent('AllFileHandled', function ($params) {
                                                if ($params['error'] === null) {
                                                    $this->widget('FileImportFlexView')->showView('step2');
                                                }
                                            })

On the 2nd step I process the data and it needs time...around 10 sec. All this time I still see the 1st step view... even I am on the second step already. And I see no way to control it.

So the question is: can I change the view to the 2nd step immediately after the file is uploaded and show not the $bigFileInformationWidget content but something like the progress bar or big spinner, start the data processing and show $bigFileInformationWidget content when the data processing is done.

My first idea is to split all this things to the two screens but in this case I have to jump to the next step automatically - show "progress bar" and jump to the step 3 where start the data processing (no idea how to do it so maybe it is wrong approach).

So maybe there is a more simple and correct solutions?

Eugene commented on May 29

I also tried this approach with actions

FlexView::create('FileImportFlexView')
                ->action('changeView', function ($request, $response) {
                    $this->sibling('FileImportFlexView')->showView('step2');
                })

...

'step2' => function ($params) {
                        return [
                            Panel::create()->type('primary')->header('<b>Step 2: File Processing</b>'),
                            Client::widget('FileImportFlexView')->action('changeView'),
                        ];
                    },

but get message:

[Widget] SalesSummaryUploader
Message: Call to a member function app() on string
KoolReport commented on May 29

The Client::widget('FileImportFlexView')->action('changeView') is not a widget, it generate javascript string (it translate to string like this "widgetAction('FileImportFlexView','changeView')"), so it is not correct that you add to return of the view. You can assign this Client::... to onClick() function for example so that it can be execute when user click to change view.

Eugene commented on May 29

ok. but how to realize changing the view from the server side for example if I need that one view was shown and than changed to another one in some interval of time?

KoolReport commented on May 31

Hi Eugene,

This is for the question you asked about "showing loader" and increase the responsiveness of dashboard. The Widget of Dashboard support something called lazyLoading. It meaning that, the view will be render with a "loading icon", and then the widget will contact the server for real content.

You can make your BigFileInformationWidget to behave like so by setting ->lazyLoading(true). But you need to be aware of "unavailability of params["savedFileName"] " at that point of loading. So you could use TWidgetState trait to make your widget statable. The state will store any information that you need to be persisted during trip back and forth between server.

The use of state will be simple like this:

To make it stateful widget, you add trait TWidgetState:

use koolreport\dashboard\TWidgetState;

class BigFileInformationWidget extends Widget
{
    use TWidgetState;
}
 * $state = $this->state(); //get all state
 * $this->state("key","value"); //Mutate state, assigning value
 * $this->state("key"); // Return state of key

So when you have the savedFileName, you can store it in state to be used later in next load.

Hope that helps.

P:S: We have some examples of implementing the lazyLoading, for example the Metrics and Table in this example

P.S 2: You can create new topic for new issue at anytime, try to keep 1 topic for 1 specific issue :)

Build Your Excellent Data Report

Let KoolReport help you to make great reports. It's free & open-source released under MIT license.

Download KoolReport View demo
help needed

Dashboard