diff --git a/etemplate/js/et2_dataview_controller.js b/etemplate/js/et2_dataview_controller.js index 8046dc7cde..62eda33e65 100644 --- a/etemplate/js/et2_dataview_controller.js +++ b/etemplate/js/et2_dataview_controller.js @@ -15,7 +15,10 @@ et2_core_inheritance; et2_dataview_interfaces; + et2_dataview_view_aoi; et2_dataview_view_row; + + egw_action.egw_action; */ /** @@ -36,14 +39,31 @@ var et2_dataview_controller = Class.extend({ /** * Constructor of the et2_dataview_controller, connects to the grid * callback. + * + * @param _grid is the grid the controller should controll. + * @param _dataProvider is an object implementing the et2_IDataProvider + * interface. + * @param _rowCallback is the callback function that gets called when a row + * is requested. + * @param _linkCallback is the callback function that gets called for + * requesting action links for a row. The row data, the index of the row and + * the uid are passed as parameters to the function. + * uid is passed to the function. + * @param _context is the context in which the _rowCallback and the + * _linkCallback are called. + * @param _actionObjectManager is the object that manages the action + * objects. */ - init: function (_grid, _dataProvider, _rowCallback, _context) + init: function (_grid, _dataProvider, _rowCallback, _linkCallback, _context, + _actionObjectManager) { // Copy the given arguments this._grid = _grid; this._dataProvider = _dataProvider; this._rowCallback = _rowCallback; - this._rowContext = _context; + this._linkCallback = _linkCallback; + this._context = _context; + this._actionObjectManager = _actionObjectManager; // Initialize the "index map" which contains all currently displayed // containers hashed by the "index" @@ -133,7 +153,8 @@ var et2_dataview_controller = Class.extend({ { this._indexMap[_idx] = { "row": null, - "uid": null + "uid": null, + "ao": null }; } @@ -404,14 +425,38 @@ var et2_dataview_controller = Class.extend({ this.entry.row.clear(); // Fill the row DOM Node with data + var tr = this.entry.row.getDOMNode(); this.self._rowCallback.call( - this.self._rowContext, + this.self._context, _data, - this.entry.row.getDOMNode(), + tr, this.entry.idx, this.entry ); + // If we have an object manager, create a new action object for this + // row and a new row AOI + if (this.self._linkCallback && this.self._actionObjectManager) + { + // Call the link callback + var links = this.self._linkCallback.call( + this.self._context, + _data, + this.entry.idx, + this.entry.uid + ); + + // Create the action object + var aom = this.self._actionObjectManager; + this.entry.ao = aom.addObject( + this.entry.uid, + new et2_dataview_rowAOI(tr) + ); + + // Update the action links + this.entry.ao.updateActionLinks(links); + } + // Invalidate the current row entry this.entry.row.invalidate(); } diff --git a/etemplate/js/et2_dataview_view_aoi.js b/etemplate/js/et2_dataview_view_aoi.js new file mode 100644 index 0000000000..2a7cc8ee6e --- /dev/null +++ b/etemplate/js/et2_dataview_view_aoi.js @@ -0,0 +1,94 @@ +/** + * eGroupWare eTemplate2 - Contains interfaces used inside the dataview + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2012 + * @version $Id$ + */ + +"use strict"; + +/*egw:uses + egw_action.egw_action_common; + egw_action.egw_action; +*/ + +/** + * Contains the action object interface implementation for the nextmatch widget + * row. + */ + +/** + * An action object interface for each nextmatch widget row - "inherits" from + * egwActionObjectInterface + */ +function et2_dataview_rowAOI(_node) +{ + var aoi = new egwActionObjectInterface(); + + aoi.node = _node; + + aoi.checkBox = null; //($j(":checkbox", aoi.node))[0]; + + // Rows without a checkbox OR an id set are unselectable + aoi.doGetDOMNode = function() { + return aoi.node; + } + + // Prevent the browser from selecting the content of the element, when + // a special key is pressed. + $j(_node).mousedown(egwPreventSelect); + + // Now append some action code to the node + var selectHandler = function(e) { + + // Reset the focus so that keyboard navigation will work properly + // after the element has been clicked + egwUnfocus(); + + // Reset the prevent selection code (in order to allow wanted + // selection of text) + _node.onselectstart = null; + + if (e.target != aoi.checkBox) + { + var selected = egwBitIsSet(aoi.getState(), EGW_AO_STATE_SELECTED); + var state = EGW_AO_SHIFT_STATE_NONE; // Multiple row selction does not work right now + + aoi.updateState(EGW_AO_STATE_SELECTED, + !egwBitIsSet(state, EGW_AO_SHIFT_STATE_MULTI) || !selected, + state); + } + }; + + if (egwIsMobile()) { + _node.ontouchend = selectHandler; + } else { + $j(_node).click(selectHandler); + } + + $j(aoi.checkBox).change(function() { + aoi.updateState(EGW_AO_STATE_SELECTED, this.checked, EGW_AO_SHIFT_STATE_MULTI); + }); + + aoi.doSetState = function(_state) { + var selected = egwBitIsSet(_state, EGW_AO_STATE_SELECTED); + + if (this.checkBox) + { + this.checkBox.checked = selected; + } + + $j(this.node).toggleClass('focused', + egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); + $j(this.node).toggleClass('selected', + selected); + } + + return aoi; +} + diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index d24035f144..d66d1ac684 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -501,7 +501,8 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { // Create the nextmatch row provider this.rowProvider = new et2_nextmatch_rowProvider( - this.dataview.rowProvider); + this.dataview.rowProvider, this.options.settings.actionLinks, + this.options.settings.actions); // Register handler to update preferences when column properties are changed var self = this; @@ -515,7 +516,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { }; }, - _parseDataRow: function(_row, _colData) { + _parseDataRow: function(_row, _rowData, _colData) { var columnWidgets = new Array(this.columns.length); for (var x = 0; x < columnWidgets.length; x++) @@ -533,7 +534,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { } } - this.rowProvider.setDataRowTemplate(columnWidgets, this); + this.rowProvider.setDataRowTemplate(columnWidgets, _rowData, this); // Set the initial row count var total = typeof this.options.settings.total != "undefined" ? @@ -546,7 +547,11 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { this.getInstanceManager().etemplate_exec_id, "nm", this.dataview.grid, - this.rowProvider); + this.rowProvider, + null, + this.options.settings.action_links, + this.options.settings.actions + ); this.controller.setFilters(this.activeFilters); }, @@ -562,7 +567,8 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { } else { - this._parseDataRow(_grid.cells[y], _grid.colData); + this._parseDataRow(_grid.cells[y], _grid.rowData[y], + _grid.colData); } } }, diff --git a/etemplate/js/et2_extension_nextmatch_actions.js b/etemplate/js/et2_extension_nextmatch_actions.js new file mode 100644 index 0000000000..b4b767d25e --- /dev/null +++ b/etemplate/js/et2_extension_nextmatch_actions.js @@ -0,0 +1,275 @@ +/** + * eGroupWare eTemplate2 - JS Nextmatch object + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @author Ralf Becker + * @copyright Stylite 2012 + * @version $Id$ + */ + +/** + * Default action for nextmatch rows, runs action specified _action.data.nm_action: see nextmatch_widget::egw_actions() + * + * @param _action action object with attributes caption, id, nm_action, ... + * @param _senders array of rows selected + */ +function nm_action(_action, _senders) +{ + // ignore checkboxes, unless they have an explicit defined nm_action + if (_action.checkbox && (!_action.data || typeof _action.data.nm_action == 'undefined')) return; + + if (typeof _action.data == 'undefined' || !_action.data) _action.data = {}; + if (typeof _action.data.nm_action == 'undefined') _action.data.nm_action = 'submit'; + + var ids = ""; + for (var i = 0; i < _senders.length; i++) + { + ids += (_senders[i].id.indexOf(',') >= 0 ? '"'+_senders[i].id.replace(/"/g,'""')+'"' : _senders[i].id) + + ((i < _senders.length - 1) ? "," : ""); + } + //console.log(_action); console.log(_senders); + + var mgr = _action.getManager(); + + var select_all = mgr.getActionById("select_all"); + var confirm_msg = (_senders.length > 1 || select_all && select_all.checked) && + typeof _action.data.confirm_multiple != 'undefined' ? + _action.data.confirm_multiple : _action.data.confirm; + + // let user confirm the action first (if not select_all set and nm_action == 'submit' --> confirmed later) + if (!(select_all && select_all.checked && _action.data.nm_action == 'submit') && + typeof _action.data.confirm != 'undefined') + { + if (!confirm(confirm_msg)) return; + } + // in case we only need to confirm multiple selected (only _action.data.confirm_multiple) + else if (typeof _action.data.confirm_multiple != 'undefined' && (_senders.length > 1 || select_all && select_all.checked)) + { + if (!confirm(_action.data.confirm_multiple)) return; + } + + var url = '#'; + if (typeof _action.data.url != 'undefined') + { + url = _action.data.url.replace(/(\$|%24)id/,encodeURIComponent(ids)); + } + + var target = null; + if (typeof _action.data.target != 'undefined') + { + target = _action.data.target; + } + + switch(_action.data.nm_action) + { + case 'alert': + alert(_action.caption + " (\'" + _action.id + "\') executed on rows: " + ids); + break; + + case 'location': + if (typeof _action.data.targetapp != 'undefined') + { + top.egw_appWindowOpen(_action.data.targetapp, url); + } + else if(target) + { + window.open(url, target); + } + else + { + window.location.href = url; + } + break; + + case 'popup': + egw_openWindowCentered2(url,target,_action.data.width,_action.data.height); + break; + + case 'egw_open': + var params = _action.data.egw_open.split('-'); // type-appname-idNum (idNum is part of id split by :), eg. "edit-infolog" + console.log(params); + var egw_open_id = _senders[0].id; + if (typeof params[2] != 'undefined') egw_open_id = egw_open_id.split(':')[params[2]]; + egw_open(egw_open_id,params[1],params[0],params[3]); + break; + + case 'open_popup': + // open div styled as popup contained in current form and named action.id+'_popup' + if (nm_popup_action == null) + { + nm_open_popup(_action, _senders); + break; + } + // fall through, if popup is open --> submit form + case 'submit': + // let user confirm select-all + if (select_all && select_all.checked) + { + if (!confirm((confirm_msg ? confirm_msg : _action.caption.replace(/^( | | )+/,''))+"\n\n"+select_all.hint)) return; + } + var checkboxes = mgr.getActionsByAttr("checkbox", true); + var checkboxes_elem = document.getElementById(mgr.etemplate_var_prefix+'[nm][checkboxes]'); + if (checkboxes && checkboxes_elem) + for (var i in checkboxes) + checkboxes_elem.value += checkboxes[i].id + ":" + (checkboxes[i].checked ? "1" : "0") + ";"; + + document.getElementById(mgr.etemplate_var_prefix+'[nm][nm_action]').value = _action.id; + document.getElementById(mgr.etemplate_var_prefix+'[nm][selected]').value = ids; + if (typeof _action.data.button != 'undefined') + { + submitit(mgr.etemplate_form.context, mgr.etemplate_var_prefix+'[nm][rows]['+_action.data.button+']['+ids+']'); + } + else + { + mgr.etemplate_form.submit(); + } + // Clear action in case there's another one + document.getElementById(mgr.etemplate_var_prefix+'[nm][nm_action]').value = null; + + break; + } +} + +/** + * Callback to check if none of _senders rows has disableClass set + * + * @param _action egwAction object, we use _action.data.disableClass to check + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, get's called for every object in _senders + * @returns boolean true if none has disableClass, false otherwise + */ +function nm_not_disableClass(_action, _senders, _target) +{ + return !$j(_target.iface.getDOMNode()).hasClass(_action.data.disableClass); +} + +/** + * Callback to check if all of _senders rows have enableClass set + * + * @param _action egwAction object, we use _action.data.enableClass to check + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, get's called for every object in _senders + * @returns boolean true if none has disableClass, false otherwise + */ +function nm_enableClass(_action, _senders, _target) +{ + return $j(_target.iface.getDOMNode()).hasClass(_action.data.enableClass); +} + +/** + * Enable an _action, if it matches a given regular expresstion in _action.data.enableId + * + * @param _action egwAction object, we use _action.data.enableId to check + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, get's called for every object in _senders + * @returns boolean true if _target.id matches _action.data.enableId + */ +function nm_enableId(_action, _senders, _target) +{ + if (typeof _action.data.enableId == 'string') + _action.data.enableId = new RegExp(_action.data.enableId); + + return _target.id.match(_action.data.enableId); +} + +/** + * Callback to check if a certain field (_action.data.fieldId) is (not) equal to given value (_action.data.fieldValue) + * + * If field is not found, we return false too! + * + * @param _action egwAction object, we use _action.data.fieldId to check agains _action.data.fieldValue + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, get's called for every object in _senders + * @returns boolean true if field found and has specified value, false otherwise + */ +function nm_compare_field(_action, _senders, _target) +{ + var field = document.getElementById(_action.data.fieldId); + + if (!field) return false; + + var value = $j(field).val(); + + if (_action.data.fieldValue.substr(0,1) == '!') + return value != _action.data.fieldValue.substr(1); + + return value == _action.data.fieldValue; +} + +var nm_popup_action, nm_popup_senders = null; + +/** + * Open popup for a certain action requiring further input + * + * Popup needs to have eTemplate name of action id plus "_popup" + * + * @param _action + * @param _senders + */ +function nm_open_popup(_action, _senders) +{ + var popup = document.getElementById(_action.getManager().etemplate_var_prefix + '[' + _action.id + '_popup]'); + + if (popup) { + nm_popup_action = _action; + nm_popup_senders = _senders; + popup.style.display = 'block'; + } +} + +/** + * Submit a popup action + */ +function nm_submit_popup(button) +{ + button.form.submit_button.value = button.name; // set name of button (sub-action) + + // call regular nm_action to transmit action and senders correct + nm_action(nm_popup_action, nm_popup_senders); +} + +/** + * Hide popup + */ +function nm_hide_popup(element, div_id) +{ + var prefix = element.id.substring(0,element.id.indexOf('[')); + var popup = document.getElementById(prefix+'['+div_id+']'); + + // Hide popup + if(popup) { + popup.style.display = 'none'; + } + nm_popup_action = null; + nm_popup_senders = null; + + return false; +} + +/** + * Activate/click first link in row + */ +function nm_activate_link(_action, _senders) +{ + // $j(_senders[0].iface.getDOMNode()).find('a:first').trigger('click'); not sure why this is NOT working + + var a_href = $j(_senders[0].iface.getDOMNode()).find('a:first'); + + if (typeof a_href != undefined) + { + var target = a_href.attr('target'); + var href = a_href.attr('href'); + if (a_href.attr('onclick')) + a_href.click(); + else if (target) + window.open(href,target); + else + window.location = href; + } +} + + diff --git a/etemplate/js/et2_extension_nextmatch_controller.js b/etemplate/js/et2_extension_nextmatch_controller.js index 112a395d20..c222efa115 100644 --- a/etemplate/js/et2_extension_nextmatch_controller.js +++ b/etemplate/js/et2_extension_nextmatch_controller.js @@ -17,11 +17,11 @@ et2_core_inheritance; et2_dataview_view_row; - et2_dataview_controller; - et2_dataview_interfaces; + et2_extension_nextmatch_actions; // Contains nm_action + egw_data; */ @@ -35,11 +35,37 @@ var et2_nextmatch_controller = et2_dataview_controller.extend( * @param _execId is the execId of the etemplate * @param _widgetId is the id of the nextmatch-widget we are fetching data * for. + * @param _grid is the grid the grid controller will be controlling + * @param _rowProvider is the nextmatch row provider instance. + * @param _objectManager is the parent object manager (if null, the object + * manager) will be created using + * @param _actionLinks contains the action links + * @param _actions contains the actions, may be null if an object manager + * is given. */ - init: function (_egw, _execId, _widgetId, _grid, _rowProvider) { + init: function (_egw, _execId, _widgetId, _grid, _rowProvider, + _objectManager, _actionLinks, _actions) { + + // Create the action/object managers + if (_objectManager === null) + { + this._actionManager = new egwActionManager(); + this._actionManager.updateActions(_actions); + this._actionManager.setDefaultExecute("javaScript:nm_action"); + + this._objectManager = new egwActionObjectManager("", + this._actionManager); + } + else + { + this._actionManager = null; + this._objectManager = _objectManager; + } + this._actionLinks = _actionLinks; // Call the parent et2_dataview_controller constructor - this._super(_grid, this, this._rowCallback, this); + this._super(_grid, this, this._rowCallback, this._linkCallback, this, + this._objectManager); // Copy all parameters this.egw = _egw; @@ -56,6 +82,19 @@ var et2_nextmatch_controller = et2_dataview_controller.extend( this.dataUnregisterUID = _egw.dataUnregisterUID; }, + destroy: function () { + + // If the actionManager variable is set, the object- and actionManager + // were created by this instance -- clear them + if (this._actionManager) + { + this._objectManager.clear(); + this._actionManager.clear(); + } + + this._super(); + }, + /** * Updates the filter instance. */ @@ -104,6 +143,15 @@ var et2_nextmatch_controller = et2_dataview_controller.extend( { "content": _data }, _tr, _idx); }, + /** + * Returns the action links for a given data row -- currently these are + * always the same links, as we controll enabled/disabled over the row + * classes. + */ + _linkCallback: function (_data, _idx, _uid) { + return this._actionLinks; + }, + /** -- Implementation of et2_IDataProvider -- **/ diff --git a/etemplate/js/et2_extension_nextmatch_rowProvider.js b/etemplate/js/et2_extension_nextmatch_rowProvider.js index 37f66817d5..28523ba740 100644 --- a/etemplate/js/et2_extension_nextmatch_rowProvider.js +++ b/etemplate/js/et2_extension_nextmatch_rowProvider.js @@ -33,9 +33,14 @@ var et2_nextmatch_rowProvider = Class.extend({ }, /** - * Creates the data row prototype + * Creates the data row prototype. + * + * @param _widgets is an array containing the root widget for each column. + * @param _rowData contains the properties of the root "tr" (like its class) + * @param _rootWidget is the parent widget of the data rows (i.e. + * the nextmatch) */ - setDataRowTemplate: function(_widgets, _rootWidget) { + setDataRowTemplate: function(_widgets, _rowData, _rootWidget) { // Copy the root widget this._rootWidget = _rootWidget; @@ -45,6 +50,7 @@ var et2_nextmatch_rowProvider = Class.extend({ // Copy the row template var rowTemplate = { "row": row[0], + "rowData": _rowData, "widgets": _widgets, "root": _rootWidget, "seperated": null, @@ -135,8 +141,6 @@ var et2_nextmatch_rowProvider = Class.extend({ // from the entry nodes[j] = entry.nodeFuncs[j](row); } - if(typeof nodes[0] == "undefined") - egw.debug("warn", "Missing node", entry.widget.id,nodes, entry ); // Set the array managers first entry.widget._mgrs = mgrs; @@ -155,6 +159,9 @@ var et2_nextmatch_rowProvider = Class.extend({ // Insert the row into the tr _tr.appendChild(row); + // Set the row data + this._setRowData(this._template.rowData, _tr, mgrs); + return rowWidget; }, @@ -399,6 +406,17 @@ var et2_nextmatch_rowProvider = Class.extend({ nodes[j]); } } + }, + + /** + * Applies additional row data (like the class) to the tr + */ + _setRowData: function (_data, _tr, _mgrs) { + // TODO: Implement other fields than "class" + if (_data["class"]) + { + _tr.setAttribute("class", _mgrs["content"].expandName(_data["class"])); + } } });