Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

April 6, 2013

JavaScript: Use the goo.gl link shortener from your own site

Here's a quick, stripped-down version of a javascript implementation of the goo.gl link-shortener service. It asynchronously loads the Google client API, then uses another callback when the link shortener service is loaded. After the service loads, you can call shortenUrl() as many times as you'd like. For simplicity, I've only shortened one URL here. It doesn't appear that you need an API key to simply shorten URLs, but certain calls to this service would require one. Here's the basic version, which should work in modern browsers.
var shortenUrl = function() {
  var request = gapi.client.urlshortener.url.insert({
    resource: {
      longUrl: 'http://plasticsoundsupply.com'
    }
  });
  request.execute(function(response) {
    var shortUrl = response.id;
    console.log('short url:', shortUrl);
  });
};

var googleApiLoaded = function() {
  // gapi.client.setApiKey("YOUR API KEY")
  gapi.client.load("urlshortener", "v1", shortenUrl);
};

window.googleApiLoaded = googleApiLoaded;
$(document.body).append('<script src="https://apis.google.com/js/client.js?onload=googleApiLoaded"></script>');

September 11, 2012

Obj-C: Center a view horizontally with autoresizingMask properties

Cocoa's UIView layout system gives the developer some nice tools to automatically resize and reposition views inside of each other. This is usually done in the nib/xib/storyboard editor with the Origin/Autoresizing View property inspectors, and honestly it's never made a ton of sense to me. I'm only an occasional obj-c dev, so bear with me. I wanted to horizontally center and fill some UIView elements to fit the width of different devices and orientations, and I knew there should be a simple way to accomplish this. I came up with several methods to help apply these settings to any number of outer containers that should fill the width of the parent view, and inner containers that should be centered within:
- (void)setViewCentered:(UIView*)view
{
    view.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
}

- (void)setContainerToParentWidth:(UIView*)view
{
    view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}

- (int)getHorizCenterFromView:(UIView*)view
{
    return (self.view.bounds.size.width - view.frame.size.width)/2;
}

- (int)getHorizCenterFromInt:(int)width
{
    return (self.view.bounds.size.width - width)/2;
}
The key to centering a fixed-width view - this was the confusing part for me - is setting the x-position of the frame to 1/2 of the width of the parent container. The associated example call to center a view is here. From my researching, it seems that the autoresizingMask property should be changed after addSubview
    int viewW = 320;
    _controlsContainer = [[UIView alloc] initWithFrame:CGRectMake([self getHorizCenterFromInt:viewW], 0, viewW, controlsH)];
    [self.view addSubview:_controlsContainer];
    [self setViewCentered:_controlsContainer];
Note that self.view is the outer container that we're centering inside of. Then if you simply want to fill a container to the width of the parent, use something like this:
    _header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
    [self.view addSubview:_header];
    [self setContainerToParentWidth:_header];
Happy centering without having to change a UIView's frame!

March 6, 2012

Bookmarklet: Scrape a web page for YouTube videos and generate a playlist

