Base solution for your next web application
Open Closed

Multiple customized widgets showing same data. #11204


User avatar
0
aslam.loni created

Hi Team,

We have done migration from 10.2 to 11.2.1 angular for multiple widgets. In new version of angular application multiple customized widgets.

Please see below design page for customizable dashboard. we have added two customized widgets. it's showing same result.

we have added two properties uniqueWidgetId and customAccountNumber in gridInformation.

on the basis of these properties we need to initialise the customized widgets. we want to send uniqueWidgetId and customAccountNumber to the customized widgets component from customizable dashboard component. how to send these id's to customized widgets ?.


13 Answer(s)
  • User Avatar
    0
    aslam.loni created

    Hi,

    looking forward to hear from you.

    Regards, Aslam

  • User Avatar
    0
    musa.demir created

    Hi @aslam.loni

    It is not passing uniqe id to the widget in your version. It is implemented here https://github.com/aspnetzero/aspnet-zero-core/pull/4488/files#diff-4193057d53f587480db3ccfd1a5ed167d0b46371461c12ac9782e0012768b809R174.

  • User Avatar
    0
    aslam.loni created

    Hi @musa.demir

    I tried to implement code from above github link but it is not working as expected. Do you have any other solution or code to refer?

    Regards, Aslam

  • User Avatar
    0
    musa.demir created

    Hi @aslam.loni

    Can you please follow that to get access to the github project https://support.aspnetzero.com/QA/Questions/9580/How-to-access-the-ASPNET-Zero-private-GitHub-repository

  • User Avatar
    0
    aslam.loni created

    Hi @musa.demir

    I have github access to this URL. I have read that code and implimented in our sourc code. It's not working as we want in our application. Do you have any other solution. Our release is stuck due to this issue. Looking forward to hear from you as soon as possible.

    Please see below error during execution

    ERROR NullInjectorError

    core.mjs:6494 
            
           ERROR NullInjectorError: R3InjectorError(MainModule)[InjectionToken WidgetOnResizeEventHandlerToken -> InjectionToken WidgetOnResizeEventHandlerToken -> InjectionToken WidgetOnResizeEventHandlerToken -> InjectionToken WidgetOnResizeEventHandlerToken]: 
      NullInjectorError: No provider for InjectionToken WidgetOnResizeEventHandlerToken!
        at NullInjector.get (core.mjs:11157:1)
        at R3Injector.get (core.mjs:11324:1)
        at R3Injector.get (core.mjs:11324:1)
        at R3Injector.get (core.mjs:11324:1)
        at NgModuleRef.get (core.mjs:21888:1)
        at R3Injector.get (core.mjs:11324:1)
        at NgModuleRef.get (core.mjs:21888:1)
        at Object.get (core.mjs:21565:1)
        at lookupTokenUsingModuleInjector (core.mjs:3367:1)
        at getOrCreateInjectable (core.mjs:3479:1)
    

    Please see below code block

    customizable-dashboard.component.html

    <gridster-item *ngFor="let widget of page.widgets" [item]="widget.gridInformation">
                                        <ng-container *ngComponentOutlet="widget.component"></ng-container>
                                        <gridster-item *ngFor="let widget of page.widgets" [item]="widget.gridInformation" (itemResize)="onGridSterItemResize(widget)">
                                            <ng-container *ngComponentOutlet="widget.component; injector:this.widgetSubjects[widget.guid].injector"></ng-container>
                                            <button
                                            class="btn btn-sm text-white bg-danger deleteWidgetButton"
                                            *ngIf="editModeEnabled"
                                            (click)="removeItem(widget.gridInformation)"
                                        >
                                            <i class="la la-times"></i>
                                        </button>
                                        </gridster-item>
                                        
                                    </gridster-item>
    
    

    customizable-dashboard.component.ts

    import { GuidGeneratorService } from '@shared/utils/guid-generator.service';
    
    export const WIDGETONRESIZEEVENTHANDLERTOKEN = new InjectionToken<WidgetOnResizeEventHandler>('WidgetOnResizeEventHandlerToken');
    
    @Injectable()
    export class WidgetOnResizeEventHandler {
      onResize: Subject<any> = new Subject();
    }
    
    initializeUserDashboardDefinition(userDashboardResultFromServer: Dashboard, dashboardDefinitionResult: DashboardOutput) {
            this.userDashboard = {
                dashboardName: this.dashboardName,
                filters: [],
                pages: userDashboardResultFromServer.pages.map((page) => {
                    //gridster should has its own options
                    this.options.push(this.getGridsterConfig());
    
                    if (!page.widgets) {
                        return {
                            id: page.id,
                            name: page.name,
                            widgets: [],
                        };
                    }
    
                    //only use widgets which dashboard definition contains and have view definition
                    //(dashboard definition can be changed after users save their dashboard, because it depends on permissions and other stuff)
                    page.widgets = page.widgets.filter(
                        (w) =>
                            dashboardDefinitionResult.widgets.find((d) => d.id === w.widgetId) &&
                            this.getWidgetViewDefinition(w.widgetId)
                    );
    
                    return {
                        id: page.id,
                        name: page.name,
                        widgets: page.widgets.map((widget) => {
                            return {
                                id: widget.widgetId,
                                guid: this._guidGenerator.guid(),
                                //View definitions are stored in the angular side(a component of widget/filter etc.) get view definition and use defined component
                                component: this.getWidgetViewDefinition(widget.widgetId).component,
                                gridInformation: {
                                    id: widget.widgetId,
                                    cols: widget.width,
                                    rows: widget.height,
                                    x: widget.positionX,
                                    y: widget.positionY,
                                    uniqueWidgetId:widget.uniqueWidgetId,
                                    customAccountNumber:widget.customAccountNumber,
                                },
                            };
                        }),
                    };
                }),
            };
        }
        
        ngOnInit() {
        
        //abp.event.trigger('app.dashboardFilters.dateRangePicker.onDateChange', this.selectedDateRange);
        forkJoin([
                this._dashboardCustomizationServiceProxy.getUserDashboard(this.dashboardName, DashboardCustomizationConst.Applications.Angular),
                this._dashboardCustomizationServiceProxy.getDashboardDefinition(this.dashboardName, DashboardCustomizationConst.Applications.Angular)
            ]
              )
          .subscribe(([userDashboardResultFromServer, dashboardDefinitionResult]) => {
            this.dashboardDefinition = dashboardDefinitionResult;
            if (!this.dashboardDefinition.widgets || this.dashboardDefinition.widgets.length === 0) {
              this.loading = false;
              this.busy = false;
              return;
            }
    
            if (!userDashboardResultFromServer.pages ||
              userDashboardResultFromServer.pages.length === 0 ||
              userDashboardResultFromServer.pages.flatMap(page=>page.widgets).length === 0
              ) {
                 this.loading = false;
                 this.busy = false;
                 return;
            }
    
            this.initializeUserDashboardDefinition(userDashboardResultFromServer, dashboardDefinitionResult);
            this.initializeUserDashboardFilters();
            this.createWidgetSubjects();
         
            //select first page (if user delete all pages server will add default page to userDashboard.)
            this.selectedPage = {
              id: this.userDashboard.pages[0].id,
              name: this.userDashboard.pages[0].name
            };
        
        }
    
     onGridSterItemResize(item: any): void {
          if (this.editModeEnabled) {
              if (this.widgetSubjects[item.guid]) {
                  this.widgetSubjects[item.guid].handler.onResize.next(null);
              }
          }
      }
    
      createWidgetSubjects() {
        for (let i = 0; i < this.userDashboard.pages.length; i++) {
            var page = this.userDashboard.pages[i];
            for (let i = 0; i < page.widgets.length; i++) {
                const widget = page.widgets[i];
                this.createWidgetSubject(widget.guid);
            }
        }
    }
    
    
    createWidgetSubject(guid: string) {
      var handler = new WidgetOnResizeEventHandler();
      this.widgetSubjects[guid] = {
          handler,
          injector: Injector.create({
              providers: [{ provide: WIDGETONRESIZEEVENTHANDLERTOKEN, useValue: handler }],
              parent: this._injector
          })
      }
    }
    
    

    widget-profit-share.component.html

    export class WidgetProfitShareComponent extends WidgetComponentBaseComponent implements OnInit {
        profitSharePieChart: ProfitSharePieChart;
    
        constructor(injector: Injector, 
            private _dashboardService: TenantDashboardServiceProxy,
            @Inject(WIDGETONRESIZEEVENTHANDLERTOKEN) private _widgetOnResizeEventHandler: WidgetOnResizeEventHandler
            ) {
            super(injector);
            this.profitSharePieChart = new ProfitSharePieChart(this._dashboardService);
            _widgetOnResizeEventHandler.onResize.subscribe(() => { this.profitSharePieChart.reload(); });
        }
    
        ngOnInit() {
            this.profitSharePieChart.reload();
        }
    }
    
    
  • User Avatar
    0
    aslam.loni created

    Hi @musa.demir

    Looking forward to hear from you.

    Regards, Aslam

  • User Avatar
    0
    musa.demir created

    Hi @aslam.loni It seems like you forgot to add some part of code. Can you please verify that you implemented those:

    https://github.com/aspnetzero/aspnet-zero-core/pull/4488/files#diff-4193057d53f587480db3ccfd1a5ed167d0b46371461c12ac9782e0012768b809R30

    https://github.com/aspnetzero/aspnet-zero-core/pull/4488/files#diff-4193057d53f587480db3ccfd1a5ed167d0b46371461c12ac9782e0012768b809R189

    https://github.com/aspnetzero/aspnet-zero-core/pull/4488/files#diff-4193057d53f587480db3ccfd1a5ed167d0b46371461c12ac9782e0012768b809R531-R558

    https://github.com/aspnetzero/aspnet-zero-core/pull/4488/files#diff-62571f284f4986759baa1210492103467d49ce107ca53e02389acf51e119b3cdR50-R51

  • User Avatar
    0
    aslam.loni created

    Hi @musa.demir,

    Essentially we would like to pass Widget.SomeProperty ( here Widget refers to the Widget Class defined in service-proxies.ts ) from customizable-dashboard.component.html to widget-{WidgetName}.component.ts

    Can you please explain the steps needed to achieve this? We are not able to understand the relationship between the code shared above and what we need to implement exactly.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @aslam.loni

    I think this is not supported at the moment. You can create a base widget and create different implementations of the base widget with the parameter you want to pass for now. For example, you may have BaseSaleWidget and CarSaleWidget and ServiceSaleWidget etc...

  • User Avatar
    0
    aslam.loni created

    Hi @ismcagdas,

    Unfortunately this would not work for us since we want to add the same widget multiple times to the dashboard and bind each instance to a specific value. We want to then retrieve the data for the widget from the database using this specific value.

    Can you please suggest any alternative way to implement same or similar functionality?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @aslam.loni

    Could you share how you area planning to add those widgets to page ? Could you share the real life usage scenario ? If I can understand it better, maybe I can offer you another option.

  • User Avatar
    0
    aslam.loni created

    Hi @ismcagdas,

    We are building an Accounting and Financial Modelling related system. Hence, on the dashboard we have widgets that can be mapped to specific accounts selected by the user. The widgets will then show data with various filters such as Monthly, Quarterly, Yearly, under different budgets, scenarios, etc. Here, it is important to note that every user may want to add different accounts on their dashboard depending upon their role in the organization, type of the organization, organizational goals etc.

    Hence, we need to create a Custom Account Widget that can be mapped by the users to any account of their choice. Since there can be multiple accounts being watched, it would be useful to have multiple instances of this widget bound to different Account Numbers.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @aslam.loni

    Thanks. I think in that case, you can follow such an approach. First of all, you can assign a uniqueId to each widget. You can modify widget source code and generate a new guid. After that, you can define a new setting for widget filters and store it with the unique widgetId in AbpSettings table. When a widget is rendered, it can get the related setting and show the correct data by using that filter.