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

exported UI Widgets from TableAjax and fixed filters state

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

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

@@ -432,6 +432,12 @@ class TableAjax extends ViewAjax {
 		UI::inlineJS(__FILE__ . '.p5UI__TableAjax.js', [
 			'DBG' => ('1' === V::get('DBG_JS', '', $_GET)),
 		]);
+		UI::inlineJS(__FILE__ . '.p5UI__TableAjaxSortableLabel.js', [
+			'DBG' => ('1' === V::get('DBG_JS', '', $_GET)),
+		]);
+		UI::inlineJS(__FILE__ . '.p5UI__selected.js', [
+			'DBG' => ('1' === V::get('DBG_JS', '', $_GET)),
+		]);
 		UI::inlineJS(__FILE__ . '.TableAjax.js', [
 			'URI_BASE' => Request::getPathUri(),
 			'URI_WPS' => Request::getPathUri() . 'wps.php',

+ 55 - 257
SE/se-lib/TableAjax.php.TableAjax.js

@@ -20,6 +20,9 @@ var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.cre
 var p5UI__FieldCheckboxSearch = global.p5VendorJs['p5UI__FieldCheckboxSearch'];
 var p5UI__FieldCheckboxLoading = global.p5VendorJs['p5UI__FieldCheckboxLoading'];
 var p5UI__TableAjax = global.p5VendorJs['p5UI__TableAjax'];
+var P5UI__TableAjaxSortableLabel = global.p5VendorJs['P5UI__TableAjaxSortableLabel'];
+var P5UI__TableAjaxRowCheckbox = global.p5VendorJs['P5UI__TableAjaxRowCheckbox'];
+var P5UI__TableAjaxSelectedInfo = global.p5VendorJs['P5UI__TableAjaxSelectedInfo'];
 
 if (!String.prototype.startsWith) { // TODO: to global js utils
 	String.prototype.startsWith = function(search, pos) {
@@ -62,217 +65,6 @@ function p5Utils__objectToQueryWithKeyPrefix(obj, prefix, callback) {
 }
 
 
-global['P5UI__TableAjaxSortableLabel'] = createReactClass({
-	getStateFromStore: function () {
-		var state = this.props.store.getState();
-		return {
-			currSortCol: state.currSortCol,
-			currSortFlip: state.currSortFlip
-		};
-	},
-	getInitialState: function () {
-		return this.getStateFromStore();
-	},
-	componentDidMount: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::componentDidMount (field:'+this.props.fieldName+')');
-		this.unsubscribe = this.props.store.subscribe(this.storeUpdated)
-	},
-	componentWillUnmount: function () {
-		this.unsubscribe()
-	},
-	storeUpdated: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::storeUpdated (field:'+this.props.fieldName+')');
-		this.setState(this.getStateFromStore())
-	},
-	shouldComponentUpdate: function (nextProps, nextState) {
-		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::shouldComponentUpdate (field:'+this.props.fieldName+')', { state: this.state, nextState });
-		if (this.props.fieldName !== this.state.currSortCol && this.props.fieldName !== nextState.currSortCol) return false;
-		return (
-			this.state.currSortCol !== nextState.currSortCol
-			|| this.state.currSortFlip !== nextState.currSortFlip
-		);
-	},
-	handleChange: function (checked) { // handleChange: function (event) {
-		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::handleChange (field:'+this.props.fieldName+')');
-		if (!this.props.isSortable) return;
-		this.props.store.dispatch( this.props.actions.toggleSort( this.props.fieldName ) )
-	},
-	render: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::render (field:'+this.props.fieldName+')', { props: this.props, state: this.state });
-		var isSorted = ( this.props.isSortable && this.props.fieldName === this.state.currSortCol );
-		var sortFlip = this.state.currSortFlip;
-		var sortClass = "glyphicon glyphicon-triangle-" + ( sortFlip ? "bottom" : "top" );
-		var fieldProps = this.props.fieldProps;
-
-		var label = this.props.fieldName;
-		if (fieldProps.firendly) label = fieldProps.firendly;
-		var labelElements = ('ref' === fieldProps.type && fieldProps.xsdRefType)
-			?	[
-				h('i', {  style: { 'padding-right': "2px" }, className: "glyphicon glyphicon-export" }),
-				fieldProps.xsdRefType
-			]
-			: [ label ]
-		;
-
-		var title = this.props.fieldName;
-		if (fieldProps.description && fieldProps.description.length > 0) {
-			title = p5Utils__format("{0} ({1})", [ fieldProps.description, this.props.fieldName ])
-		}
-		else if (fieldProps._tsRetId > 0) {
-			title = p5Utils__format("Kliknij na pole i przejdź do powiązanych rekordów ({0})", [ title ])
-		}
-		if ('ref' === fieldProps.type && fieldProps.xsdRefType) {
-			if (fieldProps.description && fieldProps.description.length > 0 && fieldProps.description !== this.props.fieldName) {
-				title = p5Utils__format("{0} (ref {1})", [ fieldProps.description, this.props.fieldName ])
-			} else {
-				title = p5Utils__format("(ref {0})", [ this.props.fieldName ])
-			}
-		}
-
-		return h('span', {
-			onClick: this.handleChange,
-			title: title,
-			className: "pull-left",
-			style: Object.assign({
-			}, this.props.isSortable ? { cursor: "pointer" } : {})
-		}, labelElements.concat([
-			(isSorted) ? h('span', {
-				className: sortClass,
-				style: {
-					margin: "0 0 0 3px",
-					color: "#bbb"
-				}
-			}) : null
-		]));
-	}
-});
-
-global['P5UI__TableAjaxRowCheckbox'] = createReactClass({
-	// props.namespace: PropTypes.string.isRequired
-	// props.primaryKey: PropTypes.string.isRequired
-	// props.store: Redux store with state: { isLoading bool, selected array of strings }
-	// props.actions: Redux store actions
-	getStateFromStore: function () {
-		var state = this.props.store.getState();
-		return {
-			disabled: this.props.disabled ? true : false,
-			isLoading: (-1 !== state.loading.indexOf(this.props.primaryKey)),
-			checked: (-1 !== state.selected.indexOf(this.props.primaryKey))
-		};
-	},
-	getInitialState: function () {
-		return this.getStateFromStore();
-	},
-	componentDidMount: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::componentDidMount (pk:'+this.props.primaryKey+')');
-		this.unsubscribe = this.props.store.subscribe(this.storeUpdated)
-	},
-	componentWillUnmount: function () {
-		this.unsubscribe()
-	},
-	storeUpdated: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::storeUpdated (pk:'+this.props.primaryKey+')');
-		this.setState(this.getStateFromStore())
-	},
-	shouldComponentUpdate: function (nextProps, nextState) {
-		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::shouldComponentUpdate (pk:'+this.props.primaryKey+')', { state: this.state, nextState});
-		return (
-			this.state.isLoading !== nextState.isLoading
-			|| this.state.checked !== nextState.checked
-		);
-	},
-	handleChange: function (checked) { // handleChange: function (event) {
-		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::handleChange (pk:'+this.props.primaryKey+')', { checked: checked });
-		this.props.store.dispatch( this.props.actions.toggle( this.props.namespace, this.props.primaryKey, checked ) )
-	},
-	render: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::render (pk:'+this.props.primaryKey+')', { props: this.props, state: this.state });
-		return h(p5UI__FieldCheckboxLoading, {
-			size: 20,
-			color: '#999',
-			checked: this.state.checked,
-			disabled: this.state.disabled,
-			isLoading: this.state.isLoading,
-			onChange: this.handleChange
-		});
-	}
-});
-
-global['P5UI__TableAjaxSelectedInfo'] = createReactClass({
-	// props.namespace: PropTypes.string.isRequired
-	// props.store: Redux store with state: { isLoading bool, selected array of strings }
-	// props.actions: Redux store actions
-	getStateFromStore: function () {
-		var state = this.props.store.getState();
-		var wasAllSelected = (this.state) && this.state.isUpdatedAllSelected;
-		var isAllSelected = ( -1 !== state.selected.indexOf('select-all') );
-		DBG && console.log('DBG: getStateFromStore (ns:'+this.props.namespace+')', { isAllSelected, state });
-		return {
-			// isLoading: state.isLoading || 0, // add ?
-			isUpdatedAllSelected: ( !wasAllSelected && isAllSelected ),
-			totalSelected: state.totalSelected || 0,
-		};
-	},
-	getInitialState: function () {
-		return this.getStateFromStore();
-	},
-	componentDidMount: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::componentDidMount (ns:'+this.props.namespace+')');
-		this.unsubscribe = this.props.store.subscribe(this.storeUpdated)
-	},
-	componentWillUnmount: function () {
-		this.unsubscribe()
-	},
-	storeUpdated: function () {
-		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::storeUpdated (ns:'+this.props.namespace+')');
-		this.setState(this.getStateFromStore())
-	},
-	shouldComponentUpdate: function (nextProps, nextState) {
-		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::shouldComponentUpdate (ns:'+this.props.namespace+')', { state: this.state, nextState});
-		return (
-			this.state.totalSelected !== nextState.totalSelected
-			|| this.state.isUpdatedAllSelected !== nextState.isUpdatedAllSelected
-		);
-	},
-	handleUnselectAll: function (event) {
-		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::handleUnselectAll (ns:'+this.props.namespace+')');
-		this.props.store.dispatch( this.props.actions.unselectAll( this.props.namespace ) )
-	},
-	handleSelectAllByFilter: function (event) {
-		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::handleSelectAllByFilter (ns:'+this.props.namespace+')');
-		// this.props.store.dispatch( this.props.actions.unselectAll( this.props.namespace ) )
-		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', { style: {
-			padding: '0 4px'
-		} }, [
-			h('span', {}, "Wybrano " + this.state.totalSelected),
-			(this.state.totalSelected > 0) ? h('i', {
-				onClick: this.handleUnselectAll,
-				className: "glyphicon glyphicon-remove",
-				style: {
-					marginLeft: '4px',
-					cursor: 'pointer',
-					color: 'red',
-					opacity: '0.5'
-				},
-				title: this.props.title || 'Usuń wszystkie zaznaczenia'
-			}) : null,
-			(this.state.isUpdatedAllSelected) && h('br'),
-			(this.state.isUpdatedAllSelected) && h('a', {
-				onClick: this.handleSelectAllByFilter,
-				className: "btn btn-xs btn-link",
-				style: {
-					marginLeft: '4px',
-				},
-				title: this.props.title || 'Zaznacz wszystkie pasujące do wyszukiwania'
-			}, "+ wszystkie"),
-		])
-	}
-});
-
 global['P5UI__TableAjaxSpecialFilter'] = createReactClass({
 	// props.store: Redux store with state: { isLoading bool, selected array of strings }
 	// props.actions: Redux store actions
@@ -1014,31 +806,49 @@ var TableAjax = function() {
 		var stateObjectFilter = createTableFiltersStateObject(priv.options.filterInit || {});
 		priv.options.filterStore = stateObjectFilter.store;
 		priv.options.filterActions = stateObjectFilter.actions;
-		// priv.options._filterState = Object.assign({}, priv.options.filterStore.getState())
-		var curFilterState = priv.options.filterStore.getState()
-		_state._filterQuery = p5Utils__mapToQueryWithKeyPrefix(curFilterState.filter, 'f_')
-		_state._specialFilterQuery = p5Utils__mapToQueryWithKeyPrefix(curFilterState.specialFilter, 'sf_')
-		_state._currSortCol = curFilterState.currSortCol;
-		_state._currSortFlip = curFilterState.currSortFlip;
+		priv.options.filterMapStoreToState = function (curFilterState) {
+			// var curFilterState = store.getState()
+			return {
+				_filterQuery: p5Utils__mapToQueryWithKeyPrefix(curFilterState.filter, 'f_'),
+				_specialFilterQuery: p5Utils__mapToQueryWithKeyPrefix(curFilterState.specialFilter, 'sf_'),
+				_currSortCol: curFilterState.currSortCol,
+				_currSortFlip: curFilterState.currSortFlip
+			};
+		};
+		priv.options.filterGetFilterQuery = function () {
+			return [
+				_state._filterQuery,
+				_state._specialFilterQuery,
+				_state._forceFilterQuery
+			].filter(Boolean).join('&');
+		};
+		priv.options.filterGetFullFilterQuery = function () {
+			return [
+				'currSortCol=' + ( _state._currSortCol || ''),
+				'currSortFlip=' + ( _state._currSortFlip ? "desc" : "asc" ),
+				_state._filterQuery,
+				_state._specialFilterQuery,
+				_state._forceFilterQuery
+			].filter(Boolean).join('&');
+		};
+		_state = Object.assign(_state, priv.options.filterMapStoreToState(priv.options.filterStore.getState()));
 		DBG && console.log('TODO:INIT: ', { _state });
 		priv.options.filterStore.subscribe(function () {
-			curState = priv.options.filterStore.getState()
-			var curFilterQuery = p5Utils__mapToQueryWithKeyPrefix(curState.filter, 'f_')
-			var curSpecialFilterQuery = p5Utils__mapToQueryWithKeyPrefix(curState.specialFilter, 'sf_')
+			var curState = priv.options.filterMapStoreToState(priv.options.filterStore.getState());
 			var needFetchData = (
-				curFilterQuery !== _state._filterQuery
-				|| curSpecialFilterQuery !== _state._specialFilterQuery
-				|| curState.currSortCol !== _state._currSortCol
-				|| curState.currSortFlip !== _state._currSortFlip
+				curState._filterQuery !== _state._filterQuery
+				|| curState._specialFilterQuery !== _state._specialFilterQuery
+				|| curState._currSortCol !== _state._currSortCol
+				|| curState._currSortFlip !== _state._currSortFlip
 			);
 			DBG && console.log('TODO:filtersUpdated - FETCH_DATA: ', { _state, curState });
-			_state._filterQuery = curFilterQuery;
-			_state._specialFilterQuery = curSpecialFilterQuery;
-			_state._currSortCol = curState.currSortCol;
-			_state._currSortFlip = curState.currSortFlip;
+			_state._filterQuery = curState._filterQuery;
+			_state._specialFilterQuery = curState._specialFilterQuery;
+			_state._currSortCol = curState._currSortCol;
+			_state._currSortFlip = curState._currSortFlip;
 
 			if (needFetchData) {
-				DBG && console.log('TODO:filtersUpdated - FETCH_DATA: ', { curFilterQuery, curSpecialFilterQuery });
+				DBG && console.log('TODO:filtersUpdated - FETCH_DATA: ', { curState });
 				priv.loadPage(0)
 			}
 		})
@@ -1054,10 +864,12 @@ var TableAjax = function() {
 						store: priv.options.selectedStore,
 						actions: priv.options.selectedActions,
 						onSelectAllMatching: function () {
-							var filterQuery = '';
-							filterQuery += (_state._filterQuery) ? "&" + _state._filterQuery : "";
-							filterQuery += (_state._specialFilterQuery) ? "&" + _state._specialFilterQuery : "";
-							filterQuery += (_state._forceFilterQuery) ? "&" + _state._forceFilterQuery : "";
+							// TODO: priv.options.filterMapStoreToState(priv.options.filterStore.getState())
+							var filterQuery = priv.options.filterGetFilterQuery();
+							// var filterQuery = '';
+							// filterQuery += (_state._filterQuery) ? "&" + _state._filterQuery : "";
+							// filterQuery += (_state._specialFilterQuery) ? "&" + _state._specialFilterQuery : "";
+							// filterQuery += (_state._forceFilterQuery) ? "&" + _state._forceFilterQuery : "";
 							priv.options.selectedStore.dispatch( priv.options.selectedActions.selectAllMatchingFilter(priv.options.namespace, filterQuery) );
 						}
 					}),
@@ -2030,7 +1842,6 @@ var TableAjax = function() {
 			ReactDOM.render(
 				h(P5UI__TableAjaxSortableLabel, {
 					namespace: priv.options.namespace,
-					cssClassName: (_state.primaryKey === column) ? 'stickyCol stickyCol3' : '',
 					isSortable: (props.type != 'special' && props.type != 'geom' && props.type != "simpleLink" && props.type != "ref"), // TODO: props.isSortable
 					fieldName: column,
 					fieldProps: props,
@@ -2481,13 +2292,11 @@ var TableAjax = function() {
 		}
 
 		var exportUrl = 'index.php?_route=ViewTableAjax&_task=export&namespace=' + priv.options.namespace;
-		exportUrl += '&format=' + format;
-		exportUrl += '&flds=' + exportFields.join(',');
-		exportUrl += '&sortCol=' + (_state._currSortCol || '');
-		exportUrl += '&sortDir=' + (_state._currSortFlip ? "desc" : "asc");
-		exportUrl += (_state._filterQuery) ? "&" + _state._filterQuery : "";
-		exportUrl += (_state._specialFilterQuery) ? "&" + _state._specialFilterQuery : "";
-		exportUrl += (_state._forceFilterQuery) ? "&" + _state._forceFilterQuery : "";
+		exportUrl += '&' + 'format=' + format;
+		exportUrl += '&' + 'flds=' + exportFields.join(',');
+		exportUrl += '&' + 'sortCol=' + (_state._currSortCol || '');
+		exportUrl += '&' + 'sortDir=' + (_state._currSortFlip ? "desc" : "asc");
+		exportUrl += '&' + priv.options.filterGetFilterQuery();
 
 		node.attr('href', exportUrl);
 	};
@@ -2520,13 +2329,8 @@ var TableAjax = function() {
 
 		if (priv.options.debug || DBG) console.log(p5Utils__format('requesting data from url:{0}', [priv.options.url]));
 
-		var initUrlAdd = '';
-		initUrlAdd += '&currSortCol=' + _state._currSortCol;
-		initUrlAdd += '&currSortFlip=' + ( _state._currSortCol ? "desc" : "asc" );
-		DBG && console.warn('DBG:update... - FETCH_DATA', { '_state._filterQuery': _state._filterQuery, '_state._specialFilterQuery': _state._specialFilterQuery, _state });
-		initUrlAdd += (_state._filterQuery) ? "&" + _state._filterQuery : "";
-		initUrlAdd += (_state._specialFilterQuery) ? "&" + _state._specialFilterQuery : "";
-		initUrlAdd += (_state._forceFilterQuery) ? "&" + _state._forceFilterQuery : "";
+		var initUrlAdd = '&' + priv.options.filterGetFullFilterQuery();
+		DBG && console.warn('DBG:update... - FETCH_DATA', { initUrlAdd, '_state._filterQuery': _state._filterQuery, '_state._specialFilterQuery': _state._specialFilterQuery, _state });
 
 		// p5UI__notifyAjaxCallback({type: 'info', msg: 'pobieranie danych (init) ...'});
 		window.fetch(priv.options.url + initUrlAdd, {
@@ -3579,16 +3383,10 @@ var TableAjax = function() {
 			currSortFlip: _state._currSortFlip ? "desc" : "asc"
 		};
 		var urlAdd = '';
-		urlAdd += '&page=' + page;
-		urlAdd += '&pageSize=' + (pageSize || priv.options.pageSize);
-		urlAdd += '&currSortCol=' + (_state._currSortCol || '');
-		urlAdd += '&currSortFlip=' + (_state._currSortFlip ? "desc" : "asc");
-
-		// TODO: compare with filterStore
+		urlAdd += '&' + 'page=' + page;
+		urlAdd += '&' + 'pageSize=' + (pageSize || priv.options.pageSize);
 		DBG && console.log('DBG::loadPage... FETCH_DATA', { '_state._filterQuery': _state._filterQuery, '_state._specialFilterQuery': _state._specialFilterQuery, _state });
-		urlAdd += (_state._filterQuery) ? "&" + _state._filterQuery : "";
-		urlAdd += (_state._specialFilterQuery) ? "&" + _state._specialFilterQuery : "";
-		urlAdd += (_state._forceFilterQuery) ? "&" + _state._forceFilterQuery : "";
+		urlAdd += '&' + priv.options.filterGetFullFilterQuery();
 
 		_uiNode$Table.parent().parent().addClass('AjaxTable-loading');
 

+ 6 - 2
SE/se-lib/TableAjax.php.createTableFiltersStateObject.js

@@ -7,6 +7,7 @@ var ReduxThunk = global.p5VendorJs.ReduxThunk;
 var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.createStore); // TODO: to vendor.js
 
 function createFilterStoreWithInitialData(initialFilter) {
+	DBG && console.log('INIT: initialFilter', { initialFilter });
 	var initialState = {
 		isLoading: false,
 		isEmpty: true,
@@ -19,7 +20,9 @@ function createFilterStoreWithInitialData(initialFilter) {
 		if ('currSortCol' === key) {
 			initialState.currSortCol = initialFilter[key]
 		} else if ('currSortFlip' === key) {
-			initialState.currSortFlip = initialFilter[key]
+			if ('desc' === initialFilter[key]) initialState.currSortFlip = true;
+			else if ('asc' === initialFilter[key]) initialState.currSortFlip = false;
+			else initialState.currSortFlip = Boolean(initialFilter[key]);
 		} else if ('f_' === key.substr(0, 2)) {
 			initialState.filter.set(key.substr(2), initialFilter[key])
 		} else if ('sf_' === key.substr(0, 3)) {
@@ -141,8 +144,9 @@ function filterActions() {
 
 function createTableFiltersStateObject(initialData) {
 	var _initialData = initialData || {};
+	var store = createStoreWithThunkMiddleware( createFilterStoreWithInitialData( _initialData ) );
 	return {
-		store: createStoreWithThunkMiddleware( createFilterStoreWithInitialData( _initialData ) ),
+		store: store,
 		actions: filterActions()
 	}
 }

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

@@ -299,7 +299,6 @@ function createTableSelectedStateObject(idContext) {
 	return {
 		store: createStoreWithThunkMiddleware(selectedStore),
 		actions: selectedActions(_idContext)
-
 	}
 }
 

+ 103 - 0
SE/se-lib/TableAjax.php.p5UI__TableAjaxSortableLabel.js

@@ -0,0 +1,103 @@
+var DBG = DBG || false;
+var DBG1 = true;
+if (!global.p5VendorJs) throw "Missing p5 Vendor js libs";
+if (!global.p5VendorJs.Redux) throw "Missing p5 Vendor js lib: Redux";
+var createReactClass = global.p5VendorJs.createReactClass;
+var h = global.p5VendorJs.React.createElement;
+var ReactDOM = global.p5VendorJs.ReactDOM;
+var Redux = global.p5VendorJs.Redux;
+var ReduxThunk = global.p5VendorJs.ReduxThunk;
+var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.createStore); // TODO: to vendor.js
+
+var P5UI__TableAjaxSortableLabel = createReactClass({
+	// props.store: Redux store { getState(), subscribe(f), dispatch(f) }
+	// props.actions: store actions { toggleSort(fieldName) }
+	// props.namespace: string
+	// props.isSortable: bool
+	// props.fieldName: string
+	// props.fieldProps: legacy field props object { type, firendly, xsdRefType, description, ... }
+	getStateFromStore: function () {
+		var state = this.props.store.getState();
+		return {
+			currSortCol: state.currSortCol,
+			currSortFlip: state.currSortFlip
+		};
+	},
+	getInitialState: function () {
+		return this.getStateFromStore();
+	},
+	componentDidMount: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::componentDidMount (field:'+this.props.fieldName+')');
+		this.unsubscribe = this.props.store.subscribe(this.storeUpdated)
+	},
+	componentWillUnmount: function () {
+		this.unsubscribe()
+	},
+	storeUpdated: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::storeUpdated (field:'+this.props.fieldName+')');
+		this.setState(this.getStateFromStore())
+	},
+	shouldComponentUpdate: function (nextProps, nextState) {
+		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::shouldComponentUpdate (field:'+this.props.fieldName+')', { state: this.state, nextState });
+		if (this.props.fieldName !== this.state.currSortCol && this.props.fieldName !== nextState.currSortCol) return false;
+		return (
+			this.state.currSortCol !== nextState.currSortCol
+			|| this.state.currSortFlip !== nextState.currSortFlip
+		);
+	},
+	handleChange: function (checked) { // handleChange: function (event) {
+		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::handleChange (field:'+this.props.fieldName+')');
+		if (!this.props.isSortable) return;
+		this.props.store.dispatch( this.props.actions.toggleSort( this.props.fieldName ) )
+	},
+	render: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxSortableLabel::render (field:'+this.props.fieldName+')', { props: this.props, state: this.state });
+		var isSorted = ( this.props.isSortable && this.props.fieldName === this.state.currSortCol );
+		var sortFlip = this.state.currSortFlip;
+		var sortClass = "glyphicon glyphicon-triangle-" + ( sortFlip ? "bottom" : "top" );
+		var fieldProps = this.props.fieldProps;
+
+		var label = this.props.fieldName;
+		if (fieldProps.firendly) label = fieldProps.firendly;
+		var labelElements = ('ref' === fieldProps.type && fieldProps.xsdRefType)
+			?	[
+				h('i', {  style: { 'padding-right': "2px" }, className: "glyphicon glyphicon-export" }),
+				fieldProps.xsdRefType
+			]
+			: [ label ]
+		;
+
+		var title = this.props.fieldName;
+		if (fieldProps.description && fieldProps.description.length > 0) {
+			title = p5Utils__format("{0} ({1})", [ fieldProps.description, this.props.fieldName ])
+		}
+		else if (fieldProps._tsRetId > 0) {
+			title = p5Utils__format("Kliknij na pole i przejdź do powiązanych rekordów ({0})", [ title ])
+		}
+		if ('ref' === fieldProps.type && fieldProps.xsdRefType) {
+			if (fieldProps.description && fieldProps.description.length > 0 && fieldProps.description !== this.props.fieldName) {
+				title = p5Utils__format("{0} (ref {1})", [ fieldProps.description, this.props.fieldName ])
+			} else {
+				title = p5Utils__format("(ref {0})", [ this.props.fieldName ])
+			}
+		}
+
+		return h('span', {
+			onClick: this.handleChange,
+			title: title,
+			className: "pull-left",
+			style: Object.assign({
+			}, this.props.isSortable ? { cursor: "pointer" } : {})
+		}, labelElements.concat([
+			(isSorted) ? h('span', {
+				className: sortClass,
+				style: {
+					margin: "0 0 0 3px",
+					color: "#bbb"
+				}
+			}) : null
+		]));
+	}
+});
+
+global.p5VendorJs['P5UI__TableAjaxSortableLabel'] = P5UI__TableAjaxSortableLabel;

+ 144 - 0
SE/se-lib/TableAjax.php.p5UI__selected.js

@@ -0,0 +1,144 @@
+var DBG = DBG || false;
+var DBG1 = true;
+if (!global.p5VendorJs) throw "Missing p5 Vendor js libs";
+if (!global.p5VendorJs.Redux) throw "Missing p5 Vendor js lib: Redux";
+var createReactClass = global.p5VendorJs.createReactClass;
+var h = global.p5VendorJs.React.createElement;
+var ReactDOM = global.p5VendorJs.ReactDOM;
+var Redux = global.p5VendorJs.Redux;
+var ReduxThunk = global.p5VendorJs.ReduxThunk;
+var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.createStore); // TODO: to vendor.js
+var p5UI__FieldCheckboxLoading = global.p5VendorJs['p5UI__FieldCheckboxLoading'];
+
+var P5UI__TableAjaxRowCheckbox = createReactClass({
+	// props.store: Redux store { getState(), subscribe(f), dispatch(f) }
+	// props.store - state used: { isLoading bool, selected array of strings }
+	// props.actions: store actions { toggle(ns, pk, checked) }
+	// props.namespace: PropTypes.string.isRequired
+	// props.primaryKey: PropTypes.string.isRequired
+	getStateFromStore: function () {
+		var state = this.props.store.getState();
+		return {
+			disabled: this.props.disabled ? true : false,
+			isLoading: (-1 !== state.loading.indexOf(this.props.primaryKey)),
+			checked: (-1 !== state.selected.indexOf(this.props.primaryKey))
+		};
+	},
+	getInitialState: function () {
+		return this.getStateFromStore();
+	},
+	componentDidMount: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::componentDidMount (pk:'+this.props.primaryKey+')');
+		this.unsubscribe = this.props.store.subscribe(this.storeUpdated)
+	},
+	componentWillUnmount: function () {
+		this.unsubscribe()
+	},
+	storeUpdated: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::storeUpdated (pk:'+this.props.primaryKey+')');
+		this.setState(this.getStateFromStore())
+	},
+	shouldComponentUpdate: function (nextProps, nextState) {
+		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::shouldComponentUpdate (pk:'+this.props.primaryKey+')', { state: this.state, nextState});
+		return (
+			this.state.isLoading !== nextState.isLoading
+			|| this.state.checked !== nextState.checked
+		);
+	},
+	handleChange: function (checked) { // handleChange: function (event) {
+		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::handleChange (pk:'+this.props.primaryKey+')', { checked: checked });
+		this.props.store.dispatch( this.props.actions.toggle( this.props.namespace, this.props.primaryKey, checked ) )
+	},
+	render: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxRowCheckbox::render (pk:'+this.props.primaryKey+')', { props: this.props, state: this.state });
+		return h(p5UI__FieldCheckboxLoading, {
+			size: 20,
+			color: '#999',
+			checked: this.state.checked,
+			disabled: this.state.disabled,
+			isLoading: this.state.isLoading,
+			onChange: this.handleChange
+		});
+	}
+});
+
+var P5UI__TableAjaxSelectedInfo = createReactClass({
+	// props.namespace: PropTypes.string.isRequired
+	// props.store: Redux store { getState(), subscribe(f), dispatch(f) }
+	// props.store - state used: { isUpdatedAllSelected, selected, totalSelected }
+	// props.actions: store actions {}
+	// onSelectAllMatching: PropTypes.fun.isRequired (execute store.dispatch)
+	getStateFromStore: function () {
+		var state = this.props.store.getState();
+		var wasAllSelected = (this.state) && this.state.isUpdatedAllSelected;
+		var isAllSelected = ( -1 !== state.selected.indexOf('select-all') );
+		DBG && console.log('DBG: getStateFromStore (ns:'+this.props.namespace+')', { isAllSelected, state });
+		return {
+			// isLoading: state.isLoading || 0, // add ?
+			isUpdatedAllSelected: ( !wasAllSelected && isAllSelected ),
+			totalSelected: state.totalSelected || 0,
+		};
+	},
+	getInitialState: function () {
+		return this.getStateFromStore();
+	},
+	componentDidMount: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::componentDidMount (ns:'+this.props.namespace+')');
+		this.unsubscribe = this.props.store.subscribe(this.storeUpdated)
+	},
+	componentWillUnmount: function () {
+		this.unsubscribe()
+	},
+	storeUpdated: function () {
+		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::storeUpdated (ns:'+this.props.namespace+')');
+		this.setState(this.getStateFromStore())
+	},
+	shouldComponentUpdate: function (nextProps, nextState) {
+		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::shouldComponentUpdate (ns:'+this.props.namespace+')', { state: this.state, nextState});
+		return (
+			this.state.totalSelected !== nextState.totalSelected
+			|| this.state.isUpdatedAllSelected !== nextState.isUpdatedAllSelected
+		);
+	},
+	handleUnselectAll: function (event) {
+		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::handleUnselectAll (ns:'+this.props.namespace+')');
+		this.props.store.dispatch( this.props.actions.unselectAll( this.props.namespace ) )
+	},
+	handleSelectAllByFilter: function (event) {
+		DBG && console.log('DBG::P5UI__TableAjaxSelectedInfo::handleSelectAllByFilter (ns:'+this.props.namespace+')');
+		// this.props.store.dispatch( this.props.actions.unselectAll( this.props.namespace ) )
+		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', { style: {
+			padding: '0 4px'
+		} }, [
+			h('span', {}, "Wybrano " + this.state.totalSelected),
+			(this.state.totalSelected > 0) ? h('i', {
+				onClick: this.handleUnselectAll,
+				className: "glyphicon glyphicon-remove",
+				style: {
+					marginLeft: '4px',
+					cursor: 'pointer',
+					color: 'red',
+					opacity: '0.5'
+				},
+				title: this.props.title || 'Usuń wszystkie zaznaczenia'
+			}) : null,
+			(this.state.isUpdatedAllSelected) && h('br'),
+			(this.state.isUpdatedAllSelected) && h('a', {
+				onClick: this.handleSelectAllByFilter,
+				className: "btn btn-xs btn-link",
+				style: {
+					marginLeft: '4px',
+				},
+				title: this.props.title || 'Zaznacz wszystkie pasujące do wyszukiwania'
+			}, "+ wszystkie"),
+		])
+	}
+});
+
+
+global.p5VendorJs['P5UI__TableAjaxRowCheckbox'] = P5UI__TableAjaxRowCheckbox;
+global.p5VendorJs['P5UI__TableAjaxSelectedInfo'] = P5UI__TableAjaxSelectedInfo;