I wrote this little script to capture about 50 YouTube videos from a music blog I was browsing. I figured it would be useful for others, so I turned it into a bookmarklet:
// get html source
var src = document.getElementsByTagName('html')[0].innerHTML;
// handle old youtube vids, and https
var vids = src.replace(/\"/g).replace(/http:\/\/www.youtube.com\/v\//gi,'http://www.youtube.com/embed/').replace(/https/gi,'http');
// parse the source for youtube embeds
var vidArr = vids.split('http://www.youtube.com/embed/')
var links = '';
// build string for playlist
var playlist = 'http://www.ytplaylist.com/?pl=';
// build links
if(vidArr.length > 1) {
// keep track of IDs so we don't get duplicates
var videos = [];
for(var i=1; i < vidArr.length; i++) {
// grab 11-character YouTube ID
var ytID = vidArr[i].substr(0,11);
if(videos.indexOf(ytID) == -1) {
// add link to video, and to playlist link
var link = 'http://www.youtube.com/watch?v='+ytID;
links += '<a target="_blank" href="'+link+'">'+link+'</a><br/>';
playlist += ytID+';';
videos.push(ytID);
}
}
playlist = playlist.substr(0,playlist.length-1);
// draw it to the page
var container = document.createElement('div');
container.innerHTML = 'Videos Found:<br/><br/>'+links+'<br/><br/>'+'<a target="_blank" href="'+playlist+'">View as Playlist</a><br/>';
container.style.position='fixed';
container.style.background='#fff';
container.style.border='5px solid black';
container.style.padding='10px';
container.style.top='0';
container.style.right='0';
container.style.zIndex='999';
document.body.appendChild(container);
} else {
alert('No YouTube videos found');
}
And here's the bookmarklet link text:
javascript:(function(){var%20src%20%3D%20document.getElementsByTagName('html')%5B0%5D.innerHTML%3B%0Avar%20vids%20%3D%20src.replace(%2F%5C%22%2Fg).replace(%2Fhttp%3A%5C%2F%5C%2Fwww.youtube.com%5C%2Fv%5C%2F%2Fgi%2C'http%3A%2F%2Fwww.youtube.com%2Fembed%2F').replace(%2Fhttps%2Fgi%2C'http')%3B%0Avar%20vidArr%20%3D%20vids.split('http%3A%2F%2Fwww.youtube.com%2Fembed%2F')%0Avar%20links%20%3D%20''%3B%20%0Avar%20playlist%20%3D%20'http%3A%2F%2Fwww.ytplaylist.com%2F%3Fpl%3D'%3B%20%0A%2F%2F%20build%20links%0Aif(vidArr.length%20%3E%201)%20%7B%0A%20%20var%20videos%20%3D%20%5B%5D%3B%0A%20%20for(var%20i%3D1%3B%20i%20%3C%20vidArr.length%3B%20i%2B%2B)%20%7B%20%0A%20%20%20%20var%20ytID%20%3D%20vidArr%5Bi%5D.substr(0%2C11)%3B%0A%20%20%20%20if(videos.indexOf(ytID)%20%3D%3D%20-1)%20%7B%0A%20%20%20%20%20%20var%20link%20%3D%20'http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D'%2BytID%3B%0A%20%20%20%20%20%20links%20%2B%3D%20'%3Ca%20target%3D%22_blank%22%20href%3D%22'%2Blink%2B'%22%3E'%2Blink%2B'%3C%2Fa%3E%3Cbr%2F%3E'%3B%0A%20%20%20%20%20%20playlist%20%2B%3D%20ytID%2B'%3B'%3B%0A%20%20%20%20%20%20videos.push(ytID)%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20playlist%20%3D%20playlist.substr(0%2Cplaylist.length-1)%3B%0A%20%20var%20container%20%3D%20document.createElement('div')%3B%0A%20%20container.innerHTML%20%3D%20'Videos%20Found%3A%3Cbr%2F%3E%3Cbr%2F%3E'%2Blinks%2B'%3Cbr%2F%3E%3Cbr%2F%3E'%2B'%3Ca%20target%3D%22_blank%22%20href%3D%22'%2Bplaylist%2B'%22%3EView%20as%20Playlist%3C%2Fa%3E%3Cbr%2F%3E'%3B%0A%20%20container.style.position%3D'fixed'%3B%0A%20%20container.style.background%3D'%23fff'%3B%0A%20%20container.style.border%3D'5px%20solid%20black'%3B%0A%20%20container.style.padding%3D'10px'%3B%0A%20%20container.style.top%3D'0'%3B%0A%20%20container.style.right%3D'0'%3B%0A%20%20container.style.zIndex%3D'999'%3B%0A%20%20document.body.appendChild(container)%3B%0A%7D%20else%20%7B%0A%20%20alert('No%20YouTube%20videos%20found')%3B%0A%7D}());

September 1, 2011

Google Maps API: Polyline encoding/decoding issue

I'm using google's Geometry Library to encode large numbers of GPS location points into a compressed format for storage. You can add this capability to your .js by adding it to the quersystring in the javascript reference:
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=geometry&sensor=true"></script>
The library provides a very easy way to display a path, add points realtime, store this path as an encoded & compressed string, and feed that string back into a new map when you're ready to display it later. You can see a live example here.

Encoding works great, but I ran into an issue where some of my encoded paths, generated by google.maps.geometry.encoding.encodePath() from my google.maps.Polyline object, would have major errors when using the google.maps.geometry.encoding.decodePath() method. My Polyline would have random right-angle turns that effectively ruined my path. I played around with the encoded string, trying to figure out what was causing the issue, to no avail. I found another implementation of the polyline encoding algorithm, and found an explanation of what was causing the issue.

It turns out that you need to escape the backslashes that may appear in the output string from google.maps.geometry.encoding.encodePath() (and the other library linked to above). So if you're storing the string for later, you want to do something like this:
var encodedPath = google.maps.geometry.encoding.encodePath( _poly.getPath() ).replace(/\\/g,'\\\\');
You can then feed that encoded string into a new Map's Polyline instance like so:
_poly.setPath( google.maps.geometry.encoding.decodePath( encodedPath ) );
It seems like an oversight that this double-backslash issue isn't mentioned in the Google documentation. I spent hours before trying to figure out the problem in my mobile app before coming across the fix.

Finally, after you set a decoded path as the data for a Polyline, use the following code to fit the Map to the bounds of your path:
var bounds = new google.maps.LatLngBounds();
var path = _poly.getPath();
path.forEach(function( latlng ) {
	bounds.extend( latlng );
});
_map.fitBounds( bounds );	

January 24, 2011

Bash shell script: Scraping and downloading image files from a ffffound RSS feed

I wrote this little script for a friend, as an exercise in bash shell scripting. This script is for OS X.

Step 1:
Install Homebrew - this is a great tool for installing common Unix tools. You should only have to open Terminal, and paste the 1-line installation script found in the link above. Something like this:
ruby -e "$(curl -fsSLk https://gist.github.com/raw/323731/install_homebrew.rb)"

Step 2:
Install wget with Homebrew: type this into Terminal and press Enter:
brew install wget

Step 3:
Save the following code into a text file called "ffffound_sssswiped.sh" and save it into your User/Pictures/ directory:
curl http://feeds.feedburner.com/ffffound/everyone | egrep -o source\ url=\"http://[^[:space:]]*.\(jpg\|png\|gif\) | egrep -o http://[^[:space:]]*.\(jpg\|png\|gif\) | xargs wget -nc -P ~/Pictures/ffffound

Step 4:
Customize! You can replace http://feeds.feedburner.com/ffffound/everyone with your own ffffound RSS feed, or anyone else's.

Step 5:
Run the script: type the following into Terminal, and hit Enter:
bash ~/Pictures/ffffound_sssswiped.sh

You should see the download progress as it scrapes the RSS feed for just the large-format image files. You can run this as often as you want, and it will skip any files you've already downloaded.

Magic!

January 19, 2011

Android + Phonegap: Scale the WebView to fit the device

I was porting an iPad app to Android for the new Samsung tablet, and I had some trouble getting my web view to scale to the size of the device screen so that I wouldn't have to resize any of my assets. Obviously this is a questionable tactic, but I was experimenting and wanted to see how it would look :)

Here's the meat of my main App.java class:
public class App extends DroidGap {
 
 // declare the original size of the iPad app
 protected float ORIG_APP_W = 768;
 protected float ORIG_APP_H = 1004;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.loadUrl("file:///android_asset/www/index.html");
        
     // set some defaults
     this.appView.setBackgroundColor(0x000000);
     this.appView.setHorizontalScrollBarEnabled(false);
     this.appView.setHorizontalScrollbarOverlay(false);
     this.appView.setVerticalScrollBarEnabled(false);
     this.appView.setVerticalScrollbarOverlay(false);
     
     // get actual screen size
     Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
     int width = display.getWidth(); 
     int height = display.getHeight(); 
     
     // calculate target scale (only dealing with portrait orientation)
     double globalScale = Math.ceil( ( width / ORIG_APP_W ) * 100 );
     
     // make sure we're all good
     Log.v( "ORIG_APP_W", " = " + ORIG_APP_W );
     Log.v( "ORIG_APP_H", " = " + ORIG_APP_H );
     Log.v( "width", " = " + width );
     Log.v( "this.appView.getMeasuredHeight()", " = " + height );
     Log.v( "globalScale", " = " + globalScale );
     Log.v( "this.appView.getScale()", "index=" + this.appView.getScale() );
    
     // set some defaults on the web view
     this.appView.getSettings().setBuiltInZoomControls( false );
     this.appView.getSettings().setSupportZoom( false );
     this.appView.getSettings().setGeolocationEnabled( true );
     this.appView.getSettings().setLightTouchEnabled( true );
     this.appView.getSettings().setRenderPriority( RenderPriority.HIGH );
     
     // set the scale
     this.appView.setInitialScale( (int)globalScale );
   }
}
I also updated the AndroidManifest.xml file to lock the app into portrait orientation, work on tablet-sized devices, have a nice app name, and give the device access to the Internet and geolocation:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.phonegap.testapp"
      android:versionCode="1"
      android:versionName="1.0">     
     
    <application android:icon="@drawable/icon" 
        android:label="@string/app_name"
        android:debuggable="true">
        <activity android:name=".App" 
                  android:label="Test App" 
                  android:configChanges="orientation|keyboardHidden"
                  android:noHistory="true" 
                  android:stateNotNeeded="true" 
                  android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <!-- allows access to phonegap hardware features -->
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-->
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />


   <supports-screens
     android:largeScreens="true"
     android:normalScreens="false"
     android:smallScreens="false"
     android:resizeable="true"
     android:anyDensity="true"
     />

