Serenity: Binding to custom method

Created on 30 Sep 2016  路  10Comments  路  Source: serenity-is/Serenity

Dear @volkanceylan,
I'm "reopening" the #1045 issue because I think that can be useful to someone else.
Basically I have dialog1 that opens dialog2 and I would like to bind a dialog1 field with a custom method on dialog2 (and onestly I don't know if it's possibile or not).
I'm fighting with something like that:

var dlg = new myProject.Test.xyzDialog();
dlg.dialogOpen();
dlg.element.bind('dialogclose', () => alert('test dialog close'));
dlg.element.bind('mymethod', () => alert('test my method'));

where mymethod is a method defined on dialog2.
For some reason the first bind is working fine (I can see the alert when I close dialog2), but the second bind is not working at all (nothing happens when I invoke "mymethod").
What I'm trying to achieve is to bind a custom method on dialog2 to a field on dialog1 so when I call this method, I can "push" a value to the field on dialog1.
Once again, don't know if it's the right approach...

Thank you very much for your support
Bye

Most helpful comment

@marcobisio, i think you are fix mixing dialog methods and jquery based events.

dlg.element.bind('dialogclose', () => alert('test dialog close'));

Here you bind to "dialogclose" event of dlg.element, the element which dialog is created on.

Somewhere in jQuery UI dialog / widget code, this event is triggered when dialog is closed, e.g.

this.element.trigger('dialogclose')

So unless this event is triggered somewhere, you won't get it.

Here you bind to "mymethod" event of your dialog element again:

dlg.element.bind('mymethod', () => alert('test my method'));

Unless "mymethod" is triggered somewhere, you won't get that event. Having a method named "mymethod" in your dialog class doesn't change this fact. They have no relation at all.

So somewhere, you need to trigger it, e.g.

this.element.trigger('mymethod');

Attaching to jQuery element events and triggering them provides some kind of seperation of concerns and reduces dependency of classes on each other.

For example, our entity dialog has a "datachange" event that it triggers on its element. This way grid can attach to data change, without knowing about "type" of dialog, thus grid may also attach to a panel for example, while dialog may notify interested parties without knowing which kind of object attached to its "datachange" event.

In your sample at #1045, you created a direct dependency from your dialog to grid if i remember right. So both can't work without the other.

You might use this jQuery event system, to reduce dependency. It is usually fine, and it is also pass some data alongside the data, but you'll have no intellisense for this data. So sometimes that event system can be a burden.

So for simple cases, i use Actions as callbacks, e.g.

class MyDialog {
   public done: (selectedItem: MyRow) => void;

   void okClick() {
        // do some optional validation
        var selectedItem = // do something to get selected item
        this.done && this.done(selectedItem); // trigger event
        // this.dialogClose(); might close dialog here, or let callback do it after everything goes ok
   }

   public void destroy() {
      this.done = null; // clear to avoid memory holes
      super.destroy();
   }
}

class SomeGrid {
   void someMethod() {
       var dlg = new MyDialog();
       dlg.done = (selectedItem) => {
       // do something with selected item
       dlg.dialogClose(); // optional, dialog might also close itself after calling done
   }
}

Same thing, using jQuery trigger

class MyDialog {
   void okClick() {
        // do some optional validation
        var selectedItem = // do something to get selected item
        this.element.trigger({
           type: 'done',
           selectedItem: selectedItem
        });
        // this.dialogClose(); might close dialog here, or let callback do it after everything goes ok
   }
}

class SomeGrid {
   void someMethod() {
       var dlg = new MyDialog();
       dlg.element.bind('done', e => {
           var selectedItem = (e as any).selectedItem;
           // do something with selected item
           dlg.dialogClose(); // optional, dialog might also close itself after calling done
       });
   }
}

Btw, prefer "on" instead of "bind", as bind is obsolete. There are code in Serenity that i should also fix.

All 10 comments

Hi, @marcobisio . I'm now in need for this feature or similar one. I'm starting to implement this... So, do you mind if I join here? :-)

It seems that you are trying to bind an event handler to the dialog... That's why the first one works and the second doesn't... I guess... unless you are triggering some custom event to that method...

You can test your mymethod event calling something like
$('.ui-widget.s-xyzDialog').children().trigger("mymethod");

