Jump to content
BitPoet

JsonNativeField: key-value table + native JSON = fully searchable

Recommended Posts

Another proof-of-concept module:

JsonNativeField

Leverages MySQL >= 5.7.8's native JSON column type for key-value storage.

This gives us the opportunity to store arbitrary (textual) fields in the database and allows us to search for them with plain subfield selector syntax, including wildcard operators. Possible applications are storing submitted form data or adding user-defined properties to their profile.

Currently, the module is still really, really alpha, but I wanted to get input early on.  Let me know what you think.

Download from GitHub.

Here are a few screenshots for anybody interested but unable to try for themselves:

1. First page with json field and custom subfields:

jsonfield1-page1.png

 

2. Second page with json field and custom subfields:

jsonfield2-page2.png

 

3. Searching through all subfields in the json field:

jsonfield3-search-all-subfields.png

 

4. Searching only in a specific subfield:

jsonfield4-search-specific-subfield.png

 

The interface is really rather crude still. The "delete" buttons only work after saving (have to attach listeners to the newly created entries yet), and I've got to straighten out supported operators for Lister to pick up. I'll see if I find some time tomorrow to work on these issues and brush up the visual side a bit.

Edited by BitPoet
Added screenshots
  • Like 19

Share this post


Link to post
Share on other sites

Sounds great, unfortunately the MySQL requirements doesn't allow me to try. Any chance to add a screenshot to see something "tangible"? :)

  • Like 1

Share this post


Link to post
Share on other sites
25 minutes ago, tpr said:

Sounds great, unfortunately the MySQL requirements doesn't allow me to try. Any chance to add a screenshot to see something "tangible"? :)

Done. :)

  • Like 2

Share this post


Link to post
Share on other sites

Thanks, looks even better :) I often have a need for a WordPress "custom fields"-like field in PW and this module has this.

  • Like 2

Share this post


Link to post
Share on other sites

this could be a good field to hold site settings when your users need to be able to keep defining/adding settings.

Share this post


Link to post
Share on other sites

Just a quick status update: I've started implementing different field types (text, integer, float, date, date+time) with native inputfields. Types can be switched back and forth, and I'll make it hookable so custom inputfields can be added as well. Will be a while though, since it looks like I'll be busy with a lot of other stuff until the end of next week. Here's a teaser though...

jsonfield5-types.png

  • Like 8

Share this post


Link to post
Share on other sites

Unexpectedly found a half day to spare, so I've pushed 0.0.3 to GitHub.

New features:

  • Field settings for custom date, date+time and text fields can now be made on the inputfield's input tab, e.g. date and time formats, placeholder text or text validatoin patterns (numeric field settings are on the todo list)
  • Names for custom fields are limited to start and end with ASCII letters and only have alphanumeric ASCII characters and hyphens in between
  • Custom field name of "data" is silently stripped to avoid bollixing up all entries
  • The custom fields table in the backend is now sorted by name (new entries simply get appended)

The module is still alpha. Things are getting smoother, but there are still a few items on the todo list. :)

  • Like 9

Share this post


Link to post
Share on other sites

@BitPoet

Wow, this is perfect timing, as I am just working on the requirements for a project that requires storing a lot of JSON.

Does this enable storage and retrieval of nested JSON content that is multiple layers deep? E.g. $pages->find('jsonField.subField.field=foo') ?

