Home

Synchronizing Klok between computers E-mail
Tuesday, 21 February 2012 05:16

If you use the Adobe AIR application Klok for time-tracking, there's a small caveat to watch out for if you wish to synchronize your time data between computers using something like Dropbox or Windows Live Mesh.  Basically, if the path of your Klok installation differs from one machine to the other, you don't want to sync all the files in the Klok folder but only the main database file.

 

Klok stores its data in a file called klok.db, but there are a few other files in there as well, one of which I found to be to cause problems in synchronization between machines.  That file is config.xml, which looks like the following:

<config>
 
  <data-file-location>C:\Users\<User>\AppData\Roaming\
 
Klok2.DD7F2188B985C2439837C76B42A187050457E61B.1\Local Store
 
</data-file-location>
 
  <check-for-updates>true</check-for-updates>
 
  <next-update-check>03/21/2012</next-update-check>
 
  <auto-snap>false</auto-snap>
 
  <snap-interval>15</snap-interval>
 
  <first-day-of-week>0</first-day-of-week>
 
  <left-panel-size>200</left-panel-size>
 
  <skin>assets/KlokNightSkin.swf</skin>
 
  <zoom-start>NaN</zoom-start>
 
  <zoom-end>NaN</zoom-end>
 
  <clock-mode>twelveHourMode</clock-mode>
 
  <date-format>MM/DD/YYYY</date-format>
 
  <minimize-to-tray>false</minimize-to-tray>
 
  <automatically-backup>false</automatically-backup>
 
  <backup-delay>7</backup-delay>
 
  <next-backup-date>1330400138787</next-backup-date>
 
  <total-time-mode>totalAsTimeCode</total-time-mode>
 
  <last-backup-date>-1</last-backup-date>
 
  <timesheet-export-format>XLS</timesheet-export-format>
 
  <connector-debug-enabled>false</connector-debug-enabled>
 
  <backup-folder>C:\Users\<User>\AppData\Roaming\
 
Klok2.DD7F2188B985C2439837C76B42A187050457E61B.1\Local Store\backups
 
</backup-folder>
 
  <new-entry-size>60</new-entry-size>
 
  <recent-projects-size>5</recent-projects-size>
 
  <window-width>800</window-width>
 
  <window-height>600</window-height>
 
  <window-x>100</window-x>
 
  <window-y>100</window-y>
 
</config>
Note <data-file-location>, the first data node in the file.  That file location needs to be the same between your different machines in order to synchronize the full Klok folder.  If the installation directories are different, i.e., that line is different between your two Klok installations, you want to just synchronize klok.db.


 
Installing openSUSE (in VirtualBox) with a LAMP server E-mail
Tuesday, 07 February 2012 23:18

I wanted to install openSUSE in VirtualBox, and set up a LAMP development environment in it.  The "how to" information I found is already available, but it's scattered in different places (blogs, wiki posts, videos, etc.), so I decided to note it down in one place for future reference.  I am focusing on the openSUSE part of the installation process and not the VirtualBox part of it, since that's pretty easy to do.

 

The steps I followed are as follows, along with references to useful instructions:

  1. Install openSUSE in a new VirtualBox virtual machine.
    1. Download openSUSE (I used openSUSE 12.1).
    2. Create a new virtual machine (VM) in VirtualBox, and run the installation from the ISO you just downloaded.
    3. You shouldn't need to manually install VirtualBox Guest Additions because they get installed with the openSUSE installation.
    4. Some useful instructions here and here.
  2. Install Sun Java JDK.  This does not come installed or even available in the default repositories.
    1. I've come across a few programs that either require or work better with Sun Java JDK (like SmartGit and PhpStorm).
    2. Follow instructions here (the procedure for the x64 version is in the right column).  One point to note is that the author recommends not using the RPM version of the installer.
  3. Set up the LAMP server (instructions here).  openSUSE comes with a "pattern" for installing all the required packages in one easy step!
    Note: openSUSE by default creates a /public_html directory in your /home directory.  Apache/PHP store files in /srv/www/htdocs, but you can also store them in your /home/<username>/public_html directory and access them via http://localhost/~<username>/<filename>.
  4. Add and auto-mount a shared folder from the Windows 7 host environment.
    1. Add a shared folder through the VirtualBox VM Settings.
    2. Follow instructions in the 5th post on this page (that is the procedure that worked for me).


 
