bernhard

Preview/Discussion: RockDataTables

Recommended Posts

Posted (edited)
8 hours ago, bernhard said:

I think it is superior to (and in most cases also easier than) datatables, that's why I'm switching.

Yeah, at firts sight ag-grid looks great but have you seen http://tabulator.info/ lately? Namely the new release: http://tabulator.info/news

I have also checked out a lot of these JS table libraries and Tabulator seems to be the only one which is completely open source (MIT)  and is being actively developed. I have not used it just yet but I am planning a test drive in the near future.

Edited by szabesz
typo
  • Like 4

Share this post


Link to post
Share on other sites

thx @szabesz, haven't seen anything about tabulator so far. It looks quite similar to jquery datatables though and I have not seen anything that would not be possible using aggrid on the first sight. Or did I miss anything?

I'm quite far with my implementation of agGrid, so I'm quite sure I will not change the grid library (again) very soon ;) 

  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)
4 minutes ago, bernhard said:

Or did I miss anything?

Well, I am not suggesting you should switch to it, the only thing which is clearly better to have is its MIT license. I just thought you might want to reconsider it just once more but I know its not fun switching libraries "just because" :) Feel free to ignore my notes.

Edited by szabesz
typo
  • Like 1

Share this post


Link to post
Share on other sites
5 minutes ago, bernhard said:

I have not seen anything that would not be possible using aggrid

Not saying it's a reason to switch, but it does support xlsx whereas that appears to require the paid version aggrid.

  • Like 1

Share this post


Link to post
Share on other sites

Thanks for your feedback, I really enjoy the discussion.

@mit

Aggrid is also mit licensed: https://github.com/ag-grid/ag-grid/blob/master/LICENSE.txt

And I don't think it's bad to have paid pro features. Quite on the contrary. I think it can ensure a long life of the software and good quality. We know that principle from somewhere, don't we?! 😉

@xlsx

I have looked at this closely already as it is a must for me to transfer data to excel. Aggrid has the ability to export data as CSV with custom separators. It's perfectly fine to use this for excel export IMHO. It even has custom renderers that only fire when data is accessed for export. Only thing missing at CSV compared to xlsx is fancy styling and formulas etc. Personally I don't need this in any of my projects.

Ok, simple links come to my mind. That might be nice to have. But we can still use the API to get data and then use any other JavaScript xlsx library to create our files. Or just pay a little fee to enable that feature.

 

Aggrid looks really well crafted. I'm actually quite happy that I forgot my version of datatables at home over Easter so I was forced to take a pause and take a closer look to aggrid 😂

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
49 minutes ago, szabesz said:

I have also checked out a lot of these JS table libraries and Tabulator

Thanks for this! A while back (actually, a long time ago), I thought I'd checked them all but it seems there's quite a number of new kids on the block (unless I missed them first time round :)). I would have loved a Pivot Tables feature (aggrid has this in the paid version).

Sorry to hijack your thread Bernhard...

  • Like 1

Share this post


Link to post
Share on other sites

I think I'm almost done :)

Plugins are now very easy to create and extremely useful as they are reuseable across multiple projects:

5aca838420c5c_2018-04-0822_55_16-EditPage_Homeaggrid_to.thumb.png.026f2345de1dbcd41c6042b230ce566a.png

Sample setup of column statistics:

  // custom column stats
  grid.plugins.colStats.valueGetters = {
    id: function(column, selected) {
      return grid.pluck(0,{selected}).length + (selected ? ' selected' : ' rows');
    },
    percent: function(column, selected) {
      return grid.avg(column, {selected});
    },
  };

Everything works in the backend and in the frontend, just echo the field as you would do with any other field

echo $page->mygridfield

5aca837881462_2018-04-0822_58_13-MinimalSiteProfile.thumb.png.fda7ea144b0fc03cb3d24d2446fa53af.png

Data loads blazingly fast. The 10.001 rows here load in 500ms and are automatically compressed by the server to reduce transfer time (here it results in 90KB instead of 1MB of data!). Thx again @adrian for that idea.

5aca836f40c70_2018-04-0823_01_06-DeveloperTools-http___www.aggrid.to_admin_page_edit__id1.png.e4ea64f555616af30ecece5696f1fbde.png

Cell renderers can be combined as you like! That's really powerful and great! We can build libraries of different cellRenderers that we need often and then just wrap them in a function and return the one's we like:

