|
Jul, 15
2010
|
I’m moving to Tumblr [log.gmtaz.com] |
Read |
|
Jul, 15
2010
|
I’m moving to Tumblr [log.gmtaz.com] |
Read |
Actually, I’m just going to be primarily using tumblr (not moving to it, per say). www.gmtaz.com will still be up and running on wordpress with a focus on technical documentation, while my personal blog will be at log.gmtaz.com
Just a quick FYI.
****EDIT****
Actually, now that I think of it – I might move www.gmtaz.com over to tech.gmtaz.com and log.gmtaz.com over to www.gmtaz.com. I feel as though tech.gmtaz.com is going to go by the way side as I don’t really have that much time to put into writing out longer posts. We will see…
|
Jun, 2
2010
|
Quick Fix: xFBML not rendering in IE [RESOLVED]! |
Read |
I was having a problem where using xFBML and the facebook javascript API would not render elements in IE (but they worked in all other browsers).
This was resolved by fixing the DOCTYPE in the header.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:fb="http://www.facebook.com/2008/fbml">
Now everything renders fine within the site AND it also renders in an iFrame Facebook App!
|
Apr, 29
2010
|
Show Popular Stories Using Google Analytics and ASP.net C# and Sort the Results [How To] |
Read |
Recently, I needed to be able to get a list of popular pages for a site that I manage and display that information. I figured, rather than building a database and code hit tracking and page views from scratch, I would just tap into my Google Analytics data.
Requirements:
All of the articles have the path format “/articles/[ category ]/[ article-title ]/” and all articles have unique titles. First stop, create the article class:
class ArticleInfo
{
public string title { get; set; }
public string path { get; set; }
public string hits { get; set; }
}
This is what I will use to sort by title after sorting by hits. Granted, this could probably be optimized a bit more but I’m really tired…
Basically, this creates a class of titles, paths, and hits which I will be pulling from later to create the HTML.
Here is the rest of the code-behind for the user control which will display the data. I will go through this piece by piece
protected void Page_Load(object sender, EventArgs e)
{
try
{
clsDBGet db = new clsDBGet();
DataTable dt = db.GetDTfromProc("[STORED PROCEDURE - GET]", null);//Stored procedure to pull from the DB
if (dt.Rows.Count > 0)
ga.Text = dt.Rows[0]["DATA"].ToString();
else
{
try
{
ReportRequestor rr = new ReportRequestor("[gmail account email]", "[password]");
AnalyticsAccountInfo ainfo = new AnalyticsAccountInfo();
IEnumerable(AnalyticsAccountInfo) accounts = rr.GetAccounts();
string analyticsTitle = "V2 Live";//Title of Profile where I want to grab the data
AnalyticsAccountInfo account = accounts.First(a => a.Title == analyticsTitle);
DateTime from = DateTime.Now.AddDays(-2);//last 24 hours (data is always a day behind)
DateTime to = DateTime.Now;
IEnumerable(GenericEntry) report = rr.RequestReport(account, new Dimension[] { Dimension.pagePath, Dimension.pageTitle }, new Metric[] { Metric.pageviews }, from, to, 1000);
string myString = string.Empty;
int x = 0;
string[] articleTitles = new string[22]; //To only grab unique titles
ArticleInfo[] articleData = new ArticleInfo[22];
bool unique = true;
report = report.OrderByDescending(myReport => Convert.ToInt32(myReport.Metrics.First().Value));
foreach (GenericEntry myReport in report)
{
Regex isArticle = new Regex("/articles/.+/.+/");
string path = myReport.Dimensions.First().Value;
string title = myReport.Dimensions.Last().Value;
string hits = myReport.Metrics.First().Value;
if (x < 22 && isArticle.IsMatch(path))
{
if (!path.Contains("/page_") && !path.Contains("/search/") && title.Trim() != "(not set)")
{
//Add to array of article titles then check to see if it exists in the array
for (int y = 0; y < articleTitles.Length; y++)
{
if (articleTitles[y] != null && title.Trim().ToLower() == articleTitles[y].ToLower())
{
unique = false;
break;
}
else
unique = true;
}
if (unique)
{
articleData[x] = new ArticleInfo { path = Server.UrlDecode(path).Trim(), title = title.Trim(), hits = hits };
x++;
}
}
}
}
IEnumerable(ArticleInfo) articles = articleData.OrderBy(article => article.title);
foreach (ArticleInfo article in articles)
{
myString += "<li><a class=\"gaHits\" href=\"" + article.path + "\" title=\"" + article.title + "\" data=\"" + article.hits + "\"></a ></li>";
}
if (x < 10)
{
panelGA.Visible = false;
}
else
{
clsDBPost dbp = new clsDBPost();
string proc = "[STORED PROCEDURE - INSERT]";
Hashtable ht = new Hashtable();
ht.Add("data", myString);
bool inserted = dbp.ExecuteProcedure(proc, ht);
}
ga.Text = myString;
}
catch (Exception EX)
{
//Response.Write(EX.ToString());
panelGA.Visible = false;
}
}
}
catch (Exception ex2)
{
//Response.Write(ex2.ToString());
panelGA.Visible = false;
}
}
Let's break this up into parts.
The first thing I want to do is query my database to see if I already have the latest Google Analytics data for today. If it's there, render the data outright. This allows me to only query Google Analytics once per day, instead of on every page load. This is important because Google limits the amount of requests you can make per day and because we are requesting data for the previous day, there's no need to pull up to the minute results.
clsDBGet db = new clsDBGet();
DataTable dt = db.GetDTfromProc("[STORED PROCEDURE - GET]", null);
if (dt.Rows.Count > 0)
ga.Text = dt.Rows[0]["DATA"].ToString();
This sis where we tap into Reimer's reader. Further explanation can be found in the link at the top of this article but for now let's focus on some key points. I'm pulling data from Google from 2 days ago, through today. I'm also pulling the pagePath, pageTitle and using the metric pageViews. This will translate into the URL of the post, the title of the post and the hits. The last thing to note is that I'm pulling the top 1000 results because not every page on the site is an article and I want to make sure I get at least 10 articles in the data I'm pulling back. This number is arbitrary but I think it defaults at 1000 anyway.
ReportRequestor rr = new ReportRequestor("[gmail account email]", "[password]");
AnalyticsAccountInfo ainfo = new AnalyticsAccountInfo();
IEnumerable(AnalyticsAccountInfo) accounts = rr.GetAccounts();
string analyticsTitle = "V2 Live";//Title of Profile where I want to grab the data
AnalyticsAccountInfo account = accounts.First(a => a.Title == analyticsTitle);
DateTime from = DateTime.Now.AddDays(-2);//last 24 hours (data is always a day behind)
DateTime to = DateTime.Now;
IEnumerable(GenericEntry) report = rr.RequestReport(account, new Dimension[] { Dimension.pagePath, Dimension.pageTitle }, new Metric[] { Metric.pageviews }, from, to, 1000);
In this next part I'm setting my variables that I will be using later in the loops to filter the rows.
string myString = string.Empty; int x = 0; string[] articleTitles = new string[22]; //To only grab unique titles ArticleInfo[] articleData = new ArticleInfo[22]; bool unique = true; report = report.OrderByDescending(myReport => Convert.ToInt32(myReport.Metrics.First().Value));
I'm also creating an array of ArticleInfo's called articleData which will hold 22 elements as I only want to display the top 22 articles. Lastly, I'm ordering the report data by hits (pageViews) because the report data comes from Google sorted by URL path. This is bad because I want to get the top articles, not a list ordered alphabetically by URL. Without this sort, I would be getting articles in a certain category only (because the URL lists the category before the title) and it would not be a true representation of site activity.
Here come the loops:
foreach (GenericEntry myReport in report)
{
Regex isArticle = new Regex("/articles/.+/.+/");
string path = myReport.Dimensions.First().Value;
string title = myReport.Dimensions.Last().Value;
string hits = myReport.Metrics.First().Value;
if (x < 22 && isArticle.IsMatch(path))
{
if (!path.Contains("/page_") && !path.Contains("/search/") && title.Trim() != "(not set)")
{
//Add to array of article titles then check to see if it exists in the array
for (int y = 0; y < articleTitles.Length; y++)
{
if (articleTitles[y] != null && title.Trim().ToLower() == articleTitles[y].ToLower())
{
unique = false;
break;
}
else
unique = true;
}
if (unique)
{
articleTitles[x] = title.Trim();
articleData[x] = new ArticleInfo { path = Server.UrlDecode(path).Trim(), title = title.Trim(), hits = hits };
x++;
}
}
}
}
Lots of stuff is going on here. The first thing I'm doing is creating a filter to filter through the results and only pull back pages that are articles by doing "isArticle.IsMatch(path)". I also don't want to do this loop more than I need to, hence the "x < 22" part. The next "IF" statement is arbitrary and required for the site.
The next FOR LOOP is to make sure I'm only grabbing unique articles. This is important because sometimes users include capital letters in the URL and this will make Google Analytics display multiple rows for the same page and split up the numbers. There's a way to counter this in GA but for today's purposes, I'm assuming that your results might have the same page split across different URLS. I take the title of the article and check to see if it's in the articleTitles array. If it isn't already in there, I add it to the array and set the "unique" flag to true and move on. If it is, I skip that row altogether by setting the "unique" flag to false. (This method will probably produce some skewed results if you are splitting page views across URLs).
In this last part, I'm creating another enumerable list of articleInfo's and sorting it by title. This way the bar graph that I display won't just look like a linearly decreasing graph. I also create the HTML list item and append it to the full list of items in myString. This is what will be rendered out on the page and also what will be placed in the database using the stored procedure, "V2_INSERT_GOOGLE_DATA".
If there are less than 10 results, or if for some reason I cannot communicate with either Google or the database, I hide the panel altogether.
IEnumerable(ArticleInfo) articles = articleData.OrderBy(article => article.title);
foreach (ArticleInfo article in articles)
{
myString += "<li><a class=\"gaHits\" href=\"" + article.path + "\" title=\"" + article.title + "\" data=\"" + article.hits + "\"></a ></li>";
}
if (x < 10)
{
panelGA.Visible = false;
}
else
{
clsDBPost dbp = new clsDBPost();
string proc = "[STORED PROCEDURE - INSERT]";
Hashtable ht = new Hashtable();
ht.Add("data", myString);
bool inserted = dbp.ExecuteProcedure(proc, ht);
}
ga.Text = myString;
I hope this helps some of you who are trying to implement this method of pulling data from Google. I used jQuery to have the list animate as a bar graph with the article titles appearing on hover. This is the final result:
|
Apr, 23
2010
|
Protect Images Online using PHP and jQuery [How To] |
Read |
Ever go to a website and think to yourself, “Wow, those are some nice photos. I wonder if I can download them and [insert personal use line here]“?
The issue of protecting images online is quite a challenge, and borderline impossible. Using Flash is a good way to do it, but now you’ve just limited your audience. Even with Flash, it’s not 100% secure. In safari, users can go to the activity window and see what resources are being downloaded and from where. Other more advanced users often use Firebug or something of the like to grab the actual URL of the image and subsequently access that file directly.
Ultimately, users can always just take a screen shot of the page, but this will also include any of the text that is above the image (unless they use Firebug to hide it). You can’t stop that without the user agreeing to download a 3rd party plugin, so might as well deal with that fact.
In lieu of all this, I still wanted to use Pablo Yanez’s images as wallpapers for my new site design. He wanted them protected. I completely agree… and out of that need, came this project.
Theory behind the process: (I’m not going to go into too much detail for, hopefully, obvious reasons)
Here’s the trick: The encoder.php file gets the initial “Build me the URL of where the image is” request. This URL has a time stamp generated and encoded in it. Make sure the time stamp that is being generated adds or subtracts a large number of time, in this example I will say 12304 seconds get added. This part is key because if users figure out that the encoded parameter is a base_64 encoded time stamp, they can write a script to generate one too, totally defeating the purpose of the time stamp. That’s the first part of the encoder. The second part is to read the directory where the images are and, in my case, put the entire list into an array and pick a random number that is no less than 0 and no larger than the length of the array -1. This is important because in image.php, we will be doing the same lookup in the directory, except this time, we are using that random number to select the file name of the file we want to display. Also, image.php also creates a time stamp (important) and adds 12304 seconds to it. If image.php’s time stamp is within, say, 5 seconds of the encoded.php timestamp, the script continues. Otherwise, the script dies and in my case, renders out “clear.gif” to confuse people.
*Important note at this point: Using the time stamp technique disables caching of the images, but because we don’t want users to retain a copy at all, that’s fine.
This is my encoder.php file (secure parts omitted):
function getRan()
{
$dir_array = array();
if ($handle = opendir('Secure images directory, relative to site root')) {
while (false !== ($file = readdir($handle))) {
if($file!="." && $file!=".."){
$dir_array[] = $file;
}
}
closedir($handle);
}
return rand(0,count($dir_array)-1);
}
function getRanAJAX()
{
$dir_array = array();
if ($handle = opendir('Secure images directory, relative this')) {
while (false !== ($file = readdir($handle))) {
if($file!="." && $file!=".."){
$dir_array[] = $file;
}
}
closedir($handle);
}
return rand(0,count($dir_array)-1);
}
function getBGParams()
{
$r = getRan();
$t = time();//use math to change time - add or subtract in seconds
$tenc = base64_encode($t);
$tdir = bloginfo('template_directory'); //wordpress
$style= $tdir."/image.php?u=".$tenc."&p=".$r;
echo $style;
}
function getU()
{
$ref = $_SERVER['HTTP_REFERER'];
//make sure requests are coming from me
$pattern = '/^(http:\/\/www.gmtaz.com)|(http:\/\/gmtaz.com)/';
if(preg_match($pattern,$ref)){
$r = getRanAJAX();
$t = time();//use math to change time - add or subtract in seconds
$tenc = base64_encode($t);
echo '{"u":"'.$tenc.'", "d" : "' . $tdir . '/image.php", "r" : "'.$r.'", "ref" : "'.$ref.'"}';
}
}
function getR()
{
$r = rand(0, 1);
echo '{"r":"'.$r.'"}';
}
$op = $_GET['o'];
if($op=='u')
getU();
if($op == 'r')
{
getR();
}
Hopefully that all makes sense. The first AJAX request using jQuery looks like this:
< ?php include 'encoder.php' ?>
$(function(){
gmtaz.setBG('< ?php getBGParams() ?>');
setInterval(function(){
var u = '';
var url = '';
var r = '';
var dt = new Date();
var time = dt.getTime();
//for wordpress
var td = "< ?php bloginfo('template_directory'); ?>/encoder.php";
$.getJSON(td,
{o:'u', t: time},
function(d){
u = d.u;
url = d.d;
r = d.r;
var dd = '' + url + '?u='+ u +'&p=' + r;
//alert(dd +''+ '');
gmtaz.setBG(dd);
});
}, 12000);
});
setBG(dd) is actually this function:
setBG : function(url){
var url = url + gmtaz.getResolution();
$('< img />')
.attr('src', url)
.load(function(){
var src = $(this).attr('src');
$('#bg').css({
'backgroundImage':'url('+src+')',
'backgroundRepeat':'no-repeat',
'backgroundAttachment':'fixed'})
.fadeIn('slow',function(){
$('body').css({
'backgroundImage':'url('+src+')',
'backgroundRepeat':'no-repeat',
'backgroundAttachment':'fixed'
});
$(this).fadeOut('fast');
});
});
},
getResolution : function(){
return '&w='+screen.width+"&h="+screen.height;
},
All this is doing is using some jQuery to grab that image URL which is being passed in and set it as the background. I actually have two bg layers to allow for the fade effect.
Here’s image.php:
< ?php
$dir_array = array();
if ($handle = opendir('Secure Images Directory')) {
while (false !== ($file = readdir($handle))) {
if($file!="." && $file!=".."){
$dir_array[] = $file;
}
}
closedir($handle);
}
$images = $dir_array;
$t = time();//same mathematical algorithm as in encoder.php
$tdec = base64_decode($_GET['u']);
$diffT = $t-$tdec;
if($diffT > 5 || $diffT < 0)//if older than 5 seconds - die
{
die('clear.gif');
}
//Disable caching
header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Content-type: image/jpeg");
while (@ob_end_clean());
$PATH = urldecode($_GET['p']);
$File = 'secret directoy'.$images[$PATH];
if (($FileInfos= stat($File)) or die("File not found!"));
$FileExt= substr($File, -3);
$FileTypeMIME= array("jpg" => "image/jpeg",
"png" => "image/png",
"gif" => "image/gif",
"ico" => "image/x-icon");
$ContentType= $FileTypeMIME[$FileExt];
if (empty($ContentType)) die("You are not allowed to access this file!");
$h = $_GET['h'];
$w = $_GET['w'];
$im = @imagecreatefromjpeg($File);
$width = 1920;
$height = 1200;
$NewThumb= ImageCreateTrueColor($w,$h);
// check if ratios match
$_ratio=array($width/$height,$w/$h);
if ($_ratio[0] != $_ratio[1]) { // crop image
// find the right scale to use
$_scale=min((float)($width/$w),(float)($height/$h));
// coords to crop
$cropX=(float)($width-($_scale*$w));
$cropY=(float)($height-($_scale*$h));
// cropped image size
$cropW=(float)($width-$cropX);
$cropH=(float)($height-$cropY);
$crop = ImageCreateTrueColor($cropW,$cropH);
// crop the middle part of the image to fit proportions
ImageCopy(
$crop,
$im,
0,
0,
(int)($cropX/2),
(int)($cropY/2),
$cropW,
$cropH
);
}
// do the thumbnail
if (isset($crop)) { // been cropped
ImageCopyResampled(
$NewThumb,
$crop,
0,
0,
0,
0,
$w,
$h,
$cropW,
$cropH
);
ImageDestroy($crop);
} else { // ratio match, regular resize
ImageCopyResampled(
$NewThumb,
$im,
0,
0,
0,
0,
$w,
$h,
$width,
$height
);
}
$q=60;//final output quality
ImageJpeg($NewThumb,null,$q);
ImageDestroy($NewThumb);
ImageDestroy($im);
?>
You may notice that I’m doing some cropping here. Actually what I’m doing is sizing the image to the full resolution of the visitor. I crop it to fit the dimension ratio first, then I resize it. This should make the image always look the same no matter what computer you’re on. Handy for dynamically creating wallpapers as well.. I might expand on this and include that function in my wallpaper gallery.
Let me know if anyone knows a way around this. I’m interested in tightening this up!
|
Jun, 30
2009
|
Dude, I want to punch IE in the face |
Read |
Ok, seriously MS, enough already!
I was working on a login form. Finally got everything aligned the way I wanted it with floats and all that. It looked great in IE and FF.
This is what it looked like:

