Resume
Daniel Talsky
332 Marcus Garvey Blvd. #3F
Brooklyn, NY 11221
206.459.3722
danieltalsky@gmail.com
I get web applications built.
I can write database schema, turn a Photoshop file into a standards-compliant web page, add JavaScript-driven animations, and all the business logic
in-between. I have worked as an independent contractor, building applications for small and medium-sized companies, and I have written production C#
and Java code handling terabytes of data for a Seattle corporation. I take a web concept, make a coherent specification that clients and programmers
can understand, and then get it built and implemented to modern professional standards.
Tools I use:
PHP 4/5, C#.NET, Java, Ruby, JavaScript, Python, Shell
X/HTML, CSS, XML, XSLT, Smarty
MSSQL, Oracle, MySQL, PostgreSQL, SQLite
DemandWare, AJAX, jQuery, Drupal, CakePHP, CodeIgniter, WordPress
Linux, Windows, OS X, Apache, IIS, Eclipse, Adobe CS, SVN/Git
My work history:
October 2011 - February 2012: Web Developer, Bandwidth Productions, Inc.
Accomplishments
-
· Implemented eCommerce for several clients
-
· Integrated custom CMS with websites
-
· Wrote a cross-platform video processing queue using ffmpeg
Clients
-
Museum of the Moving Image
-
Middle Collegiate Church
-
Trinity Wall Street Church
February 2012 - Current: Web Developer, The Jones Group, Inc.
Accomplishments
-
· Did rapid eCommerce rollouts using the DemandWare platform
-
· Extended a common codebase for multiple apparel brands, including Nine West, Kurt Geiger, Easy Spirit, and Anne Klein
December 2010 - August 2011: Web Developer, Big Fish Games
Accomplishments
-
· Spearheaded a project to create a Solr-driven search tool (currently in production) and brought the project to completion.
-
· Wrote CSRF protection of account pages for PCI compliance.
July 2009 - December 2010: Independent Contractor
Accomplishments
-
· Worked with a designer to design and deploy complete web sites and integrated web applications
-
· Used a lightweight web-development stack to rapidly develop custom content management systems
-
· Wrote a secure, classified web application for a military contractor
Clients
-
Ross Hogin Design
-
Valley Furniture
-
Mobius Industries USA, LLC
October 2007 - June 2009: Sr. Developer, Serials Solutions
Accomplishments
-
· Wrote a high-availability SOAP-based XML API in C#.NET
-
· Improved output performance of an API framework by 500%
-
· Maintained Java-based internal tools for internal librarians to manage data
-
· Wrote scholarly articles on library and technology for public outreach. Example: http://journal.code4lib.org/articles/108
-
· Led several educational brown bags - by manager invitation - to teach scripting to non-programmer employees, enabling them to write their own
simple automated tools, saving developer resources
March 2003 - October 2007: Application Architect, Roboticat Communications
Accomplishments
-
· Created web applications from scratch using an MVC architecture
-
· Integrated new web applications with legacy databases
-
· Managed web projects both solo and with multiple programmers and scheduling constraints
-
· Used YSlow!, caching, optimization and other tools to create the most high-performance web applications possible
-
· Coordinated copywriters, visual designers and photographers for an online shopping cart that increased overall sales by 20%
Clients
-
Teague Inc.
-
Pyramid Communications
-
Viatran Inc.
-
Metric Media
-
King County Housing Authority
-
Housing Development Consortium of King County
-
Pacific Housing Advisors
-
Carol Lambert Arts
-
BzzyBee Insurance
This site is built with the cutting edge front-end web technologies:
HTML5, CSS3 animations, jQuery. It works in all major browsers, including
mobile browsers.
Why? Because I can.
I also work in back-end web languages like PHP, Ruby, C#, Python, and SQL.
Big Fish Games had an outdated search that was costing the company money in lost sales.
I helped architect and build a Solr-based search
that serves hundreds of thousands of requests a day
with modern features like spelling
correction, highlighting, and filtering.
Read the letters of thanks I recieved for this project from the
COO
and
Vice President.
Valley Furniture had thousands of dollars in unsold but
valuable inventory of antique furniture.
I wrote a custom content management system
and a tool to import from their inventory management system.
Soon customers all over the country could easily view their products.
The tool paid for itself in only a few weeks.
I wrote the first data API in SOAP and C#.NET to serve customers raw information from the popular Ulrich's brand.
I wrote Java and Ruby programs to manage and organize Terabytes worth of information about online serials.
Teague does industrial hardware design for clients like HP and Boeing.
They needed to be able to share large files up to 200MB in size and Email
wasn't doing the job. I built a cutting-edge web-based file sharing application
that let their customers and vendors share files with them frictionlessly.
Mobius Industries is a military contractor that develops computer systems for
military field personnel. I wrote a secure web-based tool they could use to manage
personell and their security clearances.
Personal
How I Write Web Applications
Research
I scour modern web development and business blogs daily looking for new trends in web business, branding, SEO, and technology.
When a technology gets hot, I install it and kick the tires so I'm ready when the perfect opportunity arises to use it.
Interviews
My process starts with user interviews. This lets me find complexity early, and set expectations
for the final product. I set the tone that the user is important. These interviews give me a
target to hit; I know what a successful tool will do and get buy-in from the user. If there is an
existing web-based tool available that does the job, then I don't write a new application at all.
Self-Documenting Software
When I develop applications, I don't think about menus and features. I think: "What is the job
that my user wants to get done?" and "How can I help them do it?" In every application I build, I
think: "Could a user see this for the first time and get their task done?"
To accomplish this I use task-based landing pages so that the first page of an application lists
common tasks, good microcopy, so that error messages guide a user naturally to the correct
action, and avoid common interfaces that don't make sense, like a "tools" menu.
Uglyware
I rarely write a detailed written spec for a user or project manager to read. Written specs rarely
allow people to see what needs to be changed. I don't want a spec to defend an application
that doesn't work; I want a spec that people can use to understand how the application will
work.
To accomplish this, I design a visual spec instead. I do simple, plain mockups in HTML, that I call
uglyware, in place of a spec (or database design) and that gets me the kind of comments I need
and gets little details worked out before I architect or write a line of code.
MVC Architecture
Ruby on Rails, CakePHP, Kohana, CodeIgniter... these are all tools I choose based on the server
environment, number of users, development time and performance needs. MVC architecture is
a common paradigm for web for ease of development and maintainability. These tools keep me
from reinventing the wheel each time.
Truly Agile Development
I always assume needs are going to change, so I don't act surprised when they do. I do my
absolute best to keep my application architecture flexible enough that I can make changes.
Code
Some people need to see the actual code. While much of the code I've written for clients needs to stay secret according to my agreements,
here's a few samples showing my code style for you to download and review. If you'd like to see me demonstrate a specific technology,
please let me know, via email.
Samples
CakePHP Controller Code
CakePHP is a PHP framework modeled after Ruby on Rails. This is an example of clean, simple controller code.
<?php
/****************************************************************************
CakePHP MVC (Controller) Sample Code
Copyright (c) 2012 Daniel Talsky
danieltalsky@gmail.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*****************************************************************************/
class SecurityPeopleController extends AppController {
var $name = 'SecurityPeople';
var $helpers = array('Html', 'Form');
var $paginate = array(
'limit' => 50,
'order' => array(
'SecurityPerson.employment_status' => 'asc',
'SecurityPerson.name' => 'asc'
)
);
function index() {
$this->pageTitle = 'List All Security People | Anonymous Security Tool';
$this->SecurityPerson->recursive = 0;
$this->set('securityPeople', $this->paginate());
}
function search_index() {
$this->pageTitle = 'List Security People by Name Contains Search | Anonymous Security Tool';
$this->SecurityPerson->recursive = 0;
if (isset($this->params['url']['nameSearch'])) {
$searchTerm = $this->params['url']['nameSearch'];
$this->set('searchTerm', $searchTerm);
$this->set('securityPeople', $this->paginate(
'SecurityPerson', array('SecurityPerson.name LIKE' => "%$searchTerm%")));
} else {
$this->set('searchTerm', 'No Search Term');
$this->set('securityPeople', $this->paginate());
}
}
function view($id = null) {
$this->pageTitle = 'View Security Person | Anonymous Security Tool';
if (!$id) {
$this->Session->setFlash(__('Invalid SecurityPerson.', true));
$this->redirect(array('action'=>'index'));
}
$this->set('securityPerson', $this->SecurityPerson->read(null, $id));
}
function add() {
$this->pageTitle = 'Add New Security Person | Anonymous Security Tool';
if (!empty($this->data)) {
$this->SecurityPerson->create();
if ($this->SecurityPerson->save($this->data)) {
$this->Session->setFlash(__('The SecurityPerson has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('The SecurityPerson could not be saved. Please, try again.', true));
}
}
$contractNumbers = $this->SecurityPerson->ContractNumber->find('list');
$clearanceLevels = $this->SecurityPerson->ClearanceLevel->find('list');
$locations = $this->SecurityPerson->Location->find('list');
$this->set(compact('contractNumbers', 'clearanceLevels', 'locations'));
}
function edit($id = null) {
$this->pageTitle = 'Edit Security Person | Anonymous Security Tool';
if (!$id && empty($this->data)) {
$this->Session->setFlash(__('Invalid SecurityPerson', true));
$this->redirect(array('action'=>'index'));
}
if (!empty($this->data)) {
if ($this->SecurityPerson->save($this->data)) {
$this->Session->setFlash(__('The SecurityPerson has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('The SecurityPerson could not be saved. Please, try again.', true));
}
}
if (empty($this->data)) {
$this->data = $this->SecurityPerson->read(null, $id);
}
$contractNumbers = $this->SecurityPerson->ContractNumber->find('list');
$clearanceLevels = $this->SecurityPerson->ClearanceLevel->find('list');
$locations = $this->SecurityPerson->Location->find('list');
$this->set(compact('contractNumbers','clearanceLevels','locations'));
}
function delete($id = null) {
$this->pageTitle = 'Delete Security Person | Anonymous Security Tool';
if (!$id) {
$this->Session->setFlash(__('Invalid id for SecurityPerson', true));
$this->redirect(array('action'=>'index'));
}
if ($this->SecurityPerson->del($id)) {
$this->Session->setFlash(__('SecurityPerson deleted', true));
$this->redirect(array('action'=>'index'));
}
}
function admin_index() {
$this->SecurityPerson->recursive = 0;
$this->set('securityPeople', $this->paginate());
}
function admin_view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid SecurityPerson.', true));
$this->redirect(array('action'=>'index'));
}
$this->set('securityPerson', $this->SecurityPerson->read(null, $id));
}
function admin_add() {
if (!empty($this->data)) {
$this->SecurityPerson->create();
if ($this->SecurityPerson->save($this->data)) {
$this->Session->setFlash(__('The SecurityPerson has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('The SecurityPerson could not be saved. Please, try again.', true));
}
}
$contractNumbers = $this->SecurityPerson->ContractNumber->find('list');
$clearanceLevels = $this->SecurityPerson->ClearanceLevel->find('list');
$locations = $this->SecurityPerson->Location->find('list');
$this->set(compact('contractNumbers', 'clearanceLevels', 'locations'));
}
function admin_edit($id = null) {
if (!$id && empty($this->data)) {
$this->Session->setFlash(__('Invalid SecurityPerson', true));
$this->redirect(array('action'=>'index'));
}
if (!empty($this->data)) {
if ($this->SecurityPerson->save($this->data)) {
$this->Session->setFlash(__('The SecurityPerson has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('The SecurityPerson could not be saved. Please, try again.', true));
}
}
if (empty($this->data)) {
$this->data = $this->SecurityPerson->read(null, $id);
}
$contractNumbers = $this->SecurityPerson->ContractNumber->find('list');
$clearanceLevels = $this->SecurityPerson->ClearanceLevel->find('list');
$locations = $this->SecurityPerson->Location->find('list');
$this->set(compact('contractNumbers','clearanceLevels','locations'));
}
function admin_delete($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid id for SecurityPerson', true));
$this->redirect(array('action'=>'index'));
}
if ($this->SecurityPerson->del($id)) {
$this->Session->setFlash(__('SecurityPerson deleted', true));
$this->redirect(array('action'=>'index'));
}
}
}
?>
jQuery AJAX and Animation
This code shows the use of jQuery to display a series of questions to the user, and send the results to the server using AJAX.
/****************************************************************************
jQuery AJAX Sample Code
Copyright (c) 2012 Daniel Talsky
danieltalsky@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
$(document).ready(function(){
/* CONSTANTS */
var toolTipConstants = {
toolTipBorderWidthPx: 2,
tooltipBorderRadiusPx: 8,
tooltipPaddingPx: 10,
tooltipTextAlign: 'left',
tooltipFontSize: '13px'
};
var questionConstants = {
firstQuestionKey: "yourStory"
};
var URLConstants = {
notInterestedRedirection: 'http://www.veryanonymous.com',
ajaxPost: '/ajax_processors/json_post_processor.php'
};
/* Initialize Tooltip Plugin */
$('span.tooltip[title]').qtip({
style: {
border: {
width: toolTipConstants.toolTipBorderWidthPx,
radius: toolTipConstants.tooltipBorderRadiusPx
},
padding: toolTipConstants.tooltipPaddingPx,
textAlign: toolTipConstants.tooltipTextAlign,
tip: true,
name: 'light',
'font-size': toolTipConstants.tooltipFontSize
}
});
// Center questionBox and set modal trigger
$('div#questionBox').center().jqm({modal: true});
// Initialize question variables
var answersHash = {"User started ":new Date().getTime()};
var firstQuestion = questionConstants.firstQuestionKey;
var questionKeyTextHash = {};
var questionIDHistory = [];
/*
EVENTS
*/
// EVENT
// Modal Box Launch (initialize)
$("a.jqModal").click(function(event){
// Initialize the answer array fresh on click
$(".questionModule").each(function(index, item){
var questionKey = $(item).attr("id");
var questionText = $(item).find("label").text();
questionKeyTextHash[questionKey] = questionText;
});
$(".questionModule").hide();
$("#" + firstQuestion).show();
});
// EVENT
// Next Button Handling
$("a.next").click(function(event){
var parentQuestionModule = $(this).closest(".questionModule");
var questionModuleID = parentQuestionModule.attr("id");
var questionValue = questionKeyTextHash[questionModuleID];
var validationMessageElement = $(parentQuestionModule).find("div.validationMessage");
var answerValue = "";
// Default answer registry (last entry trumps)
$(parentQuestionModule).find(".answer").each(function (i) {
answerValue = $(this).val();
});
// Then, stomp the result with a checked radio button if there is one
$(parentQuestionModule).find(".answer:radio:checked").each(function (i) {
answerValue = $(this).val();
});
// If there are checkboxes present, clear the value and append
// each checkbox value with a comma
if ($(parentQuestionModule).find(".answer:checkbox").length > 0){
answerValue = "";
}
$(parentQuestionModule).find(".answer:checkbox:checked").each(function (i) {
answerValue = answerValue + $(this).val() + ", ";
});
if (validateQuestion(questionModuleID, answerValue, validationMessageElement)) {
answersHash[questionValue] = answerValue;
questionIDHistory.push(questionModuleID);
var nextQuestionID = getNextQuestionID(
parentQuestionModule,
questionModuleID,
answersHash,
questionKeyTextHash);
parentQuestionModule.fadeOut("fast");
$("#" + nextQuestionID).fadeIn("slow");
}
event.preventDefault();
});
// EVENT
// Previous Button Handling
$("a.previous").click(function(event){
var parentQuestionModule = $(this).closest(".questionModule");
var questionModuleID = parentQuestionModule.attr("id");
var questionValue = questionKeyTextHash[questionModuleID];
var answerValue = $(parentQuestionModule).find(".answer").val();
answersHash[questionValue] = answerValue;
var previousQuestionID = questionIDHistory.pop();
parentQuestionModule.fadeOut("fast");
$("#" + previousQuestionID).fadeIn("slow");
event.preventDefault();
});
// EVENT
// Enough Button Handling
$("a.enough").click(function(event){
var lastChanceEmailID = 'lastChanceEmail';
var parentQuestionModule = $(this).closest(".questionModule");
var questionModuleID = parentQuestionModule.attr("id");
var questionValue = questionKeyTextHash[questionModuleID];
var answerValue = $(parentQuestionModule).find(".answer").val();
answersHash[questionValue] = answerValue;
var previousQuestionID = questionIDHistory.pop();
parentQuestionModule.fadeOut("fast");
$("#" + lastChanceEmailID).fadeIn("slow");
event.preventDefault();
});
// EVENT
// Finished Button Handling
$(".finished").click(function(event){
var parentQuestionModule = $(this).closest(".questionModule");
var questionModuleID = parentQuestionModule.attr("id");
var questionValue = questionKeyTextHash[questionModuleID];
var validationMessageElement = $(parentQuestionModule).find("div.validationMessage");
var answerElement = $(parentQuestionModule).find(".answer");
var answerValue = answerElement.val();
if ($(this).hasClass('notinterested')){
location.href = URLConstants.notInterestedRedirection;
}
else if (validateQuestion(questionModuleID, answerValue, validationMessageElement)) {
answersHash[questionValue] = answerValue;
// for php check:
// isset($_POST['id']) AND isset($_POST['jsonInterestForm']) AND isset($_POST['jsonServerInfo'])
var postVars = {
"id" : page_load_id,
"jsonInterestForm" : $.toJSON(answersHash),
"jsonServerInfo" : $.toJSON(server_info)
};
$.post(URLConstants.ajaxPost, postVars,
function(returnValue){
parentQuestionModule.fadeOut("fast");
$("#thanksForTakingTheTime").fadeIn("slow");
});
}
event.preventDefault();
});
});