Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dialogs and popovers #67

Open
b-laporte opened this issue Feb 15, 2014 · 3 comments
Open

Dialogs and popovers #67

b-laporte opened this issue Feb 15, 2014 · 3 comments

Comments

@b-laporte
Copy link
Member

One of the important (still) missing feature in hashspace is the support of dialogs (and popovers) - so here is a proposal on how I see it:

Dialogs

First I think dialogs should be as easy to use a dialog as using normal templates. This is why I think opening a dialog could be done in a similar way as calling render():

mytemplate("Hello World!").show();

The difference with render() is that show() shouldn't take a DOM element or DOM element id as argument - as hashspace should automatically create a poping div to host the template content. However hashspace should not generate any special surrounding HTML elements to frame the template content - as we should let the template choose how the dialog frame should be rendered (e.g. by using a 3rd party component).

The next question that comes to mind is then: how to pass dialog arguments (e.g. modal, center-horizontal, center-vertical, etc.). I don't think it should be passed as show() argument as it is not the controller's role to determine those rendering options. As a consequence, I would prefer going for a new {dialog-options} statement, that could look like this:

# template mytemplate(msg)
  {dialog-options center:true, modal:true}

  <div class="mydialog">
    <div class="msg">{msg}</div>
    <div class="btn"><input type="button" value="OK" onclick="{dialog.close()}"></div>
  </div>
# /template

Of course {dialog-options} should be ignored if the template is not called in a dialog context (and if multiple dialog-options are found in the template stack, the first one should be used). The arguments could be a JSON structure, without the surrounding curly brackets (btw. syntax should be similar to CSS expressions that should be updated as well to match this syntax).

As you can read, the next problem to solve is: how to close a dialog, and pass a closing argument (e.g. 'ok' or 'cancel'). For this, I think hashspace should automatically create a dialog object accessible in the template scope. This dialog object would let the template know that it is in 'dialog mode' and would expose the dialog properties. It would also expose a close() method that would allow to callback the dialog caller, if a callback is provided as show() argument:

# template mytemplate(msg)
  {dialog-options center:true, modal:true}

  <div class="mydialog">
    <div class="msg">{msg}</div>
    {if dialog}
      // only show buttons if in dialog mode
      <div class="btn">
        <input type="button" value="OK" onclick="{dialog.close('ok')}">
        <input type="button" value="OK" onclick="{dialog.close()}">
      </div>
    {/dialog}
  </div>
# /template

mytemplate("Hello World!").show(function(closeArg) {
  // do sth when the dialog is closed
});

Of course, hashspace should automatically manage the 'ESC' key to close a dialog and call the callback in cancel mode (i.e. without any argument) - this default behaviour could also be overridden through a dialog-options argument.

The dialog object structure could look like this:

dialog={
  modal:true,
  center:"both",
  centerH:true,
  centerV:true,
  backdrop:true,
  autoClose:true, // i.e. if click on the backdrop
  close:function() {}
  // etc. (to be refined)
}

Popovers

Even though popovers are a bit different from dialogs, I think we should also be able to manage them in a similar way:

# template anothertpl(msg1,msg2)
  <div class="foo" onmouseover="{info(msg2).pop('right-top')}">
    Main message: {msg1}
  </div>
# /template

# template info(msg)
  <div class="pop" onclick="{popover.close()}">
    Popup message: {msg}
  </div>
# /template

As dialog and popovers have many arguments that are not common (e.g. modal, center, etc.), I would suggest to have a 2nd context object (i.e. popover) instead of reusing dialog.

Of course, it should be possible to call it from a component's controller:

var SampleCtl=klass({
  attributes:{
    "msg":{type:"string"},
    "popupmsg":{type:"string"}
  },
  triggerPopover:function() {
    // $select should call querySelector() or sizzle on old browser
    info(this).pop('bottom-left', this.$select('.foo'), function (arg) {
      if (arg==='ok') {
        // do something
      }
    });
  }
});

# template sample using c:SampleCtl
  <div class="foo" onmouseover="{c.triggerPopover()}" ontap="{c.triggerPopover()}">
   {c.msg}
  </div>
