diff --git a/ajax/addcontact.php b/ajax/addcontact.php
new file mode 100644
index 00000000..4bd3df54
--- /dev/null
+++ b/ajax/addcontact.php
@@ -0,0 +1,60 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+function debug($msg) {
+ OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+}
+foreach ($_POST as $key=>$element) {
+ debug('_POST: '.$key.'=>'.$element);
+}
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+$aid = $_POST['aid'];
+$addressbook = OC_Contacts_App::getAddressbook( $aid );
+
+$fn = trim($_POST['fn']);
+$n = trim($_POST['n']);
+
+$vcard = new OC_VObject('VCARD');
+$vcard->setUID();
+$vcard->setString('N',$n);
+$vcard->setString('FN',$fn);
+
+$id = OC_Contacts_VCard::add($aid,$vcard->serialize());
+if(!$id) {
+ OC_JSON::error(array('data' => array('message' => $l->t('There was an error adding the contact.'))));
+ OC_Log::write('contacts','ajax/addcontact.php: Recieved non-positive ID on adding card: '.$id, OC_Log::ERROR);
+ exit();
+}
+
+OC_JSON::success(array('data' => array( 'id' => $id )));
diff --git a/ajax/contactdetails.php b/ajax/contactdetails.php
new file mode 100644
index 00000000..f8f78ad3
--- /dev/null
+++ b/ajax/contactdetails.php
@@ -0,0 +1,53 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/contactdetails.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+function debug($msg) {
+ OC_Log::write('contacts','ajax/contactdetails.php: '.$msg, OC_Log::DEBUG);
+}
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+$id = $_GET['id'];
+$vcard = OC_Contacts_App::getContactVCard( $id );
+if(is_null($vcard)) {
+ bailOut($l->t('Error parsing VCard for ID: "'.$id.'"'));
+}
+$details = OC_Contacts_VCard::structureContact($vcard);
+if(isset($details['PHOTO'])) {
+ $details['PHOTO'] = true;
+ //unset($details['PHOTO']);
+} else {
+ $details['PHOTO'] = false;
+}
+$details['id'] = $id;
+
+OC_JSON::success(array('data' => $details));
\ No newline at end of file
diff --git a/ajax/cropphoto.php b/ajax/cropphoto.php
new file mode 100644
index 00000000..878fb561
--- /dev/null
+++ b/ajax/cropphoto.php
@@ -0,0 +1,38 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$tmp_path = $_GET['tmp_path'];
+$id = $_GET['id'];
+OC_Log::write('contacts','ajax/cropphoto.php: tmp_path: '.$tmp_path.', exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+$tmpl = new OC_TEMPLATE("contacts", "part.cropphoto");
+$tmpl->assign('tmp_path', $tmp_path);
+$tmpl->assign('id', $id);
+$page = $tmpl->fetchPage();
+
+OC_JSON::success(array('data' => array( 'page' => $page )));
diff --git a/ajax/editaddress.php b/ajax/editaddress.php
new file mode 100644
index 00000000..4e6456f6
--- /dev/null
+++ b/ajax/editaddress.php
@@ -0,0 +1,31 @@
+
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+require_once('../../../lib/base.php');
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$id = $_GET['id'];
+$checksum = isset($_GET['checksum'])?$_GET['checksum']:'';
+$vcard = OC_Contacts_App::getContactVCard($id);
+$adr_types = OC_Contacts_App::getTypesOfProperty('ADR');
+
+$tmpl = new OC_TEMPLATE("contacts", "part.edit_address_dialog");
+if($checksum) {
+ $line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum);
+ $element = $vcard->children[$line];
+ $adr = OC_Contacts_VCard::structureProperty($element);
+ $tmpl->assign('adr',$adr);
+}
+
+$tmpl->assign('id',$id);
+$tmpl->assign('adr_types',$adr_types);
+
+$tmpl->printpage();
+
+?>
diff --git a/ajax/editname.php b/ajax/editname.php
new file mode 100644
index 00000000..6205cc74
--- /dev/null
+++ b/ajax/editname.php
@@ -0,0 +1,35 @@
+
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+require_once('../../../lib/base.php');
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$tmpl = new OC_TEMPLATE("contacts", "part.edit_name_dialog");
+
+$id = $_GET['id'];
+if($id) {
+ $vcard = OC_Contacts_App::getContactVCard($id);
+
+
+ $name = array('', '', '', '', '');
+ if($vcard->__isset('N')) {
+ $property = $vcard->__get('N');
+ if($property) {
+ $name = OC_Contacts_VCard::structureProperty($property);
+ }
+ }
+ $tmpl->assign('name',$name);
+ $tmpl->assign('id',$id);
+} else {
+ $addressbooks = OC_Contacts_Addressbook::active(OC_User::getUser());
+ $tmpl->assign('addressbooks', $addressbooks);
+}
+$tmpl->printpage();
+
+?>
diff --git a/ajax/loadphoto.php b/ajax/loadphoto.php
new file mode 100644
index 00000000..d9f7e737
--- /dev/null
+++ b/ajax/loadphoto.php
@@ -0,0 +1,53 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ * TODO: Translatable strings.
+ * Remember to delete tmp file at some point.
+ */
+// Init owncloud
+require_once('../../../lib/base.php');
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+// foreach ($_POST as $key=>$element) {
+// OC_Log::write('contacts','ajax/savecrop.php: '.$key.'=>'.$element, OC_Log::DEBUG);
+// }
+
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/savecrop.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+
+$image = null;
+
+$id = isset($_GET['id']) ? $_GET['id'] : '';
+
+if($id == '') {
+ bailOut('Missing contact id.');
+}
+
+$tmpl = new OC_TEMPLATE("contacts", "part.contactphoto");
+$tmpl->assign('id', $id);
+$page = $tmpl->fetchPage();
+OC_JSON::success(array('data' => array('page'=>$page)));
+?>
diff --git a/ajax/newcontact.php b/ajax/newcontact.php
new file mode 100644
index 00000000..3d1a8e74
--- /dev/null
+++ b/ajax/newcontact.php
@@ -0,0 +1,58 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+function debug($msg) {
+ OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+}
+foreach ($_POST as $key=>$element) {
+ debug('_POST: '.$key.'=>'.$element);
+}
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$addressbooks = OC_Contacts_Addressbook::all(OC_USER::getUser());
+
+$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize'));
+$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size'));
+$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
+
+$freeSpace=OC_Filesystem::free_space('/');
+$freeSpace=max($freeSpace,0);
+$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
+
+$tmpl = new OC_Template('contacts','part.contact');
+$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
+$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize));
+$tmpl->assign('addressbooks',$addressbooks);
+$tmpl->assign('id','');
+$page = $tmpl->fetchPage();
+
+OC_JSON::success(array('data' => array( 'page' => $page )));
diff --git a/ajax/savecrop.php b/ajax/savecrop.php
new file mode 100644
index 00000000..7b738472
--- /dev/null
+++ b/ajax/savecrop.php
@@ -0,0 +1,136 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ * TODO: Translatable strings.
+ * Remember to delete tmp file at some point.
+ */
+// Init owncloud
+require_once('../../../lib/base.php');
+OC_Log::write('contacts','ajax/savecrop.php: Huzzah!!!', OC_Log::DEBUG);
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+// foreach ($_POST as $key=>$element) {
+// OC_Log::write('contacts','ajax/savecrop.php: '.$key.'=>'.$element, OC_Log::DEBUG);
+// }
+
+// Firefox and Konqueror tries to download application/json for me. --Arthur
+OC_JSON::setContentTypeHeader('text/plain');
+
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/savecrop.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+
+$image = null;
+
+$x1 = (isset($_POST['x1']) && $_POST['x1']) ? $_POST['x1'] : -1;
+//$x2 = isset($_POST['x2']) ? $_POST['x2'] : -1;
+$y1 = (isset($_POST['y1']) && $_POST['y1']) ? $_POST['y1'] : -1;
+//$y2 = isset($_POST['y2']) ? $_POST['y2'] : -1;
+$w = (isset($_POST['w']) && $_POST['w']) ? $_POST['w'] : -1;
+$h = (isset($_POST['h']) && $_POST['h']) ? $_POST['h'] : -1;
+$tmp_path = isset($_POST['tmp_path']) ? $_POST['tmp_path'] : '';
+$id = isset($_POST['id']) ? $_POST['id'] : '';
+
+if(in_array(-1, array($x1, $y1, $w, $h))) {
+ bailOut('Wrong crop dimensions: '.implode(', ', array($x1, $y1, $w, $h)));
+}
+
+if($tmp_path == '') {
+ bailOut('Missing path to temporary file.');
+}
+
+if($id == '') {
+ bailOut('Missing contact id.');
+}
+
+OC_Log::write('contacts','savecrop.php: files: '.$tmp_path.' exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+
+if(file_exists($tmp_path)) {
+ $image = new OC_Image();
+ if($image->loadFromFile($tmp_path)) {
+ if($image->crop($x1, $y1, $w, $h)) {
+ if($image->resize(200)) {
+ $tmpfname = tempnam("/tmp", "occCropped"); // create a new file because of caching issues.
+ if($image->save($tmpfname)) {
+ unlink($tmp_path);
+ $card = OC_Contacts_App::getContactVCard($id);
+ if(!$card) {
+ unlink($tmpfname);
+ bailOut('Error getting contact object.');
+ }
+ if($card->__isset('PHOTO')) {
+ OC_Log::write('contacts','savecrop.php: files: PHOTO property exists.', OC_Log::DEBUG);
+ $property = $card->__get('PHOTO');
+ if(!$property) {
+ unlink($tmpfname);
+ bailOut('Error getting PHOTO property.');
+ }
+ $property->setValue($image->__toString());
+ $property->parameters[] = new Sabre_VObject_Parameter('ENCODING', 'b');
+ $property->parameters[] = new Sabre_VObject_Parameter('TYPE', $image->mimeType());
+ $card->__set('PHOTO', $property);
+ } else {
+ OC_Log::write('contacts','savecrop.php: files: Adding PHOTO property.', OC_Log::DEBUG);
+ $card->addProperty('PHOTO', $image->__toString(), array('ENCODING' => 'b', 'TYPE' => $image->mimeType()));
+ }
+ if(!OC_Contacts_VCard::edit($id,$card->serialize())) {
+ bailOut('Error saving contact.');
+ }
+ unlink($tmpfname);
+ //$result=array( "status" => "success", 'mime'=>$image->mimeType(), 'tmp'=>$tmp_path);
+ $tmpl = new OC_TEMPLATE("contacts", "part.contactphoto");
+ $tmpl->assign('tmp_path', $tmpfname);
+ $tmpl->assign('mime', $image->mimeType());
+ $tmpl->assign('id', $id);
+ $tmpl->assign('width', $image->width());
+ $tmpl->assign('height', $image->height());
+ $page = $tmpl->fetchPage();
+ OC_JSON::success(array('data' => array('page'=>$page, 'tmp'=>$tmpfname)));
+ exit();
+ } else {
+ if(file_exists($tmpfname)) {
+ unlink($tmpfname);
+ }
+ bailOut('Error saving temporary image');
+ }
+ } else {
+ bailOut('Error resizing image');
+ }
+ } else {
+ bailOut('Error cropping image');
+ }
+ } else {
+ bailOut('Error creating temporary image');
+ }
+} else {
+ bailOut('Error finding image: '.$tmp_path);
+}
+
+if($tmp_path != '' && file_exists($tmp_path)) {
+ unlink($tmp_path);
+}
+
+?>
diff --git a/ajax/saveproperty.php b/ajax/saveproperty.php
new file mode 100644
index 00000000..fd1aa9e3
--- /dev/null
+++ b/ajax/saveproperty.php
@@ -0,0 +1,134 @@
+.
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+function debug($msg) {
+ OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+}
+foreach ($_POST as $key=>$element) {
+ debug('_POST: '.$key.'=>'.$element);
+}
+
+$id = isset($_POST['id'])?$_POST['id']:null;
+$name = isset($_POST['name'])?$_POST['name']:null;
+$value = isset($_POST['value'])?$_POST['value']:null;
+$parameters = isset($_POST['parameters'])?$_POST['parameters']:null;
+$checksum = isset($_POST['checksum'])?$_POST['checksum']:null;
+// if(!is_null($parameters)) {
+// debug('parameters: '.count($parameters));
+// foreach($parameters as $key=>$val ) {
+// debug('parameter: '.$key.'=>'.implode('/',$val));
+// }
+// }
+
+if(is_array($value)){ // FIXME: How to strip_tags for compound values?
+ ksort($value); // NOTE: Important, otherwise the compound value will be set in the order the fields appear in the form!
+ $value = OC_VObject::escapeSemicolons($value);
+} else {
+ $value = trim(strip_tags($value));
+}
+if(!$id) {
+ bailOut($l->t('id is not set.'));
+}
+if(!$checksum) {
+ bailOut($l->t('checksum is not set.'));
+}
+if(!$name) {
+ bailOut($l->t('element name is not set.'));
+}
+
+$vcard = OC_Contacts_App::getContactVCard( $id );
+$line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum);
+if(is_null($line)) {
+ bailOut($l->t('Information about vCard is incorrect. Please reload the page.'.$checksum.' "'.$line.'"'));
+}
+$element = $vcard->children[$line]->name;
+
+if($element != $name) {
+ bailOut($l->t('Something went FUBAR. ').$name.' != '.$element);
+}
+
+switch($element) {
+ case 'BDAY':
+ $date = New DateTime($value);
+ //$vcard->setDateTime('BDAY', $date, Sabre_VObject_Element_DateTime::DATE);
+ $value = $date->format(DateTime::ATOM);
+ case 'FN':
+ if(!$value) {
+ // create a method thats returns an alternative for FN.
+ //$value = getOtherValue();
+ }
+ case 'N':
+ case 'ORG':
+ case 'NICKNAME':
+ debug('Setting string:'.$name.' '.$value);
+ $vcard->setString($name, $value);
+ break;
+ case 'EMAIL':
+ $value = strtolower($value);
+ case 'TEL':
+ case 'ADR': // should I delete the property if empty or throw an error?
+ debug('Setting element: (EMAIL/TEL/ADR)'.$element);
+ if(!$value) {
+ unset($vcard->children[$line]); // Should never happen...
+ } else {
+ $vcard->children[$line]->setValue($value);
+ $vcard->children[$line]->parameters = array();
+ if(!is_null($parameters)) {
+ debug('Setting parameters: '.$parameters);
+ foreach($parameters as $key => $parameter) {
+ debug('Adding parameter: '.$key);
+ foreach($parameter as $val) {
+ debug('Adding parameter: '.$key.'=>'.$val);
+ $vcard->children[$line]->add(new Sabre_VObject_Parameter($key, strtoupper($val)));
+ }
+ }
+ }
+ }
+ break;
+}
+// Do checksum and be happy
+$checksum = md5($vcard->children[$line]->serialize());
+debug('New checksum: '.$checksum);
+
+if(!OC_Contacts_VCard::edit($id,$vcard->serialize())) {
+ OC_JSON::error(array('data' => array('message' => $l->t('Error updating contact property.'))));
+ OC_Log::write('contacts','ajax/setproperty.php: Error updating contact property: '.$value, OC_Log::ERROR);
+ exit();
+}
+
+//$adr_types = OC_Contacts_App::getTypesOfProperty('ADR');
+//$phone_types = OC_Contacts_App::getTypesOfProperty('TEL');
+
+OC_JSON::success(array('data' => array( 'line' => $line, 'checksum' => $checksum, 'oldchecksum' => $_POST['checksum'] )));
diff --git a/ajax/uploadphoto.php b/ajax/uploadphoto.php
new file mode 100644
index 00000000..62cd32f5
--- /dev/null
+++ b/ajax/uploadphoto.php
@@ -0,0 +1,133 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ */
+// Init owncloud
+require_once('../../../lib/base.php');
+
+// Check if we are a user
+// Firefox and Konqueror tries to download application/json for me. --Arthur
+OC_JSON::setContentTypeHeader('text/plain');
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+function bailOut($msg) {
+ OC_JSON::error(array('data' => array('message' => $msg)));
+ OC_Log::write('contacts','ajax/uploadphoto.php: '.$msg, OC_Log::DEBUG);
+ exit();
+}
+function debug($msg) {
+ OC_Log::write('contacts','ajax/uploadphoto.php: '.$msg, OC_Log::DEBUG);
+}
+
+// foreach ($_SERVER as $key=>$element) {
+// debug('$_SERVER: '.$key.'=>'.$element);
+// }
+// foreach ($_GET as $key=>$element) {
+// debug('_GET: '.$key.'=>'.$element);
+// }
+// foreach ($_POST as $key=>$element) {
+// debug('_POST: '.$key.'=>'.$element);
+// }
+// foreach ($_FILES as $key=>$element) {
+// debug('_FILES: '.$key.'=>'.$element);
+// }
+
+// If it is a Drag'n'Drop transfer it's handled here.
+$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
+if ($fn) {
+ // AJAX call
+ if (!isset($_GET['id'])) {
+ OC_Log::write('contacts','ajax/uploadphoto.php: No contact ID was submitted.', OC_Log::DEBUG);
+ OC_JSON::error(array('data' => array( 'message' => 'No contact ID was submitted.' )));
+ exit();
+ }
+ $id = $_GET['id'];
+ $tmpfname = tempnam('/tmp', 'occOrig');
+ file_put_contents($tmpfname, file_get_contents('php://input'));
+ debug($tmpfname.' uploaded');
+ $image = new OC_Image();
+ if($image->loadFromFile($tmpfname)) {
+ if($image->width() > 400 || $image->height() > 400) {
+ $image->resize(400); // Prettier resizing than with browser and saves bandwidth.
+ }
+ if(!$image->fixOrientation()) { // No fatal error so we don't bail out.
+ debug('Couldn\'t save correct image orientation: '.$tmpfname);
+ }
+ if($image->save($tmpfname)) {
+ OC_JSON::success(array('data' => array('mime'=>$_SERVER['CONTENT_TYPE'], 'name'=>$fn, 'id'=>$id, 'tmp'=>$tmpfname)));
+ exit();
+ } else {
+ bailOut('Couldn\'t save temporary image: '.$tmpfname);
+ }
+ } else {
+ bailOut('Couldn\'t load temporary image: '.$file['tmp_name']);
+ }
+}
+
+
+if (!isset($_POST['id'])) {
+ OC_Log::write('contacts','ajax/uploadphoto.php: No contact ID was submitted.', OC_Log::DEBUG);
+ OC_JSON::error(array('data' => array( 'message' => 'No contact ID was submitted.' )));
+ exit();
+}
+if (!isset($_FILES['imagefile'])) {
+ OC_Log::write('contacts','ajax/uploadphoto.php: No file was uploaded. Unknown error.', OC_Log::DEBUG);
+ OC_JSON::error(array('data' => array( 'message' => 'No file was uploaded. Unknown error' )));
+ exit();
+}
+$error = $_FILES['imagefile']['error'];
+if($error !== UPLOAD_ERR_OK) {
+ $l=new OC_L10N('contacts');
+ $errors = array(
+ 0=>$l->t("There is no error, the file uploaded with success"),
+ 1=>$l->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
+ 2=>$l->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
+ 3=>$l->t("The uploaded file was only partially uploaded"),
+ 4=>$l->t("No file was uploaded"),
+ 6=>$l->t("Missing a temporary folder")
+ );
+ bailOut($errors[$error]);
+}
+$file=$_FILES['imagefile'];
+
+$tmpfname = tempnam("/tmp", "occOrig");
+if(file_exists($file['tmp_name'])) {
+ $image = new OC_Image();
+ if($image->loadFromFile($file['tmp_name'])) {
+ if($image->width() > 400 || $image->height() > 400) {
+ $image->resize(400); // Prettier resizing than with browser and saves bandwidth.
+ }
+ if(!$image->fixOrientation()) { // No fatal error so we don't bail out.
+ debug('Couldn\'t save correct image orientation: '.$tmpfname);
+ }
+ if($image->save($tmpfname)) {
+ OC_JSON::success(array('data' => array('mime'=>$file['type'],'size'=>$file['size'],'name'=>$file['name'], 'id'=>$_POST['id'], 'tmp'=>$tmpfname)));
+ exit();
+ } else {
+ bailOut('Couldn\'t save temporary image: '.$tmpfname);
+ }
+ } else {
+ bailOut('Couldn\'t load temporary image: '.$file['tmp_name']);
+ }
+} else {
+ bailOut('Temporary file: \''.$file['tmp_name'].'\' has gone AWOL?');
+}
+
+?>
diff --git a/contacts.php b/contacts.php
new file mode 100644
index 00000000..fded839f
--- /dev/null
+++ b/contacts.php
@@ -0,0 +1,60 @@
+ 0) {
+ $id = $contacts[0]['id'];
+}
+if(!is_null($id)) {
+ $vcard = OC_Contacts_App::getContactVCard($id);
+ $details = OC_Contacts_VCard::structureContact($vcard);
+}
+$property_types = OC_Contacts_App::getAddPropertyOptions();
+$phone_types = OC_Contacts_App::getTypesOfProperty('TEL');
+
+$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize'));
+$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size'));
+$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
+
+$freeSpace=OC_Filesystem::free_space('/');
+$freeSpace=max($freeSpace,0);
+$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
+
+OC_Util::addScript('','jquery.multiselect');
+//OC_Util::addScript('contacts','interface');
+OC_Util::addScript('contacts','contacts');
+OC_Util::addScript('contacts','jquery.inview');
+OC_Util::addScript('contacts','jquery.Jcrop');
+OC_Util::addScript('contacts','jquery.jec-1.3.3');
+OC_Util::addStyle('','jquery.multiselect');
+//OC_Util::addStyle('contacts','styles');
+OC_Util::addStyle('contacts','jquery.Jcrop');
+OC_Util::addStyle('contacts','contacts');
+
+$tmpl = new OC_Template( "contacts", "index2", "user" );
+$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
+$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize));
+$tmpl->assign('property_types',$property_types);
+$tmpl->assign('phone_types',$phone_types);
+$tmpl->assign('addressbooks', $addressbooks);
+$tmpl->assign('contacts', $contacts);
+$tmpl->assign('details', $details );
+$tmpl->assign('id',$id);
+$tmpl->printPage();
+
+?>
diff --git a/css/Jcrop.gif b/css/Jcrop.gif
new file mode 100644
index 00000000..72ea7ccb
Binary files /dev/null and b/css/Jcrop.gif differ
diff --git a/css/contacts.css b/css/contacts.css
new file mode 100644
index 00000000..a48533f8
--- /dev/null
+++ b/css/contacts.css
@@ -0,0 +1,213 @@
+/*dl > dt {
+ font-weight: bold;
+}*/
+
+#contacts { padding-left:2px; padding-top: 5px; background: #fff; }
+#leftcontent a { height: 23px; display: block; margin: 0 0 0 0; padding: 0 0 0 25px; }
+#chooseaddressbook {margin-right: 170px; float: right;}
+#contacts_deletecard {position:absolute;top:15px;right:25px;}
+#contacts_downloadcard {position:absolute;top:15px;right:50px;}
+#contacts_propertymenu_button { position:absolute;top:15px;right:150px; height: 20px; width: 150px; background:url('../../../core/img/actions/add.svg') no-repeat center; }
+#contacts_propertymenu { position:absolute;top:35px;right:150px; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; }
+#contacts_propertymenu li { display: block; font-weight: bold; border-left: thin solid #1d2d44; border-right: thin solid #1d2d44; height: 20px; width: 100px; }
+#contacts_propertymenu li:first-child { border-top: thin solid #1d2d44; -moz-border-radius-topleft:0.5em; -webkit-border-top-left-radius:0.5em; border-top-left-radius:0.5em; -moz-border-radius-topright:0.5em; -webkit-border-top-right-radius:0.5em; border-top-right-radius:0.5em; }
+#contacts_propertymenu li:last-child { border-bottom: thin solid #1d2d44; -moz-border-radius-bottomleft:0.5em; -webkit-border-bottom-left-radius:0.5em; border-bottom-left-radius:0.5em; -moz-border-radius-bottomright:0.5em; -webkit-border-bottom-right-radius:0.5em; border-bottom-right-radius:0.5em; }
+#contacts_propertymenu li a { padding: 3px; display: block }
+#contacts_propertymenu li:hover { background-color: #1d2d44; }
+#contacts_propertymenu li a:hover { color: #fff }
+#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0;}
+#card { /*max-width: 70em;*/ border: thin solid lightgray; display: block; }
+#firstrun { /*border: thin solid lightgray;*/ width: 80%; margin: 5em auto auto auto; text-align: center; font-weight:bold; font-size:1.5em; color:#777;}
+#firstrun #selections { /*border: thin solid lightgray;*/ font-size:0.8em; width: 100%; margin: 2em auto auto auto; clear: both; }
+
+#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; }
+#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; }
+input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; }
+/*input[type="text"]:valid,input[type="email"]:valid,input[type="tel"]:valid,input[type="date"]:valid { background-color: #b1d28f !important; }*/
+dl.form
+{
+ width: 100%;
+ float: left;
+ clear: right;
+ margin: 0;
+ padding: 0;
+}
+
+.form dt
+{
+ display: table-cell;
+ clear: left;
+ float: left;
+ width: 7em;
+ /*overflow: hidden;*/
+ margin: 0;
+ padding: 0.8em 0.5em 0 0;
+ font-weight: bold;
+ text-align:right;
+ text-overflow:ellipsis;
+ o-text-overflow: ellipsis;
+ vertical-align: text-bottom;
+ /*
+ white-space: pre-wrap;
+ white-space: -moz-pre-wrap !important;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;*/
+}
+
+.form dd
+{
+ display: table-cell;
+ clear: right;
+ float: left;
+ margin: 0;
+ padding: 0px;
+ white-space: nowrap;
+ vertical-align: text-bottom;
+ /*min-width: 20em;*/
+ /*background-color: yellow;*/
+}
+
+.loading { background: url('../../../core/img/loading.gif') no-repeat center !important;}
+
+/*.add { cursor: pointer; width: 25px; height: 25px; margin: 0px; float: right; position:relative; content: "\+"; font-weight: bold; color: #666; font-size: large; bottom: 0px; right: 0px; clear: both; text-align: center; vertical-align: bottom; display: none; }*/
+
+.listactions { height: 1em; width:60px; float: left; clear: right; }
+.add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; }
+.add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; }
+.delete { background:url('../../../core/img/actions/delete.svg') no-repeat center; }
+.edit { background:url('../../../core/img/actions/rename.svg') no-repeat center; }
+.mail { background:url('../../../core/img/actions/mail.svg') no-repeat center; }
+.globe { background:url('../img/globe.svg') no-repeat center; }
+
+#messagebox_msg { font-weight: bold; font-size: 1.2em; }
+
+/* Name editor */
+#edit_name_dialog {
+ /*width: 25em;*/
+ padding:0;
+}
+#edit_name_dialog > input {
+ width: 15em;
+}
+/* Address editor */
+#edit_address_dialog {
+ /*width: 30em;*/
+}
+#edit_address_dialog > input {
+ width: 15em;
+}
+#edit_photo_dialog_img {
+ display: block;
+ width: 150;
+ height: 200;
+ border: thin solid black;
+}
+#fn {
+ float: left;
+}
+.jecEditableOption {
+ margin: 2px;
+ border-radius: 0.5em;
+ border: thin solid #bbb;
+ content: 'Custom';
+}
+/**
+ * Create classes form, floateven and floatodd which flows left and right respectively.
+ */
+.contactsection {
+ float: left;
+ min-width: 30em;
+ max-width: 40em;
+ margin: 0.5em;
+ border: thin solid lightgray;
+ -webkit-border-radius: 0.5em;
+ -moz-border-radius: 0.5em;
+ border-radius: 0.5em;
+ background-color: #f8f8f8;
+}
+
+.contactpart legend {
+ /*background: #fff;
+ font-weight: bold;
+ left: 1em;
+ border: thin solid gray;
+ -webkit-border-radius: 0.5em;
+ -moz-border-radius: 0.5em;
+ border-radius: 0.5em;
+ padding: 3px;*/
+width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em;
+}
+/*#contacts_details_photo {
+ cursor: pointer;
+ z-index:1;
+ margin: auto;
+}
+*/
+#cropbox {
+ margin: auto;
+}
+
+/* Photo editor */
+/*#contacts_details_photo_wrapper {
+ z-index: 1000;
+}*/
+#contacts_details_photo {
+ border-radius: 0.5em;
+ border: thin solid #bbb;
+ padding: 0.5em;
+ margin: 1em 1em 1em 7em;
+ cursor: pointer;
+ /*background: #f8f8f8;*/
+ background: url(../../../core/img/loading.gif) no-repeat center center;
+ clear: right;
+}
+#contacts_details_photo:hover {
+ background: #fff;
+}
+#contacts_details_photo_progress {
+ margin: 0.3em 0.3em 0.3em 7em;
+ clear: left;
+}
+/* Address editor */
+#addressdisplay { padding: 0.5em; }
+dl.addresscard { background-color: #fff; float: left; width: 45%; margin: 0 0.3em 0.3em 0.3em; padding: 0; border: thin solid lightgray; }
+dl.addresscard dd {}
+dl.addresscard dt { padding: 0.3em; border-bottom: thin solid lightgray; font-weight: bold; clear: both;}
+dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; }
+#adr_type {} /* Select */
+#adr_pobox {}
+#adr_extended {}
+#adr_street {}
+#adr_city {}
+#adr_region {}
+#adr_zipcode {}
+#adr_country {}
+
+.delimiter {
+ height: 10px;
+ clear: both;
+}
+.updatebar {
+ height: 30px;
+ clear: both;
+ padding-right: 170px;
+ border: thin solid lightgray;
+}
+.updatebar button {
+ float: left; margin: 1em;
+}
+
+/*input[type="text"] { float: left; max-width: 15em; }
+input[type="radio"] { float: left; -khtml-appearance: none; width: 20px; height: 20px; vertical-align: middle; }*/
+#file_upload_target, #crop_target { display:none; }
+
+#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;}
+input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; }
+.propertycontainer dd { float: left; width: 25em; }
+.propertylist { clear: none; max-width: 28em; }
+.propertylist li { /*background-color: cyan; */ min-width: 25em; /*max-width: 30em;*/ display: block; clear: right; }
+.propertylist li > input[type="text"],input[type="email"],input[type="tel"] { float: left; max-width: 15em; }
+.propertylist li > input[type="checkbox"],input[type="radio"] { float: left; clear: left; width: 20px; height: 20px; vertical-align: middle; }
+.propertylist li > select { float: left; max-width: 8em; }
+.typelist { float: left; max-width: 10em; } /* for multiselect */
+.addresslist { clear: both; }
\ No newline at end of file
diff --git a/css/jquery.Jcrop.css b/css/jquery.Jcrop.css
new file mode 100644
index 00000000..554f013f
--- /dev/null
+++ b/css/jquery.Jcrop.css
@@ -0,0 +1,84 @@
+/* jquery.Jcrop.css
+ The code contained in this file is free software under MIT License
+ Copyright (c)2008-2011 Tapmodo Interactive LLC
+*/
+
+/*
+ The outer-most container in a typical Jcrop instance
+ If you are having difficulty with formatting related to styles
+ on a parent element, place any fixes here or in a like selector
+*/
+.jcrop-holder {
+ direction: ltr;
+ text-align: left;
+}
+
+.jcrop-vline, .jcrop-hline {
+ background: white url('Jcrop.gif') top left repeat;
+ font-size: 0px;
+ position: absolute;
+}
+
+.jcrop-vline {
+ height: 100%;
+ width: 1px !important;
+}
+
+.jcrop-hline {
+ width: 100%;
+ height: 1px !important;
+}
+
+.jcrop-vline.right {
+ right: 0px;
+}
+
+.jcrop-hline.bottom {
+ bottom: 0px;
+}
+
+.jcrop-handle {
+ background-color: #333;
+ border: 1px #eee solid;
+ font-size: 1px;
+}
+
+.jcrop-tracker {
+ height: 100%;
+ -webkit-tap-highlight-color: transparent; /* "turn off" link highlight */
+ -webkit-touch-callout: none; /* disable callout, image save panel */
+ -webkit-user-select: none; /* disable cut copy paste */
+ width: 100%;
+}
+
+/*
+*/
+
+.jcrop-light .jcrop-vline, .jcrop-light .jcrop-hline {
+ background: white;
+ filter: Alpha(opacity=70) !important;
+ opacity: .70 !important;
+}
+
+.jcrop-light .jcrop-handle {
+ background-color: black;
+ border-color: white;
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+}
+
+.jcrop-dark .jcrop-vline, .jcrop-dark .jcrop-hline {
+ background: black;
+ filter: Alpha(opacity=70) !important;
+ opacity: 0.70 !important;
+}
+
+.jcrop-dark .jcrop-handle {
+ background-color: white;
+ border-color: black;
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+}
+
diff --git a/dynphoto.php b/dynphoto.php
new file mode 100644
index 00000000..8025f559
--- /dev/null
+++ b/dynphoto.php
@@ -0,0 +1,35 @@
+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+// Init owncloud
+require_once('../../lib/base.php');
+$tmp_path = $_GET['tmp_path'];
+$maxsize = isset($_GET['maxsize']) ? $_GET['maxsize'] : -1;
+header("Cache-Control: no-cache, no-store, must-revalidate");
+
+OC_Log::write('contacts','dynphoto.php: tmp_path: '.$tmp_path.', exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+
+$image = new OC_Image($tmp_path);
+if($maxsize != -1) {
+ $image->resize($maxsize);
+}
+$image();
diff --git a/img/globe.svg b/img/globe.svg
new file mode 100644
index 00000000..4e43cfba
--- /dev/null
+++ b/img/globe.svg
@@ -0,0 +1,102 @@
+
+
+
diff --git a/img/person_large.png b/img/person_large.png
new file mode 100644
index 00000000..f57511e1
Binary files /dev/null and b/img/person_large.png differ
diff --git a/js/contacts.js b/js/contacts.js
new file mode 100644
index 00000000..c2130e08
--- /dev/null
+++ b/js/contacts.js
@@ -0,0 +1,1289 @@
+function ucwords (str) {
+ return (str + '').replace(/^([a-z])|\s+([a-z])/g, function ($1) {
+ return $1.toUpperCase();
+ });
+}
+
+String.prototype.strip_tags = function(){
+ tags = this;
+ stripped = tags.replace(/[\<\>]/gi, "");
+ return stripped;
+}
+
+
+Contacts={
+ UI:{
+ notImplemented:function() {
+ Contacts.UI.messageBox(t('contacts', 'Not implemented'), t('contacts', 'Sorry, this functionality has not been implemented yet'));
+ },
+ searchOSM:function(obj) {
+ var adr = Contacts.UI.propertyContainerFor(obj).find('.adr').val();
+ console.log('adr 1: ' + adr);
+ if(adr == undefined) {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Couldn\'t get a valid address.'));
+ return;
+ }
+ // FIXME: I suck at regexp. /Tanghus
+ var adrarr = adr.split(';');
+ var adrstr = '';
+ if(adrarr[2].trim() != '') {
+ adrstr = adrstr + adrarr[2].trim() + ',';
+ }
+ if(adrarr[3].trim() != '') {
+ adrstr = adrstr + adrarr[3].trim() + ',';
+ }
+ if(adrarr[4].trim() != '') {
+ adrstr = adrstr + adrarr[4].trim() + ',';
+ }
+ if(adrarr[5].trim() != '') {
+ adrstr = adrstr + adrarr[5].trim() + ',';
+ }
+ if(adrarr[6].trim() != '') {
+ adrstr = adrstr + adrarr[6].trim();
+ }
+ console.log('adrstr: "' + adrstr + '"');
+ adrstr = encodeURIComponent(adrstr);
+ console.log('adrstr 2: ' + adrstr);
+ var uri = 'http://open.mapquestapi.com/nominatim/v1/search.php?q=' + adrstr + '&limit=10&addressdetails=1&zoom=';
+ console.log('uri: ' + uri);
+ var newWindow = window.open(uri,'_blank');
+ newWindow.focus();
+ //Contacts.UI.notImplemented();
+ },
+ mailTo:function(obj) {
+ var adr = Contacts.UI.propertyContainerFor($(obj)).find('input[type="email"]').val().trim();
+ if(adr == '') {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Please enter an email address.'));
+ return;
+ }
+ window.location.href='mailto:' + adr;
+ },
+ propertyContainerFor:function(obj) {
+ return $(obj).parents('.propertycontainer').first();
+ },
+ checksumFor:function(obj) {
+ return $(obj).parents('.propertycontainer').first().data('checksum');
+ },
+ propertyTypeFor:function(obj) {
+ return $(obj).parents('.propertycontainer').first().data('element');
+ },
+ checkListFor:function(obj) {
+ var type = $(obj).parents('.propertycontainer').first().data('element');
+ console.log('checkListFor: ' + type);
+ switch (type) {
+ case 'EMAIL':
+ console.log('emails: '+$('#emaillist>li').length);
+ if($('#emaillist>li').length == 1) {
+ $('#emails').hide();
+ }
+ break;
+ case 'TEL':
+ console.log('phones: '+$('#phonelist>li').length);
+ if($('#phonelist>li').length == 1) {
+ $('#phones').hide();
+ }
+ break;
+ case 'ADR':
+ console.log('addresses: '+$('#addressdisplay>dl').length);
+ if($('#addressdisplay>dl').length == 1) {
+ $('#addresses').hide();
+ }
+ break;
+ case 'NICKNAME':
+ case 'ORG':
+ case 'BDAY':
+ break;
+ }
+ },
+ loading:function(obj, state) {
+ if(state) {
+ $(obj).addClass('loading');
+ } else {
+ $(obj).removeClass('loading');
+ }
+ },
+ showCardDAVUrl:function(username, bookname){
+ $('#carddav_url').val(totalurl + '/' + username + '/' + bookname);
+ $('#carddav_url').show();
+ $('#carddav_url_close').show();
+ },
+ messageBox:function(title, msg) {
+ //alert(msg);
+ if($('#messagebox').dialog('isOpen') == true){
+ // NOTE: Do we ever get here?
+ $('#messagebox').dialog('moveToTop');
+ }else{
+ $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'messagebox.php'), function(){
+ $('#messagebox').dialog(
+ {
+ autoOpen: true,
+ title: title,
+ buttons: [{
+ text: "Ok",
+ click: function() { $(this).dialog("close"); }
+ }],
+ close: function(event, ui) {
+ $(this).dialog('destroy').remove();
+ },
+ open: function(event, ui) {
+ $('#messagebox_msg').html(msg);
+ }
+ });
+ });
+ }
+ },
+ loadListHandlers:function() {
+ //$('.add,.delete').hide();
+ $('.globe,.mail,.delete,.edit').tipsy();
+ $('.addresscard,.propertylist li,.propertycontainer').hover(
+ function () {
+ $(this).find('.globe,.mail,.delete,.edit').fadeIn(100);
+ },
+ function () {
+ $(this).find('.globe,.mail,.delete,.edit').fadeOut(100);
+ }
+ );
+ },
+ loadHandlers:function() {
+ console.log('loadHandlers');
+ /*
+ $('.formfloat').hover(
+ function () {
+ $(this).find('.add').fadeIn(500);
+ },
+ function () {
+ $(this).find('.add').fadeOut(500);
+ }
+ );*/
+ $('#contacts_deletecard').tipsy({gravity: 'ne'});
+ $('#contacts_downloadcard').tipsy({gravity: 'ne'});
+ $('.button').tipsy();
+ $('#fn').jec();
+ $('.jecEditableOption').attr('title', t('contacts','Custom'));
+ $('#fn').tipsy();
+ $('#contacts_details_photo_wrapper').tipsy();
+ $('#bday').datepicker({
+ dateFormat : 'dd-mm-yy'
+ });
+ // Style phone types
+ $('#phonelist').find('select[class*="contacts_property"]').multiselect({
+ noneSelectedText: t('contacts', 'Select type'),
+ header: false,
+ selectedList: 4,
+ classes: 'typelist'
+ });
+ $('#add_email').click(function(){
+ Contacts.UI.Card.addMail();
+ });
+ $('#add_phone').click(function(){
+ Contacts.UI.Card.addPhone();
+ });
+// $('#add_address').click(function(){
+// Contacts.UI.Card.editAddress();
+// return false;
+// });
+ $('#n').click(function(){
+ Contacts.UI.Card.editName();
+ //return false;
+ });
+ $('#edit_name').click(function(){
+ Contacts.UI.Card.editName();
+ return false;
+ });
+
+ /* Initialize the photo edit dialog */
+ $('#edit_photo_dialog').dialog({ autoOpen: false, modal: true, height: 'auto', width: 'auto' });
+ $('#edit_photo_dialog' ).dialog( 'option', 'buttons', [
+ {
+ text: "Ok",
+ click: function() {
+ Contacts.UI.Card.savePhoto(this);
+ $(this).dialog('close');
+ }
+ },
+ {
+ text: "Cancel",
+ click: function() { $(this).dialog('close'); }
+ }
+ ] );
+ Contacts.UI.loadListHandlers();
+ },
+ Card:{
+ id:'',
+ fn:'',
+ fullname:'',
+ shortname:'',
+ famname:'',
+ givname:'',
+ addname:'',
+ honpre:'',
+ honsuf:'',
+ data:undefined,
+ export:function() {
+ document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id;
+ //$.get(OC.linkTo('contacts', 'export.php'),{'contactid':this.id},function(jsondata){
+ //});
+ },
+ delete:function() {
+ $('#contacts_deletecard').tipsy('hide');
+ $.getJSON('ajax/deletecard.php',{'id':this.id},function(jsondata){
+ if(jsondata.status == 'success'){
+ $('#leftcontent [data-id="'+jsondata.data.id+'"]').remove();
+ $('#rightcontent').data('id','');
+ //$('#rightcontent').empty();
+ this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = '';
+ this.data = undefined;
+ // Load empty page.
+ var firstid = $('#contacts li:first-child').data('id');
+ console.log('trying to load: ' + firstid);
+ $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':firstid},function(jsondata){
+ if(jsondata.status == 'success'){
+ Contacts.UI.Card.loadContact(jsondata.data);
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ }
+ });
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ //alert(jsondata.data.message);
+ }
+ });
+ return false;
+ },
+ loadContact:function(jsondata){
+ this.data = jsondata;
+ this.id = this.data.id;
+ console.log('loaded: ' + this.data.FN[0]['value']);
+ this.populateNameFields();
+ this.loadPhoto();
+ this.loadMails();
+ this.loadPhones();
+ this.loadAddresses();
+ this.loadSingleProperties();
+ },
+ loadSingleProperties:function() {
+ var props = ['BDAY', 'NICKNAME', 'ORG'];
+ // Clear all elements
+ $('#ident .propertycontainer[class*="propertycontainer"]').each(function(){
+ if(props.indexOf($(this).data('element')) > -1) {
+// $('#contacts_propertymenu a[data-type="'+$(this).data('element')+'"]').parent().show();
+ //console.log($(this).html());
+ $(this).data('checksum', '');
+ $(this).find('input').val('');
+ $(this).hide();
+ $(this).prev().hide();
+ }
+ });
+ for(var prop in props) {
+ //console.log('loadSingleProperties: ' + props[prop] + ': ' + this.data[props[prop]]);
+ if(this.data[props[prop]] != undefined) {
+ $('#contacts_propertymenu a[data-type="'+props[prop]+'"]').parent().hide();
+ var property = this.data[props[prop]][0];
+ var value = property['value'], checksum = property['checksum'];
+ //console.log('value: ' + property['value']);
+ //console.log('checksum: ' + property['checksum']);
+ switch(props[prop]) {
+ case 'BDAY':
+ var val = $.datepicker.parseDate('yy-mm-dd', value.substring(0, 10));
+ //console.log('Date: ' + val);
+ value = $.datepicker.formatDate('dd-mm-yy', val);
+ //console.log('Date: ' + value);
+ $('#contact_identity').find('#bday').val(value);
+ $('#contact_identity').find('#bday_value').data('checksum', checksum);
+ $('#contact_identity').find('#bday_label').show();
+ $('#contact_identity').find('#bday_value').show();
+ break;
+ case 'NICKNAME':
+ //console.log('NICKNAME: ' + value);
+ $('#contact_identity').find('#nickname').val(value);
+ $('#contact_identity').find('#nickname_value').data('checksum', checksum);
+ $('#contact_identity').find('#nickname_label').show();
+ $('#contact_identity').find('#nickname_value').show();
+ break;
+ case 'ORG':
+ //console.log('ORG: ' + value);
+ $('#contact_identity').find('#org').val(value);
+ $('#contact_identity').find('#org_value').data('checksum', checksum);
+ $('#contact_identity').find('#org_label').show();
+ $('#contact_identity').find('#org_value').show();
+ break;
+ }
+ }
+ }
+ },
+ populateNameFields:function() {
+ this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = ''
+ var full = '';
+ var narray = undefined;
+ //console.log('splitting: ' + this.data.N[0]['value']);
+ this.fn = this.data.FN[0]['value'];
+ //console.log('FN: ' + this.fn)
+ if(this.data.N == undefined) {
+ narray = [this.fn,'','','','']; // Checking for non-existing 'N' property :-P
+ full = this.fn;
+ } else {
+ narray = this.data.N[0]['value'];
+ }
+ this.famname = narray[0];
+ //console.log('famname: ' + this.famname)
+ this.givname = narray[1];
+ this.addname = narray[2];
+ this.honpre = narray[3];
+ this.honsuf = narray[4];
+ if(this.honpre.length > 0) {
+ this.fullname += this.honpre + ' ';
+ }
+ if(this.givname.length > 0) {
+ this.fullname += ' ' + this.givname;
+ }
+ if(this.addname.length > 0) {
+ this.fullname += ' ' + this.addname;
+ }
+ if(this.famname.length > 0) {
+ this.fullname += ' ' + this.famname;
+ }
+ if(this.honsuf.length > 0) {
+ this.fullname += ', ' + this.honsuf;
+ }
+ //console.log('fullname: ' + this.fullname)
+ $('#n').html(this.fullname);
+ $('.jecEditableOption').attr('title', 'Custom');
+ $('.jecEditableOption').text(this.fn);
+ //$('.jecEditableOption').attr('value', 0);
+ $('#fn').val(0);
+ $('#full').text(this.fullname);
+ $('#short').text(this.givname + ' ' + this.famname);
+ $('#reverse').text(this.famname + ' ' + this.givname);
+ $('#reverse_comma').text(this.famname + ', ' + this.givname);
+ $('#contact_identity').find('*[data-element="N"]').data('checksum', this.data.N[0]['checksum']);
+ $('#contact_identity').find('*[data-element="FN"]').data('checksum', this.data.FN[0]['checksum']);
+ },
+ editNew:function(){ // add a new contact
+ //Contacts.UI.notImplemented();
+ //return false;
+
+ $.getJSON('ajax/newcontact.php',{},function(jsondata){
+ if(jsondata.status == 'success'){
+ id = '';
+ $('#rightcontent').data('id','');
+ $('#rightcontent').html(jsondata.data.page);
+ console.log('Trying to open name edit dialog');
+ Contacts.UI.Card.editName();
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ alert(jsondata.data.message);
+ }
+ });
+ },
+ add:function(n, fn, aid){ // add a new contact
+ //Contacts.UI.notImplemented();
+ //return false;
+ console.log('Add contact: ' + n + ', ' + fn + ' ' + aid);
+ $.post(OC.filePath('contacts', 'ajax', 'addcontact.php'), { n: n, fn: fn, aid: aid },
+ function(jsondata) {
+ /*
+ * Arguments:
+ * jsondata.status
+ * jsondata.data.id
+ */
+ if (jsondata.status == 'success'){
+ $('#rightcontent').data('id',jsondata.data.id);
+ id = jsondata.data.id;
+ $('#contact_identity').show();
+ $('#actionbar').show();
+ // TODO: Add to contacts list.
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ //alert(jsondata.data.message);
+ }
+ });
+ },
+ saveProperty:function(obj){
+ // I couldn't get the selector to filter on 'contacts_property' so I filter by hand here :-/
+ if(!$(obj).hasClass('contacts_property')) {
+ //console.log('Filtering out object.' + obj);
+ return false;
+ }
+ console.log('saveProperty. ' + $(obj).val());
+ if($(obj).hasClass('nonempty') && $(obj).val().trim() == '') {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'This property has to be non-empty.'));
+ return false;
+ }
+ //Contacts.UI.loading(obj, false);
+ //return false;
+ container = $(obj).parents('.propertycontainer').first(); // get the parent holding the metadata.
+ Contacts.UI.loading(container, true);
+ //console.log('saveProperty. Container: ' + container.data('checksum'));
+ var checksum = container.data('checksum');
+ var name = container.data('element');
+ var q = container.find('input,select').serialize();
+ if(q == '' || q == undefined) {
+ console.log('Couldn\'t serialize elements.');
+ Contacts.UI.loading(container, false);
+ return false;
+ }
+ q = q + '&id=' + this.id + '&name=' + name;
+ if(checksum != undefined && checksum != '') { // save
+ q = q + '&checksum=' + checksum;
+ console.log('Saving: ' + q);
+ $.post('ajax/saveproperty.php',q,function(jsondata){
+ if(jsondata.status == 'success'){
+ container.data('checksum', jsondata.data.checksum);
+ Contacts.UI.loading(container, false);
+ return true;
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ Contacts.UI.loading(container, false);
+ return false;
+ }
+ },'json');
+ } else { // add
+ console.log('Adding: ' + q);
+ $.post('ajax/addproperty.php',q,function(jsondata){
+ if(jsondata.status == 'success'){
+ container.data('checksum', jsondata.data.checksum);
+ Contacts.UI.loading(container, false);
+ return true;
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ Contacts.UI.loading(container, false);
+ return false;
+ }
+ },'json');
+ }
+ },
+ addProperty:function(obj){
+ var type = $(obj).data('type');
+ console.log('addProperty:' + type);
+ switch (type) {
+ case 'PHOTO':
+ $('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide();
+ $('#file_upload_form').show();
+ break;
+ case 'EMAIL':
+ //console.log('emails: '+$('#emaillist>li').length);
+ if($('#emaillist>li').length == 1) {
+ $('#emails').show();
+ }
+ Contacts.UI.Card.addMail();
+ break;
+ case 'TEL':
+ //console.log('phones: '+$('#phonelist>li').length);
+ if($('#phonelist>li').length == 1) {
+ $('#phones').show();
+ }
+ Contacts.UI.Card.addPhone();
+ break;
+ case 'ADR':
+ //console.log('addresses: '+$('#addressdisplay>dl').length);
+ if($('#addressdisplay>dl').length == 1) {
+ $('#addresses').show();
+ }
+ Contacts.UI.Card.editAddress('new', true);
+ break;
+ case 'NICKNAME':
+ case 'ORG':
+ case 'BDAY':
+ $('dl dt[data-element="'+type+'"],dd[data-element="'+type+'"]').show();
+ $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide();
+ break;
+ }
+ },
+ deleteProperty:function(obj, type){
+ //console.log('deleteProperty, id: ' + this.id);
+ Contacts.UI.loading(obj, true);
+ var checksum = Contacts.UI.checksumFor(obj);
+ //var checksum = $(obj).parent().data('checksum');
+ if(checksum != undefined) {
+ //alert('deleteProperty: ' + $(obj).val() + ' ' + checksum);
+ $.getJSON('ajax/deleteproperty.php',{'id': this.id, 'checksum': checksum },function(jsondata){
+ if(jsondata.status == 'success'){
+ if(type == 'list') {
+ Contacts.UI.propertyContainerFor(obj).remove();
+ Contacts.UI.checkListFor(obj);
+ } else if(type == 'single') {
+ var proptype = Contacts.UI.propertyTypeFor(obj);
+ console.log('deleteProperty, hiding: ' + proptype);
+ $('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide();
+ $('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show();
+ Contacts.UI.loading(obj, false);
+ } else {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org'));
+ Contacts.UI.loading(obj, false);
+ }
+ }
+ else{
+ Contacts.UI.loading(obj, false);
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ }
+ });
+ } else { // Property hasn't been saved so there's nothing to delete.
+ if(type == 'list') {
+ Contacts.UI.propertyContainerFor(obj).remove();
+ Contacts.UI.checkListFor(obj);
+ } else if(type == 'single') {
+ var proptype = Contacts.UI.propertyTypeFor(obj);
+ console.log('deleteProperty, hiding: ' + proptype);
+ $('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide();
+ $('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show();
+ Contacts.UI.loading(obj, false);
+ } else {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org'));
+ }
+ }
+ },
+ editName:function(){
+ console.log('editName, id: ' + this.id);
+ //console.log('editName');
+ /* Initialize the name edit dialog */
+ if($('#edit_name_dialog').dialog('isOpen') == true){
+ $('#edit_name_dialog').dialog('moveToTop');
+ }else{ // TODO: If id=='' call addcontact.php (or whatever name) instead and reload view with id.
+ $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editname.php')+'?id='+this.id, function(){
+ $('#edit_name_dialog' ).dialog({
+ modal: (this.id == '' && true || false),
+ closeOnEscape: (this.id == '' && false || true),
+ title: (this.id == '' && t('contacts', 'Add contact') || t('contacts', 'Edit name')),
+ height: 'auto', width: 'auto',
+ buttons: {
+ 'Ok':function() {
+ Contacts.UI.Card.saveName(this);
+ $(this).dialog('destroy').remove();
+ },
+ 'Cancel':function() { $(this).dialog('destroy').remove(); }
+ },
+ close : function(event, ui) {
+ //alert('close');
+ $(this).dialog('destroy').remove();
+ //return event;
+ }/*,
+ open : function(event, ui) {
+ // load 'N' property - maybe :-P
+ }*/
+ });
+ });
+ }
+ },
+ saveName:function(dlg){
+ console.log('saveName, id: ' + this.id);
+ // TODO: Check if new, get address book id and call Contacts.UI.Card.add()
+ var n = new Array($(dlg).find('#fam').val(),$(dlg).find('#giv').val(),$(dlg).find('#add').val(),$(dlg).find('#pre').val(),$(dlg).find('#suf').val());
+ this.famname = n[0];
+ this.givname = n[1];
+ this.addname = n[2];
+ this.honpre = n[3];
+ this.honsuf = n[4];
+ //alert('saveName: ' + n);
+ $('#n').val(n.join(';'));
+ /*$('#card > input').each(function(){
+ alert($(this).attr('id') + ' ' + $(this).val());
+ });*/
+ if(n[3].length > 0) {
+ this.fullname = n[3] + ' ';
+ }
+ this.fullname += n[1] + ' ' + n[2] + ' ' + n[0];
+ if(n[4].length > 0) {
+ this.fullname += ', ' + n[4];
+ }
+ $('#short').text(n[1] + ' ' + n[0]);
+ $('#full').text(this.fullname);
+ $('#reverse').text(n[0] + ' ' + n[1]);
+ $('#reverse_comma').text(n[0] + ', ' + n[1]);
+ //$('#n').html(full);
+ $('#fn').val(0);
+ if(this.id == '') {
+ var aid = $(dlg).find('#aid').val();
+ Contacts.UI.Card.add(n, $('#short').text(), aid);
+ } else {
+ Contacts.UI.Card.saveProperty($('#n'));
+ }
+ },
+ loadAddresses:function(){
+ $('#addresses').hide();
+ $('#addressdisplay dl[class*="propertycontainer"]').remove();
+ for(var adr in this.data.ADR) {
+ $('#addressdisplay dl').first().clone().insertAfter($('#addressdisplay dl').last()).show();
+ $('#addressdisplay dl').last().removeClass('template').addClass('propertycontainer');
+ $('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
+ var adrarray = this.data.ADR[adr]['value'];
+ var adrtxt = '';
+ if(adrarray[0].length > 0) {
+ adrtxt = adrtxt + '
' + adrarray[0].strip_tags() + '';
+ }
+ if(adrarray[1].length > 0) {
+ adrtxt = adrtxt + '' + adrarray[1].strip_tags() + '';
+ }
+ if(adrarray[2].length > 0) {
+ adrtxt = adrtxt + '' + adrarray[2].strip_tags() + '';
+ }
+ if(adrarray[3].length > 0 || adrarray[5].length > 0) {
+ adrtxt = adrtxt + '' + adrarray[5].strip_tags() + ' ' + adrarray[3].strip_tags() + '';
+ }
+ if(adrarray[4].length > 0) {
+ adrtxt = adrtxt + '' + adrarray[4].strip_tags() + '';
+ }
+ if(adrarray[6].length > 0) {
+ adrtxt = adrtxt + '' + adrarray[6].strip_tags() + '';
+ }
+ $('#addressdisplay dl').last().find('.addresslist').html(adrtxt);
+ //console.log('ADR: ' + adr);
+ console.log('checksum: ' + this.data.ADR[adr]['checksum']);
+ //console.log('type: ' + jQuery.type(this.data.ADR[adr]['value']));
+ var types = new Array();
+ var ttypes = new Array();
+ for(var param in this.data.ADR[adr]['parameters']) {
+ //console.log('param: ' + param + ': ' + this.data.ADR[adr]['parameters'][param]);
+ if(param.toUpperCase() == 'TYPE') {
+ //console.log('param type: ' + jQuery.type(this.data.ADR[adr]['parameters'][param]));
+ types.push(t('contacts', ucwords(this.data.ADR[adr]['parameters'][param].toLowerCase())));
+ ttypes.push(this.data.ADR[adr]['parameters'][param]);
+ //for(ptype in this.data.ADR[adr]['parameters'][param]) {
+ // var pt = this.data.ADR[adr]['parameters'][param][ptype];
+ // // TODO: Create an array with types, translate, ucwords and join.
+ //}
+ }
+ }
+ //console.log('# types: ' + types.length);
+ //console.log('Types:' + types.join('/'));
+ $('#addressdisplay dl').last().find('.adr_type_label').text(types.join('/'));
+ $('#addressdisplay dl').last().find('.adr_type').val(ttypes.join(','));
+ $('#addressdisplay dl').last().find('.adr').val(adrarray.join(';'));
+ $('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
+ }
+ if($('#addressdisplay dl').length > 1) {
+ $('#addresses').show();
+ }
+ Contacts.UI.loadListHandlers();
+ return false;
+ },
+ editAddress:function(obj, isnew){
+ console.log('editAddress');
+ var container = undefined;
+ var q = q = '?id=' + this.id;
+ if(obj === 'new') {
+ isnew = true;
+ $('#addressdisplay dl').first().clone().insertAfter($('#addressdisplay dl').last()).show();
+ container = $('#addressdisplay dl').last();
+ container.removeClass('template').addClass('propertycontainer');
+ Contacts.UI.loadListHandlers();
+ } else {
+ q = q + '&checksum='+Contacts.UI.checksumFor(obj);
+ }
+ //console.log('editAddress: checksum ' + checksum);
+ /* Initialize the address edit dialog */
+ if($('#edit_address_dialog').dialog('isOpen') == true){
+ $('#edit_address_dialog').dialog('moveToTop');
+ }else{
+ $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editaddress.php')+q, function(){
+ $('#edit_address_dialog' ).dialog({
+ /*modal: true,*/
+ height: 'auto', width: 'auto',
+ buttons: {
+ 'Ok':function() {
+ console.log('OK:isnew ' + isnew);
+ console.log('OK:obj ' + obj);
+ if(isnew) {
+ Contacts.UI.Card.saveAddress(this, $('#addressdisplay dl:last-child').find('input').first(), isnew);
+ } else {
+ Contacts.UI.Card.saveAddress(this, obj, isnew);
+ }
+ $(this).dialog('destroy').remove();
+ },
+ 'Cancel':function() {
+ $(this).dialog('destroy').remove();
+ if(isnew) {
+ container.remove();
+ }
+ }
+ },
+ close : function(event, ui) {
+ //alert('close');
+ $(this).dialog('destroy').remove();
+ if(isnew) {
+ container.remove();
+ }
+ }/*,
+ open : function(event, ui) {
+ // load 'ADR' property - maybe :-P
+ }*/
+ });
+ });
+ }
+ },
+ saveAddress:function(dlg, obj, isnew){
+ if(isnew) {
+ container = $('#addressdisplay dl').last();
+ obj = $('#addressdisplay dl:last-child').find('input').first();
+ } else {
+ checksum = Contacts.UI.checksumFor(obj);
+ container = Contacts.UI.propertyContainerFor(obj);
+ }
+ var adr = new Array($(dlg).find('#adr_pobox').val(),$(dlg).find('#adr_extended').val(),$(dlg).find('#adr_street').val(),$(dlg).find('#adr_city').val(),$(dlg).find('#adr_region').val(),$(dlg).find('#adr_zipcode').val(),$(dlg).find('#adr_country').val());
+ $(container).find('.adr').val(adr.join(';'));
+ $(container).find('.adr_type').val($(dlg).find('#adr_type').val());
+ $(container).find('.adr_type_label').html(t('contacts',ucwords($(dlg).find('#adr_type').val().toLowerCase())));
+ Contacts.UI.Card.saveProperty($(container).find('input').first());
+ var adrtxt = '';
+ if(adr[0].length > 0) {
+ adrtxt = adrtxt + '' + adr[0] + '';
+ }
+ if(adr[1].length > 0) {
+ adrtxt = adrtxt + '' + adr[1] + '';
+ }
+ if(adr[2].length > 0) {
+ adrtxt = adrtxt + '' + adr[2] + '';
+ }
+ if(adr[3].length > 0 || adr[5].length > 0) {
+ adrtxt = adrtxt + '' + adr[5] + ' ' + adr[3] + '';
+ }
+ if(adr[4].length > 0) {
+ adrtxt = adrtxt + '' + adr[4] + '';
+ }
+ if(adr[6].length > 0) {
+ adrtxt = adrtxt + '' + adr[6] + '';
+ }
+ $(container).find('.addresslist').html(adrtxt);
+ },
+ uploadPhoto:function(filelist) {
+ if(!filelist) {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts','No files selected for upload.'));
+ return;
+ }
+ //var file = filelist.item(0);
+ var file = filelist[0];
+ var target = $('#file_upload_target');
+ var form = $('#file_upload_form');
+ var totalSize=0;
+ if(file.size > $('#max_upload').val()){
+ Contacts.UI.messageBox(t('Upload too large'), t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'));
+ return;
+ } else {
+ target.load(function(){
+ var response=jQuery.parseJSON(target.contents().text());
+ if(response != undefined && response.status == 'success'){
+ Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp);
+ //alert('File: ' + file.tmp + ' ' + file.name + ' ' + file.mime);
+ }else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), response.data.message);
+ }
+ });
+ form.submit();
+ }
+ },
+ loadPhoto:function(){
+ console.log('loadPhoto: ' + this.data.PHOTO);
+ if(this.data.PHOTO) {
+ $('#file_upload_form').show();
+ $('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide();
+ } else {
+ $('#file_upload_form').hide();
+ $('#contacts_propertymenu a[data-type="PHOTO"]').parent().show();
+ }
+ $.getJSON('ajax/loadphoto.php',{'id':this.id},function(jsondata){
+ if(jsondata.status == 'success'){
+ //alert(jsondata.data.page);
+ $('#contacts_details_photo_wrapper').html(jsondata.data.page);
+ }
+ else{
+ Contacts.UI.messageBox(jsondata.data.message);
+ }
+ });
+ },
+ editPhoto:function(id, tmp_path){
+ //alert('editPhoto: ' + tmp_path);
+ $.getJSON('ajax/cropphoto.php',{'tmp_path':tmp_path,'id':this.id},function(jsondata){
+ if(jsondata.status == 'success'){
+ //alert(jsondata.data.page);
+ $('#edit_photo_dialog_img').html(jsondata.data.page);
+ }
+ else{
+ Contacts.UI.messageBox(jsondata.data.message);
+ }
+ });
+ if($('#edit_photo_dialog').dialog('isOpen') == true){
+ $('#edit_photo_dialog').dialog('moveToTop');
+ } else {
+ $('#edit_photo_dialog').dialog('open');
+ }
+ },
+ savePhoto:function(){
+ var target = $('#crop_target');
+ var form = $('#cropform');
+ form.submit();
+ target.load(function(){
+ var response=jQuery.parseJSON(target.contents().text());
+ if(response != undefined && response.status == 'success'){
+ // load cropped photo.
+ $('#contacts_details_photo_wrapper').html(response.data.page);
+ }else{
+ Contacts.UI.messageBox(t('contacts','Error'), response.data.message);
+ }
+ });
+ $('#contacts [data-id="'+this.id+'"]').find('a').css('background','url(thumbnail.php?id='+this.id+'&refresh=1'+Math.random()+') no-repeat');
+ },
+ addMail:function() {
+ //alert('addMail');
+ $('#emaillist li[class*="template"]:first-child').clone().appendTo($('#emaillist')).show();
+ $('#emaillist li[class*="template"]:last-child').removeClass('template').addClass('propertycontainer');
+ $('#emaillist li:last-child').find('input[type="email"]').focus();
+ Contacts.UI.loadListHandlers();
+ return false;
+ },
+ loadMails:function() {
+ $('#emails').hide();
+ $('#emaillist li[class*="propertycontainer"]').remove();
+ for(var mail in this.data.EMAIL) {
+ this.addMail();
+ //$('#emaillist li:first-child').clone().appendTo($('#emaillist')).show();
+ $('#emaillist li:last-child').data('checksum', this.data.EMAIL[mail]['checksum'])
+ $('#emaillist li:last-child').find('input[type="email"]').val(this.data.EMAIL[mail]['value']);
+ //console.log('EMAIL: ' + mail);
+ //console.log('value: ' + this.data.EMAIL[mail]['value']);
+ //console.log('checksum: ' + this.data.EMAIL[mail]['checksum']);
+ for(var param in this.data.EMAIL[mail]['parameters']) {
+ //console.log('param: ' + param + ': ' + this.data.EMAIL[mail]['parameters'][param]);
+ if(param.toUpperCase() == 'PREF') {
+ $('#emaillist li:last-child').find('input[type="checkbox"]').attr('checked', 'checked')
+ }
+ }
+ //console.log('parameters: ' + jQuery.type(this.data.EMAIL[mail]['parameters']));
+ }
+ if($('#emaillist li').length > 1) {
+ $('#emails').show();
+ }
+
+ $('#emaillist li:last-child').find('input[type="text"]').focus();
+ Contacts.UI.loadListHandlers();
+ return false;
+ },
+ addPhone:function() {
+ $('#phonelist li[class*="template"]:first-child').clone().appendTo($('#phonelist')); //.show();
+ $('#phonelist li[class*="template"]:last-child').find('select').addClass('contacts_property');
+ $('#phonelist li[class*="template"]:last-child').removeClass('template').addClass('propertycontainer');
+ $('#phonelist li:last-child').find('input[type="text"]').focus();
+ Contacts.UI.loadListHandlers();
+ $('#phonelist li:last-child').find('select').multiselect({
+ noneSelectedText: t('contacts', 'Select type'),
+ header: false,
+ selectedList: 4,
+ classes: 'typelist'
+ });
+ $('#phonelist li:last-child').show();
+ return false;
+ },
+ loadPhones:function() {
+ $('#phones').hide();
+ $('#phonelist li[class*="propertycontainer"]').remove();
+ for(var phone in this.data.TEL) {
+ this.addPhone();
+ $('#phonelist li:last-child').find('select').multiselect('destroy');
+ $('#phonelist li:last-child').data('checksum', this.data.TEL[phone]['checksum'])
+ $('#phonelist li:last-child').find('input[type="text"]').val(this.data.TEL[phone]['value']);
+ //console.log('TEL: ' + phone);
+ //console.log('value: ' + this.data.TEL[phone]['value']);
+ //console.log('checksum: ' + this.data.TEL[phone]['checksum']);
+ for(var param in this.data.TEL[phone]['parameters']) {
+ //console.log('param: ' + param + ': ' + this.data.TEL[phone]['parameters'][param]);
+ if(param.toUpperCase() == 'PREF') {
+ $('#phonelist li:last-child').find('input[type="checkbox"]').attr('checked', 'checked');
+ }
+ else if(param.toUpperCase() == 'TYPE') {
+ //console.log('param type: ' + jQuery.type(this.data.TEL[phone]['parameters'][param]));
+ for(ptype in this.data.TEL[phone]['parameters'][param]) {
+ var pt = this.data.TEL[phone]['parameters'][param][ptype];
+ $('#phonelist li:last-child').find('select option').each(function(){
+ //console.log('Test: ' + $(this).val().toUpperCase() + '==' + pt);
+ if ($(this).val().toUpperCase() == pt) {
+ //console.log('Selected: ' + pt);
+ $(this).attr('selected', 'selected');
+ }
+ });
+ }
+ }
+ }
+ $('#phonelist li:last-child').find('select').multiselect({
+ noneSelectedText: t('contacts', 'Select type'),
+ header: false,
+ selectedList: 4,
+ classes: 'typelist'
+ });
+ //console.log('parameters: ' + jQuery.type(this.data.EMAIL[mail]['parameters']));
+ }
+ if($('#phonelist li').length > 1) {
+ $('#phones').show();
+ }
+ return false;
+ },
+ },
+ Addressbooks:{
+ overview:function(){
+ if($('#chooseaddressbook_dialog').dialog('isOpen') == true){
+ $('#chooseaddressbook_dialog').dialog('moveToTop');
+ }else{
+ $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'chooseaddressbook.php'), function(){
+ $('#chooseaddressbook_dialog').dialog({
+ width : 600,
+ close : function(event, ui) {
+ $(this).dialog('destroy').remove();
+ }
+ });
+ });
+ }
+ },
+ activation:function(checkbox, bookid)
+ {
+ $.post(OC.filePath('contacts', 'ajax', 'activation.php'), { bookid: bookid, active: checkbox.checked?1:0 },
+ function(data) {
+ /*
+ * Arguments:
+ * data.status
+ * data.bookid
+ * data.active
+ */
+ if (data.status == 'success'){
+ checkbox.checked = data.active == 1;
+ Contacts.UI.Contacts.update();
+ }
+ });
+ },
+ newAddressbook:function(object){
+ var tr = $(document.createElement('tr'))
+ .load(OC.filePath('contacts', 'ajax', 'addbook.php'));
+ $(object).closest('tr').after(tr).hide();
+ /* TODO: Shouldn't there be some kinda error checking here? */
+ },
+ editAddressbook:function(object, bookid){
+ var tr = $(document.createElement('tr'))
+ .load(OC.filePath('contacts', 'ajax', 'editaddressbook.php') + "?bookid="+bookid);
+ $(object).closest('tr').after(tr).hide();
+ },
+ deleteAddressbook:function(bookid){
+ var check = confirm("Do you really want to delete this address book?");
+ if(check == false){
+ return false;
+ }else{
+ $.post(OC.filePath('contacts', 'ajax', 'deletebook.php'), { id: bookid},
+ function(data) {
+ if (data.status == 'success'){
+ $('#chooseaddressbook_dialog').dialog('destroy').remove();
+ Contacts.UI.Contacts.update();
+ Contacts.UI.Addressbooks.overview();
+ } else {
+ Contacts.UI.messageBox(t('contacts', 'Error'), data.message);
+ //alert('Error: ' + data.message);
+ }
+ });
+ }
+ },
+ import:function(){
+ Contacts.UI.notImplemented();
+ },
+ submit:function(button, bookid){
+ var displayname = $("#displayname_"+bookid).val();
+ var active = $("#edit_active_"+bookid+":checked").length;
+ var description = $("#description_"+bookid).val();
+
+ var url;
+ if (bookid == 'new'){
+ url = OC.filePath('contacts', 'ajax', 'createaddressbook.php');
+ }else{
+ url = OC.filePath('contacts', 'ajax', 'updateaddressbook.php');
+ }
+ $.post(url, { id: bookid, name: displayname, active: active, description: description },
+ function(data){
+ if(data.status == 'success'){
+ $(button).closest('tr').prev().html(data.page).show().next().remove();
+ }
+ });
+ Contacts.UI.Contacts.update();
+ },
+ cancel:function(button, bookid){
+ $(button).closest('tr').prev().show().next().remove();
+ }
+ },
+ Contacts:{
+ /**
+ * Reload the contacts list.
+ */
+ update:function(){
+ $.getJSON('ajax/contacts.php',{},function(jsondata){
+ if(jsondata.status == 'success'){
+ $('#contacts').html(jsondata.data.page);
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'),jsondata.data.message);
+ //alert(jsondata.data.message);
+ }
+ });
+ setTimeout(Contacts.UI.Contacts.lazyupdate, 500);
+ },
+ /**
+ * Add thumbnails to the contact list as they become visible in the viewport.
+ */
+ lazyupdate:function(){
+ $('#contacts li').live('inview', function(){
+ if (!$(this).find('a').attr('style')) {
+ $(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat');
+ }
+ });
+ }
+ }
+ }
+}
+$(document).ready(function(){
+
+ Contacts.UI.loadHandlers();
+
+ /**
+ * Show the Addressbook chooser
+ */
+ $('#chooseaddressbook').click(function(){
+ Contacts.UI.Addressbooks.overview();
+ return false;
+ });
+
+ /**
+ * Open blank form to add new contact.
+ * FIXME: Load the same page but only show name data and popup the name edit dialog.
+ * On save load the page again with an id and show all fields.
+ * NOTE: Or: Load the full page and popup name dialog modal. On success set the newly aquired ID, on
+ * Cancel or failure give appropriate message and show ... something else :-P
+ */
+ $('#contacts_newcontact').click(function(){
+ Contacts.UI.Card.editNew();
+// $.getJSON('ajax/addcontact.php',{},function(jsondata){
+// if(jsondata.status == 'success'){
+// $('#rightcontent').data('id','');
+// $('#rightcontent').html(jsondata.data.page)
+// .find('select').chosen();
+// }
+// else{
+// Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+// //alert(jsondata.data.message);
+// }
+// });
+// return false;
+ });
+
+ /**
+ * Load the details view for a contact.
+ */
+ $('#leftcontent li').live('click',function(){
+ var id = $(this).data('id');
+ var oldid = $('#rightcontent').data('id');
+ if(oldid != 0){
+ $('#leftcontent li[data-id="'+oldid+'"]').removeClass('active');
+ }
+ $.getJSON('ajax/contactdetails.php',{'id':id},function(jsondata){
+ if(jsondata.status == 'success'){
+ Contacts.UI.Card.loadContact(jsondata.data);
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ //alert(jsondata.data.message);
+ }
+ });
+ // NOTE: Deprecated.
+// $.getJSON('ajax/getdetails.php',{'id':id, 'new':1},function(jsondata){
+// if(jsondata.status == 'success'){
+// $('#rightcontent').data('id',jsondata.data.id);
+// $('#rightcontent').html(jsondata.data.page);
+// $('#leftcontent li[data-id="'+jsondata.data.id+'"]').addClass('active');
+// //Contacts.UI.loadHandlers();
+// }
+// else{
+// Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+// //alert(jsondata.data.message);
+// }
+// });
+ return false;
+ });
+
+ /**
+ * Delete currently selected contact TODO: and clear page
+ */
+ $('#contacts_deletecard').live('click',function(){
+ Contacts.UI.Card.delete();
+ });
+
+ /**
+ * Add and insert a new contact into the list.
+ */
+ $('#contacts_addcardform input[type="submit"]').live('click',function(){
+ $.post('ajax/addcontact.php',$('#contact_identity').serialize(),function(jsondata){
+ if(jsondata.status == 'success'){
+ $('#rightcontent').data('id',jsondata.data.id);
+ $('#rightcontent').html(jsondata.data.page);
+ $('#leftcontent .active').removeClass('active');
+ var item = ''+jsondata.data.name+'';
+ var added = false;
+ $('#leftcontent ul li').each(function(){
+ if ($(this).text().toLowerCase() > jsondata.data.name.toLowerCase()) {
+ $(this).before(item).fadeIn('fast');
+ added = true;
+ return false;
+ }
+ });
+ if(!added) {
+ $('#leftcontent ul').append(item);
+ }
+ }
+ else{
+ Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+ //alert(jsondata.data.message);
+ }
+ }, 'json');
+ return false;
+ });
+
+ $('#contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
+ if (isInView) { //NOTE: I've kept all conditions for future reference ;-)
+ // element is now visible in the viewport
+ if (visiblePartY == 'top') {
+ // top part of element is visible
+ } else if (visiblePartY == 'bottom') {
+ // bottom part of element is visible
+ } else {
+ // whole part of element is visible
+ if (!$(this).find('a').attr('style')) {
+ //alert($(this).data('id') + ' has background: ' + $(this).attr('style'));
+ $(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat');
+ }/* else {
+ alert($(this).data('id') + ' has style ' + $(this).attr('style').match('url'));
+ }*/
+ }
+ } else {
+ // element has gone out of viewport
+ }
+ });
+
+ $('.button').tipsy();
+ // Triggers invisible file input
+ $('#contacts_details_photo').live('click', function() {
+ $('#file_upload_start').trigger('click');
+ return false;
+ });
+
+ // NOTE: For some reason the selector doesn't work when I select by '.contacts_property' too...
+ // I do the filtering in the event handler instead.
+ $('input[type="text"],input[type="checkbox"],input[type="email"],input[type="tel"],input[type="date"], select').live('change', function(){
+ Contacts.UI.Card.saveProperty(this);
+ });
+
+ // Name has changed. Update it and reorder.
+ $('#fn').live('change',function(){
+ var name = $('#fn').val();
+ var item = $('#contacts [data-id="'+Contacts.UI.Card.id+'"]').clone();
+ $('#contacts [data-id="'+Contacts.UI.Card.id+'"]').remove();
+ $(item).find('a').html(name);
+ var added = false;
+ $('#contacts li').each(function(){
+ if ($(this).text().toLowerCase() > name.toLowerCase()) {
+ $(this).before(item).fadeIn('fast');
+ added = true;
+ return false;
+ }
+ });
+ if(!added) {
+ $('#leftcontent ul').append(item);
+ }
+ });
+
+ /**
+ * Profile picture upload handling
+ */
+ // New profile picture selected
+ $('#file_upload_start').live('change',function(){
+ Contacts.UI.Card.uploadPhoto(this.files);
+ });
+ $('#contacts_details_photo').bind('dragover',function(event){
+ console.log('dragover');
+ $(event.target).css('background-color','red');
+ event.stopPropagation();
+ event.preventDefault();
+ });
+ $('#contacts_details_photo').bind('dragleave',function(event){
+ console.log('dragleave');
+ $(event.target).css('background-color','white');
+ //event.stopPropagation();
+ //event.preventDefault();
+ });
+ $('#contacts_details_photo').bind('drop',function(event){
+ event.stopPropagation();
+ event.preventDefault();
+ console.log('drop');
+ $(event.target).css('background-color','white')
+ $.fileUpload(event.originalEvent.dataTransfer.files);
+ });
+
+ /**
+ * Upload function for dropped files. Should go in the Contacts class/object.
+ */
+ $.fileUpload = function(files){
+ var file = files[0];
+ console.log('size: '+file.size);
+ if(file.size > $('#max_upload').val()){
+ Contacts.UI.messageBox(t('contacts','Upload too large'), t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'));
+ return;
+ }
+ if (file.type.indexOf("image") != 0) {
+ Contacts.UI.messageBox(t('contacts','Wrong file type'), t('contacts','Only image files can be used as profile picture.'));
+ return;
+ }
+ var xhr = new XMLHttpRequest();
+
+ if (!xhr.upload) {
+ Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Your browser doesn\'t support AJAX upload. Please click on the profile picture to select a photo to upload.'))
+ }
+ fileUpload = xhr.upload,
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4){
+ response = $.parseJSON(xhr.responseText);
+ if(response.status == 'success') {
+ if(xhr.status == 200) {
+ Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp);
+ } else {
+ Contacts.UI.messageBox(t('contacts', 'Error'), xhr.status + ': ' + xhr.responseText);
+ }
+ } else {
+ //alert(xhr.responseText);
+ Contacts.UI.messageBox(t('contacts', 'Error'), response.data.message);
+ }
+ // stop loading indicator
+ //$('#contacts_details_photo_progress').hide();
+ }
+ };
+
+ fileUpload.onprogress = function(e){
+ if (e.lengthComputable){
+ var _progress = Math.round((e.loaded * 100) / e.total);
+ if (_progress != 100){
+ $('#contacts_details_photo_progress').text(_progress + '%');
+ $('#contacts_details_photo_progress').val(_progress);
+ }
+ }
+ };
+ // Start loading indicator.
+ //$('#contacts_details_photo_progress').show()();
+ xhr.open("POST", 'ajax/uploadphoto.php?id='+Contacts.UI.Card.id+'&imagefile='+encodeURIComponent(file.name), true);
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
+ //xhr.setRequestHeader("X_FILENAME", file.name);
+ xhr.setRequestHeader('X-File-Size', file.size);
+ xhr.setRequestHeader('Content-Type', file.type);
+ xhr.send(file);
+ }
+
+ $('#contacts_propertymenu a').live('click',function(){
+ Contacts.UI.Card.addProperty(this);
+ });
+
+
+});
diff --git a/js/jquery.Jcrop.js b/js/jquery.Jcrop.js
new file mode 100644
index 00000000..36f9a0f0
--- /dev/null
+++ b/js/jquery.Jcrop.js
@@ -0,0 +1,1765 @@
+/**
+ * jquery.Jcrop.js v0.9.9 {{{
+ *
+ * jQuery Image Cropping Plugin - released under MIT License
+ * Author: Kelly Hallman
+ * http://github.com/tapmodo/Jcrop
+ *
+ * }}}
+ * Copyright (c) 2008-2012 Tapmodo Interactive LLC {{{
+ *
+ * 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.
+ *
+ * }}}
+ */
+
+(function ($) {
+
+ $.Jcrop = function (obj, opt) {
+ var options = $.extend({}, $.Jcrop.defaults),
+ docOffset, lastcurs, ie6mode = false;
+
+ // Internal Methods {{{
+ function px(n) {
+ return parseInt(n, 10) + 'px';
+ }
+ function cssClass(cl) {
+ return options.baseClass + '-' + cl;
+ }
+ function supportsColorFade() {
+ return $.fx.step.hasOwnProperty('backgroundColor');
+ }
+ function getPos(obj) //{{{
+ {
+ var pos = $(obj).offset();
+ return [pos.left, pos.top];
+ }
+ //}}}
+ function mouseAbs(e) //{{{
+ {
+ return [(e.pageX - docOffset[0]), (e.pageY - docOffset[1])];
+ }
+ //}}}
+ function setOptions(opt) //{{{
+ {
+ if (typeof(opt) !== 'object') opt = {};
+ options = $.extend(options, opt);
+
+ $.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e) {
+ if (typeof(options[e]) !== 'function') options[e] = function () {};
+ });
+ }
+ //}}}
+ function startDragMode(mode, pos) //{{{
+ {
+ docOffset = getPos($img);
+ Tracker.setCursor(mode === 'move' ? mode : mode + '-resize');
+
+ if (mode === 'move') {
+ return Tracker.activateHandlers(createMover(pos), doneSelect);
+ }
+
+ var fc = Coords.getFixed();
+ var opp = oppLockCorner(mode);
+ var opc = Coords.getCorner(oppLockCorner(opp));
+
+ Coords.setPressed(Coords.getCorner(opp));
+ Coords.setCurrent(opc);
+
+ Tracker.activateHandlers(dragmodeHandler(mode, fc), doneSelect);
+ }
+ //}}}
+ function dragmodeHandler(mode, f) //{{{
+ {
+ return function (pos) {
+ if (!options.aspectRatio) {
+ switch (mode) {
+ case 'e':
+ pos[1] = f.y2;
+ break;
+ case 'w':
+ pos[1] = f.y2;
+ break;
+ case 'n':
+ pos[0] = f.x2;
+ break;
+ case 's':
+ pos[0] = f.x2;
+ break;
+ }
+ } else {
+ switch (mode) {
+ case 'e':
+ pos[1] = f.y + 1;
+ break;
+ case 'w':
+ pos[1] = f.y + 1;
+ break;
+ case 'n':
+ pos[0] = f.x + 1;
+ break;
+ case 's':
+ pos[0] = f.x + 1;
+ break;
+ }
+ }
+ Coords.setCurrent(pos);
+ Selection.update();
+ };
+ }
+ //}}}
+ function createMover(pos) //{{{
+ {
+ var lloc = pos;
+ KeyManager.watchKeys();
+
+ return function (pos) {
+ Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
+ lloc = pos;
+
+ Selection.update();
+ };
+ }
+ //}}}
+ function oppLockCorner(ord) //{{{
+ {
+ switch (ord) {
+ case 'n':
+ return 'sw';
+ case 's':
+ return 'nw';
+ case 'e':
+ return 'nw';
+ case 'w':
+ return 'ne';
+ case 'ne':
+ return 'sw';
+ case 'nw':
+ return 'se';
+ case 'se':
+ return 'nw';
+ case 'sw':
+ return 'ne';
+ }
+ }
+ //}}}
+ function createDragger(ord) //{{{
+ {
+ return function (e) {
+ if (options.disabled) {
+ return false;
+ }
+ if ((ord === 'move') && !options.allowMove) {
+ return false;
+ }
+
+ // Fix position of crop area when dragged the very first time.
+ // Necessary when crop image is in a hidden element when page is loaded.
+ docOffset = getPos($img);
+
+ btndown = true;
+ startDragMode(ord, mouseAbs(e));
+ e.stopPropagation();
+ e.preventDefault();
+ return false;
+ };
+ }
+ //}}}
+ function presize($obj, w, h) //{{{
+ {
+ var nw = $obj.width(),
+ nh = $obj.height();
+ if ((nw > w) && w > 0) {
+ nw = w;
+ nh = (w / $obj.width()) * $obj.height();
+ }
+ if ((nh > h) && h > 0) {
+ nh = h;
+ nw = (h / $obj.height()) * $obj.width();
+ }
+ xscale = $obj.width() / nw;
+ yscale = $obj.height() / nh;
+ $obj.width(nw).height(nh);
+ }
+ //}}}
+ function unscale(c) //{{{
+ {
+ return {
+ x: parseInt(c.x * xscale, 10),
+ y: parseInt(c.y * yscale, 10),
+ x2: parseInt(c.x2 * xscale, 10),
+ y2: parseInt(c.y2 * yscale, 10),
+ w: parseInt(c.w * xscale, 10),
+ h: parseInt(c.h * yscale, 10)
+ };
+ }
+ //}}}
+ function doneSelect(pos) //{{{
+ {
+ var c = Coords.getFixed();
+ if ((c.w > options.minSelect[0]) && (c.h > options.minSelect[1])) {
+ Selection.enableHandles();
+ Selection.done();
+ } else {
+ Selection.release();
+ }
+ Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
+ }
+ //}}}
+ function newSelection(e) //{{{
+ {
+ if (options.disabled) {
+ return false;
+ }
+ if (!options.allowSelect) {
+ return false;
+ }
+ btndown = true;
+ docOffset = getPos($img);
+ Selection.disableHandles();
+ Tracker.setCursor('crosshair');
+ var pos = mouseAbs(e);
+ Coords.setPressed(pos);
+ Selection.update();
+ Tracker.activateHandlers(selectDrag, doneSelect);
+ KeyManager.watchKeys();
+
+ e.stopPropagation();
+ e.preventDefault();
+ return false;
+ }
+ //}}}
+ function selectDrag(pos) //{{{
+ {
+ Coords.setCurrent(pos);
+ Selection.update();
+ }
+ //}}}
+ function newTracker() //{{{
+ {
+ var trk = $('').addClass(cssClass('tracker'));
+ if ($.browser.msie) {
+ trk.css({
+ opacity: 0,
+ backgroundColor: 'white'
+ });
+ }
+ return trk;
+ }
+ //}}}
+
+ // }}}
+ // Initialization {{{
+ // Sanitize some options {{{
+ if ($.browser.msie && ($.browser.version.split('.')[0] === '6')) {
+ ie6mode = true;
+ }
+ if (typeof(obj) !== 'object') {
+ obj = $(obj)[0];
+ }
+ if (typeof(opt) !== 'object') {
+ opt = {};
+ }
+ // }}}
+ setOptions(opt);
+ // Initialize some jQuery objects {{{
+ // The values are SET on the image(s) for the interface
+ // If the original image has any of these set, they will be reset
+ // However, if you destroy() the Jcrop instance the original image's
+ // character in the DOM will be as you left it.
+ var img_css = {
+ border: 'none',
+ visibility: 'visible',
+ margin: 0,
+ padding: 0,
+ position: 'absolute',
+ top: 0,
+ left: 0
+ };
+
+ var $origimg = $(obj),
+ img_mode = true;
+
+ if (obj.tagName == 'IMG') {
+ // Fix size of crop image.
+ // Necessary when crop image is within a hidden element when page is loaded.
+ if ($origimg[0].width != 0 && $origimg[0].height != 0) {
+ // Obtain dimensions from contained img element.
+ $origimg.width($origimg[0].width);
+ $origimg.height($origimg[0].height);
+ } else {
+ // Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0).
+ var tempImage = new Image();
+ tempImage.src = $origimg[0].src;
+ $origimg.width(tempImage.width);
+ $origimg.height(tempImage.height);
+ }
+
+ var $img = $origimg.clone().removeAttr('id').css(img_css).show();
+
+ $img.width($origimg.width());
+ $img.height($origimg.height());
+ $origimg.after($img).hide();
+
+ } else {
+ $img = $origimg.css(img_css).show();
+ img_mode = false;
+ if (options.shade === null) { options.shade = true; }
+ }
+
+ presize($img, options.boxWidth, options.boxHeight);
+
+ var boundx = $img.width(),
+ boundy = $img.height(),
+
+
+ $div = $('').width(boundx).height(boundy).addClass(cssClass('holder')).css({
+ position: 'relative',
+ backgroundColor: options.bgColor
+ }).insertAfter($origimg).append($img);
+
+ if (options.addClass) {
+ $div.addClass(options.addClass);
+ }
+
+ var $img2 = $(''),
+
+ $img_holder = $('')
+ .width('100%').height('100%').css({
+ zIndex: 310,
+ position: 'absolute',
+ overflow: 'hidden'
+ }),
+
+ $hdl_holder = $('')
+ .width('100%').height('100%').css('zIndex', 320),
+
+ $sel = $('')
+ .css({
+ position: 'absolute',
+ zIndex: 600
+ }).dblclick(function(){
+ var c = Coords.getFixed();
+ options.onDblClick.call(api,c);
+ }).insertBefore($img).append($img_holder, $hdl_holder);
+
+ if (img_mode) {
+
+ $img2 = $('')
+ .attr('src', $img.attr('src')).css(img_css).width(boundx).height(boundy),
+
+ $img_holder.append($img2);
+
+ }
+
+ if (ie6mode) {
+ $sel.css({
+ overflowY: 'hidden'
+ });
+ }
+
+ var bound = options.boundary;
+ var $trk = newTracker().width(boundx + (bound * 2)).height(boundy + (bound * 2)).css({
+ position: 'absolute',
+ top: px(-bound),
+ left: px(-bound),
+ zIndex: 290
+ }).mousedown(newSelection);
+
+ /* }}} */
+ // Set more variables {{{
+ var bgcolor = options.bgColor,
+ bgopacity = options.bgOpacity,
+ xlimit, ylimit, xmin, ymin, xscale, yscale, enabled = true,
+ btndown, animating, shift_down;
+
+ docOffset = getPos($img);
+ // }}}
+ // }}}
+ // Internal Modules {{{
+ // Touch Module {{{
+ var Touch = (function () {
+ // Touch support detection function adapted (under MIT License)
+ // from code by Jeffrey Sambells - http://github.com/iamamused/
+ function hasTouchSupport() {
+ var support = {},
+ events = ['touchstart', 'touchmove', 'touchend'],
+ el = document.createElement('div'), i;
+
+ try {
+ for(i=0; i x1 + ox) {
+ ox -= ox + x1;
+ }
+ if (0 > y1 + oy) {
+ oy -= oy + y1;
+ }
+
+ if (boundy < y2 + oy) {
+ oy += boundy - (y2 + oy);
+ }
+ if (boundx < x2 + ox) {
+ ox += boundx - (x2 + ox);
+ }
+
+ x1 += ox;
+ x2 += ox;
+ y1 += oy;
+ y2 += oy;
+ }
+ //}}}
+ function getCorner(ord) //{{{
+ {
+ var c = getFixed();
+ switch (ord) {
+ case 'ne':
+ return [c.x2, c.y];
+ case 'nw':
+ return [c.x, c.y];
+ case 'se':
+ return [c.x2, c.y2];
+ case 'sw':
+ return [c.x, c.y2];
+ }
+ }
+ //}}}
+ function getFixed() //{{{
+ {
+ if (!options.aspectRatio) {
+ return getRect();
+ }
+ // This function could use some optimization I think...
+ var aspect = options.aspectRatio,
+ min_x = options.minSize[0] / xscale,
+
+
+ //min_y = options.minSize[1]/yscale,
+ max_x = options.maxSize[0] / xscale,
+ max_y = options.maxSize[1] / yscale,
+ rw = x2 - x1,
+ rh = y2 - y1,
+ rwa = Math.abs(rw),
+ rha = Math.abs(rh),
+ real_ratio = rwa / rha,
+ xx, yy, w, h;
+
+ if (max_x === 0) {
+ max_x = boundx * 10;
+ }
+ if (max_y === 0) {
+ max_y = boundy * 10;
+ }
+ if (real_ratio < aspect) {
+ yy = y2;
+ w = rha * aspect;
+ xx = rw < 0 ? x1 - w : w + x1;
+
+ if (xx < 0) {
+ xx = 0;
+ h = Math.abs((xx - x1) / aspect);
+ yy = rh < 0 ? y1 - h : h + y1;
+ } else if (xx > boundx) {
+ xx = boundx;
+ h = Math.abs((xx - x1) / aspect);
+ yy = rh < 0 ? y1 - h : h + y1;
+ }
+ } else {
+ xx = x2;
+ h = rwa / aspect;
+ yy = rh < 0 ? y1 - h : y1 + h;
+ if (yy < 0) {
+ yy = 0;
+ w = Math.abs((yy - y1) * aspect);
+ xx = rw < 0 ? x1 - w : w + x1;
+ } else if (yy > boundy) {
+ yy = boundy;
+ w = Math.abs(yy - y1) * aspect;
+ xx = rw < 0 ? x1 - w : w + x1;
+ }
+ }
+
+ // Magic %-)
+ if (xx > x1) { // right side
+ if (xx - x1 < min_x) {
+ xx = x1 + min_x;
+ } else if (xx - x1 > max_x) {
+ xx = x1 + max_x;
+ }
+ if (yy > y1) {
+ yy = y1 + (xx - x1) / aspect;
+ } else {
+ yy = y1 - (xx - x1) / aspect;
+ }
+ } else if (xx < x1) { // left side
+ if (x1 - xx < min_x) {
+ xx = x1 - min_x;
+ } else if (x1 - xx > max_x) {
+ xx = x1 - max_x;
+ }
+ if (yy > y1) {
+ yy = y1 + (x1 - xx) / aspect;
+ } else {
+ yy = y1 - (x1 - xx) / aspect;
+ }
+ }
+
+ if (xx < 0) {
+ x1 -= xx;
+ xx = 0;
+ } else if (xx > boundx) {
+ x1 -= xx - boundx;
+ xx = boundx;
+ }
+
+ if (yy < 0) {
+ y1 -= yy;
+ yy = 0;
+ } else if (yy > boundy) {
+ y1 -= yy - boundy;
+ yy = boundy;
+ }
+
+ return makeObj(flipCoords(x1, y1, xx, yy));
+ }
+ //}}}
+ function rebound(p) //{{{
+ {
+ if (p[0] < 0) {
+ p[0] = 0;
+ }
+ if (p[1] < 0) {
+ p[1] = 0;
+ }
+
+ if (p[0] > boundx) {
+ p[0] = boundx;
+ }
+ if (p[1] > boundy) {
+ p[1] = boundy;
+ }
+
+ return [p[0], p[1]];
+ }
+ //}}}
+ function flipCoords(x1, y1, x2, y2) //{{{
+ {
+ var xa = x1,
+ xb = x2,
+ ya = y1,
+ yb = y2;
+ if (x2 < x1) {
+ xa = x2;
+ xb = x1;
+ }
+ if (y2 < y1) {
+ ya = y2;
+ yb = y1;
+ }
+ return [Math.round(xa), Math.round(ya), Math.round(xb), Math.round(yb)];
+ }
+ //}}}
+ function getRect() //{{{
+ {
+ var xsize = x2 - x1,
+ ysize = y2 - y1,
+ delta;
+
+ if (xlimit && (Math.abs(xsize) > xlimit)) {
+ x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
+ }
+ if (ylimit && (Math.abs(ysize) > ylimit)) {
+ y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
+ }
+
+ if (ymin / yscale && (Math.abs(ysize) < ymin / yscale)) {
+ y2 = (ysize > 0) ? (y1 + ymin / yscale) : (y1 - ymin / yscale);
+ }
+ if (xmin / xscale && (Math.abs(xsize) < xmin / xscale)) {
+ x2 = (xsize > 0) ? (x1 + xmin / xscale) : (x1 - xmin / xscale);
+ }
+
+ if (x1 < 0) {
+ x2 -= x1;
+ x1 -= x1;
+ }
+ if (y1 < 0) {
+ y2 -= y1;
+ y1 -= y1;
+ }
+ if (x2 < 0) {
+ x1 -= x2;
+ x2 -= x2;
+ }
+ if (y2 < 0) {
+ y1 -= y2;
+ y2 -= y2;
+ }
+ if (x2 > boundx) {
+ delta = x2 - boundx;
+ x1 -= delta;
+ x2 -= delta;
+ }
+ if (y2 > boundy) {
+ delta = y2 - boundy;
+ y1 -= delta;
+ y2 -= delta;
+ }
+ if (x1 > boundx) {
+ delta = x1 - boundy;
+ y2 -= delta;
+ y1 -= delta;
+ }
+ if (y1 > boundy) {
+ delta = y1 - boundy;
+ y2 -= delta;
+ y1 -= delta;
+ }
+
+ return makeObj(flipCoords(x1, y1, x2, y2));
+ }
+ //}}}
+ function makeObj(a) //{{{
+ {
+ return {
+ x: a[0],
+ y: a[1],
+ x2: a[2],
+ y2: a[3],
+ w: a[2] - a[0],
+ h: a[3] - a[1]
+ };
+ }
+ //}}}
+
+ return {
+ flipCoords: flipCoords,
+ setPressed: setPressed,
+ setCurrent: setCurrent,
+ getOffset: getOffset,
+ moveOffset: moveOffset,
+ getCorner: getCorner,
+ getFixed: getFixed
+ };
+ }());
+
+ //}}}
+ // Shade Module {{{
+ var Shade = (function() {
+ var enabled = false,
+ holder = $('').css({
+ position: 'absolute',
+ zIndex: 240,
+ opacity: 0
+ }),
+ shades = {
+ top: createShade(),
+ left: createShade().height(boundy),
+ right: createShade().height(boundy),
+ bottom: createShade()
+ };
+
+ function resizeShades(w,h) {
+ shades.left.css({ height: px(h) });
+ shades.right.css({ height: px(h) });
+ }
+ function updateAuto()
+ {
+ return updateShade(Coords.getFixed());
+ }
+ function updateShade(c)
+ {
+ shades.top.css({
+ left: px(c.x),
+ width: px(c.w),
+ height: px(c.y)
+ });
+ shades.bottom.css({
+ top: px(c.y2),
+ left: px(c.x),
+ width: px(c.w),
+ height: px(boundy-c.y2)
+ });
+ shades.right.css({
+ left: px(c.x2),
+ width: px(boundx-c.x2)
+ });
+ shades.left.css({
+ width: px(c.x)
+ });
+ }
+ function createShade() {
+ return $('').css({
+ position: 'absolute',
+ backgroundColor: options.shadeColor||options.bgColor
+ }).appendTo(holder);
+ }
+ function enableShade() {
+ if (!enabled) {
+ enabled = true;
+ holder.insertBefore($img);
+ updateAuto();
+ Selection.setBgOpacity(1,0,1);
+ $img2.hide();
+
+ setBgColor(options.shadeColor||options.bgColor,1);
+ if (Selection.isAwake())
+ {
+ setOpacity(options.bgOpacity,1);
+ }
+ else setOpacity(1,1);
+ }
+ }
+ function setBgColor(color,now) {
+ colorChangeMacro(getShades(),color,now);
+ }
+ function disableShade() {
+ if (enabled) {
+ holder.remove();
+ $img2.show();
+ enabled = false;
+ if (Selection.isAwake()) {
+ Selection.setBgOpacity(options.bgOpacity,1,1);
+ } else {
+ Selection.setBgOpacity(1,1,1);
+ Selection.disableHandles();
+ }
+ colorChangeMacro($div,0,1);
+ }
+ }
+ function setOpacity(opacity,now) {
+ if (enabled) {
+ if (options.bgFade && !now) {
+ holder.animate({
+ opacity: 1-opacity
+ },{
+ queue: false,
+ duration: options.fadeTime
+ });
+ }
+ else holder.css({opacity:1-opacity});
+ }
+ }
+ function refreshAll() {
+ options.shade ? enableShade() : disableShade();
+ if (Selection.isAwake()) setOpacity(options.bgOpacity);
+ }
+ function getShades() {
+ return holder.children();
+ }
+
+ return {
+ update: updateAuto,
+ updateRaw: updateShade,
+ getShades: getShades,
+ setBgColor: setBgColor,
+ enable: enableShade,
+ disable: disableShade,
+ resize: resizeShades,
+ refresh: refreshAll,
+ opacity: setOpacity
+ };
+ }());
+ // }}}
+ // Selection Module {{{
+ var Selection = (function () {
+ var awake, hdep = 370;
+ var borders = {};
+ var handle = {};
+ var seehandles = false;
+ var hhs = options.handleOffset;
+
+ // Private Methods
+ function insertBorder(type) //{{{
+ {
+ var jq = $('').css({
+ position: 'absolute',
+ opacity: options.borderOpacity
+ }).addClass(cssClass(type));
+ $img_holder.append(jq);
+ return jq;
+ }
+ //}}}
+ function dragDiv(ord, zi) //{{{
+ {
+ var jq = $('').mousedown(createDragger(ord)).css({
+ cursor: ord + '-resize',
+ position: 'absolute',
+ zIndex: zi
+ }).addClass('ord-'+ord);
+
+ if (Touch.support) {
+ jq.bind('touchstart.jcrop', Touch.createDragger(ord));
+ }
+
+ $hdl_holder.append(jq);
+ return jq;
+ }
+ //}}}
+ function insertHandle(ord) //{{{
+ {
+ var hs = options.handleSize;
+ return dragDiv(ord, hdep++).css({
+ top: px(-hhs + 1),
+ left: px(-hhs + 1),
+ opacity: options.handleOpacity
+ }).width(hs).height(hs).addClass(cssClass('handle'));
+ }
+ //}}}
+ function insertDragbar(ord) //{{{
+ {
+ var s = options.handleSize,
+ h = s,
+ w = s,
+ t = hhs,
+ l = hhs;
+
+ switch (ord) {
+ case 'n':
+ case 's':
+ w = '100%';
+ break;
+ case 'e':
+ case 'w':
+ h = '100%';
+ break;
+ }
+
+ return dragDiv(ord, hdep++).width(w).height(h).css({
+ top: px(-t + 1),
+ left: px(-l + 1)
+ });
+ }
+ //}}}
+ function createHandles(li) //{{{
+ {
+ var i;
+ for (i = 0; i < li.length; i++) {
+ handle[li[i]] = insertHandle(li[i]);
+ }
+ }
+ //}}}
+ function moveHandles(c) //{{{
+ {
+ var midvert = Math.round((c.h / 2) - hhs),
+ midhoriz = Math.round((c.w / 2) - hhs),
+ north = -hhs + 1,
+ west = -hhs + 1,
+ east = c.w - hhs,
+ south = c.h - hhs,
+ x, y;
+
+ if (handle.e) {
+ handle.e.css({
+ top: px(midvert),
+ left: px(east)
+ });
+ handle.w.css({
+ top: px(midvert)
+ });
+ handle.s.css({
+ top: px(south),
+ left: px(midhoriz)
+ });
+ handle.n.css({
+ left: px(midhoriz)
+ });
+ }
+ if (handle.ne) {
+ handle.ne.css({
+ left: px(east)
+ });
+ handle.se.css({
+ top: px(south),
+ left: px(east)
+ });
+ handle.sw.css({
+ top: px(south)
+ });
+ }
+ if (handle.b) {
+ handle.b.css({
+ top: px(south)
+ });
+ handle.r.css({
+ left: px(east)
+ });
+ }
+ }
+ //}}}
+ function moveto(x, y) //{{{
+ {
+ if (!options.shade) {
+ $img2.css({
+ top: px(-y),
+ left: px(-x)
+ });
+ }
+ $sel.css({
+ top: px(y),
+ left: px(x)
+ });
+ }
+ //}}}
+ function resize(w, h) //{{{
+ {
+ $sel.width(w).height(h);
+ }
+ //}}}
+ function refresh() //{{{
+ {
+ var c = Coords.getFixed();
+
+ Coords.setPressed([c.x, c.y]);
+ Coords.setCurrent([c.x2, c.y2]);
+
+ updateVisible();
+ }
+ //}}}
+
+ // Internal Methods
+ function updateVisible(select) //{{{
+ {
+ if (awake) {
+ return update(select);
+ }
+ }
+ //}}}
+ function update(select) //{{{
+ {
+ var c = Coords.getFixed();
+
+ resize(c.w, c.h);
+ moveto(c.x, c.y);
+ if (options.shade) Shade.updateRaw(c);
+
+ if (seehandles) {
+ moveHandles(c);
+ }
+ if (!awake) {
+ show();
+ }
+
+ if (select) {
+ options.onSelect.call(api, unscale(c));
+ } else {
+ options.onChange.call(api, unscale(c));
+ }
+ }
+ //}}}
+ function setBgOpacity(opacity,force,now)
+ {
+ if (!awake && !force) return;
+ if (options.bgFade && !now) {
+ $img.animate({
+ opacity: opacity
+ },{
+ queue: false,
+ duration: options.fadeTime
+ });
+ } else {
+ $img.css('opacity', opacity);
+ }
+ }
+ function show() //{{{
+ {
+ $sel.show();
+
+ if (options.shade) Shade.opacity(bgopacity);
+ else setBgOpacity(bgopacity,true);
+
+ awake = true;
+ }
+ //}}}
+ function release() //{{{
+ {
+ disableHandles();
+ $sel.hide();
+
+ if (options.shade) Shade.opacity(1);
+ else setBgOpacity(1);
+
+ awake = false;
+ options.onRelease.call(api);
+ }
+ //}}}
+ function showHandles() //{{{
+ {
+ if (seehandles) {
+ moveHandles(Coords.getFixed());
+ $hdl_holder.show();
+ }
+ }
+ //}}}
+ function enableHandles() //{{{
+ {
+ seehandles = true;
+ if (options.allowResize) {
+ moveHandles(Coords.getFixed());
+ $hdl_holder.show();
+ return true;
+ }
+ }
+ //}}}
+ function disableHandles() //{{{
+ {
+ seehandles = false;
+ $hdl_holder.hide();
+ }
+ //}}}
+ function animMode(v) //{{{
+ {
+ if (animating === v) {
+ disableHandles();
+ } else {
+ enableHandles();
+ }
+ }
+ //}}}
+ function done() //{{{
+ {
+ animMode(false);
+ refresh();
+ }
+ //}}}
+ /* Insert draggable elements {{{*/
+
+ // Insert border divs for outline
+ if (options.drawBorders) {
+ borders = {
+ top: insertBorder('hline'),
+ bottom: insertBorder('hline bottom'),
+ left: insertBorder('vline'),
+ right: insertBorder('vline right')
+ };
+ }
+
+ // Insert handles on edges
+ if (options.dragEdges) {
+ handle.t = insertDragbar('n');
+ handle.b = insertDragbar('s');
+ handle.r = insertDragbar('e');
+ handle.l = insertDragbar('w');
+ }
+
+ // Insert side and corner handles
+ if (options.sideHandles) {
+ createHandles(['n', 's', 'e', 'w']);
+ }
+ if (options.cornerHandles) {
+ createHandles(['sw', 'nw', 'ne', 'se']);
+ }
+ //}}}
+
+ // This is a hack for iOS5 to support drag/move touch functionality
+ $(document).bind('touchstart.jcrop-ios',function(e) {
+ if ($(e.currentTarget).hasClass('jcrop-tracker')) e.stopPropagation();
+ });
+
+ var $track = newTracker().mousedown(createDragger('move')).css({
+ cursor: 'move',
+ position: 'absolute',
+ zIndex: 360
+ });
+
+ if (Touch.support) {
+ $track.bind('touchstart.jcrop', Touch.createDragger('move'));
+ }
+
+ $img_holder.append($track);
+ disableHandles();
+
+ return {
+ updateVisible: updateVisible,
+ update: update,
+ release: release,
+ refresh: refresh,
+ isAwake: function () {
+ return awake;
+ },
+ setCursor: function (cursor) {
+ $track.css('cursor', cursor);
+ },
+ enableHandles: enableHandles,
+ enableOnly: function () {
+ seehandles = true;
+ },
+ showHandles: showHandles,
+ disableHandles: disableHandles,
+ animMode: animMode,
+ setBgOpacity: setBgOpacity,
+ done: done
+ };
+ }());
+
+ //}}}
+ // Tracker Module {{{
+ var Tracker = (function () {
+ var onMove = function () {},
+ onDone = function () {},
+ trackDoc = options.trackDocument;
+
+ function toFront() //{{{
+ {
+ $trk.css({
+ zIndex: 450
+ });
+ if (Touch.support) {
+ $(document)
+ .bind('touchmove.jcrop', trackTouchMove)
+ .bind('touchend.jcrop', trackTouchEnd);
+ }
+ if (trackDoc) {
+ $(document)
+ .bind('mousemove.jcrop',trackMove)
+ .bind('mouseup.jcrop',trackUp);
+ }
+ }
+ //}}}
+ function toBack() //{{{
+ {
+ $trk.css({
+ zIndex: 290
+ });
+ $(document).unbind('.jcrop');
+ }
+ //}}}
+ function trackMove(e) //{{{
+ {
+ onMove(mouseAbs(e));
+ return false;
+ }
+ //}}}
+ function trackUp(e) //{{{
+ {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (btndown) {
+ btndown = false;
+
+ onDone(mouseAbs(e));
+
+ if (Selection.isAwake()) {
+ options.onSelect.call(api, unscale(Coords.getFixed()));
+ }
+
+ toBack();
+ onMove = function () {};
+ onDone = function () {};
+ }
+
+ return false;
+ }
+ //}}}
+ function activateHandlers(move, done) //{{{
+ {
+ btndown = true;
+ onMove = move;
+ onDone = done;
+ toFront();
+ return false;
+ }
+ //}}}
+ function trackTouchMove(e) //{{{
+ {
+ e.pageX = e.originalEvent.changedTouches[0].pageX;
+ e.pageY = e.originalEvent.changedTouches[0].pageY;
+ return trackMove(e);
+ }
+ //}}}
+ function trackTouchEnd(e) //{{{
+ {
+ e.pageX = e.originalEvent.changedTouches[0].pageX;
+ e.pageY = e.originalEvent.changedTouches[0].pageY;
+ return trackUp(e);
+ }
+ //}}}
+ function setCursor(t) //{{{
+ {
+ $trk.css('cursor', t);
+ }
+ //}}}
+
+ if (!trackDoc) {
+ $trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);
+ }
+
+ $img.before($trk);
+ return {
+ activateHandlers: activateHandlers,
+ setCursor: setCursor
+ };
+ }());
+ //}}}
+ // KeyManager Module {{{
+ var KeyManager = (function () {
+ var $keymgr = $('').css({
+ position: 'fixed',
+ left: '-120px',
+ width: '12px'
+ }),
+ $keywrap = $('').css({
+ position: 'absolute',
+ overflow: 'hidden'
+ }).append($keymgr);
+
+ function watchKeys() //{{{
+ {
+ if (options.keySupport) {
+ $keymgr.show();
+ $keymgr.focus();
+ }
+ }
+ //}}}
+ function onBlur(e) //{{{
+ {
+ $keymgr.hide();
+ }
+ //}}}
+ function doNudge(e, x, y) //{{{
+ {
+ if (options.allowMove) {
+ Coords.moveOffset([x, y]);
+ Selection.updateVisible(true);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ //}}}
+ function parseKey(e) //{{{
+ {
+ if (e.ctrlKey || e.metaKey) {
+ return true;
+ }
+ shift_down = e.shiftKey ? true : false;
+ var nudge = shift_down ? 10 : 1;
+
+ switch (e.keyCode) {
+ case 37:
+ doNudge(e, -nudge, 0);
+ break;
+ case 39:
+ doNudge(e, nudge, 0);
+ break;
+ case 38:
+ doNudge(e, 0, -nudge);
+ break;
+ case 40:
+ doNudge(e, 0, nudge);
+ break;
+ case 27:
+ if (options.allowSelect) Selection.release();
+ break;
+ case 9:
+ return true;
+ }
+
+ return false;
+ }
+ //}}}
+
+ if (options.keySupport) {
+ $keymgr.keydown(parseKey).blur(onBlur);
+ if (ie6mode || !options.fixedSupport) {
+ $keymgr.css({
+ position: 'absolute',
+ left: '-20px'
+ });
+ $keywrap.append($keymgr).insertBefore($img);
+ } else {
+ $keymgr.insertBefore($img);
+ }
+ }
+
+
+ return {
+ watchKeys: watchKeys
+ };
+ }());
+ //}}}
+ // }}}
+ // API methods {{{
+ function setClass(cname) //{{{
+ {
+ $div.removeClass().addClass(cssClass('holder')).addClass(cname);
+ }
+ //}}}
+ function animateTo(a, callback) //{{{
+ {
+ var x1 = parseInt(a[0], 10) / xscale,
+ y1 = parseInt(a[1], 10) / yscale,
+ x2 = parseInt(a[2], 10) / xscale,
+ y2 = parseInt(a[3], 10) / yscale;
+
+ if (animating) {
+ return;
+ }
+
+ var animto = Coords.flipCoords(x1, y1, x2, y2),
+ c = Coords.getFixed(),
+ initcr = [c.x, c.y, c.x2, c.y2],
+ animat = initcr,
+ interv = options.animationDelay,
+ ix1 = animto[0] - initcr[0],
+ iy1 = animto[1] - initcr[1],
+ ix2 = animto[2] - initcr[2],
+ iy2 = animto[3] - initcr[3],
+ pcent = 0,
+ velocity = options.swingSpeed;
+
+ x = animat[0];
+ y = animat[1];
+ x2 = animat[2];
+ y2 = animat[3];
+
+ Selection.animMode(true);
+ var anim_timer;
+
+ function queueAnimator() {
+ window.setTimeout(animator, interv);
+ }
+ var animator = (function () {
+ return function () {
+ pcent += (100 - pcent) / velocity;
+
+ animat[0] = x + ((pcent / 100) * ix1);
+ animat[1] = y + ((pcent / 100) * iy1);
+ animat[2] = x2 + ((pcent / 100) * ix2);
+ animat[3] = y2 + ((pcent / 100) * iy2);
+
+ if (pcent >= 99.8) {
+ pcent = 100;
+ }
+ if (pcent < 100) {
+ setSelectRaw(animat);
+ queueAnimator();
+ } else {
+ Selection.done();
+ if (typeof(callback) === 'function') {
+ callback.call(api);
+ }
+ }
+ };
+ }());
+ queueAnimator();
+ }
+ //}}}
+ function setSelect(rect) //{{{
+ {
+ setSelectRaw([parseInt(rect[0], 10) / xscale, parseInt(rect[1], 10) / yscale, parseInt(rect[2], 10) / xscale, parseInt(rect[3], 10) / yscale]);
+ options.onSelect.call(api, unscale(Coords.getFixed()));
+ Selection.enableHandles();
+ }
+ //}}}
+ function setSelectRaw(l) //{{{
+ {
+ Coords.setPressed([l[0], l[1]]);
+ Coords.setCurrent([l[2], l[3]]);
+ Selection.update();
+ }
+ //}}}
+ function tellSelect() //{{{
+ {
+ return unscale(Coords.getFixed());
+ }
+ //}}}
+ function tellScaled() //{{{
+ {
+ return Coords.getFixed();
+ }
+ //}}}
+ function setOptionsNew(opt) //{{{
+ {
+ setOptions(opt);
+ interfaceUpdate();
+ }
+ //}}}
+ function disableCrop() //{{{
+ {
+ options.disabled = true;
+ Selection.disableHandles();
+ Selection.setCursor('default');
+ Tracker.setCursor('default');
+ }
+ //}}}
+ function enableCrop() //{{{
+ {
+ options.disabled = false;
+ interfaceUpdate();
+ }
+ //}}}
+ function cancelCrop() //{{{
+ {
+ Selection.done();
+ Tracker.activateHandlers(null, null);
+ }
+ //}}}
+ function destroy() //{{{
+ {
+ $div.remove();
+ $origimg.show();
+ $(obj).removeData('Jcrop');
+ }
+ //}}}
+ function setImage(src, callback) //{{{
+ {
+ Selection.release();
+ disableCrop();
+ var img = new Image();
+ img.onload = function () {
+ var iw = img.width;
+ var ih = img.height;
+ var bw = options.boxWidth;
+ var bh = options.boxHeight;
+ $img.width(iw).height(ih);
+ $img.attr('src', src);
+ $img2.attr('src', src);
+ presize($img, bw, bh);
+ boundx = $img.width();
+ boundy = $img.height();
+ $img2.width(boundx).height(boundy);
+ $trk.width(boundx + (bound * 2)).height(boundy + (bound * 2));
+ $div.width(boundx).height(boundy);
+ Shade.resize(boundx,boundy);
+ enableCrop();
+
+ if (typeof(callback) === 'function') {
+ callback.call(api);
+ }
+ };
+ img.src = src;
+ }
+ //}}}
+ function colorChangeMacro($obj,color,now) {
+ var mycolor = color || options.bgColor;
+ if (options.bgFade && supportsColorFade() && options.fadeTime && !now) {
+ $obj.animate({
+ backgroundColor: mycolor
+ }, {
+ queue: false,
+ duration: options.fadeTime
+ });
+ } else {
+ $obj.css('backgroundColor', mycolor);
+ }
+ }
+ function interfaceUpdate(alt) //{{{
+ // This method tweaks the interface based on options object.
+ // Called when options are changed and at end of initialization.
+ {
+ if (options.allowResize) {
+ if (alt) {
+ Selection.enableOnly();
+ } else {
+ Selection.enableHandles();
+ }
+ } else {
+ Selection.disableHandles();
+ }
+
+ Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
+ Selection.setCursor(options.allowMove ? 'move' : 'default');
+
+ if (options.hasOwnProperty('trueSize')) {
+ xscale = options.trueSize[0] / boundx;
+ yscale = options.trueSize[1] / boundy;
+ }
+
+ if (options.hasOwnProperty('setSelect')) {
+ setSelect(options.setSelect);
+ Selection.done();
+ delete(options.setSelect);
+ }
+
+ Shade.refresh();
+
+ if (options.bgColor != bgcolor) {
+ colorChangeMacro(
+ options.shade? Shade.getShades(): $div,
+ options.shade?
+ (options.shadeColor || options.bgColor):
+ options.bgColor
+ );
+ bgcolor = options.bgColor;
+ }
+
+ if (bgopacity != options.bgOpacity) {
+ bgopacity = options.bgOpacity;
+ if (options.shade) Shade.refresh();
+ else Selection.setBgOpacity(bgopacity);
+ }
+
+ xlimit = options.maxSize[0] || 0;
+ ylimit = options.maxSize[1] || 0;
+ xmin = options.minSize[0] || 0;
+ ymin = options.minSize[1] || 0;
+
+ if (options.hasOwnProperty('outerImage')) {
+ $img.attr('src', options.outerImage);
+ delete(options.outerImage);
+ }
+
+ Selection.refresh();
+ }
+ //}}}
+ //}}}
+
+ if (Touch.support) $trk.bind('touchstart.jcrop', Touch.newSelection);
+
+ $hdl_holder.hide();
+ interfaceUpdate(true);
+
+ var api = {
+ setImage: setImage,
+ animateTo: animateTo,
+ setSelect: setSelect,
+ setOptions: setOptionsNew,
+ tellSelect: tellSelect,
+ tellScaled: tellScaled,
+ setClass: setClass,
+
+ disable: disableCrop,
+ enable: enableCrop,
+ cancel: cancelCrop,
+ release: Selection.release,
+ destroy: destroy,
+
+ focus: KeyManager.watchKeys,
+
+ getBounds: function () {
+ return [boundx * xscale, boundy * yscale];
+ },
+ getWidgetSize: function () {
+ return [boundx, boundy];
+ },
+ getScaleFactor: function () {
+ return [xscale, yscale];
+ },
+
+ ui: {
+ holder: $div,
+ selection: $sel
+ }
+ };
+
+ if ($.browser.msie) {
+ $div.bind('selectstart', function () {
+ return false;
+ });
+ }
+
+ $origimg.data('Jcrop', api);
+ return api;
+ };
+ $.fn.Jcrop = function (options, callback) //{{{
+ {
+ var api;
+ // Iterate over each object, attach Jcrop
+ this.each(function () {
+ // If we've already attached to this object
+ if ($(this).data('Jcrop')) {
+ // The API can be requested this way (undocumented)
+ if (options === 'api') return $(this).data('Jcrop');
+ // Otherwise, we just reset the options...
+ else $(this).data('Jcrop').setOptions(options);
+ }
+ // If we haven't been attached, preload and attach
+ else {
+ if (this.tagName == 'IMG')
+ $.Jcrop.Loader(this,function(){
+ $(this).css({display:'block',visibility:'hidden'});
+ api = $.Jcrop(this, options);
+ if ($.isFunction(callback)) callback.call(api);
+ });
+ else {
+ $(this).css({display:'block',visibility:'hidden'});
+ api = $.Jcrop(this, options);
+ if ($.isFunction(callback)) callback.call(api);
+ }
+ }
+ });
+
+ // Return "this" so the object is chainable (jQuery-style)
+ return this;
+ };
+ //}}}
+ // $.Jcrop.Loader - basic image loader {{{
+
+ $.Jcrop.Loader = function(imgobj,success,error){
+ var $img = $(imgobj), img = $img[0];
+
+ function completeCheck(){
+ if (img.complete) {
+ $img.unbind('.jcloader');
+ if ($.isFunction(success)) success.call(img);
+ }
+ else window.setTimeout(completeCheck,50);
+ }
+
+ $img
+ .bind('load.jcloader',completeCheck)
+ .bind('error.jcloader',function(e){
+ $img.unbind('.jcloader');
+ if ($.isFunction(error)) error.call(img);
+ });
+
+ if (img.complete && $.isFunction(success)){
+ $img.unbind('.jcloader');
+ success.call(img);
+ }
+ };
+
+ //}}}
+ // Global Defaults {{{
+ $.Jcrop.defaults = {
+
+ // Basic Settings
+ allowSelect: true,
+ allowMove: true,
+ allowResize: true,
+
+ trackDocument: true,
+
+ // Styling Options
+ baseClass: 'jcrop',
+ addClass: null,
+ bgColor: 'black',
+ bgOpacity: 0.6,
+ bgFade: false,
+ borderOpacity: 0.4,
+ handleOpacity: 0.5,
+ handleSize: 7,
+ handleOffset: 5,
+
+ aspectRatio: 0,
+ keySupport: true,
+ cornerHandles: true,
+ sideHandles: true,
+ drawBorders: true,
+ dragEdges: true,
+ fixedSupport: true,
+ touchSupport: null,
+
+ shade: null,
+
+ boxWidth: 0,
+ boxHeight: 0,
+ boundary: 2,
+ fadeTime: 400,
+ animationDelay: 20,
+ swingSpeed: 3,
+
+ minSelect: [0, 0],
+ maxSize: [0, 0],
+ minSize: [0, 0],
+
+ // Callbacks / Event Handlers
+ onChange: function () {},
+ onSelect: function () {},
+ onDblClick: function () {},
+ onRelease: function () {}
+ };
+
+ // }}}
+}(jQuery));
diff --git a/js/jquery.Jcrop.min.js b/js/jquery.Jcrop.min.js
new file mode 100644
index 00000000..0c05d67c
--- /dev/null
+++ b/js/jquery.Jcrop.min.js
@@ -0,0 +1,255 @@
+/**
+ * jquery.Jcrop.min.js v0.9.9 {{{ (build:20120102)
+ * jQuery Image Cropping Plugin - released under MIT License
+ * Copyright (c) 2008-2012 Tapmodo Interactive LLC
+ * https://github.com/tapmodo/Jcrop
+ */
+
+(function($){$.Jcrop=function(obj,opt){var options=$.extend({},$.Jcrop.defaults),docOffset,lastcurs,ie6mode=false;function px(n){return parseInt(n,10)+'px';}
+function cssClass(cl){return options.baseClass+'-'+cl;}
+function supportsColorFade(){return $.fx.step.hasOwnProperty('backgroundColor');}
+function getPos(obj)
+{var pos=$(obj).offset();return[pos.left,pos.top];}
+function mouseAbs(e)
+{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];}
+function setOptions(opt)
+{if(typeof(opt)!=='object')opt={};options=$.extend(options,opt);$.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e){if(typeof(options[e])!=='function')options[e]=function(){};});}
+function startDragMode(mode,pos)
+{docOffset=getPos($img);Tracker.setCursor(mode==='move'?mode:mode+'-resize');if(mode==='move'){return Tracker.activateHandlers(createMover(pos),doneSelect);}
+var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);}
+function dragmodeHandler(mode,f)
+{return function(pos){if(!options.aspectRatio){switch(mode){case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}}else{switch(mode){case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}}
+Coords.setCurrent(pos);Selection.update();};}
+function createMover(pos)
+{var lloc=pos;KeyManager.watchKeys();return function(pos){Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};}
+function oppLockCorner(ord)
+{switch(ord){case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';}}
+function createDragger(ord)
+{return function(e){if(options.disabled){return false;}
+if((ord==='move')&&!options.allowMove){return false;}
+docOffset=getPos($img);btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};}
+function presize($obj,w,h)
+{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0){nw=w;nh=(w/$obj.width())*$obj.height();}
+if((nh>h)&&h>0){nh=h;nw=(h/$obj.height())*$obj.width();}
+xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);}
+function unscale(c)
+{return{x:parseInt(c.x*xscale,10),y:parseInt(c.y*yscale,10),x2:parseInt(c.x2*xscale,10),y2:parseInt(c.y2*yscale,10),w:parseInt(c.w*xscale,10),h:parseInt(c.h*yscale,10)};}
+function doneSelect(pos)
+{var c=Coords.getFixed();if((c.w>options.minSelect[0])&&(c.h>options.minSelect[1])){Selection.enableHandles();Selection.done();}else{Selection.release();}
+Tracker.setCursor(options.allowSelect?'crosshair':'default');}
+function newSelection(e)
+{if(options.disabled){return false;}
+if(!options.allowSelect){return false;}
+btndown=true;docOffset=getPos($img);Selection.disableHandles();Tracker.setCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Selection.update();Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();e.stopPropagation();e.preventDefault();return false;}
+function selectDrag(pos)
+{Coords.setCurrent(pos);Selection.update();}
+function newTracker()
+{var trk=$('').addClass(cssClass('tracker'));if($.browser.msie){trk.css({opacity:0,backgroundColor:'white'});}
+return trk;}
+if($.browser.msie&&($.browser.version.split('.')[0]==='6')){ie6mode=true;}
+if(typeof(obj)!=='object'){obj=$(obj)[0];}
+if(typeof(opt)!=='object'){opt={};}
+setOptions(opt);var img_css={border:'none',visibility:'visible',margin:0,padding:0,position:'absolute',top:0,left:0};var $origimg=$(obj),img_mode=true;if(obj.tagName=='IMG'){if($origimg[0].width!=0&&$origimg[0].height!=0){$origimg.width($origimg[0].width);$origimg.height($origimg[0].height);}else{var tempImage=new Image();tempImage.src=$origimg[0].src;$origimg.width(tempImage.width);$origimg.height(tempImage.height);}
+var $img=$origimg.clone().removeAttr('id').css(img_css).show();$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();}else{$img=$origimg.css(img_css).show();img_mode=false;if(options.shade===null){options.shade=true;}}
+presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);if(options.addClass){$div.addClass(options.addClass);}
+var $img2=$(''),$img_holder=$('').width('100%').height('100%').css({zIndex:310,position:'absolute',overflow:'hidden'}),$hdl_holder=$('').width('100%').height('100%').css('zIndex',320),$sel=$('').css({position:'absolute',zIndex:600}).dblclick(function(){var c=Coords.getFixed();options.onDblClick.call(api,c);}).insertBefore($img).append($img_holder,$hdl_holder);if(img_mode){$img2=$('').attr('src',$img.attr('src')).css(img_css).width(boundx).height(boundy),$img_holder.append($img2);}
+if(ie6mode){$sel.css({overflowY:'hidden'});}
+var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var bgcolor=options.bgColor,bgopacity=options.bgOpacity,xlimit,ylimit,xmin,ymin,xscale,yscale,enabled=true,btndown,animating,shift_down;docOffset=getPos($img);var Touch=(function(){function hasTouchSupport(){var support={},events=['touchstart','touchmove','touchend'],el=document.createElement('div'),i;try{for(i=0;ix1+ox){ox-=ox+x1;}
+if(0>y1+oy){oy-=oy+y1;}
+if(boundyboundx){xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}else{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0){yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}else if(yy>boundy){yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
+if(xx>x1){if(xx-x1max_x){xx=x1+max_x;}
+if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xxmax_x){xx=x1-max_x;}
+if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
+if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
+if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
+return makeObj(flipCoords(x1,y1,xx,yy));}
+function rebound(p)
+{if(p[0]<0){p[0]=0;}
+if(p[1]<0){p[1]=0;}
+if(p[0]>boundx){p[0]=boundx;}
+if(p[1]>boundy){p[1]=boundy;}
+return[p[0],p[1]];}
+function flipCoords(x1,y1,x2,y2)
+{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2xlimit)){x2=(xsize>0)?(x1+xlimit):(x1-xlimit);}
+if(ylimit&&(Math.abs(ysize)>ylimit)){y2=(ysize>0)?(y1+ylimit):(y1-ylimit);}
+if(ymin/yscale&&(Math.abs(ysize)0)?(y1+ymin/yscale):(y1-ymin/yscale);}
+if(xmin/xscale&&(Math.abs(xsize)0)?(x1+xmin/xscale):(x1-xmin/xscale);}
+if(x1<0){x2-=x1;x1-=x1;}
+if(y1<0){y2-=y1;y1-=y1;}
+if(x2<0){x1-=x2;x2-=x2;}
+if(y2<0){y1-=y2;y2-=y2;}
+if(x2>boundx){delta=x2-boundx;x1-=delta;x2-=delta;}
+if(y2>boundy){delta=y2-boundy;y1-=delta;y2-=delta;}
+if(x1>boundx){delta=x1-boundy;y2-=delta;y1-=delta;}
+if(y1>boundy){delta=y1-boundy;y2-=delta;y1-=delta;}
+return makeObj(flipCoords(x1,y1,x2,y2));}
+function makeObj(a)
+{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};}
+return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}());var Shade=(function(){var enabled=false,holder=$('').css({position:'absolute',zIndex:240,opacity:0}),shades={top:createShade(),left:createShade().height(boundy),right:createShade().height(boundy),bottom:createShade()};function resizeShades(w,h){shades.left.css({height:px(h)});shades.right.css({height:px(h)});}
+function updateAuto()
+{return updateShade(Coords.getFixed());}
+function updateShade(c)
+{shades.top.css({left:px(c.x),width:px(c.w),height:px(c.y)});shades.bottom.css({top:px(c.y2),left:px(c.x),width:px(c.w),height:px(boundy-c.y2)});shades.right.css({left:px(c.x2),width:px(boundx-c.x2)});shades.left.css({width:px(c.x)});}
+function createShade(){return $('').css({position:'absolute',backgroundColor:options.shadeColor||options.bgColor}).appendTo(holder);}
+function enableShade(){if(!enabled){enabled=true;holder.insertBefore($img);updateAuto();Selection.setBgOpacity(1,0,1);$img2.hide();setBgColor(options.shadeColor||options.bgColor,1);if(Selection.isAwake())
+{setOpacity(options.bgOpacity,1);}
+else setOpacity(1,1);}}
+function setBgColor(color,now){colorChangeMacro(getShades(),color,now);}
+function disableShade(){if(enabled){holder.remove();$img2.show();enabled=false;if(Selection.isAwake()){Selection.setBgOpacity(options.bgOpacity,1,1);}else{Selection.setBgOpacity(1,1,1);Selection.disableHandles();}
+colorChangeMacro($div,0,1);}}
+function setOpacity(opacity,now){if(enabled){if(options.bgFade&&!now){holder.animate({opacity:1-opacity},{queue:false,duration:options.fadeTime});}
+else holder.css({opacity:1-opacity});}}
+function refreshAll(){options.shade?enableShade():disableShade();if(Selection.isAwake())setOpacity(options.bgOpacity);}
+function getShades(){return holder.children();}
+return{update:updateAuto,updateRaw:updateShade,getShades:getShades,setBgColor:setBgColor,enable:enableShade,disable:disableShade,resize:resizeShades,refresh:refreshAll,opacity:setOpacity};}());var Selection=(function(){var awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;function insertBorder(type)
+{var jq=$('').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;}
+function dragDiv(ord,zi)
+{var jq=$('').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi}).addClass('ord-'+ord);if(Touch.support){jq.bind('touchstart.jcrop',Touch.createDragger(ord));}
+$hdl_holder.append(jq);return jq;}
+function insertHandle(ord)
+{var hs=options.handleSize;return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).width(hs).height(hs).addClass(cssClass('handle'));}
+function insertDragbar(ord)
+{var s=options.handleSize,h=s,w=s,t=hhs,l=hhs;switch(ord){case'n':case's':w='100%';break;case'e':case'w':h='100%';break;}
+return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});}
+function createHandles(li)
+{var i;for(i=0;i').css({position:'fixed',left:'-120px',width:'12px'}),$keywrap=$('').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
+{if(options.keySupport){$keymgr.show();$keymgr.focus();}}
+function onBlur(e)
+{$keymgr.hide();}
+function doNudge(e,x,y)
+{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible(true);}
+e.preventDefault();e.stopPropagation();}
+function parseKey(e)
+{if(e.ctrlKey||e.metaKey){return true;}
+shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode){case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:if(options.allowSelect)Selection.release();break;case 9:return true;}
+return false;}
+if(options.keySupport){$keymgr.keydown(parseKey).blur(onBlur);if(ie6mode||!options.fixedSupport){$keymgr.css({position:'absolute',left:'-20px'});$keywrap.append($keymgr).insertBefore($img);}else{$keymgr.insertBefore($img);}}
+return{watchKeys:watchKeys};}());function setClass(cname)
+{$div.removeClass().addClass(cssClass('holder')).addClass(cname);}
+function animateTo(a,callback)
+{var x1=parseInt(a[0],10)/xscale,y1=parseInt(a[1],10)/yscale,x2=parseInt(a[2],10)/xscale,y2=parseInt(a[3],10)/yscale;if(animating){return;}
+var animto=Coords.flipCoords(x1,y1,x2,y2),c=Coords.getFixed(),initcr=[c.x,c.y,c.x2,c.y2],animat=initcr,interv=options.animationDelay,ix1=animto[0]-initcr[0],iy1=animto[1]-initcr[1],ix2=animto[2]-initcr[2],iy2=animto[3]-initcr[3],pcent=0,velocity=options.swingSpeed;x=animat[0];y=animat[1];x2=animat[2];y2=animat[3];Selection.animMode(true);var anim_timer;function queueAnimator(){window.setTimeout(animator,interv);}
+var animator=(function(){return function(){pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent>=99.8){pcent=100;}
+if(pcent<100){setSelectRaw(animat);queueAnimator();}else{Selection.done();if(typeof(callback)==='function'){callback.call(api);}}};}());queueAnimator();}
+function setSelect(rect)
+{setSelectRaw([parseInt(rect[0],10)/xscale,parseInt(rect[1],10)/yscale,parseInt(rect[2],10)/xscale,parseInt(rect[3],10)/yscale]);options.onSelect.call(api,unscale(Coords.getFixed()));Selection.enableHandles();}
+function setSelectRaw(l)
+{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();}
+function tellSelect()
+{return unscale(Coords.getFixed());}
+function tellScaled()
+{return Coords.getFixed();}
+function setOptionsNew(opt)
+{setOptions(opt);interfaceUpdate();}
+function disableCrop()
+{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');}
+function enableCrop()
+{options.disabled=false;interfaceUpdate();}
+function cancelCrop()
+{Selection.done();Tracker.activateHandlers(null,null);}
+function destroy()
+{$div.remove();$origimg.show();$(obj).removeData('Jcrop');}
+function setImage(src,callback)
+{Selection.release();disableCrop();var img=new Image();img.onload=function(){var iw=img.width;var ih=img.height;var bw=options.boxWidth;var bh=options.boxHeight;$img.width(iw).height(ih);$img.attr('src',src);$img2.attr('src',src);presize($img,bw,bh);boundx=$img.width();boundy=$img.height();$img2.width(boundx).height(boundy);$trk.width(boundx+(bound*2)).height(boundy+(bound*2));$div.width(boundx).height(boundy);Shade.resize(boundx,boundy);enableCrop();if(typeof(callback)==='function'){callback.call(api);}};img.src=src;}
+function colorChangeMacro($obj,color,now){var mycolor=color||options.bgColor;if(options.bgFade&&supportsColorFade()&&options.fadeTime&&!now){$obj.animate({backgroundColor:mycolor},{queue:false,duration:options.fadeTime});}else{$obj.css('backgroundColor',mycolor);}}
+function interfaceUpdate(alt)
+{if(options.allowResize){if(alt){Selection.enableOnly();}else{Selection.enableHandles();}}else{Selection.disableHandles();}
+Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');if(options.hasOwnProperty('trueSize')){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
+if(options.hasOwnProperty('setSelect')){setSelect(options.setSelect);Selection.done();delete(options.setSelect);}
+Shade.refresh();if(options.bgColor!=bgcolor){colorChangeMacro(options.shade?Shade.getShades():$div,options.shade?(options.shadeColor||options.bgColor):options.bgColor);bgcolor=options.bgColor;}
+if(bgopacity!=options.bgOpacity){bgopacity=options.bgOpacity;if(options.shade)Shade.refresh();else Selection.setBgOpacity(bgopacity);}
+xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if(options.hasOwnProperty('outerImage')){$img.attr('src',options.outerImage);delete(options.outerImage);}
+Selection.refresh();}
+if(Touch.support)$trk.bind('touchstart.jcrop',Touch.newSelection);$hdl_holder.hide();interfaceUpdate(true);var api={setImage:setImage,animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,setClass:setClass,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,release:Selection.release,destroy:destroy,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},getScaleFactor:function(){return[xscale,yscale];},ui:{holder:$div,selection:$sel}};if($.browser.msie){$div.bind('selectstart',function(){return false;});}
+$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options,callback)
+{var api;this.each(function(){if($(this).data('Jcrop')){if(options==='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
+else{if(this.tagName=='IMG')
+$.Jcrop.Loader(this,function(){$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);});else{$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);}}});return this;};$.Jcrop.Loader=function(imgobj,success,error){var $img=$(imgobj),img=$img[0];function completeCheck(){if(img.complete){$img.unbind('.jcloader');if($.isFunction(success))success.call(img);}
+else window.setTimeout(completeCheck,50);}
+$img.bind('load.jcloader',completeCheck).bind('error.jcloader',function(e){$img.unbind('.jcloader');if($.isFunction(error))error.call(img);});if(img.complete&&$.isFunction(success)){$img.unbind('.jcloader');success.call(img);}};$.Jcrop.defaults={allowSelect:true,allowMove:true,allowResize:true,trackDocument:true,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:0.6,bgFade:false,borderOpacity:0.4,handleOpacity:0.5,handleSize:7,handleOffset:5,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,fixedSupport:true,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}};}(jQuery));
\ No newline at end of file
diff --git a/js/jquery.jec-1.3.3.js b/js/jquery.jec-1.3.3.js
new file mode 100644
index 00000000..a0367dac
--- /dev/null
+++ b/js/jquery.jec-1.3.3.js
@@ -0,0 +1,863 @@
+/**
+ * jQuery jEC (jQuery Editable Combobox) 1.3.3
+ * http://code.google.com/p/jquery-jec
+ *
+ * Copyright (c) 2008-2009 Lukasz Rajchel (lukasz@rajchel.pl | http://rajchel.pl)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * Documentation : http://code.google.com/p/jquery-jec/wiki/Documentation
+ * Changelog : http://code.google.com/p/jquery-jec/wiki/Changelog
+ *
+ * Contributors : Lukasz Rajchel, Artem Orlov
+ */
+
+/*jslint maxerr: 50, indent: 4, maxlen: 120*/
+/*global Array, Math, String, clearInterval, document, jQuery, setInterval*/
+/*members ':', Handle, Remove, Set, acceptedKeys, addClass, all, append, appendTo, array, attr, before, bind,
+blinkingCursor, blinkingCursorInterval, blur, bool, browser, ceil, change, charCode, classes, clearCursor, click, css,
+cursorState, data, destroy, disable, each, editable, enable, eq, expr, extend, filter, find, floor, fn, focus,
+focusOnNewOption, fromCharCode, get, getId, handleCursor, ignoredKeys, ignoreOptGroups, inArray, init, initJS, integer,
+isArray, isPlainObject, jEC, jECTimer, jec, jecKill, jecOff, jecOn, jecPref, jecValue, keyCode, keyDown, keyPress,
+keyRange, keyUp, keys, length, max, maxLength, min, msie, object, openedState, optionClasses, optionStyles, parent,
+position, pref, prop, push, random, remove, removeAttr, removeClass, removeData, removeProp, safari, setEditableOption,
+styles, substring, text, trigger, triggerChangeEvent, unbind, uneditable, useExistingOptions, val, value,
+valueIsEditable, which*/
+(function ($) {
+ 'use strict';
+
+ $.jEC = (function () {
+ var pluginClass = 'jecEditableOption', cursorClass = 'hasCursor', options = {}, values = {}, lastKeyCode,
+ defaults, Validators, EventHandlers, Combobox, activeCombobox;
+
+ if ($.fn.prop === undefined) {
+ $.fn.extend({
+ 'prop': function (key, valueSet) {
+ if (valueSet) {
+ $(this).attr(key, key);
+ } else {
+ $(this).removeAttr(key);
+ }
+ },
+ 'removeProp': function (key) {
+ $(this).removeAttr(key);
+ }
+ });
+ }
+
+ defaults = {
+ position: 0,
+ ignoreOptGroups: false,
+ maxLength: 255,
+ classes: [],
+ styles: {},
+ optionClasses: [],
+ optionStyles: {},
+ triggerChangeEvent: false,
+ focusOnNewOption: false,
+ useExistingOptions: false,
+ blinkingCursor: false,
+ blinkingCursorInterval: 1000,
+ ignoredKeys: [],
+ acceptedKeys: [[32, 126], [191, 382]]
+ };
+
+ Validators = (function () {
+ return {
+ integer: function (value) {
+ return typeof value === 'number' && Math.ceil(value) === Math.floor(value);
+ },
+
+ keyRange: function (value) {
+ var min, max;
+ if ($.isPlainObject(value)) {
+ min = value.min;
+ max = value.max;
+ } else if ($.isArray(value) && value.length === 2) {
+ min = value[0];
+ max = value[1];
+ }
+ return Validators.integer(min) && Validators.integer(max) && min <= max;
+ }
+ };
+ }());
+
+ EventHandlers = (function () {
+ var getKeyCode;
+
+ getKeyCode = function (event) {
+ var charCode = event.charCode;
+ if (charCode !== undefined && charCode !== 0) {
+ return charCode;
+ } else {
+ return event.keyCode;
+ }
+ };
+
+ return {
+ // focus event handler
+ // enables blinking cursor
+ focus: function () {
+ var opt = options[Combobox.getId($(this))];
+ if (opt.blinkingCursor && $.jECTimer === undefined) {
+ activeCombobox = $(this);
+ $.jECTimer = setInterval($.jEC.handleCursor, opt.blinkingCursorInterval);
+ }
+ },
+
+ // blur event handler
+ // disables blinking cursor
+ blur: function () {
+ if ($.jECTimer !== undefined) {
+ clearInterval($.jECTimer);
+ $.jECTimer = undefined;
+ activeCombobox = undefined;
+ Combobox.clearCursor($(this));
+ }
+ Combobox.openedState($(this), false);
+ },
+
+ // keydown event handler
+ // handles keys pressed on select (backspace and delete must be handled
+ // in keydown event in order to work in IE)
+ keyDown: function (event) {
+ var keyCode = getKeyCode(event), option, value;
+
+ lastKeyCode = keyCode;
+
+ switch (keyCode) {
+ case 8: // backspace
+ case 46: // delete
+ option = $(this).find('option.' + pluginClass);
+ if (option.val().length >= 1) {
+ value = option.text().substring(0, option.text().length - 1);
+ option.val(value).text(value).prop('selected', true);
+ }
+ return (keyCode !== 8);
+ default:
+ break;
+ }
+ },
+
+ // keypress event handler
+ // handles the rest of the keys (keypress event gives more informations
+ // about pressed keys)
+ keyPress: function (event) {
+ var keyCode = getKeyCode(event), opt = options[Combobox.getId($(this))],
+ option, value, specialKeys, exit = false, text;
+
+ Combobox.clearCursor($(this));
+ if (keyCode !== 9 && keyCode !== 13 && keyCode !== 27) {
+ // special keys codes
+ specialKeys = [37, 38, 39, 40, 46];
+ // handle special keys
+ $.each(specialKeys, function (i, val) {
+ if (keyCode === val && keyCode === lastKeyCode) {
+ exit = true;
+ }
+ });
+
+ // don't handle ignored keys
+ if (!exit && $.inArray(keyCode, opt.ignoredKeys) === -1) {
+ // remove selection from all options
+ $(this).find('option:selected').removeProp('selected');
+
+ if ($.inArray(keyCode, opt.acceptedKeys) !== -1) {
+ option = $(this).find('option.' + pluginClass);
+ text = option.text();
+
+ if (text.length < opt.maxLength) {
+ value = text + String.fromCharCode(getKeyCode(event));
+ option.val(value).text(value);
+ }
+
+ option.prop('selected', true);
+ }
+ }
+
+ return false;
+ }
+ },
+
+ keyUp: function () {
+ var opt = options[Combobox.getId($(this))];
+ if (opt.triggerChangeEvent) {
+ $(this).trigger('change');
+ }
+ },
+
+ // change event handler
+ // handles editable option changing based on a pre-existing values
+ change: function () {
+ var opt = options[Combobox.getId($(this))];
+ if (opt.useExistingOptions) {
+ Combobox.setEditableOption($(this));
+ }
+ },
+
+ click: function () {
+ if (!$.browser.safari) {
+ Combobox.openedState($(this), !Combobox.openedState($(this)));
+ }
+ }
+ };
+ }());
+
+ // Combobox
+ Combobox = (function () {
+ var Parameters, EditableOption, generateId, setup;
+
+ // validates and set combobox parameters
+ Parameters = (function () {
+ var Set, Remove, Handle;
+
+ Set = (function () {
+ var parseKeys, Handles;
+
+ parseKeys = function (value) {
+ var keys = [];
+ if ($.isArray(value)) {
+ $.each(value, function (i, val) {
+ var j, min, max;
+ if (Validators.keyRange(val)) {
+ if ($.isArray(val)) {
+ min = val[0];
+ max = val[1];
+ } else {
+ min = val.min;
+ max = val.max;
+ }
+ for (j = min; j <= max; j += 1) {
+ keys.push(j);
+ }
+ } else if (typeof val === 'number' && Validators.integer(val)) {
+ keys.push(val);
+ }
+ });
+ }
+ return keys;
+ };
+
+ Handles = (function () {
+ return {
+ integer: function (elem, name, value) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && Validators.integer(value)) {
+ opt[name] = value;
+ return true;
+ }
+ return false;
+ },
+ bool: function (elem, name, value) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && typeof value === 'boolean') {
+ opt[name] = value;
+ return true;
+ }
+ return false;
+ },
+ array: function (elem, name, value) {
+ if (typeof value === 'string') {
+ value = [value];
+ }
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && $.isArray(value)) {
+ opt[name] = value;
+ return true;
+ }
+ return false;
+ },
+ object: function (elem, name, value) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && value !== null && $.isPlainObject(value)) {
+ opt[name] = value;
+ }
+ },
+ keys: function (elem, name, value) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && $.isArray(value)) {
+ opt[name] = parseKeys(value);
+ }
+ }
+ };
+ }());
+
+ return {
+ position: function (elem, value) {
+ if (Handles.integer(elem, 'position', value)) {
+ var id = Combobox.getId(elem), opt = options[id], optionsCount;
+ optionsCount =
+ elem.find('option:not(.' + pluginClass + ')').length;
+ if (value > optionsCount) {
+ opt.position = optionsCount;
+ }
+ }
+ },
+
+ ignoreOptGroups: function (elem, value) {
+ Handles.bool(elem, 'ignoreOptGroups', value);
+ },
+
+ maxLength: function (elem, value) {
+ if (Handles.integer(elem, 'maxLength', value)) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (value < 0 || value > 255) {
+ opt.maxLength = 255;
+ }
+ }
+ },
+
+ classes: function (elem, value) {
+ Handles.array(elem, 'classes', value);
+ },
+
+ optionClasses: function (elem, value) {
+ Handles.array(elem, 'optionClasses', value);
+ },
+
+ styles: function (elem, value) {
+ Handles.object(elem, 'styles', value);
+ },
+
+ optionStyles: function (elem, value) {
+ Handles.object(elem, 'optionStyles', value);
+ },
+
+ triggerChangeEvent: function (elem, value) {
+ Handles.bool(elem, 'triggerChangeEvent', value);
+ },
+
+ focusOnNewOption: function (elem, value) {
+ Handles.bool(elem, 'focusOnNewOption', value);
+ },
+
+ useExistingOptions: function (elem, value) {
+ Handles.bool(elem, 'useExistingOptions', value);
+ },
+
+ blinkingCursor: function (elem, value) {
+ Handles.bool(elem, 'blinkingCursor', value);
+ },
+
+ blinkingCursorInterval: function (elem, value) {
+ Handles.integer(elem, 'blinkingCursorInterval', value);
+ },
+
+ ignoredKeys: function (elem, value) {
+ Handles.keys(elem, 'ignoredKeys', value);
+ },
+
+ acceptedKeys: function (elem, value) {
+ Handles.keys(elem, 'acceptedKeys', value);
+ }
+ };
+ }());
+
+ Remove = (function () {
+ var removeClasses, removeStyles;
+
+ removeClasses = function (elem, classes) {
+ $.each(classes, function (i, val) {
+ elem.removeClass(val);
+ });
+ };
+
+ removeStyles = function (elem, styles) {
+ $.each(styles, function (key) {
+ elem.css(key, '');
+ });
+ };
+
+ return {
+ classes: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ removeClasses(elem, opt.classes);
+ }
+ },
+
+ optionClasses: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ removeClasses(elem.find('option.' + pluginClass),
+ opt.optionClasses);
+ }
+ },
+
+ styles: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ removeStyles(elem, opt.styles);
+ }
+ },
+
+ optionStyles: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ removeStyles(elem.find('option.' + pluginClass),
+ opt.optionStyles);
+ }
+ },
+
+ all: function (elem) {
+ Remove.classes(elem);
+ Remove.optionClasses(elem);
+ Remove.styles(elem);
+ Remove.optionStyles(elem);
+ }
+ };
+ }());
+
+ Handle = (function () {
+ var setClasses, setStyles;
+
+ setClasses = function (elem, classes) {
+ $.each(classes, function (i, val) {
+ elem.addClass(String(val));
+ });
+ };
+
+ setStyles = function (elem, styles) {
+ $.each(styles, function (key, val) {
+ elem.css(key, val);
+ });
+ };
+
+ return {
+ position: function (elem) {
+ var opt = options[Combobox.getId(elem)], option, uneditableOptions, container;
+ option = elem.find('option.' + pluginClass);
+
+ uneditableOptions = elem.find('option:not(.' + pluginClass + ')');
+ if (opt.position < uneditableOptions.length) {
+ container = uneditableOptions.eq(opt.position);
+
+ if (!opt.ignoreOptGroups && container.parent('optgroup').length > 0) {
+ uneditableOptions.eq(opt.position).parent().before(option);
+ } else {
+ uneditableOptions.eq(opt.position).before(option);
+ }
+ } else {
+ elem.append(option);
+ }
+ },
+
+ classes: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ setClasses(elem, opt.classes);
+ }
+ },
+
+ optionClasses: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ setClasses(elem.find('option.' + pluginClass), opt.optionClasses);
+ }
+ },
+
+ styles: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ setStyles(elem, opt.styles);
+ }
+ },
+
+ optionStyles: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined) {
+ setStyles(elem.find('option.' + pluginClass), opt.optionStyles);
+ }
+ },
+
+ focusOnNewOption: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && opt.focusOnNewOption) {
+ elem.find(':not(option.' + pluginClass + ')').removeProp('selected');
+ elem.find('option.' + pluginClass).prop('selected', true);
+ }
+ },
+
+ useExistingOptions: function (elem) {
+ var id = Combobox.getId(elem), opt = options[id];
+ if (opt !== undefined && opt.useExistingOptions) {
+ Combobox.setEditableOption(elem);
+ }
+ },
+
+ all: function (elem) {
+ Handle.position(elem);
+ Handle.classes(elem);
+ Handle.optionClasses(elem);
+ Handle.styles(elem);
+ Handle.optionStyles(elem);
+ Handle.focusOnNewOption(elem);
+ Handle.useExistingOptions(elem);
+ }
+ };
+ }());
+
+ return {
+ Set: Set,
+ Remove: Remove,
+ Handle: Handle
+ };
+ }());
+
+ EditableOption = (function () {
+ return {
+ init: function (elem) {
+ if (!elem.find('option.' + pluginClass).length) {
+ var editableOption = $('