First round of massive refactoring

git-svn-id: http://svn.mozilla.org/projects/phonebook/trunk@50675 4eb1ac78-321c-0410-a911-ec516a8615a5
This commit is contained in:
wlee@mozilla.com 2009-09-02 10:58:21 +00:00
Родитель 26709f8724
Коммит fff2cad47f
18 изменённых файлов: 752 добавлений и 882 удалений

42
config.php Normal file
Просмотреть файл

@ -0,0 +1,42 @@
<?php
$tree_config = array(
"ldap" => array(
"search_base" => "o=com, dc=mozilla",
"search_filter" => "mail=*",
"search_attributes" => array(
"cn", "manager", "title", "mail", "employeeType"
)
)
);
function tree_view_process_entry($person) {
return array(
"title" => !empty($person["title"][0]) ? $person["title"][0] : null,
"name" => !empty($person["cn"][0]) ? $person["cn"][0] : null,
"disabled" => isset($person["employeetype"]) ?
strpos($person["employeetype"][0], 'D') !== FALSE:
FALSE
);
}
function tree_view_roots() {
return array(
"mitchell@mozilla.com", "lilly@mozilla.com"
);
}
function tree_view_item($email, $leaf=FALSE) {
global $everyone;
$email = htmlspecialchars($email);
$id = str_replace('@', "-at-", $email);
$name = htmlspecialchars($everyone[$email]["name"]);
$title = htmlspecialchars($everyone[$email]["title"]);
$leaf = $leaf ? " leaf" : '';
$disabled = $everyone[$email]["disabled"] ? " disabled" : '';
return "<li id=\"$id\" class=\"hr-node expanded$leaf$disabled\">".
"<a href=\"#search/$email\" class=\"hr-link\">$name</a> ".
"<span class=\"title\">$title</span>".
"</li>";
}

Просмотреть файл

@ -178,17 +178,16 @@ a:hover {
/* Card view */ /* Card view */
#results { #results {
overflow: auto; margin: 0 1em 1em;
width: 62em; text-align: center;
margin: 0 auto;
} }
.vcard { .vcard {
display: block; text-align: left;
display: inline-block; display: inline-block;
width: 31em; width: 31em;
vertical-align: top; vertical-align: top;
margin-top: 1em; margin: 1em 1em 0 1em;
} }
.vcard .header, .vcard .header,
.vcard .body, .vcard .body,
@ -345,14 +344,6 @@ a:hover {
/* Editing view */ /* Editing view */
form#edit { form#edit {
/*width: 85%;
margin: 0 auto 1em auto;
padding: 1em;
background-color: #FFF;
border-radius: 0.5em;
-moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em;
font-size: 0.75em;*/
text-align: center; text-align: center;
} }

Просмотреть файл

@ -97,6 +97,6 @@ if (!empty($_POST)) {
} }
$user_data = clean_userdata($user_data); $user_data = clean_userdata($user_data);
$managerlist = manager_list($ldapconn); $managerlist = everyone_list($ldapconn);
require_once 'templates/edit.php'; require_once 'templates/edit.php';

148
face.php
Просмотреть файл

@ -1,148 +0,0 @@
<?php
require_once('templates/header.php'); ?>
<div id="results">
</div>
<div id="overlay">
</div>
<script type="text/javascript">
Function.prototype.lazify = function() {
var cache = {};
return this.wrap(function(original, x) {
return cache[x] ? cache[x] : (cache[x] = original(x));
});
};
String.toInt = parseInt.methodize();
Number.emToPx = function(x) {
var div = new Element("div").setStyle({
position: "absolute", margin: "0", padding: "0", visibility: "none",
width: x + "em", height: "9px", top: "-100000em", left: "-100000em"
});
$(document.body).insert(div);
var px = div.getWidth();
div.remove();
return px;
}.lazify();
$(document).observe("keypress", function(e) {
if ((e.charCode || e.keyCode) == 47) { // KEY_SLASH
$("text").focus(); e.stop();
}
});
$(document).observe("dom:loaded", function() {
$("phonebook-search").addClassName("large");
var adjustLayout = function() {
var img = $$("img.wall-photo");
img = img.length ? img[0] : null;
var width = 140;
if (img) {
width = img.width +
$w("paddingLeft marginLeft paddingRight marginRight").map(function(x) {
return img.getStyle(x).replace("px", '').toInt();
}).inject(function(x, y) { return x + y; });
} else {
width += Number.emToPx(5);
}
var photos = 4;
Event.observe(Prototype.resizeTarget, "resize", readjust);
function readjust() {
var c = Math.floor(document.viewport.getWidth() / width);
console.log(c);
if (photos != c && c != 0) {
$("results").setStyle({width: ((photos = c) * width) + "px"});
}
}
};
adjustLayout();
$("results").setStyle({width: "880px"});
$("menu").down("a.wall").addClassName("selected");
$(document).observe("hash:changed", function(e) {
$("text").value = e.memo.hash.replace("search/", '');
startSearch();
});
$("phonebook-search").observe("submit", function(e) {
window.location.hash = "#search/" + $F("text");
e.stop();
});
$("overlay").observe("click", function(e) {
if (e.element() == this) {
$(document.body).removeClassName("lightbox");
}
});
Event.observe(Prototype.resizeTarget, "resize", function() {
$$("div.vcard").invoke("verticallyCenter");
});
var stoppedResizing = false;
if (window.location.hash.startsWith("#search/")) {
$(document).fire("hash:changed", {hash: window.location.hash.substring(1)});
} else {
$("header").verticallyCenter();
Event.observe(Prototype.resizeTarget, "resize", centralize);
$("text").focus();
}
function centralize() {
if (!arguments.callee.stop) {
$("header").verticallyCenter();
}
}
function startSearch() {
if (!stoppedResizing) {
centralize.stop = true;
stoppedResizing = true;
$("header").stopVerticallyCentering();
$("phonebook-search").removeClassName("large");
}
$("phonebook-search").request({
parameters: {format: "json"},
onSuccess: function onSuccess(r) {
$("text").releaseFocus();
$("results").update('');
r.responseText.evalJSON().each(function(person) {
var container = new Element("div").addClassName("photo-frame");
var face = new Element("img", {
src: person.picture + "&type=thumb", "class": "wall-photo"
}).observe("click", function() { showPerson(person.dn); });
var name = new Element("span").update(person.cn);
container.insert(name).insert(face);
if (person.employeetype.indexOf("DISABLED") != -1) {
container.addClassName("disabled");
}
$("results").insert(container);
});
adjustLayout();
}
});
}
function showPerson(dn) {
$("phonebook-search").request({
parameters: {format: "html", query: dn.dnToEmail()},
onSuccess: function onSuccess(r) {
$(document.body).addClassName("lightbox");
var close = new Element("div").observe("click", function(e) {
$(document.body).removeClassName("lightbox");
}).addClassName("close-button").writeAttribute("title", "Close");
$("overlay").update('').update(r.responseText).
down("div.vcard").verticallyCenter().
down("div.header").insert(close);
$("overlay").down(".vcard p.manager a").observe("click", function() {
$(document.body).removeClassName("lightbox");
});
}
});
}
});
</script>
<?php require_once('templates/footer.php'); ?>

