Jump to content

extend InputfieldSelectMultiple but save some extra text too?


bcartier
 Share

Recommended Posts

Hi guys,

I'm creating a simple InputField for selecting multiple page references. It displays a text field that accepts a PW selector. When you click the "Apply" button you see a count of matching pages. The use case is selecting large numbers of pages from very large page sets very quickly without slowing down the edit page form. 

I've got the selector query working with the Page Search API  (populating a hidden field, just like PageAutocomplete does). Saving the page creates the references. The missing piece is saving the actual selector.

The InputField currently extends InputfieldSelectMultiple to be able to save multiple page refs, so I'm not quite sure how to also save the selector the user enters to get those pages. I starting this thing by working from PageAutocomplete, but I wonder if I've painted myself into a corner.

I was hoping someone might have some suggestions...

Thanks for you help!

-Brent

  • Like 1
Link to comment
Share on other sites

Sounds interesting.

You should be able to save the selector by defining a new database field for your fieldtype (in addition to the standard "data" field). This approach is used in a few fieldtype modules, like:

http://modules.processwire.com/modules/fieldtype-phone/

http://modules.processwire.com/modules/fieldtype-dimension/

http://modules.processwire.com/modules/fieldtype-map-marker/

Have a look at the getDatabaseSchema and updateDatabaseSchema functions in those modules, along with their dabatase tables and you should get the idea.

Hope that helps.

Also, this is just a quasi-related module that I'd reference here for the sake of it:

http://modules.processwire.com/modules/fieldtype-pages-selector-query/

Link to comment
Share on other sites

Thanks adrian

So far the addon is only an InputField. Is there a way I can leverage the DatabaseSchema from the InputField, as opposed to a FieldType? 

Will I need to create a custom fieldtype that extends the Page fieldtype?

Really appreciate the help!

-Brent

Link to comment
Share on other sites

Brent - sorry, I didn't read that you were only doing an InputField at the moment. From my understanding of things, a new FieldType is likely your only option.

Actually, I wonder if you can have a separate field that stores the selector. Maybe that hidden field you are using is an actual PW field, or you take the content of your existing hidden field and copy it to an actual PW field (visibility set to hidden) on page save. Then on form render it is grabbed and populates the selector text field. Something like that anyway. Not sure how well this would work - seems like it should be possible, but maybe someone else will chime in with a better idea, or flame this one :)

Link to comment
Share on other sites

Are you wanting to store just the selector (text) or both the selector (text) and the resulting page IDs? I'm thinking just the selector text, since the resulting pages may change ... that's what's kind of unique about storing the selector string, as the results of it really couldn't be known until runtime. If that's the case, then I don't think there would be any reason to extend FieldtypePage, as it is heavily geared towards storing IDs of page (references). Your own Fieldtype could store just the selector string, but expand that to a PageArray in the Fieldtype::formatValue function, as one possibility. The main disadvantage of storing just the selector is that you couldn't query the field from another selector to know what pages were stored in it... since the results are determined at runtime and not stored on disk. 

If you only need the selector string as a way to determine which pages are selectable (as opposed to which pages are selected), then this is pretty much in-line with what the PageAutocomplete inputfield does. Only it sounds like you need to define this selector on a per-page basis, rather than a per-field basis. Your Inputfield certainly could create its own DB table if you wanted it to. While it's true that dealing with the storage-side is exclusive to Fieldtypes, this one would be kind of a grey area. If it suits your needs and is ultimately simpler, don't be afraid to have your Inputfield create it's own DB table and store whatever it needs to. But keep in mind that by taking this route, it may prevent the Inputfield from having much value in other contexts like other Inputfield forms or FormBuilder (which may not matter) ... this is already the case with PageAutocomplete and PageListSelect Inputfields. 

  • Like 1
Link to comment
Share on other sites

Thanks for your thoughts Ryan!