5aca83700d4c7_2018-04-0823_00_27-EditPage_Homeaggrid_to.png.f5d1ed399d050317dce2a08be1569502.png

In this column I combine the "actionItems" renderer and the "percentBar" renderer. The actionItems are only applied for regular rows. For rows of the "colStats" plugin we only append " (avg)" to the cell and don't show the icons because they are here to edit this lines related page (and of course the statistics row has no related page):

  colDef.cellRenderer = function(params) {
    var str = '';
    if(!params.data.colStatsRowType) {
      str = RockGrid.renderers.actionItems(params, [
        {
          icon: 'fa fa-bolt',
          href: '/admin/page/edit/?id=' + params.data.id,
          target: ' target="_blank"',
          str: 'Open external: ' + params.data.title,
        },{
          icon: 'fa fa-search',
          href: '/admin/page/edit/?field=percent&id=' + params.data.id,
          str: 'Open panel: ' + params.data.title,
          class: ' class="pw-panel hover"',
        },
      ]);
    }
    else {
      str = ' (avg)'
    }
    return RockGrid.renderers.percentBar(params, str, (!params.data.colStatsRowType ? 0 : 2));
  };

rockgrid.gif.8038a7a2af085ac562fe7c938b0dc895.gif

Updating the grid after closing the panel is on my todo-list :)

  • Like 6

Share this post


Link to post
Share on other sites

Just tested the field on a custom admin page:

  public function ___execute() {
    $form = $this->modules->get('InputfieldForm');

    $f = $this->modules->get('InputfieldRockGrid');
    $f->label = 'Default Report';
    $f->themeBorder = 'none';
    $f->setData("id>0, limit=10", [
      'title',
      'templates_id',
      'created',
      'status',
    ]);
    $form->add($f);

    return $form->render();
  }

screencapture-finance-to-admin-finance-2018-04-10-12_59_04.thumb.png.db1c243c5b0fe8a7be83fe48157bd295.png

O0

Thanks again @adrian for Tracy's awesome request info panel to get the proper "themeBorder" setting :) 

edit: ajax mode does not yet work in processmodules...

  • Like 2

Share this post


Link to post
Share on other sites

Question to all of you: At the moment the module loads all assets separately. Each plugin and each renderer is placed in one file. I don't want to change this, because that way it stays clean and maintainable. But I thought it might not be ideal to load all those files separately for sometimes only one line of code inside this file. Do you have any ideas how i could combine those files to one js file and one css file? ProCache could do that on the frontend, but I don't want to rely on a pro module, and it would not work in the backend.

Thanks for any hints

5accb08af4154_2018-04-1014_38_55-DeveloperTools-http___www.finance.to_admin_finance_.png.0ff9f1beb39743376141b95b1a62a823.png

Share this post


Link to post
Share on other sites

Check out https://github.com/mrclay/minify

But I don't know if it is worth the effort. For in the backend I wouldn't mind having multiple files loaded even if some of them have only one line.

  • Like 2

Share this post


Link to post
Share on other sites
2 hours ago, bernhard said:

Do you have any ideas how i could combine those files to one js file and one css file?

Maybe you could provide a Gulp tasks file along the module which could be called and generate a rockdatatables.min.js/css.

 

  • Like 1

Share this post


Link to post
Share on other sites

Hey @bernhard - totally OT, but any ideas why the full Tracy bar is showing twice in your screenshot?

  • Like 1

Share this post


Link to post
Share on other sites
Just now, adrian said:

Hey @bernhard - totally OT, but any ideas why the full Tracy bar is showing twice in your screenshot?

I knew you would ask :D Thats the screenshot tool ;)

26 minutes ago, flydev said:

Maybe you could provide a Gulp tasks file along the module which could be called and generate a rockdatatables.min.js/css.

Hm, never worked with gulp and don't think it would be worth the effort. Maybe it does not matter at all? Does anybody know of any real issues when loading a lot of files? Does anybody know at which number this "a lot" could be?

I thought of a snippet that does a foreach and creates a file on the fly with file_put_contents. But the file would need to be different for all pages (thats how it works now). Hm... I could actually change that behaviour and create one single js and one single css file thats always loaded when any rockgrid item is available on the page. Since all the plugins load with eventlisteners this should work...

I'll leave this on the ideas-list...

  • Like 1

Share this post


Link to post
Share on other sites