Obviously this would present some challenges for displaying the data on the back end using your current tabular format. Maybe a nested list (like ProcessWire's page tree) would be a more scalable approach?

Share this post


Link to post
Share on other sites

I admit I haven't thought too much about nesting, and the wiring around the field's json data would obviously need to be completely different. InnoDB does support searching in nested structures, but PW already treats subfields on the left side of a selector in its own way and throws away anything starting at the second full stop. A different syntax would be necessary, e.g.

$pages->find('jsonField=/subField/field:foo') 

There might be issues where search expressions need some kind of escaping (e.g. searching for literal text "/subfield"), but this would allow for wildcard queries.

$pages->find('jsonField=/subField/*/name:foo') 

Or even searching arrays (any element or by index):

$pages->find('jsonField=/subField/fields#*/name:foo');

$pages->find('jsonField=/subField/field#12/name:foo');

Here's a tiny adoption of my module to allow this search syntax. There's no back and forth conversion though, just a textarea containing the JSON.

<?php

class FieldtypeJsonDocument extends FieldtypeTextarea implements Module {
	public static function getModuleInfo() {
		return array(
			"title"				=>	"Fieldtype Json Document",
			"summary"			=>	"Fieldtype utilizing native MySQL support for searching JSON documents.",
			"version"			=>	"0.0.3",
		);
	}
	
	public function getMatchQuery($query, $table, $subfield, $operator, $value) {
		$database = $this->wire("database");

		list($path, $value) = explode(':', $value, 2);
		if(empty($value) && !empty($path)) {
			$value = $path;
			$path = "";
		}
		$path = '$' . ((empty($subfield) || $subfield == "data") ? "" : ".$subfield") . (empty($path) ? ".*" : str_replace('/', '.', preg_replace('~/?#(\d+|\*)~', "[$1]", $path)));

		$table = $database->escapeTable($table);
		$value = $database->escapeStr($value);
		if($operator == "=") {
			$query->where("JSON_SEARCH({$table}.data, 'one', '$value', NULL, '$path') IS NOT NULL");			
		} else if($operator == "*=" || $operator == "%=") {
			$query->where("JSON_SEARCH({$table}.data, 'one', '%$value%', NULL, '$path') IS NOT NULL");			
		} else if($operator == "^=") {
			$query->where("JSON_SEARCH({$table}.data, 'one', '$value%', NULL, '$path') IS NOT NULL");			
		} else if($operator == "$=") {
			$query->where("JSON_SEARCH({$table}.data, 'one', '%$value', NULL, '$path') IS NOT NULL");			
		}

		$this->log->message($query->getQuery());

		return $query; 
	}

	
	public function getDatabaseSchema(Field $field) {
		$engine = $this->wire('config')->dbEngine; 
		$charset = $this->wire('config')->dbCharset;
		$schema = array(
			'pages_id' => 'int UNSIGNED NOT NULL', 
			'data' => "JSON", // each Fieldtype should override this in particular
			'keys' => array(
				'primary' => 'PRIMARY KEY (`pages_id`)',
			),
			// additional data 
			'xtra' => array(
				// any optional statements that should follow after the closing paren (i.e. engine, default charset, etc)
				'append' => "ENGINE=$engine DEFAULT CHARSET=$charset", 
				
				// true (default) if this schema provides all storage for this fieldtype.
				// false if other storage is involved with this fieldtype, beyond this schema (like repeaters, PageTable, etc.)
				'all' => true, 
			)
		); 
		return $schema; 
	}

    public function getInputfield(Page $page, Field $field) {
        $inputField = $this->modules->get('InputfieldTextarea');
        return $inputField;
    }
	
	public function install() {
		if($this->config->dbEngine != "InnoDB") {
			throw new WireException($this->_("InnoDB database engine needs to be used for native JSON support"));
		}
		
		$dbver = $this->database->getAttribute(PDO::ATTR_SERVER_VERSION);
		if(version_compare($dbver, '5.7.8', '<')) {
			throw new WireException(sprintf($this->_("MySQL Server version needs to be at least 5.7.8 for fully working JSON support, installed version is %s"), $dbver));
		}
	}
}

There would probably be a bit of recursive shuffling necessary in sleep-/wakeup-/sanitizeValue to build nested structures based on WireData/WireArray so accessing subfields and filtering PageArrays is possible, but that should be doable too. How difficult the backend input + display parts get depends solely on your exact requirements.

  • Like 4

Share this post


Link to post
Share on other sites

Status update: JsonNativeField version 0.0.4 now has configuration options for all field types in the field's input settings. Coming up next is some more data validation (i.e. "don't shoot your foot" checking) both in the UI when switching input type and in the module code when saving to and loading from the db. This will be the major step towards moving the module from alpha to beta stage.

  • Like 6

Share this post


Link to post
Share on other sites

It's a bit on the back burner right now. I was hoping for some more elaborate possibilities with MySQL 8, but the changes to JSON support there were on the homeopathic end rather than the dynamic typing support I had hoped for. I plan to brush up the UI and client side validation a bit once PW 3.1 gets out, though, and test things out in depth with UIkit admin theme.

  • Like 6

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By gebeer
      Although the PW backend is really intuitive, ever so often my clients need some assistance. Be it they are not so tech savvy or they are not working in the backend often.
      For those cases it is nice to make some help videos available to editors. This is what this module does.
      ProcessHelpVideos Module
      A Process module to display help videos for the ProcessWire CMS. It can be used to make help videos (screencasts) available to content editors.
      This module adds a 'Help Videos" section to the ProcessWire backend. The help videos are accessible through an automatically created page in the Admin page tree. You can add your help videos as pages in the page tree. The module adds a hidden page to the page tree that acts as parent page for the help video pages. All necessary fields and templates will be installed automatically. If there are already a CKEditor field and/or a file field for mp4 files installed in the system, the module will use those. Otherwise it will create the necessary fields. Also the necessary templates for the parent help videos page and it's children are created on module install. The module installs a permission process-helpvideos. Every user role that should have access to the help video section, needs this permission. I use the help video approach on quite a few production sites. It is stable so far and well received by site owners/editors. Up until now I installed required fields, templates and pages manually and then added the module. Now I added all this logic to the install method of the module and it should be ready to share.
      The module and further description on how to use it is available on github: https://github.com/gebeer/ProcessHelpVideos
      If you like to give it a try, I am happy to receive your comments/suggestions here.
    • By Robin S
      A module created in response to the topic here:
      Page List Select Multiple Quickly
      Modifies PageListSelectMultiple to allow you to select multiple pages without the tree closing every time you select a page.
      The screencast says it all:

       
      https://github.com/Toutouwai/PageListSelectMultipleQuickly
      https://modules.processwire.com/modules/page-list-select-multiple-quickly/
    • By gebeer
      Hello all,
      sharing my new module FieldtypeImagePicker. It provides a configurable input field for choosing any type of image from a predefined folder.
      The need for it came up because a client had a custom SVG icon set and I wanted the editors to be able to choose an icon in the page editor.
      It can also be used to offer a choice of images that are used site-wide without having to upload them to individual pages.
      There are no image manipulation methods like with the native PW image field.
      Module and full description can be found on github https://github.com/gebeer/FieldtypeImagePicker
      Kudos to @Martijn Geerts. I used his module FieldTypeSelectFile as a base to build upon.
      Here's how the input field looks like in the page editor:

      Hope it can be of use to someone.
      If you like to give it a try, I'm happy to hear your comments or suggestions for improvement. Eventually this will go in the module directory soon, too.
    • By bernhard
      @Sergio asked about the pdf creation process in the showcase thread about my 360° feedback/survey tool and so I went ahead and set my little pdf helper module to public.
      Description from PW Weekly:
       
      Modules Directory: https://modules.processwire.com/modules/rock-pdf/
      Download & Docs: https://github.com/BernhardBaumrock/RockPDF
       
      You can combine it easily with RockReplacer: 
      See also a little showcase of the RockPdf module in this thread:
       
    • By ukyo
      FieldtypeFontIconPicker
      Supported Icon Libraries
      FontAwesome 4.7.0 Uikit 3.0.34 IonicIcons 2.0.1 Cahangelog
      NOTE: Module store data without prefix, you need to add "prefix" when you want to show your icon on front-end, because some of front-end frameworks using font-awesome with different "prefix".
      Module will search site/modules/**/configs/IconPicker.*.php and site/templates/IconPicker.*.php paths for FieldtypeFontIconPicker config files.
      All config files need to return a PHP ARRAY like examples.
      Example config file : create your own icon set.
      File location is site/configs/IconPicker.example.php
      <?php namespace ProcessWire; /** * IconPicker : Custom Icons */ return [ "name" => "my-custom-icons", "title" => "My Custom Icon Set", "version" => "1.0.0", "styles" => array( wire("config")->urls->templates . "dist/css/my-custom-icons.css" ), "scripts" => array( wire("config")->urls->templates . "dist/js/my-custom-icons.js" ), "categorized" => true, "attributes" => array(), "icons" => array( "brand-icons" => array( "title" => "Brand Icons", "icons" => array( "google", "facebook", "twitter", "instagram" ) ), "flag-icons" => array( "title" => "Flag Icons", "icons" => array( "tr", "gb", "us", "it", "de", "nl", "fr" ) ) ) ]; Example config file : use existing and extend it.
      File location is site/configs/IconPicker.altivebir.php
      <?php namespace ProcessWire; /** * IconPicker : Existing & Extend */ $resource = include wire("config")->paths->siteModules . "FieldtypeFontIconPicker/configs/IconPicker.uikit.php"; $url = wire("config")->urls->templates . "dist"; $resource["scripts"] = array_merge($resource["scripts"], ["{$url}/js/Altivebir.Icon.min.js"]); $resource["icons"]["flag-icons"] = [ "title" => "Flag Icons", "icons" => array("tr", "en", "fr", "us", "it", "de") ]; $resource["icons"]["brand-icons"]["icons"] = array_merge($resource["icons"]["brand-icons"]["icons"], array( "altivebir" )); return $resource; After you add your custom config file, you will see your config file on library select box. Library Title (Location Folder Name).

      If your library categorized and if you have categorized icons set like uikit and fontawesome libraries, you will have category limitation options per icon field or leave it empty for allow all categories (default).

      Example : output
      if ($icon = $page->get("iconField")) { echo "<i class='prefix-{$icon}' />"; } MarkupFontIconPicker Usage
      // MarkupFontIconPicker::render(YourIconField=string, Options=array) echo MarkupFontIconPicker::render($page->YourIconField, [ 'prefix' => 'uk-icon-', // Icon class prefix, if you have different prefix, default is : "fa fa-" 'tag' => 'span', // Icon tag default is : "i" 'class' => 'fa-lg', // If you have extra cutom classes, for example : icons sizes, Array or Sting value 'style' => 'your custom styles if you have' // Array or String Value ]); Theme support

      Search support

      Category support

       
×
×
  • Create New...