Or maybe$('.ui-widget').trigger("mymethod"); in your console...

I'm researching to use your row selectable grid (described in 1241) in dialog2 to trigger multiple input filling in dialog1 from multiple columns of dialog2 grid... But I'm on the very start and.. kind of lost.. :-)

Is it possible to return all columns in that selected grid row just like the checkboxes grid mixins example? That issue combined to this one would be the best of two worlds...

_Edit: Ok... I've already managed to get the row values onclick._

Regards

Hi @edson, of course you're welcome!

To get row values I think that there are two distinct approach: the lookup one for dataset smaller that 10k records, while for largest dataset I prefer to use a service request and disable the lookup for such entity, because otherwise all the data are sent to the client (at least one time).

I think you're right: I'm trying to bind an event handler (dialogclose) and also to a method...perhaps the last one isn't possible, but once again I don't fully understand the @volkanceylan suggestion in the last part of the #1045 issue

I wouldnt create a dependency from your dialog to the form it is used on. That way you cant use that dialog with another form. Your dialog should have an event that sends selected item ids. Your form should attach to that event after creating dialog.

I've a fully functional picker dialog (@edson if you need my sample code, just ask) that I can use only with a specific form, so I have to "copy" such dialog for every entity where I want to use such picker.
I tried to add a method to my picker dialog that retrieve the selected keys, but now I don't know how to attach the parent form to that event.

If you have any idea, let me know!
Thanks
Bye

@marcobisio, i think you are fix mixing dialog methods and jquery based events.

dlg.element.bind('dialogclose', () => alert('test dialog close'));

Here you bind to "dialogclose" event of dlg.element, the element which dialog is created on.

Somewhere in jQuery UI dialog / widget code, this event is triggered when dialog is closed, e.g.

this.element.trigger('dialogclose')

So unless this event is triggered somewhere, you won't get it.

Here you bind to "mymethod" event of your dialog element again:

dlg.element.bind('mymethod', () => alert('test my method'));

Unless "mymethod" is triggered somewhere, you won't get that event. Having a method named "mymethod" in your dialog class doesn't change this fact. They have no relation at all.

So somewhere, you need to trigger it, e.g.

this.element.trigger('mymethod');

Attaching to jQuery element events and triggering them provides some kind of seperation of concerns and reduces dependency of classes on each other.

For example, our entity dialog has a "datachange" event that it triggers on its element. This way grid can attach to data change, without knowing about "type" of dialog, thus grid may also attach to a panel for example, while dialog may notify interested parties without knowing which kind of object attached to its "datachange" event.

In your sample at #1045, you created a direct dependency from your dialog to grid if i remember right. So both can't work without the other.

You might use this jQuery event system, to reduce dependency. It is usually fine, and it is also pass some data alongside the data, but you'll have no intellisense for this data. So sometimes that event system can be a burden.

So for simple cases, i use Actions as callbacks, e.g.

class MyDialog {
   public done: (selectedItem: MyRow) => void;

   void okClick() {
        // do some optional validation
        var selectedItem = // do something to get selected item
        this.done && this.done(selectedItem); // trigger event
        // this.dialogClose(); might close dialog here, or let callback do it after everything goes ok
   }

   public void destroy() {
      this.done = null; // clear to avoid memory holes
      super.destroy();
   }
}