@bernhard You could use the AIOM Module for this, it should also work in the backend. Please look at the sections "minify Javascript" and "Conditional loading". You can even parse LESS files with it. But who needs LESS, when you have SASS? Oooops, I didn't say anything ;) 

Share this post


Link to post
Share on other sites
2 minutes ago, jmartsch said:

@bernhard You could use the AIOM Module for this, it should also work in the backend. Please look at the sections "minify Javascript" and "Conditional loading". You can even parse LESS files with it. But who needs LESS, when you have SASS? Oooops, I didn't say anything ;) 

Thx. But I don't want to add any dependencies if not really necessary (like RockSqlFinder)

Share this post


Link to post
Share on other sites

Then a simple gulp task or webpack would be the way to go. Are you interested in a simple minification script for gulp?

Share this post


Link to post
Share on other sites
1 hour ago, bernhard said:

Hm, never worked with gulp and don't think it would be worth the effort. Maybe it does not matter at all? Does anybody know of any real issues when loading a lot of files? Does anybody know at which number this "a lot" could be?

 

Multiple files would be multiple HTTP Requests which would take a longer time, than loading one large file. There are some readings what is "a lot" and if you stay under 14KB you can do the whole file in one roundtrip. Don't know if this is correct, but this is something I remember.

Don't know if this still is relevant with HTTP 2.

Share this post


Link to post
Share on other sites

Ok, here you go. It's simple.

Create two files in your root directory

gulpfile.js

var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var gulp = require('gulp');

var jsFiles = 'lib/**/*.js',
    jsDest = 'dist/';

gulp.task('scripts', function() {
   return gulp.src(jsFiles)
    .pipe(concat('scripts.js'))
    .pipe(uglify())
    .pipe(gulp.dest(jsDest))
})

gulp.task('default', ['scripts']);

Please change the paths to your javascript files according to your needs.

In my example every .js file under "lib" and its subdirectories are catched.

You can also change the jsDest. This is where the concatenated file "scripts.js" will be stored.

Create a package.json

{
  "name": "gulp-simple-concat",
  "version": "1.0.0",
  "description": "",
  "main": "gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-concat": "^2.6.1",
    "gulp-uglify": "^3.0.0"
  }
}

Then run

npm install

Node.js has to be installed for this to work. But it is very common now for web-devs to have this installed.

When npm is finished, just run `gulp`in your console/terminal.

That's it. Now you have a concatenated and minified javascript file.

 

  • Like 1

Share this post


Link to post
Share on other sites
15 hours ago, bernhard said:

Does anybody know of any real issues when loading a lot of files? Does anybody know at which number this "a lot" could be?

If I were you, I would not put too much effort in it – as we are talking about the backend – but if you want to learn Gulp then why not? ;) Soon it is http2 time and as far as I can remember I read that when using http2 merging assets into one is often the wrong choice. But even with http1, since files are cached by the browser you loose a few millisecs first, that's all I guess.

I have seen WP sites with about a hundred js/css files loaded separately but the real issue with page load  speed never seemed to be loading those but mostly other bottlenecks are at play.

Of course, I biased, just read my current motto:
"It is unwise to relentlessly chase performance at the expense of everything else. Better is the enemy of good!"

:D 

Share this post


Link to post
Share on other sites

I wouldn't bother with that neither. Perhaps you could set up a js loader (eg. Require.js) that would load all the js files but I think that's also unnecessary.

  • Like 1

Share this post


Link to post
Share on other sites

Thanks @szabesz and @tpr, same feeling here. I just thought there might be a processwireish-3-lines-of-code-no-dependencies-at-all solution ;) 

Also thanks @jmartsch, but the last thing i want is to include any dependencies for such a small priority :) 

Thanks for your feedback.

  • Like 2

Share this post


Link to post
Share on other sites

I don't want to pollute your official thread so I post here.

Perhaps you have an idea before I go further in my questions :)

I have a template which contain some fields. Two of them are timestamp, rest are integer.

What I want is to fetch pages that are in a date range, so I have this selector : 

Quote

has_parent=734756, template=transaction, range=(date>=1514761200, date<=1523483999), montant_especes=0, montant_carte_bancaire=0, montant_cheques=0

In this example, the system fetch 18119 pages. Results :

Quote

 

$this->pages->findIDs($selector); // 9387.67ms, 0.04 MB