# /template

# template info(c)
  <div class="pop" onclick="{popover.close('ok')}">
    {c.popupmsg}
  </div>
# /template

... so - what do you think?

@PK1A
Copy link
Contributor

PK1A commented Feb 15, 2014

@b-laporte this is much needed discussions and I'm going to have several remarks. At the beginning I would like to focus on dialogs / modals as I think that getting them right will make it easier to deal with popovers / tooltips and the like. But before diving into more details, I would like to know more about this statement:

The next question that comes to mind is then: how to pass dialog arguments (e.g. modal, center-horizontal, center-vertical, etc.). I don't think it should be passed as show() argument as it is not the controller's role to determine those rendering options.

Why do you feel that passing them from a controller is "bad"? Ar there any practical downsides of such approach? For me this is very important to get it right and personally I would favor passing options from a controller instead of introducing dialog-options as proposed here.

Generally speaking IMO the problem is a bit larger and we need to think about concerns like those:

  • how to pass data / model / context to the modal window - in fact there are 2 "places" where we need to pass scope - modal content itself (application data) and modal's decoration (modal window). In fact there is 3-rd place in case of modals - modal backdrop
  • passing data back from a modal to the invoking content. Once again this is even more complex as there are at least 2 main conceptual ways of existing a dialog: closing it with OK (data selection, clicking OK etc.) and dismissing a window (pressing ESC, backdrop click etc.)
  • listing to modal events (open, close, dismiss) - I can see at least 2 approaches here - either using events or promises (dialog can be seen as an async operation)
  • one more thing - a modal should be closeable from within modal's template, modal's decoration template and the invoking context (JavaScript)

In any case at this point I'm trying to understand how the proposal could / would work with all the use-cases I've encountered while working on the modals so far. But yeh, before discussing syntax details I would like to better understand why you feel like passing options from a controller might cause problems.

@b-laporte
Copy link
Member Author

Well, I am not necessarily against offering the possibility to pass the dialog options from the controller - e.g. through:

mytemplate("Hello World!").show({center:true, modal:true, backdropCSS:"foo"/*etc*/});

But those options belong to the 'view' - and not to the controller. As such it should be possible (and to me preferable) to pass them from the 'view', and not (only) from the controller.

Having said that, and to answer your points - in this proposal:

  • passing data to the dialog is done through the template arguments
  • passing data back to the controller is done through a callback (passed by the controller) and indirectly called through dialog.close(arg1,arg2...,argN). We could also support promises - provided that we don't embed any heavy framework for that.
  • modal events: same thing - i.e. callback or promise
  • closing a dialog can be done from any place in the template (or sub-template) as the dialog object should be automatically propagated to any sub-template or component, so if we use a component for the dialog decoration it will be able to call dialog.close(). The only point I forgot indeed is to be able to close the dialog from the calling context - in this case I would suggest that show() returns an object (call it a special promise) that supports a close() method:
var dlg=mytemplate("hello").show();
dlg.onclose=function (closeArg) {}; // callback, could be directly passed as show() argument
//.. then to force the dialog closure:
dlg.close(); // then onclose will be called with no arg = cancel or dismiss

Btw. a template that uses a component for the dialog decoration would look like this:

# template mytemplate(msg,title)
  <#bs.dialog title="{title}" class="foo" type="ok-cancel" onok="{dialog.close('foo','bar')}" category="info">
    // note: bs.dialog can define default {dialog-options}
    {msg}
 </#bs.dialog>
# /template

@benouat
Copy link
Member

benouat commented Feb 17, 2014

For dialogs (and popovers) i think we should in the first place, try everything that is doable with only what we have today (ie templates, controllers, and css positioning).
Then, and only then (this is just my point of view), if we encounter real issues that prevent us to create nice and usable dialogs we should list them and maybe address them into a dedicated piece of code.

PS: on top of that we should also have a mandatory look at the html5 specification for dialog/popup, because again everything that we are detailing here might only be a matter of polyfills (that probably already exists.....)

EDIT: it already exists !! https://github.com/GoogleChrome/dialog-polyfill

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants