Просмотр исходного кода

+ handle Tools for @selected features

Piotr Labudda 7 лет назад
Родитель
Сommit
388c0aca90

+ 10 - 0
SE/se-lib/Api/Process/P5/GetSelectedFeatures.php

@@ -23,6 +23,15 @@ class Api_Process_P5_GetSelectedFeatures { // TODO: extends Api_ProcessBase
 			$totalSelected = FeatureAttrSelected::getTotalSelected($typeName);
 			$totalSelected = FeatureAttrSelected::getTotalSelected($typeName);
 		}
 		}
 
 
+		$idAcl = DB::getPDO()->fetchValue("
+			select t.idZasob
+			from `CRM_#CACHE_ACL_OBJECT` t
+			where t.namespace = :namespace
+				and t.idZasob IS NOT NULL
+		", [
+			':namespace' => Api_WfsNs::toNamespace($typeName),
+		]);
+
 		header('Content-Type: application/json');
 		header('Content-Type: application/json');
 		echo json_encode([
 		echo json_encode([
 			'type' => "success",
 			'type' => "success",
@@ -30,6 +39,7 @@ class Api_Process_P5_GetSelectedFeatures { // TODO: extends Api_ProcessBase
 			'body' => [
 			'body' => [
 				'selected' => $listSelectedState,
 				'selected' => $listSelectedState,
 				'totalSelected' => $totalSelected,
 				'totalSelected' => $totalSelected,
+				'tools' => $idAcl ? Route_UrlAction::getSelectedFeatureTools($idAcl, User::getLogin()) : [],
 			]
 			]
 		]);
 		]);
 		exit;
 		exit;

+ 1 - 1
SE/se-lib/Core/AclHelper.php

@@ -255,7 +255,7 @@ class Core_AclHelper {// Helper class for Acl
 			}
 			}
 		}
 		}
 
 
-		if ($urlFunctions = Route_UrlAction::getTableFunctions($acl->getID(), $id, $acl->getName(), User::getLogin())) {
+		if ($urlFunctions = Route_UrlAction::getFeatureTools($acl->getID(), $id, User::getLogin())) {
 			foreach ($urlFunctions as $urlFunction) {
 			foreach ($urlFunctions as $urlFunction) {
 				// TODO: is allowed to view - test by Router::getRoute('UrlAction')->isFunctionAllowedForRecord($routeName = $urlFunction['name'], $acl->getID(), $id);
 				// TODO: is allowed to view - test by Router::getRoute('UrlAction')->isFunctionAllowedForRecord($routeName = $urlFunction['name'], $acl->getID(), $id);
 				$rowFunction = array();
 				$rowFunction = array();

+ 10 - 0
SE/se-lib/FeatureAttrSelected.php

@@ -12,6 +12,16 @@ class FeatureAttrSelected {
 		");
 		");
 	}
 	}
 
 
+	static function getAllSelected($typeName) {
+		$idUser = User::getID();
+		self::prepareUserTable($typeName, $idUser);
+		$tableName = self::getAttributeTableName($typeName, $idUser);
+		return DB::getPDO()->fetchValuesList("
+			select primaryKey
+			from `{$tableName}`
+		");
+	}
+
 	static function getSelectState($typeName, array $listPrimaryKeys) {
 	static function getSelectState($typeName, array $listPrimaryKeys) {
 		$idUser = User::getID();
 		$idUser = User::getID();
 		self::prepareUserTable($typeName, $idUser);
 		self::prepareUserTable($typeName, $idUser);

+ 72 - 45
SE/se-lib/Route/UrlAction.php

@@ -89,8 +89,7 @@ class Route_UrlAction extends RouteBase {// TODO: UrlActionBase
 			22317  URL_ACTION ProjektyKosztyWstepnychRobot
 			22317  URL_ACTION ProjektyKosztyWstepnychRobot
 				22324  PARAM_IN ID_PROJECT
 				22324  PARAM_IN ID_PROJECT
 	 */
 	 */
-	public static function fetchTableFunctions($idTbl, $idRecord) {
-		$sqlIdZasob = DB::getPDO()->quote($idTbl, PDO::PARAM_STR);
+	static function fetchTableFunctions($idTbl) {
 		return DB::getPDO()->fetchAll("
 		return DB::getPDO()->fetchAll("
 			select z.ID as ID
 			select z.ID as ID
 				, IF(z.DESC_PL != '', z.DESC_PL, IF(za.DESC_PL != '', za.DESC_PL, z.`DESC`)) as fun_label
 				, IF(z.DESC_PL != '', z.DESC_PL, IF(za.DESC_PL != '', za.DESC_PL, z.`DESC`)) as fun_label
@@ -101,14 +100,14 @@ class Route_UrlAction extends RouteBase {// TODO: UrlActionBase
 				join CRM_LISTA_ZASOBOW za on(za.ID = z.ALIAS_ID)
 				join CRM_LISTA_ZASOBOW za on(za.ID = z.ALIAS_ID)
 				left join CRM_LISTA_ZASOBOW zp on(zp.PARENT_ID = z.ID and zp.`TYPE` = 'PARAM_IN')
 				left join CRM_LISTA_ZASOBOW zp on(zp.PARENT_ID = z.ID and zp.`TYPE` = 'PARAM_IN')
 				left join CRM_LISTA_ZASOBOW zpa on(zpa.ID = zp.ALIAS_ID and zpa.`TYPE` = 'PARAM_IN' and zpa.`DESC` = zp.`DESC`)
 				left join CRM_LISTA_ZASOBOW zpa on(zpa.ID = zp.ALIAS_ID and zpa.`TYPE` = 'PARAM_IN' and zpa.`DESC` = zp.`DESC`)
-			where z.PARENT_ID = {$sqlIdZasob}
+			where z.PARENT_ID = :id_table
 				and z.`TYPE` = 'URL_ACTION'
 				and z.`TYPE` = 'URL_ACTION'
-		");
+		", [
+			':id_table' => $idTbl,
+		]);
 	}
 	}
 
 
-	public static function fetchTableFunctionsForUser($idTbl, $idRecord, $usrLogin) {
-		$sqlIdZasob = DB::getPDO()->quote($idTbl, PDO::PARAM_STR);
-		$sqlUserLogin = DB::getPDO()->quote($usrLogin, PDO::PARAM_STR);
+	static function fetchTableFunctionsForUser($idTbl, $usrLogin) {
 		return DB::getPDO()->fetchAll("
 		return DB::getPDO()->fetchAll("
 			select z.ID as ID
 			select z.ID as ID
 				, IF(z.DESC_PL != '', z.DESC_PL, IF(za.DESC_PL != '', za.DESC_PL, z.`DESC`)) as fun_label
 				, IF(z.DESC_PL != '', z.DESC_PL, IF(za.DESC_PL != '', za.DESC_PL, z.`DESC`)) as fun_label
@@ -125,57 +124,83 @@ class Route_UrlAction extends RouteBase {// TODO: UrlActionBase
 		--		join CRM_PROCES p on(p.ID = w.ID_PROCES)
 		--		join CRM_PROCES p on(p.ID = w.ID_PROCES)
 				join CRM_PROCES_idx_USER_to_PROCES_VIEW upv on(upv.ID_PROCES = w.ID_PROCES)
 				join CRM_PROCES_idx_USER_to_PROCES_VIEW upv on(upv.ID_PROCES = w.ID_PROCES)
 				left join CRM_LISTA_ZASOBOW param on(param.PARENT_ID = z.ID and param.`TYPE` = 'DANE')
 				left join CRM_LISTA_ZASOBOW param on(param.PARENT_ID = z.ID and param.`TYPE` = 'DANE')
-			where z.PARENT_ID = {$sqlIdZasob}
+			where z.PARENT_ID = :id_table
 				and z.`TYPE` = 'URL_ACTION'
 				and z.`TYPE` = 'URL_ACTION'
-				and upv.ADM_ACCOUNT = {$sqlUserLogin}
+				and upv.ADM_ACCOUNT = :user_login
 		--	group by z.ID
 		--	group by z.ID
-		");
+		", [
+			':id_table' => $idTbl,
+			':user_login' => $usrLogin,
+		]);
 	}
 	}
 
 
-	public static function getTableFunctions($idTbl, $idRecord, $tblName = '', $usrLogin = null) {
-		$rows = array();
-		if (!empty($usrLogin)) {
-			$rows = self::fetchTableFunctionsForUser($idTbl, $idRecord, $usrLogin);
-		} else {
-			$rows = self::fetchTableFunctions($idTbl, $idRecord);
-		}
-		DBG::log($rows, 'array', "getTableFunctions({$idTbl}, {$idRecord}, ...) :: rows");
-		$functions = array();
-		foreach ($rows as $row) {
-			// TODO: Router::getRoute("UrlAction_{$row['fun_name']}")->isRecordAllowed($idTbl, $idRecord, $tblName);
-			if (!array_key_exists($row['ID'], $functions)) {
+	static function getFeatureTools($idTable, $idFeature, $usrLogin = null) {
+		return array_filter(
+			self::_fetchAllTools($idTable, $usrLogin),
+			function ($func) use ($idFeature) {
+				return ("rowFunction" === $func['type']);
+			}
+		);
+	}
+	static function getObjectTools($idTable, $usrLogin = null) {
+		return array_filter(
+			self::_fetchAllTools($idTable, $usrLogin),
+			function ($func) use ($idFeature) {
+				return ("table" === $func['type']);
+			}
+		);
+	}
+	static function getSelectedFeatureTools($idTable, $usrLogin = null) {
+		return array_filter(
+			self::_fetchAllTools($idTable, $usrLogin),
+			function ($func) use ($idFeature) {
+				return ("@selected" === $func['type']);
+			}
+		);
+	}
+	static function _fetchAllTools($idTable, $usrLogin = null) {
+		$rows = (!empty($usrLogin))
+			? self::fetchTableFunctionsForUser($idTable, $usrLogin)
+			: $rows = self::fetchTableFunctions($idTable)
+		;
+		DBG::log($rows, 'array', "_fetchAllTools({$idTbl}, {$idRecord}, ...) :: rows");
+		$functions = array_reduce($rows, function ($ret, $item) {
+			if (!array_key_exists($item['ID'], $ret)) {
 				$fun = array();
 				$fun = array();
-				$fun['label'] = $row['fun_label'];
-				$fun['name'] = $row['fun_name'];
-				$fun['baseLink'] = "index.php?_route=UrlAction_{$row['fun_name']}";
+				$fun['type'] = "table"; // default type - 'table', may be: '@selected' or 'rowFunction'
+				$fun['label'] = $item['fun_label'];
+				$fun['name'] = $item['fun_name'];
+				$fun['baseLink'] = "index.php?_route=UrlAction_{$item['fun_name']}";
 				$fun['ico'] = "glyphicon glyphicon-share";
 				$fun['ico'] = "glyphicon glyphicon-share";
 				$fun['cell_id_params'] = array();
 				$fun['cell_id_params'] = array();
-				$fun['link_target'] = '_blank';// LINK_TARGET_SELF
+				$fun['link_target'] = '_blank'; // LINK_TARGET_SELF
 				$fun['url_args'] = []; // Zasob `TYPE` = 'DANE' whhere `DESC` != 'LINK_TARGET_SELF'
 				$fun['url_args'] = []; // Zasob `TYPE` = 'DANE' whhere `DESC` != 'LINK_TARGET_SELF'
-				$functions[ $row['ID'] ] = $fun;
+				$ret[ $item['ID'] ] = $fun;
 			}
 			}
-			$funParams = $functions[ $row['ID'] ]['cell_id_params'];
-			if (!empty($row['param_in_name'])) {
-				if ($row['param_in_to_cell_id'] > 0) {// ALIAS to field - get field value from row
-					$funParams[ $row['param_in_to_cell_id'] ] = $row['param_in_name'];
-					$functions[ $row['ID'] ]['cell_id_params'] = $funParams;
+
+			$funParams = $ret[ $item['ID'] ]['cell_id_params'];
+			if ("@selected" === $item['param_in_name']) {
+				$ret[ $item['ID'] ]['type'] = "@selected";
+			} else if (!empty($item['param_in_name'])) {
+				if ($item['param_in_to_cell_id'] > 0) {// ALIAS to field - get field value from item
+					$funParams[ $item['param_in_to_cell_id'] ] = $item['param_in_name'];
+					$ret[ $item['ID'] ]['cell_id_params'] = $funParams;
+					$ret[ $item['ID'] ]['type'] = "rowFunction";
 				}
 				}
-				else if (false !== strpos($row['param_in_name'], '=')) {// "{arg_name}={value}" - add to url
-					$functions[ $row['ID'] ][ 'baseLink' ] .= "&" . $row['param_in_name'];
+				else if (false !== strpos($item['param_in_name'], '=')) {// "{arg_name}={value}" - add to url
+					$ret[ $item['ID'] ][ 'baseLink' ] .= "&" . $item['param_in_name'];
 				}
 				}
 			}
 			}
-			if ('LINK_TARGET_SELF' == $row['link_param']) {
-				unset($functions[ $row['ID'] ]['link_target']);
+			if ('LINK_TARGET_SELF' == $item['link_param']) {
+				unset($ret[ $item['ID'] ]['link_target']);
 			}
 			}
-			else if (!empty($row['link_param'])) {
-				$functions[ $row['ID'] ]['url_args'][] = ( strpos($row['link_param'], '=') ? $row['link_param'] : $row['link_param'] . "=1" );
+			else if (!empty($item['link_param'])) {
+				$ret[ $item['ID'] ]['url_args'][] = ( strpos($item['link_param'], '=') ? $item['link_param'] : $item['link_param'] . "=1" );
 			}
 			}
-		}
-		$functions = array_filter($functions, function ($rowFunction) use ($idRecord) {
-				return ($idRecord > 0)
-				?	!empty($rowFunction['cell_id_params'])
-				:	empty($rowFunction['cell_id_params']);
-		});
+
+			return $ret;
+		}, []);
+
 		$functions = array_map(function ($fun) { // fix url_args
 		$functions = array_map(function ($fun) { // fix url_args
 			$urlArgs = array_unique($fun['url_args']);
 			$urlArgs = array_unique($fun['url_args']);
 			unset($fun['url_args']);
 			unset($fun['url_args']);
@@ -184,7 +209,9 @@ class Route_UrlAction extends RouteBase {// TODO: UrlActionBase
 			}
 			}
 			return $fun;
 			return $fun;
 		}, $functions);
 		}, $functions);
-		DBG::log($functions, 'array', "getTableFunctions({$idTbl}, {$idRecord}, ...) :: functions");
+
+		DBG::log($functions, 'array', "_fetchAllTools({$idTbl}, {$idRecord}, ...) :: all functions");
+
 		return $functions;
 		return $functions;
 	}
 	}
 
 

+ 1 - 1
SE/se-lib/Route/ViewTableAjax.php

@@ -1601,7 +1601,7 @@ class Route_ViewTableAjax extends RouteBase {
 		if (!$namespace) throw new Exception("Missing namespace");
 		if (!$namespace) throw new Exception("Missing namespace");
 		$acl = Core_AclHelper::getAclByNamespace($namespace, $forceTblAclInit = ('1' == V::get('_force', '', $_GET)));
 		$acl = Core_AclHelper::getAclByNamespace($namespace, $forceTblAclInit = ('1' == V::get('_force', '', $_GET)));
 
 
-		$listUrlFunctions = Route_UrlAction::getTableFunctions($acl->getID(), $idRecord = 0, $acl->getName(), User::getLogin());
+		$listUrlFunctions = Route_UrlAction::getObjectTools($acl->getID(), User::getLogin());
 		DBG::log($listUrlFunctions, 'array', "\$listUrlFunctions");
 		DBG::log($listUrlFunctions, 'array', "\$listUrlFunctions");
 
 
 		$listUrlFunctions = array_map(function ($urlFunction) use ($namespace) {
 		$listUrlFunctions = array_map(function ($urlFunction) use ($namespace) {

+ 1 - 0
SE/se-lib/TableAjax.php

@@ -534,6 +534,7 @@ class TableAjax extends ViewAjax {
 				[ 'create' =>  [ 'href' => '#CREATE', 'icon' => 'plus', 'title' => "Dodaj nowy rekord" ] ],
 				[ 'create' =>  [ 'href' => '#CREATE', 'icon' => 'plus', 'title' => "Dodaj nowy rekord" ] ],
 				[ 'refresh' => [ 'icon' => 'refresh', 'title' => "Odśwież dane", 'method' => "refresh" ] ]
 				[ 'refresh' => [ 'icon' => 'refresh', 'title' => "Odśwież dane", 'method' => "refresh" ] ]
 			),
 			),
+			'SELECTED_FUNCTIONS' => Route_UrlAction::getSelectedFeatureTools($acl->getID(), User::getLogin()),
 			'EXPORT_FIELDS' => $exportFields,
 			'EXPORT_FIELDS' => $exportFields,
 		]);
 		]);
 		UI::inlineJS(__FILE__ . '.upload.js', [
 		UI::inlineJS(__FILE__ . '.upload.js', [

+ 7 - 2
SE/se-lib/TableAjax.php.TableAjax.js

@@ -734,6 +734,7 @@ var TableAjax = function() {
 		rowFunctions: false,
 		rowFunctions: false,
 		tblFunctions: false,
 		tblFunctions: false,
 		specialFilterFunctions: false,
 		specialFilterFunctions: false,
+		selectedFunctions: false,
 		filtersClean: false,
 		filtersClean: false,
 		router: false,
 		router: false,
 		longDesc: false,
 		longDesc: false,
@@ -919,6 +920,7 @@ var TableAjax = function() {
 						namespace: priv.options.namespace,
 						namespace: priv.options.namespace,
 						store: priv.options.selectedStore,
 						store: priv.options.selectedStore,
 						actions: priv.options.selectedActions,
 						actions: priv.options.selectedActions,
+						tools: priv.options.selectedFunctions,
 						onSelectAllMatching: function () {
 						onSelectAllMatching: function () {
 							// TODO: priv.filter_mapStoreToState(priv.options.filterStore.getState())
 							// TODO: priv.filter_mapStoreToState(priv.options.filterStore.getState())
 							var filterQuery = priv.filter_getFilterQuery();
 							var filterQuery = priv.filter_getFilterQuery();
@@ -1121,8 +1123,6 @@ var TableAjax = function() {
 		topWrap.appendChild(topRightWrap)
 		topWrap.appendChild(topRightWrap)
 		topWrap.style.padding = '12px 0'
 		topWrap.style.padding = '12px 0'
 		topWrap.style.borderTop = '1px solid #ddd'
 		topWrap.style.borderTop = '1px solid #ddd'
-		topWrap.style.borderBottom = '1px solid #ddd'
-		topWrap.style.overflow = 'hidden'
 		topLeftWrap.style.width = '' + priv.getStickyColsSumWidth() + 'px'
 		topLeftWrap.style.width = '' + priv.getStickyColsSumWidth() + 'px'
 		topLeftWrap.style.float = 'left'
 		topLeftWrap.style.float = 'left'
 		topLeftWrap.style.fontSize = '12px'
 		topLeftWrap.style.fontSize = '12px'
@@ -1131,6 +1131,11 @@ var TableAjax = function() {
 		topLeftWrap.appendChild(_uiNodeSelectedInfo)
 		topLeftWrap.appendChild(_uiNodeSelectedInfo)
 		topRightWrap.appendChild(_uiNodeSpecialFilters)
 		topRightWrap.appendChild(_uiNodeSpecialFilters)
 		_uiNodeCont.parentNode.insertBefore(topWrap, _uiNodeCont)
 		_uiNodeCont.parentNode.insertBefore(topWrap, _uiNodeCont)
+		var afterTopWrap = document.createElement('div')
+		afterTopWrap.style.paddingTop = '12px'
+		afterTopWrap.style.borderBottom = '1px solid #ddd'
+		afterTopWrap.style.clear = 'both'
+		_uiNodeCont.parentNode.insertBefore(afterTopWrap, _uiNodeCont)
 
 
 		priv.renderInlineEditBox();// .tblAjax__inlineEditBox
 		priv.renderInlineEditBox();// .tblAjax__inlineEditBox
 
 

+ 1 - 0
SE/se-lib/TableAjax.php.init.js

@@ -31,6 +31,7 @@ jQuery(document).ready(function(){
 		rowFunctions: ROW_FUNCTIONS,
 		rowFunctions: ROW_FUNCTIONS,
 		exportFields: EXPORT_FIELDS,
 		exportFields: EXPORT_FIELDS,
 		specialFilterFunctions: SPECIAL_FILTER_FUNCTIONS,
 		specialFilterFunctions: SPECIAL_FILTER_FUNCTIONS,
+		selectedFunctions: SELECTED_FUNCTIONS,
 		router: tableAjaxRouter,
 		router: tableAjaxRouter,
 		filtersClean: true,
 		filtersClean: true,
 		longDesc: true
 		longDesc: true

+ 153 - 11
SE/se-lib/TableAjax.php.p5UI__selected.js

@@ -4,6 +4,19 @@ if (!global.p5VendorJs) throw "Missing p5 Vendor js libs";
 var createReactClass = global.p5VendorJs.createReactClass;
 var createReactClass = global.p5VendorJs.createReactClass;
 var h = global.p5VendorJs.React.createElement;
 var h = global.p5VendorJs.React.createElement;
 var p5UI__FieldCheckboxLoading = global.p5VendorJs['p5UI__FieldCheckboxLoading'];
 var p5UI__FieldCheckboxLoading = global.p5VendorJs['p5UI__FieldCheckboxLoading'];
+if (!global.p5UI__clickedOutsideElement) throw "Missing p5UI__clickedOutsideElement";
+var clickedOutsideElement = global.p5UI__clickedOutsideElement
+
+function p5UI__renderSelectedActionButton(props) { // props: { baseLink: URL, ico: BS_ICO_NAME, label: LABEL }
+	return h('a', {
+		href: props.baseLink,
+		target: "_blank",
+		// onClick: this.makeHandleClickTool(idTool) // TODO: move to POST with output in right sidebar
+	}, [
+		(props.ico) && h('i', { className: "glyphicon glyphicon-" + props.ico, style: { marginRight: "6px" } }),
+		props.label
+	]);
+}
 
 
 var P5UI__TableAjaxRowCheckbox = createReactClass({
 var P5UI__TableAjaxRowCheckbox = createReactClass({
 	// props.store: Redux store { getState(), subscribe(f), dispatch(f) }
 	// props.store: Redux store { getState(), subscribe(f), dispatch(f) }
@@ -75,7 +88,9 @@ var P5UI__TableAjaxSelectedInfo = createReactClass({
 		};
 		};
 	},
 	},
 	getInitialState: function () {
 	getInitialState: function () {
-		return this.getStateFromStore();
+		return Object.assign({}, {
+			open: false
+		}, this.getStateFromStore());
 	},
 	},
 	componentDidMount: function () {
 	componentDidMount: function () {
 		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::componentDidMount (ns:'+this.props.namespace+')');
 		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::componentDidMount (ns:'+this.props.namespace+')');
@@ -90,9 +105,11 @@ var P5UI__TableAjaxSelectedInfo = createReactClass({
 	},
 	},
 	shouldComponentUpdate: function (nextProps, nextState) {
 	shouldComponentUpdate: function (nextProps, nextState) {
 		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::shouldComponentUpdate (ns:'+this.props.namespace+')', { state: this.state, nextState});
 		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::shouldComponentUpdate (ns:'+this.props.namespace+')', { state: this.state, nextState});
+
 		return (
 		return (
 			this.state.totalSelected !== nextState.totalSelected
 			this.state.totalSelected !== nextState.totalSelected
 			|| this.state.isUpdatedAllSelected !== nextState.isUpdatedAllSelected
 			|| this.state.isUpdatedAllSelected !== nextState.isUpdatedAllSelected
+			|| this.state.open !== nextState.open
 		);
 		);
 	},
 	},
 	handleUnselectAll: function (event) {
 	handleUnselectAll: function (event) {
@@ -104,26 +121,151 @@ var P5UI__TableAjaxSelectedInfo = createReactClass({
 		// this.props.store.dispatch( this.props.actions.unselectAll( this.props.namespace ) )
 		// this.props.store.dispatch( this.props.actions.unselectAll( this.props.namespace ) )
 		this.props.onSelectAllMatching()
 		this.props.onSelectAllMatching()
 	},
 	},
-	render: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::render (ns:'+this.props.namespace+')', { props: this.props, state: this.state });
-		return h('div', {
-			className: "p5UI__TableAjaxSelectedInfo",
+	handleToggle: function () {
+		DBG1 && console.log('DBG::P5UI__TableAjaxSelectedInfo::handleToggle (ns:' + this.props.namespace + ')', { open: this.state.open });
+		this.setState({ open: !this.state.open }, this.state.open ? this._unbindCloseDropdownActions : this._bindCloseDropdownActions)
+	},
+	_closeDropdownWhenClickedOutside: function (event) {
+		if (!this.state.open) return;
+
+		if (clickedOutsideElement(this._dropdownNode, event)) {
+			this.setState({
+				open: false
+			}, this._unbindCloseDropdownActions);
+		}
+	},
+	_closeDropdownWhenHitEscape: function (event) {
+		if (!this.state.open) return;
+
+		if (27 === event.keyCode) {
+			this.setState({
+				open: false
+			}, this._unbindCloseDropdownActions);
+		}
+	},
+	_unbindCloseDropdownActions: function () {
+		if (!document.removeEventListener && document.detachEvent) {
+			document.detachEvent('onclick', this._closeDropdownWhenClickedOutside);
+			document.detachEvent('keydown', this._closeDropdownWhenHitEscape);
+		} else {
+			document.removeEventListener('click', this._closeDropdownWhenClickedOutside);
+			document.removeEventListener('keydown', this._closeDropdownWhenHitEscape);
+		}
+	},
+	_bindCloseDropdownActions: function () {
+		if (!document.addEventListener && document.attachEvent) {
+			document.attachEvent('onclick', this._closeDropdownWhenClickedOutside);
+			document.attachEvent('keydown', this._closeDropdownWhenHitEscape);
+		} else {
+			document.addEventListener('click', this._closeDropdownWhenClickedOutside);
+			document.addEventListener('keydown', this._closeDropdownWhenHitEscape);
+		}
+	},
+
+	renderClearBtn: function () {
+		if (!this.state.totalSelected) return null;
+
+		return h('a', {
+			className: "btn btn-xs btn-default",
 			style: {
 			style: {
-				padding: '0 0 0 12px'
+				borderLeft: 0,
 			}
 			}
 		}, [
 		}, [
-			h('span', {}, "Wybrano " + this.state.totalSelected),
-			(this.state.totalSelected > 0) ? h('i', {
+			h('i', {
 				onClick: this.handleUnselectAll,
 				onClick: this.handleUnselectAll,
 				className: "glyphicon glyphicon-remove",
 				className: "glyphicon glyphicon-remove",
 				style: {
 				style: {
-					marginLeft: '4px',
-					cursor: 'pointer',
+					// marginLeft: '4px',
+					// cursor: 'pointer',
 					color: 'red',
 					color: 'red',
 					opacity: '0.5'
 					opacity: '0.5'
 				},
 				},
 				title: this.props.title || 'Usuń wszystkie zaznaczenia'
 				title: this.props.title || 'Usuń wszystkie zaznaczenia'
-			}) : null,
+			})
+		]);
+	},
+	renderToolsBtn: function () {
+		if (!this.state.totalSelected) return null;
+		if (!this.props.tools || !Object.keys(this.props.tools).length) return null;
+		DBG1 && console.log('DBG::P5UI__TableAjaxSelectedInfo::renderTools (ns:' + this.props.namespace + ')', { props: this.props, state: this.state });
+		return h('a', {
+			className: "btn btn-xs btn-default dropdown-toggle",
+			onClick: this.handleToggle,
+			// onClick: function (event) {
+			// 	console.log('DBG:handleToggle', { event });
+			// },
+		}, [
+			h('i', { className: "glyphicon glyphicon-menu-hamburger" }),
+		]);
+	},
+	makeHandleClickTool: function () {
+		// TODO: view result in right slide or in background & unselect all?
+	},
+	makeDropdownRef: function (reactComponent) {
+		this._dropdownNode = reactComponent
+	},
+	renderToolsMenu: function () {
+		if (!this.state.totalSelected) return null;
+		if (!this.props.tools || !Object.keys(this.props.tools).length) return null;
+		var tools = this.props.tools;
+
+		return h('ul', { ref: this.makeDropdownRef, className: "dropdown-menu" }, Object.keys(tools).map(function (idTool) {
+			var func = tools[idTool];
+			console.log('DBG: render tool', { func });
+			// baseLink: "index.php?_route=UrlAction_BiallProduktyPlikiSyncSelected"
+			// cell_id_params: []
+			// ico: "glyphicon glyphicon-share"
+			// label: "Synchronizuj pliki wybranych produktów"
+			// link_target: "_blank"
+			// name: "BiallProduktyPlikiSyncSelected"
+			// type: "@selected"
+
+			return h('li', {}, [
+				// p5UI__renderSelectedActionButton({ baseLink: func.baseLink, ico: func.ico, label: func.label }),
+				h(p5UI__renderSelectedActionButton, { baseLink: func.baseLink, ico: func.ico, label: func.label }),
+			]);
+		}))
+	},
+
+		// <button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+		// 	<span class="caret"></span>
+		// 	<span class="sr-only">Toggle Dropdown</span>
+		// </button>
+		// 	<ul class="dropdown-menu">
+		// 		<li><a href="#">Action</a></li>
+		// 		<li><a href="#">Another action</a></li>
+		// 		<li><a href="#">Something else here</a></li>
+		// 		<li role="separator" class="divider"></li>
+		// 		<li><a href="#">Separated link</a></li>
+		// 	</ul>
+
+
+		// 		<div class="btn-group">
+		// 			<button class="btn btn-default btn-xs dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+		// 				Extra small button <span class="caret"></span>
+		// 			</button>
+		// 			<ul class="dropdown-menu">
+		// 				...
+		//   </ul>
+		// 		</div>
+
+	render: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::render (ns:'+this.props.namespace+')', { props: this.props, state: this.state });
+		return h('div', {
+			className: "p5UI__TableAjaxSelectedInfo",
+			style: {
+				padding: '0 0 0 12px'
+			}
+		}, [
+
+			h('div', { className: "btn-group" + (this.state.open ? " open" : "") }, [
+				// h('a', { className: "btn btn-xs btn-default" }, "Wybrano " + this.state.totalSelected),
+				h('button', { className: "btn btn-xs btn-default" }, "Wybrano " + this.state.totalSelected),
+				this.renderClearBtn(),
+				this.renderToolsBtn(),
+				this.renderToolsMenu(),
+			]),
+
 			(this.state.isUpdatedAllSelected) && h('br'),
 			(this.state.isUpdatedAllSelected) && h('br'),
 			(this.state.isUpdatedAllSelected) && h('a', {
 			(this.state.isUpdatedAllSelected) && h('a', {
 				onClick: this.handleSelectAllByFilter,
 				onClick: this.handleSelectAllByFilter,