Then I added a hidden form element…let me repeat: HIDDEN form element. HIDDEN. In case you don’t get the point I’m trying to make, the element was HIDDEN. This happened:

Moving the hiddent element to the end of the form fixed the issue… but I mean, really. Is that necessary??? It’s …wait for it….HIDDEN!
|
Jun, 17
2009
|
Microsoft's stupid old browser, IE, campaign… |
Read |
So MS has started a campaign to get users to download IE with the lure of a 10k reward in the form of an online treasure hunt. Ok fine… but what gets me (and a LOT of other people) up in arms is the fact that MS is insulting the other browsers in the process! Not to mention that the site looks like an old strip club in Nevada or somewhere….
Here are a couple of screenshots-
And here’s a screenshot viewing the site using IE8??? No compatability mode icon in the bar. Weird, right??
Somehow, none of this really shocks me.
|
Jun, 15
2009
|
HUGE IE8 textbox CSS bug |
Read |
Quick note: For those of you running IE8 (get firefox) and are wondering why you can’t click on your textboxes <input type=”text”>, the answer is the styling. IE8 seems to have a bug where if the background is set to transparent, the only clickable area is the border (if it’s visible). Setting the background of the textbox to a color fixes this problem.
How did MS miss this one??
–EDIT–
After trying to duplicate this issue (see comments), it looks like it was something specific to the site in question. Unfortunately, I can’t say which site it was, but if you do some digging around, I’m sure you can figure it out.
Something that I did notice (Seems to be IE specific), is when the background of a textbox (<input type=”text”>) is set to transparent, the cursor does not change to a text-input cursor unless the user hovers over the border. How ’bout THEM apples??
|
May, 1
2009
|
jQuery 1.3 cheat sheet wallpaper |
Read |
I went ahead and created a cheatsheet wallpaper for jQuery 1.3 using screenshots from http://oscarotero.com/jquery/
EDIT: One person commented about not being able to set up a different wallpaper for each space on OSX. Check out Spacesuit. This looks like a pretty good solution. Let me know if this helps out!
Click on a link below to download:

