-
Posts
528 -
Joined
-
Last visited
-
Days Won
4
Everything posted by Beluga
-
Earlier in this topic I asked about autoHeight for rows. Now I have created a nice and only slightly hacky solution for it (no hacks in libs, just working around stuff). The existing simple option is not acceptable in our use case, because even the official docs say: When using autoHeight for 35k rows, I got twice the message "the web page seems to be running slow, do you want to stop it". I don't even dare to imagine what would happen on an old smartphone. The obvious (to me) solution was to calculate automatic height on demand only for the handful of rows displayed at a time. This has to happen on page load, on filtering and on pagination navigation. Due how pagination is implemented, a hoop had to be jumped through with it as well (to avoid an infinite loop). To be clear: the ag-Grid API offered no immediately useful event I could listen to! viewportChanged sounded like it would work, but in practice it failed to cover page navigation. var paginationAttached = false; var paginationHandler = function (event) { rowHeighter(grid, event); } function rowHeighter(grid, event) { // calculate row height for displayed rows to wrap text var cols = [ grid.gridOptions.columnApi.getColumn("code"), grid.gridOptions.columnApi.getColumn("variation") ]; grid.gridOptions.api.getRenderedNodes().forEach(function(node) { node.columnController.isAutoRowHeightActive = function() { return true; }; node.columnController.getAllAutoRowHeightCols = function() { return cols; }; node.setRowHeight(grid.gridOptions.api.gridOptionsWrapper.getRowHeightForNode(node)); }); // if listening to paginationChange, onRowHeightChanged creates an infinite loop, so work around it if(paginationAttached === false) { grid.gridOptions.api.onRowHeightChanged(); grid.gridOptions.api.addEventListener('paginationChanged', paginationHandler); paginationAttached = true; } else { grid.gridOptions.api.removeEventListener('paginationChanged', paginationHandler); grid.gridOptions.api.onRowHeightChanged(); grid.gridOptions.api.addEventListener('paginationChanged', paginationHandler); } } You can see here that I had to brute-force overwrite a couple of functions in the ag-Grid row height calculation logic. I define the columns I want to target for the auto height and trick the getAllAutoRowHeightCols to just provide them. My filter event listener in RockGridItemAfterInit is grid.gridOptions.api.addEventListener('filterChanged', function(event) { rowHeighter(grid, event); }); A CSS rule is also required: div[col-id="variation"].ag-cell, div[col-id="code"].ag-cell { white-space: normal; } Using automatic row heights brought about an issue with the default fixed height grid: a wild scrollbar appeared! I found out I can make the grid height adapt, putting this into RockGridItemBeforeInit: // pagination with a fixed number of rows and adapting grid height grid.gridOptions.paginationAutoPageSize = false; grid.gridOptions.paginationPageSize = 15; grid.gridOptions.domLayout = 'autoHeight'; Then I decided the pagination controls should live above the grid, because otherwise their position will change annoyingly whenever the grid height changes. Putting this into RockGridItemAfterInit: // move the pagination panel to the top, because the dynamic row heights would change its position at the bottom var pagingPanel = document.querySelector('.ag-paging-panel'); var rootWrapper = document.querySelector('.ag-root-wrapper'); rootWrapper.prepend(pagingPanel);
-
GD supports webp: http://php.net/manual/en/image.installation.php
-
Firefox 65 will support WebP. Will be out in January 2019.
-
Another possibility: https://wodby.com/docs/stacks/php/local/ I use one of their containers for my more exotic Docker setup. I like to keep my PW files (and MariaDB database) on my host so everything is clearer.
-
Great, then installing gutenprint package should be enough! http://gimp-print.sourceforge.net/p_Supported_Printers.php
-
Can you reveal the model? I have wrestled with a couple of Canons. One was easy as just installing gutenprint package did the trick. Another printer-related annoyance in Manjaro is that the user is not in "sys" group by default and thus is prevented access to fiddling with the printer in the CUPS admin. This does the trick: usermod -a -G sys yourusername
-
I use Arch Linux myself and have installed Manjaro for various family members & friends. KDE for desktop environment. Even though Arch/Manjaro is the bleeding edge, breakage is rare. Most problems are related to printers and that is just a fact of life no matter which operating system we use. I use Arch for a couple of servers as well.
-
I am using ApexCharts.js, which is kind of a spiritual successor to Chartist.js in that they both produce SVG charts. I am experimenting with what is possible, trying to figure out visualisations of the data that would be useful and attractive. Here is a screenshot of ApexCharts playing together in real time with RockGrid filtering (edit: nevermind the incorrect/repeating data labels, I only noticed and corrected later): We see a stacked bar chart representation of the number of literature references per filtered proverb type. I intend to split the thing into separate charts for each of the 13 top level categories (ApexCharts unfortunately does not support multiple series of stacked bars in a single chart). This will make it readable even with the unfiltered view of all 325 proverb types. It was quite convoluted to get the libraries to play together - grid.gridOptions.api.getModel().rootNode.childrenAfterFilter did not want to yield its contents, but guarded it like a jealous dragon. To get access to the data, I had to brute-force dispatch an input event like so: var inputTarget = document.querySelector('.ag-floating-filter-full-body:first-child input'); var inputEvent = new Event('input', {'bubbles': true, 'cancelable': true}); // have to use delta timing to delay the input event - in // case of big existing CPU load, it will fire too soon! var start = new Date().getTime(); setTimeout(function() { var now = new Date().getTime(), delta = now-start; inputTarget.dispatchEvent(inputEvent); },500); Then, to initialise the Apex chart in proper order, I had to wrap its stuff into a function. I called the function from my afterFilter: function afterFilter(grid) { var filterKids = grid.gridOptions.api.getModel().rootNode.childrenAfterFilter; var mapCodes = filterKids.map(x => x.data.code); var countedCodes = mapCodes.reduce((r,k)=>{r[k]=1+r[k]||1;return r},{}); apexseries = Object.entries(countedCodes).map(([p, v]) => ({'name':p, 'data':[v]})); var apexdiv = document.querySelector("#chart"); if(!apexdiv.hasChildNodes()) { apexi(); } else { ApexCharts.exec('proverbs', 'updateSeries', apexseries); } }
-
Thanks for the free support ? I got it working with the syntax grid.gridOptions.api.addEventListener('filterChanged', function() { afterFilter() }); This allows me to pass the grid object to afterFilter and then do interesting stuff with grid.gridOptions.api.getModel().rootNode.childrenAfterFilter I am going to mess around with dynamic charting!!
-
I am having trouble with events. In my RockGridItemAfterInit block I have grid.gridOptions.onFilterChanged = afterFilter(); Then outside it the function function afterFilter() { console.info("filter changed"); } The function fires exactly once - when the grid is initialised. It does not fire when the filters are changed. If I instead use grid.gridOptions.api.addEventListener('filterChanged', afterFilter()); It fires when the grid is initialised and when I change a filter, I get this in the console (the first time, on further tries I get nothing): TypeError: t is not a function ag-grid.min.js:26:2395 p</e.prototype.dispatchToListeners/</< http://0.0.0.0/site/modules/FieldtypeRockGrid/lib/ag-grid.min.js:26:2395 p</e.prototype.flushAsyncQueue/< http://0.0.0.0/site/modules/FieldtypeRockGrid/lib/ag-grid.min.js:26:2814 forEach self-hosted:262:13 p</e.prototype.flushAsyncQueue http://0.0.0.0/site/modules/FieldtypeRockGrid/lib/ag-grid.min.js:26:2785 <anonymous> self-hosted:973:17 What am I doing wrong?
-
Here is how I do linkifying, any tips for improvements or alternatives welcome: In rockgrid.php I include 'name' as a column, but then in RockGridItemAfterInit I do grid.setColumns(['code', 'variation']); to make it hidden. I have these to get the urls I want: var url = document.URL; // URL API's origin gets us a href of the hostname without the trailing slash (does not work with IE11) var domain = new URL(url).origin; For the 'variation' column I have a cellRenderer to link to the children of the page using the 'name' column data: col.cellRenderer = function(params) { if(params.value !== null) { return '<a href="' + url + params.data.name + '">' + params.value + '</a>'; } } For the 'code' column I have a valueGetter to pull the human-readable name for the category from a JS object and stick it after the code. Then I use a cellRenderer to link the text to anywhere I want: // let's combine the code with its meaning col.valueGetter = function(params) { return params.data.code + ' ' + codesarray[params.data.code]; } col.cellRenderer = function(params) { return '<a href="' + domain + '/clas2/">' + params.value + '</a>'; }
-
The idea is to have an additional dropdown to filter the items. It is not possible to have two filters in the same column, so I have to use the external filter feature. Thanks for nudging me in the right direction. I include a minimised version of my current code. When I started writing this reply, I was stuck, but I continued banging my head against the wall. Now I have a solution, where the dropdown completely overrides the smart filter. This might actually be useful, as then you can filter further (by typing) the already filtered content, but I should turn it off by default and include a checkbox to control it. Something that I don't understand is: why do I have to have both onchange="" in the select element and the addEventListener for the thing to work? I hope it will not be troublesome to create the checkbox solution to control the listener (tips welcome, I'm taking a break). Edit: I added a checkbox and tied the isExternalFilterPresent function to it and now it is perfect to me! document.addEventListener('RockGridItemBeforeInit', function(e) { if(e.target.id != 'RockGridItem_rockgrid') return; var grid = RockGrid.getGrid(e.target.id); grid.gridOptions.isExternalFilterPresent = isExternalFilterPresent; grid.gridOptions.doesExternalFilterPass = doesExternalFilterPass; var col = grid.getColDef('code'); col.headerName = grid.js.code; var col = grid.getColDef('variation'); col.headerName = grid.js.variation; col.filter = RockGrid.filters.smart; col.floatingFilterComponent = RockGrid.filters.smartFloating; }); var grid = null; var eFilterText = null; var dropcheck = null; document.addEventListener('RockGridItemAfterInit', function(e) { if(e.target.id != 'RockGridItem_rockgrid') return; var col; var colDef; grid = RockGrid.getGrid(e.target.id); grid.setColumns(['code', variation]); var selectoptions = ''; // let's build the select element and its options by using a pre-populated object // with key:value pairs like A1a:"water and fire as natural elements" var codeskey = Object.keys(codesarray); Object.values(codesarray).forEach(function(item, index) { selectoptions += '<option value="' + codeskey[index] + '">' + item + '</option>'; }); var dropgui = document.createElement('div'); dropgui.innerHTML = '<select id="filterText" onchange="externalFilterChanged(eFilterText, grid)">' + selectoptions + '</select><input id="dropcheck" type="checkbox" onchange="externalFilterChanged(eFilterText, grid)" />'; e.target.prepend(dropgui); dropcheck = document.getElementById('dropcheck'); eFilterText = document.getElementById('filterText'); eFilterText.addEventListener("change", externalFilterChanged(eFilterText, grid)); }); var filterText = null; // dropdown filter is present, if the checkbox is checked function isExternalFilterPresent() { return (dropcheck !== null && dropcheck.checked); } // this is given a rowNode by ag-Grid function doesExternalFilterPass(node) { var passed = true; var filterWord = filterText.toLowerCase(); var value = node.data.code; if (value === null) value = ''; if (value.toString().toLowerCase().indexOf(filterWord) < 0) { passed = false; } return passed; } function externalFilterChanged(eFilterText, grid) { filterText = eFilterText.options[eFilterText.selectedIndex].value; grid.gridOptions.api.onFilterChanged(); }
-
I've been looking into External filter. There is a problem, though: rockgrid.js gets loaded in the head element, so we cannot use an external element rendered outside rockgrid.js. I am having trouble figuring out how I should go about using and rendering markup that goes outside the grid in rockgrid.js. Or should I just modify things so that rockgrid.js loads at the end of the document?
-
I think mods should split all of the replies to another topic. To save @pwired some time on Bolt CMS investigation, this is talking about the backend: https://docs.bolt.cm/3.6/internals/javascript-css-build#css-bootstrap-and-custom-scss "The CSS is based on Bootstrap 3.3.7, with our own theming and custom styles added to that" You can see at the end of this all the JS and CSS dependencies of the backend: https://github.com/bolt/bolt/blob/3.6/app/src/grunt/concat.js In the frontend you have to use Twig templating. I would like to continue this conversation in a split topic as I am curious to know, why pwired had no problem with jQuery UI being in the backend for ages even though it is very much a CSS framework as well.
-
Created this MR https://gitlab.com/baumrock/kickstart/merge_requests/1 so weirdos like me can use kickstart.php?skiplint and the config parameter skipcompat. Other stuff: Maybe there could be a 'httpHosts' in the example kickstartfile, to show the \n and a comment emphasising that you have to use double-quotes for them to work? The function timezones() in install.php gets the tz list with PHP's timezone_identifiers_list and adds some extras. It would seem to me that the number and ordering of this data might change at any point in history, so it feels a bit awkward to rely on the IDs we happen to get at present. On the other hand, the current solution using CURLOPT_POSTFIELDS to pass stuff to install.php is simple and getting the tz IDs by parsing the select option texts would add a gymnastic extra step.
-
Yep, it works fine. I'll see if I can come up with something for the php linting and the compatibility error (but am not in a hurry). Ah, and now I notice my syntax error for the httpHosts, should have just been a string with the hosts separated by newlines like in your defaults: $hosts = "www.".ltrim($this->host, 'www.')."\n".ltrim($this->host, 'www.'); Cheers.
-
Thanks for this module, I am just getting into it - the long way ? I will explain what I needed to hack in order to make this work with my three-container Docker setup. First, I want to ask something: How can I set 'timezone' and 'httpHosts'? I saw in the example kickstartfile you had a numeric ID for Vienna, but that seems very odd (and how to find out the ID)!? I tried with these, but they didn't make it into the config.php 'timezone' => 'Europe/Helsinki' 'httpHosts' => ['0.0.0.0:2015','0.0.0.0','localhost:2015','localhost'] Now some interesting notes from my Docker adventure. These are hacks to the kickstart.php file. - I had to change the checkPHP function to always return true, because it wanted to run 'php -l', but php binary does not exist in my web server container! I can always validate the syntax of my recipe myself, so it's fine. - I learned I need to specify 0.0.0.0 in my Caddyfile instead of localhost, because otherwise curl will get connection refused. This was helpful in debugging: https://blog.kettle.io/debugging-curl-requests-in-php/ Then in the postToPW function I gave the container address: $url = 'http://caddy:2015/install.php'; - because Caddy is not Apache, I always get the Apache mod_rewrite on PW's compatibility check. So I silenced the error: // do all the pw installation steps if($this->postToPW(['step'=>1], ['stepname' => 'Check compatibility', 'errors' => [], 'quiet' => true])) { During the testing phase I benefited from commenting out the zip download and deletion: //$this->downloadAndSave($this->config['pw'], 'pw.zip'); and //if($delete) unlink($zipfile);
-
Good point. Chat apps for peer support can be a very distracting thing. I use IRC to collaboratively work on FOSS. If there was a big group of people working on PW core every day, real-time chat would be more crucial.
-
View my post as an example. Open the dev tools of your browser, inspect the code block element so you see its children (the spans making up the highlighted code). Select the string convertedFieldPairings)) from the forum post. As the Quote selection tooltip appears, observe what happens in your dev tools view. An empty span element has appeared inside the span containing the )) part of the string. If you now copy and paste to the Find field of your text editor already containing a block of code with the string, it will not find a match. I did not have the energy to inspect the clipboard to find what sort of an invisible character lurks inside the string.
- 1 reply
-
- 1
-
In some ways yes, but I think the key thing is: GitHub is a commercial company built around an open protocol. The problem with GitHub is monoculture, just like the problem with Gmail having too much of an influence on how we use and consume email these days. Slack is completely proprietary all the way down to the protocol level. Let's not forget that Slack used to be able to talk to IRC and XMPP, but suddenly removed support in March 2018. When a company like Slack changes something, users just have to deal with it. When it goes bankrupt, a whole galaxy of stuff built around it goes down the drain. This sort of scenario is playing over and over again while IRC and email are still with us after decades of service.