</manifest> 
And finally my res/layout/main.xml file, though I'm not sure if this is different from the Phonegap default:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >    
            <WebView android:id="@+id/appView"
            android:layout_height="fill_parent"
            android:layout_width="fill_parent"
            /> 
</LinearLayout>
I hope this helps someone port their hybrid html5 iPad app to Android tablets.

Javascript: Clear a webkitTransition animation

I was animating an html element with Webkit transitions via javascript, and after the animation was done, I applied non-animated webkitTransform positioning to the same element. It animated instead of immediately displaying the new style. I came up with the following function to clear any previous Webkit animation values:
function clearAnimation( element ) {
  if( typeof element.style.webkitTransform !== 'undefined' && element.style.webkitTransform ) {   // 2nd conditional fixes bug in Chrome on windows
    element.style.webkitTransition = '';
    element.style.webkitTransform = '';
  }
}
This will prevent any overlap when switching between animatiions and instant repositioning.

January 11, 2011

Actionscript: &nbsp; / HTML text issue from an old Flash 8 project

I built a project back in 2006 that's amazingly still alive and making lots of money for a client. I had a bit of code that would insert an &nbsp; into a textfield for dummy spacing in between other letters. Out of nowhere, this stopped working for the client, and all the text was space-less. This is what I had:
StringUtil.searchReplace( myStr, _dummyChar, '<span class="textDummy">&nbsp;</span>' );
This now fails to insert a space. I tried just using an actual space, but since this is an html textfield, multiple consecutive spaces don't keep making more room. So, I added a space after the &nbsp;, and magically, it works like it had in previous years:
StringUtil.searchReplace( myStr, _dummyChar, '<span class="textDummy">&nbsp;</span> ' );
Whew fun!