My specific use case is selecting from a large but mostly static set of pages. One example is selecting from a set of municipalities. The page tree structure is Country > Province > Region > City. What makes a selector work so well in these cases is that this hierarchy has an existing code system that represents the hierarchy. A province might have a geographical code of 35, then a region might be 3506 then a city would be 3506008. This makes using a selector easy, because the content editors know the geographical codes well (and there are lots of external references they could use. 

In this case I do need to store both the selector and also store page references. I want to be able to find pages that have a specific geographical code as a page reference. I think the module adrian mentioned already does a good job of storing the selector and creating the page array at run time, but the main use for my case is to be able to query the page references like pages->find('template=program,code*=35') to find programs that apply to the province and all it's municipalities.

Should I continue looking into extending FieldtypePage? I assume  would have to add another table? It would store page_id and the selector, as opposed to the default for page references, which is page_id of the parent and page_id of the referenced page (one row per reference)... right?

^

EDIT: Actually, if working with the DB is feasible in an InputField, it would make more sense to create the "selector" storage table as part of the Inputfield. That way I wouldn't have to extend FieldtypePage at all.  Ryan - do you agree that this would be an acceptable case for bending the rule of only using Fieldtypes for DB manipulation?

Thanks again for your help, 

-Brent

Link to comment
Share on other sites

Brent, if I understand correctly I think I agree there. Though if you are wanting to query the code like "35" then your selector probably needs to be something like "template=program, code.title^=35". 

This may be a dumb question, but if the content editors already know the geographical codes, why use page references? Could you just store the codes themselves?

Assuming both the matching string "35" and the resulting page references need to be stored, then I'd probably approach it the way you are. If it was something I was releasing for others to use, I'd make a Fieldtype and an Inputfield. The Fieldtype would have the same schema as the current FieldtypePage schema, except that it would have an extra column for "code", to contain an integer that the user enters. I'd have the column indexed like the others, so that it could be queried from selectors. It would offer a shortcut for querying, potentially faster than querying the page titles of page references.

One of the advantages of going the Fieldtype route is that ProcessWire will be managing things like deletions. If you delete a page, then it's going to delete the entries for that page in the DB. If you are managing your own via the Inputfield, that won't happen. This may or may not be an issue in your case. Just depends on how permanent the data is I guess. It could always be resolved fairly easily by adding a Pages::deleted hook. 

In your case, since the use is so specific I think just developing an inputfield, and storing those codes in your own table may offer a simpler solution. You'd just need to store the pages_id and code. Your render() method would do something like this:

// get page being edited from ProcessPageEdit
$page = $this->process->getPage();
$query = $this->database->prepare("SELECT code FROM your_table WHERE pages_id=:id"); 
$query->bindValue(":id", $page->id, PDO::PARAM_INT);
$query->execute();
if($query->rowCount() > 0) {
  list($code) = $query->fetch(PDO::FETCH_NUM);    
  $code = $this->sanitizer->entities($code); 
} else {
  $code = '';
}

$out = '';
// here's where they enter the code, already populated with the existing value
$out .= "<input type='text' name='{$this->name}_code' value='$code' />"; 
// then your page selection follows

For your processInput method, you'd do the opposite... storing the code that was entered. 

$page = $this->process->getPage();
$code = $input[$this->name . "_code"]; 
// delete existing value if already present
$query = $this->database->prepare("DELETE FROM your_table WHERE pages_id=:id"); 
$query->bindValue(":id", $page->id, PDO::PARAM_INT); 
$query->execute();
// insert the new value for code 
$query = $this->database->prepare("INSERT INTO your_table SET pages_id=:id, code=:code"); 
$query->bindValue(":id", $page->id, PDO::PARAM_INT); 
$query->bindValue(":code", $code, PDO::PARAM_INT); // or PARAM_STR if appropriate
$query->execute();
 

Let me know if you want an example of how to do the Pages::deleted hook. 

Link to comment
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
 Share

  • Recently Browsing   0 members

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