Jump to content

Table Helper


Recommended Posts

Posted

I got it ALMOST working.

I have 4 test filters, filter_1, filter_2, filter_3, filter_4. I default it to filter_1. If I reload the url with filter=filter_1 in the url, it works fine still. But if I reload the url with filter=filter_2 (or 3 or 4), it stops working totally.

I'm getting so stressed again, with skinning in general, because I'm wasting so many hours trying to get pages to look good and no matter how much I change things, I'm unhappy with it. In 2.x and 3.x I could just throw a ipsTable everywhere. :(

  • Replies 53
  • Created
  • Last Reply
Posted
10 hours ago, Tactical Geeks said:

filter just shows all doesn't change doesn't matter what i set filter too

Are you using a default table template? When I was using Table\Content with the default table template for it, it was a big mess, so had to just make my own template. I don't have any problem getting filters to show up (with Table\Db), but for some reason, like I mentioned, if I reload the page when the filter is anything other than the first one I created, the filters stop working.

Well, and not only when reloading... If I were to enter a direct url with filter=filter_2 in it, the filters won't work. But if I do it with filter=filter_1, they work fine.

Posted

Just for curiosity's sake:

		$this->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'tables', 'core' ), 'table' );

Why is there even an error on the public side when not defining a table template, even though this line exists in Table.php in the constructor called in Db.php's constructor?

I even looked and noticed there is a table.phtml for both the front and admin sides, so why would it give an error?

I can obviously just do my own file, but was just curious, since that above lien is in the constructor in Table.php.

Posted

 @Adriano Fariathey must have added the line I just mentioned above, later on, as it does not give any error if you don't set a table template on the front end anymore. I just tested it. I wondered why it would, given that line.

edit: but if I comment out the ROWS template it does give an error. So apparently that one must still be defined, but tableTemplate doesn't. I think the main reason the rows template one has to be done is because in that default template it is expecting it to be an AR object, not just a simple database row, otherwise I assume even rowsTemplate could be omitted.

Posted

Btw, I assume $quickSearch is only for the acp, even though it's a parameter in some front end templates?

edit: I was about to make a question/topic about there being no way to do an inner join for Table\Db, then noticed in the file it should be working, then realized the reason I thought it wasn't working was because I forgot I ahd multiple rows of data in the joined table. lol.

Posted
12 hours ago, Midnight Modding said:

@newbie LACI was talking about the general error Adriano mentioned earlier, where front end requires table template set, but acp side doesn't.

He wrote about rowsTemplate

9 hours ago, Midnight Modding said:

Btw, I assume $quickSearch is only for the acp, even though it's a parameter in some front end templates?

You can spend some time to check if the quick search works on front end.

My experiments said yes, you can use QS on front end

Posted
2 hours ago, newbie LAC said:

My experiments said yes, you can use QS on front end

Personally I haven't seen that working on the front end and in fact I've had to implement my own.

Posted

I dont think I really need it, anyway. Was just curious. Could come in handy fooling with it down the road.

Anyway, thanks, I sure thought the error mentioned had been about the table template.

I am so ready to be done with this app... I forgot just how many features (a LOT) were added in this custom version and it's taking forever. But finally getting close.

Posted

Sigh. The ordering is not set up in a way to allow sorting by multiple columns. And I sure don't want to overwrite such a large method as getRows(). I can't think of any creative ways to do it, since the field and the direction are 2 separate properties.