11
faces.php Normal file
Просмотреть файл

@ -0,0 +1,11 @@
<?php
require_once('templates/header.php'); ?>
<div id="results">
</div>
<div id="overlay">
</div>
<script type="text/javascript" src="js/view-faces.js"></script>
<?php require_once('templates/footer.php'); ?>

92
filters.inc Normal file
Просмотреть файл

@ -0,0 +1,92 @@
<?php
function get_filters() {
return array(
"employeetype" => "employee_status",
"manager" => "get_manager",
"physicaldeliveryofficename" => "location_formatter",
"mobile" => "mobile_normalizer",
"im" => "mobile_normalizer",
"description" => "wikilinks",
"other" => "wikilinks"
);
}
function employee_status($status) {
global $orgs, $emp_type;
$current_org = $current_emp_type = "";
if ($status != "") {
$current_org = $status[0];
$current_emp_type = $status[1];
}
if ($status == "DISABLED") {
return array('DISABLED');
} else {
if (array_key_exists($current_org, $orgs) &&
array_key_exists($current_emp_type, $emp_type)) {
return array($orgs[$current_org], $emp_type[$current_emp_type]);
} else {
return array('Unknown');
}
}
}
function get_manager($manager_dn) {
global $ldapconn, $memcache_on, $memcache;
if ($memcache_on && ($manager = $memcache->get($manager_dn))) {
return $manager;
}
$manager_search = @ldap_search($ldapconn, $manager_dn, '(mail=*)', array('cn','mail'));
if (ldap_errno($ldapconn) == 32) { // No manager found
return null;
}
if ($manager_search) {
$entry = ldap_first_entry($ldapconn, $manager_search);
if ($entry) {
$attrs = ldap_get_attributes($ldapconn, $entry);
$manager = array(
"cn" => $attrs['cn'][0],
"dn" => $manager_dn
);
} else {
$manager = null;
}
if ($memcache_on) {
$memcache->set($manager_dn, $manager);
}
return $manager;
}
}
function location_formatter($location) {
return str_replace(":::", "/", $location);
}
function mobile_normalizer($m) {
if (!is_array($m)) {
$m = array($m);
}
return array_map("wikilinks", $m);
}
function wikilinks($string) {
$matches = array();
$string = nl2br(htmlspecialchars($string));
if (preg_match_all('/\[(.+?)(?:\s(.+?))?\]/', $string, $matches)) {
foreach ($matches[1] as $key => $value) {
if (!empty($matches[2][$key])) {
$title = $matches[2][$key];
} else {
$title = $value;
}
$string = str_replace(
$matches[0][$key],
'<a href="'. $value .'">'. $title .'</a>',
$string
);
}
}
return $string;
}

Просмотреть файл

