Permalink
Browse files

API Refactor the CMS layouting to provide access to options.

It is now possible to change the threeColumnLayout width options for the
columns via entwine property LayoutOptions and accessor methods.

Thanks @robert-h-curry, @clarkepaul for contributing!
  • Loading branch information...
1 parent 544d2eb commit d4f13fe5320bd05908c0f4d708c35eea13a8bf74 @mateusz mateusz committed with chillu Nov 26, 2012
@@ -1463,10 +1463,6 @@ public function BaseCSSClasses() {
return $this->CSSClasses('Controller');
}
- public function IsPreviewExpanded() {
- return ($this->request->getVar('cms-preview-expanded'));
- }
-
/**
* @return String
*/
View
Oops, something went wrong.
@@ -6,76 +6,136 @@
$.fn.layout.defaults.resize = false;
- var minMenuWidth = 40;
- var maxMenuWidth = 150;
- var prefContentWidth = 820;
- var prefPreviewWidth = 500;
- var minPreviewWidth = 400;
-
+ /**
+ * Acccess the global variable in the same way the plugin does it.
+ */
jLayout = (typeof jLayout === 'undefined') ? {} : jLayout;
- jLayout.threeColumnCompressor = function (spec) {
- var obj = {}, menu = $.jLayoutWrap(spec.menu), content = $.jLayoutWrap(spec.content), preview = $.jLayoutWrap(spec.preview);
+ /**
+ * Factory function for generating new type of algorithm for our CMS.
+ *
+ * Spec requires a definition of three column elements:
+ * - `menu` on the left
+ * - `content` area in the middle (includes the EditForm, side tool panel, actions, breadcrumbs and tabs)
+ * - `preview` on the right (will be shown if there is enough space)
+ *
+ * Required options:
+ * - `minContentWidth`: minimum size for the content display as long as the preview is visible
+ * - `minPreviewWidth`: preview will not be displayed below this size
+ * - `mode`: one of "split", "content" or "preview"
+ *
+ * The algorithm first checks which columns are to be visible and which hidden.
+ *
+ * In the case where both preview and content should be shown it first tries to assign half of non-menu space to
+ * preview and the other half to content. Then if there is not enough space for either content or preview, it tries
+ * to allocate the minimum acceptable space to that column, and the rest to the other one. If the minimum
+ * requirements are still not met, it falls back to showing content only.
+ *
+ * @param spec A structure defining columns and parameters as per above.
+ */
+ jLayout.threeColumnCompressor = function (spec, options) {
+ // Spec sanity checks.
+ if (typeof spec.menu==='undefined' ||
+ typeof spec.content==='undefined' ||
+ typeof spec.preview==='undefined') {
+ throw 'Spec is invalid. Please provide "menu", "content" and "preview" elements.';
+ }
+ if (typeof options.minContentWidth==='undefined' ||
+ typeof options.minPreviewWidth==='undefined' ||
+ typeof options.mode==='undefined') {
+ throw 'Spec is invalid. Please provide "minContentWidth", "minPreviewWidth", "mode"';
+ }
+ if (options.mode!=='split' && options.mode!=='content' && options.mode!=='preview') {
+ throw 'Spec is invalid. "mode" should be either "split", "content" or "preview"';
+ }
+
+ // Instance of the algorithm being produced.
+ var obj = {
+ options: options
+ };
+
+ // Internal column handles, also implementing layout.
+ var menu = $.jLayoutWrap(spec.menu),
+ content = $.jLayoutWrap(spec.content),
+ preview = $.jLayoutWrap(spec.preview);
+ /**
+ * Required interface implementations follow.
+ * Refer to https://github.com/bramstein/jlayout#layout-algorithms for the interface spec.
+ */
obj.layout = function (container) {
- var contentHidden = (content.item.is('.is-collapsed'));
-
var size = container.bounds(),
insets = container.insets(),
top = insets.top,
bottom = size.height - insets.bottom,
left = insets.left,
right = size.width - insets.right;
- var menuWidth = $('#cms-menu.cms-panel').hasClass('collapsed') ? minMenuWidth : maxMenuWidth;
- var contentWidth = contentHidden ? 0 : prefContentWidth;
- var previewWidth = right - left - (menuWidth + contentWidth);
-
- if (!contentHidden) {
- var previewWidth = right - left - (menuWidth + contentWidth);
- var previewUnderlay = false;
-
- // If preview width is less than the minimum size, take some off the menu
- if (previewWidth < prefPreviewWidth) {
- if (previewWidth < minPreviewWidth) {
- contentWidth = right - left - menuWidth;
- previewWidth = right - left - menuWidth;
- previewUnderlay = true;
-
- if (contentWidth < prefContentWidth) {
- contentWidth = right - left - menuWidth;
- previewWidth = right - left - menuWidth;
- }
- }
+ var menuWidth = spec.menu.width(),
+ contentWidth = 0,
+ previewWidth = 0;
+
+ if (this.options.mode==='preview') {
+ // All non-menu space allocated to preview.
+ contentWidth = 0;
+ previewWidth = right - left - menuWidth;
+ } else if (this.options.mode==='content') {
+ // All non-menu space allocated to content.
+ contentWidth = right - left - menuWidth;
+ previewWidth = 0;
+ } else { // ==='split'
+ // Split view - first try 50-50 distribution.
+ contentWidth = (right - left - menuWidth) / 2;
+ previewWidth = right - left - (menuWidth + contentWidth);
+
+ // If violating one of the minima, try to readjust towards satisfying it.
+ if (contentWidth < this.options.minContentWidth) {
+ contentWidth = this.options.minContentWidth;
+ previewWidth = right - left - (menuWidth + contentWidth);
+ } else if (previewWidth < this.options.minPreviewWidth) {
+ previewWidth = this.options.minPreviewWidth;
+ contentWidth = right - left - (menuWidth + previewWidth);
}
- else if (previewWidth > 500) {
- contentWidth = (right - left - menuWidth) / 2;
- previewWidth = right - left - (menuWidth + contentWidth);
+ // If still violating one of the (other) minima, remove the preview and allocate everything to content.
+ if (contentWidth < this.options.minContentWidth || previewWidth < this.options.minPreviewWidth) {
+ contentWidth = right - left - menuWidth;
+ previewWidth = 0;
}
}
+ // Apply classes for elements that might not be visible at all.
+ spec.content.toggleClass('column-hidden', contentWidth===0);
+ spec.preview.toggleClass('column-hidden', previewWidth===0);
+
+ // Apply the widths to columns, and call subordinate layouts to arrange the children.
menu.bounds({'x': left, 'y': top, 'height': bottom - top, 'width': menuWidth});
menu.doLayout();
+
left += menuWidth;
content.bounds({'x': left, 'y': top, 'height': bottom - top, 'width': contentWidth});
- content.item.css({display: contentHidden ? 'none' : 'block'});
content.doLayout();
- if (!previewUnderlay) left += contentWidth;
+
+ left += contentWidth;
preview.bounds({'x': left, 'y': top, 'height': bottom - top, 'width': previewWidth});
preview.doLayout();
-
return container;
};
+ /**
+ * Helper to generate the required `preferred`, `minimum` and `maximum` interface functions.
+ */
function typeLayout(type) {
var func = type + 'Size';
return function (container) {
- var menuSize = menu[func](), contentSize = content[func](), previewSize = preview[func](), insets = container.insets();
+ var menuSize = menu[func](),
+ contentSize = content[func](),
+ previewSize = preview[func](),
+ insets = container.insets();
width = menuSize.width + contentSize.width + previewSize.width;
height = Math.max(menuSize.height, contentSize.height, previewSize.height);
@@ -87,10 +147,12 @@
};
}
+ // Generate interface functions.
obj.preferred = typeLayout('preferred');
obj.minimum = typeLayout('minimum');
obj.maximum = typeLayout('maximum');
+
return obj;
};
-}(jQuery));
+}(jQuery));
@@ -218,14 +218,17 @@
onchange: function(e) {
e.preventDefault();
- var content = $('.cms-content');
+ var container = $('.cms-container');
var state = $(this).val();
+
if (state == 'split') {
- content.removeClass('is-collapsed');
+ container.splitViewMode();
+ } else if (state == 'edit') {
+ container.contentViewMode();
} else {
- content.addClass('is-collapsed');
+ container.previewMode();
}
- content.parent().redraw();
+
this.addIcon(); //run generic addIcon, on select.preview-dropdown
}
});
@@ -103,6 +103,17 @@ jQuery.noConflict();
StateChangeCount: 0,
/**
+ * Options for the threeColumnCompressor layout algorithm.
+ *
+ * See LeftAndMain.Layout.js for description of these options.
+ */
+ LayoutOptions: {
+ minContentWidth: 820,
+ minPreviewWidth: 400,
+ mode: 'split'
+ },
+
+ /**
* Constructor: onmatch
*/
onadd: function() {
@@ -120,7 +131,7 @@ jQuery.noConflict();
this._super();
return;
}
-
+
// Initialize layouts
this.redraw();
@@ -145,14 +156,66 @@ jQuery.noConflict();
onaftersubmitform: function(){ this.redraw(); }
},
+ /**
+ * Ensure the user can see the requested section - restore the default view.
+ */
+ 'from .cms-menu-list li a': {
+ onclick: function() {
+ this.splitViewMode();
+ }
+ },
+
+ /**
+ * Change the options of the threeColumnCompressor layout, and trigger layouting. You can provide any or
+ * all options. The remaining options will not be changed.
+ */
+ updateLayoutOptions: function(newSpec) {
+ var spec = this.getLayoutOptions();
+ $.extend(spec, newSpec);
+ this.redraw();
+ },
+
+ /**
+ * Enable the split view - with content on the left and preview on the right.
+ */
+ splitViewMode: function() {
+ this.updateLayoutOptions({
+ mode: 'split'
+ });
+ this.redraw();
+ },
+
+ /**
+ * Content only.
+ */
+ contentViewMode: function() {
+ this.updateLayoutOptions({
+ mode: 'content'
+ });
+ this.redraw();
+ },
+
+ /**
+ * Preview only.
+ */
+ previewMode: function() {
+ this.updateLayoutOptions({
+ mode: 'preview'
+ });
+ this.redraw();
+ },
+
redraw: function() {
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
- // Use the three column compressor layout, which squishes preview, then menu, then content
- this.data('jlayout', jLayout.threeColumnCompressor({
- menu: this.children('.cms-menu'),
- content: this.children('.cms-content'),
- preview: this.children('.cms-preview')}
+ // Reset the algorithm.
+ this.data('jlayout', jLayout.threeColumnCompressor(
+ {
+ menu: this.children('.cms-menu'),
+ content: this.children('.cms-content'),
+ preview: this.children('.cms-preview')
+ },
+ this.getLayoutOptions()
));
// Trigger layout algorithm once at the top. This also lays out children - we move from outside to
@@ -426,10 +489,9 @@ jQuery.noConflict();
// Set loading state and store element state
var origStyle = contentEl.attr('style');
- var origVisible = contentEl.is(':visible');
var origParent = contentEl.parent();
var origParentLayoutApplied = (typeof origParent.data('jlayout')!=='undefined');
- var layoutClasses = ['east', 'west', 'center', 'north', 'south'];
+ var layoutClasses = ['east', 'west', 'center', 'north', 'south', 'column-hidden'];
var elemClasses = contentEl.attr('class');
var origLayoutClasses = [];
if(elemClasses) {
@@ -443,7 +505,6 @@ jQuery.noConflict();
.removeClass(layoutClasses.join(' '))
.addClass(origLayoutClasses.join(' '));
if(origStyle) newContentEl.attr('style', origStyle);
- newContentEl.css('visibility', 'hidden');
// Allow injection of inline styles, as they're not allowed in the document body.
// Not handling this through jQuery.ondemand to avoid parsing the DOM twice.
@@ -453,9 +514,6 @@ jQuery.noConflict();
// Replace panel completely (we need to override the "layout" attribute, so can't replace the child instead)
contentEl.replaceWith(newContentEl);
- // Unset loading and restore element state (to avoid breaking existing panel visibility, e.g. with preview expanded)
- if(origVisible) newContentEl.css('visibility', 'visible');
-
// Force jlayout to rebuild internal hierarchy to point to the new elements.
// This is only necessary for elements that are at least 3 levels deep. 2nd level elements will
// be taken care of when we lay out the top level element (.cms-container).
Oops, something went wrong.

0 comments on commit d4f13fe

Please sign in to comment.