I guess I could set sortBy as 'field1 desc, field2' and sortDirection to asc or desc. But my guess is that would cause a problem elsewhere (for instance when it changes the color of the header to indicate it's sorted by the sortBy column).

Also, I wonder if I can still sort by columnA and also put columnA into noSort (ie if noSort is only for html purposes).

Posted

Both of what I just gave as possibilities do seem to work... I sorted by 2 columns with my workaround mentioned above and I successfully sorted with columns from my noSort, so it does still sort, just doesn't make the header clickable/sortable.

So I guess there's no problem with me sorting by multiple columns that way... if it causes a problem, I haven't seen it yet. I figured it would work for the ordering itself, since in the file it simply takes sortBy and then appends sortDirection to it.

i guess the problem only comes in if I do want user options for sorting. Otherwise, I don't see a problem.

Posted

I wish there was more flexibility with using Table\Db. I'm having to do so many workarounds. Since we have no control over what is selected from the main table, I am having to do some column math in the "select' in a join, even though the columns are in the main table. (I saw in the file that it puts the join select columns in there in a way where it should work...). Had I not needed a join, I'd have had to do a fake join or else not even use Db.

edit: this was one long, stressful day. lol. The sortBy workaround, sorting by column math which is actually selected in the join method, noSort workaround to account for some of these other changes, and then had to fix a ton of syntax errors in another file.

Posted
6 hours ago, Midnight Modding said:

I wish there was more flexibility with using Table\Db. I'm having to do so many workarounds. Since we have no control over what is selected from the main table, I am having to do some column math in the "select' in a join, even though the columns are in the main table. (I saw in the file that it puts the join select columns in there in a way where it should work...). Had I not needed a join, I'd have had to do a fake join or else not even use Db.

edit: this was one long, stressful day. lol. The sortBy workaround, sorting by column math which is actually selected in the join method, noSort workaround to account for some of these other changes, and then had to fix a ton of syntax errors in another file.

Why do you not have control? You can select anything. You can do joins. What are you missing? I have very rarely needed to override getRows. 

Posted
7 hours ago, HeadStand said:

Why do you not have control? You can select anything. You can do joins. What are you missing? I have very rarely needed to override getRows. 

I'm talking about for Table\Db. You can't pass what to select from the main table, only the where clause and joins. It automatically selects the row, but that means if you need virtual columns, it can't be done unless it's done through the join select. (unless I am overlooking a property to add in something into the select without joining).

I haven't looked at Table\Custom yet, because it would be doing a lot of work to almost do the same thing the Db one does.

Also, what i did was for anything that needed a true table like this, I was going to use the same Table and Rows templates for all of them on the front side, just as the acp side does. But the more I think about it... that takes away a lot of options for the customer by lumping them all together. In this custom version it probably doesn't matter, but otherwise it's a tough decision because if I give options to be able to easily rearrange things in templates for each individual table, that means a lot of what Table\Db sets up (headers, etc...) is just wasted.

edit: ah I see how.... $selects. Didn't notice that.

Posted
1 hour ago, HeadStand said:

There is also $table->include, which is an array of fields to select. You can use $table->parsers to customize the display of "fake" columns. 

Yes, I know, I've used those. But I looked through Db.php and include is only used to control what shows, not what is selected. I am ordering by a computed/virtual column, so that is why include wasn't enough. I'll have to either use selects or put it in the join 'select', which obviously selects is the non-hacky way.

So i pretty much know how to do everything. Now I'm just deciding if it was a bad idea to lump multiple front end tables into the same table and rows templates like this, and plus using parsers means even more of the work is done in the php file. But, for now, that's what i am doing, even though this is the front side.

This is for legit tables, like the acp ones, and I am trying to use it sparingly. I figured for these rare cases, people wouldn't need to change them. Whereas for anything where I am using grids, columns, data lists, etc.... I do those with their own templates instead of this "automated for many" way. Obviously, a lot of the convenience of the Table\Db setup is lost if I manually list out the headers and fields in the templates.

Posted
On 3/28/2018 at 7:44 AM, HeadStand said:

Personally I haven't seen that working on the front end and in fact I've had to implement my own.

This is exactly what I'm trying to avoid right now. It seems kind of counter intuitive to implement my own when IPS should support it by default (at least in my opinion).

Would you be willing to share yours? I completely understand if no.

I was looking through bfarber's post in this:

which I'll probably do some more research into before writing any code myself.

Best, pndemc

Posted
22 minutes ago, pndemc said:

This is exactly what I'm trying to avoid right now. It seems kind of counter intuitive to implement my own when IPS should support it by default (at least in my opinion).

Would you be willing to share yours? I completely understand if no.

I was looking through bfarber's post in this:

which I'll probably do some more research into before writing any code myself.

Best, pndemc

Just tested his method actually. Including the file:

/dev/js/admin/mixins/ips.core.table.js

Allows the quickSearch to work perfectly fine. :)

Now to tackle the issue of it working on multiple fields...

