Jump to content

Feature request: Page ID attribute


mindplay.dk
 Share

Recommended Posts

Several times now, the following has happened to me: I place a page in the tree as "/team", and I have some templates for menus etc. that go and grab that specific page, to create a list of it's children. Another team member (someone who's working on content) comes in and moves that page from "/team" to, say, "/about/team" - the template that tries to list the children of the "/team" page are now broken and listing nothing at all.

ProcessWire gets around this issue by placing the actual page IDs in a configuration file somewhere - I really don't like that approach, since those IDs are just auto-incrementing numeric keys with no particular meaning.

I'd like to request a simple, small new feature: an optional, unique page ID attribute.

This would work much like the ID-attribute in the DOM - where only one element with the same ID is allowed in the same document, only one page would be allowed to have the same ID on a site.

And where in the DOM, you can find a particular element by it's ID, regardless of where in the document it's located, in ProcessWire, you'd be able to find a page by it's ID, regardless of where in the sitemap it's located.

Thoughts?

Edit: a different term would need to be used, since the numeric ID is already known as the "page ID" - so maybe "page key".

  • Like 2
Link to comment
Share on other sites

So you mean instead of:

$pages->get(1919); // grab the teams-page

You would like to have:

$page->get("teams"); 

It had not occurred to me to overload the $page->get() method in that way, but I guess that would make sense, yeah - a simple check for number vs string in the method should make it easy to differentiate numeric ID requests from "key" requests.

Link to comment
Share on other sites

I'm going to state the obvious here and point out that you shouldn't change a site's structure that significantly after launch anyway.