September 8, 2010

Javascript / CSS animated text fire effect

A coworker sent me a funny example of a text-shadow CSS fire effect. I had a little time to kill, so I took the example and created an animated version using javascript. It's not very realistic, but it is highly silly:


Copy and paste the code into an html file to try it out:
<!DOCTYPE html>
<html>
  <head>
    <title>Fire</title>

    <script type="text/javascript">
        function fireText()
        {
            var FireColorStop = function( xPos, yPos, blur, color )
            {
                this.x = xPos;
                this.y = yPos;
                this.blur = blur;
                this.color = color;
                this.oscSpeed = Math.random() * Math.abs( yPos ) / 75;
                this.oscIncrement = 0;
                this.xOffset = 0;
                this.yOffset = 0;
                this.blurOffset = 0;
            };
        
            FireColorStop.prototype.oscillate = function() 
            {
                this.oscIncrement += this.oscSpeed;
                this.xOffset = Math.sin(this.oscIncrement) * this.blur / 3;
                this.yOffset = Math.sin(this.oscIncrement) * 1;
                this.blurOsc = this.blur + 10 + Math.sin(this.oscIncrement) * 3;
            };
        
            FireColorStop.prototype.getCSS = function() 
            {
                return ( this.x + this.xOffset ) + 'px ' + ( this.y + this.yOffset ) + 'px ' + this.blurOsc + 'px ' + this.color; 
            };
            
            // create objects for each color stop for independent animation
            var fireColors = [  new FireColorStop(0,  0,  4,  '#FFFFFF'),
                                new FireColorStop(0, -5,  4,  '#FFFF33'),
                                new FireColorStop(2, -10, 6,  '#FFDD33'),
                                new FireColorStop(-2,-15, 11, '#FF8800'),
                                new FireColorStop(2, -25, 18, '#FF2200')
                                ];
        
            var fps = 1000/30;
            var text = document.getElementById('fireText');
            
            // oscillate color stops and rebuild fire css
            setInterval( function(){ 
                var shadowCSS = '';
                for( var i = 0; i < fireColors.length; i++ )
                {
                    fireColors[i].oscillate();
                
                    shadowCSS += fireColors[i].getCSS();
                    if( i < fireColors.length - 1 )
                        shadowCSS += ', ';
                }
                text.style.textShadow = shadowCSS;
            }, fps );
        }
    </script>
        
    <style>
        body, html {
            background-color:black;
        }
        #fireText {
            background-color:black;
            position:absolute;
            display:block;
            width:100%;
            height:300px;
            line-height:300px;
            color:white;
            font-family: Arial, Verdana, sans-serif;
            font-size:50px;
            font-weight:bold;
            text-align:center;
        }
    </style>
    
  </head>
  <body>
    <div id="fireText">
        Yeah Dude.
    </div>
    <script type="text/javascript">
        fireText();
    </script>
  </body>