$this->pages->findMany($selector); // 10494.93ms, 0.29 MB

$this->pages->findObjects($selector, ['title']); // 12259.55ms, 0.56 MB

 

It take around 10 seconds to fetch all pages, in the DBMS it take 0.3908 second(s) for 18119 pages.

I think I am missing something :undecided:

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Noel Boss
      Page Query Boss
      Build complex nested queries containing multiple fields and pages and return an array or JSON. This is useful to fetch data for SPA and PWA.
      You can use the Module to transform a ProcessWire Page or PageArray – even RepeaterMatrixPageArrays – into an array or JSON. Queries can be nested and contain closures as callback functions. Some field-types are transformed automatically, like Pageimages or MapMarker.
      Installation
      Via ProcessWire Backend
      It is recommended to install the Module via the ProcessWire admin "Modules" > "Site" > "Add New" > "Add Module from Directory" using the PageQueryBoss class name.
      Manually
      Download the files from Github or the ProcessWire repository: https://modules.processwire.com/modules/page-query-builder/
      Copy all of the files for this module into /site/modules/PageQueryBoss/ Go to “Modules > Refresh” in your admin, and then click “install” for the this module. Module Methods
      There are two main methods:
      Return query as JSON
      $page->pageQueryJson($query); Return query as Array
      $page->pageQueryArray($query); Building the query
      The query can contain key and value pairs, or only keys. It can be nested and 
      contain closures for dynamic values. To illustrate a short example:
      // simple query: $query = [ 'height', 'floors', ]; $pages->find('template=skyscraper')->pageQueryJson($query); Queries can be nested, contain page names, template names or contain functions and ProcessWire selectors:
      // simple query: $query = [ 'height', 'floors', 'images', // < some fileds contain default sub-queries to return data 'files' => [ // but you can also overrdide these defaults: 'filename' 'ext', 'url', ], // Assuming there are child pages with the architec template, or a // field name with a page relation to architects 'architect' => [ // sub-query 'name', 'email' ], // queries can contain closure functions that return dynamic content 'querytime' => function($parent){ return "Query for $parent->title was built ".time(); } ]; $pages->find('template=skyscraper')->pageQueryJson($query); Keys:
      A single fieldname; height or floors or architects 
      The Module can handle the following fields:
      Strings, Dates, Integer… any default one-dimensional value Page references Pageimages Pagefiles PageArray MapMarker FieldtypeFunctional A template name; skyscraper or city
      Name of a child page (page.child.name=pagename); my-page-name A ProcessWire selector; template=building, floors>=25
      A new name for the returned index passed by a # delimiter:
      // the field skyscraper will be renamed to "building": $query = ["skyscraper`#building`"]  
      Key value pars:
      Any of the keys above (1-5) with an new nested sub-query array:
      $query = [ 'skyscraper' => [ 'height', 'floors' ], 'architect' => [ 'title', 'email' ], ]  
      A named key and a closure function to process and return a query. The closure gets the parent object as argument:
      $query = [ 'architecs' => function($parent) { $architects = $parent->find('template=architect'); return $architects->arrayQuery(['name', 'email']); // or return $architects->explode('name, email'); } ] Real life example:
      $query = [ 'title', 'subtitle', // naming the key invitation 'template=Invitation, limit=1#invitation' => [ 'title', 'subtitle', 'body', ], // returns global speakers and local ones... 'speakers' => function($page){ $speakers = $page->speaker_relation; $speakers = $speakers->prepend(wire('pages')->find('template=Speaker, global=1, sort=-id')); // build a query of the speakers with return $speakers->arrayQuery([ 'title#name', // rename title field to name 'subtitle#ministry', // rename subtitle field to ministry 'links' => [ 'linklabel#label', // rename linklabel field to minlabelistry 'link' ], ]); }, 'Program' => [ // Child Pages with template=Program 'title', 'summary', 'start' => function($parent){ // calculate the startdate from timetables return $parent->children->first->date; }, 'end' => function($parent){ // calculate the endate from timetables return $parent->children->last->date; }, 'Timetable' => [ 'date', // date 'timetable#entry'=> [ 'time#start', // time 'time_until#end', // time 'subtitle#description', // entry title ], ], ], // ProcessWire selector, selecting children > name result "location" 'template=Location, limit=1#location' => [ 'title#city', // summary title field to city 'body', 'country', 'venue', 'summary#address', // rename summary field to address 'link#tickets', // rename ticket link 'map', // Mapmarker field, automatically transformed 'images', 'infos#categories' => [ // repeater matrix! > rename to categories 'title#name', // rename title field to name 'entries' => [ // nested repeater matrix! 'title', 'body' ] ], ], ]; if ($input->urlSegment1 === 'json') { header('Content-type: application/json'); echo $page->pageQueryJson($query); exit(); } Module default settings
      The modules settings are public. They can be directly modified, for example:
      $modules->get('PageQueryBoss')->debug = true; $modules->get('PageQueryBoss')->defaults = []; // reset all defaults Default queries for fields:
      Some field-types or templates come with default selectors, like Pageimages etc. These are the default queries:
      // Access and modify default queries: $modules->get('PageQueryBoss')->defaults['queries'] … public $defaults = [ 'queries' => [ 'Pageimages' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'focus', ], 'Pagefiles' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'filesize', 'filesizeStr', 'hash', ], 'MapMarker' => [ 'lat', 'lng', 'zoom', 'address', ], 'User' => [ 'name', 'email', ], ], ]; These defaults will only be used if there is no nested sub-query for the respective type. If you query a field with complex data and do not provide a sub-query, it will be transformed accordingly:
      $page->pageQueryArry(['images']); // returns something like this 'images' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'focus'=> [ 'top', 'left', 'zoom', 'default', 'str', ] ]; You can always provide your own sub-query, so the defaults will not be used:
      $page->pageQueryArry([ 'images' => [ 'filename', 'description' ], ]); Overriding default queries:
      You can also override the defaults, for example
      $modules->get('PageQueryBoss')->defaults['queries']['Pageimages'] = [ 'basename', 'url', 'description', ]; Index of nested elements
      The index for nested elements can be adjusted. This is also done with defaults. There are 3 possibilities:
      Nested by name (default) Nested by ID Nested by numerical index Named index (default):
      This is the default setting. If you have a field that contains sub-items, the name will be the key in the results:
      // example $pagesByName = [ 'page-1-name' => [ 'title' => "Page one title", 'name' => 'page-1-name', ], 'page-2-name' => [ 'title' => "Page two title", 'name' => 'page-2-name', ] ] ID based index:
      If an object is listed in $defaults['index-id'] the id will be the key in the results. Currently, no items are listed as defaults for id-based index:
      // Set pages to get ID based index: $modules->get('PageQueryBoss')->defaults['index-id']['Page']; // Example return array: $pagesById = [ 123 => [ 'title' => "Page one title", 'name' => 123, ], 124 => [ 'title' => "Page two title", 'name' => 124, ] ] Number based index
      By default, a couple of fields are transformed automatically to contain numbered indexes:
      // objects or template names that should use numerical indexes for children instead of names $defaults['index-n'] => [ 'Pageimage', 'Pagefile', 'RepeaterMatrixPage', ]; // example $images = [ 0 => [ 'filename' => "image1.jpg", ], 1 => [ 'filename' => "image2.jpg", ] ] Tipp: When you remove the key 'Pageimage' from $defaults['index-n'], the index will again be name-based.
       
      Debug
      The module respects wire('config')->debug. It integrates with TracyDebug. You can override it like so:
      // turns on debug output no mather what: $modules->get('PageQueryBoss')->debug = true; Todos
      Make defaults configurable via Backend. How could that be done in style with the default queries?
    • By daniels
      General
      This is a lightweight alternative to other newsletter & newsletter-subscription modules.
      It can subscribe, update, unsubscribe & delete a user in a list in Mailchimp with MailChimp API 3.0. It does not provide any forms or validation, so you can feel free to use your own. To protect your users, it does not save any user data in logs or sends them to an admin.
      This module fits your needs if you...
      ...use Mailchimp as your newsletter / email-automation tool ...want to let users subscribe to your newsletter on your website ...want to use your own form, validation and messages (with or without the wire forms) ...don't want any personal user data saved in any way in your ProcessWire environment (cf. EU data regulation terms) ...like to subscribe, update, unsubscribe or delete users to/from different lists ...like the Mailchimp UI for creating / sending / reviewing email campaigns You can find it here: https://github.com/danielstieber/SubscribeToMailchimp
      Let me know what you think and if I should add it to the Modules Directory.
      Setup
      Log into your Mailchimp account and go to  Profile > Extras > API Keys. If you don't have an API Key, create a new one. Copy your API Key and paste it in the module settings (Processwire > Modules > Site > SubscribeToMailchimp). Back in Mailchimp, go to the list, where you want your new subscribers. Go to Settings > List name and defaults. Copy the List ID an paste it in to the module settings.
      Usage
      To use the module, you need to load it into your template:
      $mc = $modules->get("SubscribeToMailchimp"); Now you can pass an email address to the module and it will try to edit (if the user exists) or create a new subscriber in your list.
      $mc->subscribe('john.doe@example.com'); You can also pass a data array, to add additional info.
      $mc->subscribe('john.doe@example.com', ['FNAME' => 'John', 'LNAME' => 'Doe']); You can even choose an alternative list, if you don't want this subscriber in your default list.
      $mc->subscribe('john.doe@example.com', ['FNAME' => 'John', 'LNAME' => 'Doe'], 'abcdef1356'); // Subscribe to List ID abcdef1356 If you want to unsubscribe a user from a list, you can use the unsubscribe method.
      $mc->unsubscribe('john.doe@example.com'); // Unsubscribe john.doe@example.com from the default list $mc->unsubscribe('john.doe@example.com', 'abcdef1356'); // Unsubscribe john.doe@example.com from the list abcdef1356 If you want to permantly delete a user, you can call the delete method. Carefully, this step cannot be undone
      $mc->delete('john.doe@example.com'); // Permanently deletes john.doe@example.com from the default list $mc->delete('john.doe@example.com', 'abcdef1356'); // Permanently deletes john.doe@example.com from the list abcdef1356  
      Important Notes
      This module does not do any data validation. Use a sever-sided validation like Valitron Make sure that you have set up your fields in your Mailchimp list. You can do it at Settings > List fields and *|MERGE|* tags Example
      Example usage after a form is submitted on your page:
      // ... validation of form data $mc = $modules->get("SubscribeToMailchimp"); $email = $input->post->email; $subscriber = [ 'FNAME' => $input->post->firstname, 'LNAME' => $input->post->lastname, ]; $mc->subscribe($email, $subscriber);  
      Troubleshooting
      In case of trouble check your ProcessWire warning logs.
      I can't see the subscriber in the list
      If you have enabled double opt-in (it is enabled by default) you will not see the subscriber, until he confirmed the subscription in the email sent by Mailchimp
      I get an error in my ProccessWire warning logs
      Check if you have the right List ID and API Key. Check if you pass fields, that exist in your list. Check if you pass a valid email address. Go to Mailchimps Error Glossary for more Information
      How To Install
      Download the zip file at Github or clone directly the repo into your site/modules If you downloaded the zip file, extract it in your sites/modules directory. You might have to change the folders name to 'SubscribeToMailchimp'. Goto the modules admin page, click on refresh and install it  
      Changelog
      0.0.2
      Note: You can update safely from 0.0.1 without any changes in your code
      New Features
      Added 'Unsubscribe' method $mc->unsubscribe($email, $list = "") Added 'Delete' method $mc->delete($email, $list = "") Bug Fixes and compatibility changes
      Removed type declarations to be compatible with PHP 5.1+* (thanks to wbmnfktr) Other
      Changed the way, the base url for the api gets called *I have only tested it with PHP 7.x so far, so use on owners risk
    • By BitPoet
      As threatened in the Pub sub forum in the "What are you currently building?" thread, I've toyed around with Collabora CODE and built file editing capabilities for office documents (Libre-/OpenOffice formats and MS Office as well as a few really old file types) into a PW module.
      If you are running OwnCloud or NextCloud, you'll perhaps be familiar with the Collabora app for this purpose.
      LoolEditor
      Edit office files directly in ProcessWire
      Edit your docx, odt, pptx, xlsx or whatever office files you have stored in your file fields directly from ProcessWire's page editor. Upload, click the edit icon, make your changes and save. Can be enabled per field, even in template context.
      Currently supports opening and saving of office documents. Locking functionality is in development.
      See the README on GitHub for installation instructions. You should be reasonably experienced with configuring HTTPS and running docker images to get things set up quickly.
      Pull requests are welcome!
      Here is a short demonstration:

    • By Robin S
      An Images field allows you to:
      Rename images by clicking the filename in the edit panel or in list view. Replace images, keeping metadata and filename (when possible) by dropping a new image on the thumbnail in the edit panel. Introduced here. But neither of these things is possible in File fields, which prompted this module. The way that files are renamed or replaced in this module is not as slick as in the Images field but it gets the job done. The most time-consuming part was dealing with the UI differences of the core admin themes. @tpr, gives me even more respect for the work that must go into AdminOnSteroids.
      Most of the code to support the rename/replace features is already present in InputfieldFile - there is just no UI for it currently. So hopefully that means these features will be offered in the core soon and this module can become obsolete.
       
      Files Rename Replace
      Allows files to be renamed or replaced in Page Edit.

      Usage
      Install the Files Rename Replace module.
      If you want to limit the module to certain roles only, select the roles in the module config. If no roles are selected then any role may rename/replace files.
      In Page Edit, click "Rename/Replace" for a file...
      Rename
      Use the text input to edit the existing name (excluding file extension).
      Replace
      Use the "Replace with" select to choose a replacement file from the same field. On page save the file will be replaced with the file you selected. Metadata (description, tags) will be retained, and the filename also if the file extensions are the same.
      Tip: newly uploaded files will appear in the "Replace with" select after the page has been saved.
       
      https://github.com/Toutouwai/FilesRenameReplace
      http://modules.processwire.com/modules/files-rename-replace/
    • By d'Hinnisdaël
      So I decided to wade into module development and created a wrapper module around ImageOptim, a service that compresses and optimizes images in the cloud. ImageOptim currently handles JPG, PNG and GIF files and, depending on the settings you use, shaves off between 15% and 60% in filesize. Great for bandwidth and great for users, especially on mobile.
      This module handles the part of uploading images to ImageOptim via their official API, downloading the optimized version and storing it alongside the original image.
       

       
      Download & Info
      GitHub / Module directory / Readme / Usage
       
      Why ImageOptim?
      There are other image optimization services out there, some of them free, that have outstanding ProcessWire modules. A few things make ImageOptim the best tool for most of my customers: It's not free, i.e. it will probably be around for a while and offers support. However, it's cheaper than some of the bigger competitors like Cloudinary. And it does PNG compression better than any of the free services out there, especially those with alpha channels.
       
      Installation
      Install the module like any other ProcessWire module, by either copying the folder into your modules folder or installing it via the admin. See above for downloads links on GitHub and in the module directory.
       
      Requirements
      To be able to upload images to the service, allow_url_fopen must be set on the server. The module will abort installation if that's not the case.
      I have only tested the module on ProcessWire 3.x installations. I don't see why it shouldn't work in 2.x, if anyone wants to try it out and report back.
       
      ImageOptim account
      To compress images, you first need to sign up for an ImageOptim account. They offer free trials to try the service.
       
      Usage (manual optimization)
      Images can be optimized by calling the optimize() method on any image. You can pass an options array to set ImageOptim API parameters.
      $image->size(800,600)->optimize()->url $image->optimize(['quality' => 'low', 'dpr' => 2]) // Set quality to low and enable hi-dpi mode
      Automatic optimization
      The module also has an automatic mode that optimizes all image variations after resizing. This is the recommended way to use this module since it leaves the original image uncompressed, but optimizes all derivative images.
      $image->size(800,600)->url // nothing to do here; image is optimized automatically
      To change compression setting for single images, you can pass an options array along with the standard ImageResizer options. Passing false disables optimization.
      $image->size(800, 600, ['optimize' => 'medium']) $image->size(800, 600, ['optimize' => ['quality' => 'low', 'dpr' => 2]]) $image->size(800, 600, ['optimize' => false])
      For detailed usage instructions and all API parameters, see the usage instructions on GitHub.
       
      Filenames
      Optimized images will be suffixed, e.g. image.jpg becomes image.optim.jpg. You can configure the suffix in the module settings.
       
      Roadmap
      Asynchronous processing. Not really high on the list. Image variations need to be created anyway, so waiting a few seconds longer on first load is preferable to adding complexity to achieve async optimization. Optimize image variations created by other modules. CroppableImage comes to mind. I don't use any of these, so if somebody wants to help out and submit a pull request — all for it! Add a dedicated page in the setup menu with a dashboard and detailed statistics. ImageOptim's API is very barebones for now, so not sure if that's feasible or even necessary.
      Stability
      I've been using this module on production sites for some time now, without hiccups. If you do notice oddities, feel free to comment here or investigate and submit PRs.