Posted

So I actually came across something interesting. The thing I tried by including the mixin file works great when working on it in dev mode, but as soon as I took the site out of dev, it stopped working as intended. Any idea what could be causing this?

Thanks!

Posted

image.thumb.png.971e003fb2af355d685b31df0105c796.png

If you have extend getRows and the class is a instance of Table/Content you can do this:

namespace IPS\someApp;
class _Table extends \IPS\Helpers\Table\Content
{
...//copy here following code
}

/**
 * Get rows
 *
 * @param	array	$advancedSearchValues	Values from the advanced search form
 * @return	array
 */
public function getRows( $advancedSearchValues )
{
	$this->sortOptions['random'] = "RAND()";
	if ( $this->quickSearch !== NULL and \IPS\Request::i()->quicksearch )
	{
		if ( is_callable( $this->quickSearch ) )
		{
			$this->where[] = call_user_func( $this->quickSearch, trim( \IPS\Request::i()->quicksearch ) );
		}
		else
		{
			$columns = is_array( $this->quickSearch ) ? $this->quickSearch[0] : $this->quickSearch;
			$columns = is_array( $columns ) ? $columns : array( $columns );
			$_where = '';
			foreach ( $columns as $c )
			{
				$_where[] = "LOWER(`{$c}`) LIKE CONCAT( '%', ?, '%' )";
			}
			$this->where[] = array_merge( array( '(' . implode( ' OR ', $_where ) . ')' ), array_fill( 0, count( $_where ), mb_strtolower( trim( \IPS\Request::i()->quicksearch ) ) ) );
		}
	}
	$return = parent::getRows( $advancedSearchValues );
	$this->sortOptions['random'] = "random";
	if(!count($return))
	{
		$this->extraHtml = \IPS\Theme::i()->getTemplate( 'global', 'core','global' )->message( 'no_item_to_show', 'information ipsSpacer_top  ipsSpacer_half' );
	}
	/* Return */
	return $return;
}

and for enable search on front end for a widget (without update navigation url) take a idea from