</html>

April 2, 2009

ActionScript 3: Easily generate code to draw a complex image

In Flash, I always do everything in a programmatic way if possible. This includes drawing shapes with code instead of having library items. This is especially true, now that the preferred method of compiling is using the Flex mxmlc compiler. Sometimes, though, certain things are too much of a pain to draw pixel by pixel. No longer though, as there's a REALLY simple way to generate the code to draw complex shapes for you.

Step 1: save a .png of the image you want drawn.
Step 2: open Flash, and drop your image into the library, giving it a linkage ID of "MyGraphic".
Step 3: drop the following code into the actions panel and publish.
Step 4: copy the output code and apply it to a BitmapData object in your project.

var mc : MyGraphic = new MyGraphic( null, null );
for( var i:int = 0; i < mc.width; i ++)
{
for( var j:int = 0; j < mc.height; j ++)
{
if( mc.getPixel32( i, j ) != 0 ) trace( "bmp.setPixel32( "+i+", "+j+", "+mc.getPixel32( i, j )+" );" );
}
}



the resulting code in my case looked like this:

public static function drawVideoPlayhead():Bitmap
{
var bitmap:Bitmap = new Bitmap();
var bmp:BitmapData = new BitmapData( 7, 8, true, 0x00000000 );

// begin generated code
bmp.setPixel32( 0, 3, 4289309097 );
bmp.setPixel32( 0, 4, 4288322202 );
bmp.setPixel32( 0, 5, 4287664272 );
bmp.setPixel32( 0, 6, 4286874756 );
bmp.setPixel32( 1, 2, 4290888129 );
bmp.setPixel32( 1, 3, 4291085508 );
bmp.setPixel32( 1, 4, 4290822336 );
bmp.setPixel32( 1, 5, 4290032820 );
bmp.setPixel32( 1, 6, 4288256409 );
bmp.setPixel32( 1, 7, 4286874756 );
bmp.setPixel32( 2, 1, 4291677645 );
bmp.setPixel32( 2, 2, 4291875024 );
bmp.setPixel32( 2, 3, 4291677645 );
bmp.setPixel32( 2, 4, 4290822336 );
bmp.setPixel32( 2, 5, 4290032820 );
bmp.setPixel32( 2, 6, 4289374890 );
bmp.setPixel32( 2, 7, 4287598479 );
bmp.setPixel32( 3, 0, 4293322470 );
bmp.setPixel32( 3, 1, 4293388263 );
bmp.setPixel32( 3, 2, 4292335575 );
bmp.setPixel32( 3, 3, 4291677645 );
bmp.setPixel32( 3, 4, 4290822336 );
bmp.setPixel32( 3, 5, 4290032820 );
bmp.setPixel32( 3, 6, 4289374890 );
bmp.setPixel32( 3, 7, 4287598479 );
bmp.setPixel32( 4, 1, 4294046193 );
bmp.setPixel32( 4, 2, 4293256677 );
bmp.setPixel32( 4, 3, 4291677645 );
bmp.setPixel32( 4, 4, 4290822336 );
bmp.setPixel32( 4, 5, 4290032820 );
bmp.setPixel32( 4, 6, 4289374890 );
bmp.setPixel32( 4, 7, 4287598479 );
bmp.setPixel32( 5, 2, 4293717228 );
bmp.setPixel32( 5, 3, 4292861919 );
bmp.setPixel32( 5, 4, 4290822336 );
bmp.setPixel32( 5, 5, 4290032820 );
bmp.setPixel32( 5, 6, 4289703855 );
bmp.setPixel32( 5, 7, 4288059030 );
bmp.setPixel32( 6, 3, 4293914607 );
bmp.setPixel32( 6, 4, 4293190884 );
bmp.setPixel32( 6, 5, 4292730333 );
bmp.setPixel32( 6, 6, 4291677645 );
// end generated code

bitmap.bitmapData = bmp;
return bitmap;
}


Notes: using the setPixel32() function, and defaulting the BitmapData to a background color of 0x00000000 ensures that your Bitmap will have a transparent background. Note that the code here ignores empty pixels for efficiency. Finally, this code could easily be converted to draw into a Shape or Sprite, but for my purposes, bitmaps are usually what I want. Hope you find this useful!