tag:blogger.com,1999:blog-15245566308935846562024-03-14T02:15:42.955-07:00Robert Waddell on Web Development, and moreTopics covering web development, analytics, HTML, CSS, Javascript, jQuery, ColdFusion and more.Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.comBlogger61125tag:blogger.com,1999:blog-1524556630893584656.post-91933250183964372352018-02-07T11:26:00.000-08:002018-02-07T11:26:17.024-08:00Detecting Cordova Webview Visibility Changes (e.g. detecting when an AdMob ad is running on top of the webview)I've not posted in awhile, but recently worked through a challenge that I think needs to be recorded for future reference.<br />
<br />
Running a <a href="https://github.com/floatinghotpot/cordova-admob-pro" target="_blank">Cordova AdMob interstitial ad</a> on top of a Cordova webview that already has an AdMob banner ad (bottom positioned) is breaking the viewport height calculation in iOS (except iPhone X) for me once the interstitial is closed. In order to address this, the banner ad needs to be removed when the interstitial is shown (easy with showInterstitial callback), but the banner ad needs to be added back once the interstitial is closed (hard without any callback).<br />
<br />
Sorting through lots of ideas online, all of which were hacks or not quite what I need (pause and resume cordova events don't fire in this case), I was left trying to find anything about the DOM that changed when the interstitial was on top. Traditional element visibility checks yielded the same results regardless of whether the interstitial was open or closed. What to do?<br />
<br />
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API" target="_blank">HTML5 Page Visibility API</a> to the rescue! Listening for 'visibilitychange' and then checking document.hidden is exactly what I was looking for.<br />
<br />
<code>
document.addEventListener('visibilitychange',function(){<br />
if (document.hidden) {<br />
// remove the ad<br />
} else {<br />
// show the ad<br />
}<br />
},false);
</code>
<br />
<br />
While this may seem simple, it is not insignificant. While Cordova handles pause and resume states, when the app has a plugin sitting on top of the webview (oAuth SDKs, ads, etc), we sometimes need the webview to be aware of that and the page visibility API gives us that ability.Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-41567916304847620232017-06-04T15:29:00.001-07:002017-06-04T15:29:44.630-07:00How-To Tail (ColdFusion) Log Files in Windows Command Line via PowerShell and .bat ScriptSetting up a new environment, I wanted to tail my coldfusion log files. Usually this is simple in the CFEclipse or CFBuilder log window. Unfortunately, that doesn't work in my new environment (beside the point). So I needed a different solution.... PowerShell to the rescue! It turns out it is super simple to tail files in PowerShell via a command like <code>$ Get-Content coldfusion-out.log -wait</code>. Awesome! But when I save this to a .ps1 file and double-click, it just opens my text editor. How can I double click like a good old .bat file?... well make a a .bat file named the exact same with contents like this:<br />
<code>@ECHO OFF<br />
PowerShell.exe -executionpolicy remotesigned -File %~dpn0.ps1<br />
PAUSE</code><br />
<br />Now I can double-click my .bat and launch a regular old command window that tails my log file using PowerShell.<br />
<br />
In the end, the files look like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsajqqxYKZhcFh_exgOr4Z1NHelEO4V94DFo9VYs6zwJ4Y7RjeNdjeUnbMOdM80wrJD8WEEBknHDu74sHXEqtwGIOA6-2l1KYEyRfJsn_P50IyceaKxKXjZPEjm_prZbFl052QQxfVSPH6/s1600/tailing-files.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="570" data-original-width="943" height="385" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsajqqxYKZhcFh_exgOr4Z1NHelEO4V94DFo9VYs6zwJ4Y7RjeNdjeUnbMOdM80wrJD8WEEBknHDu74sHXEqtwGIOA6-2l1KYEyRfJsn_P50IyceaKxKXjZPEjm_prZbFl052QQxfVSPH6/s640/tailing-files.PNG" width="640" /></a></div>
<br />
And the .bat file double-click to command window looks like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVOB5XgMWhMBt1wsY3oWss5FN1ay8SMQ7Nt6CT9T5SF5bE1ThbpN5VL5EAaBb3ch9nvYM_EkrLrYWBjw-UvElJsynbOpvW3xs9pFkfVWOb2Pfy2tp3oj_CRdJclsniwBcS0ZcqJj6ah03o/s1600/tailing-bat-cmd.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="495" data-original-width="1158" height="273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVOB5XgMWhMBt1wsY3oWss5FN1ay8SMQ7Nt6CT9T5SF5bE1ThbpN5VL5EAaBb3ch9nvYM_EkrLrYWBjw-UvElJsynbOpvW3xs9pFkfVWOb2Pfy2tp3oj_CRdJclsniwBcS0ZcqJj6ah03o/s640/tailing-bat-cmd.PNG" width="640" /></a></div>
<br />
Rinse and repeat with different log file names for any other log files you want to tail. Of course, this has nothing specifically to do with ColdFusion, but that's what I use it for :-)<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-34620229807076670532017-03-23T13:21:00.001-07:002017-03-23T13:21:42.769-07:00Samsung Gear S2 Won't Turn On - How I fixed itI was a reluctant buyer of a smartwatch. I got the Gear S2 refurbished a few months ago and I ended up really liking it. Unfortunately, I had one charger die and had to replace it. Otherwise, it has been all good.... until yesterday. I grabbed my watch off the charger and it was dead. It wouldn't even turn on. The charger indicator LED showed blue as if it were charging. I went watch-less for the day. Upon returning home, I tried the following steps which brought my Gear S2 back to life:<br />
<br />
<ol>
<li>Remove any protective backing from the watch (I had a protector skin on it)</li>
<li>Plug the charger into a computer USB (low voltage) port</li>
<li>Place the watch on the charger</li>
<li>Wait 2-3 minutes</li>
<li>The watchface should light up and show that it is charging</li>
<li>Let it fully charge (will take awhile)</li>
<li>Take it off charger and power on</li>
<li>Back to normal! Charge with regular power adapter again as needed</li>
</ol>
<div>
Essentially, my watch refused to charge on the regular power adapter. It would only "trickle-charge" on the lower voltage power source. I think it was in some sort of protective anti-overheating mode as it had been very hot when I pulled it off the charger when it wouldn't power on. The low voltage charge must have been "allowed" by the software whereas the higher voltage charging was being restricted. Just my theory...</div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-16993547395838170562017-03-01T11:50:00.002-08:002017-03-01T12:22:36.633-08:00Prevent Ionic Header from Auto-Scrolling Content to Top when Header is TappedAn interesting piece of functionality in Ionic that only became aware of today is "tap-scroll" which auto-scrolls to the top of the screen when the header/title of the view is tapped. This only seems to work in iOS and caused some strange behavior in a view that had some custom scrolling related javascript code. This led me to look for ways to disable this cleanly. It turns out there is an attribute available on ion-header-bar called "no-tap-scroll" that can disable this behavior when set to "true".<br />
<br />
Take a look at ionic.bundle.js and you'll see this defined for ion-header-bar.<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZfP5_rLIRUHSmBE0K1L-v_86-U8UrbXLl6RuwLga5v0Cp6m4Ppp1rFWGffqUqdGmH4q5wM9AOJc9p-zHnEyGpoa2PRNpTPMNI3xjPDcKbd9R32azy1-Qn-KoK1l9b1WAVfDdT64UDyRpB/s1600/Capture.PNG" imageanchor="1"><img border="0" height="92" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZfP5_rLIRUHSmBE0K1L-v_86-U8UrbXLl6RuwLga5v0Cp6m4Ppp1rFWGffqUqdGmH4q5wM9AOJc9p-zHnEyGpoa2PRNpTPMNI3xjPDcKbd9R32azy1-Qn-KoK1l9b1WAVfDdT64UDyRpB/s640/Capture.PNG" width="640" /></a>
<br />
<br />
When setting no-tap-scroll="true" on the ion-header-bar element, you'll be able to disable this auto-scroll to top functionality, which I think is rather unexpected behavior.<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-9301932014111707572017-03-01T06:47:00.002-08:002017-03-01T06:47:29.066-08:00Synology Cloud Sync with Multiple Folders / DirectoriesIf you have a Synology NAS running DSM, you are likely familiar with the Cloud Sync package. This package has lots of potential, but has one major blocker: each account can only sync one folder. This has supposedly been addressed via "tasks" in newer DSM versions (6+) but if you are stuck on an older version of DSM, you are limited to syncing one folder to the cloud service of choice (i.e. Drive, OneDrive, Amazon, etc). This is problematic if you have a folder structure where folders like Homes, Videos, Music, Photos, etc are all top level shared folders with various permissions. Say you want to back up all Homes and also Videos, Music, and Photos... you must back each one up to a different cloud service. This is a deal breaker. Alternatively, you can move your folder structure around, but that is also a deal breaker if you have multiple users with different permission levels on these folders.<br />
<br />
What is the solution for DSM < 6 then? <b>Permanent Symlinks</b><br />
With permanent symlinks, you can create "virtual" directories that Cloud Sync will see as real directories to include in the sync. This allows you to have something like "\homes\user1\movies" that actually points to "\movies". Granted, your cloud backup folder structure will be different than your actual folder structure, you will at least get everything backed up.<br />
<br />
To implement this, there are two steps:<br />
<br />
<ol>
<li>Make the links:</li>
<ol>
<li>SSH into your Synology</li>
<li>Run "mount -o bind ‹TargetDirectory› ‹VirtualDirectory›"</li>
</ol>
<li>Make them permanent</li>
<ol>
<li>Run the link scripts at startup (more info at <a href="https://forum.synology.com/enu/viewtopic.php?t=102158">https://forum.synology.com/enu/viewtopic.php?t=102158</a>)</li>
</ol>
</ol>
<div>
<br /></div>
<div>
<br /></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-7362692910694304682016-11-12T12:44:00.002-08:002016-11-12T12:44:53.569-08:00Find Your GSM SIM Ready Laptop / Convertible / 2-in-1 / Tablet IMEII have been using a Lenovo Thinkpad Helix for months now, successfully replacing 4 towers, 1 laptop, 1 iPad, and 1 Android tablet with the single all-in-one device. It has been great! Now, I am ready to add a GSM SIM card and get this thing fully mobile.<br />
<br />
I had no idea what the IMEI is on the device as there is no sticker and I don't have the original box or documentation. After a little searching, I found a handy command that works in Windows command line: '<b>$ netsh mbn show interface</b>'<br />
<br />
This produces the following results:<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1UH-EROqauWwQneMyUCgJKHGsh2AzzO3KbFEa2e8HGPDFYplYyvYq-LS7N2lq4YMIwSlx0jzNSs25oLPRD06E3CVGUGR2aFqmMgrX7wTgAHyIJYee1bzBnoSC5C_vPMrbLHF1vMXiW757/s1600/Capture.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1UH-EROqauWwQneMyUCgJKHGsh2AzzO3KbFEa2e8HGPDFYplYyvYq-LS7N2lq4YMIwSlx0jzNSs25oLPRD06E3CVGUGR2aFqmMgrX7wTgAHyIJYee1bzBnoSC5C_vPMrbLHF1vMXiW757/s400/Capture.PNG" width="400" /></a></div>
<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-68567919938727938902016-07-12T07:21:00.001-07:002016-07-12T07:21:25.976-07:00How to Convert Apache .htaccess URL Rewrite Rules to IIS web.configHaving put in lots of testing and debugging time, your .htaccess URL rewrites are finally rock solid ( or good enough maybe :-) ). Now you are changing servers from Apache to IIS and you have to come up with all new web.config URL rewrite rules. I've been though this, manually refactoring .htaccess to web.config, and it isn't quick. The good news is that Microsoft has a tool to make this super simple and fast!<br />
<br />
First, make sure you have the <a href="http://www.iis.net/downloads/microsoft/url-rewrite" target="_blank">URL Rewrite module</a> installed in IIS. This is a simple download and install.<br />
<br />
Second, open up IIS and go to your website. You should now have a URL Rewrite icon. Double-click on this new icon and you'll see a menu on the right side with an "Import Rules..." option. This is where the magic happens. You can import your .htaccess file or copy/paste the contents and IIS will do the heavy lifting to convert it to web.config.<br />
<br />
One thing to note here that could save you some time... the web.config gets generated in the website root. Therefore if your source .htaccess was not in the website root, the generated web.config may need some touch up to the paths. Otherwise, the generated web.config is spot on based on my experience and you are all set!<br />
<br />
Reference: <a href="https://blogs.msdn.microsoft.com/azureossds/2015/04/23/converting-apache-htaccess-rules-to-iis-web-config-using-iis-manager-for-azure-websites/">https://blogs.msdn.microsoft.com/azureossds/2015/04/23/converting-apache-htaccess-rules-to-iis-web-config-using-iis-manager-for-azure-websites/</a>Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-16511353161953597022016-07-06T07:00:00.001-07:002016-07-06T07:00:22.830-07:00Creating a PDF on the fly with Ionic/CordovaSo you've got an Ionic app and you need to create PDFs from within the app. You don't want to offload to a server for PDF generation/download because the PDF is user specific and unique each time it is generated, so why not just generated it client side? For one, that was near impossible a number of years ago. Two, you've tried before and it just never worked right. Well, I managed to get PDF generation in an Ionic app and wanted to share my experience so you can do the same.<br />
<br />
What we are going to do is capture a portion of the HTML, turn it into a PDF, and trigger the download for the user.<br />
<br />
<b>The first step is to add the required libraries:</b><br />
Cordova Plugins:<br />
"cordova-plugin-file-transfer"<br />
"cordova-plugin-file-opener2"<br />
<br />
index.html (install via bower or point to CDN):<br />
<script src="lib/jspdf/dist/jspdf.min.js"></script><br />
<script src="lib/html2canvas/dist/html2canvas.js"></script><br />
<br />
<b>Next, we create the PDF generation function:</b><br />
<br />
<div class="code" style="background-color: #e8e8e8; border: thin solid rgb(204, 204, 204); clear: both; color: #963200; font-family: courier, monospace !important; overflow: auto; padding: 1em 1em 2em;">
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80</pre>
</td><td><pre style="line-height: 125%; margin: 0;">$scope.generatePDF <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span> (method) {
<span style="color: #888888;">//set the margins for the PDF</span>
<span style="color: #008800; font-weight: bold;">var</span> margins <span style="color: #333333;">=</span> {
top<span style="color: #333333;">:</span> <span style="color: #0000dd; font-weight: bold;">40</span>,
left<span style="color: #333333;">:</span> <span style="color: #0000dd; font-weight: bold;">40</span>
};
<span style="color: #888888;">// function to create a hidden clone of the target element,</span>
<span style="color: #888888;">// allowing manipulation specific for the PDF output without changing the actual target element.</span>
<span style="color: #008800; font-weight: bold;">var</span> hiddenClone <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span> (element) {
<span style="color: #007020;">document</span>.documentElement.style.overflow <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'visible'</span>;
<span style="color: #007020;">document</span>.body.style.overflow <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'visible'</span>;
<span style="color: #888888;">// Create clone of element</span>
<span style="color: #008800; font-weight: bold;">var</span> clone <span style="color: #333333;">=</span> element.cloneNode(<span style="color: #008800; font-weight: bold;">true</span>);
<span style="color: #888888;">// Position element relatively within the</span>
<span style="color: #888888;">// body but still out of the viewport</span>
<span style="color: #008800; font-weight: bold;">var</span> style <span style="color: #333333;">=</span> clone.style;
style.position <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'relative'</span>;
style.top <span style="color: #333333;">=</span> <span style="color: #007020;">window</span>.innerHeight <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'px'</span>;
style.left <span style="color: #333333;">=</span> <span style="color: #0000dd; font-weight: bold;">0</span>;
style.width <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'535px'</span>;
style.zIndex <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'9999'</span>;
<span style="color: #888888;">// Append clone to body and return the clone</span>
<span style="color: #007020;">document</span>.body.appendChild(clone);
<span style="color: #008800; font-weight: bold;">return</span> clone;
}
<span style="color: #888888;">// now create the clone of your target element and send it to html2canvas</span>
<span style="color: #008800; font-weight: bold;">var</span> html2Pdf <span style="color: #333333;">=</span> hiddenClone(<span style="color: #007020;">document</span>.getElementById(<span style="background-color: #fff0f0;">'pdfTarget'</span>));
html2canvas(html2Pdf, {
onrendered<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">function</span> (canvas) {
<span style="color: #888888;">// once the canvas is rendered, create new jsPDF object and working variables</span>
<span style="color: #008800; font-weight: bold;">var</span> doc <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">new</span> jsPDF(<span style="background-color: #fff0f0;">'p'</span>, <span style="background-color: #fff0f0;">'pt'</span>, <span style="background-color: #fff0f0;">'letter'</span>);
<span style="color: #008800; font-weight: bold;">var</span> dataUrl <span style="color: #333333;">=</span> canvas.toDataURL(<span style="background-color: #fff0f0;">'image/jpeg'</span>);
doc.addImage(dataUrl, margins.top, margins.left, canvas.width, canvas.height);
<span style="color: #008800; font-weight: bold;">var</span> dataUrlPdf <span style="color: #333333;">=</span> doc.output();
<span style="color: #008800; font-weight: bold;">var</span> dataUrlPdfBase64 <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'data:application/pdf;base64,'</span> <span style="color: #333333;">+</span> btoa(dataUrlPdf);
<span style="color: #888888;">// once we've got the canvas, we can remove the clone element and reset styles</span>
<span style="color: #007020;">document</span>.body.removeChild(html2Pdf);
<span style="color: #007020;">document</span>.documentElement.style.overflow <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'hidden'</span>;
<span style="color: #007020;">document</span>.body.style.overflow <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'hidden'</span>;
<span style="color: #008800; font-weight: bold;">var</span> filename <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'PDF-'</span> <span style="color: #333333;">+</span> $filter(<span style="background-color: #fff0f0;">'date'</span>)(<span style="color: #008800; font-weight: bold;">new</span> <span style="color: #007020;">Date</span>(), <span style="background-color: #fff0f0;">'yyyy-M-d'</span>) <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'.pdf'</span>;
<span style="color: #888888;">// Save location</span>
<span style="color: #008800; font-weight: bold;">var</span> targetPath <span style="color: #333333;">=</span> cordova.file.externalRootDirectory <span style="color: #333333;">+</span> filename;
<span style="color: #888888;">// use the cordova file transfer plugin to send the file to the cordova file opener plugin,</span>
<span style="color: #888888;">// allowing user to pick download method (only seems to work on Android)</span>
$cordovaFileTransfer.download(dataUrlPdfBase64, targetPath, {}, <span style="color: #008800; font-weight: bold;">true</span>).then(<span style="color: #008800; font-weight: bold;">function</span> (fileEntry) {
console.log(<span style="background-color: #fff0f0;">'Success'</span>);
console.log(fileEntry);
fileEntry.file(<span style="color: #008800; font-weight: bold;">function</span> (file) {
cordova.plugins.fileOpener2.open(
file.localURL,
file.type, {
error<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">function</span> (e) {
console.log(<span style="background-color: #fff0f0;">'Error status: '</span> <span style="color: #333333;">+</span> e.status <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' - Error message: '</span> <span style="color: #333333;">+</span> e.message);
},
success<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">function</span> () {
console.log(<span style="background-color: #fff0f0;">'file opened successfully'</span>);
}
}
);
});
}, <span style="color: #008800; font-weight: bold;">function</span> (error) {
console.log(<span style="background-color: #fff0f0;">'Error'</span>);
console.log(error);
}, <span style="color: #008800; font-weight: bold;">function</span> (progress) {
<span style="color: #888888;">// PROGRESS HANDLING GOES HERE</span>
});
}
});
};
</pre>
</td></tr>
</tbody></table>
</div>
</div>
<br />
<b>Now you can run the function and try it out...</b><br />
Hopefully you'll have successful client side PDF generation from HTML. I will say these plugins/libraries are touchy, especially when used together but this formula worked for me. Best of luck and feel free to share your implementations and feedback!<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-67931263888523485732016-03-01T17:03:00.000-08:002016-03-06T18:37:10.515-08:00Implementing Ionic ion-slides in place of the deprecated ion-slide-boxI have been working with Ionic 1.7.14 lately and ran into issues with <ion-slide-box>. It turns out <ion-slide-box> is deprecated and replaced by <ion-slides>. Unfortunately, the documentation is poor. The silver lining is that it is worth the switch as ion-slides is built on iDangerous' Swiper which is probably the best slider/slidebox out there.<br />
<br />
For those Ionic loyalists who live by the documentation, good luck making much of anything great based on the documentation at <a href="http://ionicframework.com/docs/v2/api/components/slides/Slides/" target="_blank">http://ionicframework.com/docs/v2/api/components/slides/Slides/</a> and <a href="http://ionicframework.com/docs/v2/components/#slides" target="_blank">http://ionicframework.com/docs/v2/components/#slides</a>.<br />
<br />
The reason I say this is because the documentation doesn't show how to expose all the functions of the Swiper plugin via javascript which are seemingly endless (<a href="http://idangero.us/swiper/api/" target="_blank">http://idangero.us/swiper/api/</a>). This is also the main reason to even use the new <ion-slides> directive. While you can specify options like loop and pagination (see below) via the Ionic directive's "option" directive, these aren't even explained in the docs except as when used as directives themselves (i.e. pager and loop). Further, it is especially unclear how the entire Swiper API can be leveraged using <ion-slides>.<br />
<br />
Here is a solution, specifically note the onInit which captures the Swiper plugin (in original form) to the $scope. Now you can leverage the Swiper API (as referenced in link above) against this $scope.swiper object. The $scope.sliderOptions will be used by the <ion-slides> via the "options" directive as mentioned previously.<br />
<br />
<div class="code">
// CONTROLLER<br />
// Setup swiper<br />
$scope.sliderOptions = {<br />
loop: true,<br />
pagination: false,<br />
onInit: function(swiper){<br />
$scope.swiper = swiper;<br />
},<br />
onSlideChangeStart: function(swiper){<br />
//<br />
}<br />
};<br />
<br />
// TEMPLATE<br />
<ion-slides options="sliderOptions"><br />
<ion-slide-page ng-repeat="slidesin slides"><br />
// iDangerous Swiper Slide in Ionic<br />
</ion-slide><br />
</ion-slides>
</div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-13195665883449397882016-02-16T16:47:00.002-08:002016-08-24T17:08:36.612-07:00Installing Cisco VPN Client and SonicWall Global VPN Client on Windows 10 Upgrade<b>UPDATE 08/24/2016:</b> The Windows 10 Anniversary Update broke my Cisco VPN Client again. The guide <a href="https://richdunajewski.com/fix-windows-10-update-cisco-vpn-client-removed/" target="_blank">here</a> got me back on track.<br />
<br />
So you've upgraded to Windows 10 and been promised everything will keep working just as before... but one of the most important apps you have is now broken! This is what happened to me when upgrading to Windows 10. Both my Cisco VPN Client and SonicWall Global VPN Client stopped working. Fortunately, there is a fix... and it is surprisingly pretty.<br />
<br />
In comes <a href="http://blog.techygeekshome.info/2016/01/cisco-vpn-client-how-to-make-it-work-with-windows-10" target="_blank">TechyGeeksHome</a> to the rescue! They have put together a great utility to help rectify these issues (specifically for Cisco VPN, but works for SonicWall Global VPN too).<br />
<br />
<br />
<ol>
<li>Uninstall your VPN(s).</li>
<li>Go to <a href="http://blog.techygeekshome.info/2016/01/cisco-vpn-client-how-to-make-it-work-with-windows-10">http://blog.techygeekshome.info/2016/01/cisco-vpn-client-how-to-make-it-work-with-windows-10</a>.</li>
<li>Download the Windows 10 fix package.</li>
<li>Run the included WinFix.exe (by Citrix)</li>
<li>Run the included DNE Update (by Citrix)</li>
<li>Install the Cisco VPN <u style="font-weight: bold;">MSI</u> (the exe will not work) (msi for x64 can be found <a href="https://www.ibm.com/developerworks/community/files/app#/file/90b4f704-9b8b-4118-81e5-d4c4b949b3c3" target="_blank">here</a> according to Google search)</li>
<li>Run the included CiscoVPNClientFixx (by TechyGeeksHome)</li>
<li>If you also want to use SonicWall Global VPN, you can now install it as well</li>
</ol>
<div>
These steps solved my issues and I am back up and running on Windows 10 + VPN thanks to <a href="http://blog.techygeekshome.info/2016/01/cisco-vpn-client-how-to-make-it-work-with-windows-10" target="_blank">TechyGeeksHome</a>.</div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-43749538411888875302016-01-31T17:57:00.003-08:002016-01-31T17:57:39.783-08:00Android Intent App Options Now Include Apps from Play Store That Are Not Installed On DeviceI was using my android recently and noticed something quite surprising. I was opening an .XLSX file and the typical intent/app selection popover came up on screen. I had Google Sheets and Quickoffice apps as options as always (they are installed on my device), but I now also had an "Excel" option with the text "Free" to the right. At this point I'm not sure what goes into getting a "yet-to-be-installed" app to appear in the list of intent/app options, but this is a pretty big move for Android to take. What do you think?<br />
<br />
Here is a screenshot for your enjoyment:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjORQYGTEd20Rl5gMFLhBDWMNXbVzQHkAzoiRcYJVIkk8Hjlsv9Fqq-aDSYZSbOdgzQZb7WcOREfT9S6hcGZBZhZRppF_SxmzZaVbNr_f0wtjS7mECd_YWZ5nURaEsAx-I7-UkZ09ALhOKw/s1600/2016-02-01+01.23.14.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjORQYGTEd20Rl5gMFLhBDWMNXbVzQHkAzoiRcYJVIkk8Hjlsv9Fqq-aDSYZSbOdgzQZb7WcOREfT9S6hcGZBZhZRppF_SxmzZaVbNr_f0wtjS7mECd_YWZ5nURaEsAx-I7-UkZ09ALhOKw/s400/2016-02-01+01.23.14.png" width="250" /></a></div>
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-25038768218181362692015-12-13T09:39:00.001-08:002015-12-13T09:39:31.610-08:00Fixing iOS 9 (9.1,9.2) Blank uiWebView Page in a Corona AppAs expected, an update has come along that breaks something in your Corona app... this particular issue has to do with changes in iOS 9 uiWebView security. If you are not connecting your webview via (valid) https, you will now get a blank page in your webview. This can be resolved by customizing App Transport Security settings in info.plist (for native apps). This file won't exist in your Corona project, but it is supported via the build.settings file. There is a Corona forum post on how to update build.settings to generate the necessary info.plist settings in your build <a href="https://forums.coronalabs.com/topic/58812-ats-on-ios9/" target="_blank">here</a>. Even better, there is a Corona blog entry with more insight <a href="https://coronalabs.com/blog/2015/09/17/about-app-transport-security-ats/" target="_blank">here</a>. Finally, if you want to truly understand your settings, reference the Apple docs on this <a href="https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40016240-CH1-SW5" target="_blank">here</a>.<br />
<br />
In the end, your build.settings will need an exception added like so:<br />
<br />
<div class="code" style="background-color: #e8e8e8; border: thin solid rgb(204, 204, 204); clear: both; color: #963200; font-family: courier, monospace !important; overflow: auto; padding: 1em 1em 2em;">
<span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">settings </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">{</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
iphone </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">{</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
plist </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">{</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
NSAppTransportSecurity </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">{</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
NSExceptionDomains </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">{</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">[</span><span class="str" style="box-sizing: border-box; color: #f56608; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">"example.com"</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">]</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;"> </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">{</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
NSIncludesSubdomains </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;"> </span><span class="kwd" style="box-sizing: border-box; color: #eb8b2b; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">true</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">,</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
NSThirdPartyExceptionAllowsInsecureHTTPLoads </span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">=</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;"> </span><span class="kwd" style="box-sizing: border-box; color: #eb8b2b; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">true</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">},</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">},</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">},</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">},</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">},</span><span class="pln" style="box-sizing: border-box; color: #48484c; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">
</span><span class="pun" style="box-sizing: border-box; color: #989898; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 15px; white-space: pre;">}</span></div>
<br />
<br />
If you control the domain, you should use NSExceptionAllowsInsecureHTTPLoads rather than NSThirdPartyExceptionAllowsInsecureHTTPLoads. Only use NSIncludesSubdomains if you need to allow multiple subdomains.<br />
<br />
Of course, the ideal solution is to ensure any webview is connecting via a valid https connection... but that is not always possible.Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-90234337260889192542015-11-25T16:27:00.002-08:002015-11-25T16:27:14.164-08:00Ebates First 30 Days ReviewOf course I had heard of <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a>, but I had never actually tried <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> until 30 days ago as per the recommendation of a friend. This is my 30 day review.<br />
<br />
The main reason I had never tried <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> before is because the idea of coupon sites, comparison sites, and deal sites has become quite lackluster to me over the years. I thought <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> was the same as the rest. I thought I could just assume <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> was another comparison engine, posting coupons and deals just like the rest. <b>Boy was I wrong...</b><br />
<br />
My first experience with <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> was great. I signed up, saw a promo for a $10 gift card after first $25 purchase and proceeded to order a laptop battery from Newegg. I would have ordered the battery from Newegg anyway, but now I was getting cashback and a $10 WalMart gift card just for clicking through from <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> first. Sure enough, the cashback showed up in my account and my gift card was processed... not immediately of course, but ultimately it happened without any strings attached and without any intervention required. <b>I have a $10 WalMart gift card on the way to me right now.</b><br />
<br />
The first <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> experience was good enough that I installed their chrome extension. Since then, I have made a couple more online purchases and received cashback. <b>My cashback balance reached $5.33 in the first 30 days.</b> I thought there was surely some strict threshold required before they would cut me a check, but I have a $5.33 check on the way as of today. Again,<b> no strings attached and no intervention required.</b><br />
<br />
Now that I've had a successful first month and <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> has proven to be non-disruptive and practically automatic, I will certainly continue to use them. I have actually started looking for coupons again since <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a> makes that super easy as well. Further, I am considering some larger purchases I would have not otherwise considered due to <b>6% cashback and readily-available coupons</b> via <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a>.<br />
<br />
I highly recommend getting on board with <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a>. It is <b>free </b>and supports Facebook and Google login to make it even easier. Login and in 1-2 clicks you'll be on your favorite ecommerce store as always, but this time you'll be earning no-strings-attached cashback via <a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank">Ebates</a>.<br />
<br />
<a href="http://www.ebates.com/rf.do?referrerid=JAd31sK%2BkCrRj96inUvH4A%3D%3D&eeid=29041" target="_blank"><b>If you're ready to sign up for Ebates now, click here to get started.</b></a>Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-78137450180165708082015-07-28T21:14:00.001-07:002015-07-28T21:14:49.249-07:00API for Retrieving Website Favicon with FallbackI have worked on a number of projects where pulling in favicons from external websites needed to be done. This would typically be approached by checking for existence of a /favicon.ico in the root of the website or something along those lines. This isn't fool proof by any means though since the favicon can be named differently or located in a different path. It also takes a lot of unnecessary overhead to check for existence of a favicon before serving up a fallback.<br />
<br />
Fortunately, there is a "hidden" Google API of sorts that handles this for you. It is used to pull in favicons for websites attached to your Google+ profile page. It works as follows:<br />
<br />
<pre>http://s2.googleusercontent.com/s2/favicons?domain=mrrobwad.blogspot.com&alt=p</pre>
<br />
It turns out the fallback can be one of a few things. So far, I've discovered that setting the 'alt' parameter to 's' yields a blank page icon and 'p' yields a page template icon when no favicon is available. Setting to any other single letter yields a browser/world icon. There could be more, but I have only checked for single letters so far.<br />
<br />
Enjoy!Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-75744571195547892812015-07-03T13:11:00.002-07:002015-07-03T13:11:46.328-07:00How To: Minify JS and CSS with ColdFusionIf you're reading this post, I can assume two things.<br />
<ol>
<li>You're running ColdFusion and need to minify JS and/or CSS files.</li>
<li>You've researched other solutions such as complex regex routines or CFCs and are not satisfied with those solutions.</li>
</ol>
<div>
Regarding #2, modern minification is complex and tedious. If you've tried writing your own routines, you know what I mean. Not to mention, effective minification consists of more than just code formatting. So what's the answer? Well, there is already a clear and proven minification winner: YUI Compressor. This utility is widely used, reliable, and efficient. It also includes useful options to give you some level of control as well as insight.</div>
<div>
<br /></div>
<div>
Now that we've picked our minification engine, how do we make it work with ColdFusion?</div>
<div>
<br /></div>
<div>
First you need to get YUI Compressor installed on your server and YUI Compressor relies on Rhino, so we need to get that installed as well. All of this relies on you having Java installed, but since you are running ColdFusion I am assuming you do!</div>
<br />
<div>
<br /></div>
<div>
<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Download_Rhino" target="_blank">DOWNLOAD RHINO</a> (recommended release at time of this post is 1.7R5)<br /><ul>
<li>Install to directory of choice, i.e. C:\apps\rhino\rhino1_7R5</li>
<ul>
</ul>
<li>Add to Windows CLASSPATH</li>
<ul>
<li>System Properties > Advanced > Environment Variables > System Variables > CLASSPATH</li>
<ul>
<li>If CLASSPATH system variable doesn't already exist, add it with value of "c:\apps\rhino\rhino1_7R5" or your chosen install directory.</li>
<ul>
</ul>
<li>If CLASSPATH system variable already exists, edit it by adding ";c:\apps\rhino\rhino1_7R5" or ";" followed by your chosen install directory.</li>
</ul>
</ul>
</ul>
<br /><ul><ul><ul><ul>
</ul>
</ul>
</ul>
</ul>
<a href="https://github.com/yui/yuicompressor/releases" target="_blank">DOWNLOAD YUI COMPRESSOR</a> (recommend release at time of this post is 2.4.8)<br /><ul>
<li>Install to directory of choice, i.e. c:\apps\yuicompressor\yuicompressor-2.4.8.jar</li>
<ul>
</ul>
</ul>
<div>
<br /></div>
Verify you can run YUI Compressor from command line<br /><ul>
<li>Open a command prompt in a test directory with a non-minified JS file present, i.e. test.js</li>
<li>Run command $ java -jar c:\apps\yuicompressor\yuicompressor-2.4.8.jar -v c:\test\test.js -o test.min.js</li>
<li>The "-v" argument causes output to include analysis of the JS with tips for improvements. Don't include this option if you are minifying CSS.</li>
<li>You should see test.min.js created in the current/test directory.</li>
<ul>
</ul>
</ul>
<div>
<br />
Run minification from within ColdFusion:<br />
<ul>
<li>Use <cfexecute> to run YUI Compressor against file in need of minification.</li>
<li>minifyResult will contain the command prompt output.</li>
<li>minifyError will contain error information.</li>
</ul>
</div>
</div>
<div class="code" style="background-color: #e8e8e8; border: thin solid rgb(204, 204, 204); clear: both; color: #963200; font-family: courier, monospace !important; overflow: auto; padding: 1em 1em 2em;">
<cfexecute name="java" arguments="-jar c:\apps\yuicompressor\yuicompressor-2.4.8.jar c:\test\test.js -o test.min.js" variable="minifyResult" errorVariable="minifyError" timeout="10"/></div>
<div>
<br />
<ul>
<li>ColdFusion runs cfexecute from the ColdFusion bin, so this is where the minified file will be generated, so we need to move the generated file from the CF bin to the desired target directory.</li>
<li>The cf_bin_path variable value will likely be different for your install, so you need to update this variable to point to your actual CF bin directory.</li>
</ul>
</div>
<div class="code" style="background-color: #e8e8e8; border: thin solid rgb(204, 204, 204); clear: both; color: #963200; font-family: courier, monospace !important; overflow: auto; padding: 1em 1em 2em;">
<cfset cf_bin_path = "c:\ColdFusion\runtime\bin"><br />
<cffile action = "move"<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>source = "#cf_bin_path#\test.min.js" <br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>destination = "c:\target-directory\test.min.js"></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com1tag:blogger.com,1999:blog-1524556630893584656.post-81060031424753906732015-06-16T17:33:00.000-07:002015-06-16T17:33:23.632-07:00Installing PhantomJS as a Windows Service (BONUS: Using PhantomJS with ColdFusion)If you aren't familiar with PhantomJS, you probably wouldn't be looking at the blog post. Regardless, "PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG." available at <a href="http://phantomjs.org/">http://phantomjs.org/</a>.<br />
<br />
PhantomJS is a very powerful and versatile tool. Once you get comfortable with PhantomJS you may want to install your PhantomJS application as a Windows service. There are a few good reasons for this, but one is running a persistent PhantomJS web server.<br />
<br />
If you are interfacing with PhantomJS from another application, it is simple enough to create a new instance of PhantomJS each time you need to use it (i.e. via <cfexecute> in ColdFusion). The problem is that this will result in multiple PhantomJS processes, one for each instance, which leads to unnecessary system overhead. Alternatively, you may want to keep a persistent instance of PhantomJS running as a Windows service to handle multiple requests from your application.<br />
<br />
For this to be practical, the persistent instance of PhantomJS has to be able to accept requests and provide responses to your application. In my case, I created a PhantomJS web server application to achieve this. It listens to HTTP requests on an obscure port only available internally. The PhantomJS website has some helpful examples for setting such a web server up. From there, my ColdFusion application simply needs to interface with my PhantomJS server via HTTP.<br />
<br />
None of this is helpful unless the PhantomJS web server is up and running. By registering the PhantomJS application as a Windows service, we can ensure it is up and running at all times. We can also stop it, start it, and restart it as needed.<br />
<br />
Before proceeding, note that this process requires changes to your registry. I hold no responsibility for changes you make. All changes are your own responsibility. I have successfully used this process on a both a Windows 7 64bit install and a Windows Server 2008 R2 64bit install.<br />
<div>
<br /></div>
Ensure you can run your PhantomJS application from command line without failure before turning it into a Windows service to begin with. For example, from command line: C:\apps\your-application\bin\phantomjs.exe phantom.your-script.js <port-number>. Where <port-number> represents the port number your phantom.your-script.js is setup to accept as an argument<br />
<div>
<br /></div>
<div>
Download necessary tools from: <a href="http://www.microsoft.com/downloads/en/details.aspx?FamilyID=9d467a69-57ff-4ae7-96ee-b18c4790cffd&displaylang=en">http://www.microsoft.com/downloads/en/details.aspx?FamilyID=9d467a69-57ff-4ae7-96ee-b18c4790cffd&displaylang=en</a><br />
<ul>
<li>I suggest installing to C:\apps\rktools\</li>
<li>We will need these exe files:</li>
<ul>
<li>C:\apps\rktools\srvany.exe</li>
<li>C:\apps\rktools\instsrv.exe</li>
</ul>
</ul>
<br />
Run at CMD: C:\apps\rktools\INSTSRV.EXE "Phantom JS" C:\apps\rktools\SRVANY.EXE<br />
<ul>
<li>"PhantomJS" is an arbitrary service name. Call it whatever you like. You can use spaces here, but maintain the quotes.</li>
</ul>
<div>
<br /></div>
Open regedit.exe<br />
<ul>
<li>Navigate to HKEY_LOCAL_MACHINE > SYSTEM > CurrentControlSet > Services > "Phantom JS" (your service name)</li>
<li>Right-click on the service name > New > Key</li>
<ul>
<li>Name the key "Parameters"</li>
</ul>
<li>Right-click on the Parameters key > New > String Value</li>
<ul>
<li>Name the String value "Application"</li>
<li>Right-click "Application" > Modify</li>
<ul>
<li>Set the value to: "C:\apps\your-application\bin\phantomjs.exe" phantom.your-script.js <port-number></li>
<ul>
<li>Make sure the path to the exe is quoted and the arguments are not. This should essentially be the same string you can pass to the CMD line to run your PhantomJS application but with quotes around the exe path. Again, <port-number> represents the port number your phantom.your-script.js is setup to accept as an argument</li>
</ul>
</ul>
</ul>
<li>Right-click on the Parameters key > New > String Value</li>
<ul>
<li>Name the String value "AppDirectory"</li>
<li>Right-click "AppDirectory" > Modify</li>
<ul>
<li>Set the value to: C:\apps\your-application\bin\</li>
<ul>
<li>This is the path to your application folder containing the PhantomJS exe and your PhantomJS script.</li>
</ul>
</ul>
</ul>
</ul>
<div>
<br /></div>
<div>
Your entries should look something like this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKjc_OtwqkWuxTce2moB4g7Jgu8JgL8Fu5bwjSyXoXS9Q_-tDixFNnwIG42l7Jc6_QXuGsKOp-2XW5TyL6kt87J4apG2VxEaz9QuRJFc_KejRt0M30tQAeEY4PUB3jXKC9_nH4Hh54cngU/s1600/Capture2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKjc_OtwqkWuxTce2moB4g7Jgu8JgL8Fu5bwjSyXoXS9Q_-tDixFNnwIG42l7Jc6_QXuGsKOp-2XW5TyL6kt87J4apG2VxEaz9QuRJFc_KejRt0M30tQAeEY4PUB3jXKC9_nH4Hh54cngU/s1600/Capture2.PNG" width="100%" /></a></div>
<br />
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
Open services.msc and you should see your new windows service present. It is set to auto-start by default, but go ahead and start it for the first time to ensure it runs without failure.</div>
</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinCQhh__HIa5NMU0-llmb0XPRK-ezk-AgZrhei8XWsyHRsFlyAnHfdXMxsqhIemnZIFIMjNeEjm_I21upEqxo_v-8sXBy_Pylw8_Pm4wyJNkZBZgQBSq5K6QBUsxEvxwAVs5HKcpplBWl-/s1600/Capture.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinCQhh__HIa5NMU0-llmb0XPRK-ezk-AgZrhei8XWsyHRsFlyAnHfdXMxsqhIemnZIFIMjNeEjm_I21upEqxo_v-8sXBy_Pylw8_Pm4wyJNkZBZgQBSq5K6QBUsxEvxwAVs5HKcpplBWl-/s1600/Capture.PNG" width="100%" /></a></div>
<br /></div>
<br />
<br /></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-84643978052669305992015-06-16T16:28:00.000-07:002015-06-16T16:36:22.676-07:00Why "Googling Yourself" is not an excuse to ignore keyword rank tracking for SEOI recently was asked my opinion on some SEO advice that had been shared with a client. The advice was essentially: <b>Ignore keyword rank tracking because all Google results are personalized and you can't accurately track rankings by "Googling Yourself"</b>. <b>Instead, look to organic traffic in Google Analytics as the sole indicator of SEO success.</b><br />
<b><br /></b>
First off, "Googling Yourself" seems like a bit of a misnomer for the act of checking Google ranks by Google searching a keyword phrase yourself. I think of Googling my name when I read that but it is evident what they mean when read in context. Ironically, one of the biggest issues I have with this has to do with context itself... read on to see what I mean.<br />
<br />
There are a few things right about this:<br />
<br />
<ol>
<li>Keyword rank tracking can be considered unreliable, since search results are personalized.</li>
<li>"Googling Yourself" can provide misleading information on keyword rankings since your results are personalized.</li>
<li>Organic traffic in Analytics is a great indicator of overall SEO success.</li>
</ol>
<br />
<br />
<h2>Beyond those, this advice falls very short of effective SEO but there are ways to reliably check keyword rankings.</h2>
While "Googling Yourself" is not a reliable way to check rankings and Google results are indeed personalized, it doesn't mean keyword rankings don't matter for SEO. This is why we built our own in-house keyword rank checking tool that uses Google's Search API to determine ranks. The Search API doesn't factor any personalization in, but it also isn't an exact mirror of the Google.com search engine. Even so, we believe it to provide an authoritative baseline and the ranks returned are the most reliable ranks we can get.<br />
<br />
<h2>Keyword rankings and activity are important.</h2>
Knowing how users found your website, specific what keyword terms were used, is very beneficial. This lets you know what is working, what isn't working, and sheds light on user perception and intent. In the past, Google Analytics reported on the keywords that people used to reach your website. This has since been all but stopped by Google (which is an entirely separate topic). However, you can get some referral keyword insight through Google Webmaster Tools. It isn't ideal, but it does offer some insight.<br />
<br />
That said, knowing what keyword searches led to visits is a passive exercise. Keyword research and targeting is an active exercise. Why discount this just because it is difficult to asses rank? Why accept that overall organic traffic patterns are enough to indicate SEO success?<br />
<br />
<h2>Being pro-active about SEO is better than watching organic traffic numbers passively.</h2>
It is true that the best overall indicator of how well you are doing on SEO is organic traffic. That is the end goal in most every case: more traffic from the search engines, period. The more organic traffic, the better you are doing, but how does this help you to be pro-active about SEO? It simply doesn't. It doesn't even help you to be reactive, because you have no insights into why your organic traffic is what it is. In this scenario, you simply implement some best practices (hopefully), hope for Google's favor, and anxiously wait for your organic traffic to increase. Google is smart enough that this is actually still pretty effective, but it is missing a huge piece of the equation... keyword ranking tracking.<br />
<br />
Without keyword research and tracking, you can't know what keywords are competitive or low-hanging fruit, effective or ineffective. With keyword research and tracking, you can come up with a plan to target specific phrases. This is where you can get an edge on rankings and pro-actively increase your organic traffic. Google wants to provide the best results to its users, but it needs the help of websites to make that possible. If your website cooperates by offering up clean and clear indications of context, you are helping Google identify what your website is all about. You are also laser-focusing your content toward that context, which helps build authority in Google's eyes. You can partly accomplish this laser-focus through a natural but focused application of keyword phrases in all the right places (headings, lists, titles, descriptions, etc). Do not mistake this for keyword stuffing, but approach it as naturally editing your copy to provide consistency and focus.<br />
<br />
<h2>In summary:</h2>
Search rankings should not be discounted or ignored. Checking ranks on specific search terms (via a tool like ours) isn't going to provide an accurate view of your overall organic search performance, only a sub-set of specific terms you determine worthy of tracking. However, those specific keyword rankings do effect the personalized search results everyone sees. If you rank high on highly relevant keyword searches, your chances of ranking high in related personalized results is much higher. Rankings also offer invaluable insight into how well Google associates you with what you believe the context of your website to be. If you are going after a specific niche, targeting specific keyword phrases and monitoring their performance is a huge part of reaching that niche. It all helps Google understand context (who you are) and authority (what you know or have to offer).<br />
<br />
<h2>Client example:</h2>
<blockquote>
<b>Passive </b>(Keyword targeting strategy NOT in place): 1035 organic visitors (12/2014)<br />
<b>Pro-Active </b>(Keyword targeting strategy in place): 1723 organic visitors. (2/2015)</blockquote>
<blockquote>
The December sample, shows how the website was doing on organic traffic without keyword research and targeting, but the February sample shows how the website was doing after keyword research and targeting was in place for only a few weeks.</blockquote>
<br />
<b>There is too much potential benefit to ignore keyword rankings simply because Google personalizes results.</b><br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-12025390593749011912015-03-09T17:57:00.000-07:002015-08-14T13:35:30.499-07:00Connecting to a Netgear Nighthawk VPN with AndroidAccording to <a href="http://kb.netgear.com/app/answers/detail/a_id/23854/~/how-do-i-use-the-vpn-service-on-my-nighthawk-router-with-my-windows-client%3F" target="_blank">Netgear</a>, neither iOS or Android devices are supported by Netgear OpenVPN routers. This post is in reference to Android only, so don't make much of my comments if you are looking for iOS help. The lack of Netgear VPN support for Android is for two sensible reasons, though they aren't explained by Netgear.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVsSopoIOLMtXQBT9gLf9miRkkvzU7-yuLp-cPVHVXcdzsTtNdUJiA-vQCntiX82pIpCpo66vIN1mYFqVnSDyUISRxAnEYj8kjhGD7_fMMKjTLmzc43F8CQxFj743EsjTyDMF-9_9u2CP6/s1600/netgear-VPN.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="47" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVsSopoIOLMtXQBT9gLf9miRkkvzU7-yuLp-cPVHVXcdzsTtNdUJiA-vQCntiX82pIpCpo66vIN1mYFqVnSDyUISRxAnEYj8kjhGD7_fMMKjTLmzc43F8CQxFj743EsjTyDMF-9_9u2CP6/s1600/netgear-VPN.png" width="320" /></a></div>
<br />
<br />
<ol>
<li><b>Netgear uses OpenVPN.</b> Most built-in in VPN support on mobile devices is intended for use with PPTP VPNs, not OpenVPN. At first, you might be frustrated with Netgear for using a protocol without the widest support, but they have very good reason: security. PPTP is the least secure VPN protocol. Even <a href="https://technet.microsoft.com/en-us/library/security/2743314.aspx" target="_blank">Microsoft</a>, who helped to develop PPTP, advises using a different VPN protocol for security reasons. BestVPN put together a <a href="https://www.bestvpn.com/blog/4147/pptp-vs-l2tp-vs-openvpn-vs-sstp-vs-ikev2/" target="_blank">great comparison of VPN protocols</a> that reiterates the issues with PPTP and makes the advantages to OpenVPN clear. After reading this, you may be thanking Netgear for selecting OpenVPN and questioning their competitors for sticking with PPTP VPNs.</li>
<li><b>Netgear's OpenVPN is TAP, not TUN.</b> OpenVPN is operated in one of two main modes: <a href="http://en.wikipedia.org/wiki/TUN/TAP" target="_blank">TAP or TUN</a>. The main difference being that TAP is layer 2 and works more like a switch or bridge and TUN is layer 3 and works on the network level to route packets on the VPN. Netgear isn't very obvious that it utilizes TAP and that is a problem for Android users as Android only supports TUN. Download most any OpenVPN client on Android and you'll be trying to make a TUN connection, which simply won't work with Netgear.</li>
</ol>
<div>
The simple solution, an Android app called <a href="https://play.google.com/store/apps/details?id=it.colucciweb.openvpn&hl=en" target="_blank">OpenVPN Client</a>. This is the only OpenVPN client on Android that currently supports TAP as far as I am aware. It also is a stable app with good developer support. Before you go and install this app and start trying to connect, let me save you a few headaches and save the developer from some support emails as well.</div>
<div>
<br /></div>
<div>
To connect your Android to 4.0+ device to your Netgear Nighthawk OpenVPN:</div>
<div>
<ol>
<li>Setup your VPN on your router as per steps 1-6 on <a href="http://kb.netgear.com/app/answers/detail/a_id/23854/~/how-do-i-use-the-vpn-service-on-my-nighthawk-router-with-my-windows-client%3F" target="_blank">these instructions from Netgear</a>.</li>
<li>Download the "Windows" setup zip from your router. <div class="separator" style="clear: both; text-align: center;">
<a href="http://www.downloads.netgear.com/files/answer_media/images/R7000/VPN_Enable%20VPN%20Service.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.downloads.netgear.com/files/answer_media/images/R7000/VPN_Enable%20VPN%20Service.png" height="106" width="320" /></a></div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Extract the files to a dedicated/new folder on your Android where you can easily find them.</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Install the <b>PAID</b> version of the OpenVPN Client app <b>(the free version does not support TAP)</b></div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Tap the circular green "+" icon when you open the app. </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXjndkfq7lIMcPcflh89V_iWRGpls9oVUi3UFFUbhEjd9GvjFzfU37ekYFT_rYNFkWQqoIgLXdBqq6ibG1f1EG-ujXWrai1-q6KBzkQPmCLsuaiw2mrEwgzLppwec_AJJJWTaGPMMEVJEU/s1600/unnamed.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXjndkfq7lIMcPcflh89V_iWRGpls9oVUi3UFFUbhEjd9GvjFzfU37ekYFT_rYNFkWQqoIgLXdBqq6ibG1f1EG-ujXWrai1-q6KBzkQPmCLsuaiw2mrEwgzLppwec_AJJJWTaGPMMEVJEU/s320/unnamed.png" width="187" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Select Import VPN.</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Navigate to the folder where you extracted the setup files in step 3 and select the .opvn file.</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Open the new VPN connection in the app and select the Edit icon.</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Select Custom Options. </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjE-wBsyLGtspHC2BzaMdwBtkU5jlvKrY3aeZqPRNziwynfQ57QUsEI3pZO1KMyDpQSAxJDxfT9q1_c3BdFab8rx0bNWQb5cdNzJkxpS8PztGog_INOMyoDKkaFu3LW4DsTZztwuytv088-/s1600/unnamed2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjE-wBsyLGtspHC2BzaMdwBtkU5jlvKrY3aeZqPRNziwynfQ57QUsEI3pZO1KMyDpQSAxJDxfT9q1_c3BdFab8rx0bNWQb5cdNzJkxpS8PztGog_INOMyoDKkaFu3LW4DsTZztwuytv088-/s320/unnamed2.png" width="187" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Add the option "route-gateway" with the value set to 192.168.1.1 (in most cases, your local network).</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Save changes and connect to the VPN.</div>
</li>
</ol>
<div>
<a href="https://play.google.com/store/apps/details?id=it.colucciweb.openvpn&hl=en" target="_blank">OpenVPN Client</a> is well worth the money and you'll be off to taking full advantage of your Netgear Nighthawk OpenVPN TAP VPN via your Android anywhere you may go.</div>
</div>
<div>
<br /></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-17378703694717584412014-12-18T17:54:00.001-08:002014-12-18T17:54:23.062-08:00Charles Maier Carbon Fiber Wallet Review, 1 Year LaterLast Christmas I received a <a href="http://charlesmaier.com/product/mens-usa-carbon-fiber-wallet/">Carbon Fiber Wallet by Charles Maier</a> much to my excitement. Carbon fiber has gone from an industrial, aerospace, and motorsports exclusive to a fashion statement and Charles Maier is the best of the best in that respect. When I found out Charles Maier was releasing a line up of handmade Italian leather wallets and bags with real high quality carbon fiber integrated into the design, I was intrigued to see them since I have seen lots of low quality and imitation carbon fiber goods in the past.<br />
<br />
When I received the wallet a year ago, I was immediately impressed with the quality and workmanship. The wallet looked as good in real life as it does in the photos on the Charles Maier site. The leather took a little breaking in, but everything fit perfectly in the wallet. Since then, I've used the wallet daily and it has not stretched, cracked, or degraded in any way. It still holds my cards firmly and the carbon fiber still looks as glossy as the day I got it. I even accidentally put the wallet in the wash with my pants for a few minutes and it came out unscathed. The Charles Maier carbon fiber wallet is perfect for anyone into motorsports, technology, fashion or luxury.<br />
<br />
Here are real life pictures of my Charles Maier carbon fiber wallet after one year of daily use. If you are thinking of investing in a wallet that will not only make a statement about you but also hold up to daily use, this is the right wallet for you. Most other wallets I've had fall apart after about a year and the rugged wallets I've had are just ugly and made of unappealing materials. The Charles Maier wallet is made of high quality leather, high quality carbon fiber, and lasts.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg88VSLdZ1ecRBpdVlww3RXTfo_ww38v8qknF3y_Pg-__awOX3Vrgmz4RSqr2GXmrtoTXO2cSMDBpdJHjX_2vi-g9cqCracPR8f_JBRAg3CFFJhOgYEKiVn-G-NMhsCPigXrr__szUFJeVB/s1600/2014-12-16+11.31.33.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg88VSLdZ1ecRBpdVlww3RXTfo_ww38v8qknF3y_Pg-__awOX3Vrgmz4RSqr2GXmrtoTXO2cSMDBpdJHjX_2vi-g9cqCracPR8f_JBRAg3CFFJhOgYEKiVn-G-NMhsCPigXrr__szUFJeVB/s400/2014-12-16+11.31.33.jpg" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyBbLKHNfCUifrSJ4_6T78wIJZp0T0n_6dNU3ep6aZ4IeqvFG3wqd3bQjPRHmZkvDoxbkyYyDhMIS8JRd5IBqQhp3ZYcBoc4X7H7R-kabWBZRETyZ2oKle6ctGG9hraqm0uiN8-CA-ghm2/s1600/2014-12-16+11.32.15.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyBbLKHNfCUifrSJ4_6T78wIJZp0T0n_6dNU3ep6aZ4IeqvFG3wqd3bQjPRHmZkvDoxbkyYyDhMIS8JRd5IBqQhp3ZYcBoc4X7H7R-kabWBZRETyZ2oKle6ctGG9hraqm0uiN8-CA-ghm2/s400/2014-12-16+11.32.15.jpg" /></a></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-447515634406055912014-11-25T13:16:00.000-08:002014-11-25T13:16:30.064-08:00Tracking Multiple Subdomains in Universal AnalyticsThe new Universal Analytics has improved subdomain tracking ease out-of-the-box, no longer requiring the core tracking code to be modified to support multiple subdomains. The older ga.js tracking code would set cookies at the subdomain level, while the Universal Analytics tracking code sets cookies at the root domain level. This means that it doesn't matter what subdomain the the Universal Analytics tracking code runs on, the cookies will still be valid.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-4iCzqEfHASU/VHTt9sL7d8I/AAAAAAAAjbY/VzXeN_BfS8w/s1600/analytics-cookies.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-4iCzqEfHASU/VHTt9sL7d8I/AAAAAAAAjbY/VzXeN_BfS8w/s1600/analytics-cookies.png" height="185" width="320" /></a></div>
<br />
Just because Universal Analytics works out-of-the-box for subdomain tracking, doesn't mean it does what you need it to of course. Without any further setup, you won't be able to different subdomain traffic within your reports... you'll just see plain URIs like /contact-us and /learn-more. You won't be able to differentiate which subdomain that URI belonged to when the visitor loaded it. Further, you won't be able to report on subdomain traffic only without further setup.<br />
<br />
The resolution of these short-falls is actually pretty easy.<br />
<br />
<b>First, make sure you have your basic Universal Analytics tracking code installed on every subdomain.</b><br />
<br />
<b>Next, your primary view needs custom filters setup:</b><br />
<br />
<ol>
<li>Go to Admin and select your primary view.</li>
<li>Click Filters on the View column.</li>
<li>Click +New filter</li>
<li>Set the name as the full domain with subdomain (i.e.
xyz.google.com)</li>
<li>Set the Type to Custom</li>
<li>Select Advanced as the subtype</li>
<li>Set Field A -> Extract A to "Hostname" and
"(xyz.google.com)" respectively</li>
<li>Set Field B -> Extract B to "Request URI" and "(.*)"
respectively</li>
<li>Set Output To -> Constructor to "Request URI" and
"$A1$B1" respectively</li>
<li>Check Field A Required</li>
<li>Uncheck Field B Required</li>
<li>Check Override Output Field</li>
<li>Uncheck Case Sensitive</li>
<li>Save<br />
</li>
</ol>
This will track subdomain URLs separately from the primary
domain URLs within your primary view. This will help differentiate traffic between your core domain and subdomains as well as between the subdomains themselves.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-JU9GY02qFew/VHTxfR-kRHI/AAAAAAAAjbs/zXOgTrJUB2k/s1600/analytics-subdomain-view-filter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-JU9GY02qFew/VHTxfR-kRHI/AAAAAAAAjbs/zXOgTrJUB2k/s1600/analytics-subdomain-view-filter.png" height="320" width="203" /></a></div>
<br />
<br />
<b>Finally, the new Views for the subdomains need to be setup:</b><br />
<br />
<ol>
<li>Go to Admin and click on the dropdown at the top of the View
column</li>
<li>Click Create New View</li>
<li>Set the name of the view as the full domain (i.e
xyz.google.com)</li>
<li>Set the timezone as the same as your primary view (Eastern)</li>
<li>Save this new view and click on Filters under the Admin >
View column</li>
<li>Click +New Filter</li>
<li>Name it Subdomain Only</li>
<li>Set the Type to Predefined and "Include Only" "traffic to
the hostname" "that contain"</li>
<li>Set the Hostname to the full subdomain (i.e. xyz.google.com)</li>
<li>Save</li>
</ol>
<br />
This will add a new view tracking only the traffic on the
subdomain.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-2lBOFgYLPvs/VHTxB2XAnTI/AAAAAAAAjbk/tduO25b84Ps/s1600/analytics-subdomain-filter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-2lBOFgYLPvs/VHTxB2XAnTI/AAAAAAAAjbk/tduO25b84Ps/s1600/analytics-subdomain-filter.png" height="211" width="320" /></a></div>
<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-71501492549342029022014-11-12T15:43:00.000-08:002014-11-12T15:43:02.410-08:00Samsung Galaxy S5 Black Friday Deals at WalMart, Target, and Best BuyIf you have been waiting to pick up a Galaxy S5 until Black Friday comes around, you are in luck this year! Wal-Mart, BestBuy, and Target all have excellent deals.<br />
<br />
Starting at Best Buy, you can get the Galaxy S5 for $1. A few hours later, you can get the Galaxy S5 for $0.01 at Target. The next morning, on Black Friday itself, you can get paid $1 to get the Galaxy S5 from Wal-Mart. Yes, they charge $99 and include a $100 Wal-Mart gift card! All of these deals require new/upgrade 2yr contracts of course, but that doesn't mean there aren't good cash and finance deals as well. I will be looking for a good deal on a Galaxy Note 4 but will likely go for the practically free Galaxy S5.<br />
<br />
<a href="http://blackfriday.bestbuy.com/?category=doorbusters" target="_blank">Samsung Galaxy S5 at Best Buy for $1 at 5pm Thanksgiving, Day before Black Friday</a><br />
<br />
<a href="http://9to5google.com/2014/11/10/black-friday-galaxy-s5-chromecast-deals/" target="_blank">Samsung Galaxy S5 at Target for $0.01 at 9pm Thanksgiving, Day before Black Friday</a><br />
<br />
<a href="http://news.walmart.com/news-archive/2014/11/12/the-new-black-friday-exclusively-at-walmart" target="_blank">Samsung Galaxy S5 at Wal-Mart for -$1 at 6am on Black Friday ($99 w/ $100 Gift Card Included)</a><br />
<br />
Good luck to all you Black Friday shoppers!<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-22172782915660533802014-09-07T21:41:00.000-07:002014-09-07T21:41:22.409-07:00ColdFusion CFHTTP and WebDavAs it turns out, I recently ran into the need to interface with a WebDAV server via ColdFusion. Surprisingly, there is practically no information online regarding this topic. I just wrapped up a test PUT file snippet and figured I would share it with all you ColdFusioneers who aren't finding the copy/paste you are looking for :-).<br />
<br />
First off, you can't simply use cfhttpparam type="file" as you'll end up sending file data with headers included in the body of the request. The WebDAV server will write these headers into the file content and you'll end up with a corrupted file. Instead, the WebDAV spec calls for the entire body of the request to be file data.<br />
<br />
That leads us to the following code. You'll see this is uploading a simple jpeg to the WebDAV server. The cffile readbinary file attribute is being set to the full local file path. The cfhttpparams include the typical Content-Type header, but also Overwrite and Translate which are commonly supported and sometimes required by WebDAV servers. The body of the request is the raw binary file data. Simple!<br />
<br />
<div class="code">
<br />
<cffile action="readBinary" file="#loc.images[curr_image]#" variable="loc.curr_binary"><br />
<cfhttp method="PUT"<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>url="#This.webDAVsettings.server#/import/#loc.curr_filename#.jpg"<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>username="#This.webDAVsettings.username#" password="#This.webDAVsettings.password#"<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>throwonerror="true"><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><cfhttpparam type="header" name="Content-Type" value="image/jpeg"><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><cfhttpparam type="header" name="Overwrite" value="T"><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><cfhttpparam type="header" name="Translate" value="F"><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><cfhttpparam type="body" value="#loc.curr_binary#"><br />
</cfhttp></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-67243994020614731652014-09-02T12:54:00.001-07:002014-09-02T12:54:31.420-07:00Taming the Elusive 5 Column Bootstrap LayoutTwitter Bootstrap is one of the best things to happen to web development in recent years. Bootstrap 3 brought some awesome changes, but still there are troublesome areas. One the most common is setting up 5 column rows. Sure, you can build a custom compiled Bootstrap with a 20 column grid system or something, but most of us use vanilla Bootstrap. Thanks to <a href="http://www.wearesicc.com/quick-tips-5-column-layout-with-twitter-bootstrap/" target="_blank">SICC</a>, we have a great solution for 5 column rows in Bootstrap 3. Simply add the following to a custom CSS file (no need to alter the Bootstrap CSS).<br />
<br />
<h2>
Core CSS</h2>
<div class="code">
.col-xs-15,<br />
.col-sm-15,<br />
.col-md-15,<br />
.col-lg-15 {<br />
position: relative;<br />
min-height: 1px;<br />
padding-right: 10px;<br />
padding-left: 10px;<br />
}<br />
.col-xs-15 {<br />
width: 20%;<br />
float: left;<br />
}<br />
@media (min-width: 768px) {<br />
.col-sm-15 {<br />
width: 20%;<br />
float: left;<br />
}<br />
}<br />
@media (min-width: 992px) {<br />
.col-md-15 {<br />
width: 20%;<br />
float: left;<br />
}<br />
}<br />
@media (min-width: 1200px) {<br />
.col-lg-15 {<br />
width: 20%;<br />
float: left;<br />
}<br />
}</div>
<br />
<br />
<h2>
General Use</h2>
<div class="code">
<div class="row"><br />
<div class="col-md-15"><br />
...<br />
</div><br />
<div class="col-md-15"><br />
...<br />
</div><br />
<div class="col-md-15"><br />
...<br />
</div><br />
<div class="col-md-15"><br />
...<br />
</div><br />
<div class="col-md-15"><br />
...<br />
</div><br />
</div></div>
<br />
<br />Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-79629023888883487022014-04-13T21:33:00.000-07:002014-04-13T21:33:21.565-07:00Forcing Google to NOT Crawl and Index Mobile SiteLet's assume you've got a desktop site and a mobile version on different subdomains or subdirectories. Google might actually index your mobile site's URLs and serve them up in desktop search results... yes, I have been a victim of this and seen it myself (see screenshot below). It is surprising, but Google can't get this right 100% of the time.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-UTRxGaePO6A/U0tjyrnLQfI/AAAAAAAAcss/E2TCjn9w_8c/s1600/example.PNG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-UTRxGaePO6A/U0tjyrnLQfI/AAAAAAAAcss/E2TCjn9w_8c/s1600/example.PNG" height="67" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Desktop search result with mobile URL in Google index</td></tr>
</tbody></table>
<b>So what do you do?</b><br />
<br />
You have two main options to force Google desktop to crawl your desktop site and Google mobile to crawl your mobile site. Setup <a href="https://www.google.com/webmasters/tools/" target="_blank">Webmaster Tools</a> to specify this setup and/or modify your robots.txt. The Webmaster Tools option pretty much consists of registering both sites in Webmaster Tools and setting up distinct sitemaps. This still leaves a lot to chance assuming Google will take these directions when indexing your URLs. Also, <b>this only works if your mobile site is on a different subdomain</b>. If your mobile site resides in a subdirectory, like <a href="http://www.gtautomax.com/">http://www.gtautomax.com</a>'s mobile site (http://www.gtautomax.com/mobile/), then you are left with robots.txt modification as your main option.<br />
<br />
<b>The strategy is to direct Googlebot and other desktop search bots away from the mobile site and allow Googlebot-Mobile and other mobile search bots to access the mobile site. (</b>This assumes your site is already redirecting users from desktop URLs to mobile URLs automatically.)<br />
<br />
<br />
<b>If you have different desktop/mobile subdomains, your robots.txt will look something like this:</b><br />
<br />
<b>Desktop Site:</b><br />
User-agent: Googlebot<br />
User-agent: Slurp<br />
User-agent: bingbot<br />
Allow: /<br />
<br />
User-agent: Googlebot-Mobile<br />
User-Agent: YahooSeeker/M1A1-R2D2<br />
User-Agent: MSNBOT_Mobile<br />
Disallow: /<br />
<br />
<b>Mobile Site:</b><div>
<div>
User-agent: Googlebot</div>
<div>
User-agent: Slurp</div>
<div>
User-agent: bingbot</div>
<div>
Disallow: /</div>
<div>
<br /></div>
<div>
User-agent: Googlebot-Mobile</div>
<div>
User-Agent: YahooSeeker/M1A1-R2D2</div>
<div>
User-Agent: MSNBOT_Mobile</div>
<div>
Allow: /</div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<b>If your desktop/mobile sites are differentiated by subdirectory, then your robots.txt will look something like this:</b><br />
<br />
<b>Desktop Site:</b><br />
User-agent: Googlebot<br />
User-agent: Slurp<br />
User-agent: bingbot<br />
Disallow: /mobile/<br />
<br />
<b>Mobile Site:</b><div>
<div>
<div>
User-agent: Googlebot-Mobile</div>
<div>
User-Agent: YahooSeeker/M1A1-R2D2</div>
<div>
User-Agent: MSNBOT_Mobile</div>
<div>
Allow: /mobile/</div>
</div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b>As always, make sure and check your robots.txt file in <a href="https://www.google.com/webmasters/tools/" target="_blank">Webmaster Tools</a> for any errors and to ensure mobile URLs and desktop URLs are handled properly.</b></div>
Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0tag:blogger.com,1999:blog-1524556630893584656.post-81828406991147740942014-03-14T09:41:00.001-07:002014-03-17T09:14:01.157-07:00Google AdWords Event Based Conversion TrackingIf you've ever implemented AdWords, you've probably dropped a conversion tracking snippet in your code that looks like this:<br />
<br />
<div class="code" style="background:#FFFFFF;color:#000000;">
<span style="color: #38761d;"><!-- Google Code for Conversion Page --></span><br />
<script type="text/javascript"><br />
/* <![CDATA[ */<br />
var google_conversion_id = <span style="color: #999999;">123456789</span>;<br />
var google_conversion_language = "en";<br />
var google_conversion_format = "3";<br />
var google_conversion_color = "ffffff";<br />
var google_conversion_label = "<span style="color: #999999;">12345678-123-123456</span>";<br />
var google_remarketing_only = false;<br />
/* ]]> */<br />
</script><br />
<script src="//www.googleadservices.com/pagead/conversion.js" type="text/javascript"><br />
</script><br />
<noscript><br />
<div style="display:inline;"><br />
<img height="1" width="1" style="border-style:none;" alt="" src="//www.googleadservices.com/pagead/conversion/<span style="color: #999999;">123456789</span>/?label=<span style="color: #999999;">12345678-123-123456</span>&amp;guid=ON&amp;script=0"/><br />
</div><br />
</noscript></div>
<br />
<br />
This works great for success or landing pages, much like simple URL based goals in Analytics. However, just like Analytics, simple URL based goal/conversion tracking doesn't always cut it. Analytics offers Event tracking to allow more control over tracking goal conversions (<a href="http://mrrobwad.blogspot.com/2013/10/no-code-required-auto-analytics-event-tracking-with-google-tag-maanger.html" target="_blank">even automatically</a>).<br />
<br />
So what about when you need to track AdWords conversions more programmatically though? For example, tracking a link click as an AdWords conversion. Luckily, the AdWords conversion logic makes this easy. See the <noscript> part of the tracking snippet? Typically the most useless part of the code, this is actually our lucky break. Throw in some jQuery and you've got event based AdWords conversion tracking! I also threw in some Analytics event tracking for good measure (the _gaq.push() portion).<br />
<br />
<br />
<div class="code" style="background:#FFFFFF;color:#000000;">
$(document).ready( function() {<br />
$("#conversionLink").click( function() {<br />
$('body').append('<br /><div style="display: inline;"><img alt="" height="1" src="//www.googleadservices.com/pagead/conversion/<span style="color: #999999;">123456789</span>/?label=<span style="color: #999999;">12345678-123-123456</span>&amp;guid=ON&amp;script=0" style="border-style: none;" width="1" /></div>');<br />
_gaq.push(['_trackEvent','Conversion Link', 'Conversion Link Click']);<br />
});<br />
});</div>Robert Waddellhttp://www.blogger.com/profile/01496512970066846255noreply@blogger.com0