dtpublic / malhar-angular-dashboard

Generic Dashboard/Widgets functionality with AngularJS (directive)
http://datatorrent.github.io/malhar-angular-dashboard/#/
Apache License 2.0
737 stars 297 forks source link

Widget to Widget Interaction #126

Open rsnelling opened 9 years ago

rsnelling commented 9 years ago

Andy/Nick,

We're working through a prototype of the dashboard for a patient care application. We implemented several of the NVD3 widgets and have started building custom widgets with data from our REST API's. One thing we're investigating is how to have one widget effect the scope of another. For instance in a patient roster table widget, if we select a patient then we'd like to update additional widgets (ie. contact information widget or patient medication history widget) to reflect the data of the patient selected in the roster widget.

I'm working through gathering a better understanding of how the widget scope relates to the dashboard controller scope but can't see how to get a handle into other widgets. Any thoughts or suggestions in regards to this?

Thanks, Robert

awashbrook commented 9 years ago

Hi Robert,

This is something I'm also working through this week. It would be good to get wider feedback on my approach to achieve the same thing using malhar.

Should highlight that we are using malhar data layers and data options with common data prototypally inherited by various kinds of widgets, e.g. this WDO:

{
  name: 'widget inheriting common data',
  directive: 'widget-rendering-some-data-model',
  dataModelType: SomeDataModelExtendingCommonData,
  dataModelOptions: {
    common: {
      data: 'initial value'
    },
    settingsModalOptions: {
      settingsModalOptions: {
        templateUrl: 'src/controllers/widgetOptions.html',
        controller: 'widgetOptionsController'
      },
      onSettingsClose: function (result, widget, dashboardScope) {
        console.log('Widget specific settings resolved.');
        if (!angular.equals(result.dataModelOptions.common, widget.dataModelOptions.common)) {
          dashboardScope.$broadcast('commonDataChanged', newWidget.dataModelOptions.common);
        }
      },
      onSettingsDismiss: function (reason, dashboardScope) {
        console.log('Widget-specific settings have been dismissed: ', reason);
      }
    }
  }
}

Each widget will persist all data in full, but I am synchronizing common data across our dashboard using angular events. Lets assume that all of our widgets inherit the method setCommon(data) from the following angular service:

.factory('CommonDataModel', function (WidgetDataModel) {
  function CommonDataModel() {}

  CommonDataModel.prototype = Object.create(WidgetDataModel.prototype);

  CommonDataModel.prototype.setup = function (widget, scope) {
    WidgetDataModel.prototype.setup.call(this, widget, scope);

    this.dataModelOptions = this.dataModelOptions || {};
    this.dataModelOptions.common = this.dataModelOptions.common  ||  { data: 'default value' };
    ...
    scope.$on('commonDataChanged', function (event, data) {
      console.log('Common data changed for: ' + this.widgetScope.widget.title);
      this.setCommon(data);
    }.bind(this));
  };

  CommonDataModel.prototype.setCommon = function (data) {
    if (data && (!angular.equals(this.dataModelOptions.common, data))) {
      console.log(this.widgetScope.widget.title + ' data model options changed');

        this.dataModelOptions.common = data;
      ...
      this.widgetScope.$emit('widgetChanged', this.widgetScope.widget);
    }
  };
  ...
}

Now every time someone hits ok on the modal, any change in the common settings is detected and broadcast to all those inheriting this data.

@andyperlitch, please let me know what you think of this solution? Feel I may be missing something regarding appropriate use of dataModelOptions vs dataModel, as per your example https://github.com/DataTorrent/malhar-angular-dashboard/blob/aebd4b9cf44d2ee5222b56a5847c61cbfb441b67/src/app/customWidgetSettings.js

andyperlitch commented 9 years ago

@awashbrook I like your approach, and would be similar to the approach I myself would take.

@rsnelling, I think the main idea you should extrapolate from andy's comments is to use scope.$broadcast on the dashboard scope. In his specific case, the onSettingsClose method conveniently gets the dashboard scope as a third argument.

If in your specific use-case you would like to trigger changes directly from the widget itself (i.e. not opening the settings dialog), you could do something like this.widgetScope.$parent.$broadcast('someEvent', { some: 'data' });, then have other widgets listen for this event.

If you need more explanation, I'll be happy to elaborate further. I highly recommend reading through the docs for $broadcast and $emit on the angular scope API page.

Also, I'd love to work an example that does something like this into our demo. I am pretty swamped with work at the moment, but if I can find some time, I'll certainly try and get to it and notify you all.

Best, Andy

awashbrook commented 9 years ago

Thanks Andy, yes the discovery that dashboard scope was passed as the mysterious third argument, was a most pleasant, surprise as I was expecting to have to solve this the hard way :)

On Thursday, 19 March 2015, Andy Perlitch notifications@github.com wrote:

@awashbrook https://github.com/awashbrook I like your approach, and would be similar to the approach I myself would take.

@rsnelling https://github.com/rsnelling, I think the main idea you should extrapolate from andy's comments is to use scope.$broadcast on the dashboard scope. In his specific case, the onSettingsClose method conveniently gets the dashboard scope as a third argument.

If in your specific use-case you would like to trigger changes directly from the widget itself (i.e. not opening the settings dialog), you could do something like this.widgetScope.$parent.$broadcast('someEvent', { some: 'data' });, then have other widgets listen for this event.

If you need more explanation, I'll be happy to elaborate further. I highly recommend reading through the docs for $broadcast and $emit on the angular scope API page https://docs.angularjs.org/api/ng/type/%24rootScope.Scope .

Also, I'd love to work an example that does something like this into our demo. I am pretty swamped with work at the moment, but if I can find some time, I'll certainly try and get to it and notify you all.

Best, Andy

— Reply to this email directly or view it on GitHub https://github.com/DataTorrent/malhar-angular-dashboard/issues/126#issuecomment-83686126 .

robertmazzo commented 9 years ago

This is interesting also because in my case I'd like a chart in one widget to update a chart or grid in another. I'm seeing that I could use this.widgetScope.$parent.$broadcast('someEvent', { some: 'data' });, then have other widgets listen for this event