Having said that, I wonder if the best way of doing this is something like the PageHistory module that tracks page locations and renames (I think that that module only tracks renames but it might do both). Then calls using the API like in your example could be routed to the correct place (pretty sure that that module does'nt do that yet but I might be wrong!).

Link to comment
Share on other sites

This will had complexity and force the table to have one more index, no?

Personaly I'm happy with

$pages->get(1919); // get "teams"

Yeah, I don't like that at all.

What PW does internally is at least somewhat cleaner than that - defining the ID once and making it available globally, e.g. $pages->get($config->teamPageID) would work okay and is reasonably semantic.

I don't think this feature would add any significant amount of complexity, it's a very simple feature.

It would feel good in queries as well, with a CSS-like syntax, e.g. $pages->find('#team, template=team-member')

I'm going to state the obvious here and point out that you shouldn't change a site's structure that significantly after launch anyway.

Of course, not after launch - during development.

Link to comment
Share on other sites

There are quite a few ways to accomplish this already:

// numeric id should remain same no matter what.. and if it doesn't, no other attribute could be trusted either
$pages->get(1919);
// like you pointed out yourself, config values are one option; you can set them in config.php or run-time
$pages->get($config->teamPagesID);
// you could also use your own custom field for this and define it global if you want it to always exist
$pages->get('page_id=team'); 
// if the name of that page is very unlikely to change (and very likely to be unique), you could even find the page based on that
$pages->get('name=team');

This is just my opinion, but I'm not confident that this would be beneficial enough to justify a core addition. I'd rather suggest that you turn it into a module; under the hood $pages->get() uses $pages->findOne() which in turn uses $pages->find() and since $pages->find() is hookable you could easily inject your own desired functionality there.

This would, of course, require addition of that custom page_id field I mentioned above and perhaps another module / method to fill it in (and make sure that it's really unique) but that doesn't sound too complicated either.

Edit: clarification about the uniqueness of name field added, thanks @Soma for pointing this out.

Edited by teppo
  • Like 4
Link to comment
Share on other sites

I forgot about the config option - seems like the most sensible solution since page IDs don't change and after initially setting these in the config you would never have to change them again.

Link to comment
Share on other sites

Yep. If using id or $config->specificPageId isn't enough, then I think best option would be to create new fieldtype (globalName), which keeps names unique in global space. Then adding the new field as a global (if really wanted for everything). This would be as effective as having it as a core module. Clearly a good place for a module, not to core feature in my opinion.

Link to comment
Share on other sites

Name field isnt unique. A textfield and making it global isnt either.

Good point, name field probably isn't best possible solution here. And yes, if that text field would have to be strictly unique it would require a module that makes sure that this is always the case -- or better yet a new field type as Antti pointed out above :)

Link to comment
Share on other sites

I think it's a good feature to have. There are times when I use an ID get a $pages->get(123) and it always demands a comment to describe what the ID is. Whereas I much prefer "self describing" identifiers, like paths. But of course, it's possible for a path to change (for some sites). I also agree it's not something that's needed in the core (or at least turned on by default), where we are trying to keep things simple and not introduce more new concepts for people to keep track of. But for regular users of PW, I can see this being a useful module to have. It would be particularly easy to implement too... basically just the FieldtypeText with a mysql 'unique' index on the data column. If we were implementing this, it might be nice to have 'id' and 'class' attributes supported by the fieldtype, for familiarity and consistency with what people are using in html/css. Then people could retrieve pages with selectors like "attr.id=form-builder" or "attr.class=beta". Though the 'class' one would be a little harder to implement than the id one, and it has a lot of crossover with selection by template, page reference, etc., so definitely not as useful as id. 

  • Like 1
Link to comment
Share on other sites

// you could also use your own custom field for this and define it global if you want it to always exist
$pages->get('page_id=team'); 

Of course! How did I not think of that! :)

The only issue then, is how to enforce that this field must contain a unique value?

Perhaps a very simple new InputField type could perform that validation? This would also enable you to have more than one type of ID, as you could add multiple fiedls, each with a unique value. I would be nice to have this be a standard feature. Could be useful for other things.

Link to comment
Share on other sites

The only issue then, is how to enforce that this field must contain a unique value?

The simplest way would just be to edit the table in phpmyadmin and add a 'unique' index to the 'data' column. But I think what we'll do is either offer this option on the Text fieldtype or have another [similar] fieldtype that already has the unix index built in. 

Perhaps a very simple new InputField type could perform that validation?

In this case, the Inputfield probably wouldn't get involved in it since the uniqueness would be enforced at the DB index level. Though the Inputfield would still be the one reporting the error when it occurs. 

Link to comment
Share on other sites

I guess I don't completely understand where or how input-validation occurs.

In input-fields or in field-types?

I was trying to reference the Integer input-field and field-type the other night, but it just sanitizes the input and throws away invalid values without complaint. Come to think of it, this could be a problem if the user thinks they've actually put in a value like, say, a price, and a product gets published to the site with no price-value...

Link to comment
Share on other sites

  • 1 month later...

Looks like validation occurs in input-fields?

I started writing a simple module:

https://gist.github.com/mindplay-dk/71b7c74b1d73fe58146a

I don't know how to implement the uniqueness validation...

As far as I can tell, input-fields are unaware of the Page being edited? As you can see near the TODO in InputfieldUniqueId, I'm stuck...

Field-types, on the other hand, don't seem to be able to report error-conditions? So I can't put the validation there.

(Is this the real reason why so many field-types simply throw away invalid values without warning? Because they can't report invalid values? I'm pretty concerned about this, as mentioned above...)

Link to comment
Share on other sites

Looks like validation occurs in input-fields?

The answer is both Inputfields and Fieldtypes, but they are validating in totally different contexts. Inputfields validate user input. Fieldtypes validate data set to a $page, regardless of where it comes from. Think of Fieldtypes as API-side type validation, and Inputfields as interactive/user-input validation. Here's more detail: 

Inputfields only come into play when there's interactivity, i.e. a form and a user inputting something. Input should be validated by Inputfields so that the data is valid enough to echo back to the user, or for something else to pull the value from it. Inputfields should always be validated server-side where appropriate, but can also optionally include client-side validation. Inputfields can be used separately from Fieldtypes (they are used throughout ProcessWire in all forms). If there is some kind of input validation that isn't specific to a page or fieldtype, then it should be done by the Inputfield. Meaning, most validation responsibility goes with the Inputfield and an Inputfield shouldn't assume there's going to be any more validation beyond it. 

Inputfield sanitization/validation is done in $inputfield->processInput() or $inputfield->setAttribute('value', ...). The processInput() method is the first line of defense for values coming directly from a form. Whereas the setAttribute('value', ...) will see values sent from the form as well as values sent from the API to the $inputfield, like an $inputfield->attr('value', 'some value'), call. Where you put your sanitization/validation depends on what you are trying to account for.

You may see some Inputfields have a setAttributeValue() function. This is not part of the Inputfield interface. It's just a methodology I sometimes use to isolate setAttribute('value', ...) for more readable code. 

Inputfields report errors by calling $this->error('error message'); The user will see that error message within the context of the field. 

Fieldtypes come into play every time a value is populated to a page. This could be from the API, the core, modules, importers, Inputfields… really anything. They need to validate that the value sent to the page is consistent with the type of data the Fieldtype is responsible for (type validation). So if the Fieldtype only stores a string, then it needs to make sure it's a string. If you set something invalid to a $page, the Fieldtype needs to either convert it to be something valid or if it can't, throw it out. For the most part, they should do it as quickly and silently as possible, because they get such a high volume of traffic. Not to mention, Fieldtypes can't assume there's a user present to see anything.

Fieldtype sanitization/validation is performed by $fieldtype->sanitizeValue(). Every value set to a $page gets routed through that function, for the appropriate fieldtype. Whatever you do in there needs to be fast. 

I was trying to reference the Integer input-field and field-type the other night, but it just sanitizes the input and throws away invalid values without complaint.

That would be the correct behavior for the Fieldtype, but not for the Inputfield. 

As far as I can tell, input-fields are unaware of the Page being edited? As you can see near the TODO in InputfieldUniqueId, I'm stuck...

The scope for Inputfields goes beyond just pages. They don't know what they are being used for. If they did, then their usefulness would be much less. But that's just "in general". There are some cases where you are building a Page-dependent Inputfield and your Inputfield needs to know about the $page being edited. Core examples would Inputfields related to files and repeaters. In that case, make your Fieldtype pass it along to the Inputfield. Each Fieldtype has a getInputfield() method that gets a copy of the $page and $field being edited. It has to return the appropriate Inputfield to edit the provided data. So you can easily send it along to the Inputfield right from there:

public function getInputfield($page, $field) {
  $inputfield = wire('modules')->get('InputfieldSomething'); 
  $inputfield->set('editPage', $page); // inputfield now has a $this->editPage property
  return $inputfield; 
}
Field-types, on the other hand, don't seem to be able to report error-conditions? So I can't put the validation there.

They can report error conditions, but technically they aren't the place for it. 

(Is this the real reason why so many field-types simply throw away invalid values without warning? Because they can't report invalid values? I'm pretty concerned about this, as mentioned above...)

Yes. In general, Fieldtypes can't assume there's anyone there to see an error. But if you have some need where you think it is necessary to do from the Fieldtype, try $this->error('message') … it will only be seen in an interactive context. If you want to report an API error, throw an Exception. But if you can keep all of your error reporting in the Inputfield, that is the right place for it. 

  • Like 5
Link to comment
Share on other sites

You should copy/paste these explanations to the wiki - this is important stuff.

Added to the wiki. Though not exactly sure how to categorize it… but we'll figure it out eventually. 

Link to comment
Share on other sites

I think we need a "core concepts" category - explaining every major idea in PW, and it's relations to other ideas. This stuff is critical to help new developers get up to speed. THanks for taking the time to describe this :)

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...