Jump to content

ryan

Administrators
  • Posts

    16,451
  • Joined

  • Last visited

  • Days Won

    1,453

Everything posted by ryan

  1. That is definitely strange. There is no connection between the page ID and page name, so it's particularly odd that your name field is getting populated with the ID. It almost sounds to me like the browser is mixing up the fields. What browser are you using? Also what version of ProcessWire is this? (2.0 or 2.1) and how recently did you download it? Do you have any 3rd party modules installed? You mentioned that it developed this issue. Does that mean that it wasn't doing it, and then it started recently? If so, can you think of anything that changed, whether in your PW install, the server, or the browser you are using to access it?
  2. Arkster, Welcome to the forums and thank you for your feedback! Ryan
  3. Antti, thanks for the update! I have merged into the source, great ideas and updates. The module is still a work in progress and I agree it's showing too many options in the state before you updated it, so this is a welcome change. We may want to go even further, and not show any sidebar at all unless they clicked a "show more options" or "advanced search" link somewhere (perhaps under or above the search results). This is for reasons that are related to the paragraph below... Regarding the 'another way to browse', that's a good point. Wanted to get all these search options as GET vars so that other modules could link to specific things. For instance, when viewing what roles are assigned to a template, each role would be linked to a search of: "template=user, role=$role" so that you could then get a list of all users that have access to pages using a template. That's just one example of many that we could already start using in the admin. But in this use, I'm thinking it's maybe better to hide any other search options behind an 'advanced' link. In part because many users may not even think of this as a traditional search since it doesn't involve searching for any text. And less options may help it to feel more integrated with the context of where they clicked from. What do you think? One of the next steps for this module is also giving it the ability to output results in JSON, for ajax use. Thanks, Ryan
  4. Just updated the 2.1 development source code on GitHub with a new admin search module. This one is a major improvement over the existing admin search module (the existing one was very basic). This should also be compatible with 2.0, so I'm going to be adding it to the stable 2.0 branch after a little more testing. Here is a screenshot attached below that demonstrates what's new in this module.
  5. Thanks I just got to check it out -- tested with my latest version of 2.1. I'm really impressed with what you've done! I love what is possible with media queries. Nice work. I'm looking forward to keeping up to date with this. I'm also thinking I should make it possible to install admin templates like modules, so that you can keep several installed at once and be able to switch them out more easily... what do you think?
  6. I got to check out the video on my wife's laptop and this looks awesome, really great work! Im going to install later today. I've not had the opportunity to use and learn media queries yet, but this seems like a great use of them. So if I were to access the admin on a cell phone, it would use the narrowest interface? Very cool, I'm impressed.
  7. Thanks for posting this -- I can't wait to check this and the video out! I'm stuck on the cell phone till I can get back in the office tomorrow afternoon for a bit. This sounds really great.
  8. ryan

    Simple gallery

    Well done, that's a very good example of loading the thumbnails in the lightbox... A nice variation.
  9. ryan

    Simple gallery

    Giovanni, This is possible. The examples below are simple examples that exclude the container markup (like header/footer, etc.) but focus on just the parts you are asking about. Your main album index page would use template code something like this: /site/templates/album-list.php <ul class='albums'> <?php foreach($page->children() as $album) { // assumes your field with images is named 'images' if(!count($album->images)) continue; // skip if no images $image = $album->images->first(); // get the first image $thumb = $image->width(150); // make a thumbnail 150px wide echo "<li style='width: 150px; float: left; margin: 10px;'>"; // use stylesheet instead of inline styles echo "<a href='{$album->url}'><img src='{$thumb->url}' alt='' /></a><br />"; echo $album->album_description; echo "</li>"; } ?> </ul> <br style='clear: both;' /> Then your template that is used for each of the album pages would look something like this: /site/templates/album.php <h1><?=$page->title?></h1> <h2><?=$page->album_description?></h2> <ul class='images'> <?php foreach($page->images as $image) { $thumb = $image->size(100, 100); // specify your own width/height echo "<li style='width:100px; height:100px; float:left; margin:5px;'>"; // better to use a stylesheet echo "<a href='{$image->url}' class='lightbox' rel='gallery'>"; echo "<img src='{$thumb->url}' alt='{$thumb->description}' /></a>"; echo "</li>"; } ?> </ul> <br style='clear: both;' /> The details of how this loads into your lightbox script will depend on what lightbox script you are using. I have used jQuery Fancybox most recently, which requires a class attribute on the <a> tag containing the image ("lightbox" used in the example above), and a common "rel" attribute on that tag that relates it to the other images in the gallery ("gallery" used in the example above). The point of the "rel" attribute is so that you can have next/prev buttons in the lightbox.
  10. Great site, and looks like a great restaurant too! You are on a roll - launching a new site nearly every day. I need to catch up and update the 'sites running PW' page on the site soon.
  11. Looking good! My only question is where you rendering the $results? I just see the $results->renderPager(), and that only outputs the pagination navigation. Do you have some code after the "find()" and before the "echo" that renders the results?
  12. Nice work! I like all that you did with the photos.
  13. Hani, thanks for your continued updates. The sanitizeValue() function is most likely the reason why it's not saving values when you use the value/label ... the snippet I posted before wasn't coded with that in mind, so it would have to be updated to account for the change. While I haven't yet tested it, I'm thinking this might do it (to replace the preg_match currently in the sanitizeValue function): if(!preg_match('/(^|[\r\n]) *' . preg_quote($value, '/') . ' *(:=|[\r\n]+|$)/', $field->select_options)) $value = '';
  14. Almonk, Here's one way you could do it (below), using ProcessWire's WireUpload class: <?php $page->setOutputFormatting(false); // instantiate the class and give it the name of the HTML field $u = new WireUpload('userfile'); // tell it to only accept 1 file $u->setMaxFiles(1); // tell it to rename rather than overwrite existing files $u->setOverwrite(false); // have it put the files in their final destination. this should be okay since // the WireUpload class will only create PW compatible filenames $u->setDestinationPath($page->files->path); // tell it what extensions to expect $u->setValidExtensions('jpg', 'jpeg', 'gif', 'png'); // execute() returns an array, so we'll foreach() it even though only 1 file foreach($u->execute() as $filename) $page->files->add($filename); // save the page $page->save(); Another way to do it would be a more traditional PHP way: <?php $page->setOutputFormatting(false); // we run the $name through $sanitizer to make it PW compatible... // ...this prevents the possibility of it getting renamed on the add() call below $name = strtolower($sanitizer->name($_FILES['userfile']['name'])); // prepend underscores to filename until it's unique... // ...this is just one possible way to handle duplicate filenames while(is_file($page->files->path . $name)) $name = "_" . $name; // validate the file extension $pos = strrpos($name, "."); if(!$pos) throw new WireException("File is missing extension"); $ext = substr($name, $pos+1); if(!in_array($ext, array('jpg', 'jpeg', 'gif', 'png'))) throw new WireException("Invalid extension"); // determine the final destination and store it in $file $file = $page->files->path . $name; // move the file to it's destination using PHP's function if(move_uploaded_file($_FILES['userfile']['tmp_name'], $file)) { // add it to the page $page->files->add($file); /// save the page $page->save(); } Btw, no need to turn outputFormatting back on unless you are rendering parts of that page in the same request. Neither of these examples perform much error checking, which you may want to add. Though the WireUpload class will populate PW's "$notices" API var with any errors: <?php foreach($notices as $notice) { if($notice instanceof NoticeError) { echo "<p>Error: {$notice->text}</p>"; } } If you are using the PW's admin templates, then it already does something like the above snippet.
  15. If you want PW to do the pagination for you, you don't ever need to specify a "start=0" in your selector. PW will take care of making sure it starts at the right place, as long as you have page numbers turned on for your template (Admin > Setup > Templates > Advanced > Page Numbers). The "limit=n" implies that you substitute a number for "n", i.e. "limit=10". In the examples you listed, it looks like you are pulling from all the pages in the site? I'm guessing you want to pull from a more specific set of pages, like this? <?php $results = $pages->find("parent=/categories/bmw/, limit=10, sort=title"); echo $results->render(); or another way of saying the same thing: <?php $results = $pages->get("/categories/bmw/")->children("limit=10, sort=title"); echo $results->render(); One other thing I want to note is that $results->render() is provided by a plugin module. Make sure you have these modules installed under Admin > Modules > Markup > (you may need to click the 'install' button to activate them) MarkupPageArray: PageArray Markup MarkupPagerNav: Pager (Pagination) Navigation That render() method is just provided for convenience, and it doesn't do anything other than output a list of pages linked by title. Chances are you'll want to generate your own nav and then use the $results->renderPager() method at the bottom to output the pagination navigation.
  16. This is great! Thanks for posting this. This is a great guide for people that want to implement some of PW's form controls outside of the admin template. It looks like you've got all the parts covered. Since this thread is turning into a helpful tutorial and reference, I want to cover the different ways of getting and setting values to Inputfields: <?php // setting $inputfield->attr('name', 'value'); $inputfield->set('name', 'value'); $inputfield->name = value; // same as set() above, but shorter // getting $value = $input->attr('name'); $value = $inputfield->get('value'); $value = $inputfield->value; // same as get() above, but shorter The attr() method is designed for explicitly setting setting/getting attributes that should go with the form input. For instance, 'name', 'class', 'id', 'value', 'checked', 'type', or whatever other attributes you want to go with the input. It should not be used for anything else because whatever you pass to it will end up as an attribute on the markup that gets output. Whereas the set(), get() and direct reference methods are designed for setting/getting other properties, like field configuration options. They also work with attributes like the attr() method, but it's better to use the attr() method when setting an attribute just to be clear to PW that you intend that to be an actual attribute with the markup that gets output. If you look in PW's code, you might also see it using these two functions in some instances: <?php $inputfield->setAttribute('name', 'value'); // same as $inputfield->attr('name', 'value'); $value = $inputfield->getAttribute('name'); // same as $value = $inputfield->attr('name'); These are the same thing as the attr() method, and the preferred syntax is to use the attr() method in your code. Internally, PW translates the attr() method to either setAttribute() or getAttribute(), depending on the number of arguments. The reason setAttribute() and getAttribute() exist is for people creating new Inputfield classes... it's easier to override single purpose methods rather than multi-purpose methods (like attr). But on a public interface, it's easier to use a multi-purpose method like attr(), so I recommend ignoring setAttribute() and getAttribute() unless you are developing new Inputfields. Variations of attr() In addition to getting and setting single attributes, the attr() method can also set multiple attributes at the same time. Below are all the possible variations (some repeated from above): <?php // set single attribute (same as example above) $inputfield->attr('name', 'value'); // get single attribute (same as example above) $value = $inputfield->attr('name'); // set multiple attributes with same value $inputfield->attr('id+name', 'value'); // set multiple attributes with different values $attrs = array( 'name' => 'value', 'name' => 'value', // etc.. ); $inputfield->attr($attrs); Inputfields that contain other Inputfields One type of Inputfield is the InputfieldWrapper, and it's designed solely to contain other Inputfields. Examples of InputfieldWrappers also include InputfieldForm and InputfieldFieldset (all are derived from InputfieldWrapper). On these Inputfields, the get() and direct reference can be used to retrieve any one of the fields by name: <?php $inputfield = $form->get('your_field_name'); $inputfield = $form->your_field_name; // direct reference works as alternate syntax Internally, the two calls above translate to this: <?php $inputfield = $form->find("name=your_field_name")->first(); What that means is it'll find any field in the form, not just direct children of the field you are checking. So a call to $form->get('your_field_name') will return the associated field, regardless of of many fieldsets it's wrapped under. This is just to keep things simple. After all, the form fields are all living in the same namespace when the form gets output. By the way, that find() method mentioned above can be used with any selector, just like with pages. It will return all inputfields in the form that match the properties you give it. But in practice, I've not ever needed it in my forms, so not sure how useful it really is. You can add/remove Inputfields as children using these methods. In the example below, we'll assume that $form is an instance of InputfieldForm: <?php $form->append($inputfield); // append an inputfield to the form $form->prepend($inputfield); // prepend an inputfield to the form $form->add($inputfield); // same as append() $form->remove($inputfield); // remove inputfield from the form Built in properties All Inputfields have these built-in properties that you can set or get: <?php $inputfield->label = "The clickable label that appears above this field. Should only be a few words."; $inputfield->description = "A longer description that appears below the label. Can be any length."; $inputfield->notes = "A extra highlighted area that appears under the field. Can be any length."; $inputfield->head = "Headline that appears below label/above description. "; // PW 2.1 only! $inputfield->id; // HTML 'id' attribute. Auto-generated if you don't set it. $inputfield->name; // HTML 'name' attribute, required $inputfield->value; // HTML 'value' attribute, if applicable $inputfield->class; // HTML 'class' attribute, optional $inputfield->required = 0; // value not required for this inputfield $inputfield->required = 1; // value IS required for this inputfield $inputfield->collapsed = Inputfield::collapsedNo; // Field will display open (this is the default) $inputfield->collapsed = Inputfield::collapsedYes; // Field will display collapsed, requiring a click to open $inputfield->collapsed = Inputfield::collapsedBlank; // Field will display collapsed only if blank $inputfield->collapsed = Inputfield::collapsedHidden; // Field will not be rendered in the form
  17. Hani, nice work with this fieldtype, as well as the state select one. I tried both out and they work well! I should explain why PW doesn't implement selects in the manner that this plugin does. This plugin stores values are not normalized; it uses the database as a non-relational flat file. One of the resulting issues if that if you need to go back and change or remove one of the options, your change will not affect any values already present, ultimately corrupting the data. This could be a problem in large scale usage. PW uses a Page fieldtype to achieve the same thing (albeit with a little more effort) so as to avoid the issues mentioned above and others. But the reality is that many sites are not working at that scale or affected by the drawbacks, so I think your Select fieldtype and it's simpler administration will be a welcome solution for many. Thank you for putting it together. I know you likely produced this fieldtype with an understanding of the compromises, so just wanted to highlight the considerations for others that may researching which solution to use in a given situation. I can certainly think of situations where I will use your fieldtype. Down into the actual fieldtype's code, I have a few suggestions for you: In your getInputfield() method, these two lines are not necessary: $inputfield->attr('name', $field->name); $inputfield->class = $this->className(); On my installation, the submitted values were getting stored with the "\n" as part of them. In your getInputfield() method, I recommend trimming the value before sending it to $inputfield->addOption(), i.e. foreach($options as $option) { $option = trim($option); if(!strlen($option)) continue; $inputfield->addOption($option); } When I edited a page with this fieldtype, it already had a value selected. I recommend adding a blank option at the top of the select so that the first value isn't automatically selected (again, in your getInputfield() method): $inputfield = $this->modules->get('InputfieldSelect'); $inputfield->addOption(''); // blank or unselected option In your sanitizeValue() method, you may want to consider adding something that checks that the value is actually one of the allowed options. This is optional, but just an extra if you want it. There is some overhead in doing it, so you may decide it's worthwhile or not depending on what you want. But here's one way you could do it: <?php public function sanitizeValue(Page $page, Field $field, $value) { // remove any leading/trailing whitespace $value = trim($value); // If value isn't present in the select_options string, surrounded by CRLFs or CRs then it's not valid. // We also allow for match at beginning/end of select_options, as well as optional spaces in the select_options string // before and after the CRLF/CR that separate the options in the string. if(!preg_match('/(^|[\r\n]) *' . preg_quote($value, '/') . ' *([\r\n]+|$)/', $field->select_options)) $value = ''; return $value; }
  18. It looks like you've got it right to me. The only thing I'm wondering is if in your example, does the first snippet get executed before the one that includes the styles/scripts? It would have to in order for those styles/scripts to be populated... i.e. you'd have to render those fields and store them in an output variable before rendering the styles/scripts in your document <head>. This is the way PW always works, in that the document wrapper (final output template) gets generated last, just so that all factors (styles, scripts, ajax, etc.) are known ahead of time and can be accounted for. When it comes time to process the form, you can do this: <?php if($input->post->submit_save) { // replace 'submit_save' (above) with the name of your 'save' button field name. // tell the form to process input from the POST vars, $input->post: $form->processInput($input->post); } If you want to check if there were any errors (after calling processInput above): <?php $errors = $form->getErrors(); if(count($errors)) { // $errors is an array of strings with error messages // each Inputfield also highlights it's own errors the next time you call render() } Here's how you can retrieve the values from any given field: <?php $inputfield = $form->get('your_field_name'); $value = $inputfield->value; // or a shorter version of the same thing: $value = $form->your_field_name->value; You can also iterate the form: <?php foreach($form as $inputfield) { echo "<li>{$inputfield->name} = " . htmlentities($inputfield->value); } The only thing to note about iterating the form is that some form fields contain more fields within them. You can identify them by the type: <?php if($inputfield instanceof InputfieldWrapper) { // field contains more fields } So to process a form for a page, you might want to use a recursive function like this: <?php function populatePage($page, $inputfield) { if($inputfield instanceof InputfieldWrapper) { foreach($inputfield as $i) populatePage($page, $i); } else { // set the value to the page $page->set($inputfield->name, $inputfield->value); // optionally perform additional validation on the $inputfield->value before setting it } } $form->processInput($input->post); populatePage($page, $form); Another approach for the same thing is to iterate $input->post, which puts them in a flat (non-recursive) context. But in this strategy, you have to do more validation to make sure the submitted fields are ones you intend to populate: <?php $form->processInput($input->post); foreach($input->post as $key => $unused) { $inputfield = $form->get($key); if(!$inputfield || !$inputfield instanceof Inputfield) continue; if(!$page->fields->has($inputfield->name)) continue; // optionally perform additional validation here $page->set($inputfield->name, $inputfield->value); } You can of course retrieve your values directly from $input->post (or $_POST), but the advantage of retrieving it from the $inputfield->value is that many inputfields perform some basic validation on the values you set to them. In addition, every time you set a value to a page, it passes through the related Fieldtype's sanitize() method. That means that all values set to a page are converted to the right type for the Page. For example, a string submitted for a file in the POST vars is converted to a Pagefile object when you call $page->set($key, $value)... that in turn calls the Fieldtype's sanitize() method. This all happens behind the scenes. An important thing to note is that these Fieldtype sanitize() methods are there to sanitize for type, not security. That Fieldtype::sanitize method doesn't know if it's being provided submitted input, or something that you just set to the $page from the API, or something loaded from the DB. So the sanitize method for FieldtypePage (as an example) will ensure that whatever gets sent to it is a Page or something that it can convert to a Page (like a string or array of strings representing pages). Whereas something like FieldtypeText will accept any text you provide to it... after all, a <script> tag may be a very legitimate thing in your text field. But a <script> tag can also be a very dangerous thing in another situation. So if you are using Inputfields outside of an administrative context, or in a context where you want to enforce specific values, you may want to perform additional validation according to your need. The PW admin doesn't need additional validation, but your own forms may. Here's an example of validating a field for specific needs before setting it to a page. In this case, we know that in our field 'message' we just want plain text and no markup, and we want to limit the length to 500 characters. In our 'message' field settings, we have the "encode entities" output filter turned on, so we are not encoding entities as part of the validation. <?php $inputfield = $form->get("message"); $value = strip_tags(trim($inputfield->value)); if(strlen($value) > 500) { $value = substr($value, 0, 500); $inputfield->error("Truncated length of this field to 500 characters"); } $page->set('message', $value);
  19. ryan

    Simple gallery

    Looks great! Thanks for posting this example.
  20. Awesome thanks for putting this together (and the states module)! I can't wait to check these out when I get back to the computer.
  21. Very nice, thanks for posting! Joinery is a term I've not heard before. I'm thinking it means the same thing as carpentry where I'm located. Now that I think about it more, joinery seems like a more logical term for it than carpentry.
  22. In that case, I think you want to change the child() to children() and then loop through the result: <?php $children = $pages->get("/libri/garazha/prishtina/")->children("sort=-created, fotoballina>0"); foreach($children as $prishtina) { $image = $prishtina->fotoballina->first(); $thumb = $image->size(50, 50); echo "... your output ..."; } Note that I added: the "fotoballina>0" (aka "images>0") so that it only returns pages that have at least one image. That way you don't have to check them in the loop.
  23. $prishtina = $pages->get("/libri/garazha/prishtina/")->find("sort=-created, limit=0"); There are a couple problems with this statement above: 1. The code you posted assumes that $prishtina is a single page. And it would be if your statement didn't have the "->find(...)" in it. But the returned value in the statement above is a PageArray (something that can hold multiple pages), not a single Page. 2. The "limit=0" would prevent it from finding anything in the tree of pages below /libri/garazha/prishtina/. Are you sure you didn't mean "limit=1" ? 3. Assuming you changed that "limit=0" to be "limit=1" or something higher, using the "find()" there searches all pages in the structure below /libri/garazha/prishtina/, regardless of whether they are children, grandchildren, etc. I am thinking that you probably want that to be "children()" rather than "find()"? Or better yet, since the code below it assumes it's dealing with a single page, you probably want it to be the "child()" function, which just returns the first matching child Page. So I am thinking this is what you want instead? (below) $prishtina = $pages->get("/libri/garazha/prishtina/")->child("sort=-created"); Note that there is no need to have a "limit=n" in the selector, because child() just returns 1 page. I'm guessing that if you replaced the snippet above with the one in your code, it would achieve what you were wanting it to (if I've understood it correctly).
  24. I've posted the 2.1 development version on GitHub if anyone wants to take a look at it. The features in this version are those described above. I don't recommend building any production sites with this, because this is very much a work in progress and things may yet change. However, if anyone is interested in testing I appreciate any feedback you have. Currently you can only do a fresh install of this version, because the upgrade script is not yet ready (i.e. don't attempt to upgrade an existing PW 2.0 site with this version). http://github.com/ryancramerdesign/P21 Thanks, Ryan
  25. ryan

    www.kta-ks.com

    Thanks for posting. Looks like the site came together very well. Nice work.
×
×
  • Create New...