diff --git a/etemplate/js/et2_dataview_controller.js b/etemplate/js/et2_dataview_controller.js
index 4fc141f40d..8eacb1a646 100644
--- a/etemplate/js/et2_dataview_controller.js
+++ b/etemplate/js/et2_dataview_controller.js
@@ -352,7 +352,7 @@ var et2_dataview_controller = Class.extend({
if (!_entry.row)
{
createdRow = true;
- _entry.row = new et2_dataview_row(this._grid);
+ _entry.row = this._createRow(ctx);
_entry.row.setDestroyCallback(this._destroyCallback, ctx);
}
@@ -388,6 +388,17 @@ var et2_dataview_controller = Class.extend({
return this.hasData;
},
+
+ /**
+ * Create a new row.
+ *
+ * @param {type} ctx
+ * @returns {et2_dataview_container}
+ */
+ _createRow: function(ctx) {
+ return new et2_dataview_row(this._grid);
+ },
+
/**
* Function which gets called by the grid when data is requested.
*
diff --git a/etemplate/js/et2_dataview_view_tile.js b/etemplate/js/et2_dataview_view_tile.js
new file mode 100644
index 0000000000..57fbf278c2
--- /dev/null
+++ b/etemplate/js/et2_dataview_view_tile.js
@@ -0,0 +1,106 @@
+/**
+ * EGroupware eTemplate2 - dataview code
+ *
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage dataview
+ * @link http://www.egroupware.org
+ * @author Nathan Gray
+ * @copyright Nathan Gray 2014
+ * @version $Id: et2_dataview_view_container_1.js 46338 2014-03-20 09:40:37Z ralfbecker $
+ */
+
+"use strict";
+
+/*egw:uses
+ jquery.jquery;
+ et2_dataview_interfaces;
+*/
+
+/**
+ * Displays tiles or thumbnails (squares) instead of full rows.
+ *
+ * It's important that the template specifies a fixed width and height (via CSS)
+ * so that the rows and columns work out properly.
+ *
+ * @augments et2_dataview_container
+ */
+var et2_dataview_tile = et2_dataview_row.extend([],
+{
+ columns: 4,
+
+ /**
+ * Creates the row container. Use the "setRow" function to load the actual
+ * row content.
+ *
+ * @param _parent is the row parent container.
+ * @memberOf et2_dataview_row
+ */
+ init: function(_parent) {
+ // Call the inherited constructor
+ this._super(_parent);
+
+ // Make sure the needed class is there to get the CSS
+ this.tr.addClass('tile');
+ },
+
+ makeExpandable: function (_expandable, _callback, _context) {
+ // Nope. It mostly works, it's just weird.
+ },
+
+ getAvgHeightData: function() {
+ var res = {
+ "avgHeight": this.getHeight() / this.columns,
+ "avgCount": this.columns
+ };
+ return res;
+ },
+
+ /**
+ * Returns the height for the tile.
+ *
+ * This is where we do the magic. If a new row should start, we return the proper
+ * height. If this should be another tile in the same row, we say it has 0 height.
+ * @returns {Number}
+ */
+ getHeight: function() {
+ if(this._index % this.columns == 0)
+ {
+ return this._super();
+ }
+ else
+ {
+ return 0;
+ }
+ },
+
+ /**
+ * Broadcasts an invalidation through the container tree. Marks the own
+ * height as invalid.
+ */
+ invalidate: function() {
+ if(this._inTree && this.tr)
+ {
+ var template_width = $j('.innerContainer',this.tr).children().outerWidth(true);
+ if(template_width)
+ {
+
+ this.tr.css('width', template_width + (this.tr.outerWidth(true) - this.tr.width()));
+ }
+ }
+ this._recalculate_columns();
+ this._super();
+ },
+
+ /**
+ * Recalculate how many columns we can fit in a row.
+ * While browser takes care of the actual layout, we need this for proper
+ * pagination.
+ */
+ _recalculate_columns: function() {
+ if(this._inTree && this.tr && this.tr.parent())
+ {
+ this.columns = parseInt(this.tr.parent().innerWidth() / this.tr.outerWidth(true));
+ }
+ }
+});
diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js
index 4d79656bcd..c48d2a9769 100644
--- a/etemplate/js/et2_extension_nextmatch.js
+++ b/etemplate/js/et2_extension_nextmatch.js
@@ -138,6 +138,10 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
createNamespace: true,
columns: [],
+
+ // Current view, either row or tile. We store it here as controllers are
+ // recreated when the template changes.
+ view: 'row',
/**
* Constructor
@@ -1109,6 +1113,9 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
}
this.controller.setPrefix(this.options.settings.dataStorePrefix);
+ // Set the view
+ this.controller._view = this.view;
+
// Load the initial order
/*this.controller.loadInitialOrder(this._getInitialOrder(
this.options.settings.rows, this.options.settings.row_id
@@ -1651,6 +1658,26 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
}
},
+ /**
+ * Switch view between row and tile.
+ * This should be followed by a call to change the template to match, which
+ * will cause a reload of the grid using the new settings.
+ *
+ * @param {type} view
+ */
+ set_view: function(view)
+ {
+ // Restrict to the only 2 accepted values
+ if(view == 'tile')
+ {
+ this.view = 'tile';
+ }
+ else
+ {
+ this.view = 'row';
+ }
+ },
+
/**
* Set a different / additional handler for dropped files.
*
diff --git a/etemplate/js/et2_extension_nextmatch_controller.js b/etemplate/js/et2_extension_nextmatch_controller.js
index 94bddfc98a..98075d90fa 100644
--- a/etemplate/js/et2_extension_nextmatch_controller.js
+++ b/etemplate/js/et2_extension_nextmatch_controller.js
@@ -19,6 +19,7 @@
et2_dataview_view_row;
et2_dataview_controller;
et2_dataview_interfaces;
+ et2_dataview_view_tile;
et2_extension_nextmatch_actions; // Contains nm_action
@@ -30,6 +31,10 @@
*/
var et2_nextmatch_controller = et2_dataview_controller.extend(et2_IDataProvider,
{
+ // Display constants
+ VIEW_ROW: 'row',
+ VIEW_TILE: 'tile',
+
/**
* Initializes the nextmatch controller.
*
@@ -94,6 +99,8 @@ var et2_nextmatch_controller = et2_dataview_controller.extend(et2_IDataProvider,
// dataUnregisterUID
this.dataUnregisterUID = _egw.dataUnregisterUID;
+ // Default to rows
+ this._view = et2_nextmatch_controller.prototype.VIEW_ROW;
},
destroy: function () {
@@ -190,6 +197,34 @@ var et2_nextmatch_controller = et2_dataview_controller.extend(et2_IDataProvider,
/** -- PRIVATE FUNCTIONS -- **/
+ /**
+ * Create a new row, either normal or tiled
+ *
+ * @param {type} ctx
+ * @returns {et2_dataview_container}
+ */
+ _createRow: function(ctx) {
+ switch(this._view)
+ {
+ case et2_nextmatch_controller.prototype.VIEW_TILE:
+ var row = new et2_dataview_tile(this._grid);
+ // Try to overcome chrome rendering issue where float is not
+ // applied properly, leading to incomplete rows
+ window.setTimeout(function() {
+ if(!row.tr) return;
+ row.tr.css('float','none');
+ window.setTimeout(function() {
+ if(!row.tr) return;
+ row.tr.css('float','left');
+ },50);
+ },100);
+ return row;
+ case et2_nextmatch_controller.prototype.VIEW_ROW:
+ default:
+ return new et2_dataview_row(this._grid);
+ }
+ },
+
/**
* Initializes the action and the object manager.
*/
diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css
index 0b038393ab..f87233b0ca 100644
--- a/etemplate/templates/default/etemplate2.css
+++ b/etemplate/templates/default/etemplate2.css
@@ -1147,6 +1147,18 @@ div.message.floating {
margin-right: -11px;
}
/* End of hierarchy */
+
+/* Nextmatch tiled view */
+.et2_nextmatch .egwGridView_grid tr.tile {
+ display: inline-block;
+ width: 240px;
+ float: left;
+ padding: 2px;
+}
+.et2_nextmatch .egwGridView_grid tr.tile > td > div > *:first-child {
+ text-align: center;
+}
+
/* Mangled link-to widget inside a nextmatch - used for DnD uploads */
.et2_nextmatch * .et2_link_to {
position: fixed;
diff --git a/filemanager/js/app.js b/filemanager/js/app.js
index 75c62ba248..801120af17 100644
--- a/filemanager/js/app.js
+++ b/filemanager/js/app.js
@@ -606,6 +606,42 @@ app.classes.filemanager = AppJS.extend(
this.path_widget[etemplate_name].set_value(_dir);
},
+ /**
+ * Toggle view between tiles and rows
+ *
+ * @param {string} [view] - Specify what to change the view to. Either 'tile' or 'row'.
+ * @param {et2_widget} [button_widget] - The widget that's calling
+ */
+ change_view: function(view, button_widget)
+ {
+ var nm = this.et2.getWidgetById('nm');
+ if(!nm)
+ {
+ egw.debug('warn', 'Could not find nextmatch to change view');
+
+ return;
+ }
+
+ if(button_widget && button_widget.instanceOf(et2_button))
+ {
+ // Switch view based on button icon, since controller can get re-created
+ if(typeof view != 'string')
+ {
+ view = button_widget.options.image.replace('list_','');
+ }
+
+ // Toggle button icon to the other view
+ button_widget.set_image("list_"+(view == nm.controller.VIEW_ROW ? nm.controller.VIEW_TILE : nm.controller.VIEW_ROW));
+
+ button_widget.set_label(view == nm.controller.VIEW_ROW ? this.egw.lang("Tile view") : this.egw.lang('List view'));
+ }
+
+ nm.set_view(view);
+
+ // Change template to match
+ this.et2.getWidgetById('nm').set_template(view == nm.controller.VIEW_ROW ? 'filemanager.index.rows' : 'filemanager.tile');
+ },
+
/**
* Open/active an item
*
diff --git a/filemanager/templates/default/app.css b/filemanager/templates/default/app.css
index 5f9b30d101..ce3d69e691 100644
--- a/filemanager/templates/default/app.css
+++ b/filemanager/templates/default/app.css
@@ -64,6 +64,32 @@ div.filemanager_navigation > label > input {
max-height: none;
}
+/**
+ * Tile view
+table.egwGridView_grid .tile .file_tile {
+ height: 150px;
+}
+ */
+.egwGridView_grid .tile span.iconOverlayContainer {
+ display: block;
+ max-width: 140px;
+}
+table.egwGridView_grid .tile .file_tile img.vfsMimeIcon {
+ height: auto;
+ width: auto;
+ max-height: 120px;
+ display:block;
+ margin: 0 auto;
+}
+.egwGridView_grid tr.tile:hover .innerContainer {
+ overflow: visible;
+}
+.egwGridView_grid tr.tile:hover .file_tile > :not(.iconOverlayContainer) {
+ position: relative;
+ z-index:90;
+ background-color: white;
+}
+
/**
* Select file dialog
*/
diff --git a/filemanager/templates/default/images/list_row.png b/filemanager/templates/default/images/list_row.png
new file mode 100644
index 0000000000..189c68cae3
Binary files /dev/null and b/filemanager/templates/default/images/list_row.png differ
diff --git a/filemanager/templates/default/images/list_tile.png b/filemanager/templates/default/images/list_tile.png
new file mode 100644
index 0000000000..d3963f5e91
Binary files /dev/null and b/filemanager/templates/default/images/list_tile.png differ
diff --git a/filemanager/templates/default/index.xet b/filemanager/templates/default/index.xet
index b383990f78..7b8282d254 100644
--- a/filemanager/templates/default/index.xet
+++ b/filemanager/templates/default/index.xet
@@ -50,6 +50,7 @@
+
diff --git a/filemanager/templates/default/tile.xet b/filemanager/templates/default/tile.xet
new file mode 100644
index 0000000000..fb0e2ee549
--- /dev/null
+++ b/filemanager/templates/default/tile.xet
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/filemanager/templates/pixelegg/app.css b/filemanager/templates/pixelegg/app.css
index 540cafd5b0..a1dfab0340 100755
--- a/filemanager/templates/pixelegg/app.css
+++ b/filemanager/templates/pixelegg/app.css
@@ -94,6 +94,31 @@ div.filemanager_navigation > label > input {
text-overflow: ellipsis;
max-height: none;
}
+/**
+ * Tile view
+table.egwGridView_grid .tile .file_tile {
+ height: 150px;
+}
+ */
+.egwGridView_grid .tile span.iconOverlayContainer {
+ display: block;
+ max-width: 140px;
+}
+table.egwGridView_grid .tile .file_tile img.vfsMimeIcon {
+ height: auto;
+ width: auto;
+ max-height: 120px;
+ display: block;
+ margin: 0 auto;
+}
+.egwGridView_grid tr.tile:hover .innerContainer {
+ overflow: visible;
+}
+.egwGridView_grid tr.tile:hover .file_tile > :not(.iconOverlayContainer) {
+ position: relative;
+ z-index: 90;
+ background-color: white;
+}
/**
* Select file dialog
*/
diff --git a/filemanager/templates/pixelegg/images/list_row.png b/filemanager/templates/pixelegg/images/list_row.png
new file mode 100644
index 0000000000..333f56c66c
Binary files /dev/null and b/filemanager/templates/pixelegg/images/list_row.png differ
diff --git a/filemanager/templates/pixelegg/images/list_tile.png b/filemanager/templates/pixelegg/images/list_tile.png
new file mode 100644
index 0000000000..8ac4c9133e
Binary files /dev/null and b/filemanager/templates/pixelegg/images/list_tile.png differ