@ -57,83 +57,6 @@ function search_users($ldapconn, $search) {
return ldap_get_entries($ldapconn, $search); return ldap_get_entries($ldapconn, $search);
} }
function employee_status($status) {
global $orgs, $emp_type;
$current_org = $current_emp_type = "";
if ($status != "") {
$current_org = $status[0];
$current_emp_type = $status[1];
}
if ($status == "DISABLED") {
return array('DISABLED');
} else {
if (array_key_exists($current_org, $orgs) &&
array_key_exists($current_emp_type, $emp_type)) {
return array($orgs[$current_org], $emp_type[$current_emp_type]);
} else {
return array('Unknown');
}
}
}
function get_manager($manager_dn) {
global $ldapconn, $memcache_on, $memcache;
if ($memcache_on && ($manager = $memcache->get($manager_dn))) {
return $manager;
}
$manager_search = @ldap_search($ldapconn, $manager_dn, '(mail=*)', array('cn','mail'));
if (ldap_errno($ldapconn) == 32) { // No manager found
return null;
}
if ($manager_search) {
$entry = ldap_first_entry($ldapconn, $manager_search);
if ($entry) {
$attrs = ldap_get_attributes($ldapconn, $entry);
$manager = array(
"cn" => $attrs['cn'][0],
"dn" => $manager_dn
);
} else {
$manager = null;
}
if ($memcache_on) {
$memcache->set($manager_dn, $manager);
}
return $manager;
}
}
function wikilinks($string) {
$matches = array();
$string = nl2br(htmlspecialchars($string));
if (preg_match_all('/\[(.+?)(?:\s(.+?))?\]/', $string, $matches)) {
foreach ($matches[1] as $key => $value) {
if (!empty($matches[2][$key])) {
$title = $matches[2][$key];
} else {
$title = $value;
}
$string = str_replace(
$matches[0][$key],
'<a href="'. $value .'">'. $title .'</a>',
$string
);
}
}
return $string;
}
function location_formatter($location) {
return str_replace(":::", "/", $location);
}
function mobile_normalizer($m) {
if (!is_array($m)) {
$m = array($m);
}
return array_map("wikilinks", $m);
}
// The logic here is that failure to find out who has permissions to edit // The logic here is that failure to find out who has permissions to edit
// someone else's entry implies that you aren't one of them. // someone else's entry implies that you aren't one of them.
function is_phonebook_admin($ldapconn, $mail) { function is_phonebook_admin($ldapconn, $mail) {
@ -176,7 +99,7 @@ function clean_userdata($user_data) {
return $user_data; return $user_data;
} }
function manager_list($ldapconn) { function everyone_list($ldapconn) {
$search = ldap_search($ldapconn, 'o=com,dc=mozilla', 'objectClass=mozComPerson'); $search = ldap_search($ldapconn, 'o=com,dc=mozilla', 'objectClass=mozComPerson');
ldap_sort($ldapconn, $search, 'cn'); ldap_sort($ldapconn, $search, 'cn');
return ldap_get_entries($ldapconn, $search); return ldap_get_entries($ldapconn, $search);

Просмотреть файл

@ -1,191 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" dir="ltr">
<head>
<title>Phonebook</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script src="./js/prototype.js" type="text/javascript"></script>
<!--<script src="./js/jquery-1.3.2.min.js" type="text/javascript"></script>-->
<link rel="stylesheet" type="text/css" href="./css/style.css"/>
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico" />
<script type='text/javascript'>
// jQuery.noConflict();
if (!window.console) {
window.console = {};
window.console.log = Prototype.emptyFunction;
}
// Convert a dn to an email address
String.prototype.toEmail = function String_toEmail() {
var m = this.match(/mail=(\w+@mozilla.*),o=/);
return (m ? m[1] : null);
};
// Implement onhashchange support
(function() {
var hash = window.location.hash;
var fire = function(str) {
$(document).fire("hash:changed", { hash: str.substring(1) });
};
var pe = new PeriodicalExecuter(function() {
var newHash = window.location.hash;
if (newHash != hash) {
fire(newHash);
hash = newHash;
}
}, 1);
})();
$(document).observe("keypress", function(e) {
if ((e.charCode || e.keyCode) == 47) { // KEY_SLASH
$("text").focus();
e.stop();
}
});
$(document).observe("dom:loaded", function() {
Prototype.Browser.WebKit && $("text").writeAttribute({
type: "search", results: 5
}) && $("search").hide(); // Just this one special treatment, Safari
var fillAndSearch = function() {
$("text").value = window.location.hash.replace("#search/", '');
startSearch();
};
if (window.location.hash.startsWith("#search/")) {
fillAndSearch();
} else {
$("text").focus();
}
$(document).observe("hash:changed", fillAndSearch);
$("phonebook-search").observe("submit", function(e) {
window.location.hash = "#search/" + $F("text");
startSearch(); e.stop();
});
var listify = function(a) {
return $A(a).map(function(x) {
return "<li>" + x + "</li>";
}).join('');
};
var emailLinkify = function(s) {
return '<li><a class="value" href="mailto:#{s}">#{s}</a></li>'.interpolate({s: s});
};
var processors = $H({
"email": emailLinkify,
"emailalias": emailLinkify.wrap(function(original, email) {
email = Object.isString(email) ? [email] : $A(email);
return email.map(original);
}),
"employeetype": function(l) { return l.join(", "); },
"im": listify.wrap(function(original, im) {
return im ? ('<ul class="im">' + original(im) + '</ul>') : '';
}),
"mobile": listify.wrap(function(original, list) {
return '<ul class="telecommunications">' +
original(list).replace(/<li>/, '<li class="tel">') + '</ul>';
}),
"description": function(s) {
return '<div class="note">I work on: #{s}</div>'.interpolate({s: s});
},
"other": function(s) {
return '<div class="other">' + s + '</div>';
},
"manager": function(m) {
return '<p class="manager">Manager: <a href="#search/#{email}">#{name}</a></p>'.interpolate({
email: m.dn.toEmail(),
name: m.cn
});
},
"telephonenumber": function(x) { return "ext. " + x; },
"bugzillaemail": function(s) {
return '<ul class="bugmail"><li><a title="Bugmail">#{s}</a></li></ul>'.interpolate({s: s});
}
});
function startSearch() {
$("phonebook-search").request({onSuccess: function onSuccess(r) {
$("results").update('');
var results = r.responseText.evalJSON().each(function entryEach(e) {
var code = process(e);
var vcard = new Element("div", {"class":"vcard"}).update(code);
$("results").insert(vcard);
});
$("text").blur();
if (results.length == 0) {
$("results").update(
'<div style="text-align: center; margin-top: 5em;">' +
'<img src="./img/ohnoes.jpg" />' +
'<h2>OH NOES! No ones were foundz.</h2>' +
'</div>'
);
}
}});
};
function process(entry) {
entry.email = [entry.dn.toEmail()];
entry.picture = '<a href="#{url}" class="photo" target="_blank"><img class="photo" src="#{url}&type=thumb" alt="Photo of #{name}" /></a>'.interpolate({
url: entry.picture,
name: entry.cn
});
console.log("JSON preprocessing done");
processors.each(function processorEach(pair) {
if (entry[pair.key])
entry[pair.key] = pair.value(entry[pair.key]);
});
console.log("JSON postprocessing done");
return template(entry);
}
function template(person) {
return [
'<div class="header"><h2 class="fn">#{cn}</h2></div>',
'<div class="body">#{picture}',
'<div class="employee">',
'<p class="title">#{title}</p>',
'<p class="employee-type">#{employeetype}</p>',
'#{manager}',
'</div>',
'<ul class="adr"><li>#{telephonenumber} @ ',
'<span class="locality">#{physicaldeliveryofficename}</span>',
'</li></ul>',
'#{mobile}',
'<ul class="email">#{email}#{emailalias}</ul>',
'#{bugzillaemail}',
'#{im}',
'#{description}',
'#{other}',
'</div><div class="footer"></div>'
].join('').interpolate(person);
}
});
</script>
</head>
<body>
<div id="header">
<form action="search.php" method="get" id="phonebook-search">
<h1>Phonebook</h1>
<div id="search-region">
<input type="text" name="query" id="text" />
<input type="submit" value="Search" id="search" />
</div>
<ul id="menu">
<li><a href="./#search/*">Everyone</a></li>
<li><a href="https://intranet.mozilla.org/">Intranet</a></li>
<li><a href="https://intranet.mozilla.org/OfficeLocations">Offices</a></li>
<li class="edit"><a href="edit.php" id="edit-entry">Edit My Entry</a></li>
</ul>
</form>
</div>
<div id="results">
</div>
</body>
</html>

Просмотреть файл

@ -6,89 +6,5 @@ require_once('templates/header.php');
<div id="results"> <div id="results">
</div> </div>
<script type="text/javascript"> <script type="text/javascript" src="js/view-cards.js"></script>
Function.prototype.lazify = function() {
var cache = {};
return this.wrap(function(original, x) {
return cache[x] ? cache[x] : (cache[x] = original(x));
});
};
Number.emToPx = function(x) {
var div = new Element("div").setStyle({
position: "absolute", margin: "0", padding: "0", visibility: "none",
width: x + "em", height: "9px", top: "-100000em", left: "-100000em"
});
$(document.body).insert(div);
var px = div.getWidth();
div.remove();
return px;
}.lazify();
(function() {
var cards = 2;
Event.observe(Prototype.resizeTarget, "resize", function() {
var c = Math.floor(document.viewport.getWidth() / Number.emToPx(1) / 31);
if (cards != c && c != 0) {
$("results").setStyle({width: ((cards = c) * 31) + "em"});
}
});
})();
$(document).observe("keypress", function(e) {
if ((e.charCode || e.keyCode) == 47) { // KEY_SLASH
$("text").focus(); e.stop();
}
});
$(document).observe("dom:loaded", function() {
$("menu").down("a.card").addClassName("selected");
$(document).observe("hash:changed", function(e) {
$("text").value = e.memo.hash.replace("search/", '');
startSearch();
});
$("phonebook-search").observe("submit", function(e) {
e.stop();
window.location.hash = "#search/" + $F("text");
});
var stoppedResizing = false;
if (window.location.hash.startsWith("#search/")) {
$(document).fire("hash:changed", {hash: window.location.hash.substring(1)});
} else {
$("phonebook-search").addClassName("large");
$("header").verticallyCenter();
Event.observe(Prototype.resizeTarget, "resize", centralize);
$("text").focus();
}
function centralize() {
if (!arguments.callee.stop) {
$("header").verticallyCenter();
}
}
function startSearch() {
if (!$F("text").strip()) {
window.location = "./";
}
if (!stoppedResizing) {
centralize.stop = true;
stoppedResizing = true;
$("header").stopVerticallyCentering();
$("phonebook-search").removeClassName("large");
}
$("phonebook-search").request({onSuccess: function onSuccess(r) {
$("results").update('').update(r.responseText ||
'<div style="text-align: center; margin-top: 5em;">' +
'<img src="./img/ohnoes.jpg" />' +
'<h2>OH NOES! No ones were foundz.</h2>' +
'</div>'
);
$("text").releaseFocus();
}});
}
});
</script>
<?php require_once('templates/footer.php'); ?> <?php require_once('templates/footer.php'); ?>

232
js/common.js Normal file
Просмотреть файл

@ -0,0 +1,232 @@
if (!window.console) {
window.console = {log: Prototype.emptyFunction};
}
// Implement onhashchange support
(function() {
var hash = window.location.hash;
var fire = function(str) {
$(document).fire("hash:changed", { hash: str.substring(1) });
};
var pe = new PeriodicalExecuter(function() {
var newHash = window.location.hash;
if (newHash != hash) {
fire(newHash);
hash = newHash;
}
}, 1);
})();
Element.addMethods({
verticallyCenter: function(element, percentage) {
element = $(element);
if (!element.retrieve("originalMarginTop")) {
element.store("originalMarginTop", element.getStyle("marginTop"));
}
var offset = $(document).viewport.getHeight() - element.getHeight();
return element.setStyle({marginTop: (offset * (percentage || 0.4)) + "px"});
},
stopVerticallyCentering: function(element) {
element = $(element);
var original = element.getStorage().unset("originalMarginTop");
return element.setStyle({marginTop: original});
}
});
// Force Gecko to redraw the blinking cursor
Element.addMethods("input", {
releaseFocus: function(element) {
$w("blur focus blur").each(function(method) { $(element)[method](); });
return element;
}
});
Prototype.resizeTarget = document.onresize ? document : window;
Array.prototype.find = function find(selector) {
return this.filter(function(x) {
return x.match ? x.match(selector) : false;
});
};
// Show / hide throbbers accoring to Ajax requests
Ajax.Responders.register({
onCreate: function() { $("throbber").addClassName("loading"); },
onComplete: function() { $("throbber").removeClassName("loading"); }
});
// Search keyword persistence between different views
$(document).observe("dom:loaded", function() {
$$("#menu a.persist").each(function(link) {
link.store("originalLink", link.readAttribute("href"));
});
});
$(document).observe("hash:changed", function(e) {
$$("#menu a.persist").each(function(link) {
link.writeAttribute("href", link.retrieve("originalLink") + "#" + e.memo.hash);
});
});
Function.prototype.lazify = function() {
var cache = {};
return this.wrap(function(original, x) {
return cache[x] ? cache[x] : (cache[x] = original(x));
});
};
Number.emToPx = function(x) {
var div = new Element("div").setStyle({
position: "absolute", margin: "0", padding: "0", visibility: "none",
width: x + "em", height: "9px", top: "-100000em", left: "-100000em"
});
$(document.body).insert(div);
var px = div.getWidth();
div.remove();
return px;
}.lazify();
Number.prototype.emToPx = Number.emToPx.methodize();
String.prototype.toInt = window.parseInt.methodize();
// BehaviorManager allows encapsulation of event handlers that frequently need
// to be enabled or disabled due to change of state. Behaviors must be first
// registered with BehaviorManager.register().
var BehaviorManager = {
behaviors: {},
// `behaviors' is an object that must support three methods at minimal:
// `enable', `disable', and `fire'.
// * `enable' is called when a behavior is switched on.
// * `disable' is called when a behavior is switched off.
// * `fire' is called when the behavior must be executed immediately.
// If `fire' is not present, BehaviorManager assumes that a function called
// `observer' is present and calls it.
// Although not enforced by BehaviorManager, it is customary to name the
// event handler `observer".
// The `behaviors' object must not manipulate `_enabled', as it is used
// internally by BehaviorManager.
register: function(name, behaviors) {
this.behaviors[name] = behaviors;
this.behaviors[name]._enabled = false;
},
enable: function(behavior, immediatelyFire) {
var name = behavior;
behavior = this.behaviors[behavior];
(behavior._enabled = true) && behavior.enable();
if (immediatelyFire) {
this.fire(name);
}
},
// Returns false if the behavior has already been disabled, otherwise true.
disable: function(behavior) {
behavior = this.behaviors[behavior];
if (behavior._enabled) {
behavior.disable();
behavior._enabled = false;
return true;
} else {
return false;
}
},
fire: function(behavior) {
behavior = this.behaviors[behavior];
(behavior.fire || behavior.observer).bind(behavior)();
}
};
// If o is a string, it will be treated as an id.
// If o is an element, make sure the DOM is loaded.
// If o is a Selector, it will be a live set of nodes.
Function.prototype.toBehavior = function(o, event) {
return {
observer: this,
enable: o instanceof Selector ? function() {
o.findElements().uniq().invoke("observe", event, this.observer);
} : function() { Event.observe(o, event, this.observer); },
disable: o instanceof Selector ? function() {
o.findElements().uniq().invoke("stopObserving", event, this.observer);
} : function() { Event.stopObserving(o, event, this.observer); }
};
};
BehaviorManager.register("slashSearch", function(e) {
if (e.findElement().identify() == "text") {
return;
}
if ((e.which || e.keyCode) == 47) { // KEY_SLASH
$("text").focus(); e.stop();
}
}.toBehavior(document, "keypress"));
BehaviorManager.register("submitOnEnter", function(e) {
e.stop();
window.location.hash = "#search/" + $F("text");
}.toBehavior("phonebook-search", "submit"));
BehaviorManager.register("centerHeader", {
attached: false, fire: function() {
this.observer.adjust = true;
this.observer();
},
observer: function(e) {
if (arguments.callee.adjust) { $("header").verticallyCenter(); }
},
enable: function() {
this.observer.adjust = true;
if (!this.attached) {
Event.observe(Prototype.resizeTarget, "resize", this.observer);
this.attached = true;
}
},
disable: function() {
this.observer.adjust = false;
$("header").stopVerticallyCentering();
}
});
// Normally, you should simply mix in a method called `startSearch' and then
// call SearchManager.initialize() when DOM is loaded. For advanced examples,
// take a look at tree.js, which overrides everything but `initialize'.
var SearchManager = {
enabledBehaviorsOnInit: ["submitOnEnter"],
initialize: function() {
$(document).observe("hash:changed", this.onHashChange.bind(this));
this.onLoad();
this.enabledBehaviorsOnInit.each(function(b) {
BehaviorManager.enable(b);
});
},
onHashChange: function(e) {
console.log("hash");
console.log(this);
$("text").value = e.memo.hash.replace("search/", '');
this.startSearch.bind(this)();
},
onLoad: function() {
if (window.location.hash.startsWith("#search/")) {
$(document).fire("hash:changed", {
hash: window.location.hash.substring(1)
});
} else {
$("phonebook-search").addClassName("large");
BehaviorManager.enable("centerHeader", true);
$("text").focus();
}
}
};
String.prototype.dnToEmail = function() {
var m = this.match(/mail=(\w+@mozilla.*),o=/);
return (m ? m[1] : null);
};

76
js/edit.js Normal file
Просмотреть файл

@ -0,0 +1,76 @@
BehaviorManager.enable("slashSearch");
$(document).observe("dom:loaded", function() {
$("edit-entry").addClassName("selected").removeAttribute("href");
if (window.location.search.toQueryParams().edit_mail) {
$("edit-entry").update("Edit Entry");
}
$("phonebook-search").observe("submit", function(e) {
e.stop();
window.location = "./#search/" + $F("text");
});
var countryMap = {
'Mountain View': 'US',
'Auckland': 'NZ',
'Beijing': 'CN',
'Denmark': 'DK',
'Paris': 'FR',
'Toronto': 'CA',
'Tokyo': 'JP'
};
$("office-city-select").observe("change", function(e) {
var city = $F(this);
$("office-city-text")[city == "Other" ? "show" : "hide"]();
if (countryMap[city]) {
$("office-country-select").value = countryMap[city];
}
});
var remover = function(e) {
e.element().up().remove();
e.stop();
};
var adder = function(name, title) {
title = "Remove " + title;
return function(e) {
var div = new Element("div");
var input = new Element("input", {type: "text", name: name});
var a = new Element("a", {href: '#', title: title});
div.insert(input).insert(a);
a.observe("click", remover).addClassName("remove-link");
e.element().insert({before: div}); e.stop();
input.focus();
};
};
$("email-alias-add").observe("click", adder("emailAlias[]", "e-mail"));
$("phone-number-add").observe("click", adder("mobile[]", "number"));
$("im-add").observe("click", adder("im[]", "account"));
$w("email-aliases phone-numbers im-accounts").map(function(x) {
return $(x).descendants().find("input + a");
}).flatten().compact().invoke("observe", "click", remover).each(function(x) {
x.writeAttribute("title", x.innerHTML).update('');
});
// Replace dumb combobox with an autocomplete textbox
var manager = new Element("input", {type: "text", id: "manager-text"});
$("select-manager").hide().insert({before: manager});
manager.value = $$("option[value='#{dn}']".interpolate({
dn: $F("select-manager")
}))[0].innerHTML;
new Autocomplete(manager, {
serviceUrl: "./search.php?format=autocomplete",
minChars: 2,
onSelect: function(value, data) {
$("select-manager").value = data;
}
});
});

23
js/view-cards.js Normal file
Просмотреть файл

@ -0,0 +1,23 @@
BehaviorManager.enable("slashSearch");
SearchManager.startSearch = function() {
if (!$F("text").strip()) {
window.location = "./";
}
BehaviorManager.disable("centerHeader") &&
$("phonebook-search").removeClassName("large");
$("phonebook-search").request({onSuccess: function onSuccess(r) {
$("results").update('').update(r.responseText ||
'<div style="text-align: center; margin-top: 5em;">' +
'<img src="./img/ohnoes.jpg" />' +
'<h2>OH NOES! No ones were foundz.</h2>' +
'</div>'
);
$("text").releaseFocus();
}});
};
$(document).observe("dom:loaded", function() {
$("menu").down("a.card").addClassName("selected");
SearchManager.initialize();
});

72
js/view-faces.js Normal file
Просмотреть файл

@ -0,0 +1,72 @@
BehaviorManager.register("centerVCard", {
observer: function() { $$("div.vcard").invoke("verticallyCenter"); },
enable: function() {
Event.observe(Prototype.resizeTarget, "resize", this.observer);
},
disable: Prototype.emptyFunction
});
Object.extend(SearchManager, {
startSearch: function() {
BehaviorManager.disable("centerHeader") &&
$("phonebook-search").removeClassName("large");
$("phonebook-search").request({
parameters: {format: "json"},
onSuccess: function onSuccess(r) {
$("text").releaseFocus();
$("results").update('');
r.responseText.evalJSON().each(function(person) {
var container = new Element("div").addClassName("photo-frame");
var face = new Element("img", {
src: person.picture + "&type=thumb", "class": "wall-photo"
}).observe("click", function() {
this.showPerson(person.dn);
}.bind(this));
var name = new Element("span").update(person.cn);
container.insert(name).insert(face);
if (person.employeetype.indexOf("DISABLED") != -1) {
container.addClassName("disabled");
}
$("results").insert(container);
}.bind(this));
}.bind(this)
});
},
showPerson: function(dn) {
$("phonebook-search").request({
parameters: {format: "html", query: dn.dnToEmail()},
onSuccess: function onSuccess(r) {
$(document.body).addClassName("lightbox");
var close = new Element("div").observe("click", function(e) {
$(document.body).removeClassName("lightbox");
}).addClassName("close-button").writeAttribute("title", "Close");
$("overlay").update('').update(r.responseText).
down("div.vcard").verticallyCenter().
down("div.header").insert(close);
$("overlay").down(".vcard p.manager a").observe("click", function() {
$(document.body).removeClassName("lightbox");
});
}
});
}
});
BehaviorManager.enable("slashSearch");
$(document).observe("dom:loaded", function() {
$("menu").down("a.wall").addClassName("selected");
BehaviorManager.enable("centerVCard");
BehaviorManager.enable("submitOnEnter");
$("overlay").observe("click", function(e) {
if (e.element() == this) {
$(document.body).removeClassName("lightbox");
}
});
SearchManager.initialize();
});

168
js/view-tree.js Normal file
Просмотреть файл

@ -0,0 +1,168 @@
Element.addMethods("li", {
childTree: function(element) {
var next = $(element).next();
return (next && next.match("ul")) ? next : undefined;
},
collapse: function(element) {
var child = $(element).childTree();
child && child.hide();
return $(element).addClassName("collapsed").removeClassName("expanded");
},
expand: function(element) {
var child = $(element).childTree();
child && child.show();
return $(element).addClassName("expanded").removeClassName("collapsed");
},
collapsed: function(element) {
return $(element).hasClassName("collapsed");
},
expanded: function(element) {
return !$(element).collapsed();
},
toggleTree: function(element) {
element = $(element);
return element[element.collapsed() ? "expand" : "collapse"]();
}
});
BehaviorManager.register("scrollSnap", function() {
var card = $("person").down("div.vcard");
var tree = $("orgchart");
if (!card || !tree) { return; }
if (!card.retrieve("originalTop")) {
card.store("originalTop", card.getStyle("marginTop"));
}
var refTop = tree.viewportOffset().top;
card[refTop >= 5 ? "removeClassName" : "addClassName"]("snap-to-top");
}.toBehavior(document, "scroll"));
BehaviorManager.register("treeNodeToggling", function(e) {
!e.element().match("a") && $(this).toggleTree();
Tree.select(this);
e.stop();
}.toBehavior(new Selector("div li.hr-node"), "click"));
BehaviorManager.register("treeNodeSelecting", function(e) {
Tree.select($(this).up().addClassName("selected"));
e.stop();
}.toBehavior(new Selector("div li.hr-node span.title"), "click"));
BehaviorManager.register("stopFilteringOnEsc", function(e) {
console.log(e.which || e.keyCode);
if ((e.which || e.keyCode) == Event.KEY_ESC) {
(function() { $(this).clear(); }).bind(this).defer();
Tree.stopFiltering();
}
}.toBehavior("text", "keyup"));
BehaviorManager.register("filterOnSubmit", function(e) {
e.stop();
Tree.stopFiltering();
$("text").blur();
if (!$F("text").strip()) {
$$("#orgchart li:not(.leaf)").invoke("expand");
return;
}
Tree.filter();
}.toBehavior("phonebook-search", "submit"));
var Tree = {
selected: null,
select: function(node, changeHash) {
this.selected && this.selected.removeClassName("selected");
this.selected = $(node).addClassName("selected");
window.location.hash = "#search/" + this.selected.id.replace("-at-", '@');
},
stopFiltering: function() {
$("orgchart").removeClassName("filter-view");
$$("#orgchart li.highlighted").invoke("removeClassName", "highlighted");
$$("#orgchart li:not(.leaf)").invoke("expand");
},
showPerson: function(email) {
new Ajax.Request("search.php", {
method: "get",
parameters: {query: email, format: "html"},
onSuccess: function(r) {
$("person").update(r.responseText).down(".vcard");
BehaviorManager.fire("scrollSnap");
}
});
},
filter: function() {
$("phonebook-search").request({
parameters: {format: "json"},
onSuccess: function onSuccess(r) {
var people = r.responseText.evalJSON();
people = people.pluck("dn").map(this.dnToEmail).compact();
people.sort(function(a, b) {
return a.cumulativeOffset().top - b.cumulativeOffset().top;
});
console.log("Post-eval");
var allowedToShow = people.map(function(x) {
console.log(" " + x.id);
var rootwards = x.ancestors().find("ul").compact().invoke("previous", "li");
console.log(" Post rootwards");
var leafwards = [];
console.log(" Post leafwards");
console.log(" Post leafing");
return [x].concat(rootwards).concat(leafwards).compact();
});
$$("#orgchart li:not(.leaf)").invoke("collapse");
console.log("Post-collapsing");
allowedToShow.flatten().uniq().find(":not(.leaf)").invoke("expand");
console.log("Post-expansion");
people.invoke("addClassName", "highlighted").first().scrollTo();
$("orgchart").addClassName("filter-view");
}.bind(this)
});
}
};
Object.extend(SearchManager, {
enabledBehaviorsOnInit: ["filterOnSubmit"],
onHashChange: function(e) {
var email = e.memo.hash.replace("search/", '');
Tree.showPerson(email);
Tree.select($(email.replace('@', "-at-")));
},
onLoad: function() {
var hash = window.location.hash;
if (hash.startsWith("#search/")) {
$(document).fire("hash:changed", {hash: hash.substring(1)});
var search = $("text").value = hash.replace(/^#search\//, '');
if (!search.strip()) { return; }
Tree.filter();
}
}
});
$(document).observe("dom:loaded", function() {
$("search").update("Filter");
$("menu").down("a.tree").addClassName("selected");
BehaviorManager.enable("scrollSnap");
BehaviorManager.enable("treeNodeToggling");
BehaviorManager.enable("slashSearch");
BehaviorManager.enable("stopFilteringOnEsc");
SearchManager.initialize();
});
Object.extend(Tree, {
dnToEmail: function(x) {
var m = x.match(/mail=(\w+@mozilla.*),o=/);
return m ? $(m[1].replace('@', "-at-")) : null;
}
});

Просмотреть файл

@ -1,23 +1,15 @@
<?php <?php
require_once("init.php"); require_once("init.php");
require_once("filters.inc");
$keyword = isset($_GET["query"]) ? $_GET["query"] : '*'; $keyword = isset($_GET["query"]) ? $_GET["query"] : '*';
$entries = normalize(search_users($ldapconn, $keyword)); $entries = normalize(search_users($ldapconn, $keyword));
$filters = array( $filters = get_filters();
"description" => "wikilinks",
"other" => "wikilinks",
"employeetype" => "employee_status",
"physicaldeliveryofficename" => "location_formatter",
"mobile" => "mobile_normalizer",
"im" => "mobile_normalizer",
"manager" => "get_manager"
);
foreach ($entries as &$entry) { foreach ($entries as &$entry) {
foreach ($entry as $name => $attribute) { foreach ($entry as $name => $attribute) {
if (isset($filters[$name]) && function_exists($filter = $filters[$name])) { if (isset($filters[$name]) && function_exists($filter = $filters[$name])) {
$entry[$name] = call_user_func($filter, $attribute); $entry[$name] = call_user_func($filter, $attribute);
} else {
# $attribute = htmlspecialchars($entry);
} }
} }
if (preg_match("/mail=(\w+@mozilla.*),o=/", $entry["dn"], $m)) { if (preg_match("/mail=(\w+@mozilla.*),o=/", $entry["dn"], $m)) {

Просмотреть файл

@ -63,7 +63,7 @@
<tr> <tr>
<td><label>Office Country</label></td> <td><label>Office Country</label></td>
<td> <td>
<select id="office_country_select" name="office_country"> <select id="office-country-select" name="office_country">
<option value=""></option> <option value=""></option>
<?php <?php
foreach($country_codes as $country_name => $code) { foreach($country_codes as $country_name => $code) {
@ -170,86 +170,7 @@
</div> </div>
<script src="js/autocomplete.js" type="text/javascript"></script> <script type="text/javascript" src="js/autocomplete.js"></script>
<script type="text/javascript"> <script type="text/javascript" src="js/edit.js"></script>
var countryMap = {
'Mountain View': 'US',
'Auckland': 'NZ',
'Beijing': 'CN',
'Denmark': 'DK',
'Paris': 'FR',
'Toronto': 'CA',
'Tokyo': 'JP'
};
$(document).observe("keypress", function(e) {
if ((e.charCode || e.keyCode) == 47) { // KEY_SLASH
$("text").focus();
e.stop();
}
});
$(document).observe("dom:loaded", function() {
$("phonebook-search").observe("submit", function(e) {
e.stop();
window.location = "./#search/" + $F("text");
});
$("edit-entry").addClassName("selected").removeAttribute("href");
if (window.location.search.toQueryParams().edit_mail) {
$("edit-entry").update("Edit Entry");
}
$("office-city-select").observe("change", function(e) {
var city = $F("office-city-select");
$("office-city-text")[city == "Other" ? "show" : "hide"]();
if (countryMap[city]) {
$("office-country-select").value = countryMap[city];
}
});
var remover = function(e) {
e.element().up().remove();
e.stop();
};
var adder = function(name, title) {
title = "Remove " + title;
return function(e) {
var div = new Element("div");
var input = new Element("input", {type: "text", name: name});
var a = new Element("a", {href: '#', title: title});
div.insert(input).insert(a);
a.observe("click", remover).addClassName("remove-link");
e.element().insert({before: div}); e.stop();
input.focus();
};
};
$("email-alias-add").observe("click", adder("emailAlias[]", "e-mail"));
$("phone-number-add").observe("click", adder("mobile[]", "number"));
$("im-add").observe("click", adder("im[]", "account"));
$w("email-aliases phone-numbers im-accounts").map(function(x) {
return $(x).descendants().find("input + a");
}).flatten().compact().invoke("observe", "click", remover).each(function(x) {
x.writeAttribute("title", x.innerHTML).update('');
});
var manager = new Element("input", {type: "text", id: "manager-text"});
$("select-manager").hide().insert({before: manager});
manager.value = $$("option[value='#{dn}']".interpolate({
dn: $F("select-manager")
}))[0].innerHTML;
new Autocomplete(manager, {
serviceUrl: "./search.php?format=autocomplete",
minChars: 2,
onSelect: function(value, data) {
$("select-manager").value = data;
}
});
});
</script>
<?php require_once('templates/footer.php'); ?> <?php require_once('templates/footer.php'); ?>

Просмотреть файл

@ -5,85 +5,8 @@
<title>Mozilla Phonebook</title> <title>Mozilla Phonebook</title>
<link href="css/style.css" rel="stylesheet" type="text/css" /> <link href="css/style.css" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="./favicon.ico" />
<script src="js/prototype.js" type="text/javascript"></script> <script type="text/javascript" src="js/prototype.js"></script>
<script type="text/javascript"> <script type="text/javascript" src="js/common.js"></script>
if (!window.console) {
window.console = {};
window.console.log = Prototype.emptyFunction;
}
// Implement onhashchange support
(function() {
var hash = window.location.hash;
var fire = function(str) {
$(document).fire("hash:changed", { hash: str.substring(1) });
};
var pe = new PeriodicalExecuter(function() {
var newHash = window.location.hash;
if (newHash != hash) {
fire(newHash);
hash = newHash;
}
}, 1);
})();
Element.addMethods({
verticallyCenter: function(element, percentage) {
element = $(element);
if (!element.retrieve("originalMarginTop")) {
element.store("originalMarginTop", element.getStyle("marginTop"));
}
var offset = $(document).viewport.getHeight() - element.getHeight();
return element.setStyle({marginTop: (offset * (percentage || 0.4)) + "px"});
},
stopVerticallyCentering: function(element) {
element = $(element);
var original = element.getStorage().unset("originalMarginTop");
return element.setStyle({marginTop: original});
},
searchify: function(element, results) {
return $(element).writeAttribute({type: "search", results: results || 5});
}
});
Element.addMethods("input", {
releaseFocus: function(element) {
$w("blur focus blur").each(function(method) { $(element)[method](); });
return element;
}
});
Prototype.resizeTarget = document.onresize ? document : window;
Array.prototype.find = function find(selector) {
return this.filter(function(x) {
return x.match ? x.match(selector) : false;
});
};
String.prototype.dnToEmail = function() {
var m = this.match(/mail=(\w+@mozilla.*),o=/);
return (m ? m[1] : null);
};
Ajax.Responders.register({
onCreate: function() { $("throbber").addClassName("loading"); },
onComplete: function() { $("throbber").removeClassName("loading"); }
});
$(document).observe("dom:loaded", function() {
$$("#menu a.persist").each(function(link) {
link.store("originalLink", link.readAttribute("href"));
});
});
$(document).observe("hash:changed", function(e) {
$$("#menu a.persist").each(function(link) {
link.writeAttribute("href", link.retrieve("originalLink") + "#" + e.memo.hash);
});
});
</script>
</head> </head>
<body> <body>
@ -98,7 +21,7 @@
<div id="throbber"></div> <div id="throbber"></div>
<ul id="menu"> <ul id="menu">
<li><a class="card persist" href="./">Cards</a></li> <li><a class="card persist" href="./">Cards</a></li>
<li><a class="wall persist" href="./face.php">Faces</a></li> <li><a class="wall persist" href="./faces.php">Faces</a></li>
<li><a class="tree" href="./tree.php">Org Chart</a></li> <li><a class="tree" href="./tree.php">Org Chart</a></li>
<li class="edit"><a class="edit" href="./edit.php" id="edit-entry">Edit My Entry</a></li> <li class="edit"><a class="edit" href="./edit.php" id="edit-entry">Edit My Entry</a></li>
</ul> </ul>

213
tree.php
Просмотреть файл

@ -1,11 +1,12 @@
<?php <?php
require_once "init.php"; require_once "init.php";
require_once "config.php";
$search = ldap_search( $search = ldap_search(
$ldapconn, $ldapconn,
"o=com, dc=mozilla", $tree_config["ldap"]["search_base"],
"mail=*", $tree_config["ldap"]["search_filter"],
array("cn", "manager", "title", "mail", "employeeType") $tree_config["ldap"]["search_attributes"]
); );
$data = ldap_get_entries($ldapconn, $search); $data = ldap_get_entries($ldapconn, $search);
@ -13,15 +14,11 @@ $people = array();
$orphans = array(); $orphans = array();
$everyone = array(); $everyone = array();
$tree_view_roots = tree_view_roots();
foreach ($data as $person) { foreach ($data as $person) {
$mail = $person['mail'][0]; $mail = $person['mail'][0];
$everyone[$mail] = array( $everyone[$mail] = tree_view_process_entry($person);
"title" => !empty($person["title"][0]) ? $person["title"][0] : null,
"name" => !empty($person["cn"][0]) ? $person["cn"][0] : null,
"disabled" => isset($person["employeetype"]) ?
strpos($person["employeetype"][0], 'D') !== FALSE:
FALSE
);
// If a user has a manager, try to find their place in the tree. // If a user has a manager, try to find their place in the tree.
if (!empty($person["manager"][0])) { if (!empty($person["manager"][0])) {
@ -34,34 +31,17 @@ foreach ($data as $person) {
} else { } else {
$people[$manager][] = $mail; $people[$manager][] = $mail;
} }
} elseif (!empty($mail) && } elseif (!empty($mail) && !in_array($mail, $tree_view_roots)) {
strpos("lilly@mozilla.com", $mail) === FALSE &&
strpos("mitchell@mozilla.com", $mail) === FALSE) {
// Person is an orphan. // Person is an orphan.
$orphans[] = $mail; $orphans[] = $mail;
} }
} }
function item($email, $leaf=FALSE) {
global $everyone;
$email = htmlspecialchars($email);
$id = str_replace('@', "-at-", $email);
$name = htmlspecialchars($everyone[$email]["name"]);
$title = htmlspecialchars($everyone[$email]["title"]);
$leaf = $leaf ? " leaf" : '';
$disabled = $everyone[$email]["disabled"] ? " disabled" : '';
return "<li id=\"$id\" class=\"hr-node expanded$leaf$disabled\">".
"<a href=\"#search/$email\" class=\"hr-link\">$name</a> ".
"<span class=\"title\">$title</span>".
"</li>";
}
function make_tree($level, $root, $nodes=null) { function make_tree($level, $root, $nodes=null) {
global $people; global $people;
global $everyone; global $everyone;
print "\n". item($root, ($nodes == null)); print "\n". tree_view_item($root, ($nodes == null));
if (is_array($nodes)) { if (is_array($nodes)) {
print "\n<ul>"; print "\n<ul>";
@ -85,8 +65,15 @@ require_once "templates/header.php";
<div id="orgchart" class="tree"> <div id="orgchart" class="tree">
<ul> <ul>
<?= make_tree(0, 'mitchell@mozilla.com'); ?> <?php
<?= make_tree(0, 'lilly@mozilla.com', $people['lilly@mozilla.com']); ?> foreach ($tree_view_roots as $root) {
if (!isset($people[$root])) {
make_tree(0, $root);
} else {
make_tree(0, $root, $people[$root]);
}
}
?>
</ul> </ul>
</div> </div>
<br /> <br />
@ -96,7 +83,7 @@ require_once "templates/header.php";
<ul> <ul>
<?php <?php
foreach ($orphans as $orphan) { foreach ($orphans as $orphan) {
print "\n". item($orphan, TRUE); print "\n". tree_view_item($orphan, TRUE);
} }
?> ?>
</ul> </ul>
@ -108,166 +95,6 @@ print "\n". item($orphan, TRUE);
</div> </div>
<script type="text/javascript"> <script type="text/javascript" src="js/view-tree.js"></script>
Element.addMethods("li", {
childTree: function(element) {
var next = $(element).next();
return (next && next.match("ul")) ? next : undefined;
},
collapse: function(element) {
var child = $(element).childTree();
child && child.hide();
return $(element).addClassName("collapsed").removeClassName("expanded");
},
expand: function(element) {
var child = $(element).childTree();
child && child.show();
return $(element).addClassName("expanded").removeClassName("collapsed");
},
collapsed: function(element) {
return $(element).hasClassName("collapsed");
},
expanded: function(element) {
return !$(element).collapsed();
},
toggleTree: function(element) {
element = $(element);
return element[element.collapsed() ? "expand" : "collapse"]();
}
});
$(document).observe("dom:loaded", function() {
$("search").update("Filter");
$("menu").down("a.tree").addClassName("selected");
var region = $("person");
$(document).observe("scroll", scrollSnap);
$$("div li.hr-node").invoke("observe", "click", function(e) {
!e.element().match("a") && $(this).toggleTree();
select(this);
e.stop();
});
$$("div li.hr-node span.title").invoke("observe", "click", function(e) {
select($(this).up().addClassName("selected"));
e.stop();
});
$(document).observe("keypress", function(e) {
if ((e.which || e.keyCode) == 47) { // KEY_SLASH
$("text").focus(); e.stop();
}
});
$("text").observe("keypress", function(e) {
if ((e.which || e.keyCode) == Event.KEY_ESC) {
(function() { $(this).clear(); }).bind(this).defer();
stopFiltering();
}
if ((e.which || e.keyCode) == 47) {
e.stop();
}
});
$("phonebook-search").observe("submit", function(e) {
e.stop();
stopFiltering();
$("text").blur();
if (!$F("text").strip()) {
$$("#orgchart li:not(.leaf)").invoke("expand");
return;
}
// window.location.hash = "#search/" + $F("text");
filter();
});
function scrollSnap() {
var card = region.down("div.vcard");
var tree = $("orgchart");
if (!card || !tree) { return; }
if (!card.retrieve("originalTop")) {
card.store("originalTop", card.getStyle("marginTop"));
}
var refTop = tree.viewportOffset().top;
card[refTop >= 5 ? "removeClassName" : "addClassName"]("snap-to-top");
}
function filter() {
$("phonebook-search").request({
parameters: {format: "json"},
onSuccess: function onSuccess(r) {
var people = r.responseText.evalJSON().pluck("dn").map(function(x) {
var m = x.match(/mail=(\w+@mozilla.*),o=/);
return m ? $(m[1].replace('@', "-at-")) : null;
}).compact();
people.sort(function(a, b) {
return a.cumulativeOffset().top - b.cumulativeOffset().top;
});
console.log("Post-eval");
var allowedToShow = people.map(function(x) {
console.log(" " + x.id);
var rootwards = x.ancestors().find("ul").compact().invoke("previous", "li");
console.log(" Post rootwards");
var leafwards = [];
console.log(" Post leafwards");
// leafwards = leafwards && leafwards.match("ul") ? leafwards.select("li") : [];
console.log(" Post leafing");
return [x].concat(rootwards).concat(leafwards).compact();
});
$$("#orgchart li:not(.leaf)").invoke("collapse");
console.log("Post-collapsing");
allowedToShow.flatten().uniq().find(":not(.leaf)").invoke("expand");
console.log("Post-expansion");
people.invoke("addClassName", "highlighted").first().scrollTo();
$("orgchart").addClassName("filter-view");
// allowedToShow.each(function(x) console.log(x));
}
});
}
function stopFiltering() {
$("orgchart").removeClassName("filter-view");
$$("#orgchart li.highlighted").invoke("removeClassName", "highlighted");
$$("#orgchart li:not(.leaf)").invoke("expand");
}
var selected = null;
function select(node) {
selected && selected.removeClassName("selected");
selected = $(node).addClassName("selected");
window.location.hash = "#search/" + selected.id.replace("-at-", '@');
}
$(document).observe("hash:changed", function(e) {
showPerson(e.memo.hash.replace("search/", ''));
});
var hash = window.location.hash;
if (hash.startsWith("#search/")) {
$(document).fire("hash:changed", {hash: hash.substring(1)});
var search = $("text").value = hash.replace(/^#search\//, '');
if (!search.strip()) { return; }
filter();
}
function showPerson(email) {
new Ajax.Request("search.php", {
method: "get",
parameters: {query: email, format: "html"},
onSuccess: function(r) {
$("person").update(r.responseText).down(".vcard");
scrollSnap();
}
});
}
});
</script>
<?php require_once "templates/footer.php"; ?> <?php require_once "templates/footer.php"; ?>