Google Maps not loading in IE8 or IE7? E-mail
Monday, 19 September 2011 07:08

I came across the following implementation of a Google Maps embedded map in a Rails application.  The map loaded fine in every popular browser except Internet Explorer 8 and 7.  After some time I figured out what it was:

 

Original code

this.drawMap = function() {
 
    if($('#map[data-map-js-url]').length > 0) {
 
        $.get($('#map').attr('data-map-js-url'), function(data){
 
        script = $(data).text();    //line to change
 
        eval(script);
 
        });
 
    }
 
}

 

Modified line that loads map in IE8 and IE7

script = $(data).html();

 

If I were to just guess at the cause, I would say that newer browser are more forgiving in what they let eval parse, whereas IE8 and IE7 require proper HTML.



Tags: jQuery  .html()  .text()  Internet Explorer  IE8  IE7  JavaScript  eval  
 
OpenCart 1.5.0.x: creating a simple new layout E-mail
Sunday, 26 June 2011 21:45

Introduction

If you don't already know, OpenCart is an open-source e-commerce solution written in PHP that's designed around the MVC paradigm.  I've only worked with it briefly, but if you're familiar with other MVC platforms (like Ruby on Rails or CodeIgniter for PHP) then you can find your way around fairly easily.

 

One thing I would say about OpenCart is that it doesn't have the greatest documentation in the world.  Since it is MVC-based you can make up for that loss from your own experience, but still, it's less than ideal.  The forums seem pretty active, but they're still no substitute for thorough documentation.

 

In this tutorial I want to demonstrate how to add a custom catalog (non-admin) view to the many default ones OpenCart comes with.  These include (but are not limited to):

  • home
  • category
  • product
  • manufacturer

Task and assumptions

The view I want to add is a simple one.  It displays all the categories with some number of products from each category all on one page.  That's a view that OpenCart doesn't come with by default.  In OpenCart parlance what we're creating is a new layout.

 

This really only makes sense with a flat category structure.  If you have nested categories it would get unwieldy pretty quickly to display all the categories and their respective products.  So I will assume that categories are only one level deep with no children.

 

I will be demonstrating on a default installation of OpenCart 1.5.0.  Before you start, make a copy of the default theme, under /catalog/view/theme/default.  I'll call it default-copy.

File structure