;( function($, _, undefined){
	"use strict";
	ips.controller.mixin('myCustomMixin', 'core.global.core.table', false, function () {
		this._timer = null;
		this._searchField = null;
		this._curSearchValue = '';
		this._currentValue = '';
		this.before('initialize',function () {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				this.scope.find('[data-role="tablePagination"] .ipsPagination_pageJump').hide();
				this.on( document, 'tablePaginationUpdated', _.bind( this.tableRowsUpdated, this ) );
				this.on( 'focus', '[data-role="tableSearch"]', this.startLiveSearch );
				this.on( 'blur', '[data-role="tableSearch"]', this.endLiveSearch );
			}
		});
		/**
		 * Mixin for _handleStateChange, checking for an updated search value
		 *
		 * @returns {object}	Extended object including search value
		 */
		this.after('_handleStateChanges', function (state) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				if( !_.isUndefined( state.data.quicksearch ) && state.data.quicksearch != this._urlParams.quicksearch ){
					this._updateSearch( state.data.quicksearch );
				}
			}
		});
		/**
		 * Set up search
		 *
		 * @returns {void}
		 */
		this.before('setup', function () {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				this._searchField = this.scope.find('[data-role="tableSearch"]');
				this.scope.find('[data-role="tableSearch"]').removeClass('ipsHide').show();
			}

		});

		/**
		 * Mixin for _getUrlParams, adding our current search value
		 *
		 * @returns {object}	Extended object including search value
		 */
		this.around('_getUrlParams', function (origFn) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				return _.extend( origFn(), {
					quicksearch: this._getSearchValue() || ''
				});
			}
		});
		/**
		 * Focus event handler for live search box
		 *
		 * @param	{event} 	e 		Event object
		 * @returns {void}
		 */
		this.startLiveSearch = function (e) {
			this._timer = setInterval( _.bind( this._checkSearchValue, this ), 500 );
		};

		/**
		 * Blur event handler for live search box
		 *
		 * @param	{event} 	e 		Event object
		 * @returns {void}
		 */
		this.endLiveSearch = function (e) {
			clearInterval( this._timer );
		};

		/**
		 * Determines whether the search field value has changed from the last loop run,
		 * and updates the URL if it has
		 *
		 * @returns {void}
		 */
		this._checkSearchValue = function () {
			var val = this._searchField.val();

			if( this._currentValue != val ){
				this.updateURL({
					quicksearch: val,
					page: 1
				});

				this._currentValue = val;
			}
		};

		/**
		 * Updates the search field with a provided value
		 *
		 * @param	{string} 	searchValue 		Value to update
		 * @returns {void}
		 */
		this._updateSearch = function (searchValue) {
			this._searchField.val( searchValue );
		};
		/**
		 * Gets the current search value, either from the URL or contents of the search box
		 *
		 * @returns {string}
		 */
		this._getSearchValue = function () {
			if( ips.utils.url.getParam('quicksearch') ){
				return ips.utils.url.getParam('quicksearch');
			}

			return this.scope.find('[data-role="tableSearch"]').val();
		};

		this.tableRowsUpdated = function (e) {
			var target      = $(e.target),
				activepage  = target.find('.ipsPagination_active');
			target.find('.ipsPagination_pageJump').hide();
			target
				.children().css({
				textAlign: 'center',
				display: 'block',
				padding: 0
			}).end()
				.find('.ipsPagination_page')
				.not(activepage)
				.not(activepage.prev()).not(activepage.prev().prev())
				.not(activepage.next()).not(activepage.next().next())
				.add('.ipsPagination_prev')
				.add('.ipsPagination_next')
				.hide();
		};
		// THIS PREVENT UPDATE URL ON NAVIGATION BAR
		this.around('updateURL', function (fn,newParams) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				_.extend( this._urlParams, newParams );

				var tmpStateData	= _.extend( _.clone( this._urlParams ), { controller: this.controllerID } );
				var newUrl			= this._baseURL + this._getURL();

				if( newUrl.slice(-1) == '?' ){
					newUrl = newUrl.substring( 0, newUrl.length - 1 );
				}
				this.fakeChange({
					data: tmpStateData,
					title: document.title,
					url: newUrl
				});
			}
			else
			{
				fn(newParams);
			}
		});
		this.around('stateChange', function (fn) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') === -1)
			{
				fn();
			}
		});
		this.before('_updateTable', function (response) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				if($.trim(response.pagination))
				{
					this.scope.find('[data-role="tablePagination"]').closest('.ipsButtonBar').show();
				}
				else
				{
					this.scope.find('[data-role="tablePagination"]').closest('.ipsButtonBar').hide();
				}
			}
		});
		/**
		 * stateChange alternative
		 *
		 * @returns {void}
		 */
		this.fakeChange = function (state) {
			// Because tables can exist alongside other widgets that manage the URL, we use the controller property
			// of the state data to identify states set by this controller only.
			// If that property doesn't exist, or if it doesn't match us, just ignore it.
			if( ( _.isUndefined( state.data.controller ) || state.data.controller != this.controllerID ) && this._initialURL != state.url ) {
				return;
			}
			/*if( state.data.bypassState ){
			 Debug.log('got state, but bypassing update');
			 //Debug.log( state );
			 //return;
			 }*/

			this._handleStateChanges( state );

			// Update data
			this._urlParams = state.data;

			// Get le new results
			// If the initial URL matches the URL for this state, then we'll load results by URL instead
			// of by object (since we don't have an object for the URL on page load)
			if( this._initialURL == state.url ){
				this._getResults( state.url );
			} else {
				this._getResults();
			}
		};
		/**
		 * @see https://invisioncommunity.com/forums/topic/440356-_handlestatechanges-not-call-_updatesort-420/?page=0#comment-2707974
		 * @TODO remove this extension and make sure _updateSort is called on _handleStateChanges
		 * Event handler for choosing new sort column/order
		 *
		 * @param	{event} 	e 		Event object
		 * @returns {void}
		 */
		this.after('changeSorting', function (e, data) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				if( _.isUndefined( data.selectedItemID ) ){
					return;
				}

				var current = this._getSortValue();
				var menuItem = data.menuElem.find('[data-ipsMenuValue="' + data.selectedItemID + '"]');
				var sortBy = data.selectedItemID;
				var sortDirection = current.order;

				// Does this option also have a direction?
				if( menuItem.attr('data-sortDirection') ){
					sortDirection = menuItem.attr('data-sortDirection');
				}
				// Select the one that was clicked, unselect others
				this._updateSort({
					by: sortBy,
					order: sortDirection
				});

				// if (by != this._urlParams.by || order != this._urlParams.order) {
				// 	this.updateURL({
				// 		sortby: by,
				// 		sortdirection: order,
				// 		page: 1
				// 	});
				// }
			}
		});

		this.after('_updateFilter', function (newFilter) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				var url = $(document).find('#' + this.scope.find("[id^=elSortByMenu_]").attr('id') + '_menu a[href*="advancedSearchForm"]');
				var filter = ips.utils.url.getParam('filter', url.attr('href'));
				url.attr('href', function (i, a) {
					return filter ?
						a.replace(new RegExp('(^|&)filter=' + filter, 'ig'), (newFilter ? '$1filter=' + newFilter : '')) :
						(newFilter ? a + "&filter=" + newFilter : '');
				}).data('_dialog', '');
			}
		});

		this.after('_updateSort', function(data) {
			if(this.scope.data('controller').indexOf('core.global.core.table( myCustomMixin )') !== -1)
			{
				var url = $(document).find('#' + this.scope.find("[id^=elSortByMenu_]").attr('id') + '_menu a[href*="advancedSearchForm"]');

				if (data.by) {
					var sortby = ips.utils.url.getParam('sortby', url.attr('href'));
					url.attr('href', function (i, a) {
						return sortby ?
							a.replace(new RegExp('(^|&)sortby=' + sortby, 'ig'), (data.by ? '$1sortby=' + data.by : '')) :
							(data.by ? a + "&sortby=" + data.by : '');
					}).data('_dialog','');
				}

				if (data.order) {
					var sortdirection = ips.utils.url.getParam('sortdirection', url.attr('href'));
					url.attr('href', function (i, a) {
						return sortdirection ?
							a.replace(new RegExp('(^|&)sortdirection=' + sortdirection, 'ig'), (data.order ? '$1sortdirection=' + data.order : '')) :
							(data.order ? a + "&sortdirection=" + data.order : '');
					}).data('_dialog','');
				}
			}
		});
	});
}(jQuery, _));