class SomeGrid {
   void someMethod() {
       var dlg = new MyDialog();
       dlg.done = (selectedItem) => {
       // do something with selected item
       dlg.dialogClose(); // optional, dialog might also close itself after calling done
   }
}

Same thing, using jQuery trigger

class MyDialog {
   void okClick() {
        // do some optional validation
        var selectedItem = // do something to get selected item
        this.element.trigger({
           type: 'done',
           selectedItem: selectedItem
        });
        // this.dialogClose(); might close dialog here, or let callback do it after everything goes ok
   }
}

class SomeGrid {
   void someMethod() {
       var dlg = new MyDialog();
       dlg.element.bind('done', e => {
           var selectedItem = (e as any).selectedItem;
           // do something with selected item
           dlg.dialogClose(); // optional, dialog might also close itself after calling done
       });
   }
}

Btw, prefer "on" instead of "bind", as bind is obsolete. There are code in Serenity that i should also fix.

Hi, @marcobisio . Thanks a lot. Based on your code in #1045 I was able to do this. In fact, I only needed one selected row values.

What I did, following your code and master @volkanceylan guidance:
In MyXYZDialog1.ts:

.click(() => {
                        var nDialog: MyXYZDialog2 = new MyXYZDialog2 ();
                        nDialog.dialogOpen();

                        nDialog.element.on('selectedRow', e => {
                            var selectedItem = (e as any).selectedItem;
                            console.log(selectedItem);
                            //some assignment with this.form.fieldName = selectedItem.columnName values
                            nDialog.dialogClose();
                        });

                    });

And in MyXYZGrid2.ts, when a row is clicked I'm returning that row (so dialog2 is closed in MyXYZDialog1.ts when the event is triggered):

protected createSlickGrid(): Slick.Grid {
            var selfGrid = this;
            var grid = super.createSlickGrid();
            grid.setSelectionModel(new Slick.RowSelectionModel());
            grid.onClick.subscribe(function (e, args) {

                var item = grid.getDataItem(args.row);


                selfGrid.element.trigger({
                    type: 'selectedRow',
                    selectedItem: item
                });


            });
            return grid;
        }

So it is working...
But, intellisense and building keeps telling that:
_Object literal may only specify known properties, and 'selectedItem' does not exist in type 'JQueryEventObject'._

It's just a warning, but code is working. I've also tried to put the item content to

{
                    type: 'selectedRow',
                    data: item
                }

But data always comes undefined at event handler in MyXYZDialog1.ts. Also tried sending item as argument but it seems that the event handler is actually wrapped in JQueryEventObject for this typescript port of jquery, so the arguments are at e.handler function... I suppose.

It's just a warning, but annoying one... It doesn't prevent code from working, though. :-)
Any ideas to get rid of it?

Thanks
Regards

trigger(<any>{.....})

wow. thanks @volkanceylan . I'm still a noobie with the Typescript thing. But I'll catch up and hopefully help you and the serenity community more.

Have a nice weekend and thanks for your time.
Best Regards

So. In order to have some intellisense to my selectedItem, I麓ve added the myXYZRow to:

.click(() => {
                        var nDialog: MyXYZDialog2 = new MyXYZDialog2 ();
                        nDialog.dialogOpen();

                        nDialog.element.on('selectedRow', e => {
                            var selectedItem: myXYZRow  = (e as any).selectedItem;
                            console.log(selectedItem);
                            //some assignment with this.form.fieldName = selectedItem.columnName values
                            nDialog.dialogClose();
                        });

                    });

Just a note, in case anyone needs... As I'm returning the clicked row in myxyzdialog2 for my problem...

Regards

Thank you very much @volkanceylan for the detailed explanation...as I already know, there is always something to learn!! Thanks again to point me in the right direction!

Thanks also to you @edson for sharing your code...you scratch my back and I'll scratch yours :-D

Have a nice weekend!
Bye

Sure, @marcobisio . :-) . Master @volkanceylan always writes insightful texts.
Just to add... As we are playing with events. I added some resizing events handlers to the xyzDialog2.ts, so that if the dialog is resized, maximized or restored, the attached slickgrid resizes accordingly:

 private _MyXYZGrid2: MyXYZGrid2;

        constructor() {

            super();

            this._MyXYZGrid2= new MyXYZGrid2(this.byId('MyXYZGrid2'));
        }



        dialogOpen() {
            var selfDialog = this;
            super.dialogOpen();
            this.element.on("dialogextendmaximize", function (event, ui) {
                selfDialog._MyXYZGrid2.slickGrid.resizeCanvas();
            });
            this.element.on("dialogextendrestore", function (event, ui) {
                selfDialog._MyXYZGrid2.slickGrid.resizeCanvas();
            });
            this.element.on("dialogresize", function (event, ui) {
                selfDialog._MyXYZGrid2.slickGrid.resizeCanvas();
            });

        }

In case anyone needs.
Best Regards

Once again, thank you very much for sharing your code @edson !!
Bye

Was this page helpful?
0 / 5 - 0 ratings

Related issues

john20xdoe picture john20xdoe  路  3Comments

dudeman972 picture dudeman972  路  3Comments

gfo2007 picture gfo2007  路  3Comments

dkontod picture dkontod  路  3Comments

stepankurdylo picture stepankurdylo  路  3Comments