|
Apr, 16
2009
|
Quickly Trim Text with Regular Expressions and jQuery |
Read |
I ran into an issue ( a while back but finally had time to address it ) where we had lines of text that we could trim down to fit an HTML container. If the text was longer that 37 characters, we stripped off the end and replaced it with ‘…’. The problem with doing this strictly on a character count basis was that the break would usually be mid-word and with some words like, assign, that would could be bad.
A simple way to fix address this is to use regular expressions. Specifically, this regular expression: ^(.){1,37}\b
My solution was to use javascript and css to replace the text on the fly. Applying the ‘white-space:nowrap’ style to the text element allows me to keep everything on one line. Overflow:hidden hides the text that would flow out of the block. This helps me keep a minimum amount of text visible until the javascript function applies the fix. Also, I’m using in-line javascript (big no-no) but it does the job fast.
This is the script:
function trimText(element)
{
var list = $(element);
var regToShow = new RegExp(/^(.){1,37}\b/);
list.each(function(){
var toShow = $(this).text().match(regToShow);
$(this).text(toShow[0]+'...');
});
}
trimText(Selector);
Basically, what I’m doing here is applying the regular expression to the called-in-class’ text. If you know about regular expressions or want to know more about them, check out RegExr. It’s an online and downloadable (Adobe Air) tool for regular expressions testing and design. You can also put in a conditional statement if you don’t to add the ‘…’ at the end unless you are actually trimming the line.
if($(this).text()!=toShow[0])
$(this).text(toShow[0]+'...');
This just checks to see if the final text is the same as the original text (i.e. no trimming) and if so, do not add ‘…’.
It’s quick and dirty but it works. It would work better if you apply the Regular Expression in the code BEFORE the text gets rendered, but sometimes, that’s not a viable option.
|
Mar, 10
2009
|
Really simple trick to return the correct information when making AJAX requests |
Read |
I’ve been doing a lot of AJAX work recently and noticed that sometimes, when doing AJAX calls on key up or key down, the results returned don’t always reflect the query. This is the case when there are multiple AJAX requests occuring at the same time. If they complete out of order, the updated results can often be confusing.
For example:
I have the following JSON dataset:
{ “name” : “smatt” , “name” : “smoke”, “name” : “smith” , “name” : “smith2″ …etc }
This data is stored off-site and has a lag time when getting returned ( This, most likely, would not be the case with a small predefined dataset like the one above, but would very possibly happen with database queries ). As I type ‘s’, a query is sent out (‘s’); ‘m’ adds another query (‘sm’); ‘i’ adds another (‘smi’)…etc.
If my ‘smi’ query is returned before my ‘s’ query, then my results for ‘smi’ will be overwritten by my results for ‘s’, returning “smatt” “smoke” “smith”… when what I should be getting back is a list of all the “smith”s. A simple way to check this is to add an additional “if” statement on success. Example:
function query(input_string_from_field)
{
var url = "getdata.php?q="+input_string_from_field;
$.getJSON(url, function(data){
//This runs the check to make sure you are returning
//the correct data
if(input_string_from_field == $('#input_field_ID').val())
{
//display results
}
});
}
By adding:
if(input_string_from_field == $('#input_field_ID').val())
we check to make sure that the last thing that the user typed is the query for the data that is being returned. If it isn’t, the data isn’t displayed. If it is, show the data.