Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 12/04/2012 in all areas

  1. Words of encouragement from the Pub's amateur psychiatrist (Read best in a soft spoken, German accent) Ah, you are here because you have been reading things in this forum about pages that have left you confused, disorientated, befuddled. You thought you knew what a page was - you have been thumbing through them, folding them, studying them, wrapping things up in them and scrolling up and down them for most of your life! A page is this solid item - this great lump of data stuffed with things. But now you have come to Processwire, and everything you thought was true, simply isn't any more. For in Processwire pages are completely different - they are not great gulps of information, but tiny little things, nothing more than a link to the more interesting world of fields and templates; just a little blip of data in your huge fascinating database. Pages in Processwire are used for all kinds of things. They can be used as a marker in your pages list. They can be used as a group parent for other pages. They can be used as categories, tags or lists or users. And they can even be used for simple drop-down selects - just to supply a label and value. It can seem crazy to use such an iconic thing like a page for such a mundane and trivial task! But don't worry and fret, don't lose sleep or pace the floor as you think the reputation of the noble page is being crushed! In Processwire, they are fulfilling their duty to the full - and the more slight the task, the more they bound up to the wall and jump up and down shouting "use me, use me!" And, as a bonus, they can even be used for content - for all that stuff you thought was a page, but in Processwire isn't! So, don't be put off by the Processwire page - use it for everything! it is much smaller than you think, takes up hardly any space at all, is well behaved and only will do its thing when it is called. In fact it is hardly a page at all .... and then again, it is also so much more!! Better now? Joss Freud
    5 points
  2. I was in doubt if I should post this one because it's not completely ready. The site is for a thesis project of a friend. In short, he collects raw videos filmed by the random citizen in the Historical Center of Oporto to further analyze and contextualize them. The videos are being submitted on the site, uploaded to youtube and shown on a big random wall. The project will be rethought, and might change a lot in the future. I guess that's why I decided to post it now... PW has a big part on this since every video that is uploaded to youtube is kept as a PW page and connected to a filmmaker (also pages). This allows me to collect all the videos quickly without the limitations that are imposed by requesting with the youtube API, and allows me to keep much more information for each video than YouTube Would. It was also very easy to create a script to import some videos that were already on youtube to the system. PW rocks http://museudoresgate.org/
    5 points
  3. Hi, Here is another processwire powered site, completed recently. http://www.kapelis.com/ the way this is setup would not have been possible without processwire, which has made it easy and accessible for the client to update all aspects of the site without having to code or use a wysiwyg... -marc
    2 points
  4. Since everyone (mostly) are nattering on about responsive templates of one sort or another, though you all might be interested in this little article that I found giving a rough sum up of a pile of responsive frameworks. Not all, by any means, but not a bad little collection: http://designshack.net/articles/css/which-is-right-for-me-22-responsive-css-frameworks-and-boilerplates-explained/ Joss
    2 points
  5. We still have the Wiki ready to go (http://wiki.processwire.com/). It's just looking for someone to show it some love. I've closed off public post access because of all the spammers, but happy to add any new accounts for anyone that wants to work on this -- let me know?
    2 points
  6. As of the latest commit to the dev branch, you no longer have to do this. You can now replace the above bit of code with this: $forPage = $product->getForPage(); That returns the page that the repeater item is for. I also added this, should anyone ever need it: $forField = $product->getForField();
    2 points
  7. Okay I think I've got this fixed. Would you mind trying it out and seeing if it also fixes it on your end too? It is on the latest commit to the dev branch. https://github.com/ryancramerdesign/ProcessWire/commit/7dab612baa55b7ca02a7d6679e70d1aef4298f8b It involved a re-thinking of what a repeater Page is. I've changed it so that repeater pages are now represented by their own class (RepeaterPage rather than Page). The new RepeaterPage is the same thing as a Page, but overrides the method used to determine when files should be secured or not (which it now delegates to its owning 'for' page) . It also adds 2 methods (see below) so that people don't have to do stuff like this to figure out what Page or Field a given repeater item belongs to. For those interested, these methods now appear on all repeater item pages: $page->getForPage(); // returns the owning Page this repeater item is for $page->getForField(); // returns the Field this repeater item belongs to
    2 points
  8. You could do it easily adding classes with PHP, but CSS allows you to do this: tr:nth-child(odd){ background-color: red; } tr:nth-child(even){ background-color: blue; } edit: Joss was faster and mine would be a damn ugly table...
    2 points
  9. @bcartier: The ImportPagesCSV-module can't do this as is. But I tried making a tiny addition to make it support FieldtypePage (those used to make page references) and it worked amazingly well. The only change needed was to add 'FieldtypePage' to $fieldtypes array (just before init() function if you take a look at the module file), like this: protected $fieldtypes = array( 'FieldtypePageTitle', 'FieldtypeText', 'FieldtypeTextarea', 'FieldtypeInteger', 'FieldtypeFloat', 'FieldtypeEmail', 'FieldtypeURL', 'FieldtypeCheckbox', 'FieldtypeFile', 'FieldtypePage', // add this line ); After that addition it's possible to choose a Page field when connecting the fields from the CSV to the ones in the chosen template. I had pre-populated categories at the target site and used their id's in the CSV file to reference those categories. Multiple categories worked like a charm as well, just use a pipe in between id's (123|456|789). Moreover, if you've got only one category per entry to reference, then you don't even need the id's of the categories - you can use paths as well. Here's a little example: cat.csv: title one two three four entries.csv: title,categories a,/cats/four/ b,/cats/three/ c,/cats/one/ d,/cats/two/ Import cat.csv using a template for categories with (at least) title field, under a page at /cats/. Then import entries.csv using a template for entries, having a title field and a page field. This should leave you with entries that are connected to categories. I hope this gets you somewhere. @ryan: Looks like page references could be supported very easily. I just used this successfully to import ~3500 pages with category references from an old site to a new PW one. But maybe there's still something else to be done before they're fully supported?
    2 points
  10. Antti, yes, it's certainly possible. You can hook whenever and wherever you like. It's just a matter of being certain your hook has been registered before the event you're aiming for takes place. So you only need an autoload module to hook something you don't have full control yourself, and don't want to or are not able to require some initialization being called before using the hook. Here goes. And this one I tested a little so I know it works, for me at least . function myCustomAuthentication($event) { $user = $event->arguments[0]; $pass = $event->arguments[1]; // TODO: do whatever check is needed to authenticate $user // $pass has whatever you like, a token of some kind probably // must set replace-flag to prevent the original Session::authenticate() being called $event->replace = true; // return value is boolean // true: successfully authenticated // false: authentication failed $event->return = true; } // ...aquire a user name, somewhere, somehow... // hook *before* Session::authenticate() to override it // second argument is null because we're using a plain function and not a method inside an object $session->addHookBefore('authenticate', null, 'myCustomAuthentication'); // log in the user, passing whatever needed by myCustomAuthentication() as a password - if anything $user = $session->login("some-username", "some-token-with-a-meaning-in-this-very-context"); I'll actually be using this piece of code myself as well, this week I hope.
    2 points
  11. Not to completely hijack this thread, but... you may wish to read this: http://www.cmscritic.com/critics-choice-for-best-free-cms-goes-to/ I've sent Ryan the badge below for ProcessWire.com if you feel like showing it off
    2 points
  12. (can someone check this for accuracy and let me know any changes I should make - I am very new to PW) I wanted to create a simple select drop-down, but there isn't one in Processwire by default. Reading the forums, it became clear this was for very good reasons and that you are expected to use pages. But I couldn't find clear instructions how to do this. So, having worked out how, I wrote myself this little tutorial: ##################### Processwire by default does not have a simple select field. The reason, given by Ryan the developer, is that a plain simple select field is not related to the database, so you have, in effect, some of your data in a flat file - sort of - which is not good practice. The preferred way of doing select fields is by using pages for the values and the Pages field to list them in your form. If you just want a simple on/off state, use a checkbox, but if you need more options, then this is the way. Creating Pages for Select Fields These pages must be published to be accessible by the pages field but are obviously not wanted on the website. First, create a dummy template (a tempalte with no associated file) called something like "selects." Give it two fields - the normal title field and a text field called select_value Create a Top Level (child of Home) page called Selects (or something friendly). Give it a blank dummy template. Make this page hidden. Create a child page and call it something that will work for this group of selects - perhaps call it "Yes No." Again, give it a blank dummy template Create three children under this - one called Yes, another called Maybe and a third called No. Use the dummy template "selects" that you created earlier. In the select_value field put a value - in this case the most obvious would be yes, maybe and no! For ease of use, make sure these values have no spaces or anything clever. These three pages will be your select values. How to add to a template Create a field using the Pages field type. Call it something like "yes_no_maybe_selector" Under the details tab, select the single/null option. Under the Input tab, select the Yes No page as the parent. This will then display your three children in the select. Choose either Radio or Select. Add this new field to your template in the usual way. How to output into your template.php The field is called in the usual way, remembering to call the particular value from the select page. So: $page->yes_no_maybe_selector->select_value; The chances are that you are going to use this as a condition - if "yes" do something, if maybe do something else and if "no" do something else again. IMPORTANT: You cannot set a default state - so you will always have another option - Null. So, if you are only wanting to do a two way selection, then you are probably better off just doing a simple checkbox. In this example, therefore, we will effecting have Yes, Maybe, No and Null - that is no, twice! Never mind.... First, lets add it to a variable: $yesno = $page->yes_no_maybe_selector->select_value; Now, we can set up our variations. if ($yesno === "yes") { echo "Say Yes"; } elseif ($yesno === "maybe") { echo "Shrug your shoulders"; } elseif ($yesno === "no") { echo "not a chance"; } else { echo "nada"; } Note that the last option is effectively the null option, so something is being outputted, even if it just some blank space which saves potential errors or silly bits of markup (well, in my case that seems to happen!) And that is it! Joss
    1 point
  13. @digitex: You may have a logical problem, if I've understood you right. Let's say you've got rental periods like this: A1: 2013-01-05 ... 2013-01-12, cottage A, booked: 0 B1: 2013-01-05 ... 2013-01-12, cottage B, booked: 0 A2: 2013-01-12 ... 2013-01-19, cottage A, booked: 1 B2: 2013-01-12 ... 2013-01-19, cottage B, booked: 0 Now if you're targeting the repeater items with a selector like this (left some details out) "date_from>=$fromvalue, date_to<=$tovalue, booked=0" to find out which cottages are available from 2013-01-05 to 2013-01-19, you'd get items A1, B1 and B2. Then, using the method Ryan described in post #14 of this thread, it's easy to find those items correspond to cottages A and B. But this does not mean both of those cottages are available for the whole period, just like you're saying. So, if the rental period (say 14 days) spans over more than one rental term (7 days), it's necessary to repeat the procedure for each of the terms individually and only then you're able to see which cottages appear in the results for all the terms. Combining all of the above, I'm suggesting something like this (haven't tested so no guarantees): // rental terms of the desired period in a handy structure $termsInPeriod = array( array( 'start' => '2013-01-05', 'end' => '2013-01-12' ), array( 'start' => '2013-01-12', 'end' => '2013-01-19' ) ); $cottages = array(); foreach($termsInPeriod as $term) { // find matches for this term $matches = $pages->find("template=repeater_rental_period, date_from>={$term['start']}, date_to<={$term['end']}, booked=0, include=all"); // no matches this round --> no matches on the whole, no matter what may have been found before if(!count($matches)) { $cottages = array(); break; } // array to hold found cottages for this term $termCottages = array(); foreach($matches as $item) { $property = $pages->get((int) substr($item->parent->name, 9)); if(!$property->viewable()) continue; // skip if property is unpublished or something // now you have the $property and the matching repeater $item $termCottages[] = $item->id; } // some results from previous rounds? // - yes, then find out common matches // - nope, first round --> use the results as is if(count($cottages)) $cottages = array_intersect($cottages, $termCottages); else $cottages = $termCottages; } In the end $cottages should hold all the cottages available for the given period. I'm using $item->id because of the array_intersect(). I'll leave it to you to form a structure like $termsInPeriod from your input - and probably this is fuel for your own thinking at most anyway.
    1 point
  14. Can I just say that if ever there is a book written about how to use ProcessWire, this should be the foreword - it's perfect! I did fail to read it in the suggested accent and read it in an Austrian Arnold Schwarzenegger accent - it was amazing (like this - http://bit.ly/c7op9H ).
    1 point
  15. Textile has support for much broader spectrum of features, like classes, ids, tables, and that is a huge plus. Markdown has support. And that's even bigger plus, e.g. there is probably around 200x more text editors for MD, rather than Tx. All the cool kids play with MD
    1 point
  16. Markdown reads a bit friendlier to me. I think that's part of the reason why it's more popular. Textile uses h1. Headline for an h1, and Markdown uses # Headline #. With that said, I prefer using Textile because of its features and it hasn't fragmented the way the Markdown offerings have.
    1 point
  17. Thanks guys, I've actually been working a lot on textile recently and want to push some big changes to the code soon. In the meantime Antti, if you do go down the textile route, then you probably want to use the TextileRestricted() method as this is meant to deal with input that's coming from untrusted sources such as public comments. The normal mode in textile supports a far wider range of features.
    1 point
  18. Ryan - I'm poking through the core PW code and I have discovered, as yu no doubt are aware, that Repeater fields are not cloneable and the clone() methods simply throws and exception with relevant message. I have been testing the pages->clone() method today as part of trying to develop this page draft module and when I copy a page within the page listing interface in the CMS, I can successfully copy a page, alter values in repeater fields in the new page copy, and save them without altering the original page. So, as far as I can tell, it is possible to clone repeater fields! I am also trying to work out what my options are for trying to overwrite existing repeater fields, as mentioned above in this thread, for the purpose of overwriting a page with values from a draft/duplicate. Do you have any more insight into how I might be able to achieve this or if there is any development on the roadmap that will help? I was also wondering if there is any documentation on the way repeaters work under the hood at the DB level? Or if not, could you try to lay it out for me so I can have a crack at writing some code to clone them? I've been examining the DB but I'm just getting a headache! A nutshell explanation would be really helpful! I'm determined to get this thing working!
    1 point
  19. I'm not sure I understand the 1 week vs 2 week thing. It really shouldn't matter whether the range is days, weeks, months, years, etc. There are no 1-week dependencies that I can see in any of the code in this thread so far. You should be able to provide any date range in your selector. $fromvalue ="2013-07-13"; $tovalue = "2013-07-27"; or $fromvalue = strtotime("2013-07-13"); $tovalue = strtotime("+2 weeks", $fromvalue); Also wanted to add, I have a better solution for this now (on the latest commit to the dev branch). You can now do this, where $item is a repeater item: $property = $item->getForPage();
    1 point
  20. They didn't used to entity encode those feeds, but looks like now they are. I think I've got this fixed. Please give it a try and let me know how it works for you: https://github.com/ryancramerdesign/MarkupTwitterFeed
    1 point
  21. I started by using Markdown but switched to Textile after using it. I think it just feels less criptic and friendlier to editors. One main thing that I prefer in Textile over Markdown is how they handle new lines. markitUp works with both. And you can even use it's examples to checkout the differences http://markitup.jaysalvat.com/examples/textile/ http://markitup.jaysalvat.com/examples/markdown/ Indeed, having netcarver here is a big plus in favor of Textile
    1 point
  22. antknight, Check out this thread. It has some nice alternatives and you might pick something up along the way.
    1 point
  23. It's a style thing. If you can do it with CSS, do it with CSS It won't work on old browsers, but this is where I think the degradation is perfectly acceptable.
    1 point
  24. With CSS 3: tr:nth-child(odd) { background-color:#ccc; } tr:nth-child(even) { background-color:#666; } Or, there is this JQuery to add classes (I haven't tried this, just stole it....) <script type="text/javascript"> $(document).ready(function() { $(".myTable tr:even").addClass("bgOne"); $(".content div:odd").addClass("bgTwo"); }); </script> Joss
    1 point
  25. Maybe we should have a forum for this kind of doubts. PUB is too general...
    1 point
  26. That's a bug (glad we have a dev branch). I'll work on this.
    1 point
  27. Hey Joss, Wow, great work! You're doing this after such a short time with processWire, there's no telling where you are headed next! Seeing this is really inspiring. Thanks for your work. Matthew
    1 point
  28. Nice, the black with the yellow never gets old. I do love the simplicity of the homepage though.
    1 point
  29. Thanks for writing this. There's many ways and different use cases, and that's hard to write everything down to not get to lengthy. Some thoughts. This technique with pages and a page field or without can be used for various things. You can build such helper pages to build a select on the frontend only and render the options in a foreach. Doing it this way you benefit from being able to add and move options in the admin page tree, and allow the editor to even edit or add to them. And all stays in the same manner as all other pages. I can tell my clients "well there's the pages that make these options and they know how already". Being everything accessible through API so easy, would make it easy to even build some little tool in the backend to manage those options outside the page tree, to go even further. Also those page references are great for doing categories, tagging, ingredients, .. whatever. And later those pages can be even used in your site and make them accessible and give them a template. There you then show all pages that have a reference to the currently viewed category. I've for example created a whole shop where the navigation are the categories and articles are just linked to some category and display when browsing the category tree. And best of all it stays flexible and can be adapted or expanded in no time with no limits. One could write down a whole book solely on this subject as it's endless, but guess nobody would buy it. Creativity is what I enjoy in PW. One thing about selects having a empty option. This is simply to be able to "deselect" it in the admin. So if you use page field select for doing something on the frontend you would use usually check for if not empty to determine if anything at all. So your example could aswell be like this: if ($yesno) { // anything selected? if($yesno == "yes") { echo "Say Yes"; } elseif ($yesno == "maybe") { echo "Shrug your shoulders"; } elseif ($yesno == "no") { echo "not a chance"; } } No need to write === here, == is sufficient I think when comparing strings. You do triple === usually on comparing objects.
    1 point
  30. OK. What do we wait for? I could help with if we could create a basic but structured roadmap with distribution of tasks for that project.
    1 point
  31. Love it. The clouds don't move for me in Chrome or Firefox on Windows 8 but they do in Safari... albeit very jerkily! Whether they move or not isn't essential though since it's the content that counts and the focus should be on the screenshots. I'm seeing both sides of the argument (not that anyone's arguing) on having the Futura admin theme on the homepage - I wonder if these three areas should be some sort of slideshow instead? Perhaps the middle panel could start out as the default admin theme - if only to match the colours - and then fade through? It's a tricky one as all the shots show off the admin or a module, but I wonder if somehow it needs to show what you can create with ProcessWire too as Tripsite and VillaRental from your site sold me on ProcessWire ryan!
    1 point
  32. You don't even need the two fields on the template. Since you only need one information, the title is enough. So, in your example, the title would hold the the "yes", "maybe" and "no" values. To output do this instead: $page->yes_no_maybe_selector->title; or $page->yes_no_maybe_selector->name;
    1 point
  33. Thanks for the help. I've done some testing and fiddling and it looks like, for best performance, I can execute an SQL query directly against wire('db') to check modification date of a page row and then either utilise the cache or fetch entire fresh page from DB. It gave a performance gain of around 20%, nothing earth-shattering. I won't know for sure until it's tested in a live environment with real traffic (rather than on my local machine), but good to know what's possible.
    1 point
  34. Strange I'm getting this as my img url: http://localhost/pw_template/admin/repeaters/for-field-134/for-page-1/1354274914-77-1/-/full_hands.jpg which is showing when logged in but giving me a 404 when not If I output normally with url, I still get blank images
    1 point
  35. This forum slowly turns into a stackoverflow. Works fine like this in all browsers: http://jsfiddle.net/mYYYF/2/
    1 point
  36. Yep, there is a problem when gmap is hidden. This happened to me on the frontend of a site where I was using jqueryUi to show the map, and apparently it's very common with lightboxes. The problem is that, if you are using display: none; to hide the map, it first gets rendered with 0px size, and this is what jquery's .hide() uses. The solution would be to use visibility:hidden; or position it absolutely outside of the screen instead. But I think this wouldn't be easy to do in the admin, since it's using jqueryUi...
    1 point
  37. Ok. Updated it. I used some code of another module of mine for the table. And in this I had a delete function
    1 point
  38. ps. got a bit impressed when I realized I could use dot syntax to display connected fields: title, category.title
    1 point
  39. Here's a great example of mobile first http://aneventapart.com/ I love how it looks in my android phone!
    1 point
  40. I am really pleased I have solved this particular problem! Flexslider has an out-of-the-box issue when using the "fade" animation type. Basically, there is a huge gap before the animation gets going - once it starts it is fine. I have searched long and hard to find a solution and keep coming up with people saying you should set .slides li { display:none} That does work - unless you have captions, in which case they vanish completely. After staring at the live code with Chrome dev tools I noticed the problem is that if the show starts on the first slide, it has to wait for all the hidden slides to go through their animations before the loop comes back to the beginning and the visual show starts - hence the gap. However, if in the initialisation script for flexslider set startAt: to the last slide (if you have 4 slides, set it at 3), then the next slide is the first slide and the problem is solved - PROPERLY. Whoopee! Now, relating that to processwire, and in this case a repeat field with images and captions, all you need to is set up a count. $totalslides = -1; foreach($page->slideshow_repeater as $slideshowrepeater) { $totalslides++ ..... and then insert the result into the initialisation script at the bottom of your template, for instance: <script type="text/javascript" charset="utf-8"> $(document).ready(function() { $('.homeflexslider').flexslider({ selector: ".homeslides > div", initDelay: 0, startAt: <?php echo $totalslides; ?>, animation: "fade" }); }); </script> And there you have it - it all works properly. And I am going to brew a nice hot jug of good Italian coffee to celebrate! Joss
    1 point
  41. Glad you found it. Eventually I think we'll have to add a 'default sort' option for repeaters, so that they automatically sort by some field present in the repeater.
    1 point
  42. And now you should add it here: http://modules.processwire.com/
    1 point
  43. Today I started to develop the project. And what should I say? The API is so fantastic, I love it! As first I startet the development of the user management. At the moment I have a page called "profile". Here is a if-statement, which check the session status. If TRUE you will be able to see content. (Nothing generated yet) If false you can choose between login and registration. In the registration process you can insert username, email and password. The user will be added with the status inactive. A mail with a confirmation code will be sent to the email-address and after the confirmation the user is active. Everything worked finde at the moment and I think in a few weeks I will post this community project to the showcase. Many thanks for your input at the beginning, it was a good start in understanding for me. z.
    1 point
  44. Hah! http://www.google.com/trends/explore#q=processwire%2C%20%22Nico%20Knoll%22%2C%20Adam%20Kiss&cmpt=q I bet it's something else, though.
    1 point
  45. I never expected we could complete on votes with projects like Concrete5 and the others that were above us. In terms of size/age, I thought it very likely that we would come in last, despite [iMO] being the best product with the best community. So I'm very proud of how well we did, with somewhere between 110-120 "thumbs up" votes in both categories, last I checked. Despite being the smallest (in project age and user base), we were far from last in votes. I think we did great. The fact that we came in above both ModX and ExpressionEngine in "Best Budget CMS" was also great I thought. Though all it would have taken is 1 tweet from EE or ModX, and I'm sure they would have gone to 1st, based purely on size of user base... so I'm glad they never participated in that way. Thanks to everyone who voted! Also want to thank Mike for running this. I feel like we've gained a lot of new users here since these awards started, and I think that may have something to do with the exposure ProcessWire has been getting there.
    1 point
  46. I think it is totally appropriate to have some conventions and not configuration for everything. Trailing slashes are - in my opinion - a good convention.
    1 point
  47. Just to give more explanation behind it, the "a" class is meant to count from the beginning, so "a1" means first item, "a2" means second item, etc., while the "z" class is meant to count from the end so "z1" means last item, "z2" means second to last, etc. It could just as easily be called "from-the-beginning-1" or "from-the-end-1", but just wanted to make sure my extra short class names weren't causing confusion. The purpose of going this route was to keep comparisons to numbers rather than objects. We know that the first item is always going to have a key of 0, and the last item is going to have the key of $page->numChildren-1. So we don't really need to call first() or last(), though there's no harm in it either. The only other real difference here from earlier examples is just that I'm not using the getItemKey() method. It's not necessary to do that because anytime you call foreach() in PHP you have the option of including the $key in the foreach. As a result, this: foreach($page->children as $key => $item) { is the same as this: foreach($page->children as $item) { $key = $item->getItemKey();
    1 point
  48. Using getItemKey isn't really necessary since you can already get that from the foreach(). I think that CSS is where all this really belongs, but if needed to support legacy browsers without using the ":" options in CSS, here's another approach... I'm writing in the browser without testing, so forgive me if this needs adjustment. foreach($page->children as $key => $item) { $class = 'a' . ($key+1) . ' z' . ($page->numChildren - $key) . ' ' . ($key % 2 ? 'odd' : 'even'); echo "<li class='$class'>{$item->title}</li>"; } From there, you should be able to target any item directly with CSS: li.a1 { /* first item */ } li.z1 { /* last item */ } li.a2 { /* second item */ } li.z3 { /* 3rd from last item, etc... */ } li.even, li.odd { /* self explanatory */ } The "a" class is counting from the beginning while the "z" class is counting from the end. You could substitute any class name, but I'm just using "a" to refer to "from the beginning" and "z" to refer to "from the end" (US alphabet).
    1 point
×
×
  • Create New...