Platform-history icon indicating copy to clipboard operation
Platform-history copied to clipboard

Q/columns tool

Open EGreg opened this issue 11 years ago • 0 comments

Let's make a tool that will form the core of the Groups user experience. It will be the tool that the user interacts with the most when they are navigating the app.

And it will work on both desktop and mobile.

Here is how you make a tool: http://platform.qbix.com/guide/tools

Basically, the idea is to manage one or more columns, in which we will display content. On the desktop and tablet environments, imagine a dark gray desktop with some sheets of white paper (maybe with some shadow). Some areas in the sheet of paper will be scrollable -- the ones with overflow: auto. Each column will be a div with a .Q_column class, allowing you to style it in CSS. You can use the html.Q_notTouchscreen .Q_column selectors to match the desktop environment, and html.Q_touchscreen.Q_notMobile .Q_column selectors to match the tablet environment, or html.Q_notMobile .Q_column to match both.

Please have a scrollbarsAutoHide: {} option for this tool, which will provide options to the Q/scrollbarsAutoHide jQuery plugin that the Q/columns tool will call on all the overflow: auto areas when it makes a new column. You can see the effect at http://platform.qbix.com -- it appears only on the desktop, but it lets you see only one scrollbar at a time, when you mouse over. This is to prevent lots of distracting scrollbars from appearing.

Column indexes will go from 0 for the first column, increasing by one for each column.

On mobile, only one column at a time will be displayed. I will now describe the methods for the tool, which also explain how to add/remove columns as well as switch between them on a mobile device.

  • count() - this will return the number of columns that are currently being shown
  • open(url, slotName [, index] [, options]) will create a new <div class="Q_column" data-index="3"> element if index is undefined or is = count(), otherwise it will replace html in an existing column's div. The method should throw an error if index > count(). You should use Q.loadUrl with the provided url and slotName but also supply slotContainer object to map the slotName to the right div to fill. You should pass Q.extend({url: ..., slotName: ..., slotContainer: ...}, options) to Q.loadUrl options. I think Q.loadUrl calls Q.activate automatically after filling that div, so your job will be almost done. In addition to passing the options to Q.loadUrl, there will be an additional option for the open method called trigger, which expects a reference to an Element. See the onOpen and beforeClose events below.
  • close([index]) closes that particular column, if index is omitted then it should be = count() - 1.
  • column(index) - a reference to the div corresponding to that column
  • trigger(index) - a reference to an Element passed as the trigger option to open method, above, for that column, or null if nothing was passed

The tool should also accept the following default options:

{
   onOpen: new Q.Event(function () { ... }, 'Q/columns'},
   beforeClose: new Q.Event(function () { ... }, 'Q/columns'},
   animation: { 
     duration: 500 // milliseconds
   }
}

these are events with handlers added by default, under the Q/columns key. These events should be handled from open and close functions, as in the following code:

open: function (url, slotName, index, options) {
   if (index > this.count()) throw new Q.Exception("Q/columns open: index is too big");
   var div = this.column(index);
   if (!div) {
      div = document.createElement('div').addClass('Q_column');
      this.element.appendChild(div); // trying to avoid jQuery just for practice
      ++this.state.count;
      this.state.columns[index] = div;
   }
   var slotContainer = {}; slotContainer[slotName] = div;
   var o = Q.extend({
     slotNames: [slotName], 
     slotContainer: slotContainer
   }, options);
   o.onActivate = function _onActivate() {
      this.onOpen.handle(url, slotName, index, options);
   }
   this.state.triggers[index] = options.trigger || null;
   Q.loadUrl(url, o);
},

close: function (index) {
    var div = this.column(index);
    if (!div) throw new Q.Exception("Column with index " + index + " doesn't exist");
    var shouldContinue = this.beforeClose.handle();
    if (shouldContinue === false) return;

    // this is the proper way to remove an element, so tools inside can be destructed:
    Q.removeElement(this.column(index));
},

column: function (index) { return this.columns[index]; },

trigger: function (index) { return this.triggers[index]; }

finally, use the Q.Animation class to implement those default handlers for onOpen and onClose events. All the animations should complete in a total of this.state.duration milliseconds. Something like this:

onOpen:

var div = this.column(index);
var $trigger = $(trigger);
var offset = $trigger.offset();
var cloned = trigger.cloneNode(true).copyComputedStyle(trigger);
   $(div).css({
     style: 0,
     position: 'absolute',
     left: offset.left,
     top: offset.top,
     width: $trigger.width(),
     height: $trigger.height(),
     zIndex: $trigger.css('zIndex')
   });
   Q.Animation.play(callback, this.state.animation.duration, this.state.animation.ease, params);

basically the onOpen animation should animate the column's opacity from 0 to 1 as it animates its left, top, width and height from overlapping the trigger to its final position ... see the diagram for its position in desktop and tablet vs mobile.

And beforeClose animation should do the reverse, moving it back to the trigger.

However, if the trigger is completely off screen, or not in the DOM, or is null, then just animate the opacity from without moving the column, and then remove the column in the close method, after beforeClose finishes. columns

EGreg avatar Jul 29 '14 07:07 EGreg