THE FAMILYSEARCH 
REFERENCE CLIENT 
Open-source implementation of the Family Tree UI 
Written as a Single-Page Application in Javascript 
using the REST API 
DALLAN QUASS 
LYNN MONSON 
DOVY PAUKSTYS
TOUR
SELECT PERSON
PERSON
PERSON (SCROLL DOWN)
EDIT NAME
CHINESE
ADD EVENT
ADD SOURCE
SOURCE BOX
WHY WAS IT 
DEVELOPED? 
1. Make it easy for partners to allow their customers to access 
the FamilySearch tree using an easily-extensible framework 
2. Provide a set of re-usable components for use by partners 
3. A real-world example of accessing the FamilySearch Tree 
using the FamilySearch Javascript SDK
DISCLAIMERS 
Not official - not an official FamilySearch project 
Not supported - code is provided as-is 
Not maintained - everything currently works...
WHAT CAN I DO WITH IT? 
1. Have you ever thought you could improve upon the 
FamilySearch UI? 
Fork this project and extend it 
2. Do you want to allow people to edit names and are intimidated 
by the complexity? 
Use the name edit component from this project 
3. Would you like to understand better how to use the 
FamilySearch REST API? 
Review the source for this project
HOW CAN I GET 
STARTED? 
Install pre-requisites 
Install node.js (nodejs.org) 
npm install -g bower install bower 
npm install -g grunt-cli install grunt 
Install PhantomJS (phantomjs.org)
HOW CAN I GET 
STARTED? 
Get an App Key 
Contact FamilySearch developer support if you don't already 
have one 
Ask FamilySearch developer support to add 
as an http://localhost:9000/#!/auth OAuth redirect URL
HOW CAN I GET 
STARTED? 
Run the code 
clone github.com/rootsdev/familysearch-reference-client 
npm install install build dependencies 
bower install install client dependencies 
grunt watch launch a server and watch for changes 
Visit 
http://localhost:9000 
Sign in using your sandbox username and password
ANGULARJS
ANGULARJS FRAMEWORK 
Models 
Views 
Controllers 
Directives (components) 
Filters 
Services 
dependency injection 
easy unit tests
index.html 
head> 
<!-- include stylesheets, fonts, and javascripts --> 
</head> 
body> 
<div class="navbar navbar-default navbar-static-top"> 
<div class="container"> 
<div class="navbar-header"> 
<a class="navbar-brand" href="/">FamilySearch Reference Client</a> 
<span ng-show="busy" style="float: left; line-height: 50px"><i class="fa fa-</div> 
<div class="collapse navbar-collapse header-collapse"> 
</div> 
</div> 
</div> 
<div id="themeContainer" class="familysearch_theme"> 
<div fs-growl=""></div> 
<div fs-re-authenticate=""></div> 
<div class="container" ui-view=""></div> 
</div> 
</body>
app.js 
angular.module('fsReferenceClient', [ 'fsReferenceClientShared', 'templates-app', 'templates-.config(function($stateProvider, $urlRouterProvider) { 
$urlRouterProvider.otherwise('/'); 
}) 
.config(function(fsApiProvider) { 
fsApiProvider 
.setClientId('WCQY-7J1Q-GKVV-7DNM-SQ5M-9Q5H-JX3H-CMJK') 
.setEnvironmentName('sandbox') 
.setRedirectUri('http://localhost:9000/#!/auth'); 
}) 
.config(function(fsLocationProvider) { 
var prefix = '/#'; 
fsLocationProvider.configure({ 
getPersonLocation: function(personId) { 
return { 
prefix: prefix, 
path: '/person/'+personId 
}; 
}, 
... 
}); 
})
app.js (continued) 
.controller('AppController', function ($scope) { 
$scope.environment = 'Sandbox'; 
$scope.$on('$stateChangeStart', function(event, toState) { 
if (toState.resolve) { 
$scope.busy = true; 
} 
}); 
$scope.$on('$stateChangeSuccess', function() { 
$scope.busy = false; 
}); 
$scope.$on('$stateChangeError', function() { 
$scope.busy = false; 
}); 
});
person.js 
angular.module('fsReferenceClient') 
.config(function ($stateProvider) { 
$stateProvider.state('person', { 
url: '/person/:personId', 
controller: 'PersonController', 
templateUrl: 'person/person.tpl.html', 
data: { pageTitle: 'Person' }, 
resolve: { 
person: ['$stateParams','fsApi',function($stateParams, fsApi) { 
return fsApi.getPerson($stateParams.personId).then(function (response) { 
return response.getPerson(); 
}); 
}], 
sources: ['_','$q','$stateParams','fsApi',function(_, $q, $stateParams, fsApi) 
return fsApi.getPersonSourcesQuery($stateParams.personId).then(function(response) 
return _.map(response.getSourceRefs(), function(sourceRef) { 
return { 
ref: sourceRef, 
description: response.getSourceDescription(sourceRef.$sourceDescriptionId 
id: sourceRef.id 
}; 
}); 
}); 
}] 
} 
});
person.js (continued) 
.controller('PersonController', function ($scope, $state, $rootScope, person, sources, var sections = ['vitalFacts', 'otherInfo', 'familyMembers', 'sources', 'discussions' 
$scope.states = {}; 
sections.forEach(function(section) { 
$scope.states[section] = {value: 'open'}; 
}); 
$scope.person = person; 
$scope.sources = sources; 
sources.forEach(function(source) { 
fsUtils.mixinStateFunctions($scope, source); 
}); 
var unbindRestored = $rootScope.$on('restored', function() { 
fsApi.getPerson($scope.person.id).then(function (response) { 
fsUtils.refresh($scope.person, response.getPerson()); 
}); 
}); 
$scope.$on('$destroy', unbindRestored); 
$scope.$on('delete', function(event, person, changeMessage) { 
event.stopPropagation(); 
person._busy = true; 
person.$delete(changeMessage).then(function() { 
person._busy = false; 
fsCurrentUserCache.getUser().then(function(user) { 
$state.go('person', { personId: user.personId });
person.tpl.html 
div fs-person-profile="" person="person"></div> 
div class="mainContent"> 
<div class="row"> 
<div class="col-md-9"> 
<div fs-vital-facts-section="" person="person" sources="sources" state="states 
<div fs-other-info-section="" person="person" state="states['otherInfo']"></div 
<div fs-family-members-section="" person="person" state="states['familyMembers 
<div fs-sources-section="" person="person" sources="sources" state="states['sources 
<div fs-discussions-section="" person="person" state="states['discussions']"></ 
<div fs-notes-section="" person="person" state="states['notes']"></div> 
</div> 
<div class="col-md-3 sidebar"> 
<div fs-changes-section="" person="person"></div> 
<div fs-tools-section="" person="person"></div> 
</div> 
</div> 
</div>
fsPersonProfile.js 
angular.module('fsReferenceClientShared') 
.directive('fsPersonProfile', function (fsLocation) { 
return { 
templateUrl: 'fsReferenceClientShared/fsPersonProfile/fsPersonProfile.tpl.html' 
scope: { 
person: '=' 
}, 
link: function(scope) { 
scope.treeHref = fsLocation.getTreeUrl(scope.person.id); 
} 
}; 
});
fsPersonProfile.tpl.html 
div class="profileHeaderContainer"> 
<div class="tree-family jumbotron"> 
<div class="personWrapper"> 
<div class="nameWrapper"> 
<div class="row"> 
<div class="col-lg-2 col-md-2"> 
<i class="icon-rounded hidden-xs" ng-class="{'fs-icon-male': person._isMale(), 
'fs-icon-female': !person._isMale()}"> </i> 
</div> 
<div class="col-lg-10 col-md-10"> 
<div class="pull-right"><span ng-show="person._busy"> 
<i class="fa fa-spinner fa-spin"></i></span></div> 
<h1>{{person.$getDisplayName()}}</h1> 
<span class="lifeSpan">{{person.$getDisplayBirthDate()}} - 
{{person.$getDisplayDeathDate()}}</span> 
<span class="pid">{{person.id}}</span> 
<div class="personActions"> 
<ul class="list-collapsed"> 
<li class="pull-left"><a ng-href="{{treeHref}}"> 
<i class="fs-icon-male fs-icon-pedigree"> </i>View Tree</a></li> 
</ul> 
</div> 
</div> 
</div> 
</div> 
</div>
EXTENDING
RESULT
RE-USE COMPONENTS
FORK 
Fork 
https://github.com/rootsdev/familysearch-reference-client 
create a directory for your components; e.g., 
src/common/dqComponents
dqPedigreeMini.js 
angular.module('dqPedigreeMini', ['loDash', 'fsReferenceClientShared', 'dqPersonMini' 
.directive('dqPedigreeMini', function (_, fsApi) { 
return { 
templateUrl: 'dqComponents/dqPedigreeMini.tpl.html', 
scope: { 
personId: '@' 
}, 
controller: function($scope) { 
fsApi.getAncestry($scope.personId, {generations: 2}).then(function(response) 
$scope.persons = _.filter(response.getPersons(), function(person) { 
return person.$getAscendancyNumber() >= 1 && 
person.$getAscendancyNumber() <= 7; 
}); 
}); 
} 
}; 
});
dqPedigreeMini.tpl.html 
<div class="panel panel-info"> 
<div class="panel-heading"> 
<h4 class="panel-title">Mini Pedigree</h4> 
</div> 
<div class="panel-body dq-pedigree-mini-outer"> 
<div ng-repeat="person in persons"> 
<div dq-person-mini person="person" 
is-focus="{{person.$getAscendancyNumber() == 1}}" 
class="dq-pedigree-mini-person" 
ng-class="'dq-pedigree-mini-pos'+person.$getAscendancyNumber()"> 
</div> 
</div> 
</div> 
</div>
dqPersonMini.js 
angular.module('dqPersonMini', []) 
.directive('dqPersonMini', function () { 
return { 
templateUrl: 'dqComponents/dqPersonMini.tpl.html', 
scope: { 
person: '=', 
isFocus: '@' 
} 
}; 
});
dqPersonMini.tpl.html 
div class="dq-person-mini"> 
<a href="" fs-person-popover="" person="person" popover-placement="left"> 
<span class="dq-person-mini-name" 
ng-style="{'font-weight': isFocus === 'true' ? 'bold' : 'normal', 
'color': isFocus === 'true' ? '#333' : '#0051c4'}"> 
{{person.$getDisplayName()}} 
</span> 
</a> 
</div>
INJECT INTO PERSON 
<div class="col-md-3 sidebar"> 
<div fs-changes-section="" person="person"></div> 
<div fs-tools-section="" person="person"></div> 
<div dq-pedigree-mini="" person-id="{{person.id}}"></div> 
</div>
THE END 
Slides are at https://github.com/DallanQ/fs-reference-client- 
2014-slides

FamilySearch Reference Client

  • 1.
    THE FAMILYSEARCH REFERENCECLIENT Open-source implementation of the Family Tree UI Written as a Single-Page Application in Javascript using the REST API DALLAN QUASS LYNN MONSON DOVY PAUKSTYS
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
    WHY WAS IT DEVELOPED? 1. Make it easy for partners to allow their customers to access the FamilySearch tree using an easily-extensible framework 2. Provide a set of re-usable components for use by partners 3. A real-world example of accessing the FamilySearch Tree using the FamilySearch Javascript SDK
  • 12.
    DISCLAIMERS Not official- not an official FamilySearch project Not supported - code is provided as-is Not maintained - everything currently works...
  • 13.
    WHAT CAN IDO WITH IT? 1. Have you ever thought you could improve upon the FamilySearch UI? Fork this project and extend it 2. Do you want to allow people to edit names and are intimidated by the complexity? Use the name edit component from this project 3. Would you like to understand better how to use the FamilySearch REST API? Review the source for this project
  • 14.
    HOW CAN IGET STARTED? Install pre-requisites Install node.js (nodejs.org) npm install -g bower install bower npm install -g grunt-cli install grunt Install PhantomJS (phantomjs.org)
  • 15.
    HOW CAN IGET STARTED? Get an App Key Contact FamilySearch developer support if you don't already have one Ask FamilySearch developer support to add as an http://localhost:9000/#!/auth OAuth redirect URL
  • 16.
    HOW CAN IGET STARTED? Run the code clone github.com/rootsdev/familysearch-reference-client npm install install build dependencies bower install install client dependencies grunt watch launch a server and watch for changes Visit http://localhost:9000 Sign in using your sandbox username and password
  • 17.
  • 18.
    ANGULARJS FRAMEWORK Models Views Controllers Directives (components) Filters Services dependency injection easy unit tests
  • 19.
    index.html head> <!--include stylesheets, fonts, and javascripts --> </head> body> <div class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="/">FamilySearch Reference Client</a> <span ng-show="busy" style="float: left; line-height: 50px"><i class="fa fa-</div> <div class="collapse navbar-collapse header-collapse"> </div> </div> </div> <div id="themeContainer" class="familysearch_theme"> <div fs-growl=""></div> <div fs-re-authenticate=""></div> <div class="container" ui-view=""></div> </div> </body>
  • 20.
    app.js angular.module('fsReferenceClient', ['fsReferenceClientShared', 'templates-app', 'templates-.config(function($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/'); }) .config(function(fsApiProvider) { fsApiProvider .setClientId('WCQY-7J1Q-GKVV-7DNM-SQ5M-9Q5H-JX3H-CMJK') .setEnvironmentName('sandbox') .setRedirectUri('http://localhost:9000/#!/auth'); }) .config(function(fsLocationProvider) { var prefix = '/#'; fsLocationProvider.configure({ getPersonLocation: function(personId) { return { prefix: prefix, path: '/person/'+personId }; }, ... }); })
  • 21.
    app.js (continued) .controller('AppController',function ($scope) { $scope.environment = 'Sandbox'; $scope.$on('$stateChangeStart', function(event, toState) { if (toState.resolve) { $scope.busy = true; } }); $scope.$on('$stateChangeSuccess', function() { $scope.busy = false; }); $scope.$on('$stateChangeError', function() { $scope.busy = false; }); });
  • 22.
    person.js angular.module('fsReferenceClient') .config(function($stateProvider) { $stateProvider.state('person', { url: '/person/:personId', controller: 'PersonController', templateUrl: 'person/person.tpl.html', data: { pageTitle: 'Person' }, resolve: { person: ['$stateParams','fsApi',function($stateParams, fsApi) { return fsApi.getPerson($stateParams.personId).then(function (response) { return response.getPerson(); }); }], sources: ['_','$q','$stateParams','fsApi',function(_, $q, $stateParams, fsApi) return fsApi.getPersonSourcesQuery($stateParams.personId).then(function(response) return _.map(response.getSourceRefs(), function(sourceRef) { return { ref: sourceRef, description: response.getSourceDescription(sourceRef.$sourceDescriptionId id: sourceRef.id }; }); }); }] } });
  • 23.
    person.js (continued) .controller('PersonController',function ($scope, $state, $rootScope, person, sources, var sections = ['vitalFacts', 'otherInfo', 'familyMembers', 'sources', 'discussions' $scope.states = {}; sections.forEach(function(section) { $scope.states[section] = {value: 'open'}; }); $scope.person = person; $scope.sources = sources; sources.forEach(function(source) { fsUtils.mixinStateFunctions($scope, source); }); var unbindRestored = $rootScope.$on('restored', function() { fsApi.getPerson($scope.person.id).then(function (response) { fsUtils.refresh($scope.person, response.getPerson()); }); }); $scope.$on('$destroy', unbindRestored); $scope.$on('delete', function(event, person, changeMessage) { event.stopPropagation(); person._busy = true; person.$delete(changeMessage).then(function() { person._busy = false; fsCurrentUserCache.getUser().then(function(user) { $state.go('person', { personId: user.personId });
  • 24.
    person.tpl.html div fs-person-profile=""person="person"></div> div class="mainContent"> <div class="row"> <div class="col-md-9"> <div fs-vital-facts-section="" person="person" sources="sources" state="states <div fs-other-info-section="" person="person" state="states['otherInfo']"></div <div fs-family-members-section="" person="person" state="states['familyMembers <div fs-sources-section="" person="person" sources="sources" state="states['sources <div fs-discussions-section="" person="person" state="states['discussions']"></ <div fs-notes-section="" person="person" state="states['notes']"></div> </div> <div class="col-md-3 sidebar"> <div fs-changes-section="" person="person"></div> <div fs-tools-section="" person="person"></div> </div> </div> </div>
  • 25.
    fsPersonProfile.js angular.module('fsReferenceClientShared') .directive('fsPersonProfile',function (fsLocation) { return { templateUrl: 'fsReferenceClientShared/fsPersonProfile/fsPersonProfile.tpl.html' scope: { person: '=' }, link: function(scope) { scope.treeHref = fsLocation.getTreeUrl(scope.person.id); } }; });
  • 26.
    fsPersonProfile.tpl.html div class="profileHeaderContainer"> <div class="tree-family jumbotron"> <div class="personWrapper"> <div class="nameWrapper"> <div class="row"> <div class="col-lg-2 col-md-2"> <i class="icon-rounded hidden-xs" ng-class="{'fs-icon-male': person._isMale(), 'fs-icon-female': !person._isMale()}"> </i> </div> <div class="col-lg-10 col-md-10"> <div class="pull-right"><span ng-show="person._busy"> <i class="fa fa-spinner fa-spin"></i></span></div> <h1>{{person.$getDisplayName()}}</h1> <span class="lifeSpan">{{person.$getDisplayBirthDate()}} - {{person.$getDisplayDeathDate()}}</span> <span class="pid">{{person.id}}</span> <div class="personActions"> <ul class="list-collapsed"> <li class="pull-left"><a ng-href="{{treeHref}}"> <i class="fs-icon-male fs-icon-pedigree"> </i>View Tree</a></li> </ul> </div> </div> </div> </div> </div>
  • 27.
  • 28.
  • 29.
  • 30.
    FORK Fork https://github.com/rootsdev/familysearch-reference-client create a directory for your components; e.g., src/common/dqComponents
  • 31.
    dqPedigreeMini.js angular.module('dqPedigreeMini', ['loDash','fsReferenceClientShared', 'dqPersonMini' .directive('dqPedigreeMini', function (_, fsApi) { return { templateUrl: 'dqComponents/dqPedigreeMini.tpl.html', scope: { personId: '@' }, controller: function($scope) { fsApi.getAncestry($scope.personId, {generations: 2}).then(function(response) $scope.persons = _.filter(response.getPersons(), function(person) { return person.$getAscendancyNumber() >= 1 && person.$getAscendancyNumber() <= 7; }); }); } }; });
  • 32.
    dqPedigreeMini.tpl.html <div class="panelpanel-info"> <div class="panel-heading"> <h4 class="panel-title">Mini Pedigree</h4> </div> <div class="panel-body dq-pedigree-mini-outer"> <div ng-repeat="person in persons"> <div dq-person-mini person="person" is-focus="{{person.$getAscendancyNumber() == 1}}" class="dq-pedigree-mini-person" ng-class="'dq-pedigree-mini-pos'+person.$getAscendancyNumber()"> </div> </div> </div> </div>
  • 33.
    dqPersonMini.js angular.module('dqPersonMini', []) .directive('dqPersonMini', function () { return { templateUrl: 'dqComponents/dqPersonMini.tpl.html', scope: { person: '=', isFocus: '@' } }; });
  • 34.
    dqPersonMini.tpl.html div class="dq-person-mini"> <a href="" fs-person-popover="" person="person" popover-placement="left"> <span class="dq-person-mini-name" ng-style="{'font-weight': isFocus === 'true' ? 'bold' : 'normal', 'color': isFocus === 'true' ? '#333' : '#0051c4'}"> {{person.$getDisplayName()}} </span> </a> </div>
  • 35.
    INJECT INTO PERSON <div class="col-md-3 sidebar"> <div fs-changes-section="" person="person"></div> <div fs-tools-section="" person="person"></div> <div dq-pedigree-mini="" person-id="{{person.id}}"></div> </div>
  • 36.
    THE END Slidesare at https://github.com/DallanQ/fs-reference-client- 2014-slides