The place to start is by examining the OpenCart file structure to see where we want to add our modifications.  (There are always multiple ways of accomplishing the same task, and this is just one way of doing it.  Frankly I haven't explored any others.)

Here's the default OpenCart file structure:

/opencart

--- /admin

--- /catalog

--- /download

--- image

--- /system

- .htaccess.txt

- config.php

- index.php

- php.ini

 

It's fairly self-explanatory.  Since we're only adding a new front-end (catalog) view, the catalog folder is the only we're concerned with.  That folder looks like:

/catalog

--- /controller

--- /language

--- /model

--- /view

 

OpenCart follows an MVC+L design, the L represented by the language folder above.  Again, since we're only adding a new layout, we won't be doing anything with the model or language files, so we're only concerned with the controller and view folders.

There are many folders inside /catalog/controller:

/controller

--- /account

--- /affiliate

--- /checkout

--- /common

--- /error

--- /feed

--- /information

--- /module

--- /payment

--- /product

--- /total

 

Most of these are fairly obvious as to what they pertain to.  For instance, account contains controller files relevant to customer accounts, checkout contains controller files relevant to the shopping checkout process, and so on.

 

We want the controller files that deal with the products on the site.  We want to display categories and products, both of which are handled by files in the product folder.  The product folder contains:

/product

- category.php

- compare.php

- manufacturer.php

- product.php

- search.php

- special.php

 

It may or may not be obvious what these files represent (depending on whether you've worked with MVC-based applications before).  Loosely put, each of these files represents a product-related resource users of your site can request.  So:

  • category.php lets users view products for any given category
  • manufacturer.php lets users view products by manufacturer (brand)
  • product.php lets users view a single product of interest
  • search.php lets users view the products relevant to their search term

We're out to add a new layout (a "type" of view) to the ones already available, so we want to add a new controller file here.  I've called it categories.php but you can call it something else if you so desire.  Once we're done, categories.php will let users view all the categories of products, with some number of products from each category, like:

  1. Category One
    • Product One
    • Product Two
    • Product Three
  2. Category Two
    • Product Four
    • Product Five
    • Product Six
  3. Category Three
    • Product Seven
    • Product Eight
    • Product Nine

Now the view...

The categories.php controller will retrieve whatever data it requires from the relevant models (which I won't go into) and send them to the appropriate view.  View files are held inside /catalog/view, which looks like:

/view

--- /javascript

--- /theme

------ /default

------ /default-copy

--------- /image

--------- /stylesheet

--------- /template

------------ /account

------------ /affiliate

------------ /checkout

------------ /common

------------ /error

------------ /information

------------ /mail

------------ /module

------------ /payment

------------ /product

------------ /total

 

Except for the mail folder, there is a 1-to-1 correspondence between the folders under /catalog/controller and /catalog/view/theme/default-copy/template.  It makes intuitive sense that since we added a new controller under /controller/products, we'll add a corresponding view file under /template/products.

 

The /template/products folder contains the following template files (with the tpl file extension):

/template/products

- category.tpl

- compare.tpl

- manufacturer_info.tpl

- manufacturer_list.tpl

- product.tpl

- review.tpl

- search.tpl

- special.tpl

 

Similar to how we added a categories.php controller, we'll add a new view file here (call it categories.tpl), containing the HTML markup and embedded PHP to display the data categories.php is sending it.

We're adding a new controller file and new view/template file for the new view we want to implement:

  • We're calling the new controller categories.php and adding it to /catalog/controller/products
  • The new view is called categories.tpl and gets added to /catalog/view/theme/default-copy/template/products

Let's move on to the actual contents of the new files.

categories.php controller

What we want to display is most similar to what category.php currently does, which is display some number of products for one given category.  That seems like a good starting point toward our objective, to display some products from all categories.

 

For reference, this is the default category.php that comes with the default installation of OpenCart.

Let's look at categories.php, which starts out as a copy of category.php which we modify.  I will do this in three steps:

  1. Irrelevant code blocks commented out
  2. Irrelevant code blocks deleted
  3. Relevant code modified and new code added

In the code below, I've commented out blocks of code for the following features, which we don't need for our new view:

  • Product sorting, ordering, and pagination
  • Breadcrumbs - getting the category title and link, since we're not viewing any single category
  • Getting child categories - we're working with a flat category structure
  • Category information - name, description, keywords, etc.
  • Check for whether a category exists - again, we're not viewing any single category, so no sense in keeping this

It's much easier working with a smaller file with less code, so I've removed all the commented code from the previous step, which is what's shown below.

Now we can look at the full code with modifications and additions.

 

Lines of note are the following:

Lines 25-31

Here we specify the number of products we want displayed for each category under limit, which is 3 in this case.  We've commented out the sort and order lines because they don't apply in this layout.  start will be 0 since there is no pagination in this layout, which is fine for our purpose.

 

Lines 68-79

We're adding an element to the $product_data array, which is a product from the category we're currently iterating over.  The properties we don't require for this layout have been commented out.

 

Lines 82-87

We store the current category as an array element to $this->data['categories'].  The products for this category (from $product_data above) are stored as $this->data['categories']['products'].

categories.tpl view

Similar to how we modeled categories.php from category.php, we'll model categories.tpl from category.tpl.

 

Here's the default category.tpl view template:

And here's categories.tpl, which derives from it but is a lot shorter, because it:

  1. Displays less information about the products than the single category view
  2. Removes the logic to handle child/nested categories
  3. Shows the products by default (and only) in grid view, and removes the option for switching between list and grid view

I think the code below is pretty straight-forward and self-explanatory.  It iterates through all the categories and for each category:

  1. Displays the category title
  2. Displays a few products from that category
  3. Adds a "view more" link to go to the category page

One last thing

Since we've added a new layout to our OpenCart project, we should add it to the database, which keeps a list of layouts, so we can specify modules that we want to display in our layout.

There are two places to add an entry.

  1. The first is in the table called layout.  Notice the last entry in the screenshot below, which represents the new layout.

    OpenCart layout database table

     

  2. You also need an entry in layout_route, as shown below (the last entry): 

    OpenCart layout_route database table

Now we can do the following in our OpenCart admin to add a module to the new layout we've created (last entry below):

OpenCart admin module page

Summary

To create a new layout in an OpenCart 1.5.0.x project that shows a list of all categories and some number of products from each category, follow these steps:

  1. Add a new controller file to /catalog/controller.  I called the file categories.php.
  2. Copy the contents of category.php which comes with the default theme in OpenCart to the new file (or just copy the file and rename it), and modify it to look like the following:

  3. Copy the default folder under /catalog/view/theme into a new folder.  I called it default-copy.
  4. Add a new view/template file under /default-copy/template/product called categories.tpl which looks like the following:

  5. Add an entry for the new layout to the layout and layout_route tables in the OpenCart database so you can add modules to it from within the OpenCart administration panel.

Alternative

I haven't tried it, but another way to accomplish this same task would be to add a new action to the category.php controller, instead of adding a new controller like I did.  Whereas everything is contained in the index action by default, we are free to add a new action with alternative code and corresponding new view.



Tags: OpenCart  1.5.x  MVC  PHP  tutorial  view  template  controller  layout  
 
What I've learned E-mail
Saturday, 18 June 2011 11:10

Here are some things I've learned about the web design and development world in my short years in it so far:

Learning

The web development world is a maze (vs. a labyrinth), littered with innumerable equally-appealing choices.  For example:

  • JavaScript frameworks: jQuery, MooTools, Dojo, Prototype, ExtJS, ...
  • PHP frameworks: CodeIgniter, Zend, Symfony, CakePHP, Yii, ...
  • Popular web languages: Ruby, Python, PHP, C#, ...

If you're going to get anything done, you have to just step into the maze and start working.  The good news is that you can't really go wrong, even if you get lost for a while.  There are so many resources out there, and so many useful and cool things have already been done in pretty much any technology you decide to take on.

 

On the other hand, you can be like the myriad of people asking questions like:

Notice the number of [closed] questions that show up when searching for terms like best, better , and should I learn.  There's a good reason for that: questions like that are a waste of time, and are generally asked by people who are afraid and just stalling getting started.  I know because I've been there.  Pick one and don't look back, unless somehow you've managed to get yourself absolutely stuck at a dead-end.

  • It's not enough to read books.  (If it were that easy I would be a genius by now.)  You have to do the work and practice what you're reading.
  • When reading articles out there on the Web (including mine), check the date.  Technologies are getting updated so  quickly that some fix or trick that applied a few months ago may no longer work.  The publication date is the first thing I check before even reading an article.  If it's too old I'd rather spend the extra time to find a newer, more relevant piece.

Practice

If you're looking to get something done, chances are it's been done before.  Spend some time and look around the internet to see if someone's already solved the same or a similar problem.  It's worth it.  It's even worth it spending some money - you'll be rewarding someone else for their work while saving yourself time and your client money (which in turn could lead to more money in repeat business).

 

Good places to check are GitHub, CodePlex, StackOverflow, and Bing and Google.

When you eventually need help and post a question online somewhere, do it with others in mind: those who will be taking their own time to help you.

 

Ask the question first, then give the explanation.  If you've got a long-winded explanation behind your question, it's better to put it after the question.  That way the reader can scan the question, assess whether they're interested, know enough about the topic to help you, and if so, want to spend the time to read the story behind it.

 

Be respectful and courteous.  Someone else is using their valuable time to read your question and (hopefully) responding to it.  More often than not the first answer is less than helpful.  That's why there are multiple answers from multiple people with multiple backgrounds and areas of expertise.  You won't get very far with the community by being disrespectful to others.

  • Your work is automatically sandboxed to the virtual machine you're working in
  • You can experiment all you want
    • If you use VirtualBox you can take snapshots and easily revert if something breaks
    • If you run a Windows host, here is a short script to automatically take a snapshot before a virtual machine launches for use
  • Don't mess around with your host environment; only install what's absolutely necessary there

  • Make frequent backups.
    • Several times a day if you can afford to (in terms of space and computer power)
    • Here is what I do for backup
  • If and when you do experiment, record your experiments, observations, and results, and share them with others if you like
    • It's the best way to learn something for yourself, and it's also nice to share something you've spent hours on that could save another developer hours of time.  (Think about all the hours you've saved because of other's blog posts on problems you've struggled with.)

Computing

  • Get as much RAM as your system can take and/or you can afford
  • Source control is a wonderful thing, even if sometimes I wish it were easier to use
  • As much as I dislike to say it, the command line is the fastest way
    • I dislike to say it because the whole point of GUIs is so you don't have to remember many commands
    • Every developer has to strike his own balance between command line and visual interfaces

Client relations

There will be times when things won't work out with a client.  You won't agree with their demands, or they won't agree to your methods.  In these cases there is an incident I like to remember from college.  On returning a paper he graded back to us, the professor said something to the effect:

Before I return these papers I'd like to point out that this grade is not a judgment of your writing ability or your talent in any way.  It's simply an assessment of this particular paper.

 

The point is, just because things don't work out with one (or two, or three) clients doesn't mean you're not good at what you do.  Now if you're not good with three out of three clients, then it's a problem.  But if it's three out of fifteen or more clients, just take it as a matter of chance that things didn't work out.  Don't chastize yourself or doubt your ability, just move on.



 
VirtualBox script: automatic snapshot E-mail
Thursday, 16 June 2011 20:52

Update

The original code (below) fails if the current hour is less than 2 digits long, i.e., earlier than 10 AM.  This is the fix:

cd "C:\Program Files\Oracle\VirtualBox
set current_hour=%time:~0,2%
if %current_hour% lss 10 (set current_hour=0%time:~1,1%)
set backup_detailed=%date:~10,4%_%date:~7,2%_%date:~4,2%__%current_hour%_%time:~3,2%_%time:~6,2%
VBoxManage snapshot "<virtual machine name>" take %backup_detailed%
VboxManage startvm "<virtual machine name>"

 


 

VirtualBox is great for experimenting, specifically with its snapshot feature that lets you easily revert to prior states.  I like to experiment frequently, and often my experiments break the virtual machine.  For moments like that I wish I had a script that automatically made a snapshot of a VM before it launches.

 

For my Windows 7 host machine, placing the following code in a bat (batch) file does the trick:

cd "C:\Program Files\Oracle\VirtualBox
set backup_detailed=%date:~10,4%_%date:~7,2%_%date:~4,2%__%time:~0,2%_%time:~3,2%_%time:~6,2%
VBoxManage snapshot "<virtual machine name>" take %backup_detailed%
VboxManage startvm "<virtual machine name>"

This will create a virtual machine titled with a timestamp, such as 2011_16_06__18_46_50, and then launch the VM.

 

Sources

Adding timestamps in a DOS batch file

Using timestamps in batch files

Automatically create snapshot before launching Virtualbox VM?



 
JavaScript: small details E-mail
Saturday, 11 June 2011 21:29

I was thinking of alternative ways to define a custom class in JavaScript, and came up with two implementations.  I would like to share what I came up with, to demonstrate the flexibility of JavaScript as well as to highlight a couple different ways of accomplishing the same purpose.

Exhibits

Introduction

In what I've called Design 1 and Design 2, there is a different architecture of the Scanner class.  Both implementations return the same result, with the same structure (shown below).  Also in Design 2 I made several small improvements over Design 1 in the code that serves the same purpose between the two designs but is implemented differently in each.

 

The Scanner class:

  1. Has a single point of entry to start a scanning process, which
  2. Scans a web page (the DOM) for categories of posts, and
  3. For each category, it scans and retrieves the following properties of each post and saves it to a Post object:
    • Title
    • Image
    • URL
    • Rating
  4. Several objects, one for each category scanned, are returned with the properties:
    • Name of category
    • Array of Post objects
  5. There is a special category called "Recent edits", which is scanned separately
    • The posts in this category have another property, "Last edited", which is stored in a wrapper along with  the Post

The returned objects look like:

Scanner class return object

Scanner class code

Here is the actual code for the alternative class designs.  You may want to copy the code elsewhere to view the two versions side by side.

Discussion

Architecture

I'll admit that the design difference between the two models is slight, but it's an important one.  It can be summed up in one statement: In Design 1, the methods perform at the class-level, whereas in Design 2, all methods are declared on the prototype and perform on instances of the Scanner class.

 

Despite the difference being a small one, for clarity's sake I decided to map out the two designs in mind maps, shown below.  (Unfortunately you'll need to manually expand the nodes to see them.)  I asked around to see which is better, and for the scope of this small and simplified problem the answer is "it doesn't really matter."  However, if the problem were a larger, more realistic one, Design 2 seems more suitable.

 

Design 1

 

 

Design 2

 

Optimizations

This is the more interesting part, for me at least.  It's easy to forget about the small optimizations you can make in your code when you're just trying to get it to work in the first place.

 

I'm going to step through the various sections of the code in both designs, and highlight what's better in Design 2.

Design 1

//Start Scanning process
Scanner.startScanning = function() {
  Scanner.getCategories();
   $(categories).each(function() {
     Scanner.getPostsByCategory(this);
   });
   Scanner.getRecentPosts();
}

Design 2

Scanner.fn.startScanning = function() {
   var categories = this.getCategories();
   var context = this;
   for (var i = 0, j = categories.length; i < j; i++) {
     this.getPostsWithCategory($(categories[i]));
  }
  this.getRecentPosts();
 
};

  • $.each is an expensive process, so Design 2 uses a simple for loop instead

Design 1

//Return jQuery object of all post categories
Scanner.getCategories = function() {
  categories = $(// some jQuery selector);
}

Design 2

Scanner.fn.getCategories = function() {
   return $(// some jQuery selector);
}

  • Declaring a variable without the var keyword (like categories above in Design 1) makes it global, which is never a good idea unless you specifically need a global variable
  • While both designs do basically the same thing here, Design 2 adheres to functional programming principles, in that it doesn't have any side effects

Design 1

Scanner.getPostsByCategory = function(category) {
  var scanned = new Scanner();
  scanned.category = Scanner.getCategoryName(category);
  $(category).find(// some jQuery selector).each(function() {
    var post = Scanner.createPost(this);
    scanned.posts.push(post);
  });
  return scanned;
}

Design 2

Scanner.fn.getPostsWithCategory = function(jSingleCategory) {
  var context = this;
  context.category = context.getCategoryName(jSingleCategory);
  jSingleCategory.find(// some jQuery selector).each(function() {
    var post = context.createPost(this);
    context.posts.push(post);
  });
}

  • jQuery ($) lookups are expensive as well, so instead of using $(category) in multiple places like in Design 1, in Design 2 we instead pass $(categories)[i] above in startScanning() so that it gets cached, and can be quickly reused as jSingleCategory

Design 1

//Gather post data - title, URL, image, rating
Scanner.createPost = function(post) {
  var title = Scanner.getTitle(post);
  var image = Scanner.getImage(post);
  var url = Scanner.getUrl(post);
  var rating = Scanner.getRating(post);
  var post = new Post(title, image, url, rating);
  return post;
}
 
 
 
/*********************
Post class
*********************/
 
//Post constructor
var Post = function(title, image, url, rating) {
  this.title = title;
  if (image != null) {
    this.image = image;
  }
  if (url != null) {
    this.url = url;
  }
  if (rating != null) {
    this.rating = rating;
  }
}

Design 2

Scanner.fn.createPost = function(post) {
  var postObj = {};
  var jPost = $(post);
  postObj.title = this.getTitle(jPost);
  postObj.image = this.getImage(jPost);
  postObj.url = this.getUrl(jPost);
  postObj.rating = this.getRating(jPost);
  return post;
}

  • Since the Post object is doing nothing except storing a Post, it is overkill to declare a class for it.  As Design 2 does above, it's enough to just create a Post object literal in the Scanner class.



 
«StartPrev12345678910NextEnd»

Page 2 of 18