this work fine on front end but not tested for admincp...

remember to add into .phtml file the controller this:

data-controller='core.global.core.table{{if isset($table->baseUrl->queryString['orient'])}}( myCustomMixin ){{endif}}'

This way you can use a single phtml for widget and module.

NOTE: the mixin I make is for a widget purpose. remove {{if isset($table->baseUrl->queryString['orient'])}}...{{endif}} if you would not only for widget.

7 hours ago, pndemc said:

So I actually came across something interesting. The thing I tried by including the mixin file works great when working on it in dev mode, but as soon as I took the site out of dev, it stopped working as intended. Any idea what could be causing this?

Thanks!

how you have include the js into you project?

like this?

\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'mixins/ips.core.table.js', 'core', 'admin' ) );

 

Posted
3 hours ago, BomAle said:

how you have include the js into you project?

like this?


\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'mixins/ips.core.table.js', 'core', 'admin' ) );

 

Hey BomAle, thank you for your reply.

I actually tried that to no avail. I may have been doing something wrong, but I don't believe so. It seemed to be only loading the front-end ips.core.table.js file from what I could tell in the browser developer tools.

However, I suppose I just needed to get some sleep. When I woke up I just tried copying the /dev/js/admin/mixins/ips.core.table.js file over to my application in /applications/myapp/dev/js/front/mixins/myapp.mixin.table.js and it works just fine. I'm not sure what I changed that I was trying to last night, but it seems to be fully functioning and I couldn't be happier.

With that being said, I would prefer to have my app load the .js file directly from the core to require less maintaining whenever/if ever IPS updates the file with future releases. So if anyone would be able to help load the ips.core.table.js file from the admin panel rather than the front-end, that would be a great help.

Also, the code used to import the js file is:

\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'mixins/myapp.mixin.table.js', 'myapp' ) );

Thank you for the help. It's greatly appreciated.

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...