So you've created your views in Drupal. You supposed that they would bring you calmness into peaceful life. And then you get a new request from your customer -- I want these views to be rendered inside tabs, I want them ten or more per page, and I want them to load dynamically. If you're not a stunned Drupal-only developer, but just a normal guy or gal with a pile of development knowledge, you'll probably try to render your view programmatically using views_embed_view() or similar, then return it to the client side, which is after all some jQuery .click() event with a .post(), and you'll hope for the best.
Unfortunately, (and speaking about Fortune, we could rephrase to - pretty predictively), you get the nice-looking rendered view but, the AJAX doesn't work. No pagination, no other event which you have defined. You start analyzing, and get the impression that some .js files are missing.
They are missing because when Drupal renders a page, there's not a single view present (you're loading them with AJAX, remember?). You may try to collect all the javascripts and throw them all in the header just to test, but wait.
There is a nice module in Drupal, especially made for this purpose. It's called Quicktabs. It's a complex module, and it knows how to render not only Views, but also blocks, nodes and another quicktabs.
So you need to install this module. Create your View, then go to Structure >> Quicktabs, create a new one. As you can see, the interface is simple and minimal, and this is the case that every additional word about it is re-dun-dant. The only thing to notice is the Machine name, near the Title. Write it down, you're going to use this soon.
Inside your module, create a menu, then the hook_theme() etc. Now we're going to render the quicktabs programmatically with the following code:
$quicktabs = quicktabs_build_quicktabs('the_name_of_my_quicktab_that_i_wrote_down_in_the_previous_step');
$content = render($quicktabs);
return theme('draw', array( 'content' => $content)) ; // use $content variable in draw.tpl.php template
That's it. Now you can navigate to your website.local/draw or whatever you've defined, and enjoy from dynamically loaded content such as Views, Nodes and what not.
I'm adding a code for minimal module needed to demonstrate this.
The module:
'Let\'s draw some tabs!',
'description' => t('Description'),
'page callback' => 'my_draw',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK
);
return $items;
}
function my_draw() {
$quicktabs = quicktabs_build_quicktabs('the_name_of_my_quicktab_that_i_wrote_down_in_the_previous_step');
$content = render($quicktabs);
return theme('draw', array( 'content' => $content)) ; // use $content variable in draw.tpl.php template
}
/*
* Implementation of hook_theme().
*/
function my_theme($existing, $type, $theme, $path){
return array(
'draw' => array(
'variables' => array('content' => NULL),
'template' => 'draw', // place you file in 'theme' folder of you module folder
'path' => drupal_get_path('module', 'my') .'/theme/'
)
);
}
The .info:
name = My
description = My module.
package = My
core = 7.x
php = 5.2
The template from my_module/theme/:
Let's draw our quicktabs:
Cool, ehh?
We have successfully rendered AJAXified tabs and loaded contents with the power of Drupal. However, if I had to create this or similar things again, as a developer, my better choice would be writing the clean code with Ruby on Rails. Of course, Drupal is the best option when you know what you want and, you know that it's easy to achieve with Drupal and you know which module to use - when, where and how. I hope this article has helped you to solve your problem.
We minimize users interaction.
Unfortunately we still find today a lot of weird web forms. How often do you find these annoying date input fields, where you see a plain text box with a nice example text explaining you the correct format, e.g. dd-mm-yyyy or such?
First, you have to type. And this increasing the negative user experience and time on the website. The game that you created is played from now on against you. Isn’t it shocking?
Next, the user has to type correctly! And in case that there’s some validation behind, the website will return your marked input yelling some “Error! Please correct your date” output. The game continues, and the user corrects the text. This stage can go on loop several more times.
Multiply this single experience by some 10 more elements some 10 more websites per day, and your frustration is guaranteed. This is what I experience daily. You might feel the same.
How this could be done better? The developer, can add an example or two. Instead of adding dd-mm-yyyy, 04-07-2012 could be added. Now the user can just copy and paste, or mouse-select and drag and drop this example to the textbox input and change it. A datepicker could be added, but hey, this is all old and primitive. We’re in the HTML5 era fast-forwarding to HTML10, aren’t we?
The best in my opinion, would be a completely new element in HTML5, but since there’s isn’t one, let me explain how to make it with Javascript.
The date field would consist of something resembling the old counter, with guessed date. If the date cannot be guessed, today’s date can be placed. But if we’re talking about some adult, and asking about his age, we could guess that his age is more than 20 years, and place the initial value, say, 15-06-1992. Now comes the interesting part. Clicking and dragging the numbers, should roll the number up or down. So if my birthday is actually on 28-09-1979 (dd-mm-yy remember?) then I’ll drag up a bit the number 15, then just a bit more the number 06, and finally the year. Why 15? Because it’s in the middle of any months. See the logic sneaking in here? So same with the 06.
This element is kind of new. So how do we make someone feel comfortable with it? We tell the user nicely, “click and drag” and we make sure that dragging up is equivalent to dragging to the right, and down is equivalent to the left. Why? To ensure that user gets the feedback in any case even if he moved to the wrong direction.
Of course, the validation should go with AJAX or HTML5 or whatever, just don’t force the user to refresh the page! Add a simple button, and please don’t use the ugly word “Submit”. Call it “Save” or “Store” or “Update” instead. And once it’s clicked, change it to “Saved”, “Stored” or “Updated” disabled button state accordingly. Take one further step to make it even better - save automatically each few seconds, so there won’t be any need to submit anything to anyone.
Are you still reading? Good. Let’s allow the “Save” button to flow nicely to the next field which we’re editing. Do you remember how it’s troublesome, especially inside a long web form, to scroll up and down and trying to figure out where is that freaking button? No more.
Red light district. I really hate this thing. Every time a red-colored error appears before my eyes, it drives me crazy. It’s like I just created some wicked evil. Instead, I’d love to have some more nice colors, like green or blue sine-waiving calmly and driving my attention. Ok, you know what? You can add the STOP road sign of the country where the user came from. Just remember, it’s not the same in most of the countries, so please do act accordingly :)
In this pattern, we’ve covered and corrected a pattern for date input field to make users experience more pleasant on your website, thus leaving them with a good mood. This won’t push them away and eventually will bring you more happy potential customers.
So you thought Google Chrome is almost perfect, bullet-proof, iz-da-browser, and, what say you? Sure, it would be unfair, almost untrue to claim the opposite, however, there is a neat bug, and you could argue that it's not a bug but a feature, and I could argue back, and... you got it.
But since the people who wrote the Chrome are from Venus and Mars, just like you and me, their main belief is that user's experience should be comfortable, easy, and fun. You could think that I'm getting paid for this implicit advertisement, but I'm not. What they did to Chrome is this: they taught him to automatically fix errors that a programmer presumably created. And since they're awesome, all this error fixing is of course super fast, it's realtime.
The problem? Sure there's a problem. It goes like that: when you have written some Javascript code which updates the innerHTML with some, say, unclosed <table> element, at this point of time, the browser will fix that line, and add the closing tag somewhere at the end. But hey - you would say - what if I provide the innerHTML with the closing tag on the next line? Well, it's too late, because, Google Chrome is amazing.
element.innerHTML = '' + "Some text with or withot HTML tags ";
element.innerHTML += "Another textual string with or withot HTML tags";
element.innerHTML += '
';
And with legacy code, things like these can take a long time to determine. Days. Weeks. Don't believe me? Try to hide this somewhere in your website and tell your friends about amazing bug, which happens only in Chrome, and, surprisingly, not in IEs. But luckily, there's this article, so we're safe. The solution? Well, you already guessed. If not, here it comes:
var long_string = '' + "Some text with or withot HTML tags ";
long_string += "Another textual string with or withot HTML tags";
long_string += '
';
element.innerHTML = long_string;
In this case, the browser eats the whole thing like a creamy chocolate cake with a lot of ganache, and everybody's happy.
So if you don't think it's a bug, please debug. But if you think it is a bug, please don't debug. In any case, I will be happy if you leave a comment, because, well, why not?
Also, by this chance, I'd like to introduce you our new project, and it's called Open Website Project. It is a place where the good people (!!), or in other words professionals like you can find fellows, and build something different together or just discuss interesting topic.. It's small, almost private, and that's the big difference. So long.
The archived files will have unique names containing script's execution time. It should run an any Linux/Unix machine. This was written as a daily backup, it doesn't delete any previous backups, nor does it implement any incremental backup functionality. It just a simple script for your joy, so have fun.
!/bin/bash
#
# DB Backup script by webdesignpatterns.org
#
# modify the following to suit your environment
export BKP_DIR="/path/to/your/backupsdir" # << Modify this - the directory of your backups.
export DB_USER="root" # DB username
export DB_PASSWD="*****password*****" # DB password
databases=( "database_name_1" "database_name_2" "database_name_3" ) # an array - don't be shy to add more DBs here
# Create archive filename.
day=$(date +%m.%d.%Y)
for i in "${databases[@]}"
do
archive_file="$i-$day.sql.tar.gz"
echo "* Creating new backup..."
mysqldump $i -u root -p$DB_PASSWD > $BKP_DIR/$i-$day.sql
echo "* Archiving..."
tar czf $BKP_DIR/$i-$day.sql.tar.gz $BKP_DIR/$i-$day.sql
echo "* Removing raw files"
rm $BKP_DIR/$i-$day.sql
done
echo "Done"
exit 0
You'll have to copy the code and paste it into some file, say daily_backup.sh. Then put it into some directory, preferably into something like /etc/cron.daily/ which exists by default in Ubuntu for example. Please note: scripts in Ubuntu system in the /etc/cron.daily/ need to be without extensions. You can actually test which scrips will run by executing:
run-parts /etc/cron.daily --test
So:
mv /etc/cron.daily/daily_backup.sh /etc/cron.daily/daily_backup
Now you'll have to say when to execute it, so:
sudo crontab -e
and
# m h dom mon dow command
40 07 * * * root run-parts /etc/cron.daily
Also give the relvant execution permissions to the script:
chmod uga+x /path/to/your/backupsdir/daily_backup.sh # /etc/cron.daily ?
This will execute everything in /etc/cron.daily each day at 7:40am. Next, naturally, you should restart your cron service like that:
sudo service cron restart
That's it.
Enjoy.
Some patience is required to successfully install the Redmine. But some people are just like me - don't have it, especially when doing something for fun on their free time. If you encountered similar issues (and probably you did, if you googled for this article) - read next on how easily this can be fixed.
RAILS_ENV=production rake db:migrate
checking for main() in -lnsl... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lmygcc... no
checking for mysql_query() in -lmysqlclient... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.
Then below some message appears about the missing i18n gem, but when you try to install it, another error tells in some way that you need to install mysql gem, maybe something like this:
# gem install mysql
Fetching: mysql-2.8.1.gem (100%)
Building native extensions. This could take a while...
ERROR: Error installing mysql:
ERROR: Failed to build gem native extension.
/usr/bin/ruby1.8 extconf.rb
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lm... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lz... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lsocket... no
checking for mysql_query() in -lmysqlclient... no
And as any good boy or girl, you're running these lines one after another, and then install the i18n gem, and then repeat the first problematic line.
apt-get install build-essential
apt-get install libmysqlclient15-dev
updatedb
locate mysql_config
gem install mysql -- --with-mysql-dir=/usr/bin/mysql_config # <-- well, the path of yours, from the previous line's output!
Now try:
RAILS_ENV=production rake db:migrate
~HTHContemporary web technology is rapidly evolving together with the demand for online services and more attractive, more functional and faster websites are born. Some fifteen years ago, when the old yahoo.com looked approximately like most of the websites, and people were uneasy with the noisy sound of connecting modems, most of the websites were less interesting than they are now. Because today, it's even hard to find a website, where you will see "Under Construction" message with the same digging dude everywhere - a common practice in those times (why declaring that something is broken or not fully working?). But now everything virtually physical is available through the Internet - information services like news are easily accessed, every respectful business will happily provide all available data about their products and happily supply goods on your buying-request or will provide any relevant information. In this reality the competition dictates it’s own rules, and so the web technologies with qualified professionals and the demand for them -- all together evolve. With the growing demand grows the supply too. There are plenty of tools for web development, both free and non-free. There are lots of amazing designers, and plenty of charismatic salespeople who will promise you exactly what you want and/or need in exactly the right time, and if you’ll buy it right now you’ll get a "WOW" discount. All of that good stuff looks like the direct relation of the amount of money or professional time spent on the website, which is an indispensable service just like Bill Gates promised many years ago. How does one choose the right people, and the right technology when there is a plethora of them? How one doesn’t get lost in the completely foreign world? The short answer would probably be - you'll need some map that you can understand and trust and you won’t be lost. In this article, I’m going to start outlining such a map.
Typically there are several types of the websites, most of them sharing the same patterns and even same technologies. There are blogs, online stores, news websites, banks, brokerages, airports etc. It can be very tempting to think that the more you pay for it, the better you get. Sometimes this isn’t true. One of the possible reasons could be - is when you are dealing with non-professionals who claim that they are, subsequently they can’t approximate correctly the job to be done, and in the end whole project fails. So if you aren’t a web guru, who knows all the trends and at ease with all the modern advancements in the web area, it’s probably a good idea to adopt one as a guide. This guru should be familiar with all the points raised in this article, but if you want to control the situation yourself, you will find here the info you need.
In order to make our map usable, let’s define the elements which it’s made of -- or, in other words, the elements of which the whole website building process consists.
What we need is to determine:
These are the key questions that should be answered before any attempt to buy a website, so let’s form them into a more extended and informative discourse.
To define a content, one needs to use functional words. The more details you bring into this description, the easier it will be to estimate the effort. For example, instead of telling something like: “I need a great online shop with a forum and cool front page”, one may rephrase this into something like: “The shop will have five main pages types, or categories. There will be products search, where the results will be no more than ten per page, and when clicking on any result, the user will be redirected to the product page. There will be Our Branches page, where the user will be able to click on any branch, and being redirected there. There will also be the informational pages - each of which should have a promotional full-page ad-like content with no functionality”. The latter description at best, should go down to the lowest-level details, such as where the search input text will be placed, and how it should look like. Don’t worry about defining the visual content if you’re missing graphical designer’s vocabulary. You can always make a snapshot from other websites for an illustrative example.
This is true that one picture is worth ten thousand words. Similarly, one experience with a web agency is worth ten thousand recommendations. And It doesn’t really matter what others tell you about the virtues of company X. So where one gets this invaluable experience? A good approach is to have dedicated and responsible web guru who will be working closely and audit the progress of the project. Since any definition can be interpreted differently by any different people, it’s important to have a constant feedback, so the corrections would be instantly made. There is one website actually, which aims to ease on the web team creation process. But for now, steps should be taken, and hours spent on the planning.
Know your people. It should be the responsibility of a professional to look for a web team or agency, and learn about their expertise individually. This shouldn’t be too hard, since a typical web development team consisting of developers and designers can be as small as three-to-ten people. Asking them to show their portfolios may sound tempting, however practically it's of a little value. Instead, questions about technologies being used and how long it takes to complete Y are far more important. These questions unveil the expertise and abilities and allow learning about the creative approaches for problems solving that an individual may face and how he deals with unexpected difficulties. Additionally, the depth of knowledge of the fields of expertise may be very critical, especially when it’s desirable to write a clean and maintainable code, more on this sometime later.
To conclude, remember that you’re dealing with human beings. And as such, it doesn’t matter what we claim with big letters about ourselves. We may say that we are the best and would love to satisfy quickly your every request, but in fact, before all, we’re interested in being paid, we want a normal weekends rest and other usual things that you want. And yes, we all believe that we are the best, but you are the one to differentiate us. So please try to choose the right people before all, and then all of us will sleep better at nights! :-)
Laziness is the ultimate locomotive of the Progress. Many times I realize that I need a much simpler and more efficient image uploading mechanism, simpler than the regular and annoying file dialog, and even easier than an FTP client. That's why I wrote the script below. You can just copy-paste it into your new python file, name it like upload_image.py. The script asked me to allow him to explain to you about what he's doing, and I gladly agreed with him, so what's following next - is his own explanation of the process.
Let's say my owner has a small food blog, such as this one. For each post, there can be from one to an infinite number of images. To reduce the image size, my owner needs to reduce the jpegs' sizes using, for example, the neatest David's Batch Processor. The owner is a lazy user who always leaves the minimized images in the same directory with the originals (don't ask, they sometimes think that 'directory' is a bad word). Now the jpegs' sizes are all around 45k-50k, but I want to reduce them even more (bandwidth is my other friend). I run
exiv2 rm *.jpg
, which removes all the thumbnails and other unneeded data from images, and now they're just as small as I wanted them to be. Then, I move the original files into another directory on the same level as the original directory, make a gzip archive and upload it: I connect to the server, go to the images directory and unzip the archive, with the containing directory. I also output some tags, so my owner would just copy-paste them into his html editor, or whatever this person has there.
As you can see, I have some settings, and to use me you should change things like username, port etc., there aren't much. And you can run me from command line by typing
python upload_pics.py -d path/to/the/images/directory
I'll ask you for your remote server password if you don't mind. Oh, and you'll need to install all the required libs which I have in the #import declaration - but only if you don't have them already.. Just select them from Synaptic or something. And please have exiv2 installed as well. That's quite it. And, hee-hee, you know, you can't ask me question, because I'm just a script so, use me, and drink more beer on your free time.
#!/usr/bin/env python # By Val Kotlarov @webdesignpatterns.org # This will upload files and extract them. import base64, getpass, os, socket, sys, traceback, tarfile, getopt, paramiko, shutil import pyexiv2 # settings go here username = 'username' # change with your username port = 22 # your connection's port number, 22 is the default hostname = 'example.com' # change with domain or static ip address remote_images_directory = '/path/to/your/images/directory/' # set to where the images directory is max_file_size = 150000 # maximum size in bytes for images to be papcked and uploaded remote_upload_file_path = remote_images_directory+'upload.tar.gz' password = getpass.getpass('Password for %s@%s: ' % (username, hostname)) # end settings # get files in directory which are greater than given size in bytes def listDirectory(directory, minFileByteSize): "get list of file info objects for files of particular extensions" fileList = [os.path.join(directory, os.path.normcase(f)) for f in os.listdir(directory)] #filter by file size fileList = [f for f in fileList if os.path.getsize(f) > minFileByteSize] return fileList try: opts, args = getopt.getopt(sys.argv[1:], "d:v", ["help", "output="]) except getopt.GetoptError, err: # print help information and exit: print str(err) # will print something like "option -a not recognized" sys.exit(2) input_dir = None # the directory to be uploaded for o, a in opts: if o in ("-d", "--directory"): input_dir = a else: assert False, "unhandled option" exit(0) # setup logging paramiko.util.log_to_file('upload_pics.log') # move big files to a new directory on the same level big_files = listDirectory(input_dir, max_file_size) # ~150k if big_files != []: os.mkdir(input_dir + '_big') for f in big_files: shutil.move(f, input_dir + '_big/'+os.path.basename(f)) #print html for images files = listDirectory(input_dir, 0) for f in files: print '' # remove exiv info os.system("exiv2 rm " + input_dir + "/*.jpg") # store into tar file, having the archive's directory name tar = tarfile.open(input_dir + "/upload.tar.gz", "w:gz") # add this directory and its contents, without path to it! tar.add(input_dir, arcname=os.path.basename(input_dir), recursive=True) tar.close() # get host key, if we know one hostkeytype = None hostkey = None try: host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts')) except IOError: try: # try ~/ssh/ too, because windows can't have a folder named ~/.ssh/ host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts')) except IOError: print '*** Unable to open host keys file' host_keys = {} if host_keys.has_key(hostname): hostkeytype = host_keys[hostname].keys()[0] hostkey = host_keys[hostname][hostkeytype] print 'Using host key of type %s' % hostkeytype # now, connect and use paramiko Transport to negotiate SSH2 across the connection try: t = paramiko.Transport((hostname, port)) t.connect(username=username, password=password, hostkey=hostkey) sftp = paramiko.SFTPClient.from_transport(t) # upload print 'Inside directory: ' + input_dir sftp.put(input_dir+'/upload.tar.gz', remote_upload_file_path) print 'Uploaded file' ch = t.open_channel(kind = "session") ch.exec_command('cd '+ remote_images_directory+' && tar xzf upload.tar.gz') print 'Finished.' sftp.close() t.close() except Exception, e: print '*** Caught exception: %s: %s' % (e.__class__, e) traceback.print_exc() try: sftp.close() t.close() except: pass sys.exit(1)