December 23, 2009 New Testing IE 7 AND IE 8 with IETester
Recently I’ve been having some problems with IE. They are usually with CSS layouts and javascript which work in IE 8 and not in IE 7.
I found a nice application which runs the IE rendering and javascript engines from IE 5.5, 6, 7 and 8. It crashes a lot, but it allows me to easily view the same pages across multiple versions of IE from the same computer.
The link is:
http://my-debugbar.com/wiki/IETester/HomePage
You’ll also need to download their “debug bar” in order to view the source from within the application. I thought it was a bit odd that this was separated from the main application.
Happy debugging!
- Leave a comment
- Posted under CSS, Design, Javascript
November 29, 2009 Symfony prints your titles twice
I recently received a request from an SEO / online marketing company to remove the extra title from one of our sites. Symfony was displaying the title information for every page twice; once in the title tag, and again in as a meta tag with name=”title”.
Google was not displaying the title they had given the index page, and there was a concern that Google considered the title duplication to be spam.
Both the title tag and the meta tags are generated from the helper functions include_title and include_metas, respectively. I could find no easy, configuration-based way to tell include_metas to not include the title meta, so I decided replace the helper with the following:
<?php foreach ( sfContext::getInstance()->getResponse()->getMetas() as $name => $content ): if ( $name != 'title' ): echo tag('meta', array('name' => $name, 'content' => $content))."\n"; endif; endforeach; ?>
About a month later there is no change in how Google displays the site’s index page. Perhaps it will take longer to find out if this will change it. Or if it won’t change anything.
Have you had any trouble with Google not displaying your page titles in their results? Do you know if displaying the same title twice, both in the title tag and in the meta title tag, is affecting things?
Please do comment.
- Leave a comment
- Posted under SEO, Symfony 1.0
September 30, 2009 Giving your symfony apps better urls
I love pretty URLs. I don’t like file name extensions unless they end with .html.
All versions of symfony save me from address bar ugliness, but only for the first application I create. Everything else is ass.php/backwards.
I’m going to give you two ways of beautifying your second, third, and forth applications so you can http://www.get.your/application/looking/sexy. Both methods involve creating a subdirectory.
Put it all in a subdirectory, and symlink your way out
So if you have an app named “admin,” you can do the following:
- Create a directory in your web folder called admin
- Copy your admin.php and admin_dev.php into this folder
- Copy the .htaccess file into this folder
- Rename admin.php to index.php
- Edit the file now named index.php, and change the require_once statement at the top so it’s an extra directory back
- Symbolically link the js, css, images, and uploads directories from within the admin directory to the directory above; so from your app directory:
ln -s ../js
This is what I usually do in a Linux environment. However, I’m developing on a Windows box, and symbolic links don’t translate easily between Windows and Linux. So here’s the second option, without using symbolic links:
Put just the .htaccess in a subdirectory
- As before, create a subdirectory in your web folder for the admin.
- Copy the .htaccess file in, and at the very bottom of the file, change index.php to ../admin.php. It should read
RewriteRule ^(.*)$ ../admin.php [QSA,L]
- Edit your routing.yml file for the admin app, and append /admin/ to all of your routes.
That should about cover it. Also remember to set no_script_name to on in each of your applications.
Enjoy.
Alternative methods? Please leave comments.
- 4 comments
- Posted under Setup, Symfony 1.0, Symfony 1.2
September 29, 2009 Disabling the submit button with jQuery to prevent extra submissions
It happens.
Not often, but once in a while a user submits the same thing. Twice, maybe three times, and each just a second or two apart.
It’s easy to understand why. They click the submit button and then … nothing happens. If it doesn’t work right away, we assume it’s not working. The solution? Hit the button again! Hit it five times until finally, it seems to work.
There are two things you need to do to stop this from happening.
- Assure the user they successfully clicked the button, and they only have to wait a moment for the form to be processed.
- Stop the user from hitting the button again.
I’m a jQuery newbie, but it wasn’t long before I was in love with this javascript library. Here is my solution:
- Download the jQuery library if you haven’t already, and drop it in your js directory if you’re using Symfony.
- Reference the jquery library in your template, like so:
<?php use_javascript('jquery') ?>
- And finally, here’s the source code:
<script type="text/javascript"> $(document).ready(function(){ $("input[type='submit']").attr("disabled", false); $("form").submit(function(){ $("input[type='submit']").attr("disabled", true).val("Please wait..."); return true; }) }) </script>
I love the simplicity.
Once the document is ready, make sure every submit button on the page is enabled. In Firefox 3.5, if I click the back button to go back to the form after submitting, the submit button stays disabled. I’m not sure why; the value attribute is correct, but it stays disabled, even if you refresh.
In the next few statements, I add an onsubmit event to the form. If you have javascript validation on your form, you’ll have to execute your validation from within this function.
The function finds all submit buttons within the form, disables them, and changes their value to “Please wait…”. All of this is done dynamically, without altering the form directly in my html. Love it.
Does anyone have any server-side solutions to this problem? If you do I’d love to hear about them.
- 4 comments
- Posted under jQuery
September 20, 2009 Country codes in YAML format
Here are all the country codes in YAML format for your downloading pleasure! You may place them in your app.yml (for symfony users) and call the list with sfConfig::get(‘app_countries’).
all: .lists: countries: CA: Canada US: United States of America AD: Andorra AE: United Arab Emirates AF: Afghanistan AG: Antigua & Barbuda AI: Anguilla AL: Albania AM: Armenia AN: Netherlands Antilles AO: Angola AQ: Antarctica AR: Argentina AS: American Samoa AT: Austria AU: Australia AW: Aruba AZ: Azerbaijan BA: Bosnia and Herzegovina BB: Barbados BD: Bangladesh BE: Belgium BF: Burkina Faso BG: Bulgaria BH: Bahrain BI: Burundi BJ: Benin BM: Bermuda BN: Brunei Darussalam BO: Bolivia BR: Brazil BS: Bahama BT: Bhutan BU: Burma (no longer exists) BV: Bouvet Island BW: Botswana BY: Belarus BZ: Belize CC: Cocos (Keeling) Islands CF: Central African Republic CG: Congo CH: Switzerland CI: Côte D'ivoire (Ivory Coast) CK: Cook Iislands CL: Chile CM: Cameroon CN: China CO: Colombia CR: Costa Rica CS: Czechoslovakia (no longer exists) CU: Cuba CV: Cape Verde CX: Christmas Island CY: Cyprus CZ: Czech Republic DD: German Democratic Republic (no longer exists) DE: Germany DJ: Djibouti DK: Denmark DM: Dominica DO: Dominican Republic DZ: Algeria EC: Ecuador EE: Estonia EG: Egypt EH: Western Sahara ER: Eritrea ES: Spain ET: Ethiopia FI: Finland FJ: Fiji FK: Falkland Islands (Malvinas) FM: Micronesia FO: Faroe Islands FR: France FX: France, Metropolitan GA: Gabon GB: United Kingdom (Great Britain) GD: Grenada GE: Georgia GF: French Guiana GH: Ghana GI: Gibraltar GL: Greenland GM: Gambia GN: Guinea GP: Guadeloupe GQ: Equatorial Guinea GR: Greece GS: South Georgia and the South Sandwich Islands GT: Guatemala GU: Guam GW: Guinea-Bissau GY: Guyana HK: Hong Kong HM: Heard & McDonald Islands HN: Honduras HR: Croatia HT: Haiti HU: Hungary ID: Indonesia IE: Ireland IL: Israel IN: India IO: British Indian Ocean Territory IQ: Iraq IR: Islamic Republic of Iran IS: Iceland IT: Italy JM: Jamaica JO: Jordan JP: Japan KE: Kenya KG: Kyrgyzstan KH: Cambodia KI: Kiribati KM: Comoros KN: St. Kitts and Nevis KP: Korea, Democratic People's Republic of KR: Korea, Republic of KW: Kuwait KY: Cayman Islands KZ: Kazakhstan LA: Lao People's Democratic Republic LB: Lebanon LC: Saint Lucia LI: Liechtenstein LK: Sri Lanka LR: Liberia LS: Lesotho LT: Lithuania LU: Luxembourg LV: Latvia LY: Libyan Arab Jamahiriya MA: Morocco MC: Monaco MD: Moldova, Republic of MG: Madagascar MH: Marshall Islands ML: Mali MN: Mongolia MM: Myanmar MO: Macau MP: Northern Mariana Islands MQ: Martinique MR: Mauritania MS: Monserrat MT: Malta MU: Mauritius MV: Maldives MW: Malawi MX: Mexico MY: Malaysia MZ: Mozambique NA: Namibia NC: New Caledonia NE: Niger NF: Norfolk Island NG: Nigeria NI: Nicaragua NL: Netherlands "NO": Norway NP: Nepal NR: Nauru NT: Neutral Zone (no longer exists) NU: Niue NZ: New Zealand OM: Oman PA: Panama PE: Peru PF: French Polynesia PG: Papua New Guinea PH: Philippines PK: Pakistan PL: Poland PM: St. Pierre & Miquelon PN: Pitcairn PR: Puerto Rico PT: Portugal PW: Palau PY: Paraguay QA: Qatar RE: Réunion RO: Romania RU: Russian Federation RW: Rwanda SA: Saudi Arabia SB: Solomon Islands SC: Seychelles SD: Sudan SE: Sweden SG: Singapore SH: St. Helena SI: Slovenia SJ: Svalbard & Jan Mayen Islands SK: Slovakia SL: Sierra Leone SM: San Marino SN: Senegal SO: Somalia SR: Suriname ST: Sao Tome & Principe SU: Union of Soviet Socialist Republics (no longer exists) SV: El Salvador SY: Syrian Arab Republic SZ: Swaziland TC: Turks & Caicos Islands TD: Chad TF: French Southern Territories TG: Togo TH: Thailand TJ: Tajikistan TK: Tokelau TM: Turkmenistan TN: Tunisia TO: Tonga TP: East Timor TR: Turkey TT: Trinidad & Tobago TV: Tuvalu TW: Taiwan, Province of China TZ: Tanzania, United Republic of UA: Ukraine UG: Uganda UM: United States Minor Outlying Islands UY: Uruguay UZ: Uzbekistan VA: Vatican City State (Holy See) VC: St. Vincent & the Grenadines VE: Venezuela VG: British Virgin Islands VI: United States Virgin Islands VN: Viet Nam VU: Vanuatu WF: Wallis & Futuna Islands WS: Samoa YD: Democratic Yemen (no longer exists) YE: Yemen YT: Mayotte YU: Yugoslavia ZA: South Africa ZM: Zambia ZR: Zaire ZW: Zimbabwe ZZ: Unknown or unspecified country
Tags: country codes, symfony, YAML
- 4 comments
- Posted under Symfony 1.2, YAML
August 17, 2009 Securing multi-user CRUD operations through the myUser class in Symfony
Symfony has automated the process of creating a CRUD application, and even has built-in CSRF protection.
However, for a site with multiple users, the generated CRUD code just won’t do.
In my schema, each row of a model is owned by a user, according to their user_id. In the dashboard application I am building, each user has a list of programs.
They can create, read, update and delete each program that they own.
But the generated actions would allow users access to other user’s programs, simply by changing the id in URLs such as /program/edit/id/1
.
My solution? Store the user_id of the currently logged in user in myUser.class.php. Since the myUser class has ownership of the id of the currently logged in user, I thought it was appropriate to implement convenience getters and setters for the dashboard here.
So my list action would contain:
$this->programs = $this->getUser()->getPrograms();
The myUser::getPrograms()
method is implemented like so:
public function getPrograms() { return Doctrine::getTable('Program')->fetchByOwner($this->getId()); } public function getProgram($pk) { $program = Doctrine::getTable('Program')->find($pk); return $this->hasOwnershipOf($program) ? $program : false; } public function hasOwnershipOf($row) { if ( isset($row['user_id']) ) { if ( $row['user_id'] == $this->getId() ) { return true; } } return false; }
Adding the myUser::fetchByOwner()
method leaves the model functionality open to being called from another administrative application other than the dashboard. You could use a Doctrine magic method here, however I would prefer to return programs in alphabetical order (or perhaps allow for other options to be passed in using a Doctrine_Query as a second parameter).
For myUser::getProgram($pk)
, I simply use the ::find()
magic method in doctrine to retrieve the object by primary key, as usual, and then return it only if it matches the user_id of the logged-in user.
Writing for multiple users is common place for web-based applications. However, it’s something I don’t usually deal with at work where I write simple, single-user back ends for editing website content.
Is there a more elegant way of handling user ownership of database content? Perhaps by writing a doctrine behavior? How do you usually go about it?
Please comment.
- 3 comments
- Posted under Doctrine, PHP, Symfony 1.2
August 12, 2009 Create a dynamic CSS module in Symfony 1.2 fast
I’m sure there are a number of ways you can handle this. There are also tons of modifications you can do to make it more useful, or better adapted to your particular needs. This post will quickly illustrate one way to create a dynamic stylesheet in Symfony 1.2.
I just finished writing a module that allows my users to change their background color. So I thought I’d share a similar way of doing this with an example.
Create a new doctrine model. Since we’re keeping things simple, our style sheet will only have one editable element: the body tag’s background-image.
DynamicCSS: columns: identifier: string(255) background_color: string(255)
Add a fixture and load it up:
/data/fixtures/fixtures.yml
DynamicCSS: black: identifier: "main" background_color: "#000000"
$ symfony doctrine:build-all-reload
The identifier will be the stylesheet’s name. You’ll see it in the route, which you’ll add now:
/apps/frontend/config/routing.yml
dynamiccss: url: /dynamiccss/:identifier.css param: { module: dynamiccss, action: render }
Create a new module:
$ symfony init-module frontend dynamiccss
This module will be used to render a dynamic style sheet. You’ll want to make sure it doesn’t render the layout, and that it returns a content type of ‘text/css’. So edit the actions file and add a preExecute() method. Of course, you’ll also need a method to fetch the style sheet and render it.
/apps/frontend/modules/dynamiccss/actions/actions.class.php
... public function preExecute() { $this->setLayout(false); $this->getResponse()->setContentType('text/css'); } public function executeRender() { $this->stylesheet = Doctrine::getTable('DynamicCSS') ->getByIdentifier($request->getParameter('identifier')); $this->forward404Unless($this->stylesheet); } ...
Next we’ll add the template, which is at this point is very simplistic. You’ll need to have that ::getByIdentifier() method implemented in the DynamicCSSTable class, as well.
/apps/frontend/modules/dynamiccss/templates/renderSuccess.php
body { background-color: <?php echo $stylesheet->getBackgroundColor() ?>; }
/lib/model/doctrine/DynamicCSSTable.class.php
... public static function getByIdentifier($identifier) { return $this->createQuery('c') ->addWhere('c.identifier = ?', $identifier)->fetchOne(); } ...
Now you can add your dynamic style sheet to the view. Here is an example:
/apps/frontend/config/view.yml
default: ... stylesheets: [/dynamiccss/main.css: { raw_name: true }] ...
That “raw_name” option allows you to override the typical way of creating an asset’s file name.
Now whenever someone edits the background color for the “main” style sheet, from the database, the background color can change.
You’ll still need to add an interface for editing the background color (or however many other options you add). That is up to you.
Complaints? Pointers? Thought of a better way?
Please post.
Tags: dynamic css, Symfony 1.2
- 5 comments
- Posted under CSS, Design, Doctrine, PHP, Symfony 1.2
July 22, 2009 Table-level behaviors will remove global behaviors in Doctrine schemas
Here is something to watch out for. If you define global behaviors in your schema, and then define table-level behaviors, the global behavior will be removed (unless you declare it again on the table level).
Here is a quick example:
actAs: [Timestampable] Program: actAs: Sluggable: fields: [name] columns: name: string(255) user_id: integer(4) relations: sfGuardUser: foreignType: many type: one local: user_id foreign: id
For a short while I couldn’t figure out why the created_at and updated_at fields weren’t showing up in the Program table. Then I realized that it was removed at the table level, and I had to add it in again.
The official documentation does not mention this:
http://www.symfony-project.org/doctrine/1_2/en/04-Schema-Files#chapter_04_global_schema_information
Hopefully this will save you 15 minutes of scratching your head.
Here is more information on Symfony 1.2 and doctrine:
http://www.symfony-project.org/doctrine/1_2/en/04-Schema-Files
- Leave a comment
- Posted under Behaviors, Doctrine, Symfony 1.2
July 18, 2009 Using Symfony filters and CSS to present a design in-context
This post assumes you have some experience with Symfony 1.0.
At my work I needed to create several different versions of the same site. This mainly entailed changing background images to get a slightly different look and feel, and changing the content category, depending on a request parameter.
For this tutorial, I will use the company I work for as an example. Momentum IT Group has two distinct divisions: ELearning and Web. So let’s say I wanted to make slight design changes depending on which division I’m looking at.
We’ll start where all things Symfony begin: with the routes. Open up apps/frontend/config/routing.yml
and enter the following:
division_page: url: /:division/:slug param: { module: cms, action: display } requirements: { division: elearning|web }
I leave the cms module for you to implement. The display action would take a unique slug as a key to look up the page content from the database for display.
In this route, the division is required to be either elearning or web, and these two options will be used to display a different background design (and possibly select from a different content category in your cms module).
The next step is to implement the switch which determines which background to apply, if any. You can always write this code in your cms module, but you may have other modules other than the cms module which need to change design elements depending on which division the user is viewing. To handle this situation, you can create a filter.
A filter is applied once for each request. Start by opening apps/frontend/config/filters.yml
and adding a new filter class:
rendering: ~ web_debug: ~ security: ~ # generally, you will want to insert your own filters here cssFilter: class: cssFilter cache: ~ common: ~ flash: ~ execution: ~
Create the filter class in apps/frontend/lib/cssFilter.class.php
:
<?php class cssFilter extends sfFilter { public function execute($filterChain) { if ( $this->isFirstCall()) { $response = $this->getContext()->getResponse(); switch ( sfContext::getInstance()->getRequest()->getParameter('division') ) { case 'elearning': $response->addStylesheet('elearning', 'last'); break; case 'web': $response->addStylesheet('web', 'last'); break; } } } }
Make sure you place two new CSS files in your web/css
directory.
For this example, they are elearning.css and web.css. They will be the last css files in your HTML header and will override your main.css where needed. So, you might override a background image like so:
web/css/main.css
body { background: url(../images/background.jpg) no-repeat top left; }
web/css/elearning.css
body { background-image: url(../images/elearning.jpg); }
Now if you direct your browser to [your_site]/elearning/[page_slug]
, you will have taken advantage of CSS cascading and Symfony filters to present a slightly different design depending on the context.